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