Italian SQL updates
[koha.git] / C4 / Labels.pm
index 1233257..82c59cc 100644 (file)
@@ -28,7 +28,7 @@ use C4::Branch;
 use C4::Debug;
 use C4::Biblio;
 use Text::CSV_XS;
-#use Data::Dumper;
+use Data::Dumper;
 
 BEGIN {
        $VERSION = 0.03;
@@ -69,9 +69,7 @@ C4::Labels - Functions for printing spine labels and barcodes in Koha
 
 =head1 FUNCTIONS
 
-=over 2
-
-=item get_label_options;
+=head2 get_label_options;
 
        $options = get_label_options()
 
@@ -261,19 +259,25 @@ sub get_text_fields {
 }
 
 =head2 sub add_batch
+
 =over 4
+
  add_batch($batch_type,\@batch_list);
  if $batch_list is supplied,
    create a new batch with those items.
  else, return the next available batch_id.
-=return
+
+=back
+
 =cut
 
 sub add_batch ($;$) {
        my $table = (@_ and 'patroncards' eq shift) ? 'patroncards' : 'labels';
     my $batch_list = (@_) ? shift : undef;
     my $dbh = C4::Context->dbh;
-    my $q ="SELECT MAX(DISTINCT batch_id) FROM $table";
+    # FIXME : batch_id  should be an auto_incr INT.  Temporarily casting as int ( see koha bug 2555 )
+    # until a label_batches table is added, and we can convert batch_id to int.
+    my $q ="SELECT MAX( CAST(batch_id AS SIGNED) ) FROM $table";
     my $sth = $dbh->prepare($q);
     $sth->execute();
     my ($batch_id) = $sth->fetchrow_array || 0;
@@ -509,8 +513,8 @@ sub add_layout {
 
     my (
         $barcodetype,  $title,                 $subtitle,      $isbn,       $issn,
-        $itemtype,     $bcn,            $dcn,        $classif,
-        $subclass,     $itemcallnumber, $author,     $tmpl_id,
+        $itemtype,     $bcn,            $text_justify,        $callnum_split,
+        $itemcallnumber, $author,     $tmpl_id,
         $printingtype, $guidebox,       $startlabel, $layoutname, $formatstring
     ) = @_;
 
@@ -520,15 +524,15 @@ sub add_layout {
     $sth2->execute();
     $query2 = "INSERT INTO labels_conf
             ( barcodetype, title, subtitle, isbn,issn, itemtype, barcode,
-              dewey, classification, subclass, itemcallnumber, author, printingtype,
+              text_justify, callnum_split, itemcallnumber, author, printingtype,
                 guidebox, startlabel, layoutname, formatstring, active )
-               values ( ?, ?,?, ?, ?, ?, ?, ?,  ?,?, ?, ?, ?, ?, ?,?,?, 1 )";
+               values ( ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?,?, 1 )";
     $sth2 = $dbh->prepare($query2);
     $sth2->execute(
         $barcodetype, $title, $subtitle, $isbn, $issn,
 
-        $itemtype, $bcn,            $dcn,    $classif,
-        $subclass, $itemcallnumber, $author, $printingtype,
+        $itemtype, $bcn,            $text_justify,    $callnum_split,
+        $itemcallnumber, $author, $printingtype,
         $guidebox, $startlabel,     $layoutname, $formatstring
     );
     $sth2->finish;
@@ -541,8 +545,8 @@ sub save_layout {
 
     my (
         $barcodetype,  $title,          $subtitle,     $isbn,       $issn,
-        $itemtype,     $bcn,            $dcn,        $classif,
-        $subclass,     $itemcallnumber, $author,     $tmpl_id,
+        $itemtype,     $bcn,            $text_justify,        $callnum_split,
+        $itemcallnumber, $author,     $tmpl_id,
         $printingtype, $guidebox,       $startlabel, $layoutname, $formatstring,
         $layout_id
     ) = @_;
@@ -552,14 +556,14 @@ sub save_layout {
     my $dbh    = C4::Context->dbh;
     my $query2 = "update labels_conf set 
              barcodetype=?, title=?, subtitle=?, isbn=?,issn=?, 
-            itemtype=?, barcode=?,    dewey=?, classification=?,
-             subclass=?, itemcallnumber=?, author=?,  printingtype=?,  
+            itemtype=?, barcode=?,    text_justify=?, callnum_split=?,
+            itemcallnumber=?, author=?,  printingtype=?,  
                guidebox=?, startlabel=?, layoutname=?, formatstring=? where id = ?";
     my $sth2 = $dbh->prepare($query2);
     $sth2->execute(
         $barcodetype, $title,          $subtitle,      $isbn,       $issn,
-        $itemtype,    $bcn,            $dcn,        $classif,
-        $subclass,    $itemcallnumber, $author,     $printingtype,
+        $itemtype,    $bcn,            $text_justify,        $callnum_split,
+        $itemcallnumber, $author,     $printingtype,
         $guidebox,    $startlabel,     $layoutname, $formatstring,  $layout_id
     );
     $sth2->finish;
@@ -567,7 +571,7 @@ sub save_layout {
     return;
 }
 
-=item GetAllPrinterProfiles;
+=head2 GetAllPrinterProfiles;
 
     @profiles = GetAllPrinterProfiles()
 
@@ -591,7 +595,7 @@ sub GetAllPrinterProfiles {
     return @resultsloop;
 }
 
-=item GetSinglePrinterProfile;
+=head2 GetSinglePrinterProfile;
 
     $profile = GetSinglePrinterProfile()
 
@@ -610,7 +614,7 @@ sub GetSinglePrinterProfile {
     return $template;
 }
 
-=item SaveProfile;
+=head2 SaveProfile;
 
     SaveProfile('parameters')
 
@@ -634,7 +638,7 @@ sub SaveProfile {
     $sth->finish;
 }
 
-=item CreateProfile;
+=head2 CreateProfile;
 
     CreateProfile('parameters')
 
@@ -663,7 +667,7 @@ sub CreateProfile {
     return $error;
 }
 
-=item DeleteProfile;
+=head2 DeleteProfile;
 
     DeleteProfile(prof_id)
 
@@ -682,7 +686,7 @@ sub DeleteProfile {
     return $error;
 }
 
-=item GetAssociatedProfile;
+=head2 GetAssociatedProfile;
 
     $assoc_prof = GetAssociatedProfile(tmpl_id)
 
@@ -705,7 +709,7 @@ sub GetAssociatedProfile {
     return $assoc_prof;
 }
 
-=item SetAssociatedProfile;
+=head2 SetAssociatedProfile;
 
     SetAssociatedProfile($prof_id, $tmpl_id)
 
@@ -726,7 +730,7 @@ sub SetAssociatedProfile {
 }
 
 
-=item GetLabelItems;
+=head2 GetLabelItems;
 
         $options = GetLabelItems()
 
@@ -744,15 +748,18 @@ sub GetLabelItems {
     my $sth;
     
     if ($batch_id) {
-        my $query3 = "SELECT *
-                        FROM labels
-                        WHERE batch_id = ?
-                        ORDER BY labelid";
+        my $query3 = "
+            SELECT *
+            FROM labels
+            WHERE batch_id = ?
+            ORDER BY labelid";
         $sth = $dbh->prepare($query3);
         $sth->execute($batch_id);
     }
     else {
-        my $query3 = "Select * from labels";
+        my $query3 = "
+            SELECT *
+            FROM labels";
         $sth = $dbh->prepare($query3);
         $sth->execute();
     }
@@ -761,12 +768,14 @@ sub GetLabelItems {
     while ( my $data = $sth->fetchrow_hashref ) {
 
         # lets get some summary info from each item
-        my $query1 = "
-        select i.*, bi.*, b.*  from items i,biblioitems bi,biblio b 
-               where itemnumber=? and  i.biblioitemnumber=bi.biblioitemnumber and                  
-               bi.biblionumber=b.biblionumber"; 
-     
-       my $sth1 = $dbh->prepare($query1);
+        my $query1 =
+#            FIXME This makes for a very bulky data structure; data from tables w/duplicate col names also gets overwritten.
+#            Something like this, perhaps, but this also causes problems because we need more fields sometimes.
+#            SELECT i.barcode, i.itemcallnumber, i.itype, bi.isbn, bi.issn, b.title, b.author
+           "SELECT bi.*, i.*, b.*
+            FROM items AS i, biblioitems AS bi ,biblio AS b
+            WHERE itemnumber=? AND i.biblioitemnumber=bi.biblioitemnumber AND bi.biblionumber=b.biblionumber";
+        my $sth1 = $dbh->prepare($query1);
         $sth1->execute( $data->{'itemnumber'} );
 
         my $data1 = $sth1->fetchrow_hashref();
@@ -787,49 +796,81 @@ sub GetLabelItems {
 
 sub GetItemFields {
     my @fields = qw (
-      barcode title subtitle
-      dewey isbn issn author class
-      itemtype subclass itemcallnumber
+      barcode           title
+      isbn              issn
+      author            itemtype
+      itemcallnumber
     );
     return @fields;
 }
 
-=head GetBarcodeData
+=head2 GetBarcodeData
 
 =over 4
+
 Parse labels_conf.formatstring value
 (one value of the csv, which has already been split)
 and return string from koha tables or MARC record.
+
 =back
+
 =cut
-#'
+
 sub GetBarcodeData {
-       my ($f,$item,$record) = @_;
-       my $kohatables= &_descKohaTables();
-       my $datastring;
-       my $last_f = $f;
-       my $match_kohatable = join('|', (@{$kohatables->{biblio}},@{$kohatables->{biblioitems}},@{$kohatables->{items}}) );
-       while( $f ) {
-               if( $f =~ /^'(.*)'.*/ ) {
-                       # single quotes indicate a static text string.
-                       $datastring .= $1 ;
-                       $f = $';
-               } elsif ( $f =~ /^($match_kohatable).*/ ) { 
-                       # grep /$f/, (@$kohatables->{biblio},@$kohatables->{biblioitems},@$kohatables->{items}) ) {
-                       $datastring .= $item->{$f};
-                       $f = $';
-               } elsif ( $f =~ /^([0-9a-z]{3})(\w)(\W*).*/ ) {
-                       $datastring .= $record->subfield($1,$2) . $3 if($record->subfield($1,$2)) ;
-                       $f = $';
-               } 
-               last if ( $f eq $last_f ); # failed to match
-       }
-       return $datastring;
+    my ( $f, $item, $record ) = @_;
+    my $kohatables = &_descKohaTables();
+    my $datastring = '';
+    my $match_kohatable = join(
+        '|',
+        (
+            @{ $kohatables->{biblio} },
+            @{ $kohatables->{biblioitems} },
+            @{ $kohatables->{items} }
+        )
+    );
+    while ($f) {  
+        $f =~ s/^\s?//;
+        if ( $f =~ /^'(.*)'.*/ ) {
+            # single quotes indicate a static text string.
+            $datastring .= $1;
+            $f = $';
+        }
+        elsif ( $f =~ /^($match_kohatable).*/ ) {
+            $datastring .= $item->{$f};
+            $f = $';
+        }
+        elsif ( $f =~ /^([0-9a-z]{3})(\w)(\W?).*?/ ) {
+            my ($field,$subf,$ws) = ($1,$2,$3);
+            my $subf_data;
+            my ($itemtag, $itemsubfieldcode) = &GetMarcFromKohaField("items.itemnumber",'');
+            my @marcfield = $record->field($field);
+            if(@marcfield) {
+                if($field eq $itemtag) {  # item-level data, we need to get the right item.
+                    foreach my $itemfield (@marcfield) {
+                        if ( $itemfield->subfield($itemsubfieldcode) eq $item->{'itemnumber'} ) {
+                            $datastring .= $itemfield->subfield($subf ) . $ws;
+                            last;
+                        }
+                    }
+                } else {  # bib-level data, we'll take the first matching tag/subfield.
+                    $datastring .= $marcfield[0]->subfield($subf) . $ws ;
+                }
+            }
+            $f = $';
+        }
+        else {
+            warn "failed to parse label formatstring: $f";
+            last;    # Failed to match
+        }
+    }
+    return $datastring;
 }
 
-=head descKohaTables
+=head2 descKohaTables
+
 Return a hashref of an array of hashes,
 with name,type keys.
+
 =cut
 
 sub _descKohaTables {
@@ -908,49 +949,78 @@ sub deduplicate_batch {
        return $killed, undef;
 }
 
+our $possible_decimal = qr/\d+(?:\.\d+)?/;
+
 sub split_lccn {
     my ($lccn) = @_;    
-    my ( $ll, $wnl, $dec, $cutter, $pubdate);
-
     $_ = $lccn;
-
-    # lccn example 'HE8700.7 .P6T44 1983';
-    my    @splits   = m/
-        (^[a-zA-Z]+)            # HE
-        ([0-9]+\.*[0-9]*)             # 8700.7
+    # lccn examples: 'HE8700.7 .P6T44 1983', 'BS2545.E8 H39 1996';
+    my (@parts) = m/
+        ^([a-zA-Z]+)      # HE          # BS
+        (\d+(?:\.\d)*)    # 8700.7      # 2545
         \s*
-        (\.*[a-zA-Z0-9]*)       # P6T44
+        (\.*\D+\d*)       # .P6         # .E8
         \s*
-        ([0-9]*)                # 1983
-        /x;  
-
-    # strip something occuring spaces too
-    $splits[0] =~ s/\s+$//;
-    $splits[1] =~ s/\s+$//;
-    $splits[2] =~ s/\s+$//;
-
-    # if the regex fails, then just return the whole string, 
-    # better than nothing
-    # FIXME It seems we should handle all cases, have some graceful error handling, or at least inform the caller of the failure to split
-    $splits[0] = $lccn if  $splits[0]  eq '' ;
-    return @splits;
+        (.*)              # T44 1983    # H39 1996   # everything else (except any bracketing spaces)
+        \s*
+        /x;
+    unless (scalar @parts)  {
+        $debug and print STDERR "split_lccn regexp failed to match string: $_\n";
+        push @parts, $_;     # if no match, just push the whole string.
+    }
+    push @parts, split /\s+/, pop @parts;   # split the last piece into an arbitrary number of pieces at spaces
+    $debug and print STDERR "split_lccn array: ", join(" | ", @parts), "\n";
+    return @parts;
 }
 
 sub split_ddcn {
     my ($ddcn) = @_;
-    $ddcn =~ s/\///g;   # in theory we should be able to simply remove all segmentation markers and arrive at the correct call number...
     $_ = $ddcn;
-    # ddcn example R220.3 H2793Z H32 c.2
-    my @splits = m/^([A-Z]{0,3})                # R (OS, REF, etc. up do three letters)
-                    ([0-9]+\.[0-9]*)            # 220.3
-                    \s?                         # space (not requiring anything beyond the call number)
-                    ([a-zA-Z0-9]*\.?[a-zA-Z0-9])# cutter number... maybe, but if so it is in this position (Z indicates literary criticism)
-                    \s?                         # space if it exists
-                    ([a-zA-Z]*\.?[0-9]*)        # other indicators such as cutter for author of literary criticism in this example if it exists
-                    \s?                         # space if ie exists
-                    ([a-zA-Z]*\.?[0-9]*)        # other indicators such as volume number, copy number, edition date, etc. if it exists
-                    /x;
-    return @splits;
+    s/\///g;   # in theory we should be able to simply remove all segmentation markers and arrive at the correct call number...
+    # ddcn examples: 'R220.3 H2793Z H32 c.2', 'BIO JP2 R5c.1'
+
+    my (@parts) = m/
+        ^([a-zA-Z-]+[0-9]?-?[a-zA-Z]*(?:$possible_decimal)?) # R220.3            # BIO   # first example will require extra splitting
+        \s*
+        (.+)                               # H2793Z H32 c.2   # R5c.1   # everything else (except bracketing spaces)
+        \s*
+        /x;
+    unless (scalar @parts)  {
+        $debug and print STDERR "split_ddcn regexp failed to match string: $_\n";
+        push @parts, $_;     # if no match, just push the whole string.
+    }
+
+    if ($parts[ 0] =~ /^([a-zA-Z]+)($possible_decimal)$/) {
+          shift @parts;         # pull off the mathching first element, like example 1
+        unshift @parts, $1, $2; # replace it with the two pieces
+    }
+
+    push @parts, split /\s+/, pop @parts;   # split the last piece into an arbitrary number of pieces at spaces
+
+    if ($parts[-1] !~ /^.*\d-\d.*$/ && $parts[-1] =~ /^(.*\d+)(\D.*)$/) {
+         pop @parts;            # pull off the mathching last element, like example 2
+        push @parts, $1, $2;    # replace it with the two pieces
+    }
+
+    $debug and print STDERR "split_ddcn array: ", join(" | ", @parts), "\n";
+    return @parts;
+}
+
+sub split_fcn {
+    my ($fcn) = @_;
+    my @fcn_split = ();
+    # Split fiction call numbers based on spaces
+    SPLIT_FCN:
+    while ($fcn) {
+        if ($fcn =~ m/([A-Za-z0-9]+\.?[0-9]?)(\W?).*?/x) {
+            push (@fcn_split, $1);
+            $fcn = $';
+        }
+        else {
+            last SPLIT_FCN;     # No match, break out of the loop
+        }
+    }
+    return @fcn_split;
 }
 
 sub DrawSpineText {
@@ -976,23 +1046,26 @@ sub DrawSpineText {
 
     my $vPos = ( $y_pos + ( $label_height - $top_text_margin ) );
 
-    my @str_fields = get_text_fields($layout_id, 'codes' );
+    my @str_fields = get_text_fields($layout_id, 'codes' );  
     my $record = GetMarcBiblio($$item->{biblionumber});
     # FIXME - returns all items, so you can't get data from an embedded holdings field.
     # TODO - add a GetMarcBiblio1item(bibnum,itemnum) or a GetMarcItem(itemnum).
 
     my $old_fontname = $fontname; # We need to keep track of the original font passed in...
-    my $cn_source = $$item->{'cn_source'}; 
+
+    # Grab the cn_source and if that is NULL, the DefaultClassificationSource syspref
+    my $cn_source = ($$item->{'cn_source'} ? $$item->{'cn_source'} : C4::Context->preference('DefaultClassificationSource'));
     for my $field (@str_fields) {
         $field->{'code'} or warn "get_text_fields($layout_id, 'codes') element missing 'code' field";
-        if ($$conf_data->{'formatstring'}) {
-                $field->{'data'} =  GetBarcodeData($field->{'code'},$$item,$record) ;
-        }
-        elsif ($field->{'code'} eq 'itemtype') {
+        if ($field->{'code'} eq 'itemtype') {
             $field->{'data'} = C4::Context->preference('item-level_itypes') ? $$item->{'itype'} : $$item->{'itemtype'};
         }
+        elsif ($$conf_data->{'formatstring'}) {
+            # if labels_conf.formatstring has a value, then it overrides the  hardcoded option.
+            $field->{'data'} =  GetBarcodeData($field->{'code'},$$item,$record) ;
+        }
         else {
-                $field->{data} =   $$item->{$field->{'code'}}  ;
+            $field->{data} =   $$item->{$field->{'code'}}  ;
         }
         # This allows us to print the title in italic (oblique) type... (Times Roman has a different nomenclature.)
         # It seems there should be a better way to handle fonts in the label/patron card tool altogether -fbcit
@@ -1000,6 +1073,7 @@ sub DrawSpineText {
         my $font = prFont($fontname);
         # if the display option for this field is selected in the DB,
         # and the item record has some values for this field, display it.
+        # Or if there is a csv list of fields to display, display them.
         if ( ($$conf_data->{'formatstring'}) || ( $$conf_data->{$field->{code}} && $$item->{$field->{code}} ) ) {
             # get the string
             my $str = $field->{data} ;
@@ -1007,11 +1081,16 @@ sub DrawSpineText {
             $str =~ s/\n//g;
             $str =~ s/\r//g;
             my @strings;
-            if ($field->{code} eq 'itemcallnumber' and $printingtype eq 'BIB') { # If the field contains the call number, we do some special processing on it here...
+            my @callnumber_list = ('itemcallnumber', '050a', '050b', '082a', '952o'); # Fields which hold call number data  ( 060? 090? 092? 099? )
+            if ((grep {$field->{code} =~ m/$_/} @callnumber_list) and ($printingtype eq 'BIB') and ($$conf_data->{'callnum_split'})) { # If the field contains the call number, we do some sp
                 if ($cn_source eq 'lcc') {
                     @strings = split_lccn($str);
+                    @strings = split_fcn($str) if !@strings;    # If it was not a true lccn, try it as a fiction call number
+                    push (@strings, $str) if !@strings;         # If it was not that, send it on unsplit
                 } elsif ($cn_source eq 'ddc') {
                     @strings = split_ddcn($str);
+                    @strings = split_fcn($str) if !@strings;
+                    push (@strings, $str) if !@strings;
                 } else {
                     # FIXME Need error trapping here; something to be informative to the user perhaps -crn
                     push @strings, $str;
@@ -1037,13 +1116,16 @@ sub DrawSpineText {
             }
             # loop for each string line
             foreach my $str (@strings) {
+                next if $str eq '';
                 my $hPos = 0;
-                if ( $printingtype eq 'BIB' ) { #FIXME: This is a hack and needs to be implimented as a text justification option in the template...
-                    # some code to try and center each line on the label based on font size and string point width...
-                    my $stringwidth = prStrWidth($str, $fontname, $fontsize);
-                    my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
-                    $hPos = ( ( $whitespace  / 2 ) + $x_pos + $left_text_margin );
-                    #warn "\$label_width=$label_width \$stringwidth=$stringwidth \$whitespace=$whitespace \$left_text_margin=$left_text_margin for $str\n";
+                my $stringwidth = prStrWidth($str, $fontname, $fontsize);
+                if ( $$conf_data->{'text_justify'} eq 'R' ) { 
+                    $hPos = $x_pos + $label_width - ( $left_text_margin + $stringwidth );
+                } elsif($$conf_data->{'text_justify'} eq 'C') {
+                     # some code to try and center each line on the label based on font size and string point width...
+                     my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
+                     $hPos = ( ( $whitespace  / 2 ) + $x_pos + $left_text_margin );
+                #warn "\$label_width=$label_width \$stringwidth=$stringwidth \$whitespace=$whitespace \$left_text_margin=$left_text_margin for $str\n";
                 } else {
                     $hPos = ( $x_pos + $left_text_margin );
                 }
@@ -1120,6 +1202,7 @@ sub DrawBarcode {
                 ySize         => ( .02 * $height ),
                 xSize         => $xsize_ratio,
                 hide_asterisk => 1,
+                mode          => 'graphic',  # the only other option here is Type3...
             );
         };
         if ($@) {
@@ -1145,6 +1228,7 @@ sub DrawBarcode {
                 ySize         => ( .02 * $height ),
                 xSize         => $xsize_ratio,
                 hide_asterisk => 1,
+                mode          => 'graphic',  # the only other option here is Type3...
             );
         };
 
@@ -1170,7 +1254,8 @@ sub DrawBarcode {
                 ySize         => ( .02 * $height ),
                 xSize         => $xsize_ratio,
                 hide_asterisk => 1,
-                               text         => 0, 
+               text          => 0, 
+                mode          => 'graphic',  # the only other option here is Type3...
             );
         };
 
@@ -1224,7 +1309,7 @@ sub DrawBarcode {
     warn "BAR_WDTH = $bar_width, TOT.BAR.LGHT=$tot_bar_length  R*TOT.BAR =$moo2" if $debug;
 }
 
-=item build_circ_barcode;
+=head2 build_circ_barcode;
 
   build_circ_barcode( $x_pos, $y_pos, $barcode,
                $barcodetype, \$item);
@@ -1288,6 +1373,7 @@ sub build_circ_barcode {
                 xSize => .85,
 
                 ySize => 1.3,
+                mode            => 'graphic',  # the only other option here is Type3...
             );
         };
         if ($@) {
@@ -1520,7 +1606,7 @@ sub build_circ_barcode {
 
 }
 
-=item draw_boundaries
+=head2 draw_boundaries
 
  sub draw_boundaries ($x_pos_spine, $x_pos_circ1, $x_pos_circ2,
                 $y_pos, $spine_width, $label_height, $circ_width)  
@@ -1554,7 +1640,7 @@ sub draw_boundaries {
     }
 }
 
-=item drawbox
+=head2 drawbox
 
        sub drawbox {   $lower_left_x, $lower_left_y, 
                        $upper_right_x, $upper_right_y )
@@ -1592,8 +1678,6 @@ END { }    # module clean-up code here (global destructor)
 1;
 __END__
 
-=back
-
 =head1 AUTHOR
 
 Mason James <mason@katipo.co.nz>