Merge remote-tracking branch 'origin/new/bug_7805'
[koha.git] / C4 / Creators / Layout.pm
1 package C4::Creators::Layout;
2
3 use strict;
4 use warnings;
5
6 use autouse 'Data::Dumper' => qw(Dumper);
7
8 use C4::Context;
9 use C4::Debug;
10 use C4::Creators::PDF;
11
12 BEGIN {
13     use version; our $VERSION = qv('3.07.00.049');
14 }
15
16 # FIXME: Consider this style parameter verification instead...
17 #  my %param = @_;
18 #   for (keys %param)
19 #    {   my $lc = lc($_);
20 #        if (exists $default{$lc})
21 #        {  $default{$lc} = $param{$_};
22 #        }
23 #        else
24 #        {  print STDERR "Unknown parameter $_ , not used \n";
25 #        }
26 #    }
27
28 sub _check_params {
29     my $exit_code = 0;
30     my @valtmpl_id_params = (
31         'layout_id',
32         'barcode_type',
33         'printing_type',
34         'layout_name',
35         'guidebox',
36         'font',
37         'font_size',
38         'callnum_split',
39         'text_justify',
40         'format_string',
41         'layout_xml',           # FIXME: all layouts should be stored in xml format to greatly simplify handling -chris_n
42         'creator',
43         'units',
44         'start_label',
45     );
46     if (scalar(@_) >1) {
47         my %given_params = @_;
48         foreach my $key (keys %given_params) {
49             if (!(grep m/$key/, @valtmpl_id_params)) {
50                 warn sprintf('(Multiple parameters) Unrecognized parameter type of "%s".', $key);
51                 $exit_code = 1;
52             }
53         }
54     }
55     else {
56         if (!(grep m/$_/, @valtmpl_id_params)) {
57             warn sprintf('(Single parameter) Unrecognized parameter type of "%s".', $_);
58             $exit_code = 1;
59         }
60     }
61     return $exit_code;
62 }
63
64 use constant PRESET_FIELDS => [qw(title author isbn issn itemtype barcode itemcallnumber)];
65 sub new {
66     my $invocant = shift;
67     my $self = '';
68     if (_check_params(@_) eq 1) {
69         return -1;
70     }
71     my $type = ref($invocant) || $invocant;
72     if (grep {$_ eq 'Labels'} @_) {
73        $self = {
74             layout_xml      =>      '',
75             units           =>      'POINT',
76             start_label     =>      1,
77             barcode_type    =>      'CODE39',
78             printing_type   =>      'BAR',
79             layout_name     =>      'DEFAULT',
80             guidebox        =>      0,
81             font            =>      'TR',
82             font_size       =>      3,
83             callnum_split   =>      0,
84             text_justify    =>      'L',
85             format_string   =>      join(', ', @{ PRESET_FIELDS() }),
86             @_,
87         };
88     }
89     elsif (grep {$_ eq 'Patroncards'} @_) {
90         $self = {
91             layout_xml => '<opt>Default Layout</opt>',
92             @_,
93         }
94     }
95     bless ($self, $type);
96     return $self;
97 }
98
99 sub retrieve {
100     my $invocant = shift;
101     my %opts = @_;
102     my $type = ref($invocant) || $invocant;
103     my $query = "SELECT * FROM creator_layouts WHERE layout_id = ? AND creator = ?";
104     my $sth = C4::Context->dbh->prepare($query);
105     $sth->execute($opts{'layout_id'}, $opts{'creator'});
106     if ($sth->err) {
107         warn sprintf('Database returned the following error: %s', $sth->errstr);
108         return -1;
109     }
110     my $self = $sth->fetchrow_hashref;
111     bless ($self, $type);
112     return $self;
113 }
114
115 sub delete {
116     my $self = {};
117     my %opts = ();
118     my $call_type = '';
119     my @params = ();
120     if (ref($_[0])) {
121         $self = shift;  # check to see if this is a method call
122         $call_type = 'C4::Labels::Layout->delete';
123         push @params, $self->{'layout_id'}, $self->{'creator'};
124     }
125     else {
126         my $class = shift;
127         %opts = @_;
128         $call_type = $class . '::delete';
129         push @params, $opts{'layout_id'}, $opts{'creator'};
130     }
131     if (scalar(@params) < 2) {   # If there is no layout id or creator type then we cannot delete it
132         warn sprintf('%s : Cannot delete layout as the profile id is invalid or non-existant.', $call_type) if !$params[0];
133         warn sprintf('%s : Cannot delete layout as the creator type is invalid or non-existant.', $call_type) if !$params[1];
134         return -1;
135     }
136     my $query = "DELETE FROM creator_layouts WHERE layout_id = ? AND creator = ?";
137     my $sth = C4::Context->dbh->prepare($query);
138     $sth->execute(@params);
139     if ($sth->err) {
140         warn sprintf('Database returned the following error on attempted DELETE: %s', $sth->errstr);
141         return -1;
142     }
143 }
144
145 sub save {
146     my $self = shift;
147     if ($self->{'layout_id'}) {        # if we have an id, the record exists and needs UPDATE
148         my @params;
149         my $query = "UPDATE creator_layouts SET ";
150         foreach my $key (keys %{$self}) {
151             next if ($key eq 'layout_id') || ($key eq 'creator');
152             push (@params, $self->{$key});
153             $query .= "$key=?, ";
154         }
155         $query = substr($query, 0, (length($query)-2));
156         $query .= " WHERE layout_id=? AND creator = ?;";
157         push (@params, $self->{'layout_id'}, $self->{'creator'});
158         my $sth = C4::Context->dbh->prepare($query);
159         #local $sth->{TraceLevel} = "3";        # enable DBI trace and set level; outputs to STDERR
160         $sth->execute(@params);
161         if ($sth->err) {
162             warn sprintf('Database returned the following error: %s', $sth->errstr);
163             return -1;
164         }
165         return $self->{'layout_id'};
166     }
167     else {                      # otherwise create a new record
168         my @params;
169         my $query = "INSERT INTO creator_layouts (";
170         foreach my $key (keys %{$self}) {
171             push (@params, $self->{$key});
172             $query .= "$key, ";
173         }
174         $query = substr($query, 0, (length($query)-2));
175         $query .= ") VALUES (";
176         for (my $i=1; $i<=(scalar keys %$self); $i++) {
177             $query .= "?,";
178         }
179         $query = substr($query, 0, (length($query)-1));
180         $query .= ");";
181         my $sth = C4::Context->dbh->prepare($query);
182         $sth->execute(@params);
183         if ($sth->err) {
184             warn sprintf('Database returned the following error: %s', $sth->errstr);
185             return -1;
186         }
187         my $sth1 = C4::Context->dbh->prepare("SELECT MAX(layout_id) FROM creator_layouts;");
188         $sth1->execute();
189         my $id = $sth1->fetchrow_array;
190         $self->{'layout_id'} = $id;
191         return $id;
192     }
193 }
194
195 sub get_attr {
196     my $self = shift;
197     if (_check_params(@_) eq 1) {
198         return -1;
199     }
200     my ($attr) = @_;
201     if (exists($self->{$attr})) {
202         return $self->{$attr};
203     }
204     else {
205         return -1;
206     }
207     return;
208 }
209
210 sub set_attr {
211     my $self = shift;
212     if (_check_params(@_) eq 1) {
213         return -1;
214     }
215     my %attrs = @_;
216     foreach my $attrib (keys(%attrs)) {
217         $self->{$attrib} = $attrs{$attrib};
218     };
219     return 0;
220 }
221
222 sub get_text_wrap_cols {
223     my $self = shift;
224     my %params = @_;
225     my $string = '';
226     my $strwidth = 0;
227     my $col_count = 0;
228     my $textlimit = $params{'label_width'} - (( 3 * $params{'left_text_margin'} ) || 13.5 );
229
230     while ($strwidth < $textlimit) {
231         $string .= '0';
232         $col_count++;
233         $strwidth = C4::Creators::PDF->StrWidth( $string, $self->{'font'}, $self->{'font_size'} );
234     }
235     return $col_count;
236 }
237
238 1;
239 __END__
240
241 =head1 NAME
242
243 C4::Labels::Layout -A class for creating and manipulating layout objects in Koha
244
245 =head1 ABSTRACT
246
247 This module provides methods for creating, retrieving, and otherwise manipulating label layout objects used by Koha to create and export labels.
248
249 =head1 METHODS
250
251 =head2 new()
252
253     Invoking the I<new> method constructs a new layout object containing the default values for a layout.
254     The following parameters are optionally accepted as key => value pairs:
255
256         C<barcode_type>         Defines the barcode type to be used on labels. NOTE: At present only the following barcode types are supported in the label creator code:
257
258 =over 9
259
260 =item .
261             CODE39          = Code 3 of 9
262
263 =item .
264             CODE39MOD       = Code 3 of 9 with modulo 43 checksum
265
266 =item .
267             CODE39MOD10     = Code 3 of 9 with modulo 10 checksum
268
269 =item .
270             COOP2OF5        = A varient of 2 of 5 barcode based on NEC's "Process 8000" code
271
272 =item .
273             INDUSTRIAL2OF5  = The standard 2 of 5 barcode (a binary level bar code developed by Identicon Corp. and Computer Identics Corp. in 1970)
274
275 =back
276
277         C<printing_type>        Defines the general layout to be used on labels. NOTE: At present there are only five printing types supported in the label creator code:
278
279 =over 9
280
281 =item .
282 BIB     = Only the bibliographic data is printed
283
284 =item .
285 BARBIB  = Barcode proceeds bibliographic data
286
287 =item .
288 BIBBAR  = Bibliographic data proceeds barcode
289
290 =item .
291 ALT     = Barcode and bibliographic data are printed on alternating labels
292
293 =item .
294 BAR     = Only the barcode is printed
295
296 =back
297
298         C<layout_name>          The descriptive name for this layout.
299         C<guidebox>             Setting this to '1' will result in a guide box being drawn around the labels marking the edge of each label
300         C<font>                 Defines the type of font to be used on labels. NOTE: The following fonts are available by default on most systems:
301
302 =over 9
303
304 =item .
305 TR      = Times-Roman
306
307 =item .
308 TB      = Times Bold
309
310 =item .
311 TI      = Times Italic
312
313 =item .
314 TBI     = Times Bold Italic
315
316 =item .
317 C       = Courier
318
319 =item .
320 CB      = Courier Bold
321
322 =item .
323 CO      = Courier Oblique (Italic)
324
325 =item .
326 CBO     = Courier Bold Oblique
327
328 =item .
329 H       = Helvetica
330
331 =item .
332 HB      = Helvetica Bold
333
334 =item .
335 HBO     = Helvetical Bold Oblique
336
337 =back
338
339         C<font_size>            Defines the size of the font in postscript points to be used on labels
340         C<callnum_split>        Setting this to '1' will enable call number splitting on labels
341         C<text_justify>         Defines the text justification to be used on labels. NOTE: The following justification styles are currently supported by label creator code:
342
343 =over 9
344
345 =item .
346 L       = Left
347
348 =item .
349 C       = Center
350
351 =item .
352 R       = Right
353
354 =back
355
356         C<format_string>        Defines what fields will be printed and in what order they will be printed on labels. These include any of the data fields that may be mapped
357                                 to your MARC frameworks. Specify MARC subfields as a 4-character tag-subfield string: ie. 254a Enclose a whitespace-separated list of fields
358                                 to concatenate on one line in double quotes. ie. "099a 099b" or "itemcallnumber barcode" Static text strings may be entered in single-quotes:
359                                 ie. 'Some static text here.'
360
361     example:
362         C<my $layout = Layout->new(); # Creates and returns a new layout object>
363
364         C<my $layout = C4::Labels::Layout->new(barcode_type => 'CODE39', printing_type => 'BIBBAR', font => 'C', font_size => 6); # Creates and returns a new layout object using
365             the supplied values to override the defaults>
366
367     B<NOTE:> This layout is I<not> written to the database until save() is invoked. You have been warned!
368
369 =head2 retrieve(layout_id => layout_id)
370
371     Invoking the I<retrieve> method constructs a new layout object containing the current values for layout_id. The method returns a new object upon success and 1 upon failure.
372     Errors are logged to the Apache log.
373
374     example:
375         C<my $layout = Layout->retrieve(layout_id => 1); # Retrieves layout record 1 and returns an object containing the record>
376
377 =head2 delete()
378
379     Invoking the delete method attempts to delete the layout from the database. The method returns 0 upon success and -1 upon failure. Errors are logged to the Apache log.
380     NOTE: This method may also be called as a function and passed a key/value pair simply deleteing that template from the database. See the example below.
381
382     examples:
383         C<my $exitstat = $layout->delete(); # to delete the record behind the $layout object>
384         C<my $exitstat = Layout->delete(layout_id => 1); # to delete layout record 1>
385
386 =head2 save()
387
388     Invoking the I<save> method attempts to insert the layout into the database if the layout is new and update the existing layout record if the layout exists.
389     The method returns the new record id upon success and -1 upon failure (This avoids conflicting with a record id of 1). Errors are logged to the Apache log.
390
391     example:
392         C<my $exitstat = $layout->save(); # to save the record behind the $layout object>
393
394 =head2 get_attr($attribute)
395
396     Invoking the I<get_attr> method will return the value of the requested attribute or -1 on errors.
397
398     example:
399         C<my $value = $layout->get_attr($attribute);>
400
401 =head2 set_attr(attribute => value, attribute_2 => value)
402
403     Invoking the I<set_attr> method will set the value of the supplied attributes to the supplied values. The method accepts key/value pairs separated by
404     commas.
405
406     example:
407         C<$layout->set_attr(attribute => value);>
408
409 =head2 get_text_wrap_cols()
410
411     Invoking the I<get_text_wrap_cols> method will return the number of columns that can be printed on the label before wrapping to the next line.
412
413     examples:
414         C<my $text_wrap_cols = $layout->get_text_wrap_cols();>
415
416 =head1 AUTHOR
417
418 Chris Nighswonger <cnighswonger AT foundations DOT edu>
419
420 =head1 COPYRIGHT
421
422 Copyright 2009 Foundations Bible College.
423
424 =head1 LICENSE
425
426 This file is part of Koha.
427
428 Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
429 Foundation; either version 2 of the License, or (at your option) any later version.
430
431 You should have received a copy of the GNU General Public License along with Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
432 Fifth Floor, Boston, MA 02110-1301 USA.
433
434 =head1 DISCLAIMER OF WARRANTY
435
436 Koha is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
437 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
438
439 =cut