more XHTML corrections for new circ reports
[koha.git] / C4 / Labels.pm
blob320fdcf4e2f6d6d010686720b80922e74c7baea3
1 package C4::Labels;
3 # Copyright 2006 Katipo Communications.
5 # This file is part of Koha.
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.
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.
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
20 use strict;
21 use vars qw($VERSION @ISA @EXPORT);
23 use PDF::Reuse;
24 use Text::Wrap;
25 use Algorithm::CheckDigits;
26 use C4::Members;
27 use C4::Branch;
28 # use Data::Dumper;
29 # use Smart::Comments;
31 BEGIN {
32 $VERSION = 0.03;
33 require Exporter;
34 @ISA = qw(Exporter);
35 @EXPORT = qw(
36 &get_label_options &GetLabelItems
37 &build_circ_barcode &draw_boundaries
38 &drawbox &GetActiveLabelTemplate
39 &GetAllLabelTemplates &DeleteTemplate
40 &GetSingleLabelTemplate &SaveTemplate
41 &CreateTemplate &SetActiveTemplate
42 &SaveConf &DrawSpineText &GetTextWrapCols
43 &GetUnitsValue &DrawBarcode &DrawPatronCardText
44 &get_printingtypes &GetPatronCardItems
45 &get_layouts
46 &get_barcode_types
47 &get_batches &delete_batch
48 &add_batch &printText
49 &GetItemFields
50 &get_text_fields
51 get_layout &save_layout &add_layout
52 &set_active_layout &by_order
53 &build_text_dropbox
54 &delete_layout &get_active_layout
55 &get_highest_batch
56 &deduplicate_batch
57 &GetAllPrinterProfiles &GetSinglePrinterProfile
58 &SaveProfile &CreateProfile &DeleteProfile
59 &GetAssociatedProfile &SetAssociatedProfile
63 my $DEBUG = 0;
65 =head1 NAME
67 C4::Labels - Functions for printing spine labels and barcodes in Koha
69 =head1 FUNCTIONS
71 =over 2
73 =item get_label_options;
75 $options = get_label_options()
77 Return a pointer on a hash list containing info from labels_conf table in Koha DB.
79 =cut
82 sub get_label_options {
83 my $dbh = C4::Context->dbh;
84 my $query2 = " SELECT * FROM labels_conf where active = 1";
85 my $sth = $dbh->prepare($query2);
86 $sth->execute();
87 my $conf_data = $sth->fetchrow_hashref;
88 $sth->finish;
89 return $conf_data;
92 sub get_layouts {
94 ## FIXME: this if/else could be compacted...
95 my $dbh = C4::Context->dbh;
96 my @data;
97 my $query = " Select * from labels_conf";
98 my $sth = $dbh->prepare($query);
99 $sth->execute();
100 my @resultsloop;
101 while ( my $data = $sth->fetchrow_hashref ) {
103 $data->{'fieldlist'} = get_text_fields( $data->{'id'} );
104 push( @resultsloop, $data );
106 $sth->finish;
108 # @resultsloop
110 return @resultsloop;
113 sub get_layout {
114 my ($layout_id) = @_;
115 my $dbh = C4::Context->dbh;
117 # get the actual items to be printed.
118 my $query = " Select * from labels_conf where id = ?";
119 my $sth = $dbh->prepare($query);
120 $sth->execute($layout_id);
121 my $data = $sth->fetchrow_hashref;
122 $sth->finish;
123 return $data;
126 sub get_active_layout {
127 my ($layout_id) = @_;
128 my $dbh = C4::Context->dbh;
130 # get the actual items to be printed.
131 my $query = " Select * from labels_conf where active = 1";
132 my $sth = $dbh->prepare($query);
133 $sth->execute();
134 my $data = $sth->fetchrow_hashref;
135 $sth->finish;
136 return $data;
139 sub delete_layout {
140 my ($layout_id) = @_;
141 my $dbh = C4::Context->dbh;
143 # get the actual items to be printed.
144 my $query = "delete from labels_conf where id = ?";
145 my $sth = $dbh->prepare($query);
146 $sth->execute($layout_id);
147 $sth->finish;
150 sub get_printingtypes {
151 my ($layout_id) = @_;
152 my @printtypes;
153 # FIXME: hard coded print types
154 push( @printtypes, { code => 'BAR', desc => "barcode" } );
155 push( @printtypes, { code => 'BIB', desc => "biblio" } );
156 push( @printtypes, { code => 'BARBIB', desc => "barcode / biblio" } );
157 push( @printtypes, { code => 'BIBBAR', desc => "biblio / barcode" } );
158 push( @printtypes, { code => 'ALT', desc => "alternating labels" } );
159 push( @printtypes, { code => 'PATCRD', desc => "patron cards" } );
161 my $conf = get_layout($layout_id);
162 my $active_printtype = $conf->{'printingtype'};
164 # lop thru layout, insert selected to hash
166 foreach my $printtype (@printtypes) {
167 if ( $printtype->{'code'} eq $active_printtype ) {
168 $printtype->{'active'} = 'MOO';
171 return @printtypes;
174 sub build_text_dropbox {
175 my ($order) = @_;
177 # my @fields = get_text_fields();
178 # my $field_count = scalar @fields;
179 my $field_count = 10; # <----------- FIXME hard coded
181 my @lines;
182 !$order
183 ? push( @lines, { num => '', selected => '1' } )
184 : push( @lines, { num => '' } );
185 for ( my $i = 1 ; $i <= $field_count ; $i++ ) {
186 my $line = { num => "$i" };
187 $line->{'selected'} = 1 if $i eq $order;
188 push( @lines, $line );
191 # add a blank row too
193 return @lines;
196 sub get_text_fields {
197 my ($layout_id, $sorttype) = @_;
199 my ( $a, $b, $c, $d, $e, $f, $g, $h, $i ,$j, $k );
201 my $sortorder = get_layout($layout_id);
203 # $sortorder
205 $a = {
206 code => 'itemtype',
207 desc => "Item Type",
208 order => $sortorder->{'itemtype'}
210 $b = {
211 code => 'dewey',
212 desc => "Dewey",
213 order => $sortorder->{'dewey'}
215 $c = { code => 'issn', desc => "ISSN",
216 order => $sortorder->{'issn'} };
217 $d = { code => 'isbn', desc => "ISBN",
218 order => $sortorder->{'isbn'} };
219 $e = {
220 code => 'class',
221 desc => "Classification",
222 order => $sortorder->{'class'}
224 $f = {
225 code => 'subclass',
226 desc => "Sub-Class",
227 order => $sortorder->{'subclass'}
229 $g = {
230 code => 'barcode',
231 desc => "Barcode",
232 order => $sortorder->{'barcode'}
234 $h =
235 { code => 'author', desc => "Author", order => $sortorder->{'author'} };
236 $i = { code => 'title', desc => "Title", order => $sortorder->{'title'} };
237 $j = { code => 'itemcallnumber', desc => "Call Number", order => $sortorder->{'itemcallnumber'} };
238 $k = { code => 'subtitle', desc => "Subtitle", order => $sortorder->{'subtitle'} };
240 my @text_fields = ( $a, $b, $c, $d, $e, $f, $g, $h, $i ,$j, $k );
242 my @new_fields;
243 foreach my $field (@text_fields) {
244 push( @new_fields, $field ) if $field->{'order'} > 0;
247 my @sorted_fields = sort by_order @new_fields;
248 my $active_fields;
249 foreach my $field (@sorted_fields) {
250 $sorttype eq 'codes' ? $active_fields .= "$field->{'code'} " :
251 $active_fields .= "$field->{'desc'} ";
253 return $active_fields;
257 sub by_order {
258 $$a{order} <=> $$b{order};
261 =head2 sub add_batch
262 =over 4
263 add_batch($batch_type,\@batch_list);
264 if $batch_list is supplied,
265 create a new batch with those items.
266 else, return the next available batch_id.
267 =return
268 =cut
269 sub add_batch {
270 my ( $batch_type,$batch_list ) = @_;
271 my $new_batch;
272 my $dbh = C4::Context->dbh;
273 my $q ="SELECT MAX(DISTINCT batch_id) FROM $batch_type";
274 my $sth = $dbh->prepare($q);
275 $sth->execute();
276 my ($batch_id) = $sth->fetchrow_array;
277 $sth->finish;
278 if($batch_id) {
279 $batch_id++;
280 } else {
281 $batch_id = 1;
283 # TODO: let this block use $batch_type
284 if(ref($batch_list) && ($batch_type eq 'labels') ) {
285 my $sth = $dbh->prepare("INSERT INTO labels (`batch_id`,`itemnumber`) VALUES (?,?)");
286 for my $item (@$batch_list) {
287 $sth->execute($batch_id,$item);
290 return $batch_id;
293 #FIXME: Needs to be ported to receive $batch_type
294 # ... this looks eerily like add_batch() ...
295 sub get_highest_batch {
296 my $new_batch;
297 my $dbh = C4::Context->dbh;
298 my $q =
299 "select distinct batch_id from labels order by batch_id desc limit 1";
300 my $sth = $dbh->prepare($q);
301 $sth->execute();
302 my $data = $sth->fetchrow_hashref;
303 $sth->finish;
305 if ( !$data->{'batch_id'} ) {
306 $new_batch = 1;
308 else {
309 $new_batch = $data->{'batch_id'};
312 return $new_batch;
316 sub get_batches {
317 my ( $batch_type ) = @_;
318 my $dbh = C4::Context->dbh;
319 my $q = "SELECT batch_id, COUNT(*) AS num FROM $batch_type GROUP BY batch_id";
320 my $sth = $dbh->prepare($q);
321 $sth->execute();
322 my @resultsloop;
323 while ( my $data = $sth->fetchrow_hashref ) {
324 push( @resultsloop, $data );
326 $sth->finish;
328 # Not sure why we are doing this rather than simply telling the user that no batches are currently defined.
329 # So I'm commenting this out and modifying label-manager.tmpl to properly inform the user as stated. -fbcit
330 # adding a dummy batch=1 value , if none exists in the db
331 # if ( !scalar(@resultsloop) ) {
332 # push( @resultsloop, { batch_id => '1' , num => '0' } );
334 return @resultsloop;
337 sub delete_batch {
338 my ($batch_id, $batch_type) = @_;
339 warn "Deleteing batch of type $batch_type";
340 my $dbh = C4::Context->dbh;
341 my $q = "DELETE FROM $batch_type WHERE batch_id = ?";
342 my $sth = $dbh->prepare($q);
343 $sth->execute($batch_id);
344 $sth->finish;
347 sub get_barcode_types {
348 my ($layout_id) = @_;
349 my $layout = get_layout($layout_id);
350 my $barcode = $layout->{'barcodetype'};
351 my @array;
353 push( @array, { code => 'CODE39', desc => 'Code 39' } );
354 push( @array, { code => 'CODE39MOD', desc => 'Code39 + Modulo43' } );
355 push( @array, { code => 'CODE39MOD10', desc => 'Code39 + Modulo10' } );
356 push( @array, { code => 'ITF', desc => 'Interleaved 2 of 5' } );
358 foreach my $line (@array) {
359 if ( $line->{'code'} eq $barcode ) {
360 $line->{'active'} = 1;
364 return @array;
367 sub GetUnitsValue {
368 my ($units) = @_;
369 my $unitvalue;
371 $unitvalue = '1' if ( $units eq 'POINT' );
372 $unitvalue = '2.83464567' if ( $units eq 'MM' );
373 $unitvalue = '28.3464567' if ( $units eq 'CM' );
374 $unitvalue = 72 if ( $units eq 'INCH' );
375 return $unitvalue;
378 sub GetTextWrapCols {
379 my ( $font, $fontsize, $label_width, $left_text_margin ) = @_;
380 my $string = '0';
381 my $strwidth;
382 my $count = 0;
383 # my $textlimit = $label_width - ($left_text_margin);
384 my $textlimit = $label_width - ( 2* $left_text_margin);
386 while ( $strwidth < $textlimit ) {
387 $strwidth = prStrWidth( $string, $font, $fontsize );
388 $string = $string . '0';
389 #warn "strwidth:$strwidth, textlimit:$textlimit, count:$count string:$string";
390 $count++;
392 return $count;
395 sub GetActiveLabelTemplate {
396 my $dbh = C4::Context->dbh;
397 my $query = " SELECT * FROM labels_templates where active = 1 limit 1";
398 my $sth = $dbh->prepare($query);
399 $sth->execute();
400 my $active_tmpl = $sth->fetchrow_hashref;
401 $sth->finish;
402 return $active_tmpl;
405 sub GetSingleLabelTemplate {
406 my ($tmpl_id) = @_;
407 my $dbh = C4::Context->dbh;
408 my $query = " SELECT * FROM labels_templates where tmpl_id = ?";
409 my $sth = $dbh->prepare($query);
410 $sth->execute($tmpl_id);
411 my $template = $sth->fetchrow_hashref;
412 $sth->finish;
413 return $template;
416 sub SetActiveTemplate {
418 my ($tmpl_id) = @_;
420 my $dbh = C4::Context->dbh;
421 my $query = " UPDATE labels_templates SET active = NULL";
422 my $sth = $dbh->prepare($query);
423 $sth->execute();
425 $query = "UPDATE labels_templates SET active = 1 WHERE tmpl_id = ?";
426 $sth = $dbh->prepare($query);
427 $sth->execute($tmpl_id);
428 $sth->finish;
431 sub set_active_layout {
433 my ($layout_id) = @_;
434 my $dbh = C4::Context->dbh;
435 my $query = " UPDATE labels_conf SET active = NULL";
436 my $sth = $dbh->prepare($query);
437 $sth->execute();
439 $query = "UPDATE labels_conf SET active = 1 WHERE id = ?";
440 $sth = $dbh->prepare($query);
441 $sth->execute($layout_id);
442 $sth->finish;
445 sub DeleteTemplate {
446 my ($tmpl_id) = @_;
447 my $dbh = C4::Context->dbh;
448 my $query = " DELETE FROM labels_templates where tmpl_id = ?";
449 my $sth = $dbh->prepare($query);
450 $sth->execute($tmpl_id);
451 $sth->finish;
454 sub SaveTemplate {
455 my (
456 $tmpl_id, $tmpl_code, $tmpl_desc, $page_width,
457 $page_height, $label_width, $label_height, $topmargin,
458 $leftmargin, $cols, $rows, $colgap,
459 $rowgap, $font, $fontsize, $units
460 ) = @_;
461 warn "Passed \$font:$font";
462 my $dbh = C4::Context->dbh;
463 my $query =
464 " UPDATE labels_templates SET tmpl_code=?, tmpl_desc=?, page_width=?,
465 page_height=?, label_width=?, label_height=?, topmargin=?,
466 leftmargin=?, cols=?, rows=?, colgap=?, rowgap=?, font=?, fontsize=?,
467 units=?
468 WHERE tmpl_id = ?";
470 my $sth = $dbh->prepare($query);
471 $sth->execute(
472 $tmpl_code, $tmpl_desc, $page_width, $page_height,
473 $label_width, $label_height, $topmargin, $leftmargin,
474 $cols, $rows, $colgap, $rowgap,
475 $font, $fontsize, $units, $tmpl_id
477 my $dberror = $sth->errstr;
478 $sth->finish;
479 return $dberror;
482 sub CreateTemplate {
483 my $tmpl_id;
484 my (
485 $tmpl_code, $tmpl_desc, $page_width, $page_height,
486 $label_width, $label_height, $topmargin, $leftmargin,
487 $cols, $rows, $colgap, $rowgap,
488 $font, $fontsize, $units
489 ) = @_;
491 my $dbh = C4::Context->dbh;
493 my $query = "INSERT INTO labels_templates (tmpl_code, tmpl_desc, page_width,
494 page_height, label_width, label_height, topmargin,
495 leftmargin, cols, rows, colgap, rowgap, font, fontsize, units)
496 VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
498 my $sth = $dbh->prepare($query);
499 $sth->execute(
500 $tmpl_code, $tmpl_desc, $page_width, $page_height,
501 $label_width, $label_height, $topmargin, $leftmargin,
502 $cols, $rows, $colgap, $rowgap,
503 $font, $fontsize, $units
505 my $dberror = $sth->errstr;
506 $sth->finish;
507 return $dberror;
510 sub GetAllLabelTemplates {
511 my $dbh = C4::Context->dbh;
513 # get the actual items to be printed.
514 my @data;
515 my $query = " Select * from labels_templates ";
516 my $sth = $dbh->prepare($query);
517 $sth->execute();
518 my @resultsloop;
519 while ( my $data = $sth->fetchrow_hashref ) {
520 push( @resultsloop, $data );
522 $sth->finish;
524 #warn Dumper @resultsloop;
525 return @resultsloop;
528 #sub SaveConf {
529 sub add_layout {
531 my (
532 $barcodetype, $title, $subtitle, $isbn, $issn,
533 $itemtype, $bcn, $dcn, $classif,
534 $subclass, $itemcallnumber, $author, $tmpl_id,
535 $printingtype, $guidebox, $startlabel, $layoutname
536 ) = @_;
538 my $dbh = C4::Context->dbh;
539 my $query2 = "update labels_conf set active = NULL";
540 my $sth2 = $dbh->prepare($query2);
541 $sth2->execute();
542 $query2 = "INSERT INTO labels_conf
543 ( barcodetype, title, subtitle, isbn,issn, itemtype, barcode,
544 dewey, class, subclass, itemcallnumber, author, printingtype,
545 guidebox, startlabel, layoutname, active )
546 values ( ?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?,?,?, 1 )";
547 $sth2 = $dbh->prepare($query2);
548 $sth2->execute(
549 $barcodetype, $title, $subtitle, $isbn, $issn,
551 $itemtype, $bcn, $dcn, $classif,
552 $subclass, $itemcallnumber, $author, $printingtype,
553 $guidebox, $startlabel, $layoutname
555 $sth2->finish;
557 SetActiveTemplate($tmpl_id);
558 return;
561 sub save_layout {
563 my (
564 $barcodetype, $title, $subtitle, $isbn, $issn,
565 $itemtype, $bcn, $dcn, $classif,
566 $subclass, $itemcallnumber, $author, $tmpl_id,
567 $printingtype, $guidebox, $startlabel, $layoutname,
568 $layout_id
569 ) = @_;
570 ### $layoutname
571 ### $layout_id
573 my $dbh = C4::Context->dbh;
574 my $query2 = "update labels_conf set
575 barcodetype=?, title=?, subtitle=?, isbn=?,issn=?,
576 itemtype=?, barcode=?, dewey=?, class=?,
577 subclass=?, itemcallnumber=?, author=?, printingtype=?,
578 guidebox=?, startlabel=?, layoutname=? where id = ?";
579 my $sth2 = $dbh->prepare($query2);
580 $sth2->execute(
581 $barcodetype, $title, $subtitle, $isbn, $issn,
582 $itemtype, $bcn, $dcn, $classif,
583 $subclass, $itemcallnumber, $author, $printingtype,
584 $guidebox, $startlabel, $layoutname, $layout_id
586 $sth2->finish;
588 return;
591 =item GetAllPrinterProfiles;
593 @profiles = GetAllPrinterProfiles()
595 Returns an array of references-to-hash, whos keys are .....
597 =cut
599 sub GetAllPrinterProfiles {
601 my $dbh = C4::Context->dbh;
602 my @data;
603 my $query = "SELECT * FROM printers_profile AS pp INNER JOIN labels_templates AS lt ON pp.tmpl_id = lt.tmpl_id; ";
604 my $sth = $dbh->prepare($query);
605 $sth->execute();
606 my @resultsloop;
607 while ( my $data = $sth->fetchrow_hashref ) {
608 push( @resultsloop, $data );
610 $sth->finish;
612 return @resultsloop;
615 =item GetSinglePrinterProfile;
617 $profile = GetSinglePrinterProfile()
619 Returns a hashref whos keys are...
621 =cut
623 sub GetSinglePrinterProfile {
624 my ($prof_id) = @_;
625 my $dbh = C4::Context->dbh;
626 my $query = " SELECT * FROM printers_profile WHERE prof_id = ?; ";
627 my $sth = $dbh->prepare($query);
628 $sth->execute($prof_id);
629 my $template = $sth->fetchrow_hashref;
630 $sth->finish;
631 return $template;
634 =item SaveProfile;
636 SaveProfile('parameters')
638 When passed a set of parameters, this function updates the given profile with the new parameters.
640 =cut
642 sub SaveProfile {
643 my (
644 $prof_id, $offset_horz, $offset_vert, $creep_horz, $creep_vert, $units
645 ) = @_;
646 my $dbh = C4::Context->dbh;
647 my $query =
648 " UPDATE printers_profile
649 SET offset_horz=?, offset_vert=?, creep_horz=?, creep_vert=?, unit=?
650 WHERE prof_id = ? ";
651 my $sth = $dbh->prepare($query);
652 $sth->execute(
653 $offset_horz, $offset_vert, $creep_horz, $creep_vert, $units, $prof_id
655 $sth->finish;
658 =item CreateProfile;
660 CreateProfile('parameters')
662 When passed a set of parameters, this function creates a new profile containing those parameters
663 and returns any errors.
665 =cut
667 sub CreateProfile {
668 my (
669 $prof_id, $printername, $paper_bin, $tmpl_id, $offset_horz,
670 $offset_vert, $creep_horz, $creep_vert, $units
671 ) = @_;
672 my $dbh = C4::Context->dbh;
673 my $query =
674 " INSERT INTO printers_profile (prof_id, printername, paper_bin, tmpl_id,
675 offset_horz, offset_vert, creep_horz, creep_vert, unit)
676 VALUES(?,?,?,?,?,?,?,?,?) ";
677 my $sth = $dbh->prepare($query);
678 $sth->execute(
679 $prof_id, $printername, $paper_bin, $tmpl_id, $offset_horz,
680 $offset_vert, $creep_horz, $creep_vert, $units
682 my $error = $sth->errstr;
683 $sth->finish;
684 return $error;
687 =item DeleteProfile;
689 DeleteProfile(prof_id)
691 When passed a profile id, this function deletes that profile from the database and returns any errors.
693 =cut
695 sub DeleteProfile {
696 my ($prof_id) = @_;
697 my $dbh = C4::Context->dbh;
698 my $query = " DELETE FROM printers_profile WHERE prof_id = ?";
699 my $sth = $dbh->prepare($query);
700 $sth->execute($prof_id);
701 my $error = $sth->errstr;
702 $sth->finish;
703 return $error;
706 =item GetAssociatedProfile;
708 $assoc_prof = GetAssociatedProfile(tmpl_id)
710 When passed a template id, this function returns the parameters from the currently associated printer profile
711 in a hashref where key=fieldname and value=fieldvalue.
713 =cut
715 sub GetAssociatedProfile {
716 my ($tmpl_id) = @_;
717 my $dbh = C4::Context->dbh;
718 # First we find out the prof_id for the associated profile...
719 my $query = "SELECT * FROM labels_profile WHERE tmpl_id = ?";
720 my $sth = $dbh->prepare($query);
721 $sth->execute($tmpl_id);
722 my $assoc_prof = $sth->fetchrow_hashref;
723 $sth->finish;
724 # Then we retrieve that profile and return it to the caller...
725 $assoc_prof = GetSinglePrinterProfile($assoc_prof->{'prof_id'});
726 return $assoc_prof;
729 =item SetAssociatedProfile;
731 SetAssociatedProfile($prof_id, $tmpl_id)
733 When passed both a profile id and template id, this function establishes an association between the two. No more
734 than one profile may be associated with any given template at the same time.
736 =cut
738 sub SetAssociatedProfile {
740 my ($prof_id, $tmpl_id) = @_;
742 my $dbh = C4::Context->dbh;
743 my $query = "INSERT INTO labels_profile (prof_id, tmpl_id) VALUES (?,?) ON DUPLICATE KEY UPDATE prof_id = ?";
744 my $sth = $dbh->prepare($query);
745 $sth->execute($prof_id, $tmpl_id, $prof_id);
746 $sth->finish;
749 =item GetLabelItems;
751 $options = GetLabelItems()
753 Returns an array of references-to-hash, whos keys are the field from the biblio, biblioitems, items and labels tables in the Koha database.
755 =cut
758 sub GetLabelItems {
759 my ($batch_id) = @_;
760 my $dbh = C4::Context->dbh;
762 my @resultsloop = ();
763 my $count;
764 my @data;
765 my $sth;
767 if ($batch_id) {
768 my $query3 = "Select * from labels where batch_id = ? order by labelid ";
769 $sth = $dbh->prepare($query3);
770 $sth->execute($batch_id);
773 else {
775 my $query3 = "Select * from labels";
776 $sth = $dbh->prepare($query3);
777 $sth->execute();
779 my $cnt = $sth->rows;
780 my $i1 = 1;
781 while ( my $data = $sth->fetchrow_hashref ) {
783 # lets get some summary info from each item
784 my $query1 = "
785 select i.*, bi.*, b.* from items i,biblioitems bi,biblio b
786 where itemnumber=? and i.biblioitemnumber=bi.biblioitemnumber and
787 bi.biblionumber=b.biblionumber";
789 my $sth1 = $dbh->prepare($query1);
790 $sth1->execute( $data->{'itemnumber'} );
792 my $data1 = $sth1->fetchrow_hashref();
793 $data1->{'labelno'} = $i1;
794 $data1->{'labelid'} = $data->{'labelid'};
795 $data1->{'batch_id'} = $batch_id;
796 $data1->{'summary'} =
797 "$data1->{'barcode'}, $data1->{'title'}, $data1->{'isbn'}";
799 push( @resultsloop, $data1 );
800 $sth1->finish;
802 $i1++;
804 $sth->finish;
805 return @resultsloop;
809 sub GetItemFields {
810 my @fields = qw (
811 barcode title subtitle
812 dewey isbn issn author class
813 itemtype subclass itemcallnumber
816 return @fields;
819 sub GetPatronCardItems {
821 my ( $batch_id ) = @_;
822 my @resultsloop;
824 my $dbh = C4::Context->dbh;
825 # my $query = "SELECT * FROM patroncards WHERE batch_id = ? ORDER BY borrowernumber";
826 my $query = "SELECT * FROM patroncards WHERE batch_id = ? ORDER BY cardid";
827 my $sth = $dbh->prepare($query);
828 $sth->execute($batch_id);
829 my $cardno = 1;
830 while ( my $data = $sth->fetchrow_hashref ) {
831 my $patron_data = GetMember( $data->{'borrowernumber'} );
832 $patron_data->{'branchname'} = GetBranchName( $patron_data->{'branchcode'} );
833 $patron_data->{'cardno'} = $cardno;
834 $patron_data->{'cardid'} = $data->{'cardid'};
835 $patron_data->{'batch_id'} = $batch_id;
836 push( @resultsloop, $patron_data );
837 $cardno++;
839 $sth->finish;
840 return @resultsloop;
844 sub deduplicate_batch {
845 my ( $batch_id, $batch_type ) = @_;
846 my $query = "
847 SELECT DISTINCT
848 batch_id," . (($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber') . ",
849 count(". (($batch_type eq 'labels') ? 'labelid' : 'cardid') . ") as count
850 FROM $batch_type
851 WHERE batch_id = ?
852 GROUP BY " . (($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber') . ",batch_id
853 HAVING count > 1
854 ORDER BY batch_id,
855 count DESC ";
856 my $sth = C4::Context->dbh->prepare($query);
857 $sth->execute($batch_id);
858 warn $sth->errstr if $sth->errstr;
859 $sth->rows or return undef, $sth->errstr;
861 my $del_query = "
862 DELETE
863 FROM $batch_type
864 WHERE batch_id = ?
865 AND " . (($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber') . " = ?
866 ORDER BY timestamp ASC
868 my $killed = 0;
869 while (my $data = $sth->fetchrow_hashref()) {
870 my $itemnumber = $data->{(($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber')} or next;
871 my $limit = $data->{count} - 1 or next;
872 my $sth2 = C4::Context->dbh->prepare("$del_query LIMIT $limit");
873 # die sprintf "$del_query LIMIT %s\n (%s, %s)", $limit, $batch_id, $itemnumber;
874 # $sth2->execute($batch_id, C4::Context->dbh->quote($data->{itemnumber}), $data->{count} - 1)
875 $sth2->execute($batch_id, $itemnumber) and
876 $killed += ($data->{count} - 1);
877 warn $sth2->errstr if $sth2->errstr;
879 return $killed, undef;
882 sub DrawSpineText {
884 my ( $x_pos, $y_pos, $label_height, $label_width, $fontname, $fontsize, $left_text_margin,
885 $text_wrap_cols, $item, $conf_data, $printingtype )
886 = @_;
887 # hack to fix column name mismatch betwen labels_conf.class, and bibitems.classification
888 $$item->{'class'} = $$item->{'classification'};
890 $Text::Wrap::columns = $text_wrap_cols;
891 $Text::Wrap::separator = "\n";
893 my $str;
895 my $top_text_margin = ( $fontsize + 3 ); #FIXME: This should be a template parameter and passed in...
896 my $line_spacer = ( $fontsize * 1 ); # number of pixels between text rows (This is actually leading: baseline to baseline minus font size. Recommended starting point is 20% of font size.).
898 # add your printable fields manually in here
900 my $layout_id = $$conf_data->{'id'};
902 # my @fields = GetItemFields();
904 my $str_fields = get_text_fields($layout_id, 'codes' );
905 my @fields = split(/ /, $str_fields);
906 #warn Dumper(@fields);
908 my $vPos = ( $y_pos + ( $label_height - $top_text_margin ) );
909 my $font = prFont($fontname);
911 # warn Dumper $conf_data;
912 #warn Dumper $item;
914 foreach my $field (@fields) {
916 # testing hack
917 # $$item->{"$field"} = $field . ": " . $$item->{"$field"};
919 # if the display option for this field is selected in the DB,
920 # and the item record has some values for this field, display it.
921 if ( $$conf_data->{"$field"} && $$item->{"$field"} ) {
923 # warn "CONF_TYPE = $field";
925 # get the string
926 $str = $$item->{"$field"};
927 # strip out naughty existing nl/cr's
928 $str =~ s/\n//g;
929 $str =~ s/\r//g;
930 # wrap lines based on call number dividers '/'
931 my @strings;
933 while ( $str =~ /\// ) {
934 $str =~ /^(.*)\/(.*)$/;
936 #warn "\$2=$2";
937 unshift @strings, $2;
938 $str = $1;
941 unshift @strings, $str;
943 # strip out division slashes
944 #$str =~ s/\///g;
945 #warn "\$str after striping division marks: $str";
946 # chop the string up into _upto_ 12 chunks
947 # and seperate the chunks with newlines
949 #$str = wrap( "", "", "$str" );
950 #$str = wrap( "", "", "$str" );
952 # split the chunks between newline's, into an array
953 #my @strings = split /\n/, $str;
955 # then loop for each string line
956 foreach my $str (@strings) {
957 my $hPos;
958 if ( $printingtype eq 'BIB' ) { #FIXME: This is a hack and needs to be implimented as a text justification option in the template...
959 # some code to try and center each line on the label based on font size and string point width...
960 my $stringwidth = prStrWidth($str, $fontname, $fontsize);
961 my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
962 $hPos = ( ( $whitespace / 2 ) + $x_pos + $left_text_margin );
963 #warn "\$label_width=$label_width \$stringwidth=$stringwidth \$whitespace=$whitespace \$left_text_margin=$left_text_margin for $str\n";
964 } else {
965 $hPos = ( $x_pos + $left_text_margin );
967 PrintText( $hPos, $vPos, $font, $fontsize, $str );
968 $vPos = $vPos - $line_spacer;
971 } # if field is
972 } #foreach feild
975 sub PrintText {
976 my ( $hPos, $vPos, $font, $fontsize, $text ) = @_;
977 my $str = "BT /$font $fontsize Tf $hPos $vPos Td ($text) Tj ET";
978 warn $str;
979 prAdd($str);
982 sub DrawPatronCardText {
984 my ( $x_pos, $y_pos, $label_height, $label_width, $fontname, $fontsize, $left_text_margin,
985 $text_wrap_cols, $text, $printingtype )
986 = @_;
988 my $top_text_margin = 25; #FIXME: This should be a template parameter and passed in...
990 my $vPos = ( $y_pos + ( $label_height - $top_text_margin ) );
991 my $font = prFont($fontname);
993 my $hPos;
995 foreach my $line (keys %$text) {
996 warn "Current text is \"$line\" and font size for \"$line\" is $text->{$line} points";
997 # some code to try and center each line on the label based on font size and string point width...
998 my $stringwidth = prStrWidth($line, $fontname, $text->{$line});
999 my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
1000 $hPos = ( ( $whitespace / 2 ) + $x_pos + $left_text_margin );
1002 PrintText( $hPos, $vPos, $font, $text->{$line}, $line );
1003 my $line_spacer = ( $text->{$line} * 1 ); # number of pixels between text rows (This is actually leading: baseline to baseline minus font size. Recommended starting point is 20% (0.20) of font size.).
1004 $vPos = $vPos - ($line_spacer + $text->{$line}); # Linefeed equiv: leading + font size
1008 # Not used anywhere.
1010 #sub SetFontSize {
1012 # my ($fontsize) = @_;
1013 #### fontsize
1014 # my $str = "BT/F13 30 Tf288 720 Td( AAAAAAAAAA ) TjET";
1015 # prAdd($str);
1018 sub DrawBarcode {
1020 # x and y are from the top-left :)
1021 my ( $x_pos, $y_pos, $height, $width, $barcode, $barcodetype ) = @_;
1022 my $num_of_bars = length($barcode);
1023 my $bar_width = $width * .8; # %80 of length of label width
1024 my $tot_bar_length;
1025 my $bar_length;
1026 my $guard_length = 10;
1027 my $xsize_ratio;
1029 if ( $barcodetype eq 'CODE39' ) {
1030 $bar_length = '17.5';
1031 $tot_bar_length =
1032 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1033 $xsize_ratio = ( $bar_width / $tot_bar_length );
1034 eval {
1035 PDF::Reuse::Barcode::Code39(
1036 x => ( $x_pos + ( $width / 10 ) ),
1037 y => ( $y_pos + ( $height / 10 ) ),
1038 value => "*$barcode*",
1039 ySize => ( .02 * $height ),
1040 xSize => $xsize_ratio,
1041 hide_asterisk => 1,
1044 if ($@) {
1045 warn "$barcodetype, $barcode FAILED:$@";
1049 elsif ( $barcodetype eq 'CODE39MOD' ) {
1051 # get modulo43 checksum
1052 my $c39 = CheckDigits('code_39');
1053 $barcode = $c39->complete($barcode);
1055 $bar_length = '19';
1056 $tot_bar_length =
1057 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1058 $xsize_ratio = ( $bar_width / $tot_bar_length );
1059 eval {
1060 PDF::Reuse::Barcode::Code39(
1061 x => ( $x_pos + ( $width / 10 ) ),
1062 y => ( $y_pos + ( $height / 10 ) ),
1063 value => "*$barcode*",
1064 ySize => ( .02 * $height ),
1065 xSize => $xsize_ratio,
1066 hide_asterisk => 1,
1070 if ($@) {
1071 warn "$barcodetype, $barcode FAILED:$@";
1074 elsif ( $barcodetype eq 'CODE39MOD10' ) {
1076 # get modulo43 checksum
1077 my $c39_10 = CheckDigits('visa');
1078 $barcode = $c39_10->complete($barcode);
1080 $bar_length = '19';
1081 $tot_bar_length =
1082 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1083 $xsize_ratio = ( $bar_width / $tot_bar_length );
1084 eval {
1085 PDF::Reuse::Barcode::Code39(
1086 x => ( $x_pos + ( $width / 10 ) ),
1087 y => ( $y_pos + ( $height / 10 ) ),
1088 value => "*$barcode*",
1089 ySize => ( .02 * $height ),
1090 xSize => $xsize_ratio,
1091 hide_asterisk => 1,
1092 text => 0,
1096 if ($@) {
1097 warn "$barcodetype, $barcode FAILED:$@";
1102 elsif ( $barcodetype eq 'COOP2OF5' ) {
1103 $bar_length = '9.43333333333333';
1104 $tot_bar_length =
1105 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1106 $xsize_ratio = ( $bar_width / $tot_bar_length ) * .9;
1107 eval {
1108 PDF::Reuse::Barcode::COOP2of5(
1109 x => ( $x_pos + ( $width / 10 ) ),
1110 y => ( $y_pos + ( $height / 10 ) ),
1111 value => $barcode,
1112 ySize => ( .02 * $height ),
1113 xSize => $xsize_ratio,
1116 if ($@) {
1117 warn "$barcodetype, $barcode FAILED:$@";
1121 elsif ( $barcodetype eq 'INDUSTRIAL2OF5' ) {
1122 $bar_length = '13.1333333333333';
1123 $tot_bar_length =
1124 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1125 $xsize_ratio = ( $bar_width / $tot_bar_length ) * .9;
1126 eval {
1127 PDF::Reuse::Barcode::Industrial2of5(
1128 x => ( $x_pos + ( $width / 10 ) ),
1129 y => ( $y_pos + ( $height / 10 ) ),
1130 value => $barcode,
1131 ySize => ( .02 * $height ),
1132 xSize => $xsize_ratio,
1135 if ($@) {
1136 warn "$barcodetype, $barcode FAILED:$@";
1140 my $moo2 = $tot_bar_length * $xsize_ratio;
1142 warn "$x_pos, $y_pos, $barcode, $barcodetype" if $DEBUG;
1143 warn "BAR_WDTH = $bar_width, TOT.BAR.LGHT=$tot_bar_length R*TOT.BAR =$moo2" if $DEBUG;
1146 =item build_circ_barcode;
1148 build_circ_barcode( $x_pos, $y_pos, $barcode,
1149 $barcodetype, \$item);
1151 $item is the result of a previous call to GetLabelItems();
1153 =cut
1156 sub build_circ_barcode {
1157 my ( $x_pos_circ, $y_pos, $value, $barcodetype, $item ) = @_;
1159 #warn Dumper \$item;
1161 #warn "value = $value\n";
1163 #$DB::single = 1;
1165 if ( $barcodetype eq 'EAN13' ) {
1167 #testing EAN13 barcodes hack
1168 $value = $value . '000000000';
1169 $value =~ s/-//;
1170 $value = substr( $value, 0, 12 );
1172 #warn $value;
1173 eval {
1174 PDF::Reuse::Barcode::EAN13(
1175 x => ( $x_pos_circ + 27 ),
1176 y => ( $y_pos + 15 ),
1177 value => $value,
1179 # prolong => 2.96,
1180 # xSize => 1.5,
1182 # ySize => 1.2,
1184 # added for xpdf compat. doesnt use type3 fonts., but increases filesize from 20k to 200k
1185 # i think its embedding extra fonts in the pdf file.
1186 # mode => 'graphic',
1189 if ($@) {
1190 $item->{'barcodeerror'} = 1;
1192 #warn "EAN13BARCODE FAILED:$@";
1195 #warn $barcodetype;
1198 elsif ( $barcodetype eq 'Code39' ) {
1200 eval {
1201 PDF::Reuse::Barcode::Code39(
1202 x => ( $x_pos_circ + 9 ),
1203 y => ( $y_pos + 15 ),
1204 value => $value,
1206 # prolong => 2.96,
1207 xSize => .85,
1209 ySize => 1.3,
1212 if ($@) {
1213 $item->{'barcodeerror'} = 1;
1215 #warn "CODE39BARCODE $value FAILED:$@";
1218 #warn $barcodetype;
1222 elsif ( $barcodetype eq 'Matrix2of5' ) {
1224 #warn "MATRIX ELSE:";
1226 #testing MATRIX25 barcodes hack
1227 # $value = $value.'000000000';
1228 $value =~ s/-//;
1230 # $value = substr( $value, 0, 12 );
1231 #warn $value;
1233 eval {
1234 PDF::Reuse::Barcode::Matrix2of5(
1235 x => ( $x_pos_circ + 27 ),
1236 y => ( $y_pos + 15 ),
1237 value => $value,
1239 # prolong => 2.96,
1240 # xSize => 1.5,
1242 # ySize => 1.2,
1245 if ($@) {
1246 $item->{'barcodeerror'} = 1;
1248 #warn "BARCODE FAILED:$@";
1251 #warn $barcodetype;
1255 elsif ( $barcodetype eq 'EAN8' ) {
1257 #testing ean8 barcodes hack
1258 $value = $value . '000000000';
1259 $value =~ s/-//;
1260 $value = substr( $value, 0, 8 );
1262 #warn $value;
1264 #warn "EAN8 ELSEIF";
1265 eval {
1266 PDF::Reuse::Barcode::EAN8(
1267 x => ( $x_pos_circ + 42 ),
1268 y => ( $y_pos + 15 ),
1269 value => $value,
1270 prolong => 2.96,
1271 xSize => 1.5,
1273 # ySize => 1.2,
1277 if ($@) {
1278 $item->{'barcodeerror'} = 1;
1280 #warn "BARCODE FAILED:$@";
1283 #warn $barcodetype;
1287 elsif ( $barcodetype eq 'UPC-E' ) {
1288 eval {
1289 PDF::Reuse::Barcode::UPCE(
1290 x => ( $x_pos_circ + 27 ),
1291 y => ( $y_pos + 15 ),
1292 value => $value,
1293 prolong => 2.96,
1294 xSize => 1.5,
1296 # ySize => 1.2,
1300 if ($@) {
1301 $item->{'barcodeerror'} = 1;
1303 #warn "BARCODE FAILED:$@";
1306 #warn $barcodetype;
1309 elsif ( $barcodetype eq 'NW7' ) {
1310 eval {
1311 PDF::Reuse::Barcode::NW7(
1312 x => ( $x_pos_circ + 27 ),
1313 y => ( $y_pos + 15 ),
1314 value => $value,
1315 prolong => 2.96,
1316 xSize => 1.5,
1318 # ySize => 1.2,
1322 if ($@) {
1323 $item->{'barcodeerror'} = 1;
1325 #warn "BARCODE FAILED:$@";
1328 #warn $barcodetype;
1331 elsif ( $barcodetype eq 'ITF' ) {
1332 eval {
1333 PDF::Reuse::Barcode::ITF(
1334 x => ( $x_pos_circ + 27 ),
1335 y => ( $y_pos + 15 ),
1336 value => $value,
1337 prolong => 2.96,
1338 xSize => 1.5,
1340 # ySize => 1.2,
1344 if ($@) {
1345 $item->{'barcodeerror'} = 1;
1347 #warn "BARCODE FAILED:$@";
1350 #warn $barcodetype;
1353 elsif ( $barcodetype eq 'Industrial2of5' ) {
1354 eval {
1355 PDF::Reuse::Barcode::Industrial2of5(
1356 x => ( $x_pos_circ + 27 ),
1357 y => ( $y_pos + 15 ),
1358 value => $value,
1359 prolong => 2.96,
1360 xSize => 1.5,
1362 # ySize => 1.2,
1365 if ($@) {
1366 $item->{'barcodeerror'} = 1;
1368 #warn "BARCODE FAILED:$@";
1371 #warn $barcodetype;
1374 elsif ( $barcodetype eq 'IATA2of5' ) {
1375 eval {
1376 PDF::Reuse::Barcode::IATA2of5(
1377 x => ( $x_pos_circ + 27 ),
1378 y => ( $y_pos + 15 ),
1379 value => $value,
1380 prolong => 2.96,
1381 xSize => 1.5,
1383 # ySize => 1.2,
1386 if ($@) {
1387 $item->{'barcodeerror'} = 1;
1389 #warn "BARCODE FAILED:$@";
1392 #warn $barcodetype;
1396 elsif ( $barcodetype eq 'COOP2of5' ) {
1397 eval {
1398 PDF::Reuse::Barcode::COOP2of5(
1399 x => ( $x_pos_circ + 27 ),
1400 y => ( $y_pos + 15 ),
1401 value => $value,
1402 prolong => 2.96,
1403 xSize => 1.5,
1405 # ySize => 1.2,
1408 if ($@) {
1409 $item->{'barcodeerror'} = 1;
1411 #warn "BARCODE FAILED:$@";
1414 #warn $barcodetype;
1417 elsif ( $barcodetype eq 'UPC-A' ) {
1419 eval {
1420 PDF::Reuse::Barcode::UPCA(
1421 x => ( $x_pos_circ + 27 ),
1422 y => ( $y_pos + 15 ),
1423 value => $value,
1424 prolong => 2.96,
1425 xSize => 1.5,
1427 # ySize => 1.2,
1430 if ($@) {
1431 $item->{'barcodeerror'} = 1;
1433 #warn "BARCODE FAILED:$@";
1436 #warn $barcodetype;
1442 =item draw_boundaries
1444 sub draw_boundaries ($x_pos_spine, $x_pos_circ1, $x_pos_circ2,
1445 $y_pos, $spine_width, $label_height, $circ_width)
1447 This sub draws boundary lines where the label outlines are, to aid in printer testing, and debugging.
1449 =cut
1452 sub draw_boundaries {
1454 my (
1455 $x_pos_spine, $x_pos_circ1, $x_pos_circ2, $y_pos,
1456 $spine_width, $label_height, $circ_width
1457 ) = @_;
1459 my $y_pos_initial = ( ( 792 - 36 ) - 90 );
1460 $y_pos = $y_pos_initial; # FIXME - why are we ignoring the y_pos parameter by redefining it?
1461 my $i = 1;
1463 for ( $i = 1 ; $i <= 8 ; $i++ ) {
1465 &drawbox( $x_pos_spine, $y_pos, ($spine_width), ($label_height) );
1467 #warn "OLD BOXES x=$x_pos_spine, y=$y_pos, w=$spine_width, h=$label_height";
1468 &drawbox( $x_pos_circ1, $y_pos, ($circ_width), ($label_height) );
1469 &drawbox( $x_pos_circ2, $y_pos, ($circ_width), ($label_height) );
1471 $y_pos = ( $y_pos - $label_height );
1476 =item drawbox
1478 sub drawbox { $lower_left_x, $lower_left_y,
1479 $upper_right_x, $upper_right_y )
1481 this is a low level sub, that draws a pdf box, it is called by draw_boxes
1483 FYI: the $upper_right_x and $upper_right_y values are RELATIVE to $lower_left_x and $lower_left_y
1485 and $lower_left_x, $lower_left_y are ABSOLUTE, this caught me out!
1487 =cut
1490 sub drawbox {
1491 my ( $llx, $lly, $urx, $ury ) = @_;
1493 # warn "llx,y= $llx,$lly , urx,y=$urx,$ury \n";
1495 my $str = "q\n"; # save the graphic state
1496 $str .= "0.5 w\n"; # border color red
1497 $str .= "1.0 0.0 0.0 RG\n"; # border color red
1498 # $str .= "0.5 0.75 1.0 rg\n"; # fill color blue
1499 $str .= "1.0 1.0 1.0 rg\n"; # fill color white
1501 $str .= "$llx $lly $urx $ury re\n"; # a rectangle
1502 $str .= "B\n"; # fill (and a little more)
1503 $str .= "Q\n"; # save the graphic state
1505 prAdd($str);
1509 END { } # module clean-up code here (global destructor)
1512 __END__
1514 =back
1516 =head1 AUTHOR
1518 Mason James <mason@katipo.co.nz>
1520 =cut