[35/40] Work on C4::Labels tests and various bugfixs resulting
[koha.git] / C4 / Labels / Lib.pm
1 package C4::Labels::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 under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA  02111-1307 USA
19
20 use strict;
21 use warnings;
22
23 use Sys::Syslog qw(syslog);
24 use Data::Dumper;
25
26 use C4::Context;
27 use C4::Debug;
28
29 BEGIN {
30     use version; our $VERSION = qv('1.0.0_1');
31     use base qw(Exporter);
32     our @EXPORT_OK = qw(get_all_templates
33                         get_all_layouts
34                         get_all_profiles
35                         get_batch_summary
36                         get_label_summary
37                         get_barcode_types
38                         get_label_types
39                         get_font_types
40                         get_text_justification_types
41                         get_label_output_formats
42                         get_column_names
43                         get_table_names
44                         get_unit_values
45                         html_table
46     );
47 }
48
49 #=head2 C4::Labels::Lib::_SELECT()
50 #
51 #    This function returns a recordset upon success and 1 upon failure. Errors are logged to the syslog.
52 #
53 #    examples:
54 #
55 #        my $field_value = _SELECT(field_name, table_name, condition);
56 #
57 #=cut
58
59 sub _SELECT {
60     my @params = @_;
61     my $query = "SELECT $params[0] FROM $params[1]";
62     $params[2] ? $query .= " WHERE $params[2];" : $query .= ';';
63     my $sth = C4::Context->dbh->prepare($query);
64 #    $sth->{'TraceLevel'} = 3;
65     $sth->execute();
66     if ($sth->err) {
67         syslog("LOG_ERR", "C4::Labels::Lib::get_single_field_value : Database returned the following error: %s", $sth->errstr);
68         return 1;
69     }
70     my $record_set = [];
71     while (my $row = $sth->fetchrow_hashref()) {
72         push(@$record_set, $row);
73     }
74     return $record_set;
75 }
76
77 my $barcode_types = [
78     {type => 'CODE39',          name => 'Code 39',              desc => 'Translates the characters 0-9, A-Z, \'-\', \'*\', \'+\', \'$\', \'%\', \'/\', \'.\' and \' \' to a barcode pattern.',                                  selected => 0},
79     {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},
80     {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},
81     {type => 'COOP2OF5',        name => 'COOP2of5',             desc => 'Creates COOP2of5 barcodes from a string consisting of the numeric characters 0-9',                                                                     selected => 0},
82 #    {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},
83 #    {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},
84 #    {type => 'IATA2of5',        name => 'IATA2of5',             desc => 'Creates IATA2of5 barcodes from a string consisting of the numeric characters 0-9',                                                                     selected => 0},
85     {type => 'INDUSTRIAL2OF5',  name => 'Industrial2of5',       desc => 'Creates Industrial2of5 barcodes from a string consisting of the numeric characters 0-9',                                                               selected => 0},
86 #    {type => 'ITF',             name => 'Interleaved2of5',      desc => 'Translates the characters 0-9 to a barcodes. These barcodes could also be called 'Interleaved2of5'.',                                                  selected => 0},
87 #    {type => 'MATRIX2OF5',      name => 'Matrix2of5',           desc => 'Creates Matrix2of5 barcodes from a string consisting of the numeric characters 0-9',                                                                   selected => 0},
88 #    {type => 'NW7',             name => 'NW7',                  desc => 'Creates a NW7 barcodes from a string consisting of the numeric characters 0-9',                                                                        selected => 0},
89 #    {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},
90 #    {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},
91 ];
92
93 my $label_types = [
94     {type => 'BIB',     name => 'Biblio',               desc => 'Only the bibliographic data is printed.',                              selected => 0},
95     {type => 'BARBIB',  name => 'Barcode/Biblio',       desc => 'Barcode proceeds bibliographic data.',                                 selected => 0},
96     {type => 'BIBBAR',  name => 'Biblio/Barcode',       desc => 'Bibliographic data proceeds barcode.',                                 selected => 0},
97     {type => 'ALT',     name => 'Alternating',          desc => 'Barcode and bibliographic data are printed on alternating labels.',    selected => 0},
98     {type => 'BAR',     name => 'Barcode',              desc => 'Only the barcode is printed.',                                         selected => 0},
99 ];
100
101 my $font_types = [
102     {type => 'TR',      name => 'Times-Roman',                  selected => 0},
103     {type => 'TB',      name => 'Times-Bold',                   selected => 0},
104     {type => 'TI',      name => 'Times-Italic',                 selected => 0},
105     {type => 'TBI',     name => 'Times-Bold-Italic',            selected => 0},
106     {type => 'C',       name => 'Courier',                      selected => 0},
107     {type => 'CB',      name => 'Courier-Bold',                 selected => 0},
108     {type => 'CO',      name => 'Courier-Oblique',              selected => 0},
109     {type => 'CBO',     name => 'Courier-Bold-Oblique',         selected => 0},
110     {type => 'H',       name => 'Helvetica',                    selected => 0},
111     {type => 'HB',      name => 'Helvetica-Bold',               selected => 0},
112     {type => 'HBO',     name => 'Helvetica-Bold-Oblique',       selected => 0},
113 ];
114
115 my $text_justification_types = [
116     {type => 'L',       name => 'Left',                         selected => 0},
117     {type => 'C',       name => 'Center',                       selected => 0},
118     {type => 'R',       name => 'Right',                        selected => 0},
119 #    {type => 'F',       name => 'Full',                         selected => 0},    
120 ];
121
122 my $unit_values = [
123     {type       => 'POINT',      desc    => 'PostScript Points',  value   => 1,                 selected => 0},
124     {type       => 'AGATE',      desc    => 'Adobe Agates',       value   => 5.1428571,         selected => 0},
125     {type       => 'INCH',       desc    => 'US Inches',          value   => 72,                selected => 0},
126     {type       => 'MM',         desc    => 'SI Millimeters',     value   => 2.83464567,        selected => 0},
127     {type       => 'CM',         desc    => 'SI Centimeters',     value   => 28.3464567,        selected => 0},
128 ];
129
130 my $label_output_formats = [
131     {type       => 'pdf',       desc    => 'PDF File'},
132     {type       => 'csv',       desc    => 'CSV File'},
133 ];
134
135 =head2 C4::Labels::Lib::get_all_templates()
136
137     This function returns a reference to a hash containing all templates upon success and 1 upon failure. Errors are logged to the syslog.
138
139     examples:
140
141         my $templates = get_all_templates();
142
143 =cut
144
145 sub get_all_templates {
146     my %params = @_;
147     my @templates = ();
148     my $query = "SELECT " . ($params{'field_list'} ? $params{'field_list'} : '*') . " FROM labels_templates";
149     $query .= ($params{'filter'} ? " WHERE $params{'filter'};" : ';');
150     my $sth = C4::Context->dbh->prepare($query);
151     $sth->execute();
152     if ($sth->err) {
153         syslog("LOG_ERR", "C4::Labels::Lib::get_all_templates : Database returned the following error: %s", $sth->errstr);
154         return -1;
155     }
156     ADD_TEMPLATES:
157     while (my $template = $sth->fetchrow_hashref) {
158         push(@templates, $template);
159     }
160     return \@templates;
161 }
162
163 =head2 C4::Labels::Lib::get_all_layouts()
164
165     This function returns a reference to a hash containing all layouts upon success and 1 upon failure. Errors are logged to the syslog.
166
167     examples:
168
169         my $layouts = get_all_layouts();
170
171 =cut
172
173 sub get_all_layouts {
174     my %params = @_;
175     my @layouts = ();
176     #my $query = "SELECT * FROM labels_layouts;";
177     my $query = "SELECT " . ($params{'field_list'} ? $params{'field_list'} : '*') . " FROM labels_layouts";
178     $query .= ($params{'filter'} ? " WHERE $params{'filter'};" : ';');
179     my $sth = C4::Context->dbh->prepare($query);
180     $sth->execute();
181     if ($sth->err) {
182         syslog("LOG_ERR", "C4::Labels::Lib::get_all_layouts : Database returned the following error: %s", $sth->errstr);
183         return -1;
184     }
185     ADD_LAYOUTS:
186     while (my $layout = $sth->fetchrow_hashref) {
187         push(@layouts, $layout);
188     }
189     return \@layouts;
190 }
191
192 =head2 C4::Labels::Lib::get_all_profiles()
193
194     This function returns an arrayref whose elements are hashes containing all profiles upon success and 1 upon failure. Errors are logged
195     to the syslog. Two parameters are accepted. The first limits the field(s) returned. This parameter should be string of comma separted
196     fields. ie. "field_1, field_2, ...field_n" The second limits the records returned based on a string containing a valud SQL 'WHERE' filter.
197     NOTE: Do not pass in the keyword 'WHERE.'
198
199     examples:
200
201         my $profiles = get_all_profiles();
202         my $profiles = get_all_profiles(field_list => field_list, filter => filter_string);
203
204 =cut
205
206 sub get_all_profiles {
207     my %params = @_;
208     my @profiles = ();
209     my $query = "SELECT " . ($params{'field_list'} ? $params{'field_list'} : '*') . " FROM printers_profile";
210     $query .= ($params{'filter'} ? " WHERE $params{'filter'};" : ';');
211     my $sth = C4::Context->dbh->prepare($query);
212 #    $sth->{'TraceLevel'} = 3 if $debug;
213     $sth->execute();
214     if ($sth->err) {
215         syslog("LOG_ERR", "C4::Labels::Lib::get_all_profiles : Database returned the following error: %s", $sth->errstr);
216         return -1;
217     }
218     ADD_PROFILES:
219     while (my $profile = $sth->fetchrow_hashref) {
220         push(@profiles, $profile);
221     }
222     return \@profiles;
223 }
224
225 =head2 C4::Labels::Lib::get_batch_summary()
226
227     This function returns an arrayref whose elements are hashes containing the batch_ids of current batches along with the item count
228     for each batch upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the syslog.
229     One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
230     
231     NOTE: Do not pass in the keyword 'WHERE.'
232
233     examples:
234
235         my $batches = get_batch_summary();
236         my $batches = get_batch_summary(filter => filter_string);
237
238 =cut
239
240 sub get_batch_summary {
241     my %params = @_;
242     my @batches = ();
243     my $query = "SELECT DISTINCT batch_id FROM labels_batches";
244     $query .= ($params{'filter'} ? " WHERE $params{'filter'};" : ';');
245     my $sth = C4::Context->dbh->prepare($query);
246 #    $sth->{'TraceLevel'} = 3;
247     $sth->execute();
248     if ($sth->err) {
249         syslog("LOG_ERR", "C4::Labels::Lib::get_batch_summary : Database returned the following error on attempted SELECT: %s", $sth->errstr);
250         return -1;
251     }
252     ADD_BATCHES:
253     while (my $batch = $sth->fetchrow_hashref) {
254         my $query = "SELECT count(item_number) FROM labels_batches WHERE batch_id=?;";
255         my $sth1 = C4::Context->dbh->prepare($query);
256         $sth1->execute($batch->{'batch_id'});
257         if ($sth1->err) {
258             syslog("LOG_ERR", "C4::Labels::Lib::get_batch_summary : Database returned the following error on attempted SELECT count: %s", $sth1->errstr);
259             return -1;
260         }
261         my $count = $sth1->fetchrow_arrayref;
262         $batch->{'_item_count'} = @$count[0];
263         push(@batches, $batch);
264     }
265     return \@batches;
266 }
267
268 =head2 C4::Labels::Lib::get_label_summary()
269
270     This function returns an arrayref whose elements are hashes containing the label_ids of current labels along with the item count
271     for each label upon success and 1 upon failure. Item counts are stored under the key '_item_count' Errors are logged to the syslog.
272     One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
273     
274     NOTE: Do not pass in the keyword 'WHERE.'
275
276     examples:
277
278         my $labels = get_label_summary();
279         my $labels = get_label_summary(items => @item_list);
280
281 =cut
282
283 sub get_label_summary {
284     my %params = @_;
285     my $label_number = 0;
286     my @label_summaries = ();
287     my $query = "SELECT b.title, b.author, bi.itemtype, i.barcode, i.biblionumber FROM biblio AS b, biblioitems AS bi ,items AS i, labels_batches AS l WHERE itemnumber=? AND l.item_number=i.itemnumber AND i.biblioitemnumber=bi.biblioitemnumber AND bi.biblionumber=b.biblionumber AND l.batch_id=?;";
288     my $sth = C4::Context->dbh->prepare($query);
289     foreach my $item (@{$params{'items'}}) {
290         $label_number++;
291         $sth->execute($item->{'item_number'}, $params{'batch_id'});
292         if ($sth->err) {
293             syslog("LOG_ERR", "C4::Labels::Lib::get_label_summary : Database returned the following error on attempted SELECT: %s", $sth->errstr);
294             return -1;
295         }
296         my $record = $sth->fetchrow_hashref;
297         my $label_summary->{'_label_number'} = $label_number;
298         $record->{'author'} =~ s/[^\.|\w]$// if $record->{'author'};  # strip off ugly trailing chars... but not periods or word chars
299         $record->{'title'} =~ s/\W*$//;  # strip off ugly trailing chars
300         $record->{'title'} = '<a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=' . $record->{'biblionumber'} . '"> ' . $record->{'title'} . '</a>';
301         $label_summary->{'_summary'} = $record->{'title'} . " | " . ($record->{'author'} ? $record->{'author'} : 'N/A');
302         $label_summary->{'_item_type'} = $record->{'itemtype'};
303         $label_summary->{'_barcode'} = $record->{'barcode'};
304         $label_summary->{'_item_number'} = $item->{'item_number'};
305         $label_summary->{'_label_id'} = $item->{'label_id'};
306         push (@label_summaries, $label_summary);
307     }
308     return \@label_summaries;
309 }
310
311 =head2 C4::Labels::Lib::get_barcode_types()
312
313     This function returns a reference to an array of hashes containing all barcode types along with their name and description.
314
315     examples:
316
317         my $barcode_types = get_barcode_types();
318
319 =cut
320
321 sub get_barcode_types {
322     return $barcode_types;
323 }
324
325 =head2 C4::Labels::Lib::get_label_types()
326
327     This function returns a reference to an array of hashes containing all label types along with their name and description.
328
329     examples:
330
331         my $label_types = get_label_types();
332
333 =cut
334
335 sub get_label_types {
336     return $label_types;
337 }
338
339 =head2 C4::Labels::Lib::get_font_types()
340
341     This function returns a reference to an array of hashes containing all font types along with their name and description.
342
343     examples:
344
345         my $font_types = get_font_types();
346
347 =cut
348
349 sub get_font_types {
350     return $font_types;
351 }
352
353 =head2 C4::Labels::Lib::get_text_justification_types()
354
355     This function returns a reference to an array of hashes containing all text justification types along with their name and description.
356
357     examples:
358
359         my $text_justification_types = get_text_justification_types();
360
361 =cut
362
363 sub get_text_justification_types {
364     return $text_justification_types;
365 }
366
367 =head2 C4::Labels::Lib::get_unit_values()
368
369     This function returns a reference to an array of  hashes containing all unit types along with their description and multiplier. NOTE: All units are relative to a PostScript Point.
370     There are 72 PS points to the inch.
371
372     examples:
373
374         my $unit_values = get_unit_values();
375
376 =cut
377
378 sub get_unit_values {
379     return $unit_values;
380 }
381
382 =head2 C4::Labels::Lib::get_label_output_formats()
383
384     This function returns a reference to an array of hashes containing all label output formats along with their description.
385
386     examples:
387
388         my $label_output_formats = get_label_output_formats();
389
390 =cut
391
392 sub get_label_output_formats {
393     return $label_output_formats;
394 }
395
396 =head2 C4::Labels::Lib::get_column_names($table_name)
397
398 Return an arrayref of an array containing the column names of the supplied table.
399
400 =cut
401
402 sub get_column_names {
403     my $table = shift;
404     my $dbh = C4::Context->dbh();
405     my $column_names = [];
406     my $sth = $dbh->column_info(undef,undef,$table,'%');
407     while (my $info = $sth->fetchrow_hashref()){
408         $$column_names[$info->{'ORDINAL_POSITION'}] = $info->{'COLUMN_NAME'};
409     }
410     return $column_names;
411 }
412
413 =head2 C4::Labels::Lib::get_table_names($search_term)
414
415 Return an arrayref of an array containing the table names which contain the supplied search term.
416
417 =cut
418
419 sub get_table_names {
420     my $search_term = shift;
421     my $dbh = C4::Context->dbh();
422     my $table_names = [];
423     my $sth = $dbh->table_info(undef,undef,"%$search_term%");
424     while (my $info = $sth->fetchrow_hashref()){
425         push (@$table_names, $info->{'TABLE_NAME'});
426     }
427     return $table_names;
428 }
429
430 =head2 C4::Labels::Lib::html_table()
431
432     This function returns an arrayref of an array of hashes contianing the supplied data formatted suitably to
433     be passed off as a T::P template parameter and used to build an html table.
434
435     examples:
436
437         my $table = html_table(header_fields, array_of_row_data);
438
439 =cut
440
441 sub html_table {
442     my $headers = shift;
443     my $data = shift;
444     return undef if scalar(@$data) == 0;      # no need to generate a table if there is not data to display
445     my $table = [];
446     my $fields = [];
447     my @headers = ();
448     my @table_columns = ();
449     my ($row_index, $col_index) = (0,0);
450     my $cols = 0;       # number of columns to wrap on
451     my $field_count = 0;
452     my $select_value = undef;
453     my $link_field = undef;
454     POPULATE_HEADER:
455     foreach my $header (@$headers) {
456         my @key = keys %$header;
457         if ($key[0] eq 'select' ) {
458             push (@table_columns, $key[0]);
459             $$fields[$col_index] = {hidden => 0, select_field => 0, field_name => ($key[0]), field_label => $header->{$key[0]}{'label'}};
460             # do special formatting stuff....
461             $select_value = $header->{$key[0]}{'value'};
462         }
463         else {
464             # do special formatting stuff....
465             $link_field->{$key[0]} = ($header->{$key[0]}{'link_field'} == 1 ? 1 : 0);
466             push (@table_columns, $key[0]);
467             $$fields[$col_index] = {hidden => 0, select_field => 0, field_name => ($key[0]), field_label => $header->{$key[0]}{'label'}};
468         }
469         $field_count++;
470         $col_index++;
471     }
472     $$table[$row_index] = {header_fields => $fields};
473     $cols = $col_index;
474     $field_count *= scalar(@$data);     # total fields to be displayed in the table
475     $col_index = 0;
476     $row_index++;
477     $fields = [];
478     POPULATE_TABLE:
479     foreach my $db_row (@$data) {
480         POPULATE_ROW:
481         foreach my $table_column (@table_columns) {
482             if (grep {$table_column eq $_} keys %$db_row) {
483                 $$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}};
484                 $col_index++;
485                 next POPULATE_ROW;
486             }
487             elsif ($table_column =~ m/^_((.*)_(.*$))/) {   # this a special case
488                 my $table_name = get_table_names($2);
489                 my $record_set = _SELECT($1, @$table_name[0], $2 . "_id = " . $db_row->{$2 . "_id"});
490                 $$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}};
491                 $col_index++;
492                 next POPULATE_ROW;
493             }
494             elsif ($table_column eq 'select' ) {
495                 $$fields[$col_index] = {hidden => 0, select_field => 1, field_name => 'select', field_value => $db_row->{$select_value}};
496             }
497         }
498         $$table[$row_index] = {text_fields => $fields};
499         $col_index = 0;
500         $row_index++;
501         $fields = [];
502     }
503     return $table;
504 }
505
506 1;
507 __END__
508
509 =head1 AUTHOR
510
511 Chris Nighswonger <cnighswonger AT foundations DOT edu>
512
513 =cut