[5/30] A rework of Label Creator code
[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 autouse 'Data::Dumper' => qw(Dumper);
24
25 use C4::Context;
26 use C4::Debug;
27
28 BEGIN {
29     use version; our $VERSION = qv('1.0.0_1');
30     use base qw(Exporter);
31     our @EXPORT_OK = qw(get_all_templates
32                         get_all_layouts
33                         get_all_profiles
34                         get_batch_summary
35                         get_label_summary
36                         get_barcode_types
37                         get_label_types
38                         get_font_types
39                         get_text_justification_types
40                         get_label_output_formats
41                         get_column_names
42                         get_table_names
43                         get_unit_values
44                         html_table
45     );
46 }
47
48 #=head2 C4::Labels::Lib::_SELECT()
49 #
50 #    This function returns a recordset upon success and 1 upon failure. Errors are logged to the Apache log.
51 #
52 #    examples:
53 #
54 #        my $field_value = _SELECT(field_name, table_name, condition);
55 #
56 #=cut
57
58 sub _SELECT {
59     my @params = @_;
60     my $query = "SELECT $params[0] FROM $params[1]";
61     $params[2] ? $query .= " WHERE $params[2];" : $query .= ';';
62     my $sth = C4::Context->dbh->prepare($query);
63 #    $sth->{'TraceLevel'} = 3;
64     $sth->execute();
65     if ($sth->err) {
66         warn sprintf('Database returned the following error: %s', $sth->errstr);
67         return 1;
68     }
69     my $record_set = [];
70     while (my $row = $sth->fetchrow_hashref()) {
71         push(@$record_set, $row);
72     }
73     return $record_set;
74 }
75
76 my $barcode_types = [
77     {type => 'CODE39',          name => 'Code 39',              desc => 'Translates the characters 0-9, A-Z, \'-\', \'*\', \'+\', \'$\', \'%\', \'/\', \'.\' and \' \' to a barcode pattern.',                                  selected => 0},
78     {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},
79     {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},
80     {type => 'COOP2OF5',        name => 'COOP2of5',             desc => 'Creates COOP2of5 barcodes from a string consisting of the numeric characters 0-9',                                                                     selected => 0},
81 #    {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},
82 #    {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},
83 #    {type => 'IATA2of5',        name => 'IATA2of5',             desc => 'Creates IATA2of5 barcodes from a string consisting of the numeric characters 0-9',                                                                     selected => 0},
84     {type => 'INDUSTRIAL2OF5',  name => 'Industrial2of5',       desc => 'Creates Industrial2of5 barcodes from a string consisting of the numeric characters 0-9',                                                               selected => 0},
85 #    {type => 'ITF',             name => 'Interleaved2of5',      desc => 'Translates the characters 0-9 to a barcodes. These barcodes could also be called 'Interleaved2of5'.',                                                  selected => 0},
86 #    {type => 'MATRIX2OF5',      name => 'Matrix2of5',           desc => 'Creates Matrix2of5 barcodes from a string consisting of the numeric characters 0-9',                                                                   selected => 0},
87 #    {type => 'NW7',             name => 'NW7',                  desc => 'Creates a NW7 barcodes from a string consisting of the numeric characters 0-9',                                                                        selected => 0},
88 #    {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},
89 #    {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},
90 ];
91
92 my $label_types = [
93     {type => 'BIB',     name => 'Biblio',               desc => 'Only the bibliographic data is printed.',                              selected => 0},
94     {type => 'BARBIB',  name => 'Barcode/Biblio',       desc => 'Barcode proceeds bibliographic data.',                                 selected => 0},
95     {type => 'BIBBAR',  name => 'Biblio/Barcode',       desc => 'Bibliographic data proceeds barcode.',                                 selected => 0},
96     {type => 'ALT',     name => 'Alternating',          desc => 'Barcode and bibliographic data are printed on alternating labels.',    selected => 0},
97     {type => 'BAR',     name => 'Barcode',              desc => 'Only the barcode is printed.',                                         selected => 0},
98 ];
99
100 my $font_types = [
101     {type => 'TR',      name => 'Times-Roman',                  selected => 0},
102     {type => 'TB',      name => 'Times-Bold',                   selected => 0},
103     {type => 'TI',      name => 'Times-Italic',                 selected => 0},
104     {type => 'TBI',     name => 'Times-Bold-Italic',            selected => 0},
105     {type => 'C',       name => 'Courier',                      selected => 0},
106     {type => 'CB',      name => 'Courier-Bold',                 selected => 0},
107     {type => 'CO',      name => 'Courier-Oblique',              selected => 0},
108     {type => 'CBO',     name => 'Courier-Bold-Oblique',         selected => 0},
109     {type => 'H',       name => 'Helvetica',                    selected => 0},
110     {type => 'HB',      name => 'Helvetica-Bold',               selected => 0},
111     {type => 'HBO',     name => 'Helvetica-Bold-Oblique',       selected => 0},
112 ];
113
114 my $text_justification_types = [
115     {type => 'L',       name => 'Left',                         selected => 0},
116     {type => 'C',       name => 'Center',                       selected => 0},
117     {type => 'R',       name => 'Right',                        selected => 0},
118 #    {type => 'F',       name => 'Full',                         selected => 0},
119 ];
120
121 my $unit_values = [
122     {type       => 'POINT',      desc    => 'PostScript Points',  value   => 1,                 selected => 0},
123     {type       => 'AGATE',      desc    => 'Adobe Agates',       value   => 5.1428571,         selected => 0},
124     {type       => 'INCH',       desc    => 'US Inches',          value   => 72,                selected => 0},
125     {type       => 'MM',         desc    => 'SI Millimeters',     value   => 2.83464567,        selected => 0},
126     {type       => 'CM',         desc    => 'SI Centimeters',     value   => 28.3464567,        selected => 0},
127 ];
128
129 my $label_output_formats = [
130     {type       => 'pdf',       desc    => 'PDF File'},
131     {type       => 'csv',       desc    => 'CSV File'},
132 ];
133
134 =head2 C4::Labels::Lib::get_all_templates()
135
136     This function returns a reference to a hash containing all templates upon success and 1 upon failure. Errors are logged to the Apache log.
137
138     examples:
139
140         my $templates = get_all_templates();
141
142 =cut
143
144 sub get_all_templates {
145     my %params = @_;
146     my @templates = ();
147     my $query = "SELECT " . ($params{'field_list'} ? $params{'field_list'} : '*') . " FROM labels_templates";
148     $query .= ($params{'filter'} ? " WHERE $params{'filter'};" : ';');
149     my $sth = C4::Context->dbh->prepare($query);
150     $sth->execute();
151     if ($sth->err) {
152         warn sprintf('Database returned the following error: %s', $sth->errstr);
153         return -1;
154     }
155     ADD_TEMPLATES:
156     while (my $template = $sth->fetchrow_hashref) {
157         push(@templates, $template);
158     }
159     return \@templates;
160 }
161
162 =head2 C4::Labels::Lib::get_all_layouts()
163
164     This function returns a reference to a hash containing all layouts upon success and 1 upon failure. Errors are logged to the Apache log.
165
166     examples:
167
168         my $layouts = get_all_layouts();
169
170 =cut
171
172 sub get_all_layouts {
173     my %params = @_;
174     my @layouts = ();
175     #my $query = "SELECT * FROM labels_layouts;";
176     my $query = "SELECT " . ($params{'field_list'} ? $params{'field_list'} : '*') . " FROM labels_layouts";
177     $query .= ($params{'filter'} ? " WHERE $params{'filter'};" : ';');
178     my $sth = C4::Context->dbh->prepare($query);
179     $sth->execute();
180     if ($sth->err) {
181         warn sprintf('Database returned the following error: %s', $sth->errstr);
182         return -1;
183     }
184     ADD_LAYOUTS:
185     while (my $layout = $sth->fetchrow_hashref) {
186         push(@layouts, $layout);
187     }
188     return \@layouts;
189 }
190
191 =head2 C4::Labels::Lib::get_all_profiles()
192
193     This function returns an arrayref whose elements are hashes containing all profiles upon success and 1 upon failure. Errors are logged
194     to the Apache log. Two parameters are accepted. The first limits the field(s) returned. This parameter should be string of comma separted
195     fields. ie. "field_1, field_2, ...field_n" The second limits the records returned based on a string containing a valud SQL 'WHERE' filter.
196     NOTE: Do not pass in the keyword 'WHERE.'
197
198     examples:
199
200         my $profiles = get_all_profiles();
201         my $profiles = get_all_profiles(field_list => field_list, filter => filter_string);
202
203 =cut
204
205 sub get_all_profiles {
206     my %params = @_;
207     my @profiles = ();
208     my $query = "SELECT " . ($params{'field_list'} ? $params{'field_list'} : '*') . " FROM printers_profile";
209     $query .= ($params{'filter'} ? " WHERE $params{'filter'};" : ';');
210     my $sth = C4::Context->dbh->prepare($query);
211 #    $sth->{'TraceLevel'} = 3 if $debug;
212     $sth->execute();
213     if ($sth->err) {
214         warn sprintf('Database returned the following error: %s', $sth->errstr);
215         return -1;
216     }
217     ADD_PROFILES:
218     while (my $profile = $sth->fetchrow_hashref) {
219         push(@profiles, $profile);
220     }
221     return \@profiles;
222 }
223
224 =head2 C4::Labels::Lib::get_batch_summary()
225
226     This function returns an arrayref whose elements are hashes containing the batch_ids of current batches along with the item count
227     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.
228     One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
229
230     NOTE: Do not pass in the keyword 'WHERE.'
231
232     examples:
233
234         my $batches = get_batch_summary();
235         my $batches = get_batch_summary(filter => filter_string);
236
237 =cut
238
239 sub get_batch_summary {
240     my %params = @_;
241     my @batches = ();
242     my $query = "SELECT DISTINCT batch_id FROM labels_batches";
243     $query .= ($params{'filter'} ? " WHERE $params{'filter'};" : ';');
244     my $sth = C4::Context->dbh->prepare($query);
245 #    $sth->{'TraceLevel'} = 3;
246     $sth->execute();
247     if ($sth->err) {
248         warn sprintf('Database returned the following error on attempted SELECT: %s', $sth->errstr);
249         return -1;
250     }
251     ADD_BATCHES:
252     while (my $batch = $sth->fetchrow_hashref) {
253         my $query = "SELECT count(item_number) FROM labels_batches WHERE batch_id=?;";
254         my $sth1 = C4::Context->dbh->prepare($query);
255         $sth1->execute($batch->{'batch_id'});
256         if ($sth1->err) {
257             warn sprintf('Database returned the following error on attempted SELECT count: %s', $sth1->errstr);
258             return -1;
259         }
260         my $count = $sth1->fetchrow_arrayref;
261         $batch->{'_item_count'} = @$count[0];
262         push(@batches, $batch);
263     }
264     return \@batches;
265 }
266
267 =head2 C4::Labels::Lib::get_label_summary()
268
269     This function returns an arrayref whose elements are hashes containing the label_ids of current labels along with the item count
270     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.
271     One parameter is accepted which limits the records returned based on a string containing a valud SQL 'WHERE' filter.
272
273     NOTE: Do not pass in the keyword 'WHERE.'
274
275     examples:
276
277         my $labels = get_label_summary();
278         my $labels = get_label_summary(items => @item_list);
279
280 =cut
281
282 sub get_label_summary {
283     my %params = @_;
284     my $label_number = 0;
285     my @label_summaries = ();
286     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=?;";
287     my $sth = C4::Context->dbh->prepare($query);
288     foreach my $item (@{$params{'items'}}) {
289         $label_number++;
290         $sth->execute($item->{'item_number'}, $params{'batch_id'});
291         if ($sth->err) {
292             warn sprintf('Database returned the following error on attempted SELECT: %s', $sth->errstr);
293             return -1;
294         }
295         my $record = $sth->fetchrow_hashref;
296         my $label_summary->{'_label_number'} = $label_number;
297         $record->{'author'} =~ s/[^\.|\w]$// if $record->{'author'};  # strip off ugly trailing chars... but not periods or word chars
298         $record->{'title'} =~ s/\W*$//;  # strip off ugly trailing chars
299         # FIXME contructing staff interface URLs should be done *much* higher up the stack - for the most part, C4 module code
300         # should not know that it's part of a web app
301         $record->{'title'} = '<a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=' . $record->{'biblionumber'} . '"> ' . $record->{'title'} . '</a>';
302         $label_summary->{'_summary'} = $record->{'title'} . " | " . ($record->{'author'} ? $record->{'author'} : 'N/A');
303         $label_summary->{'_item_type'} = $record->{'itemtype'};
304         $label_summary->{'_barcode'} = $record->{'barcode'};
305         $label_summary->{'_item_number'} = $item->{'item_number'};
306         $label_summary->{'_label_id'} = $item->{'label_id'};
307         push (@label_summaries, $label_summary);
308     }
309     return \@label_summaries;
310 }
311
312 =head2 C4::Labels::Lib::get_barcode_types()
313
314     This function returns a reference to an array of hashes containing all barcode types along with their name and description.
315
316     examples:
317
318         my $barcode_types = get_barcode_types();
319
320 =cut
321
322 sub get_barcode_types {
323     return $barcode_types;
324 }
325
326 =head2 C4::Labels::Lib::get_label_types()
327
328     This function returns a reference to an array of hashes containing all label types along with their name and description.
329
330     examples:
331
332         my $label_types = get_label_types();
333
334 =cut
335
336 sub get_label_types {
337     return $label_types;
338 }
339
340 =head2 C4::Labels::Lib::get_font_types()
341
342     This function returns a reference to an array of hashes containing all font types along with their name and description.
343
344     examples:
345
346         my $font_types = get_font_types();
347
348 =cut
349
350 sub get_font_types {
351     return $font_types;
352 }
353
354 =head2 C4::Labels::Lib::get_text_justification_types()
355
356     This function returns a reference to an array of hashes containing all text justification types along with their name and description.
357
358     examples:
359
360         my $text_justification_types = get_text_justification_types();
361
362 =cut
363
364 sub get_text_justification_types {
365     return $text_justification_types;
366 }
367
368 =head2 C4::Labels::Lib::get_unit_values()
369
370     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.
371     There are 72 PS points to the inch.
372
373     examples:
374
375         my $unit_values = get_unit_values();
376
377 =cut
378
379 sub get_unit_values {
380     return $unit_values;
381 }
382
383 =head2 C4::Labels::Lib::get_label_output_formats()
384
385     This function returns a reference to an array of hashes containing all label output formats along with their description.
386
387     examples:
388
389         my $label_output_formats = get_label_output_formats();
390
391 =cut
392
393 sub get_label_output_formats {
394     return $label_output_formats;
395 }
396
397 =head2 C4::Labels::Lib::get_column_names($table_name)
398
399 Return an arrayref of an array containing the column names of the supplied table.
400
401 =cut
402
403 sub get_column_names {
404     my $table = shift;
405     my $dbh = C4::Context->dbh();
406     my $column_names = [];
407     my $sth = $dbh->column_info(undef,undef,$table,'%');
408     while (my $info = $sth->fetchrow_hashref()){
409         $$column_names[$info->{'ORDINAL_POSITION'}] = $info->{'COLUMN_NAME'};
410     }
411     return $column_names;
412 }
413
414 =head2 C4::Labels::Lib::get_table_names($search_term)
415
416 Return an arrayref of an array containing the table names which contain the supplied search term.
417
418 =cut
419
420 sub get_table_names {
421     my $search_term = shift;
422     my $dbh = C4::Context->dbh();
423     my $table_names = [];
424     my $sth = $dbh->table_info(undef,undef,"%$search_term%");
425     while (my $info = $sth->fetchrow_hashref()){
426         push (@$table_names, $info->{'TABLE_NAME'});
427     }
428     return $table_names;
429 }
430
431 =head2 C4::Labels::Lib::html_table()
432
433     This function returns an arrayref of an array of hashes contianing the supplied data formatted suitably to
434     be passed off as a T::P template parameter and used to build an html table.
435
436     examples:
437
438         my $table = html_table(header_fields, array_of_row_data);
439
440 =cut
441
442 sub html_table {
443     my $headers = shift;
444     my $data = shift;
445     return undef if scalar(@$data) == 0;      # no need to generate a table if there is not data to display
446     my $table = [];
447     my $fields = [];
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