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