Bug 21281: Correct t/db_dependent/Creators/Lib.t failures
[koha.git] / C4 / Creators / Lib.pm
1 package C4::Creators::Lib;
2
3 # Copyright 2009 Foundations Bible College.
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use strict;
21 use warnings;
22
23 use autouse 'Data::Dumper' => qw(Dumper);
24
25 use C4::Context;
26 use C4::Debug;
27
28 BEGIN {
29     use base qw(Exporter);
30     our @EXPORT = qw(get_all_templates
31                      get_all_layouts
32                      get_all_profiles
33                      get_all_image_names
34                      get_batch_summary
35                      get_label_summary
36                      get_card_summary
37                      get_barcode_types
38                      get_label_types
39                      get_font_types
40                      get_text_justification_types
41                      get_output_formats
42                      get_table_names
43                      get_unit_values
44                      html_table
45     );
46 }
47
48 =head1 NAME
49
50 C4::Creators::Lib
51
52 =cut
53
54 =head1 FUNCTIONS
55
56 =cut
57
58 #=head2 C4::Creators::Lib::_SELECT()
59 #
60 #    This function returns a recordset upon success and 1 upon failure. Errors are logged to the Apache log.
61 #
62 #    examples:
63 #
64 #        my $field_value = _SELECT(field_name, table_name, condition);
65 #
66 #=cut
67
68 sub _SELECT {
69     my @params = @_;
70     my $fields_list = $params[0];
71     if (index($fields_list, ' ')==-1 && index($fields_list,',')==-1 && $fields_list ne '*') {
72         $fields_list = "`$fields_list`";
73     }
74     my $query = "SELECT $fields_list FROM $params[1]";
75     $params[2] ? $query .= " WHERE $params[2];" : $query .= ';';
76     my $sth = C4::Context->dbh->prepare($query);
77 #    $sth->{'TraceLevel'} = 3;
78     $sth->execute();
79     if ($sth->err) {
80         warn sprintf('Database returned the following error: %s', $sth->errstr);
81         return 1;
82     }
83     my $record_set = [];
84     while (my $row = $sth->fetchrow_hashref()) {
85         push(@$record_set, $row);
86     }
87     return $record_set;
88 }
89
90 my $barcode_types = [
91     {type => 'CODE39',          name => 'Code 39',              desc => 'Translates the characters 0-9, A-Z, \'-\', \'*\', \'+\', \'$\', \'%\', \'/\', \'.\' and \' \' to a barcode pattern.',                                  selected => 0},
92     {type => 'CODE39MOD',       name => 'Code 39 + Modulo43',   desc => 'Translates the characters 0-9, A-Z, \'-\', \'*\', \'+\', \'$\', \'%\', \'/\', \'.\' and \' \' to a barcode pattern. Encodes Mod 43 checksum.',         selected => 0},
93     {type => 'CODE39MOD10',     name => 'Code 39 + Modulo10',   desc => 'Translates the characters 0-9, A-Z, \'-\', \'*\', \'+\', \'$\', \'%\', \'/\', \'.\' and \' \' to a barcode pattern. Encodes Mod 10 checksum.',         selected => 0},
94     {type => 'COOP2OF5',        name => 'COOP2of5',             desc => 'Creates COOP2of5 barcodes from a string consisting of the numeric characters 0-9',                                                                     selected => 0},
95     {type => 'EAN13',           name => 'EAN13',                desc => 'Creates EAN13 barcodes from a string of 12 or 13 digits. The check number (the 13:th digit) is calculated if not supplied.',                           selected => 0},
96 #    {type => 'EAN8',            name => 'EAN8',                 desc => 'Translates a string of 7 or 8 digits to EAN8 barcodes. The check number (the 8:th digit) is calculated if not supplied.',                              selected => 0},
97 #    {type => 'IATA2of5',        name => 'IATA2of5',             desc => 'Creates IATA2of5 barcodes from a string consisting of the numeric characters 0-9',                                                                     selected => 0},
98     {type => 'INDUSTRIAL2OF5',  name => 'Industrial2of5',       desc => 'Creates Industrial2of5 barcodes from a string consisting of the numeric characters 0-9',                                                               selected => 0},
99 #    {type => 'ITF',             name => 'Interleaved2of5',      desc => 'Translates the characters 0-9 to a barcodes. These barcodes could also be called 'Interleaved2of5'.',                                                  selected => 0},
100 #    {type => 'MATRIX2OF5',      name => 'Matrix2of5',           desc => 'Creates Matrix2of5 barcodes from a string consisting of the numeric characters 0-9',                                                                   selected => 0},
101 #    {type => 'NW7',             name => 'NW7',                  desc => 'Creates a NW7 barcodes from a string consisting of the numeric characters 0-9',                                                                        selected => 0},
102 #    {type => 'UPCA',            name => 'UPCA',                 desc => 'Translates a string of 11 or 12 digits to UPCA barcodes. The check number (the 12:th digit) is calculated if not supplied.',                           selected => 0},
103 #    {type => 'UPCE',            name => 'UPCE',                 desc => 'Translates a string of 6, 7 or 8 digits to UPCE barcodes. If the string is 6 digits long, '0' is added first in the string. The check number (the 8:th digit) is calculated if not supplied.',                                 selected => 0},
104 ];
105
106 my $label_types = [
107     {type => 'BIB',     name => 'Biblio',               desc => 'Only the bibliographic data is printed.',                              selected => 0},
108     {type => 'BARBIB',  name => 'Barcode/Biblio',       desc => 'Barcode proceeds bibliographic data.',                                 selected => 0},
109     {type => 'BIBBAR',  name => 'Biblio/Barcode',       desc => 'Bibliographic data proceeds barcode.',                                 selected => 0},
110     {type => 'ALT',     name => 'Alternating',          desc => 'Barcode and bibliographic data are printed on alternating labels.',    selected => 0},
111     {type => 'BAR',     name => 'Barcode',              desc => 'Only the barcode is printed.',                                         selected => 0},
112 ];
113
114 my $font_types = [
115     {type => 'TR',      name => 'Times-Roman',                  selected => 0},
116     {type => 'TB',      name => 'Times-Bold',                   selected => 0},
117     {type => 'TI',      name => 'Times-Italic',                 selected => 0},
118     {type => 'TBI',     name => 'Times-Bold-Italic',            selected => 0},
119     {type => 'C',       name => 'Courier',                      selected => 0},
120     {type => 'CB',      name => 'Courier-Bold',                 selected => 0},
121     {type => 'CO',      name => 'Courier-Oblique',              selected => 0},
122     {type => 'CBO',     name => 'Courier-Bold-Oblique',         selected => 0},
123     {type => 'H',       name => 'Helvetica',                    selected => 0},
124     {type => 'HO',      name => 'Helvetica-Oblique',            selected => 0},
125     {type => 'HB',      name => 'Helvetica-Bold',               selected => 0},
126     {type => 'HBO',     name => 'Helvetica-Bold-Oblique',       selected => 0},
127 ];
128
129 my $text_justification_types = [
130     {type => 'L',       name => 'Left',                         selected => 0},
131     {type => 'C',       name => 'Center',                       selected => 0},
132     {type => 'R',       name => 'Right',                        selected => 0},
133 #    {type => 'F',       name => 'Full',                         selected => 0},
134 ];
135
136 my $unit_values = [
137     {type       => 'POINT',      desc    => 'PostScript Points',  value   => 1,                 selected => 0},
138     {type       => 'AGATE',      desc    => 'Adobe Agates',       value   => 5.1428571,         selected => 0},
139     {type       => 'INCH',       desc    => 'US Inches',          value   => 72,                selected => 0},
140     {type       => 'MM',         desc    => 'SI Millimeters',     value   => 2.83464567,        selected => 0},
141     {type       => 'CM',         desc    => 'SI Centimeters',     value   => 28.3464567,        selected => 0},
142 ];
143
144 my $output_formats = [
145     {type       => 'pdf',       desc    => 'PDF File'},
146     {type       => 'csv',       desc    => 'CSV File'},
147 ];
148
149 sub _build_query {
150     my ( $params, $table ) = @_;
151     my @fields = exists $params->{fields} ? @{ $params->{fields} } : ();
152     my @fields2 = ();
153     foreach my $field_name (@fields) {
154         if (index($field_name,' ')==-1 && $field_name ne '*') {
155             push @fields2, "`$field_name`";
156         } else {
157             push @fields2, $field_name;
158         }
159     }
160     @fields = @fields2;
161     my $query = "SELECT " . ( @fields ? join(', ', @fields ) : '*' ) . " FROM $table";
162     my @where_args;
163     if ( exists $params->{filters} ) {
164         $query .= ' WHERE 1 ';
165         while ( my ( $field, $values ) = each %{ $params->{filters} } ) {
166             if ( ref( $values ) ) {
167                 $query .= " AND `$field` IN ( " . ( ('?,') x (@$values-1) ) . "? ) "; # a comma separates elements in a list...
168                 push @where_args, @$values;
169             } else {
170                 $query .= " AND `$field` = ? ";
171                 push @where_args, $values;
172             }
173         }
174     }
175     $query .= (exists $params->{orderby} ? " ORDER BY $params->{orderby} " : '');
176     return ( $query, @where_args );
177 }
178
179 =head2 C4::Creators::Lib::get_all_templates()
180
181   my $templates = get_all_templates();
182
183 This function returns a reference to a hash containing all templates upon success and 1 upon failure. Errors are logged to the Apache log.
184
185 =cut
186
187 sub get_all_templates {
188     my ( $params ) = @_;
189     my @templates = ();
190     my ( $query, @where_args ) = _build_query( $params, 'creator_templates' );
191     my $sth = C4::Context->dbh->prepare($query);
192     $sth->execute( @where_args );
193     if ($sth->err) {
194         warn sprintf('Database returned the following error: %s', $sth->errstr);
195         return -1;
196     }
197     ADD_TEMPLATES:
198     while (my $template = $sth->fetchrow_hashref) {
199         push(@templates, $template);
200     }
201     return \@templates;
202 }
203
204 =head2 C4::Creators::Lib::get_all_layouts()
205
206   my $layouts = get_all_layouts();
207
208 This function returns a reference to a hash containing all layouts upon success and 1 upon failure. Errors are logged to the Apache log.
209
210 =cut
211
212 sub get_all_layouts {
213     my ( $params ) = @_;
214     my @layouts = ();
215     my ( $query, @where_args ) = _build_query( $params, 'creator_layouts' );
216     my $sth = C4::Context->dbh->prepare($query);
217     $sth->execute( @where_args );
218     if ($sth->err) {
219         warn sprintf('Database returned the following error: %s', $sth->errstr);
220         return -1;
221     }
222     ADD_LAYOUTS:
223     while (my $layout = $sth->fetchrow_hashref) {
224         push(@layouts, $layout);
225     }
226     return \@layouts;
227 }
228
229 =head2 C4::Creators::Lib::get_all_profiles()
230
231   my $profiles = get_all_profiles();
232
233   my $profiles = get_all_profiles({ fields => [@fields], filters => { filters => [$value1, $value2] } });
234
235 This function returns an arrayref whose elements are hashes containing all profiles upon success and 1 upon failure. Errors are logged
236 to the Apache log. Two parameters are accepted. The first limits the field(s) returned. This parameter should be string of comma separted
237 fields. ie. "field_1, field_2, ...field_n" The second limits the records returned based on a string containing a valud SQL 'WHERE' filter.
238
239 NOTE: Do not pass in the keyword 'WHERE.'
240
241 =cut
242
243 sub get_all_profiles {
244     my ( $params ) = @_;
245     my @profiles = ();
246     my ( $query, @where_args ) = _build_query( $params, 'printers_profile' );
247     my $sth = C4::Context->dbh->prepare($query);
248     $sth->execute( @where_args );
249     if ($sth->err) {
250         warn sprintf('Database returned the following error: %s', $sth->errstr);
251         return -1;
252     }
253     ADD_PROFILES:
254     while (my $profile = $sth->fetchrow_hashref) {
255         push(@profiles, $profile);
256     }
257     return \@profiles;
258 }
259
260 =head2 C4::Creators::Lib::get_all_image_names()
261
262 =cut
263
264 sub get_all_image_names {
265     my $image_names = [];
266     my $query = "SELECT image_name FROM creator_images";
267     my $sth = C4::Context->dbh->prepare($query);
268 #    $sth->{'TraceLevel'} = 3 if $debug;
269     $sth->execute();
270     if ($sth->err) {
271         warn sprintf('Database returned the following error: %s', $sth->errstr);
272         return -1;
273     }
274     grep {push @$image_names, {type => $$_[0], name => $$_[0], selected => 0}} @{$sth->fetchall_arrayref([0])};
275     return $image_names;
276 }
277
278 =head2 C4::Creators::Lib::get_batch_summary()
279
280   my $batches = get_batch_summary();
281
282   my $batches = get_batch_summary(filter => filter_string);
283
284 This function returns an arrayref whose elements are hashes containing the batch_ids of current batches along with the item count
285 for each batch upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log.
286 One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
287
288 NOTE: Do not pass in the keyword 'WHERE.'
289
290 =cut
291
292 sub get_batch_summary {
293     my ( $params ) = @_;
294     my @batches = ();
295     $params->{fields} = ['batch_id', 'count(batch_id) as _item_count'];
296     my ( $query, @where_args ) = _build_query( $params, 'creator_batches' );
297     $query .= " GROUP BY batch_id";
298     my $sth = C4::Context->dbh->prepare($query);
299     $sth->execute( @where_args );
300     if ($sth->err) {
301         warn sprintf('Database returned the following error on attempted SELECT: %s', $sth->errstr);
302         return -1;
303     }
304     while (my $batch = $sth->fetchrow_hashref) {
305         push(@batches, $batch);
306     }
307     return \@batches;
308 }
309
310 =head2 C4::Creators::Lib::get_label_summary()
311
312   my $labels = get_label_summary();
313
314   my $labels = get_label_summary(items => @item_list);
315
316 This function returns an arrayref whose elements are hashes containing the label_ids of current labels along with the item count
317 for each label upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log.
318 One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
319
320 NOTE: Do not pass in the keyword 'WHERE.'
321
322 =cut
323
324 sub get_label_summary {
325     my %params = @_;
326     my $label_number = 0;
327     my @label_summaries = ();
328     my $query = "     SELECT b.title, b.author, bi.itemtype, i.barcode, i.itemcallnumber, i.biblionumber, i.itype
329                       FROM creator_batches AS c LEFT JOIN items AS i ON (c.item_number=i.itemnumber)
330                       LEFT JOIN biblioitems AS bi ON (i.biblioitemnumber=bi.biblioitemnumber)
331                       LEFT JOIN biblio AS b ON (bi.biblionumber=b.biblionumber)
332                       WHERE itemnumber=? AND batch_id=?;
333                   ";
334     my $sth = C4::Context->dbh->prepare($query);
335     foreach my $item (@{$params{'items'}}) {
336         $label_number++;
337         $sth->execute($item->{'item_number'}, $params{'batch_id'});
338         if ($sth->err) {
339             warn sprintf('Database returned the following error on attempted SELECT: %s', $sth->errstr);
340             return -1;
341         }
342         my $record = $sth->fetchrow_hashref;
343         my $label_summary;
344         $label_summary->{'_label_number'} = $label_number;
345         $record->{'author'} =~ s/[^\.|\w]$// if $record->{'author'};  # strip off ugly trailing chars... but not periods or word chars
346         $record->{'title'} =~ s/\W*$//;  # strip off ugly trailing chars
347         # FIXME contructing staff interface URLs should be done *much* higher up the stack - for the most part, C4 module code
348         # should not know that it's part of a web app
349         $record->{'title'} = '<a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=' . $record->{'biblionumber'} . '"> ' . $record->{'title'} . '</a>';
350         $label_summary->{'_summary'} = $record->{'title'} . " | " . ($record->{'author'} ? $record->{'author'} : 'N/A');
351         $label_summary->{'_item_type'} = C4::Context->preference("item-level_itypes") ? $record->{'itype'} : $record->{'itemtype'};
352         $label_summary->{'_barcode'} = $record->{'barcode'};
353         $label_summary->{'_item_number'} = $item->{'item_number'};
354         $label_summary->{'_item_cn'} = $record->{'itemcallnumber'};
355         $label_summary->{'_label_id'} = $item->{'label_id'};
356         push (@label_summaries, $label_summary);
357     }
358     return \@label_summaries;
359 }
360
361 =head2 C4::Creators::Lib::get_card_summary()
362
363   my $cards = get_card_summary();
364
365   my $cards = get_card_summary(items => @item_list);
366
367 This function returns an arrayref whose elements are hashes containing the label_ids of current cards along with the item count
368 for each card upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the Apache log.
369 One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
370
371 NOTE: Do not pass in the keyword 'WHERE.'
372
373 =cut
374
375 sub get_card_summary {
376     my %params = @_;
377     my $card_number = 0;
378     my @card_summaries = ();
379     my $query = "SELECT CONCAT_WS(', ', surname, firstname) AS name, cardnumber FROM borrowers WHERE borrowernumber=?;";
380     my $sth = C4::Context->dbh->prepare($query);
381     foreach my $item (@{$params{'items'}}) {
382         $card_number++;
383         $sth->execute($item->{'borrower_number'});
384         if ($sth->err) {
385             warn sprintf('Database returned the following error on attempted SELECT: %s', $sth->errstr);
386             return -1;
387         }
388         my $record = $sth->fetchrow_hashref;
389         my $card_summary->{'_card_number'} = $card_number;
390         $card_summary->{'_summary'} = $record->{'name'};
391         $card_summary->{'borrowernumber'} = $item->{'borrower_number'};
392         $card_summary->{'_label_id'} = $item->{'label_id'};
393         push (@card_summaries, $card_summary);
394     }
395     return \@card_summaries;
396 }
397
398 =head2 C4::Creators::Lib::get_barcode_types()
399
400   my $barcode_types = get_barcode_types();
401
402 This function returns a reference to an array of hashes containing all barcode types along with their name and description.
403
404 =cut
405
406 sub get_barcode_types {
407     return $barcode_types;
408 }
409
410 =head2 C4::Creators::Lib::get_label_types()
411
412   my $label_types = get_label_types();
413
414 This function returns a reference to an array of hashes containing all label types along with their name and description.
415
416 =cut
417
418 sub get_label_types {
419     return $label_types;
420 }
421
422 =head2 C4::Creators::Lib::get_font_types()
423
424   my $font_types = get_font_types();
425
426 This function returns a reference to an array of hashes containing all font types along with their name and description.
427
428 =cut
429
430 sub get_font_types {
431     return $font_types;
432 }
433
434 =head2 C4::Creators::Lib::get_text_justification_types()
435
436   my $text_justification_types = get_text_justification_types();
437
438 This function returns a reference to an array of hashes containing all text justification types along with their name and description.
439
440 =cut
441
442 sub get_text_justification_types {
443     return $text_justification_types;
444 }
445
446 =head2 C4::Creators::Lib::get_unit_values()
447
448   my $unit_values = get_unit_values();
449
450 This function returns a reference to an array of  hashes containing all unit types along with their description and multiplier.
451 NOTE: All units are relative to a PostScript Point.
452 There are 72 PS points to the inch.
453
454 =cut
455
456 sub get_unit_values {
457     return $unit_values;
458 }
459
460 =head2 C4::Creators::Lib::get_output_formats()
461
462   my $label_output_formats = get_output_formats();
463
464 This function returns a reference to an array of hashes containing all label output formats along with their description.
465
466 =cut
467
468 sub get_output_formats {
469     return $output_formats;
470 }
471
472
473 =head2 C4::Creators::Lib::get_table_names($search_term)
474
475 Return an arrayref of an array containing the table names which contain the supplied search term.
476
477 =cut
478
479 sub get_table_names {
480     my $search_term = shift;
481     my $dbh = C4::Context->dbh();
482     my $table_names = [];
483     my $sth = $dbh->table_info(undef,undef,"%$search_term%");
484     while (my $info = $sth->fetchrow_hashref()){
485         push (@$table_names, $info->{'TABLE_NAME'});
486     }
487     return $table_names;
488 }
489
490 =head2 C4::Creators::Lib::html_table()
491
492 This function returns an arrayref of an array of hashes contianing the supplied data formatted suitably to
493 be passed off as a template parameter and used to build an html table.
494
495    my $table = html_table(header_fields, array_of_row_data);
496    $template->param(
497        table_loop => $table,
498    );
499
500     html example:
501
502         <table>
503             [% FOREACH table_loo IN table_loop %]
504                 [% IF ( table_loo.header_fields ) %]
505                     <tr>
506                         [% FOREACH header_field IN table_loo.header_fields %]
507                             <th>[% header_field.field_label %]</th>
508                         [% END %]
509                     </tr>
510                 [% ELSE %]
511                     <tr>
512                         [% FOREACH text_field IN table_loo.text_fields %]
513                             [% IF ( text_field.select_field ) %]
514                                 <td><input type="checkbox" name="action" value="[% text_field.field_value %]"></td>
515                             [% ELSE %]
516                                 <td>[% text_field.field_value %]</td>
517                             [% END %]
518                         [% END %]
519                     </tr>
520                 [% END %]
521             [% END %]
522         </table>
523
524 =cut
525
526 sub html_table {
527     my $headers = shift;
528     my $data = shift;
529     return undef if scalar(@$data) == 0;      # no need to generate a table if there is not data to display
530     my $table = [];
531     my $fields = [];
532     my @table_columns = ();
533     my ($row_index, $col_index) = (0,0);
534     my $cols = 0;       # number of columns to wrap on
535     my $field_count = 0;
536     my $select_value = undef;
537     my $link_field = undef;
538     POPULATE_HEADER:
539     foreach my $header (@$headers) {
540         my @key = keys %$header;
541         if ($key[0] eq 'select' ) {
542             push (@table_columns, $key[0]);
543             $$fields[$col_index] = {hidden => 0, select_field => 0, field_name => ($key[0]), field_label => $header->{$key[0]}{'label'}};
544             # do special formatting stuff....
545             $select_value = $header->{$key[0]}{'value'};
546         }
547         else {
548             # do special formatting stuff....
549             $link_field->{$key[0]} = ($header->{$key[0]}{'link_field'} == 1 ? 1 : 0);
550             push (@table_columns, $key[0]);
551             $$fields[$col_index] = {hidden => 0, select_field => 0, field_name => ($key[0]), field_label => $header->{$key[0]}{'label'}};
552         }
553         $field_count++;
554         $col_index++;
555     }
556     $$table[$row_index] = {header_fields => $fields};
557     $cols = $col_index;
558     $field_count *= scalar(@$data);     # total fields to be displayed in the table
559     $col_index = 0;
560     $row_index++;
561     $fields = [];
562     POPULATE_TABLE:
563     foreach my $db_row (@$data) {
564         POPULATE_ROW:
565         foreach my $table_column (@table_columns) {
566             if (grep {$table_column eq $_} keys %$db_row) {
567                 $$fields[$col_index] = {hidden => 0, link_field => $link_field->{$table_column}, select_field => 0, field_name => ($table_column . "_tbl"), field_value => $db_row->{$table_column}};
568                 $col_index++;
569                 next POPULATE_ROW;
570             }
571             elsif ($table_column =~ m/^_((.*)_(.*$))/) {   # this a special case
572                 my $table_name = get_table_names('creator_'.$2); #Bug 14143 fix to remove ambiguity with table 'club_template_enrollment_fields'
573                 my $record_set = _SELECT($1, @$table_name[0], $2 . "_id = " . $db_row->{$2 . "_id"});
574                 $$fields[$col_index] = {hidden => 0, link_field => $link_field->{$table_column}, select_field => 0, field_name => ($table_column . "_tbl"), field_value => $$record_set[0]{$1}};
575                 $col_index++;
576                 next POPULATE_ROW;
577             }
578             elsif ($table_column eq 'select' ) {
579                 $$fields[$col_index] = {hidden => 0, select_field => 1, field_name => 'select', field_value => $db_row->{$select_value}};
580             }
581         }
582         $$table[$row_index] = {text_fields => $fields};
583         $col_index = 0;
584         $row_index++;
585         $fields = [];
586     }
587     return $table;
588 }
589
590 1;
591 __END__
592
593 =head1 AUTHOR
594
595 Chris Nighswonger <cnighswonger AT foundations DOT edu>
596
597 =cut