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