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 ();
 
  20 use SL::System::Process;
 
  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 $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",
 
  48     CLEANUP => !$keep_temp_files,
 
  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{\.(.+)};
 
  62   my ($temp_fh, $tmpfile) = File::Temp::tempfile(
 
  63     'kivitendo-printXXXXXX',
 
  64     SUFFIX => ".${suffix}",
 
  65     DIR    => $form->{tmpdir},
 
  66     UNLINK => !$keep_temp_files,
 
  69   $form->{tmpfile} = $tmpfile;
 
  70   (undef, undef, $form->{template_meta}{tmpfile}) = File::Spec->splitpath($tmpfile);
 
  72   my $parser               = SL::Template::create(
 
  73     type                   => ($params{template_type} || 'LaTeX'),
 
  74     source                 => $form->{IN},
 
  76     myconfig               => \%::myconfig,
 
  78     variable_content_types => $params{variable_content_types},
 
  81   my $result = $parser->parse($temp_fh);
 
  88     die $parser->get_error;
 
  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;
 
 106   my $content = File::Slurp::read_file($full_file_name);
 
 114 # Alternativen zu pdfinfo wären (aber wesentlich langamer):
 
 116 # gs  -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=/dev/null $filename | grep 'Processing pages'
 
 117 # my (undef,undef,undef,undef,$pages)  = split / +/,$shell_out;
 
 119 # gs  -dBATCH -dNOPAUSE -q -dNODISPLAY -c "($filename) (r) file runpdfbegin pdfpagecount = quit"
 
 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;
 
 132   my ($class, %params) = @_;
 
 133   my $filecount = scalar(@{ $params{file_names} });
 
 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});
 
 147     return '' if $filecount == 0;
 
 148     return scalar(File::Slurp::read_file($params{file_names}->[0])) if $filecount == 1;
 
 151   my ($temp_fh, $temp_name) = File::Temp::tempfile(
 
 152     'kivitendo-printXXXXXX',
 
 154     DIR    => $::lx_office_conf{paths}->{userspath},
 
 155     UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
 
 159   my $input_names = '';
 
 162   if ($params{bothsided}) {
 
 163     $emptypage = $::instance_conf->get_templates . '/emptyPage.pdf';
 
 164     unless (-f $emptypage) {
 
 166       delete $params{bothsided};
 
 169   if ($params{inp_content}) {
 
 170     my ($temp_fh, $inp_name) = File::Temp::tempfile(
 
 171       'kivitendo-contentXXXXXX',
 
 173       DIR    => $::lx_office_conf{paths}->{userspath},
 
 174       UNLINK => ($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})? 0 : 1,
 
 177     print $temp_fh $params{inp_content};
 
 179     $input_names = $inp_name . ' ';
 
 180     $hasodd = $params{bothsided} && __PACKAGE__->has_odd_pages($inp_name);
 
 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($_);
 
 187   my $exe = $::lx_office_conf{applications}->{ghostscript} || 'gs';
 
 189     `$exe -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=${temp_name} ${input_names} 2>&1`;
 
 191   die "Executing gs failed: $ERRNO" if !defined $output;
 
 192   die $output                       if $? != 0;
 
 194   if ($params{out_path}) {
 
 195     if (!rename($temp_name, $params{out_path})) {
 
 197       # special filesystem or cross filesystem etc
 
 198       move($temp_name, $params{out_path});
 
 202   return scalar File::Slurp::read_file($temp_name);
 
 206   my ($class, %params) = @_;
 
 208   $params{name} or croak "Missing parameter 'name'";
 
 210   my $path                 = $::instance_conf->get_templates;
 
 211   my $extension            = $params{extension} || "tex";
 
 212   my ($printer, $language) = ('', '');
 
 214   if ($params{printer} || $params{printer_id}) {
 
 215     if ($params{printer} && !ref $params{printer}) {
 
 216       $printer = '_' . $params{printer};
 
 218       $printer = $params{printer} || SL::DB::Printer->new(id => $params{printer_id})->load;
 
 219       $printer = $printer->template_code ? '_' . $printer->template_code : '';
 
 223   if ($params{language} || $params{language_id}) {
 
 224     if ($params{language} && !ref $params{language}) {
 
 225       $language = '_' . $params{language};
 
 227       $language = $params{language} || SL::DB::Language->new(id => $params{language_id})->load;
 
 228       $language = $language->template_code ? '_' . $language->template_code : '';
 
 232   my @template_files = (
 
 233     $params{name} . "${language}${printer}",
 
 234     $params{name} . "${language}",
 
 239   if ($params{email}) {
 
 240     unshift @template_files, (
 
 241       $params{name} . "_email${language}${printer}",
 
 242       $params{name} . "_email${language}",
 
 246   @template_files = map { "${_}.${extension}" } uniq grep { $_ } @template_files;
 
 248   my $template = first { -f ($path . "/$_") } @template_files;
 
 250   return wantarray ? ($template, @template_files) : $template;
 
 262 SL::Helper::CreatePDF - A helper for creating PDFs from template files
 
 266   # Retrieve a sales order from the database and create a PDF for
 
 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';
 
 275   $order->flatten_to_form($print_form, format_amounts => 1);
 
 276   $print_form->prepare_for_printing;
 
 278   my $pdf = SL::Helper::CreatePDF->create_pdf(
 
 279     template  => 'sales_order',
 
 280     variables => $print_form,
 
 287 =item C<create_pdf %params>
 
 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.
 
 294 =item C<create_parsed_file %params>
 
 296 Parses a template file and returns either its content or its file
 
 297 name. The recognized parameters are:
 
 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>.
 
 305 =item * C<variables> – optional hash reference containing variables
 
 306 available to the template.
 
 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>.
 
 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.
 
 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.
 
 325 =item C<find_template %params>
 
 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
 
 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.
 
 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
 
 342 The recognized parameters are:
 
 346 =item * C<name> – mandatory. The template's file name basis
 
 347 without any additional suffix or extension, e.g. C<sales_quotation>.
 
 349 =item * C<extension> – optional file name extension to use without the
 
 350 dot. Defaults to C<tex>.
 
 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.
 
 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.
 
 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.
 
 372 =item C<merge_pdfs %params>
 
 374 Merges two or more PDFs into a single PDF by using the external
 
 375 application ghostscript.
 
 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.
 
 381 The recognized parameters are:
 
 385 =item * C<file_names> – mandatory array reference containing the file
 
 388 =item * C<inp_content> – optional, contents of first file to merge with C<file_names>.
 
 390 =item * C<out_path> – optional, returns not the merged contents but wrote him into this file
 
 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>.
 
 406 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>