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 String::ShellQuote ();
 
  22 use SL::Template::LaTeX;
 
  24 use Exporter 'import';
 
  25 our @EXPORT_OK = qw(create_pdf merge_pdfs find_template);
 
  31   my ($class, %params) = @_;
 
  33   return __PACKAGE__->create_parsed_file(
 
  35     template_type => 'LaTeX',
 
  40 sub create_parsed_file {
 
  41   my ($class, %params) = @_;
 
  43   my $userspath      = $::lx_office_conf{paths}->{userspath};
 
  44   my $vars           = $params{variables} || {};
 
  45   my $form           = Form->new('');
 
  46   $form->{$_}        = $vars->{$_} for keys %{$vars};
 
  47   $form->{format}    = lc($params{format} || 'pdf');
 
  48   $form->{cwd}       = getcwd();
 
  49   $form->{templates} = $::instance_conf->get_templates;
 
  50   $form->{IN}        = $params{template};
 
  51   $form->{tmpdir}    = $form->{cwd} . '/' . $userspath;
 
  52   my $tmpdir         = $form->{tmpdir};
 
  53   my ($suffix)       = $params{template} =~ m{\.(.+)};
 
  55   my ($temp_fh, $tmpfile) = File::Temp::tempfile(
 
  56     'kivitendo-printXXXXXX',
 
  57     SUFFIX => ".${suffix}",
 
  58     DIR    => $form->{tmpdir},
 
  59     UNLINK => $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files},
 
  62   $form->{tmpfile} = $tmpfile;
 
  63   (undef, undef, $form->{template_meta}{tmpfile}) = File::Spec->splitpath($tmpfile);
 
  65   my $parser               = SL::Template::create(
 
  66     type                   => ($params{template_type} || 'LaTeX'),
 
  67     source                 => $form->{IN},
 
  69     myconfig               => \%::myconfig,
 
  71     variable_content_types => $params{variable_content_types},
 
  74   my $result = $parser->parse($temp_fh);
 
  81     die $parser->get_error;
 
  84   # SL::Template:** modify $form->{tmpfile} by removing its
 
  85   # $form->{userspath} prefix. They also store the final file's actual
 
  86   # file name in $form->{tmpfile} – but it is now relative to
 
  87   # $form->{userspath}. Other modules return the full file name…
 
  88   my ($volume, $directory, $file_name) = File::Spec->splitpath($form->{tmpfile});
 
  89   my $full_file_name                   = File::Spec->catfile($tmpdir, $file_name);
 
  90   if (($params{return} || 'content') eq 'file_name') {
 
  91     my $new_name = File::Spec->catfile($tmpdir, 'keep-' . $form->{tmpfile});
 
  92     rename $full_file_name, $new_name;
 
  99   my $content = File::Slurp::read_file($full_file_name);
 
 107 # Alternativen zu pdfinfo wären (aber wesentlich langamer):
 
 109 # gs  -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=/dev/null $filename | grep 'Processing pages'
 
 110 # my (undef,undef,undef,undef,$pages)  = split / +/,$shell_out;
 
 112 # gs  -dBATCH -dNOPAUSE -q -dNODISPLAY -c "($filename) (r) file runpdfbegin pdfpagecount = quit"
 
 117   my ($class, $filename) = @_;
 
 118   return 0 unless -f $filename;
 
 119   my $shell_out = `pdfinfo $filename | grep 'Pages:'`;
 
 120   my ($label, $pages) = split / +/, $shell_out;
 
 125   my ($class, %params) = @_;
 
 126   my $filecount = scalar(@{ $params{file_names} });
 
 128   if ($params{inp_content}) {
 
 129     return $params{inp_content} if $filecount == 0 && !$params{out_path};
 
 130   } elsif ($params{out_path}) {
 
 131     return 0 if $filecount == 0;
 
 132     if ($filecount == 1) {
 
 133       if (!rename($params{file_names}->[0], $params{out_path})) {
 
 134         # special filesystem or cross filesystem etc
 
 135         move($params{file_names}->[0], $params{out_path});
 
 140     return '' if $filecount == 0;
 
 141     return scalar(File::Slurp::read_file($params{file_names}->[0])) if $filecount == 1;
 
 144   my ($temp_fh, $temp_name) = File::Temp::tempfile(
 
 145     'kivitendo-printXXXXXX',
 
 147     DIR    => $::lx_office_conf{paths}->{userspath},
 
 148     UNLINK => $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files},
 
 152   my $input_names = '';
 
 155   if ($params{bothsided}) {
 
 156     $emptypage = $::instance_conf->get_templates . '/emptyPage.pdf';
 
 157     unless (-f $emptypage) {
 
 159       delete $params{bothsided};
 
 162   if ($params{inp_content}) {
 
 163     my ($temp_fh, $inp_name) = File::Temp::tempfile(
 
 164       'kivitendo-contentXXXXXX',
 
 166       DIR    => $::lx_office_conf{paths}->{userspath},
 
 167       UNLINK => $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files},
 
 170     print $temp_fh $params{inp_content};
 
 172     $input_names = $inp_name . ' ';
 
 173     $hasodd = $params{bothsided} && __PACKAGE__->has_odd_pages($inp_name);
 
 175   foreach (@{ $params{file_names} }) {
 
 176     $input_names .= $emptypage . ' ' if $hasodd;
 
 177     $input_names .= String::ShellQuote::shell_quote($_) . ' ';
 
 178     $hasodd = $params{bothsided} && __PACKAGE__->has_odd_pages($_);
 
 180   my $exe = $::lx_office_conf{applications}->{ghostscript} || 'gs';
 
 182     `$exe -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=${temp_name} ${input_names} 2>&1`;
 
 184   die "Executing gs failed: $ERRNO" if !defined $output;
 
 185   die $output                       if $? != 0;
 
 187   if ($params{out_path}) {
 
 188     if (!rename($temp_name, $params{out_path})) {
 
 190       # special filesystem or cross filesystem etc
 
 191       move($temp_name, $params{out_path});
 
 195   return scalar File::Slurp::read_file($temp_name);
 
 199   my ($class, %params) = @_;
 
 201   $params{name} or croak "Missing parameter 'name'";
 
 203   my $path                 = $::instance_conf->get_templates;
 
 204   my $extension            = $params{extension} || "tex";
 
 205   my ($printer, $language) = ('', '');
 
 207   if ($params{printer} || $params{printer_id}) {
 
 208     if ($params{printer} && !ref $params{printer}) {
 
 209       $printer = '_' . $params{printer};
 
 211       $printer = $params{printer} || SL::DB::Printer->new(id => $params{printer_id})->load;
 
 212       $printer = $printer->template_code ? '_' . $printer->template_code : '';
 
 216   if ($params{language} || $params{language_id}) {
 
 217     if ($params{language} && !ref $params{language}) {
 
 218       $language = '_' . $params{language};
 
 220       $language = $params{language} || SL::DB::Language->new(id => $params{language_id})->load;
 
 221       $language = $language->template_code ? '_' . $language->template_code : '';
 
 225   my @template_files = (
 
 226     $params{name} . "${language}${printer}",
 
 227     $params{name} . "${language}",
 
 232   if ($params{email}) {
 
 233     unshift @template_files, (
 
 234       $params{name} . "_email${language}${printer}",
 
 235       $params{name} . "_email${language}",
 
 239   @template_files = map { "${_}.${extension}" } uniq grep { $_ } @template_files;
 
 241   my $template = first { -f ($path . "/$_") } @template_files;
 
 243   return wantarray ? ($template, @template_files) : $template;
 
 255 SL::Helper::CreatePDF - A helper for creating PDFs from template files
 
 259   # Retrieve a sales order from the database and create a PDF for
 
 261   my $order               = SL::DB::Order->new(id => …)->load;
 
 262   my $print_form          = Form->new('');
 
 263   $print_form->{type}     = 'invoice';
 
 264   $print_form->{formname} = 'invoice',
 
 265   $print_form->{format}   = 'pdf',
 
 266   $print_form->{media}    = 'file';
 
 268   $order->flatten_to_form($print_form, format_amounts => 1);
 
 269   $print_form->prepare_for_printing;
 
 271   my $pdf = SL::Helper::CreatePDF->create_pdf(
 
 272     template  => 'sales_order',
 
 273     variables => $print_form,
 
 280 =item C<create_pdf %params>
 
 282 Parses a LaTeX template file, creates a PDF for it and returns either
 
 283 its content or its file name. The recognized parameters are the same
 
 284 as the ones for L</create_parsed_file> with C<format> and
 
 285 C<template_type> being pre-set.
 
 287 =item C<create_parsed_file %params>
 
 289 Parses a template file and returns either its content or its file
 
 290 name. The recognized parameters are:
 
 294 =item * C<template> – mandatory. The template file name relative to
 
 295 the users' templates directory. Must be an existing file name,
 
 296 e.g. one retrieved by L</find_template>.
 
 298 =item * C<variables> – optional hash reference containing variables
 
 299 available to the template.
 
 301 =item * C<return> – optional scalar containing either C<content> (the
 
 302 default) or C<file_name>. If it is set to C<file_name> then the file
 
 303 name of the temporary file containing the PDF is returned, and the
 
 304 caller is responsible for deleting it. Otherwise a scalar containing
 
 305 the PDF itself is returned and all temporary files have already been
 
 306 deleted by L</create_pdf>.
 
 308 =item * C<format> – optional, defaults to C<pdf> and determines the
 
 309 output format. Can be set to C<html> for HTML output if
 
 310 C<template_type> is set to C<HTML> as well.
 
 312 =item * C<template_type> – optional, defaults to C<LaTeX> and
 
 313 determines the template's format. Can be set to C<HTML> for HTML
 
 314 output if C<format> is set to C<html> as well.
 
 318 =item C<find_template %params>
 
 320 Searches the user's templates directory for a template file name to
 
 321 use. The file names considered depend on the parameters; they can
 
 322 contain a template base name and suffixes for email, language and
 
 323 printers. As a fallback the name C<default.$extension> is also
 
 326 The return value depends on the context. In scalar context the
 
 327 template file name that matches the given parameters is returned. It's
 
 328 a file name relative to the user's templates directory. If no template
 
 329 file is found then C<undef> is returned.
 
 331 In list context the first element is the same value as in scalar
 
 332 context. Additionally a list of considered template file names is
 
 335 The recognized parameters are:
 
 339 =item * C<name> – mandatory. The template's file name basis
 
 340 without any additional suffix or extension, e.g. C<sales_quotation>.
 
 342 =item * C<extension> – optional file name extension to use without the
 
 343 dot. Defaults to C<tex>.
 
 345 =item * C<email> – optional flag indicating whether or not the
 
 346 template is to be sent via email. If set to true then template file
 
 347 names containing C<_email> are considered as well.
 
 349 =item * C<language> and C<language_id> – optional parameters
 
 350 indicating the language to be used. C<language> can be either a string
 
 351 containing the language code to use or an instance of
 
 352 C<SL::DB::Language>. C<language_id> can contain the ID of the
 
 353 C<SL::DB:Language> instance to load and use. If given template file
 
 354 names containing C<_language_template_code> are considered as well.
 
 356 =item * C<printer> and C<printer_id> – optional parameters indicating
 
 357 the printer to be used. C<printer> can be either a string containing
 
 358 the printer code to use or an instance of
 
 359 C<SL::DB::Printer>. C<printer_id> can contain the ID of the
 
 360 C<SL::DB:Printer> instance to load and use. If given template file
 
 361 names containing C<_printer_template_code> are considered as well.
 
 365 =item C<merge_pdfs %params>
 
 367 Merges two or more PDFs into a single PDF by using the external
 
 368 application ghostscript.
 
 370 Normally the function returns the contents of the resulting PDF.
 
 371 if The parameter C<out_path> is set the resulting PDF is in this file
 
 372 and the return value is 1 if it successful or 0 if not.
 
 374 The recognized parameters are:
 
 378 =item * C<file_names> – mandatory array reference containing the file
 
 381 =item * C<inp_content> – optional, contents of first file to merge with C<file_names>.
 
 383 =item * C<out_path> – optional, returns not the merged contents but wrote him into this file
 
 387 Note that this function relies on the presence of the external
 
 388 application ghostscript. The executable to use is configured via
 
 389 kivitendo's configuration file setting C<application.ghostscript>.
 
 399 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>