1 package SL::Helper::CreatePDF;
7 use English qw(-no_match_vars);
11 use File::Copy qw(move);
12 use List::MoreUtils qw(uniq);
13 use List::Util qw(first);
14 use Scalar::Util qw(blessed);
15 use String::ShellQuote ();
21 use SL::System::Process;
23 use SL::Template::LaTeX;
26 use Exporter 'import';
27 our @EXPORT_OK = qw(create_pdf merge_pdfs find_template);
33 my ($class, %params) = @_;
35 return __PACKAGE__->create_parsed_file(
37 template_type => 'LaTeX',
42 sub create_parsed_file {
43 my ($class, %params) = @_;
45 my $keep_temp_files = $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files};
46 my $userspath = SL::System::Process::exe_dir() . "/" . $::lx_office_conf{paths}->{userspath};
47 my $temp_dir = File::Temp->newdir(
48 "kivitendo-print-XXXXXX",
50 CLEANUP => !$keep_temp_files,
53 my $vars = $params{variables} || {};
54 my $form = Form->new('');
55 $form->{$_} = $vars->{$_} for keys %{$vars};
56 $form->{format} = lc($params{format} || 'pdf');
57 $form->{cwd} = SL::System::Process::exe_dir();
58 $form->{templates} = $::instance_conf->get_templates;
59 $form->{IN} = $params{template};
60 $form->{tmpdir} = $temp_dir->dirname;
61 my $tmpdir = $form->{tmpdir};
62 my ($suffix) = $params{template} =~ m{\.(.+)};
64 my ($temp_fh, $tmpfile) = File::Temp::tempfile(
65 'kivitendo-printXXXXXX',
66 SUFFIX => ".${suffix}",
67 DIR => $form->{tmpdir},
68 UNLINK => !$keep_temp_files,
71 $form->{tmpfile} = $tmpfile;
72 (undef, undef, $form->{template_meta}{tmpfile}) = File::Spec->splitpath($tmpfile);
76 %driver_options = _maybe_attach_zugferd_data($params{record});
79 if (my $e = SL::X::ZUGFeRDValidation->caught) {
84 my $parser = SL::Template::create(
85 type => ($params{template_type} || 'LaTeX'),
86 source => $form->{IN},
88 myconfig => \%::myconfig,
90 variable_content_types => $params{variable_content_types},
94 my $result = $parser->parse($temp_fh);
101 die $parser->get_error;
104 # SL::Template:** modify $form->{tmpfile} by removing its
105 # $form->{userspath} prefix. They also store the final file's actual
106 # file name in $form->{tmpfile} – but it is now relative to
107 # $form->{userspath}. Other modules return the full file name…
108 my ($volume, $directory, $file_name) = File::Spec->splitpath($form->{tmpfile});
109 my $full_file_name = File::Spec->catfile($tmpdir, $file_name);
110 if (($params{return} || 'content') eq 'file_name') {
111 my $new_name = File::Spec->catfile($userspath, 'keep-' . $form->{tmpfile});
112 rename $full_file_name, $new_name;
119 my $content = File::Slurp::read_file($full_file_name);
127 # Alternativen zu pdfinfo wären (aber wesentlich langamer):
129 # gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=/dev/null $filename | grep 'Processing pages'
130 # my (undef,undef,undef,undef,$pages) = split / +/,$shell_out;
132 # gs -dBATCH -dNOPAUSE -q -dNODISPLAY -c "($filename) (r) file runpdfbegin pdfpagecount = quit"
137 my ($class, $filename) = @_;
138 return 0 unless -f $filename;
139 my $shell_out = `pdfinfo $filename | grep 'Pages:'`;
140 my ($label, $pages) = split / +/, $shell_out;
145 my ($class, %params) = @_;
146 my $filecount = scalar(@{ $params{file_names} });
148 if ($params{inp_content}) {
149 return $params{inp_content} if $filecount == 0 && !$params{out_path};
150 } elsif ($params{out_path}) {
151 return 0 if $filecount == 0;
152 if ($filecount == 1) {
153 if (!rename($params{file_names}->[0], $params{out_path})) {
154 # special filesystem or cross filesystem etc
155 move($params{file_names}->[0], $params{out_path});
160 return '' if $filecount == 0;
161 return scalar(File::Slurp::read_file($params{file_names}->[0])) if $filecount == 1;
164 my ($temp_fh, $temp_name) = File::Temp::tempfile(
165 'kivitendo-printXXXXXX',
167 DIR => $::lx_office_conf{paths}->{userspath},
168 UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
172 my $input_names = '';
175 if ($params{bothsided}) {
176 $emptypage = $::instance_conf->get_templates . '/emptyPage.pdf';
177 unless (-f $emptypage) {
179 delete $params{bothsided};
182 if ($params{inp_content}) {
183 my ($temp_fh, $inp_name) = File::Temp::tempfile(
184 'kivitendo-contentXXXXXX',
186 DIR => $::lx_office_conf{paths}->{userspath},
187 UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
190 print $temp_fh $params{inp_content};
192 $input_names = $inp_name . ' ';
193 $hasodd = $params{bothsided} && __PACKAGE__->has_odd_pages($inp_name);
195 foreach (@{ $params{file_names} }) {
196 $input_names .= $emptypage . ' ' if $hasodd;
197 $input_names .= String::ShellQuote::shell_quote($_) . ' ';
198 $hasodd = $params{bothsided} && __PACKAGE__->has_odd_pages($_);
200 my $exe = $::lx_office_conf{applications}->{ghostscript} || 'gs';
202 `$exe -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=${temp_name} ${input_names} 2>&1`;
204 die "Executing gs failed: $ERRNO" if !defined $output;
205 die $output if $? != 0;
207 if ($params{out_path}) {
208 if (!rename($temp_name, $params{out_path})) {
210 # special filesystem or cross filesystem etc
211 move($temp_name, $params{out_path});
215 return scalar File::Slurp::read_file($temp_name);
219 my ($class, %params) = @_;
221 $params{name} or croak "Missing parameter 'name'";
223 my $path = $::instance_conf->get_templates;
224 my $extension = $params{extension} || "tex";
225 my ($printer, $language) = ('', '');
227 if ($params{printer} || $params{printer_id}) {
228 if ($params{printer} && !ref $params{printer}) {
229 $printer = '_' . $params{printer};
231 $printer = $params{printer} || SL::DB::Printer->new(id => $params{printer_id})->load;
232 $printer = $printer->template_code ? '_' . $printer->template_code : '';
236 if ($params{language} || $params{language_id}) {
237 if ($params{language} && !ref $params{language}) {
238 $language = '_' . $params{language};
240 $language = $params{language} || SL::DB::Language->new(id => $params{language_id})->load;
241 $language = $language->template_code ? '_' . $language->template_code : '';
245 my @template_files = (
246 $params{name} . "${language}${printer}",
247 $params{name} . "${language}",
252 if ($params{email}) {
253 unshift @template_files, (
254 $params{name} . "_email${language}${printer}",
255 $params{name} . "_email${language}",
259 @template_files = map { "${_}.${extension}" } uniq grep { $_ } @template_files;
261 my $template = first { -f ($path . "/$_") } @template_files;
263 return wantarray ? ($template, @template_files) : $template;
266 sub _maybe_attach_zugferd_data {
269 return if !blessed($record)
270 || !$record->can('customer')
271 || !$record->customer
272 || !$record->can('create_pdf_a_print_options')
273 || !$record->can('create_zugferd_data')
274 || !$record->customer->create_zugferd_invoices_for_this_customer;
276 my $xmlfile = File::Temp->new;
277 $xmlfile->print($record->create_zugferd_data);
280 my %driver_options = (
281 pdf_a => $record->create_pdf_a_print_options(zugferd_xmp_data => $record->create_zugferd_xmp_data),
283 { source => $xmlfile,
284 name => 'factur-x.xml',
285 description => $::locale->text('Factur-X/ZUGFeRD invoice'),
286 relationship => '/Alternative',
287 mime_type => 'text/xml',
292 return %driver_options;
304 SL::Helper::CreatePDF - A helper for creating PDFs from template files
308 # Retrieve a sales order from the database and create a PDF for
310 my $order = SL::DB::Order->new(id => …)->load;
311 my $print_form = Form->new('');
312 $print_form->{type} = 'invoice';
313 $print_form->{formname} = 'invoice',
314 $print_form->{format} = 'pdf',
315 $print_form->{media} = 'file';
317 $order->flatten_to_form($print_form, format_amounts => 1);
318 $print_form->prepare_for_printing;
320 my $pdf = SL::Helper::CreatePDF->create_pdf(
321 template => 'sales_order',
322 variables => $print_form,
329 =item C<create_pdf %params>
331 Parses a LaTeX template file, creates a PDF for it and returns either
332 its content or its file name. The recognized parameters are the same
333 as the ones for L</create_parsed_file> with C<format> and
334 C<template_type> being pre-set.
336 =item C<create_parsed_file %params>
338 Parses a template file and returns either its content or its file
339 name. The recognized parameters are:
343 =item * C<template> – mandatory. The template file name relative to
344 the users' templates directory. Must be an existing file name,
345 e.g. one retrieved by L</find_template>.
347 =item * C<variables> – optional hash reference containing variables
348 available to the template.
350 =item * C<return> – optional scalar containing either C<content> (the
351 default) or C<file_name>. If it is set to C<file_name> then the file
352 name of the temporary file containing the PDF is returned, and the
353 caller is responsible for deleting it. Otherwise a scalar containing
354 the PDF itself is returned and all temporary files have already been
355 deleted by L</create_pdf>.
357 =item * C<format> – optional, defaults to C<pdf> and determines the
358 output format. Can be set to C<html> for HTML output if
359 C<template_type> is set to C<HTML> as well.
361 =item * C<template_type> – optional, defaults to C<LaTeX> and
362 determines the template's format. Can be set to C<HTML> for HTML
363 output if C<format> is set to C<html> as well.
367 =item C<find_template %params>
369 Searches the user's templates directory for a template file name to
370 use. The file names considered depend on the parameters; they can
371 contain a template base name and suffixes for email, language and
372 printers. As a fallback the name C<default.$extension> is also
375 The return value depends on the context. In scalar context the
376 template file name that matches the given parameters is returned. It's
377 a file name relative to the user's templates directory. If no template
378 file is found then C<undef> is returned.
380 In list context the first element is the same value as in scalar
381 context. Additionally a list of considered template file names is
384 The recognized parameters are:
388 =item * C<name> – mandatory. The template's file name basis
389 without any additional suffix or extension, e.g. C<sales_quotation>.
391 =item * C<extension> – optional file name extension to use without the
392 dot. Defaults to C<tex>.
394 =item * C<email> – optional flag indicating whether or not the
395 template is to be sent via email. If set to true then template file
396 names containing C<_email> are considered as well.
398 =item * C<language> and C<language_id> – optional parameters
399 indicating the language to be used. C<language> can be either a string
400 containing the language code to use or an instance of
401 C<SL::DB::Language>. C<language_id> can contain the ID of the
402 C<SL::DB:Language> instance to load and use. If given template file
403 names containing C<_language_template_code> are considered as well.
405 =item * C<printer> and C<printer_id> – optional parameters indicating
406 the printer to be used. C<printer> can be either a string containing
407 the printer code to use or an instance of
408 C<SL::DB::Printer>. C<printer_id> can contain the ID of the
409 C<SL::DB:Printer> instance to load and use. If given template file
410 names containing C<_printer_template_code> are considered as well.
414 =item C<merge_pdfs %params>
416 Merges two or more PDFs into a single PDF by using the external
417 application ghostscript.
419 Normally the function returns the contents of the resulting PDF.
420 if The parameter C<out_path> is set the resulting PDF is in this file
421 and the return value is 1 if it successful or 0 if not.
423 The recognized parameters are:
427 =item * C<file_names> – mandatory array reference containing the file
430 =item * C<inp_content> – optional, contents of first file to merge with C<file_names>.
432 =item * C<out_path> – optional, returns not the merged contents but wrote him into this file
436 Note that this function relies on the presence of the external
437 application ghostscript. The executable to use is configured via
438 kivitendo's configuration file setting C<application.ghostscript>.
448 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>