6a570fa92226e005335ddb4c9d9b105f53cc8ad4
[kivitendo-erp.git] / SL / Helper / CreatePDF.pm
1 package SL::Helper::CreatePDF;
2
3 use strict;
4
5 use Carp;
6 use Cwd;
7 use English qw(-no_match_vars);
8 use File::Slurp ();
9 use File::Spec  ();
10 use File::Temp  ();
11 use File::Copy qw(move);
12 use List::MoreUtils qw(uniq);
13 use List::Util qw(first);
14 use String::ShellQuote ();
15
16 use SL::Common;
17 use SL::DB::Language;
18 use SL::DB::Printer;
19 use SL::MoreCommon;
20 use SL::System::Process;
21 use SL::Template;
22 use SL::Template::LaTeX;
23
24 use Exporter 'import';
25 our @EXPORT_OK = qw(create_pdf merge_pdfs find_template);
26 our %EXPORT_TAGS = (
27   all => \@EXPORT_OK,
28 );
29
30 sub create_pdf {
31   my ($class, %params) = @_;
32
33   return __PACKAGE__->create_parsed_file(
34     format        => 'pdf',
35     template_type => 'LaTeX',
36     %params,
37   );
38 }
39
40 sub create_parsed_file {
41   my ($class, %params) = @_;
42
43   my $keep_temp_files = $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files};
44   my $userspath       = SL::System::Process::exe_dir() . "/" . $::lx_office_conf{paths}->{userspath};
45   my $temp_dir        = File::Temp->newdir(
46     "kivitendo-print-XXXXXX",
47     DIR     => $userspath,
48     CLEANUP => !$keep_temp_files,
49   );
50
51   my $vars           = $params{variables} || {};
52   my $form           = Form->new('');
53   $form->{$_}        = $vars->{$_} for keys %{$vars};
54   $form->{format}    = lc($params{format} || 'pdf');
55   $form->{cwd}       = SL::System::Process::exe_dir();
56   $form->{templates} = $::instance_conf->get_templates;
57   $form->{IN}        = $params{template};
58   $form->{tmpdir}    = $temp_dir->dirname;
59   my $tmpdir         = $form->{tmpdir};
60   my ($suffix)       = $params{template} =~ m{\.(.+)};
61
62   my ($temp_fh, $tmpfile) = File::Temp::tempfile(
63     'kivitendo-printXXXXXX',
64     SUFFIX => ".${suffix}",
65     DIR    => $form->{tmpdir},
66     UNLINK => !$keep_temp_files,
67   );
68
69   $form->{tmpfile} = $tmpfile;
70   (undef, undef, $form->{template_meta}{tmpfile}) = File::Spec->splitpath($tmpfile);
71
72   my $parser               = SL::Template::create(
73     type                   => ($params{template_type} || 'LaTeX'),
74     source                 => $form->{IN},
75     form                   => $form,
76     myconfig               => \%::myconfig,
77     userspath              => $tmpdir,
78     variable_content_types => $params{variable_content_types},
79   );
80
81   my $result = $parser->parse($temp_fh);
82
83   close $temp_fh;
84   chdir $form->{cwd};
85
86   if (!$result) {
87     $form->cleanup;
88     die $parser->get_error;
89   }
90
91   # SL::Template:** modify $form->{tmpfile} by removing its
92   # $form->{userspath} prefix. They also store the final file's actual
93   # file name in $form->{tmpfile} – but it is now relative to
94   # $form->{userspath}. Other modules return the full file name…
95   my ($volume, $directory, $file_name) = File::Spec->splitpath($form->{tmpfile});
96   my $full_file_name                   = File::Spec->catfile($tmpdir, $file_name);
97   if (($params{return} || 'content') eq 'file_name') {
98     my $new_name = File::Spec->catfile($userspath, 'keep-' . $form->{tmpfile});
99     rename $full_file_name, $new_name;
100
101     $form->cleanup;
102
103     return $new_name;
104   }
105
106   my $content = File::Slurp::read_file($full_file_name);
107
108   $form->cleanup;
109
110   return $content;
111 }
112
113 #
114 # Alternativen zu pdfinfo wären (aber wesentlich langamer):
115 #
116 # gs  -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=/dev/null $filename | grep 'Processing pages'
117 # my (undef,undef,undef,undef,$pages)  = split / +/,$shell_out;
118 #
119 # gs  -dBATCH -dNOPAUSE -q -dNODISPLAY -c "($filename) (r) file runpdfbegin pdfpagecount = quit"
120 # $pages=$shell_out;
121 #
122
123 sub has_odd_pages {
124   my ($class, $filename) = @_;
125   return 0 unless -f $filename;
126   my $shell_out = `pdfinfo $filename | grep 'Pages:'`;
127   my ($label, $pages) = split / +/, $shell_out;
128   return $pages & 1;
129 }
130
131 sub merge_pdfs {
132   my ($class, %params) = @_;
133   my $filecount = scalar(@{ $params{file_names} });
134
135   if ($params{inp_content}) {
136     return $params{inp_content} if $filecount == 0 && !$params{out_path};
137   } elsif ($params{out_path}) {
138     return 0 if $filecount == 0;
139     if ($filecount == 1) {
140       if (!rename($params{file_names}->[0], $params{out_path})) {
141         # special filesystem or cross filesystem etc
142         move($params{file_names}->[0], $params{out_path});
143       }
144       return 1;
145     }
146   } else {
147     return '' if $filecount == 0;
148     return scalar(File::Slurp::read_file($params{file_names}->[0])) if $filecount == 1;
149   }
150
151   my ($temp_fh, $temp_name) = File::Temp::tempfile(
152     'kivitendo-printXXXXXX',
153     SUFFIX => '.pdf',
154     DIR    => $::lx_office_conf{paths}->{userspath},
155     UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
156   );
157   close $temp_fh;
158
159   my $input_names = '';
160   my $hasodd      = 0;
161   my $emptypage   = '';
162   if ($params{bothsided}) {
163     $emptypage = $::instance_conf->get_templates . '/emptyPage.pdf';
164     unless (-f $emptypage) {
165       $emptypage = '';
166       delete $params{bothsided};
167     }
168   }
169   if ($params{inp_content}) {
170     my ($temp_fh, $inp_name) = File::Temp::tempfile(
171       'kivitendo-contentXXXXXX',
172       SUFFIX => '.pdf',
173       DIR    => $::lx_office_conf{paths}->{userspath},
174       UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
175     );
176     binmode $temp_fh;
177     print $temp_fh $params{inp_content};
178     close $temp_fh;
179     $input_names = $inp_name . ' ';
180     $hasodd = $params{bothsided} && __PACKAGE__->has_odd_pages($inp_name);
181   }
182   foreach (@{ $params{file_names} }) {
183     $input_names .= $emptypage . ' ' if $hasodd;
184     $input_names .= String::ShellQuote::shell_quote($_) . ' ';
185     $hasodd = $params{bothsided} && __PACKAGE__->has_odd_pages($_);
186   }
187   my $exe = $::lx_office_conf{applications}->{ghostscript} || 'gs';
188   my $output =
189     `$exe -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=${temp_name} ${input_names} 2>&1`;
190
191   die "Executing gs failed: $ERRNO" if !defined $output;
192   die $output                       if $? != 0;
193
194   if ($params{out_path}) {
195     if (!rename($temp_name, $params{out_path})) {
196
197       # special filesystem or cross filesystem etc
198       move($temp_name, $params{out_path});
199     }
200     return 1;
201   }
202   return scalar File::Slurp::read_file($temp_name);
203 }
204
205 sub find_template {
206   my ($class, %params) = @_;
207
208   $params{name} or croak "Missing parameter 'name'";
209
210   my $path                 = $::instance_conf->get_templates;
211   my $extension            = $params{extension} || "tex";
212   my ($printer, $language) = ('', '');
213
214   if ($params{printer} || $params{printer_id}) {
215     if ($params{printer} && !ref $params{printer}) {
216       $printer = '_' . $params{printer};
217     } else {
218       $printer = $params{printer} || SL::DB::Printer->new(id => $params{printer_id})->load;
219       $printer = $printer->template_code ? '_' . $printer->template_code : '';
220     }
221   }
222
223   if ($params{language} || $params{language_id}) {
224     if ($params{language} && !ref $params{language}) {
225       $language = '_' . $params{language};
226     } else {
227       $language = $params{language} || SL::DB::Language->new(id => $params{language_id})->load;
228       $language = $language->template_code ? '_' . $language->template_code : '';
229     }
230   }
231
232   my @template_files = (
233     $params{name} . "${language}${printer}",
234     $params{name} . "${language}",
235     $params{name},
236     "default",
237   );
238
239   if ($params{email}) {
240     unshift @template_files, (
241       $params{name} . "_email${language}${printer}",
242       $params{name} . "_email${language}",
243     );
244   }
245
246   @template_files = map { "${_}.${extension}" } uniq grep { $_ } @template_files;
247
248   my $template = first { -f ($path . "/$_") } @template_files;
249
250   return wantarray ? ($template, @template_files) : $template;
251 }
252
253 1;
254 __END__
255
256 =pod
257
258 =encoding utf8
259
260 =head1 NAME
261
262 SL::Helper::CreatePDF - A helper for creating PDFs from template files
263
264 =head1 SYNOPSIS
265
266   # Retrieve a sales order from the database and create a PDF for
267   # it:
268   my $order               = SL::DB::Order->new(id => …)->load;
269   my $print_form          = Form->new('');
270   $print_form->{type}     = 'invoice';
271   $print_form->{formname} = 'invoice',
272   $print_form->{format}   = 'pdf',
273   $print_form->{media}    = 'file';
274
275   $order->flatten_to_form($print_form, format_amounts => 1);
276   $print_form->prepare_for_printing;
277
278   my $pdf = SL::Helper::CreatePDF->create_pdf(
279     template  => 'sales_order',
280     variables => $print_form,
281   );
282
283 =head1 FUNCTIONS
284
285 =over 4
286
287 =item C<create_pdf %params>
288
289 Parses a LaTeX template file, creates a PDF for it and returns either
290 its content or its file name. The recognized parameters are the same
291 as the ones for L</create_parsed_file> with C<format> and
292 C<template_type> being pre-set.
293
294 =item C<create_parsed_file %params>
295
296 Parses a template file and returns either its content or its file
297 name. The recognized parameters are:
298
299 =over 2
300
301 =item * C<template> – mandatory. The template file name relative to
302 the users' templates directory. Must be an existing file name,
303 e.g. one retrieved by L</find_template>.
304
305 =item * C<variables> – optional hash reference containing variables
306 available to the template.
307
308 =item * C<return> – optional scalar containing either C<content> (the
309 default) or C<file_name>. If it is set to C<file_name> then the file
310 name of the temporary file containing the PDF is returned, and the
311 caller is responsible for deleting it. Otherwise a scalar containing
312 the PDF itself is returned and all temporary files have already been
313 deleted by L</create_pdf>.
314
315 =item * C<format> – optional, defaults to C<pdf> and determines the
316 output format. Can be set to C<html> for HTML output if
317 C<template_type> is set to C<HTML> as well.
318
319 =item * C<template_type> – optional, defaults to C<LaTeX> and
320 determines the template's format. Can be set to C<HTML> for HTML
321 output if C<format> is set to C<html> as well.
322
323 =back
324
325 =item C<find_template %params>
326
327 Searches the user's templates directory for a template file name to
328 use. The file names considered depend on the parameters; they can
329 contain a template base name and suffixes for email, language and
330 printers. As a fallback the name C<default.$extension> is also
331 considered.
332
333 The return value depends on the context. In scalar context the
334 template file name that matches the given parameters is returned. It's
335 a file name relative to the user's templates directory. If no template
336 file is found then C<undef> is returned.
337
338 In list context the first element is the same value as in scalar
339 context. Additionally a list of considered template file names is
340 returned.
341
342 The recognized parameters are:
343
344 =over 2
345
346 =item * C<name> – mandatory. The template's file name basis
347 without any additional suffix or extension, e.g. C<sales_quotation>.
348
349 =item * C<extension> – optional file name extension to use without the
350 dot. Defaults to C<tex>.
351
352 =item * C<email> – optional flag indicating whether or not the
353 template is to be sent via email. If set to true then template file
354 names containing C<_email> are considered as well.
355
356 =item * C<language> and C<language_id> – optional parameters
357 indicating the language to be used. C<language> can be either a string
358 containing the language code to use or an instance of
359 C<SL::DB::Language>. C<language_id> can contain the ID of the
360 C<SL::DB:Language> instance to load and use. If given template file
361 names containing C<_language_template_code> are considered as well.
362
363 =item * C<printer> and C<printer_id> – optional parameters indicating
364 the printer to be used. C<printer> can be either a string containing
365 the printer code to use or an instance of
366 C<SL::DB::Printer>. C<printer_id> can contain the ID of the
367 C<SL::DB:Printer> instance to load and use. If given template file
368 names containing C<_printer_template_code> are considered as well.
369
370 =back
371
372 =item C<merge_pdfs %params>
373
374 Merges two or more PDFs into a single PDF by using the external
375 application ghostscript.
376
377 Normally the function returns the contents of the resulting PDF.
378 if The parameter C<out_path> is set the resulting PDF is in this file
379 and the return value is 1 if it successful or 0 if not.
380
381 The recognized parameters are:
382
383 =over 2
384
385 =item * C<file_names> – mandatory array reference containing the file
386 names to merge.
387
388 =item * C<inp_content> – optional, contents of first file to merge with C<file_names>.
389
390 =item * C<out_path> – optional, returns not the merged contents but wrote him into this file
391
392 =back
393
394 Note that this function relies on the presence of the external
395 application ghostscript. The executable to use is configured via
396 kivitendo's configuration file setting C<application.ghostscript>.
397
398 =back
399
400 =head1 BUGS
401
402 Nothing here yet.
403
404 =head1 AUTHOR
405
406 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
407
408 =cut