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>