Nicht abgefangene Typenkonvertierung in SL/Template.pm
[kivitendo-erp.git] / SL / Template.pm
1 #====================================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #====================================================================
8 #
9 #
10 #    NOTE: strict checks are package global. don't check this file
11 #            with perl -sc, it will only capture SimpleTemplate
12 #
13 #
14
15 package SimpleTemplate;
16
17 use strict;
18
19 # Parameters:
20 #   1. The template's file name
21 #   2. A reference to the Form object
22 #   3. A reference to the myconfig hash
23 #
24 # Returns:
25 #   A new template object
26 sub new {
27   my $type = shift;
28   my $self = {};
29
30   bless($self, $type);
31   $self->_init(@_);
32
33   return $self;
34 }
35
36 sub _init {
37   my $self = shift;
38
39   $self->{source}    = shift;
40   $self->{form}      = shift;
41   $self->{myconfig}  = shift;
42   $self->{userspath} = shift;
43
44   $self->{error}     = undef;
45   $self->{quot_re}   = '"';
46
47   $self->set_tag_style('<%', '%>');
48 }
49
50 sub set_tag_style {
51   my $self                    = shift;
52   my $tag_start               = shift;
53   my $tag_end                 = shift;
54
55   $self->{tag_start}          = $tag_start;
56   $self->{tag_end}            = $tag_end;
57   $self->{tag_start_qm}       = quotemeta $tag_start;
58   $self->{tag_end_qm}         = quotemeta $tag_end;
59
60   $self->{substitute_vars_re} = "$self->{tag_start_qm}(.+?)$self->{tag_end_qm}";
61 }
62
63 sub cleanup {
64   my ($self) = @_;
65 }
66
67 # Parameters:
68 #   1. A typeglob for the file handle. The output will be written
69 #      to this file handle.
70 #
71 # Returns:
72 #   1 on success and undef or 0 if there was an error. In the latter case
73 #   the calling function can retrieve the error message via $obj->get_error()
74 sub parse {
75   my $self = $_[0];
76   local *OUT = $_[1];
77
78   print(OUT "Hallo!\n");
79 }
80
81 sub get_error {
82   my $self = shift;
83
84   return $self->{"error"};
85 }
86
87 sub uses_temp_file {
88   return 0;
89 }
90
91 sub _get_loop_variable {
92   my $self      = shift;
93   my $var       = shift;
94   my $get_array = shift;
95   my @indices   = @_;
96
97   my $form      = $self->{form};
98   my $value;
99
100   if (($get_array || @indices) && (ref $form->{TEMPLATE_ARRAYS} eq 'HASH') && (ref $form->{TEMPLATE_ARRAYS}->{$var} eq 'ARRAY')) {
101     $value = $form->{TEMPLATE_ARRAYS}->{$var};
102   } else {
103     $value = $form->{$var};
104   }
105
106   for (my $i = 0; $i < scalar(@indices); $i++) {
107     last unless (ref($value) eq "ARRAY");
108     $value = $value->[$indices[$i]];
109   }
110
111   return $value;
112 }
113
114 sub substitute_vars {
115   my ($self, $text, @indices) = @_;
116
117   my $form = $self->{"form"};
118
119   while ($text =~ /$self->{substitute_vars_re}/) {
120     my ($tag_pos, $tag_len) = ($-[0], $+[0] - $-[0]);
121     my ($var, @options)     = split(/\s+/, $1);
122
123     my $value               = $self->_get_loop_variable($var, 0, @indices);
124     $value                  = $self->format_string($value) unless (grep(/^NOESCAPE$/, @options));
125
126     substr($text, $tag_pos, $tag_len, $value);
127   }
128
129   return $text;
130 }
131
132 sub _parse_block_if {
133   $main::lxdebug->enter_sub();
134
135   my $self         = shift;
136   my $contents     = shift;
137   my $new_contents = shift;
138   my $pos_if       = shift;
139   my @indices      = @_;
140
141   $$new_contents .= $self->substitute_vars(substr($$contents, 0, $pos_if), @indices);
142   substr($$contents, 0, $pos_if) = "";
143
144   if ($$contents !~ m/^$self->{tag_start_qm}if
145                      \s*
146                      (not\b|\!)?           # $1 -- Eventuelle Negierung
147                      \s+
148                      (\b.+?\b)             # $2 -- Name der zu überprüfenden Variablen
149                      (                     # $3 -- Beginn des optionalen Vergleiches
150                        \s*
151                        ([!=])              # $4 -- Negierung des Vergleiches speichern
152                        ([=~])              # $5 -- Art des Vergleiches speichern
153                        \s*
154                        (                   # $6 -- Gequoteter String oder Bareword
155                          $self->{quot_re}
156                          (.*?)(?<!\\)      # $7 -- Gequoteter String -- direkter Vergleich mit eq bzw. ne oder Patternmatching; Escapete Anführungs als Teil des Strings belassen
157                          $self->{quot_re}
158                        |
159                          (\b.+?\b)         # $8 -- Bareword -- als Index für $form benutzen
160                        )
161                      )?
162                      \s*
163                      $self->{tag_end_qm}
164                     /x) {
165     $self->{"error"} = "Malformed $self->{tag_start}if$self->{tag_end}.";
166     $main::lxdebug->leave_sub();
167     return undef;
168   }
169
170   my $not           = $1;
171   my $var           = $2;
172   my $operator_neg  = $4; # '=' oder '!' oder undef, wenn kein Vergleich erkannt
173   my $operator_type = $5; # '=' oder '~' für Stringvergleich oder Regex
174   my $quoted_word   = $7; # nur gültig, wenn quoted string angegeben (siehe unten); dann "value" aus <%if var == "value" %>
175   my $bareword      = $8; # undef, falls quoted string angegeben wurde; andernfalls "othervar" aus <%if var == othervar %>
176
177   $not = !$not if ($operator_neg && $operator_neg eq '!');
178
179   substr($$contents, 0, length($&)) = "";
180
181   my $block;
182   ($block, $$contents) = $self->find_end($$contents, 0, $var, $not);
183   if (!$block) {
184     $self->{"error"} = "Unclosed $self->{tag_start}if$self->{tag_end}." unless ($self->{"error"});
185     $main::lxdebug->leave_sub();
186     return undef;
187   }
188
189   my $value = $self->_get_loop_variable($var, 0, @indices);
190   my $hit   = 0;
191
192   if ($operator_type) {
193     my $compare_to = $bareword ? $self->_get_loop_variable($bareword, 0, @indices) : $quoted_word;
194     if ($operator_type eq '=') {
195       $hit         = ($not && !($value eq $compare_to))     || (!$not && ($value eq $compare_to));
196     } else {
197       $hit         = ($not && !($value =~ m/$compare_to/i)) || (!$not && ($value =~ m/$compare_to/i));
198     }
199
200   } else {
201     $hit           = ($not && ! $value)                     || (!$not &&  $value);
202   }
203
204   if ($hit) {
205     my $new_text = $self->parse_block($block, @indices);
206     if (!defined($new_text)) {
207       $main::lxdebug->leave_sub();
208       return undef;
209     }
210     $$new_contents .= $new_text;
211   }
212
213   $main::lxdebug->leave_sub();
214
215   return 1;
216 }
217
218 1;
219
220 ####
221 #### LaTeXTemplate
222 ####
223
224 package LaTeXTemplate;
225
226 use vars qw(@ISA);
227
228 @ISA = qw(SimpleTemplate);
229
230 use strict;
231
232 sub new {
233   my $type = shift;
234
235   my $self = $type->SUPER::new(@_);
236
237   return $self;
238 }
239
240 sub format_string {
241   my ($self, $variable) = @_;
242   my $form = $self->{"form"};
243
244   $variable = $main::locale->quote_special_chars('Template/LaTeX', $variable);
245
246   # Allow some HTML markup to be converted into the output format's
247   # corresponding markup code, e.g. bold or italic.
248   my %markup_replace = ('b' => 'textbf',
249                         'i' => 'textit',
250                         'u' => 'underline');
251
252   foreach my $key (keys(%markup_replace)) {
253     my $new = $markup_replace{$key};
254     $variable =~ s/\$\<\$${key}\$\>\$(.*?)\$<\$\/${key}\$>\$/\\${new}\{$1\}/gi;
255   }
256
257   $variable =~ s/[\x00-\x1f]//g;
258
259   return $variable;
260 }
261
262 sub parse_foreach {
263   my ($self, $var, $text, $start_tag, $end_tag, @indices) = @_;
264
265   my ($form, $new_contents) = ($self->{"form"}, "");
266
267   my $ary = $self->_get_loop_variable($var, 1, @indices);
268
269   my $sum                          = 0;
270   my $current_page                 = 1;
271   my ($current_line, $corrent_row) = (0, 1);
272   my $description_array            = $self->_get_loop_variable("description",     1) || [];
273   my $longdescription_array        = $self->_get_loop_variable("longdescription", 1) || [];
274   my $linetotal_array              = $self->_get_loop_variable("linetotal",       1) || [];
275
276   $form->{TEMPLATE_ARRAYS}->{cumulatelinetotal} = [];
277
278   for (my $i = 0; $i < scalar(@{$ary}); $i++) {
279     $form->{"__first__"}   = $i == 1;
280     $form->{"__last__"}    = ($i + 1) == scalar(@{$ary});
281     $form->{"__odd__"}     = (($i + 1) % 2) == 1;
282     $form->{"__counter__"} = $i + 1;
283
284     if (scalar @{$description_array} == scalar @{$ary} && $self->{"chars_per_line"} != 0) {
285       my $lines = int(length($description_array->[$i]) / $self->{"chars_per_line"});
286       my $lpp;
287
288       $description_array->[$i] =~ s/(\\newline\s?)*$//;
289       my $_description = $description_array->[$i];
290       while ($_description =~ /\\newline/) {
291         $lines++;
292         $_description =~ s/\\newline//;
293       }
294       $lines++;
295
296       if ($current_page == 1) {
297         $lpp = $self->{"lines_on_first_page"};
298       } else {
299         $lpp = $self->{"lines_on_second_page"};
300       }
301
302       # Yes we need a manual page break -- or the user has forced one
303       if ((($current_line + $lines) > $lpp) || ($description_array->[$i] =~ /<pagebreak>/) || ($longdescription_array->[$i] =~ /<pagebreak>/)) {
304         my $pb = $self->{"pagebreak_block"};
305
306         # replace the special variables <%sumcarriedforward%>
307         # and <%lastpage%>
308
309         my $psum = $form->format_amount($self->{"myconfig"}, $sum, 2);
310         $pb =~ s/$self->{tag_start_qm}sumcarriedforward$self->{tag_end_qm}/$psum/g;
311         $pb =~ s/$self->{tag_start_qm}lastpage$self->{tag_end_qm}/$current_page/g;
312
313         my $new_text = $self->parse_block($pb, (@indices, $i));
314         return undef unless (defined($new_text));
315         $new_contents .= $new_text;
316
317         $current_page++;
318         $current_line = 0;
319       }
320       $current_line += $lines;
321     }
322
323     if ($i < scalar(@{$linetotal_array})) {
324       $sum += $form->parse_amount($self->{"myconfig"}, $linetotal_array->[$i]);
325     }
326
327     $form->{TEMPLATE_ARRAYS}->{cumulatelinetotal}->[$i] = $form->format_amount($self->{"myconfig"}, $sum, 2);
328
329     my $new_text = $self->parse_block($text, (@indices, $i));
330     return undef unless (defined($new_text));
331     $new_contents .= $start_tag . $new_text . $end_tag;
332   }
333   map({ delete($form->{"__${_}__"}); } qw(first last odd counter));
334
335   return $new_contents;
336 }
337
338 sub find_end {
339   my ($self, $text, $pos, $var, $not) = @_;
340
341   my $tag_start_len = length $self->{tag_start};
342
343   my $depth = 1;
344   $pos = 0 unless ($pos);
345
346   while ($pos < length($text)) {
347     $pos++;
348
349     next if (substr($text, $pos - 1, length($self->{tag_start})) ne $self->{tag_start});
350
351     my $keyword_pos = $pos - 1 + $tag_start_len;
352
353     if ((substr($text, $keyword_pos, 2) eq 'if') || (substr($text, $keyword_pos, 3) eq 'for')) {
354       $depth++;
355
356     } elsif ((substr($text, $keyword_pos, 4) eq 'else') && (1 == $depth)) {
357       if (!$var) {
358         $self->{"error"} =
359             "$self->{tag_start}else$self->{tag_end} outside of "
360           . "$self->{tag_start}if$self->{tag_end} / "
361           . "$self->{tag_start}ifnot$self->{tag_end}.";
362         return undef;
363       }
364
365       my $block = substr($text, 0, $pos - 1);
366       substr($text, 0, $pos - 1) = "";
367       $text =~ s!^$self->{tag_start_qm}.+?$self->{tag_end_qm}!!;
368       $text =  $self->{tag_start} . 'if' . ($not ?  " " : "not ") . $var . $self->{tag_end} . $text;
369
370       return ($block, $text);
371
372     } elsif (substr($text, $keyword_pos, 3) eq 'end') {
373       $depth--;
374       if ($depth == 0) {
375         my $block = substr($text, 0, $pos - 1);
376         substr($text, 0, $pos - 1) = "";
377         $text =~ s!^$self->{tag_start_qm}.+?$self->{tag_end_qm}!!;
378
379         return ($block, $text);
380       }
381     }
382   }
383
384   return undef;
385 }
386
387 sub parse_block {
388   $main::lxdebug->enter_sub();
389
390   my ($self, $contents, @indices) = @_;
391
392   my $new_contents = "";
393
394   while ($contents ne "") {
395     my $pos_if      = index($contents, $self->{tag_start} . 'if');
396     my $pos_foreach = index($contents, $self->{tag_start} . 'foreach');
397
398     if ((-1 == $pos_if) && (-1 == $pos_foreach)) {
399       $new_contents .= $self->substitute_vars($contents, @indices);
400       last;
401     }
402
403     if ((-1 == $pos_if) || ((-1 != $pos_foreach) && ($pos_if > $pos_foreach))) {
404       $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_foreach), @indices);
405       substr($contents, 0, $pos_foreach) = "";
406
407       if ($contents !~ m|^$self->{tag_start_qm}foreach (.+?)$self->{tag_end_qm}|) {
408         $self->{"error"} = "Malformed $self->{tag_start}foreach$self->{tag_end}.";
409         $main::lxdebug->leave_sub();
410         return undef;
411       }
412
413       my $var = $1;
414
415       substr($contents, 0, length($&)) = "";
416
417       my $block;
418       ($block, $contents) = $self->find_end($contents);
419       if (!$block) {
420         $self->{"error"} = "Unclosed $self->{tag_start}foreach$self->{tag_end}." unless ($self->{"error"});
421         $main::lxdebug->leave_sub();
422         return undef;
423       }
424
425       my $new_text = $self->parse_foreach($var, $block, "", "", @indices);
426       if (!defined($new_text)) {
427         $main::lxdebug->leave_sub();
428         return undef;
429       }
430       $new_contents .= $new_text;
431
432     } else {
433       if (!$self->_parse_block_if(\$contents, \$new_contents, $pos_if, @indices)) {
434         $main::lxdebug->leave_sub();
435         return undef;
436       }
437     }
438   }
439
440   $main::lxdebug->leave_sub();
441
442   return $new_contents;
443 }
444
445 sub parse_first_line {
446   my $self = shift;
447   my $line = shift || "";
448
449   if ($line =~ m/([^\s]+)set-tag-style([^\s]+)/) {
450     if ($1 eq $2) {
451       $self->{error} = "The tag start and end markers must not be equal.";
452       return 0;
453     }
454
455     $self->set_tag_style($1, $2);
456   }
457
458   return 1;
459 }
460
461 sub _parse_config_option {
462   my $self = shift;
463   my $line = shift;
464
465   $line =~ s/^\s*//;
466   $line =~ s/\s*$//;
467
468   my ($key, $value) = split m/\s*=\s*/, $line, 2;
469
470   if ($key eq 'tag-style') {
471     $self->set_tag_style(split(m/\s+/, $value, 2));
472   }
473 }
474
475 sub _parse_config_lines {
476   my $self  = shift;
477   my $lines = shift;
478
479   my ($comment_start, $comment_end) = ("", "");
480
481   if (ref $self eq 'LaTeXTemplate') {
482     $comment_start = '\s*%';
483   } elsif (ref $self eq 'HTMLTemplate') {
484     $comment_start = '\s*<!--';
485     $comment_end   = '>\s*';
486   } else {
487     $comment_start = '\s*\#';
488   }
489
490   my $num_lines = scalar @{ $lines };
491   my $i         = 0;
492
493   while ($i < $num_lines) {
494     my $line = $lines->[$i];
495
496     if ($line !~ m/^${comment_start}\s*config\s*:(.*)${comment_end}$/i) {
497       $i++;
498       next;
499     }
500
501     $self->_parse_config_option($1);
502     splice @{ $lines }, $i, 1;
503     $num_lines--;
504   }
505 }
506
507 sub _force_mandatory_packages {
508   my $self  = shift;
509   my $lines = shift;
510
511   my (%used_packages, $document_start_line);
512
513   foreach my $i (0 .. scalar @{ $lines } - 1) {
514     if ($lines->[$i] =~ m/\\usepackage[^\{]*{(.*?)}/) {
515       $used_packages{$1} = 1;
516
517     } elsif ($lines->[$i] =~ m/\\begin{document}/) {
518       $document_start_line = $i;
519       last;
520
521     }
522   }
523
524   $document_start_line = scalar @{ $lines } - 1 if (!defined $document_start_line);
525
526   if (!$used_packages{textcomp}) {
527     splice @{ $lines }, $document_start_line, 0, "\\usepackage{textcomp}\n";
528     $document_start_line++;
529   }
530 }
531
532 sub parse {
533   my $self = $_[0];
534   local *OUT = $_[1];
535   my $form = $self->{"form"};
536
537   if (!open(IN, "$form->{templates}/$form->{IN}")) {
538     $self->{"error"} = "$!";
539     return 0;
540   }
541   my @lines = <IN>;
542   close(IN);
543
544   $self->_parse_config_lines(\@lines);
545   $self->_force_mandatory_packages(\@lines) if (ref $self eq 'LaTeXTemplate');
546
547   my $contents = join("", @lines);
548
549   # detect pagebreak block and its parameters
550   if ($contents =~ /$self->{tag_start_qm}pagebreak\s+(\d+)\s+(\d+)\s+(\d+)\s*$self->{tag_end_qm}(.*?)$self->{tag_start_qm}end(\s*pagebreak)?$self->{tag_end_qm}/s) {
551     $self->{"chars_per_line"} = $1;
552     $self->{"lines_on_first_page"} = $2;
553     $self->{"lines_on_second_page"} = $3;
554     $self->{"pagebreak_block"} = $4;
555
556     substr($contents, length($`), length($&)) = "";
557   }
558
559   $self->{"forced_pagebreaks"} = [];
560
561   my $new_contents = $self->parse_block($contents);
562   if (!defined($new_contents)) {
563     $main::lxdebug->leave_sub();
564     return 0;
565   }
566
567   print(OUT $new_contents);
568
569   if ($form->{"format"} =~ /postscript/i) {
570     return $self->convert_to_postscript();
571   } elsif ($form->{"format"} =~ /pdf/i) {
572     return $self->convert_to_pdf();
573   } else {
574     return 1;
575   }
576 }
577
578 sub convert_to_postscript {
579   my ($self) = @_;
580   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
581
582   # Convert the tex file to postscript
583
584   if (!chdir("$userspath")) {
585     $self->{"error"} = "chdir : $!";
586     $self->cleanup();
587     return 0;
588   }
589
590   $form->{tmpfile} =~ s/\Q$userspath\E\///g;
591
592   my $latex = $self->_get_latex_path();
593
594   for (my $run = 1; $run <= 2; $run++) {
595     system("${latex} --interaction=nonstopmode $form->{tmpfile} " .
596            "> $form->{tmpfile}.err");
597     if ($?) {
598       $self->{"error"} = $form->cleanup();
599       $self->cleanup();
600       return 0;
601     }
602   }
603
604   $form->{tmpfile} =~ s/tex$/dvi/;
605
606   system("dvips $form->{tmpfile} -o -q > /dev/null");
607   if ($?) {
608     $self->{"error"} = "dvips : $!";
609     $self->cleanup();
610     return 0;
611   }
612   $form->{tmpfile} =~ s/dvi$/ps/;
613
614   $self->cleanup();
615
616   return 1;
617 }
618
619 sub convert_to_pdf {
620   my ($self) = @_;
621   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
622
623   # Convert the tex file to PDF
624
625   if (!chdir("$userspath")) {
626     $self->{"error"} = "chdir : $!";
627     $self->cleanup();
628     return 0;
629   }
630
631   $form->{tmpfile} =~ s/\Q$userspath\E\///g;
632
633   my $latex = $self->_get_latex_path();
634
635   for (my $run = 1; $run <= 2; $run++) {
636     system("${latex} --interaction=nonstopmode $form->{tmpfile} " .
637            "> $form->{tmpfile}.err");
638     if ($?) {
639       $self->{"error"} = $form->cleanup();
640       $self->cleanup();
641       return 0;
642     }
643   }
644
645   $form->{tmpfile} =~ s/tex$/pdf/;
646
647   $self->cleanup();
648 }
649
650 sub _get_latex_path {
651   return $main::latex_bin || 'pdflatex';
652 }
653
654 sub get_mime_type() {
655   my ($self) = @_;
656
657   if ($self->{"form"}->{"format"} =~ /postscript/i) {
658     return "application/postscript";
659   } else {
660     return "application/pdf";
661   }
662 }
663
664 sub uses_temp_file {
665   return 1;
666 }
667
668
669 ####
670 #### HTMLTemplate
671 ####
672
673 package HTMLTemplate;
674
675 use vars qw(@ISA);
676
677 @ISA = qw(LaTeXTemplate);
678
679 use strict;
680
681 sub new {
682   my $type = shift;
683
684   return $type->SUPER::new(@_);
685 }
686
687 sub format_string {
688   my ($self, $variable) = @_;
689   my $form = $self->{"form"};
690
691   $variable = $main::locale->quote_special_chars('Template/HTML', $variable);
692
693   # Allow some HTML markup to be converted into the output format's
694   # corresponding markup code, e.g. bold or italic.
695   my @markup_replace = ('b', 'i', 's', 'u', 'sub', 'sup');
696
697   foreach my $key (@markup_replace) {
698     $variable =~ s/\&lt;(\/?)${key}\&gt;/<$1${key}>/g;
699   }
700
701   return $variable;
702 }
703
704 sub get_mime_type() {
705   my ($self) = @_;
706
707   if ($self->{"form"}->{"format"} =~ /postscript/i) {
708     return "application/postscript";
709   } elsif ($self->{"form"}->{"format"} =~ /pdf/i) {
710     return "application/pdf";
711   } else {
712     return "text/html";
713   }
714 }
715
716 sub uses_temp_file {
717   my ($self) = @_;
718
719   if ($self->{"form"}->{"format"} =~ /postscript/i) {
720     return 1;
721   } elsif ($self->{"form"}->{"format"} =~ /pdf/i) {
722     return 1;
723   } else {
724     return 0;
725   }
726 }
727
728 sub convert_to_postscript {
729   my ($self) = @_;
730   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
731
732   # Convert the HTML file to postscript
733
734   if (!chdir("$userspath")) {
735     $self->{"error"} = "chdir : $!";
736     $self->cleanup();
737     return 0;
738   }
739
740   $form->{"tmpfile"} =~ s/\Q$userspath\E\///g;
741   my $psfile = $form->{"tmpfile"};
742   $psfile =~ s/.html/.ps/;
743   if ($psfile eq $form->{"tmpfile"}) {
744     $psfile .= ".ps";
745   }
746
747   system("html2ps -f html2ps-config < $form->{tmpfile} > $psfile");
748   if ($?) {
749     $self->{"error"} = $form->cleanup();
750     $self->cleanup();
751     return 0;
752   }
753
754   $form->{"tmpfile"} = $psfile;
755
756   $self->cleanup();
757
758   return 1;
759 }
760
761 sub convert_to_pdf {
762   my ($self) = @_;
763   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
764
765   # Convert the HTML file to PDF
766
767   if (!chdir("$userspath")) {
768     $self->{"error"} = "chdir : $!";
769     $self->cleanup();
770     return 0;
771   }
772
773   $form->{"tmpfile"} =~ s/\Q$userspath\E\///g;
774   my $pdffile = $form->{"tmpfile"};
775   $pdffile =~ s/.html/.pdf/;
776   if ($pdffile eq $form->{"tmpfile"}) {
777     $pdffile .= ".pdf";
778   }
779
780   system("html2ps -f html2ps-config < $form->{tmpfile} | ps2pdf - $pdffile");
781   if ($?) {
782     $self->{"error"} = $form->cleanup();
783     $self->cleanup();
784     return 0;
785   }
786
787   $form->{"tmpfile"} = $pdffile;
788
789   $self->cleanup();
790
791   return 1;
792 }
793
794
795 ####
796 #### PlainTextTemplate
797 ####
798
799 package PlainTextTemplate;
800
801 use vars qw(@ISA);
802
803 @ISA = qw(LaTeXTemplate);
804
805 use strict;
806
807 sub new {
808   my $type = shift;
809
810   return $type->SUPER::new(@_);
811 }
812
813 sub format_string {
814   my ($self, $variable) = @_;
815
816   return $variable;
817 }
818
819 sub get_mime_type {
820   return "text/plain";
821 }
822
823 sub parse {
824 }
825
826 1;
827
828 ####
829 #### OpenDocumentTemplate
830 ####
831
832 package OpenDocumentTemplate;
833
834 use POSIX 'setsid';
835 use vars qw(@ISA);
836
837 use Cwd;
838 # use File::Copy;
839 # use File::Spec;
840 # use File::Temp qw(:mktemp);
841 use IO::File;
842
843 @ISA = qw(SimpleTemplate);
844
845 use strict;
846
847 sub new {
848   my $type = shift;
849
850   my $self = $type->SUPER::new(@_);
851
852   foreach my $module (qw(Archive::Zip Text::Iconv)) {
853     eval("use ${module};");
854     if ($@) {
855       $self->{"form"}->error("The Perl module '${module}' could not be " .
856                              "loaded. Support for OpenDocument templates " .
857                              "does not work without it. Please install your " .
858                              "distribution's package or get the module from " .
859                              "CPAN ( http://www.cpan.org ).");
860     }
861   }
862
863   $self->{"rnd"}   = int(rand(1000000));
864   $self->{"iconv"} = Text::Iconv->new($main::dbcharset, "UTF-8");
865
866   $self->set_tag_style('&lt;%', '%&gt;');
867   $self->{quot_re} = '&quot;';
868
869   return $self;
870 }
871
872 sub parse_foreach {
873   my ($self, $var, $text, $start_tag, $end_tag, @indices) = @_;
874
875   my ($form, $new_contents) = ($self->{"form"}, "");
876
877   my $ary = $self->_get_loop_variable($var, 1, @indices);
878
879   for (my $i = 0; $i < scalar(@{$ary}); $i++) {
880     $form->{"__first__"} = $i == 0;
881     $form->{"__last__"} = ($i + 1) == scalar(@{$ary});
882     $form->{"__odd__"} = (($i + 1) % 2) == 1;
883     $form->{"__counter__"} = $i + 1;
884     my $new_text = $self->parse_block($text, (@indices, $i));
885     return undef unless (defined($new_text));
886     $new_contents .= $start_tag . $new_text . $end_tag;
887   }
888   map({ delete($form->{"__${_}__"}); } qw(first last odd counter));
889
890   return $new_contents;
891 }
892
893 sub find_end {
894   my ($self, $text, $pos, $var, $not) = @_;
895
896   my $depth = 1;
897   $pos = 0 unless ($pos);
898
899   while ($pos < length($text)) {
900     $pos++;
901
902     next if (substr($text, $pos - 1, 5) ne '&lt;%');
903
904     if ((substr($text, $pos + 4, 2) eq 'if') || (substr($text, $pos + 4, 3) eq 'for')) {
905       $depth++;
906
907     } elsif ((substr($text, $pos + 4, 4) eq 'else') && (1 == $depth)) {
908       if (!$var) {
909         $self->{"error"} = '<%else%> outside of <%if%> / <%ifnot%>.';
910         return undef;
911       }
912
913       my $block = substr($text, 0, $pos - 1);
914       substr($text, 0, $pos - 1) = "";
915       $text =~ s!^\&lt;\%[^\%]+\%\&gt;!!;
916       $text = '&lt;%if' . ($not ?  " " : "not ") . $var . '%&gt;' . $text;
917
918       return ($block, $text);
919
920     } elsif (substr($text, $pos + 4, 3) eq 'end') {
921       $depth--;
922       if ($depth == 0) {
923         my $block = substr($text, 0, $pos - 1);
924         substr($text, 0, $pos - 1) = "";
925         $text =~ s!^\&lt;\%[^\%]+\%\&gt;!!;
926
927         return ($block, $text);
928       }
929     }
930   }
931
932   return undef;
933 }
934
935 sub parse_block {
936   $main::lxdebug->enter_sub();
937
938   my ($self, $contents, @indices) = @_;
939
940   my $new_contents = "";
941
942   while ($contents ne "") {
943     if (substr($contents, 0, 1) eq "<") {
944       $contents =~ m|^<[^>]+>|;
945       my $tag = $&;
946       substr($contents, 0, length($&)) = "";
947
948       if ($tag =~ m|<table:table-row|) {
949         $contents =~ m|^(.*?)(</table:table-row[^>]*>)|;
950         my $table_row = $1;
951         my $end_tag = $2;
952         substr($contents, 0, length($1) + length($end_tag)) = "";
953
954         if ($table_row =~ m|\&lt;\%foreachrow\s+(.*?)\%\&gt;|) {
955           my $var = $1;
956
957           substr($table_row, length($`), length($&)) = "";
958
959           my ($t1, $t2) = $self->find_end($table_row, length($`));
960           if (!$t1) {
961             $self->{"error"} = "Unclosed <\%foreachrow\%>." unless ($self->{"error"});
962             $main::lxdebug->leave_sub();
963             return undef;
964           }
965
966           my $new_text = $self->parse_foreach($var, $t1 . $t2, $tag, $end_tag, @indices);
967           if (!defined($new_text)) {
968             $main::lxdebug->leave_sub();
969             return undef;
970           }
971           $new_contents .= $new_text;
972
973         } else {
974           my $new_text = $self->parse_block($table_row, @indices);
975           if (!defined($new_text)) {
976             $main::lxdebug->leave_sub();
977             return undef;
978           }
979           $new_contents .= $tag . $new_text . $end_tag;
980         }
981
982       } else {
983         $new_contents .= $tag;
984       }
985
986     } else {
987       $contents =~ /^[^<]+/;
988       my $text = $&;
989
990       my $pos_if = index($text, '&lt;%if');
991       my $pos_foreach = index($text, '&lt;%foreach');
992
993       if ((-1 == $pos_if) && (-1 == $pos_foreach)) {
994         substr($contents, 0, length($text)) = "";
995         $new_contents .= $self->substitute_vars($text, @indices);
996         next;
997       }
998
999       if ((-1 == $pos_if) || ((-1 != $pos_foreach) && ($pos_if > $pos_foreach))) {
1000         $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_foreach), @indices);
1001         substr($contents, 0, $pos_foreach) = "";
1002
1003         if ($contents !~ m|^\&lt;\%foreach (.*?)\%\&gt;|) {
1004           $self->{"error"} = "Malformed <\%foreach\%>.";
1005           $main::lxdebug->leave_sub();
1006           return undef;
1007         }
1008
1009         my $var = $1;
1010
1011         substr($contents, 0, length($&)) = "";
1012
1013         my $block;
1014         ($block, $contents) = $self->find_end($contents);
1015         if (!$block) {
1016           $self->{"error"} = "Unclosed <\%foreach\%>." unless ($self->{"error"});
1017           $main::lxdebug->leave_sub();
1018           return undef;
1019         }
1020
1021         my $new_text = $self->parse_foreach($var, $block, "", "", @indices);
1022         if (!defined($new_text)) {
1023           $main::lxdebug->leave_sub();
1024           return undef;
1025         }
1026         $new_contents .= $new_text;
1027
1028       } else {
1029         if (!$self->_parse_block_if(\$contents, \$new_contents, $pos_if, @indices)) {
1030           $main::lxdebug->leave_sub();
1031           return undef;
1032         }
1033       }
1034     }
1035   }
1036
1037   $main::lxdebug->leave_sub();
1038
1039   return $new_contents;
1040 }
1041
1042 sub parse {
1043   $main::lxdebug->enter_sub();
1044
1045   my $self = $_[0];
1046   local *OUT = $_[1];
1047   my $form = $self->{"form"};
1048
1049   close(OUT);
1050
1051   my $file_name;
1052   if ($form->{"IN"} =~ m|^/|) {
1053     $file_name = $form->{"IN"};
1054   } else {
1055     $file_name = $form->{"templates"} . "/" . $form->{"IN"};
1056   }
1057
1058   my $zip = Archive::Zip->new();
1059   if (Archive::Zip->AZ_OK != $zip->read($file_name)) {
1060     $self->{"error"} = "File not found/is not a OpenDocument file.";
1061     $main::lxdebug->leave_sub();
1062     return 0;
1063   }
1064
1065   my $contents = $zip->contents("content.xml");
1066   if (!$contents) {
1067     $self->{"error"} = "File is not a OpenDocument file.";
1068     $main::lxdebug->leave_sub();
1069     return 0;
1070   }
1071
1072   my $rnd = $self->{"rnd"};
1073   my $new_styles = qq|<style:style style:name="TLXO${rnd}BOLD" style:family="text">
1074 <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
1075 </style:style>
1076 <style:style style:name="TLXO${rnd}ITALIC" style:family="text">
1077 <style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"/>
1078 </style:style>
1079 <style:style style:name="TLXO${rnd}UNDERLINE" style:family="text">
1080 <style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
1081 </style:style>
1082 <style:style style:name="TLXO${rnd}STRIKETHROUGH" style:family="text">
1083 <style:text-properties style:text-line-through-style="solid"/>
1084 </style:style>
1085 <style:style style:name="TLXO${rnd}SUPER" style:family="text">
1086 <style:text-properties style:text-position="super 58%"/>
1087 </style:style>
1088 <style:style style:name="TLXO${rnd}SUB" style:family="text">
1089 <style:text-properties style:text-position="sub 58%"/>
1090 </style:style>
1091 |;
1092
1093   $contents =~ s|</office:automatic-styles>|${new_styles}</office:automatic-styles>|;
1094   $contents =~ s|[\n\r]||gm;
1095
1096   my $new_contents = $self->parse_block($contents);
1097   if (!defined($new_contents)) {
1098     $main::lxdebug->leave_sub();
1099     return 0;
1100   }
1101
1102 #   $new_contents =~ s|>|>\n|g;
1103
1104   $zip->contents("content.xml", $new_contents);
1105
1106   my $styles = $zip->contents("styles.xml");
1107   if ($contents) {
1108     my $new_styles = $self->parse_block($styles);
1109     if (!defined($new_contents)) {
1110       $main::lxdebug->leave_sub();
1111       return 0;
1112     }
1113     $zip->contents("styles.xml", $new_styles);
1114   }
1115
1116   $zip->writeToFileNamed($form->{"tmpfile"}, 1);
1117
1118   my $res = 1;
1119   if ($form->{"format"} =~ /pdf/) {
1120     $res = $self->convert_to_pdf();
1121   }
1122
1123   $main::lxdebug->leave_sub();
1124   return $res;
1125 }
1126
1127 sub is_xvfb_running {
1128   $main::lxdebug->enter_sub();
1129
1130   my ($self) = @_;
1131
1132   local *IN;
1133   my $dfname = $self->{"userspath"} . "/xvfb_display";
1134   my $display;
1135
1136   $main::lxdebug->message(LXDebug->DEBUG2(), "    Looking for $dfname\n");
1137   if ((-f $dfname) && open(IN, $dfname)) {
1138     my $pid = <IN>;
1139     chomp($pid);
1140     $display = <IN>;
1141     chomp($display);
1142     my $xauthority = <IN>;
1143     chomp($xauthority);
1144     close(IN);
1145
1146     $main::lxdebug->message(LXDebug->DEBUG2(), "      found with $pid and $display\n");
1147
1148     if ((! -d "/proc/$pid") || !open(IN, "/proc/$pid/cmdline")) {
1149       $main::lxdebug->message(LXDebug->DEBUG2(), "  no/wrong process #1\n");
1150       unlink($dfname, $xauthority);
1151       $main::lxdebug->leave_sub();
1152       return undef;
1153     }
1154     my $line = <IN>;
1155     close(IN);
1156     if ($line !~ /xvfb/i) {
1157       $main::lxdebug->message(LXDebug->DEBUG2(), "      no/wrong process #2\n");
1158       unlink($dfname, $xauthority);
1159       $main::lxdebug->leave_sub();
1160       return undef;
1161     }
1162
1163     $ENV{"XAUTHORITY"} = $xauthority;
1164     $ENV{"DISPLAY"} = $display;
1165   } else {
1166     $main::lxdebug->message(LXDebug->DEBUG2(), "      not found\n");
1167   }
1168
1169   $main::lxdebug->leave_sub();
1170
1171   return $display;
1172 }
1173
1174 sub spawn_xvfb {
1175   $main::lxdebug->enter_sub();
1176
1177   my ($self) = @_;
1178
1179   $main::lxdebug->message(LXDebug->DEBUG2, "spawn_xvfb()\n");
1180
1181   my $display = $self->is_xvfb_running();
1182
1183   if ($display) {
1184     $main::lxdebug->leave_sub();
1185     return $display;
1186   }
1187
1188   $display = 99;
1189   while ( -f "/tmp/.X${display}-lock") {
1190     $display++;
1191   }
1192   $display = ":${display}";
1193   $main::lxdebug->message(LXDebug->DEBUG2(), "  display $display\n");
1194
1195   my $mcookie = `mcookie`;
1196   die("Installation error: mcookie not found.") if ($? != 0);
1197   chomp($mcookie);
1198
1199   $main::lxdebug->message(LXDebug->DEBUG2(), "  mcookie $mcookie\n");
1200
1201   my $xauthority = "/tmp/.Xauthority-" . $$ . "-" . time() . "-" . int(rand(9999999));
1202   $ENV{"XAUTHORITY"} = $xauthority;
1203
1204   $main::lxdebug->message(LXDebug->DEBUG2(), "  xauthority $xauthority\n");
1205
1206   system("xauth add \"${display}\" . \"${mcookie}\"");
1207   if ($? != 0) {
1208     $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started (xauth: $!)";
1209     $main::lxdebug->leave_sub();
1210     return undef;
1211   }
1212
1213   $main::lxdebug->message(LXDebug->DEBUG2(), "  about to fork()\n");
1214
1215   my $pid = fork();
1216   if (0 == $pid) {
1217     $main::lxdebug->message(LXDebug->DEBUG2(), "  Child execing\n");
1218     exec($main::xvfb_bin, $display, "-screen", "0", "640x480x8", "-nolisten", "tcp");
1219   }
1220   sleep(3);
1221   $main::lxdebug->message(LXDebug->DEBUG2(), "  parent dont sleeping\n");
1222
1223   local *OUT;
1224   my $dfname = $self->{"userspath"} . "/xvfb_display";
1225   if (!open(OUT, ">$dfname")) {
1226     $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started ($dfname: $!)";
1227     unlink($xauthority);
1228     kill($pid);
1229     $main::lxdebug->leave_sub();
1230     return undef;
1231   }
1232   print(OUT "$pid\n$display\n$xauthority\n");
1233   close(OUT);
1234
1235   $main::lxdebug->message(LXDebug->DEBUG2(), "  parent re-testing\n");
1236
1237   if (!$self->is_xvfb_running()) {
1238     $self->{"error"} = "Conversion to PDF failed because OpenOffice could not be started.";
1239     unlink($xauthority, $dfname);
1240     kill($pid);
1241     $main::lxdebug->leave_sub();
1242     return undef;
1243   }
1244
1245   $main::lxdebug->message(LXDebug->DEBUG2(), "  spawn OK\n");
1246
1247   $main::lxdebug->leave_sub();
1248
1249   return $display;
1250 }
1251
1252 sub is_openoffice_running {
1253   $main::lxdebug->enter_sub();
1254
1255   system("./scripts/oo-uno-test-conn.py $main::openofficeorg_daemon_port " .
1256          "> /dev/null 2> /dev/null");
1257   my $res = $? == 0;
1258   $main::lxdebug->message(LXDebug->DEBUG2(), "  is_openoffice_running(): $?\n");
1259
1260   $main::lxdebug->leave_sub();
1261
1262   return $res;
1263 }
1264
1265 sub spawn_openoffice {
1266   $main::lxdebug->enter_sub();
1267
1268   my ($self) = @_;
1269
1270   $main::lxdebug->message(LXDebug->DEBUG2(), "spawn_openoffice()\n");
1271
1272   my ($try, $spawned_oo, $res);
1273
1274   $res = 0;
1275   for ($try = 0; $try < 15; $try++) {
1276     if ($self->is_openoffice_running()) {
1277       $res = 1;
1278       last;
1279     }
1280
1281     if (!$spawned_oo) {
1282       my $pid = fork();
1283       if (0 == $pid) {
1284         $main::lxdebug->message(LXDebug->DEBUG2(), "  Child daemonizing\n");
1285         chdir('/');
1286         open(STDIN, '/dev/null');
1287         open(STDOUT, '>/dev/null');
1288         my $new_pid = fork();
1289         exit if ($new_pid);
1290         my $ssres = setsid();
1291         $main::lxdebug->message(LXDebug->DEBUG2(), "  Child execing\n");
1292         my @cmdline = ($main::openofficeorg_writer_bin,
1293                        "-minimized", "-norestore", "-nologo", "-nolockcheck",
1294                        "-headless",
1295                        "-accept=socket,host=localhost,port=" .
1296                        $main::openofficeorg_daemon_port . ";urp;");
1297         exec(@cmdline);
1298       }
1299
1300       $main::lxdebug->message(LXDebug->DEBUG2(), "  Parent after fork\n");
1301       $spawned_oo = 1;
1302       sleep(3);
1303     }
1304
1305     sleep($try >= 5 ? 2 : 1);
1306   }
1307
1308   if (!$res) {
1309     $self->{"error"} = "Conversion from OpenDocument to PDF failed because " .
1310       "OpenOffice could not be started.";
1311   }
1312
1313   $main::lxdebug->leave_sub();
1314
1315   return $res;
1316 }
1317
1318 sub convert_to_pdf {
1319   $main::lxdebug->enter_sub();
1320
1321   my ($self) = @_;
1322
1323   my $form = $self->{"form"};
1324
1325   my $filename = $form->{"tmpfile"};
1326   $filename =~ s/.odt$//;
1327   if (substr($filename, 0, 1) ne "/") {
1328     $filename = getcwd() . "/${filename}";
1329   }
1330
1331   if (substr($self->{"userspath"}, 0, 1) eq "/") {
1332     $ENV{'HOME'} = $self->{"userspath"};
1333   } else {
1334     $ENV{'HOME'} = getcwd() . "/" . $self->{"userspath"};
1335   }
1336
1337   if (!$self->spawn_xvfb()) {
1338     $main::lxdebug->leave_sub();
1339     return 0;
1340   }
1341
1342   my @cmdline;
1343   if (!$main::openofficeorg_daemon) {
1344     @cmdline = ($main::openofficeorg_writer_bin,
1345                 "-minimized", "-norestore", "-nologo", "-nolockcheck",
1346                 "-headless",
1347                 "file:${filename}.odt",
1348                 "macro://" . (split('/', $filename))[-1] .
1349                 "/Standard.Conversion.ConvertSelfToPDF()");
1350   } else {
1351     if (!$self->spawn_openoffice()) {
1352       $main::lxdebug->leave_sub();
1353       return 0;
1354     }
1355
1356     @cmdline = ("./scripts/oo-uno-convert-pdf.py",
1357                 $main::openofficeorg_daemon_port,
1358                 "${filename}.odt");
1359   }
1360
1361   system(@cmdline);
1362
1363   my $res = $?;
1364   if (0 == $?) {
1365     $form->{"tmpfile"} =~ s/odt$/pdf/;
1366
1367     unlink($filename . ".odt");
1368
1369     $main::lxdebug->leave_sub();
1370     return 1;
1371
1372   }
1373
1374   unlink($filename . ".odt", $filename . ".pdf");
1375   $self->{"error"} = "Conversion from OpenDocument to PDF failed. " .
1376     "Exit code: $res";
1377
1378   $main::lxdebug->leave_sub();
1379   return 0;
1380 }
1381
1382 sub format_string {
1383   my ($self, $variable) = @_;
1384   my $form = $self->{"form"};
1385   my $iconv = $self->{"iconv"};
1386
1387   $variable = $main::locale->quote_special_chars('Template/OpenDocument', $variable);
1388
1389   # Allow some HTML markup to be converted into the output format's
1390   # corresponding markup code, e.g. bold or italic.
1391   my $rnd = $self->{"rnd"};
1392   my %markup_replace = ("b" => "BOLD", "i" => "ITALIC", "s" => "STRIKETHROUGH",
1393                         "u" => "UNDERLINE", "sup" => "SUPER", "sub" => "SUB");
1394
1395   foreach my $key (keys(%markup_replace)) {
1396     my $value = $markup_replace{$key};
1397     $variable =~ s|\&lt;${key}\&gt;|<text:span text:style-name=\"TLXO${rnd}${value}\">|gi; #"
1398     $variable =~ s|\&lt;/${key}\&gt;|</text:span>|gi;
1399   }
1400
1401   return $iconv->convert($variable);
1402 }
1403
1404 sub get_mime_type() {
1405   my ($self) = @_;
1406
1407   if ($self->{"form"}->{"format"} =~ /pdf/) {
1408     return "application/pdf";
1409   } else {
1410     return "application/vnd.oasis.opendocument.text";
1411   }
1412 }
1413
1414 sub uses_temp_file {
1415   return 1;
1416 }
1417
1418
1419 ##########################################################
1420 ####
1421 #### XMLTemplate
1422 ####
1423 ##########################################################
1424
1425 package XMLTemplate;
1426
1427 use vars qw(@ISA);
1428
1429 @ISA = qw(HTMLTemplate);
1430
1431 use strict;
1432
1433 sub new {
1434   #evtl auskommentieren
1435   my $type = shift;
1436
1437   return $type->SUPER::new(@_);
1438 }
1439
1440 sub format_string {
1441   my ($self, $variable) = @_;
1442   my $form = $self->{"form"};
1443
1444   $variable = $main::locale->quote_special_chars('Template/XML', $variable);
1445
1446   # Allow no markup to be converted into the output format
1447   my @markup_replace = ('b', 'i', 's', 'u', 'sub', 'sup');
1448
1449   foreach my $key (@markup_replace) {
1450     $variable =~ s/\&lt;(\/?)${key}\&gt;//g;
1451   }
1452
1453   return $variable;
1454 }
1455
1456 sub get_mime_type() {
1457   my ($self) = @_;
1458
1459   if ($self->{"form"}->{"format"} =~ /elsterwinston/i) {
1460     return "application/xml ";
1461   } elsif ($self->{"form"}->{"format"} =~ /elstertaxbird/i) {
1462     return "application/x-taxbird";
1463   } else {
1464     return "text";
1465   }
1466 }
1467
1468 sub uses_temp_file {
1469   # tempfile needet for XML Output
1470   return 1;
1471 }
1472
1473 1;