1 package SL::ReportGenerator;
4 use List::Util qw(max);
5 use Scalar::Util qw(blessed);
7 #use PDF::API2; # these two eat up to .75s on startup. only load them if we actually need them
11 use SL::Helper::GlAttachments qw(append_gl_pdf_attachments);
12 use SL::Helper::CreatePDF qw(merge_pdfs);
14 # Cause locales.pl to parse these files:
15 # parse_html_template('report_generator/html_report')
22 $self->{myconfig} = shift;
23 $self->{form} = shift;
27 'std_column_visibility' => 0,
28 'output_format' => 'HTML',
29 'controller_class ' => '',
30 'allow_pdf_export' => 1,
31 'allow_csv_export' => 1,
32 'html_template' => 'report_generator/html_report',
35 'orientation' => 'landscape',
36 'font_name' => 'Verdana',
40 'margin_bottom' => 1.5,
41 'margin_right' => 1.5,
51 'eol_style' => 'Unix',
53 'encoding' => 'UTF-8',
58 'variable_list' => [],
61 $self->{data_present} = 0;
65 $self->set_options(@_) if (@_);
74 $self->{columns} = \%columns;
76 foreach my $column (values %{ $self->{columns} }) {
77 $column->{visible} = $self->{options}->{std_column_visibility} unless defined $column->{visible};
80 if( $::form->{report_generator_csv_options_for_import} ) {
81 foreach my $key (keys %{ $self->{columns} }) {
82 $self->{columns}{$key}{text} = $key;
86 $self->set_column_order(sort keys %{ $self->{columns} });
89 sub set_column_order {
92 $self->{column_order} = [ grep { !$seen{$_}++ } @_, sort keys %{ $self->{columns} } ];
95 sub set_sort_indicator {
98 $self->{options}->{sort_indicator_column} = shift;
99 $self->{options}->{sort_indicator_direction} = shift;
107 while (my $arg = shift) {
110 if ('ARRAY' eq ref $arg) {
113 } elsif ('HASH' eq ref $arg) {
117 $self->{form}->error('Incorrect usage -- expecting hash or array ref');
120 my @columns_with_default_alignment = grep { defined $self->{columns}->{$_}->{align} } keys %{ $self->{columns} };
122 foreach my $row (@{ $row_set }) {
123 foreach my $column (@columns_with_default_alignment) {
124 $row->{$column} ||= { };
125 $row->{$column}->{align} = $self->{columns}->{$column}->{align} unless (defined $row->{$column}->{align});
128 foreach my $field (qw(data link link_class)) {
129 map { $row->{$_}->{$field} = [ $row->{$_}->{$field} ] if (ref $row->{$_}->{$field} ne 'ARRAY') } keys %{ $row };
133 push @{ $self->{data} }, $row_set;
134 $last_row_set = $row_set;
136 $self->{data_present} = 1;
139 return $last_row_set;
145 push @{ $self->{data} }, { 'type' => 'separator' };
152 push @{ $self->{data} }, $data;
159 $self->{data_present} = 0;
166 while (my ($key, $value) = each %options) {
167 if ($key eq 'pdf_export') {
168 map { $self->{options}->{pdf_export}->{$_} = $value->{$_} } keys %{ $value };
170 $self->{options}->{$key} = $value;
175 sub set_options_from_form {
178 my $form = $self->{form};
179 my $myconfig = $self->{myconfig};
181 foreach my $key (qw(output_format)) {
182 my $full_key = "report_generator_${key}";
183 $self->{options}->{$key} = $form->{$full_key} if (defined $form->{$full_key});
186 foreach my $format (qw(pdf csv)) {
187 my $opts = $self->{options}->{"${format}_export"};
188 foreach my $key (keys %{ $opts }) {
189 my $full_key = "report_generator_${format}_options_${key}";
190 $opts->{$key} = $key =~ /^margin/ ? $form->parse_amount($myconfig, $form->{$full_key}) : $form->{$full_key};
195 sub set_export_options {
200 'variable_list' => [ @_ ],
204 sub set_custom_headers {
208 $self->{custom_headers} = [ @_ ];
210 delete $self->{custom_headers};
214 sub get_attachment_basename {
216 my $filename = $self->{options}->{attachment_basename} || 'report';
218 # FIXME: this is bonkers. add a real sluggify method somewhere or import one.
219 $filename =~ s|.*\\||;
220 $filename =~ s|.*/||;
221 $filename =~ s| |_|g;
226 sub generate_with_headers {
227 my ($self, %params) = @_;
228 my $format = lc $self->{options}->{output_format};
229 my $form = $self->{form};
231 if (!$self->{columns}) {
232 $form->error('Incorrect usage -- no columns specified');
235 if ($format eq 'html') {
236 my $content = $self->generate_html_content(%params);
237 my $title = $form->{title};
238 $form->{title} = $self->{title} if ($self->{title});
239 $form->header(no_layout => $params{no_layout});
240 $form->{title} = $title;
244 } elsif ($format eq 'csv') {
245 # FIXME: don't do mini http in here
246 my $filename = $self->get_attachment_basename();
247 print qq|content-type: text/csv\n|;
248 print qq|content-disposition: attachment; filename=${filename}.csv\n\n|;
249 $::locale->with_raw_io(\*STDOUT, sub {
250 $self->generate_csv_content();
253 } elsif ($format eq 'pdf') {
254 $self->generate_pdf_content();
257 $form->error('Incorrect usage -- unknown format (supported are HTML, CSV, PDF)');
261 sub get_visible_columns {
265 return grep { my $c = $self->{columns}->{$_}; $c && $c->{visible} && (($c->{visible} == 1) || ($c->{visible} =~ /\Q${format}\E/i)) } @{ $self->{column_order} };
272 $value = $main::locale->quote_special_chars('HTML', $value);
274 $value =~ s/\n/<br>/g;
279 sub prepare_html_content {
280 my ($self, %params) = @_;
282 my ($column, $name, @column_headers);
284 my $opts = $self->{options};
285 my @visible_columns = $self->get_visible_columns('HTML');
287 foreach $name (@visible_columns) {
288 $column = $self->{columns}->{$name};
292 'align' => $column->{align},
293 'link' => $column->{link},
294 'text' => $column->{text},
295 'raw_header_data' => $column->{raw_header_data},
296 'show_sort_indicator' => $name eq $opts->{sort_indicator_column},
297 'sort_indicator_direction' => $opts->{sort_indicator_direction},
300 push @column_headers, $header;
304 if ($self->{custom_headers}) {
305 $header_rows = $self->{custom_headers};
307 $header_rows = [ \@column_headers ];
310 my ($outer_idx, $inner_idx) = (0, 0);
314 foreach my $row_set (@{ $self->{data} }) {
315 if ('HASH' eq ref $row_set) {
316 if ($row_set->{type} eq 'separator') {
317 if (! scalar @rows) {
318 $next_border_top = 1;
320 $rows[-1]->{BORDER_BOTTOM} = 1;
328 'IS_COLSPAN_DATA' => $row_set->{type} eq 'colspan_data',
329 'NUM_COLUMNS' => scalar @visible_columns,
330 'BORDER_TOP' => $next_border_top,
331 'data' => $row_set->{data},
334 push @rows, $row_data;
336 $next_border_top = 0;
343 foreach my $row (@{ $row_set }) {
346 my $output_columns = [ ];
348 foreach my $col_name (@visible_columns) {
354 my $col = $row->{$col_name} || { data => [] };
355 $col->{CELL_ROWS} = [ ];
356 foreach my $i (0 .. scalar(@{ $col->{data} }) - 1) {
357 push @{ $col->{CELL_ROWS} }, {
358 'data' => '' . $self->html_format($col->{data}->[$i]),
359 'link' => $col->{link}->[$i],
360 link_class => $col->{link_class}->[$i],
364 # Force at least a to be displayed so that browsers
365 # will format the table cell (e.g. borders etc).
366 if (!scalar @{ $col->{CELL_ROWS} }) {
367 push @{ $col->{CELL_ROWS} }, { 'data' => ' ' };
368 } elsif ((1 == scalar @{ $col->{CELL_ROWS} }) && (!defined $col->{CELL_ROWS}->[0]->{data} || ($col->{CELL_ROWS}->[0]->{data} eq ''))) {
369 $col->{CELL_ROWS}->[0]->{data} = ' ';
372 push @{ $output_columns }, $col;
373 $skip_next = $col->{colspan} ? $col->{colspan} - 1 : 0;
377 'COLUMNS' => $output_columns,
378 'outer_idx' => $outer_idx,
379 'outer_idx_odd' => $outer_idx % 2,
380 'inner_idx' => $inner_idx,
381 'BORDER_TOP' => $next_border_top,
384 push @rows, $row_data;
386 $next_border_top = 0;
390 my @export_variables = $self->{form}->flatten_variables(@{ $self->{export}->{variable_list} });
392 my $allow_pdf_export = $opts->{allow_pdf_export};
395 'TITLE' => $opts->{title},
396 'TOP_INFO_TEXT' => $self->html_format($opts->{top_info_text}),
397 'RAW_TOP_INFO_TEXT' => $opts->{raw_top_info_text},
398 'BOTTOM_INFO_TEXT' => $self->html_format($opts->{bottom_info_text}),
399 'RAW_BOTTOM_INFO_TEXT' => $opts->{raw_bottom_info_text},
400 'ALLOW_PDF_EXPORT' => $allow_pdf_export,
401 'ALLOW_CSV_EXPORT' => $opts->{allow_csv_export},
402 'SHOW_EXPORT_BUTTONS' => ($allow_pdf_export || $opts->{allow_csv_export}) && $self->{data_present},
403 'HEADER_ROWS' => $header_rows,
404 'NUM_COLUMNS' => scalar @column_headers,
406 'EXPORT_VARIABLES' => \@export_variables,
407 'EXPORT_VARIABLE_LIST' => join(' ', @{ $self->{export}->{variable_list} }),
408 'EXPORT_NEXTSUB' => $self->{export}->{nextsub},
409 'DATA_PRESENT' => $self->{data_present},
410 'CONTROLLER_DISPATCH' => $opts->{controller_class},
411 'TABLE_CLASS' => $opts->{table_class},
412 'SKIP_BUTTONS' => !!$params{action_bar},
418 sub setup_action_bar {
419 my ($self, $action_bar, $variables) = @_;
422 foreach my $type (qw(pdf csv)) {
423 next unless $variables->{"ALLOW_" . uc($type) . "_EXPORT"};
425 my $key = $variables->{CONTROLLER_DISPATCH} ? 'action' : 'report_generator_dispatch_to';
426 my $value = "report_generator_export_as_${type}";
427 $value = $variables->{CONTROLLER_DISPATCH} . "/${value}" if $variables->{CONTROLLER_DISPATCH};
429 push @actions, action => [
430 $type eq 'pdf' ? $::locale->text('PDF export') : $::locale->text('CSV export'),
431 submit => [ '#report_generator_form', { $key => $value } ],
435 if (scalar(@actions) > 1) {
438 action => [ $::locale->text('Export') ],
444 $action_bar = ($::request->layout->get('actionbar'))[0] unless blessed($action_bar);
445 $action_bar->add(@actions) if @actions;
448 sub generate_html_content {
449 my ($self, %params) = @_;
451 $params{action_bar} //= 1;
453 my $variables = $self->prepare_html_content(%params);
455 $self->setup_action_bar($params{action_bar}, $variables) if $params{action_bar};
457 my $stuff = $self->{form}->parse_html_template($self->{options}->{html_template}, $variables);
464 return $_[0] * 72 / 2.54;
467 sub generate_pdf_content {
474 my $variables = $self->prepare_html_content();
475 my $form = $self->{form};
476 my $myconfig = $self->{myconfig};
478 my $opts = $self->{options};
479 my $pdfopts = $opts->{pdf_export};
481 my (@data, @column_props, @cell_props);
483 my ($data_row, $cell_props_row);
484 my @visible_columns = $self->get_visible_columns('PDF');
485 my $num_columns = scalar @visible_columns;
486 my $num_header_rows = 1;
488 my $font_encoding = 'UTF-8';
490 foreach my $name (@visible_columns) {
491 push @column_props, { 'justify' => $self->{columns}->{$name}->{align} eq 'right' ? 'right' : 'left' };
494 if (!$self->{custom_headers}) {
496 $cell_props_row = [];
497 push @data, $data_row;
498 push @cell_props, $cell_props_row;
500 foreach my $name (@visible_columns) {
501 my $column = $self->{columns}->{$name};
503 push @{ $data_row }, $column->{text};
504 push @{ $cell_props_row }, {};
508 $num_header_rows = scalar @{ $self->{custom_headers} };
510 foreach my $custom_header_row (@{ $self->{custom_headers} }) {
512 $cell_props_row = [];
513 push @data, $data_row;
514 push @cell_props, $cell_props_row;
516 foreach my $custom_header_col (@{ $custom_header_row }) {
517 push @{ $data_row }, $custom_header_col->{text};
519 my $num_output = ($custom_header_col->{colspan} * 1 > 1) ? $custom_header_col->{colspan} : 1;
520 if ($num_output > 1) {
521 push @{ $data_row }, ('') x ($num_output - 1);
522 push @{ $cell_props_row }, { 'colspan' => $num_output };
523 push @{ $cell_props_row }, ({ }) x ($num_output - 1);
526 push @{ $cell_props_row }, {};
532 foreach my $row_set (@{ $self->{data} }) {
533 if ('HASH' eq ref $row_set) {
534 if ($row_set->{type} eq 'colspan_data') {
535 push @data, [ $row_set->{data} ];
537 $cell_props_row = [];
538 push @cell_props, $cell_props_row;
540 foreach (0 .. $num_columns - 1) {
541 push @{ $cell_props_row }, { 'background_color' => '#666666',
542 # BUG PDF:Table -> 0.9.12:
543 # font_color is used in next row, so dont set font_color
544 # 'font_color' => '#ffffff',
545 'colspan' => $_ == 0 ? -1 : undef, };
551 foreach my $row (@{ $row_set }) {
553 $cell_props_row = [];
555 push @data, $data_row;
556 push @cell_props, $cell_props_row;
559 foreach my $col_name (@visible_columns) {
560 my $col = $row->{$col_name};
561 push @{ $data_row }, join("\n", @{ $col->{data} || [] });
563 $column_props[$col_idx]->{justify} = 'right' if ($col->{align} eq 'right');
565 my $cell_props = { };
566 push @{ $cell_props_row }, $cell_props;
568 if ($col->{colspan} && $col->{colspan} > 1) {
569 $cell_props->{colspan} = $col->{colspan};
577 foreach my $i (0 .. scalar(@data) - 1) {
578 my $aref = $data[$i];
579 my $num_columns_here = scalar @{ $aref };
581 if ($num_columns_here < $num_columns) {
582 push @{ $aref }, ('') x ($num_columns - $num_columns_here);
583 } elsif ($num_columns_here > $num_columns) {
584 splice @{ $aref }, $num_columns;
589 'a3' => [ 842, 1190 ],
590 'a4' => [ 595, 842 ],
591 'a5' => [ 420, 595 ],
592 'letter' => [ 612, 792 ],
593 'legal' => [ 612, 1008 ],
596 my %supported_fonts = map { $_ => 1 } qw(courier georgia helvetica times verdana);
598 my $paper_size = defined $pdfopts->{paper_size} && defined $papersizes->{lc $pdfopts->{paper_size}} ? lc $pdfopts->{paper_size} : 'a4';
599 my ($paper_width, $paper_height);
601 if (lc $pdfopts->{orientation} eq 'landscape') {
602 ($paper_width, $paper_height) = @{$papersizes->{$paper_size}}[1, 0];
604 ($paper_width, $paper_height) = @{$papersizes->{$paper_size}}[0, 1];
607 my $margin_top = _cm2bp($pdfopts->{margin_top} || 1.5);
608 my $margin_bottom = _cm2bp($pdfopts->{margin_bottom} || 1.5);
609 my $margin_left = _cm2bp($pdfopts->{margin_left} || 1.5);
610 my $margin_right = _cm2bp($pdfopts->{margin_right} || 1.5);
612 my $table = PDF::Table->new();
613 my $pdf = PDF::API2->new();
614 my $page = $pdf->page();
616 $pdf->mediabox($paper_width, $paper_height);
618 my $font = $pdf->corefont(defined $pdfopts->{font_name} && $supported_fonts{lc $pdfopts->{font_name}} ? ucfirst $pdfopts->{font_name} : 'Verdana',
619 '-encoding' => $font_encoding);
620 my $font_size = $pdfopts->{font_size} || 7;
621 my $title_font_size = $font_size + 1;
623 my $font_height = $font_size + 2 * $padding;
624 my $title_font_height = $font_size + 2 * $padding;
626 my $header_height = $opts->{title} ? 2 * $title_font_height : undef;
627 my $footer_height = $pdfopts->{number} ? 2 * $font_height : undef;
629 my $top_text_height = 0;
631 if ($self->{options}->{top_info_text}) {
632 my $top_text = $self->{options}->{top_info_text};
633 $top_text =~ s/\r//g;
634 $top_text =~ s/\n+$//;
636 my @lines = split m/\n/, $top_text;
637 $top_text_height = $font_height * scalar @lines;
639 foreach my $line_no (0 .. scalar(@lines) - 1) {
640 my $y_pos = $paper_height - $margin_top - $header_height - $line_no * $font_height;
641 my $text_obj = $page->text();
643 $text_obj->font($font, $font_size);
644 $text_obj->translate($margin_left, $y_pos);
645 $text_obj->text($lines[$line_no]);
653 'w' => $paper_width - $margin_left - $margin_right,
654 'start_y' => $paper_height - $margin_top - $header_height - $top_text_height,
655 'next_y' => $paper_height - $margin_top - $header_height,
656 'start_h' => $paper_height - $margin_top - $margin_bottom - $header_height - $footer_height - $top_text_height,
657 'next_h' => $paper_height - $margin_top - $margin_bottom - $header_height - $footer_height,
659 'background_color_odd' => '#ffffff',
660 'background_color_even' => '#eeeeee',
662 'font_size' => $font_size,
663 'font_color' => '#000000',
664 'num_header_rows' => $num_header_rows,
666 'bg_color' => '#ffffff',
668 'font_color' => '#000000',
670 'column_props' => \@column_props,
671 'cell_props' => \@cell_props,
672 'max_word_length' => 60,
676 foreach my $page_num (1..$pdf->pages()) {
677 my $curpage = $pdf->openpage($page_num);
679 if ($pdfopts->{number}) {
680 my $label = $main::locale->text("Page #1/#2", $page_num, $pdf->pages());
681 my $text_obj = $curpage->text();
683 $text_obj->font($font, $font_size);
684 $text_obj->translate(($paper_width - $margin_left - $margin_right) / 2 + $margin_left - $text_obj->advancewidth($label) / 2, $margin_bottom);
685 $text_obj->text($label);
688 if ($opts->{title}) {
689 my $title = $opts->{title};
690 my $text_obj = $curpage->text();
692 $text_obj->font($font, $title_font_size);
693 $text_obj->translate(($paper_width - $margin_left - $margin_right) / 2 + $margin_left - $text_obj->advancewidth($title) / 2,
694 $paper_height - $margin_top);
695 $text_obj->text($title, '-underline' => 1);
699 my $content = $pdf->stringify();
701 $main::lxdebug->message(LXDebug->DEBUG2(),"addattachments ?? =".$form->{report_generator_addattachments}." GL=".$form->{GL});
702 if ($form->{report_generator_addattachments} && $form->{GL}) {
703 $content = $self->append_gl_pdf_attachments($form,$content);
707 if ($pdfopts->{print} && $pdfopts->{printer_id}) {
708 $form->{printer_id} = $pdfopts->{printer_id};
709 $form->get_printer_code($myconfig);
710 $printer_command = $form->{printer_command};
713 if ($printer_command) {
714 $self->_print_content('printer_command' => $printer_command,
715 'content' => $content,
716 'copies' => $pdfopts->{copies});
717 $form->{report_generator_printed} = 1;
720 my $filename = $self->get_attachment_basename();
722 print qq|content-type: application/pdf\n|;
723 print qq|content-disposition: attachment; filename=${filename}.pdf\n\n|;
725 $::locale->with_raw_io(\*STDOUT, sub {
731 sub verify_paper_size {
733 my $requested_paper_size = lc shift;
734 my $default_paper_size = shift;
736 my %allowed_paper_sizes = map { $_ => 1 } qw(a3 a4 a5 letter legal);
738 return $allowed_paper_sizes{$requested_paper_size} ? $requested_paper_size : $default_paper_size;
745 foreach my $i (1 .. max $params{copies}, 1) {
746 my $printer = IO::File->new("| $params{printer_command}");
747 $main::form->error($main::locale->text('Could not spawn the printer command.')) if (!$printer);
748 $printer->print($params{content});
753 sub _handle_quoting_and_encoding {
754 my ($self, $text, $do_unquote, $encoding) = @_;
756 $text = $main::locale->unquote_special_chars('HTML', $text) if $do_unquote;
757 $text = Encode::encode($encoding || 'UTF-8', $text);
762 sub generate_csv_content {
764 my $stdout = ($::dispatcher->get_standard_filehandles)[1];
766 # Text::CSV_XS seems to downgrade to bytes already (see
767 # SL/FCGIFixes.pm). Therefore don't let FCGI do that again.
768 $::locale->with_raw_io($stdout, sub { $self->_generate_csv_content($stdout) });
771 sub _generate_csv_content {
772 my ($self, $stdout) = @_;
774 my %valid_sep_chars = (';' => ';', ',' => ',', ':' => ':', 'TAB' => "\t");
775 my %valid_escape_chars = ('"' => 1, "'" => 1);
776 my %valid_quote_chars = ('"' => 1, "'" => 1);
778 my $opts = $self->{options}->{csv_export};
779 my $eol = $opts->{eol_style} eq 'DOS' ? "\r\n" : "\n";
780 my $sep_char = $valid_sep_chars{$opts->{sep_char}} ? $valid_sep_chars{$opts->{sep_char}} : ';';
781 my $escape_char = $valid_escape_chars{$opts->{escape_char}} ? $opts->{escape_char} : '"';
782 my $quote_char = $valid_quote_chars{$opts->{quote_char}} ? $opts->{quote_char} : '"';
784 $escape_char = $quote_char if ($opts->{escape_char} eq 'QUOTE_CHAR');
786 my $csv = Text::CSV_XS->new({ 'binary' => 1,
787 'sep_char' => $sep_char,
788 'escape_char' => $escape_char,
789 'quote_char' => $quote_char,
792 my @visible_columns = $self->get_visible_columns('CSV');
794 if ($opts->{headers}) {
795 if (!$self->{custom_headers}) {
796 $csv->print($stdout, [ map { $self->_handle_quoting_and_encoding($self->{columns}->{$_}->{text}, 1, $opts->{encoding}) } @visible_columns ]);
799 foreach my $row (@{ $self->{custom_headers} }) {
802 foreach my $col (@{ $row }) {
803 my $num_output = ($col->{colspan} && ($col->{colspan} > 1)) ? $col->{colspan} : 1;
804 push @{ $fields }, ($self->_handle_quoting_and_encoding($col->{text}, 1, $opts->{encoding})) x $num_output;
807 $csv->print($stdout, $fields);
812 foreach my $row_set (@{ $self->{data} }) {
813 next if ('ARRAY' ne ref $row_set);
814 foreach my $row (@{ $row_set }) {
817 foreach my $col (@visible_columns) {
823 my $num_output = ($row->{$col}{colspan} && ($row->{$col}->{colspan} > 1)) ? $row->{$col}->{colspan} : 1;
824 $skip_next = $num_output - 1;
826 push @data, join($eol, map { s/\r?\n/$eol/g; $self->_handle_quoting_and_encoding($_, 0, $opts->{encoding}) } @{ $row->{$col}->{data} });
827 push @data, ('') x $skip_next if ($skip_next);
830 $csv->print($stdout, \@data);
835 sub check_for_pdf_api {
836 return eval { require PDF::API2; 1; } ? 1 : 0;
845 SL::ReportGenerator.pm: the kivitendo way of getting data in shape
849 my $report = SL::ReportGenerator->new(\%myconfig, $form);
850 $report->set_options(%options); # optional
851 $report->set_columns(%column_defs);
852 $report->set_sort_indicator($column, $direction); # optional
853 $report->add_data($row1, $row2, @more_rows);
854 $report->generate_with_headers();
856 This creates a report object, sets a few columns, adds some data and generates a standard report.
857 Sorting of columns will be alphabetic, and options will be set to their defaults.
858 The report will be printed including table headers, html headers and http headers.
862 Imagine the following scenario:
863 There's a simple form, which loads some data from the database, and needs to print it out. You write a template for it.
864 Then there may be more than one line. You add a loop in the template.
865 Then there are some options made by the user, such as hidden columns. You add more to the template.
866 Then it lacks usability. You want it to be able to sort the data. You add code for that.
867 Then there are too many results, you need pagination, you want to print or export that data..... and so on.
869 The ReportGenerator class was designed because this exact scenario happened about half a dozen times in kivitendo.
870 It's purpose is to manage all those formating, culling, sorting, and templating.
871 Which makes it almost as complicated to use as doing the work by yourself.
877 =item new \%myconfig,$form,%options
879 Creates a new ReportGenerator object, sets all given options, and returns it.
881 =item set_columns %columns
883 Sets the columns available to this report.
885 =item set_column_order @columns
887 Sets the order of columns. Any columns not present here are appended in alphabetic order.
889 =item set_sort_indicator $column,$direction
891 Sets sorting of the table by specifying a column and a direction, where the direction will be evaluated to ascending if true.
892 Note that this is only for displaying. The data has to have already been sorted when it was added.
894 =item add_data \@data
896 =item add_data \%data
898 Adds data to the report. A given hash_ref is interpreted as a single line of
899 data, every array_ref as a collection of lines. Every line will be expected to
900 be in a key => value format. Note that the rows have to already have been
903 The ReportGenerator is only able to display pre-sorted data and to indicate by
904 which column and in which direction the data has been sorted via visual clues
905 in the column headers. It also provides links to invert the sort direction.
909 Adds a separator line to the report.
911 =item add_control \%data
913 Adds a control element to the data. Control elements are an experimental feature to add functionality to a report the regular data cannot.
914 Every control element needs to set IS_CONTROL_DATA, in order to be recognized by the template.
915 Currently the only control element is a colspan element, which can be used as a mini header further down the report.
919 Deletes all data added to the report, but keeps options set.
921 =item set_options %options
923 Sets options. For an incomplete list of options, see section configuration.
925 =item set_options_from_form
927 Tries to import options from the $form object given at creation
929 =item set_export_options $next_sub,@variable_list
931 Sets next_sub and additional variables needed for export.
933 =item get_attachment_basename
935 Returns the set attachment_basename option, or 'report' if nothing was set. See configuration for the option.
937 =item generate_with_headers
939 Parses the report, adds headers and prints it out. Headers depend on the option 'output_format',
940 for example 'HTML' will add proper table headers, html headers and http headers. See configuration for this option.
942 =item get_visible_columns $format
944 Returns a list of columns that will be visible in the report after considering all options or match the given format.
946 =item html_format $value
948 Escapes HTML characters in $value and substitutes newlines with '<br>'. Returns the escaped $value.
950 =item prepare_html_content $column,$name,@column_headers
952 Parses the data, and sets internal data needed for certain output format. Must be called once before the template is invoked.
953 Should not be called externally, since all render and generate functions invoke it anyway.
955 =item generate_html_content
957 The html generation function. Is invoked by generate_with_headers.
959 =item generate_pdf_content
961 The PDF generation function. It is invoked by generate_with_headers and renders the PDF with the PDF::API2 library.
963 =item generate_csv_content
965 The CSV generation function. Uses XS_CSV to parse the information into csv.
971 These are known options and their defaults. Options for pdf export and csv export need to be set as a hashref inside the export option.
973 =head2 General Options
977 =item std_column_visibility
979 Standard column visibility. Used if no visibility is set. Use this to save the trouble of enabling every column. Default is no.
983 Output format. Used by generate_with_headers to determine the format. Supported options are HTML, CSV, and PDF. Default is HTML.
985 =item allow_pdf_export
987 Used to determine if a button for PDF export should be displayed. Default is yes.
989 =item allow_csv_export
991 Used to determine if a button for CSV export should be displayed. Default is yes.
995 The template to be used for HTML reports. Default is 'report_generator/html_report'.
997 =item controller_class
999 If this is used from a C<SL::Controller::Base> based controller class, pass the
1000 class name here and make sure C<SL::Controller::Helper::ReportGenerator> is
1001 used in the controller. That way the exports stay functional.
1011 Paper size. Default is a4. Supported paper sizes are a3, a4, a5, letter and legal.
1013 =item orientation (landscape)
1015 Landscape or portrait. Default is landscape.
1019 Default is Verdana. Supported font names are Courier, Georgia, Helvetica, Times and Verdana. This option only affects the rendering with PDF::API2.
1023 Default is 7. This option only affects the rendering with PDF::API2.
1033 The paper margins in cm. They all default to 1.5.
1037 Set to a true value if the pages should be numbered. Default is 1.
1041 If set then the resulting PDF will be output to a printer. If not it will be downloaded by the user. Default is no.
1059 Character to enclose entries. Default is double quote (").
1063 Character to separate entries. Default is semicolon (;).
1067 Character to escape the quote_char. Default is double quote (").
1071 End of line style. Default is Unix.
1075 Include headers? Default is yes.
1079 Character encoding. Default is UTF-8.
1087 =head1 MODULE AUTHORS
1089 Moritz Bunkus E<lt>mbunkus@linet-services.deE<gt>
1091 L<http://linet-services.de>