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