Bug 4450 Use more consistent error returns in C4/Creators/*
[koha-ffzg.git] / C4 / Creators / Template.pm
1 package C4::Creators::Template;
2
3 use strict;
4 use warnings;
5 use POSIX qw(ceil);
6 use autouse 'Data::Dumper' => qw(Dumper);
7
8 use C4::Context;
9 use C4::Debug;
10 use C4::Creators::Profile 1.000000;
11 use C4::Creators::Lib 1.000000 qw(get_unit_values);
12
13 BEGIN {
14     use version; our $VERSION = qv('1.0.0_1');
15 }
16
17 sub _check_params {
18     shift if $_[0] =~ m/::/; # this seems a bit hackish
19     my $given_params = {};
20     my $exit_code = 0;
21     my @valid_template_params = (
22         'profile_id',
23         'template_code',
24         'template_desc',
25         'page_width',
26         'page_height',
27         'label_width',
28         'label_height',
29         'card_width',
30         'card_height',
31         'top_text_margin',
32         'left_text_margin',
33         'top_margin',
34         'left_margin',
35         'cols',
36         'rows',
37         'col_gap',
38         'row_gap',
39         'units',
40         'creator',
41         'current_label',
42     );
43     if (scalar(@_) >1) {
44         $given_params = {@_};
45         foreach my $key (keys %{$given_params}) {
46             if (!(grep m/$key/, @valid_template_params)) {
47                 warn sprintf('Unrecognized parameter type of "%s".', $key);
48                 $exit_code = 1;
49             }
50         }
51     }
52     else {
53         if (!(grep m/$_/, @valid_template_params)) {
54             warn sprintf('Unrecognized parameter type of "%s".', $_);
55             $exit_code = 1;
56         }
57     }
58     return $exit_code;
59 }
60
61 sub _conv_points {
62     my $self = shift;
63     my @unit_value = grep {$_->{'type'} eq $self->{'units'}} @{get_unit_values()};
64     $self->{'page_width'}         = $self->{'page_width'} * $unit_value[0]->{'value'};
65     $self->{'page_height'}        = $self->{'page_height'} * $unit_value[0]->{'value'};
66     $self->{'label_width'}        = $self->{'label_width'} * $unit_value[0]->{'value'};
67     $self->{'label_height'}       = $self->{'label_height'} * $unit_value[0]->{'value'};
68     $self->{'top_text_margin'}    = $self->{'top_text_margin'} * $unit_value[0]->{'value'};
69     $self->{'left_text_margin'}   = $self->{'left_text_margin'} * $unit_value[0]->{'value'};
70     $self->{'top_margin'}         = $self->{'top_margin'} * $unit_value[0]->{'value'};
71     $self->{'left_margin'}        = $self->{'left_margin'} * $unit_value[0]->{'value'};
72     $self->{'col_gap'}            = $self->{'col_gap'} * $unit_value[0]->{'value'};
73     $self->{'row_gap'}            = $self->{'row_gap'} * $unit_value[0]->{'value'};
74     return $self;
75 }
76
77 sub _apply_profile {
78     my $self = shift;
79     my $creator = shift;
80     my $profile = C4::Creators::Profile->retrieve(profile_id => $self->{'profile_id'}, creator => $creator, convert => 1);
81     $self->{'top_margin'} = $self->{'top_margin'} + $profile->get_attr('offset_vert');      # controls vertical offset
82     $self->{'left_margin'} = $self->{'left_margin'} + $profile->get_attr('offset_horz');    # controls horizontal offset
83     $self->{'label_height'} = $self->{'label_height'} + $profile->get_attr('creep_vert');   # controls vertical creep
84     $self->{'label_width'} = $self->{'label_width'} + $profile->get_attr('creep_horz');     # controls horizontal creep
85     return $self;
86 }
87
88 sub new {
89     my $invocant = shift;
90     my $type = ref($invocant) || $invocant;
91     if (_check_params(@_) eq 1) {
92         return;
93     }
94     my $self = {
95         profile_id      =>      0,
96         template_code   =>      'DEFAULT TEMPLATE',
97         template_desc   =>      'Default description',
98         page_width      =>      0,
99         page_height     =>      0,
100         label_width     =>      0,
101         label_height    =>      0,
102         top_text_margin =>      0,
103         left_text_margin =>     0,
104         top_margin      =>      0,
105         left_margin     =>      0,
106         cols            =>      0,
107         rows            =>      0,
108         col_gap         =>      0,
109         row_gap         =>      0,
110         units           =>      'POINT',
111         template_stat   =>      0,      # false if any data has changed and the db has not been updated
112         @_,
113     };
114     bless ($self, $type);
115     return $self;
116 }
117
118 sub retrieve {
119     my $invocant = shift;
120     my %opts = @_;
121     my $type = ref($invocant) || $invocant;
122     my $query = "SELECT * FROM " . $opts{'table_name'} . " WHERE template_id = ? AND creator = ?";
123     my $sth = C4::Context->dbh->prepare($query);
124     $sth->execute($opts{'template_id'}, $opts{'creator'});
125     if ($sth->err) {
126         warn sprintf('Database returned the following error: %s', $sth->errstr);
127         return;
128     }
129     my $self = $sth->fetchrow_hashref;
130     $self = _conv_points($self) if (($opts{convert} && $opts{convert} == 1) || $opts{profile_id});
131     $self = _apply_profile($self, $opts{'creator'}) if $opts{profile_id} && $self->{'profile_id'};        # don't bother if there is no profile_id
132     $self->{'template_stat'} = 1;
133     bless ($self, $type);
134     return $self;
135 }
136
137 sub delete {
138     my $self = {};
139     my %opts = ();
140     my $call_type = '';
141     my @query_params = ();
142     if (ref($_[0])) {
143         $self = shift;  # check to see if this is a method call
144         $call_type = 'C4::Labels::Template->delete';
145         push @query_params, $self->{'template_id'}, $self->{'creator'};
146     }
147     else {
148         %opts = @_;
149         $call_type = 'C4::Labels::Template::delete';
150         push @query_params, $opts{'template_id'}, $opts{'creator'};
151     }
152     if (scalar(@query_params) < 2) {   # If there is no template id or creator type then we cannot delete it
153         warn sprintf('%s : Cannot delete template as the template id is invalid or non-existant.', $call_type) if !$query_params[0];
154         warn sprintf('%s : Cannot delete template as the creator type is invalid or non-existant.', $call_type) if !$query_params[1];
155         return;
156     }
157     my $query = "DELETE FROM " . $opts{'table_name'} . " WHERE template_id = ? AND creator = ?";
158     my $sth = C4::Context->dbh->prepare($query);
159     $sth->execute(@query_params);
160     $self->{'template_stat'} = 0;
161 }
162
163 sub save {
164     my $self = shift;
165     my %opts = @_;
166     if ($self->{'template_id'}) {        # if we have an template_id, the record exists and needs UPDATE
167         my @params;
168         my $query = "UPDATE " . $opts{'table_name'} . " SET ";
169         foreach my $key (keys %{$self}) {
170             next if ($key eq 'template_id') || ($key eq 'template_stat') || ($key eq 'creator');
171             push (@params, $self->{$key});
172             $query .= "$key=?, ";
173         }
174         $query = substr($query, 0, (length($query)-2));
175         push (@params, $self->{'template_id'}, $self->{'creator'});
176         $query .= " WHERE template_id=? AND creator=?;";
177         my $sth = C4::Context->dbh->prepare($query);
178         $sth->execute(@params);
179         if ($sth->err) {
180             warn sprintf('Database returned the following error: %s', $sth->errstr);
181             return;
182         }
183         $self->{'template_stat'} = 1;
184         return $self->{'template_id'};
185     }
186     else {                      # otherwise create a new record
187         my @params;
188         my $query = "INSERT INTO " . $opts{'table_name'} ." (";
189         foreach my $key (keys %{$self}) {
190             next if $key eq 'template_stat';
191             push (@params, $self->{$key});
192             $query .= "$key, ";
193         }
194         $query = substr($query, 0, (length($query)-2));
195         $query .= ") VALUES (";
196         for (my $i=1; $i<=((scalar keys %$self) - 1); $i++) {   # key count less keys not db related...
197             $query .= "?,";
198         }
199         $query = substr($query, 0, (length($query)-1));
200         $query .= ");";
201         my $sth = C4::Context->dbh->prepare($query);
202         $sth->execute(@params);
203         if ($sth->err) {
204             warn sprintf('Database returned the following error: %s', $sth->errstr);
205             return;
206         }
207         my $sth1 = C4::Context->dbh->prepare("SELECT MAX(template_id) FROM " . $opts{'table_name'} . ";");
208         $sth1->execute();
209         my $template_id = $sth1->fetchrow_array;
210         $self->{'template_id'} = $template_id;
211         $self->{'template_stat'} = 1;
212         return $template_id;
213     }
214 }
215
216 sub get_attr {
217     my $self = shift;
218     if (_check_params(@_) eq 1) {
219         return;
220     }
221     my ($attr) = @_;
222     if (exists($self->{$attr})) {
223         return $self->{$attr};
224     }
225     return;
226 }
227
228 sub set_attr {
229     my $self = shift;
230     if (_check_params(@_) eq 1) {
231         return;
232     }
233     my %attrs = @_;
234     foreach my $attrib (keys(%attrs)) {
235         $self->{$attrib} = $attrs{$attrib};
236     };
237 }
238
239 sub get_label_position {
240     my ($self, $start_label) = @_;
241     my $current_label = $self->{'current_label'};
242     if ($start_label eq 1) {
243         $current_label->{'row_count'} = 1;
244         $current_label->{'col_count'} = 1;
245         $current_label->{'llx'} = $self->{'left_margin'};
246         $current_label->{'lly'} = ($self->{'page_height'} - $self->{'top_margin'} - $self->{'label_height'});
247         $self->{'current_label'} = $current_label;
248         return ($current_label->{'row_count'}, $current_label->{'col_count'}, $current_label->{'llx'}, $current_label->{'lly'});
249     }
250     else {
251         $current_label->{'row_count'} = ceil($start_label / $self->{'cols'});
252         $current_label->{'col_count'} = ($start_label - (($current_label->{'row_count'} - 1) * $self->{'cols'}));
253         $current_label->{'llx'} = $self->{'left_margin'} + ($self->{'label_width'} * ($current_label->{'col_count'} - 1)) + ($self->{'col_gap'} * ($current_label->{'col_count'} - 1));
254         $current_label->{'lly'} = $self->{'page_height'} - $self->{'top_margin'} - ($self->{'label_height'} * $current_label->{'row_count'}) - ($self->{'row_gap'} * ($current_label->{'row_count'} - 1));
255         $self->{'current_label'} = $current_label;
256         return ($current_label->{'row_count'}, $current_label->{'col_count'}, $current_label->{'llx'}, $current_label->{'lly'});
257     }
258 }
259
260 sub get_next_label_pos {
261     my $self = shift;
262     my $current_label = $self->{'current_label'};
263     my $new_page = 0;
264     if ($current_label->{'col_count'} lt $self->get_attr('cols')) {
265         $current_label->{'llx'} = ($current_label->{'llx'} + $self->get_attr('label_width') + $self->get_attr('col_gap'));
266         $current_label->{'col_count'}++;
267     }
268     else {
269         $current_label->{'llx'} = $self->get_attr('left_margin');
270         if ($current_label->{'row_count'} eq $self->get_attr('rows')) {
271             $new_page = 1;
272             $current_label->{'lly'} = ($self->get_attr('page_height') - $self->get_attr('top_margin') - $self->get_attr('label_height'));
273             $current_label->{'row_count'} = 1;
274         }
275         else {
276             $current_label->{'lly'} = ($current_label->{'lly'} - $self->get_attr('row_gap') - $self->get_attr('label_height'));
277             $current_label->{'row_count'}++;
278         }
279         $current_label->{'col_count'} = 1;
280     }
281     return ($current_label->{'llx'}, $current_label->{'lly'}, $new_page);
282 }
283
284 1;
285 __END__
286
287 =head1 NAME
288
289 C4::Creators::Template - A class for creating and manipulating template objects in Koha
290
291 =head1 ABSTRACT
292
293 This module provides methods for creating, retrieving, and otherwise manipulating label template objects used by Koha.
294
295 =head1 METHODS
296
297 =head2 new()
298
299     Invoking the I<new> method constructs a new template object containing the default values for a template.
300     The following parameters are optionally accepted as key => value pairs:
301
302         C<profile_id>           A valid profile id to be assciated with this template. NOTE: The profile must exist in the database and B<not> be assigned to another template.
303         C<template_code>        A template code. ie. 'Avery 5160 | 1 x 2-5/8'
304         C<template_desc>        A readable description of the template. ie. '3 columns, 10 rows of labels'
305         C<page_width>           The width of the page measured in the units supplied by the units parameter in this template.
306         C<page_height>          The height of the page measured in the same units.
307         C<label_width>          The width of a single label on the page this template applies to.
308         C<label_height>         The height of a single label on the page.
309         C<top_text_margin>      The measure of the top margin on a single label on the page.
310         C<left_text_margin>     The measure of the left margin on a single label on the page.
311         C<top_margin>           The measure of the top margin of the page.
312         C<left_margin>          The measure of the left margin of the page.
313         C<cols>                 The number of columns of labels on the page.
314         C<rows>                 The number of rows of labels on the page.
315         C<col_gap>              The measure of the gap between the columns of labels on the page.
316         C<row_gap>              The measure of the gap between the rows of labels on the page.
317         C<units>                The units of measure used for this template. These B<must> match the measures you supply above or
318                                 bad things will happen to your document. NOTE: The only supported units at present are:
319
320 =over 9
321
322 =item .
323 POINT   = Postscript Points (This is the base unit in the Koha label creator.)
324
325 =item .
326 AGATE   = Adobe Agates (5.1428571 points per)
327
328 =item .
329 INCH    = US Inches (72 points per)
330
331 =item .
332 MM      = SI Millimeters (2.83464567 points per)
333
334 =item .
335 CM      = SI Centimeters (28.3464567 points per)
336
337 =back
338
339     example:
340         my $template = Template->new(); # Creates and returns a new template object with the defaults
341
342         my $template = C4::Labels::Template->new(profile_id => 1, page_width => 8.5, page_height => 11.0, units => 'INCH'); # Creates and returns a new template object using
343             the supplied values to override the defaults
344
345     B<NOTE:> This template is I<not> written to the database untill save() is invoked. You have been warned!
346
347 =head2 retrieve(template_id => $template_id)
348
349     Invoking the I<retrieve> method constructs a new template object containing the current values for template_id. The method returns
350     a new object upon success and -1 upon failure. Errors are logged to the Apache log. Two further options may be accessed. See the example
351     below for further description.
352
353     examples:
354
355         C<my $template = C4::Labels::Template->retrieve(template_id => 1); # Retrieves template record 1 and returns an object containing the record>
356
357         C<my $template = C4::Labels::Template->retrieve(template_id => 1, convert => 1); # Retrieves template record 1, converts the units to points,
358             and returns an object containing the record>
359
360         C<my $template = C4::Labels::Template->retrieve(template_id => 1, profile_id => 1); # Retrieves template record 1, converts the units
361             to points, applies the currently associated profile id, and returns an object containing the record.>
362
363 =head2 delete()
364
365     Invoking the delete method attempts to delete the template from the database. The method returns -1 upon failure. Errors are logged to the Apache log.
366     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.
367
368     examples:
369         C<my $exitstat = $template->delete(); # to delete the record behind the $template object>
370         C<my $exitstat = C4::Labels::Template::delete(template_id => 1); # to delete template record 1>
371
372 =head2 save()
373
374     Invoking the I<save> method attempts to insert the template into the database if the template is new and update the existing template record if
375     the template exists. The method returns the new record template_id upon success and undef upon failure (This avoids template_ids conflicting with a
376     record template_id of 1). Errors are logged to the Apache log.
377
378     example:
379         C<my $template_id = $template->save(); # to save the record behind the $template object>
380
381 =head2 get_attr($attribute)
382
383     Invoking the I<get_attr> method will return the value of the requested attribute or undef on errors.
384
385     example:
386         C<my $value = $template->get_attr($attribute);>
387
388 =head2 set_attr(attribute => value, attribute_2 => value)
389
390     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
391     commas.
392
393     example:
394         C<$template->set_attr(attribute => value);>
395
396 =head2 get_label_position($start_label)
397
398     Invoking the I<get_label_position> method will return the row, column coordinates on the starting page and the lower left x,y coordinates on the starting
399     label for the template object.
400
401     examples:
402         C<my ($row_count, $col_count, $llx, $lly) = $template->get_label_position($start_label);>
403
404 =head1 AUTHOR
405
406 Chris Nighswonger <cnighswonger AT foundations DOT edu>
407
408 =head1 COPYRIGHT
409
410 Copyright 2009 Foundations Bible College.
411
412 =head1 LICENSE
413
414 This file is part of Koha.
415
416 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
417 Foundation; either version 2 of the License, or (at your option) any later version.
418
419 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,
420 Fifth Floor, Boston, MA 02110-1301 USA.
421
422 =head1 DISCLAIMER OF WARRANTY
423
424 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
425 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
426
427 =cut