X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FForm.pm;h=529426ec2cd9277b322bb01771b2172cc39970f2;hb=3bdd3642c42ea17756c46e8a5dabd1cf375c29a3;hp=8e109cea7756218dccf5cfdc49ff0191c202744d;hpb=b8da8785c1b6d313518202acb307adfb50ebdb65;p=kivitendo-erp.git diff --git a/SL/Form.pm b/SL/Form.pm index 8e109cea7..529426ec2 100644 --- a/SL/Form.pm +++ b/SL/Form.pm @@ -37,163 +37,50 @@ package Form; -use strict; - use Data::Dumper; use CGI; -use CGI::Ajax; use Cwd; +use Encode; +use File::Copy; use IO::File; use SL::Auth; use SL::Auth::DB; use SL::Auth::LDAP; use SL::AM; use SL::Common; +use SL::CVar; +use SL::DB; +use SL::DBConnect; use SL::DBUtils; +use SL::DO; +use SL::IC; +use SL::IS; use SL::Mailer; use SL::Menu; +use SL::MoreCommon qw(uri_encode uri_decode); +use SL::OE; +use SL::Request; use SL::Template; use SL::User; +use SL::X; use Template; +use URI; use List::Util qw(first max min sum); +use List::MoreUtils qw(all any apply); + +use strict; my $standard_dbh; END { - if ($standard_dbh) { - $standard_dbh->disconnect(); - undef $standard_dbh; - } -} - -sub _store_value { - $main::lxdebug->enter_sub(2); - - my $self = shift; - my $key = shift; - my $value = shift; - - my $curr = $self; - - while ($key =~ /\[\+?\]\.|\./) { - substr($key, 0, $+[0]) = ''; - - if ($& eq '.') { - $curr->{$`} ||= { }; - $curr = $curr->{$`}; - - } else { - $curr->{$`} ||= [ ]; - if (!scalar @{ $curr->{$`} } || $& eq '[+].') { - push @{ $curr->{$`} }, { }; - } - - $curr = $curr->{$`}->[-1]; - } - } - - $curr->{$key} = $value; - - $main::lxdebug->leave_sub(2); - - return \$curr->{$key}; + disconnect_standard_dbh(); } -sub _input_to_hash { - $main::lxdebug->enter_sub(2); - - my $self = shift; - my $input = shift; - - my @pairs = split(/&/, $input); - - foreach (@pairs) { - my ($key, $value) = split(/=/, $_, 2); - $self->_store_value($self->unescape($key), $self->unescape($value)); - } - - $main::lxdebug->leave_sub(2); -} - -sub _request_to_hash { - $main::lxdebug->enter_sub(2); - - my $self = shift; - my $input = shift; - - if (!$ENV{'CONTENT_TYPE'} - || ($ENV{'CONTENT_TYPE'} !~ /multipart\/form-data\s*;\s*boundary\s*=\s*(.+)$/)) { - - $self->_input_to_hash($input); - - $main::lxdebug->leave_sub(2); - return; - } - - my ($name, $filename, $headers_done, $content_type, $boundary_found, $need_cr, $previous); - - my $boundary = '--' . $1; - - foreach my $line (split m/\n/, $input) { - last if (($line eq "${boundary}--") || ($line eq "${boundary}--\r")); - - if (($line eq $boundary) || ($line eq "$boundary\r")) { - ${ $previous } =~ s|\r?\n$|| if $previous; - - undef $previous; - undef $filename; - - $headers_done = 0; - $content_type = "text/plain"; - $boundary_found = 1; - $need_cr = 0; - - next; - } - - next unless $boundary_found; - - if (!$headers_done) { - $line =~ s/[\r\n]*$//; - - if (!$line) { - $headers_done = 1; - next; - } - - if ($line =~ m|^content-disposition\s*:.*?form-data\s*;|i) { - if ($line =~ m|filename\s*=\s*"(.*?)"|i) { - $filename = $1; - substr $line, $-[0], $+[0] - $-[0], ""; - } - - if ($line =~ m|name\s*=\s*"(.*?)"|i) { - $name = $1; - substr $line, $-[0], $+[0] - $-[0], ""; - } - - $previous = $self->_store_value($name, ''); - $self->{FILENAME} = $filename if ($filename); - - next; - } - - if ($line =~ m|^content-type\s*:\s*(.*?)$|i) { - $content_type = $1; - } - - next; - } - - next unless $previous; - - ${ $previous } .= "${line}\n"; - } - - ${ $previous } =~ s|\r?\n$|| if $previous; - - $main::lxdebug->leave_sub(2); +sub disconnect_standard_dbh { + return unless $standard_dbh; + $standard_dbh->disconnect(); + undef $standard_dbh; } sub new { @@ -203,35 +90,29 @@ sub new { my $self = {}; + no warnings 'once'; if ($LXDebug::watch_form) { require SL::Watchdog; tie %{ $self }, 'SL::Watchdog'; } - read(STDIN, $_, $ENV{CONTENT_LENGTH}); - - if ($ENV{QUERY_STRING}) { - $_ = $ENV{QUERY_STRING}; - } - - if ($ARGV[0]) { - $_ = $ARGV[0]; - } - bless $self, $type; - $self->_request_to_hash($_); - - $self->{action} = lc $self->{action}; - $self->{action} =~ s/( |-|,|\#)/_/g; - - $self->{version} = "2.6.0 beta 1"; + open VERSION_FILE, "VERSION"; # New but flexible code reads version from VERSION-file + $self->{version} = ; + close VERSION_FILE; + $self->{version} =~ s/[^0-9A-Za-z\.\_\-]//g; # only allow numbers, letters, points, underscores and dashes. Prevents injecting of malicious code. $main::lxdebug->leave_sub(); return $self; } +sub read_cgi_input { + my ($self) = @_; + SL::Request::read_cgi_input($self); +} + sub _flatten_variables_rec { $main::lxdebug->enter_sub(2); @@ -331,66 +212,62 @@ sub dumper { } sub escape { - $main::lxdebug->enter_sub(2); - my ($self, $str) = @_; - $str =~ s/([^a-zA-Z0-9_.-])/sprintf("%%%02x", ord($1))/ge; - - $main::lxdebug->leave_sub(2); - - return $str; + return uri_encode($str); } sub unescape { - $main::lxdebug->enter_sub(2); - my ($self, $str) = @_; - $str =~ tr/+/ /; - $str =~ s/\\$//; - - $str =~ s/%([0-9a-fA-Z]{2})/pack("c",hex($1))/eg; - - $main::lxdebug->leave_sub(2); - - return $str; + return uri_decode($str); } sub quote { + $main::lxdebug->enter_sub(); my ($self, $str) = @_; if ($str && !ref($str)) { $str =~ s/\"/"/g; } - $str; + $main::lxdebug->leave_sub(); + return $str; } sub unquote { + $main::lxdebug->enter_sub(); my ($self, $str) = @_; if ($str && !ref($str)) { $str =~ s/"/\"/g; } - $str; + $main::lxdebug->leave_sub(); + return $str; } sub hide_form { + $main::lxdebug->enter_sub(); my $self = shift; if (@_) { - map({ print($main::cgi->hidden("-name" => $_, "-default" => $self->{$_}) . "\n"); } @_); + map({ print($::request->{cgi}->hidden("-name" => $_, "-default" => $self->{$_}) . "\n"); } @_); } else { for (sort keys %$self) { next if (($_ eq "header") || (ref($self->{$_}) ne "")); - print($main::cgi->hidden("-name" => $_, "-default" => $self->{$_}) . "\n"); + print($::request->{cgi}->hidden("-name" => $_, "-default" => $self->{$_}) . "\n"); } } + $main::lxdebug->leave_sub(); +} +sub throw_on_error { + my ($self, $code) = @_; + local $self->{__ERROR_HANDLER} = sub { die SL::X::FormError->new($_[0]) }; + $code->(); } sub error { @@ -399,13 +276,17 @@ sub error { $main::lxdebug->show_backtrace(); my ($self, $msg) = @_; - if ($ENV{HTTP_USER_AGENT}) { + + if ($self->{__ERROR_HANDLER}) { + $self->{__ERROR_HANDLER}->($msg); + + } elsif ($ENV{HTTP_USER_AGENT}) { $msg =~ s/\n/
/g; $self->show_generic_error($msg); } else { - - die "Error: $msg\n"; + print STDERR "Error: $msg\n"; + ::end_of_request(); } $main::lxdebug->leave_sub(); @@ -421,13 +302,22 @@ sub info { if (!$self->{header}) { $self->header; - print qq| - |; + print qq||; } print qq| - -

$msg +

$msg

+ + + + |; } else { @@ -484,144 +374,165 @@ sub isblank { $main::lxdebug->leave_sub(); } -sub create_http_response { - $main::lxdebug->enter_sub(); - - my $self = shift; - my %params = @_; - - my $cgi = $main::cgi; - $cgi ||= CGI->new(''); +sub _get_request_uri { + my $self = shift; - my $base_path; + return URI->new($ENV{HTTP_REFERER})->canonical() if $ENV{HTTP_X_FORWARDED_FOR}; - if ($ENV{HTTP_X_FORWARDED_FOR}) { - $base_path = $ENV{HTTP_REFERER}; - $base_path =~ s|^.*?://.*?/|/|; - } else { - $base_path = $ENV{REQUEST_URI}; - } - $base_path =~ s|[^/]+$||; - $base_path =~ s|/$||; + my $scheme = $ENV{HTTPS} && (lc $ENV{HTTPS} eq 'on') ? 'https' : 'http'; + my $port = $ENV{SERVER_PORT} || ''; + $port = undef if (($scheme eq 'http' ) && ($port == 80)) + || (($scheme eq 'https') && ($port == 443)); - my $session_cookie; - if (defined $main::auth) { - my $session_cookie_value = $main::auth->get_session_id(); - $session_cookie_value ||= 'NO_SESSION'; + my $uri = URI->new("${scheme}://"); + $uri->scheme($scheme); + $uri->port($port); + $uri->host($ENV{HTTP_HOST} || $ENV{SERVER_ADDR}); + $uri->path_query($ENV{REQUEST_URI}); + $uri->query(''); - $session_cookie = $cgi->cookie('-name' => $main::auth->get_session_cookie_name(), - '-value' => $session_cookie_value, - '-path' => $base_path); - } + return $uri; +} - my %cgi_params = ('-type' => $params{content_type}); - $cgi_params{'-charset'} = $params{charset} if ($params{charset}); +sub _add_to_request_uri { + my $self = shift; - my $output = $cgi->header('-cookie' => $session_cookie, - %cgi_params); + my $relative_new_path = shift; + my $request_uri = shift || $self->_get_request_uri; + my $relative_new_uri = URI->new($relative_new_path); + my @request_segments = $request_uri->path_segments; - $main::lxdebug->leave_sub(); + my $new_uri = $request_uri->clone; + $new_uri->path_segments(@request_segments[0..scalar(@request_segments) - 2], $relative_new_uri->path_segments); - return $output; + return $new_uri; } - -sub header { +sub create_http_response { $main::lxdebug->enter_sub(); - my ($self, $extra_code) = @_; - - if ($self->{header}) { - $main::lxdebug->leave_sub(); - return; - } - - my ($stylesheet, $favicon, $pagelayout); + my $self = shift; + my %params = @_; - if ($ENV{HTTP_USER_AGENT}) { - my $doctype; + my $cgi = $::request->{cgi}; - if ($ENV{'HTTP_USER_AGENT'} =~ m/MSIE\s+\d/) { - # Only set the DOCTYPE for Internet Explorer. Other browsers have problems displaying the menu otherwise. - $doctype = qq|\n|; + my $session_cookie; + if (defined $main::auth) { + my $uri = $self->_get_request_uri; + my @segments = $uri->path_segments; + pop @segments; + $uri->path_segments(@segments); + + my $session_cookie_value = $main::auth->get_session_id(); + + if ($session_cookie_value) { + $session_cookie = $cgi->cookie('-name' => $main::auth->get_session_cookie_name(), + '-value' => $session_cookie_value, + '-path' => $uri->path, + '-secure' => $ENV{HTTPS}); } + } - my $stylesheets = "$self->{stylesheet} $self->{stylesheets}"; - - $stylesheets =~ s|^\s*||; - $stylesheets =~ s|\s*$||; - foreach my $file (split m/\s+/, $stylesheets) { - $file =~ s|.*/||; - next if (! -f "css/$file"); - - $stylesheet .= qq|\n|; - } + my %cgi_params = ('-type' => $params{content_type}); + $cgi_params{'-charset'} = $params{charset} if ($params{charset}); + $cgi_params{'-cookie'} = $session_cookie if ($session_cookie); - $self->{favicon} = "favicon.ico" unless $self->{favicon}; + map { $cgi_params{'-' . $_} = $params{$_} if exists $params{$_} } qw(content_disposition content_length); - if ($self->{favicon} && (-f "$self->{favicon}")) { - $favicon = - qq| - |; - } + my $output = $cgi->header(%cgi_params); - my $db_charset = $main::dbcharset ? $main::dbcharset : Common::DEFAULT_CHARSET; + $main::lxdebug->leave_sub(); - if ($self->{landscape}) { - $pagelayout = qq||; - } + return $output; +} - my $fokus = qq| document.$self->{fokus}.focus();| if ($self->{"fokus"}); +sub use_stylesheet { + my $self = shift; - #Set Calendar - my $jsscript = ""; - if ($self->{jsscript} == 1) { + $self->{stylesheet} = [ $self->{stylesheet} ] unless ref $self->{stylesheet} eq 'ARRAY'; + $self->{stylesheet} = [ grep { -f } + map { m:^css/: ? $_ : "css/$_" } + grep { $_ } + (@{ $self->{stylesheet} }, @_) + ]; - $jsscript = qq| - - - - - - $self->{javascript} - |; - } + return @{ $self->{stylesheet} }; +} - $self->{titlebar} = - ($self->{title}) - ? "$self->{title} - $self->{titlebar}" - : $self->{titlebar}; - my $ajax = ""; - foreach my $item (@ { $self->{AJAX} }) { - $ajax .= $item->show_javascript(); - } +sub header { + $::lxdebug->enter_sub; + + # extra code is currently only used by menuv3 and menuv4 to set their css. + # it is strongly deprecated, and will be changed in a future version. + my ($self, %params) = @_; + my $db_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET; + my @header; + + $::lxdebug->leave_sub and return if !$ENV{HTTP_USER_AGENT} || $self->{header}++; + + $self->{favicon} ||= "favicon.ico"; + $self->{titlebar} = "$self->{title} - $self->{titlebar}" if $self->{title}; + + # build includes + if ($self->{refresh_url} || $self->{refresh_time}) { + my $refresh_time = $self->{refresh_time} || 3; + my $refresh_url = $self->{refresh_url} || $ENV{REFERER}; + push @header, ""; + } + + push @header, map { qq|| } $self->use_stylesheet; + + push @header, "" if $self->{landscape}; + push @header, "" if -f $self->{favicon}; + push @header, '', + '', + '', + '', + '', + '', + '', + '', + '', + ''; + push @header, $self->{javascript} if $self->{javascript}; + push @header, map { $_->show_javascript } @{ $self->{AJAX} || [] }; + push @header, "" if $self->{fokus}; + push @header, sprintf "", + join ' - ', grep $_, $self->{title}, $self->{login}, $::myconfig{dbname}, $self->{version} if $self->{title}; + + # if there is a title, we put some JavaScript in to the page, wich writes a + # meaningful title-tag for our frameset. + my $title_hack = ''; + if ($self->{title}) { + $title_hack = qq| + |; + } + + my %doctypes = ( + strict => qq||, + transitional => qq||, + frameset => qq||, + ); - print $self->create_http_response('content_type' => 'text/html', - 'charset' => $db_charset,); - print qq|${doctype} - - + # output + print $self->create_http_response(content_type => 'text/html', charset => $db_charset); + print $doctypes{$params{doctype} || 'transitional'}, $/; + print < + + $self->{titlebar} - $stylesheet - $pagelayout - $favicon - $jsscript - $ajax - - - - - - - +EOT + print " $_\n" for @header; + print < + + + $params{extra_code} + $title_hack + - $extra_code - +EOT -|; - } - $self->{header} = 1; - - $main::lxdebug->leave_sub(); + $::lxdebug->leave_sub; } sub ajax_response_header { @@ -647,47 +556,58 @@ sub ajax_response_header { my ($self) = @_; - my $db_charset = $main::dbcharset ? $main::dbcharset : Common::DEFAULT_CHARSET; - my $cgi = $main::cgi || CGI->new(''); - my $output = $cgi->header('-charset' => $db_charset); + my $db_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET; + my $output = $::request->{cgi}->header('-charset' => $db_charset); $main::lxdebug->leave_sub(); return $output; } +sub redirect_header { + my $self = shift; + my $new_url = shift; + + my $base_uri = $self->_get_request_uri; + my $new_uri = URI->new_abs($new_url, $base_uri); + + die "Headers already sent" if $self->{header}; + $self->{header} = 1; + + return $::request->{cgi}->redirect($new_uri); +} + +sub set_standard_title { + $::lxdebug->enter_sub; + my $self = shift; + + $self->{titlebar} = "Lx-Office " . $::locale->text('Version') . " $self->{version}"; + $self->{titlebar} .= "- $::myconfig{name}" if $::myconfig{name}; + $self->{titlebar} .= "- $::myconfig{dbname}" if $::myconfig{name}; + + $::lxdebug->leave_sub; +} + sub _prepare_html_template { $main::lxdebug->enter_sub(); my ($self, $file, $additional_params) = @_; my $language; - if (!defined(%main::myconfig) || !defined($main::myconfig{"countrycode"})) { - $language = $main::language; + if (!%::myconfig || !$::myconfig{"countrycode"}) { + $language = $::lx_office_conf{system}->{language}; } else { $language = $main::myconfig{"countrycode"}; } $language = "de" unless ($language); - if (-f "templates/webpages/${file}_${language}.html") { - if ((-f ".developer") && - (-f "templates/webpages/${file}_master.html") && - ((stat("templates/webpages/${file}_master.html"))[9] > - (stat("templates/webpages/${file}_${language}.html"))[9])) { - my $info = "Developer information: templates/webpages/${file}_master.html is newer than the localized version.\n" . - "Please re-run 'locales.pl' in 'locale/${language}'."; - print(qq|
$info
|); - die($info); - } - - $file = "templates/webpages/${file}_${language}.html"; - } elsif (-f "templates/webpages/${file}.html") { + if (-f "templates/webpages/${file}.html") { $file = "templates/webpages/${file}.html"; + } else { - my $info = "Web page template '${file}' not found.\n" . - "Please re-run 'locales.pl' in 'locale/${language}'."; - print(qq|
$info
|); - die($info); + my $info = "Web page template '${file}' not found.\n"; + print qq|
$info
|; + ::end_of_request(); } if ($self->{"DEBUG"}) { @@ -700,22 +620,29 @@ sub _prepare_html_template { } if (%main::myconfig) { - map({ $additional_params->{"myconfig_${_}"} = $main::myconfig{$_}; } keys(%main::myconfig)); - my $jsc_dateformat = $main::myconfig{"dateformat"}; - $jsc_dateformat =~ s/d+/\%d/gi; - $jsc_dateformat =~ s/m+/\%m/gi; - $jsc_dateformat =~ s/y+/\%Y/gi; - $additional_params->{"myconfig_jsc_dateformat"} = $jsc_dateformat; - } - - $additional_params->{"conf_dbcharset"} = $main::dbcharset; - $additional_params->{"conf_webdav"} = $main::webdav; - $additional_params->{"conf_lizenzen"} = $main::lizenzen; - $additional_params->{"conf_latex_templates"} = $main::latex; - $additional_params->{"conf_opendocument_templates"} = $main::opendocument_templates; - - if (%main::debug_options) { - map { $additional_params->{'DEBUG_' . uc($_)} = $main::debug_options{$_} } keys %main::debug_options; + $::myconfig{jsc_dateformat} = apply { + s/d+/\%d/gi; + s/m+/\%m/gi; + s/y+/\%Y/gi; + } $::myconfig{"dateformat"}; + $additional_params->{"myconfig"} ||= \%::myconfig; + map { $additional_params->{"myconfig_${_}"} = $main::myconfig{$_}; } keys %::myconfig; + } + + $additional_params->{"conf_dbcharset"} = $::lx_office_conf{system}->{dbcharset}; + $additional_params->{"conf_webdav"} = $::lx_office_conf{features}->{webdav}; + $additional_params->{"conf_latex_templates"} = $::lx_office_conf{print_templates}->{latex}; + $additional_params->{"conf_opendocument_templates"} = $::lx_office_conf{print_templates}->{opendocument}; + $additional_params->{"conf_vertreter"} = $::lx_office_conf{features}->{vertreter}; + $additional_params->{"conf_show_best_before"} = $::lx_office_conf{features}->{show_best_before}; + $additional_params->{"conf_parts_image_css"} = $::lx_office_conf{features}->{parts_image_css}; + $additional_params->{"conf_parts_listing_images"} = $::lx_office_conf{features}->{parts_listing_images}; + $additional_params->{"conf_parts_show_image"} = $::lx_office_conf{features}->{parts_show_image}; + $additional_params->{"conf_payments_changeable"} = $::lx_office_conf{features}->{payments_changeable}; + $additional_params->{"INSTANCE_CONF"} = $::instance_conf; + + if (my $debug_options = $::lx_office_conf{debug}{options}) { + map { $additional_params->{'DEBUG_' . uc($_)} = $debug_options->{$_} } keys %$debug_options; } if ($main::auth && $main::auth->{RIGHTS} && $main::auth->{RIGHTS}->{$self->{login}}) { @@ -736,41 +663,40 @@ sub parse_html_template { $additional_params ||= { }; - $file = $self->_prepare_html_template($file, $additional_params); - - my $template = Template->new({ 'INTERPOLATE' => 0, - 'EVAL_PERL' => 0, - 'ABSOLUTE' => 1, - 'CACHE_SIZE' => 0, - 'PLUGIN_BASE' => 'SL::Template::Plugin', - 'INCLUDE_PATH' => '.:templates/webpages', - }) || die; + my $real_file = $self->_prepare_html_template($file, $additional_params); + my $template = $self->template || $self->init_template; map { $additional_params->{$_} ||= $self->{$_} } keys %{ $self }; - my $in = IO::File->new($file, 'r'); + my $output; + $template->process($real_file, $additional_params, \$output) || die $template->error; - if (!$in) { - print STDERR "Error opening template file: $!"; - $main::lxdebug->leave_sub(); - return ''; - } + $main::lxdebug->leave_sub(); - my $input = join('', <$in>); - $in->close(); + return $output; +} - if ($main::locale) { - $input = $main::locale->{iconv}->convert($input); - } +sub init_template { + my $self = shift; - my $output; - if (!$template->process(\$input, $additional_params, \$output)) { - print STDERR $template->error(); - } + return $self->template if $self->template; - $main::lxdebug->leave_sub(); + return $self->template(Template->new({ + 'INTERPOLATE' => 0, + 'EVAL_PERL' => 0, + 'ABSOLUTE' => 1, + 'CACHE_SIZE' => 0, + 'PLUGIN_BASE' => 'SL::Template::Plugin', + 'INCLUDE_PATH' => '.:templates/webpages', + 'COMPILE_EXT' => '.tcc', + 'COMPILE_DIR' => $::lx_office_conf{paths}->{userspath} . '/templates-cache', + })) || die; +} - return $output; +sub template { + my $self = shift; + $self->{template_object} = shift if @_; + return $self->{template_object}; } sub show_generic_error { @@ -778,6 +704,12 @@ sub show_generic_error { my ($self, $error, %params) = @_; + if ($self->{__ERROR_HANDLER}) { + $self->{__ERROR_HANDLER}->($error); + $main::lxdebug->leave_sub(); + return; + } + my $add_params = { 'title_error' => $params{title}, 'label_error' => $error, @@ -802,9 +734,11 @@ sub show_generic_error { $self->header(); print $self->parse_html_template("generic/error", $add_params); + print STDERR "Error: $error\n"; + $main::lxdebug->leave_sub(); - die("Error: $error\n"); + ::end_of_request(); } sub show_generic_information { @@ -824,7 +758,7 @@ sub show_generic_information { $main::lxdebug->leave_sub(); - die("Information: $text\n"); + ::end_of_request(); } # write Trigger JavaScript-Code ($qty = quantity of Triggers) @@ -874,24 +808,31 @@ sub write_trigger { return $jsscript; } #end sub write_trigger +sub _store_redirect_info_in_session { + my ($self) = @_; + + return unless $self->{callback} =~ m:^ ( [^\?/]+ \.pl ) \? (.+) :x; + + my ($controller, $params) = ($1, $2); + my $form = { map { map { $self->unescape($_) } split /=/, $_, 2 } split m/\&/, $params }; + $self->{callback} = "${controller}?RESTORE_FORM_FROM_SESSION_ID=" . $::auth->save_form_in_session(form => $form); +} + sub redirect { $main::lxdebug->enter_sub(); my ($self, $msg) = @_; - if ($self->{callback}) { - - my ($script, $argv) = split(/\?/, $self->{callback}, 2); - $script =~ s|.*/||; - $script =~ s|[^a-zA-Z0-9_\.]||g; - exec("perl", "$script", $argv); + if (!$self->{callback}) { + $self->info($msg); } else { - - $self->info($msg); - exit; + $self->_store_redirect_info_in_session; + print $::form->redirect_header($self->{callback}); } + ::end_of_request(); + $main::lxdebug->leave_sub(); } @@ -914,12 +855,12 @@ sub format_amount { if ($amount eq "") { $amount = 0; } - + # Hey watch out! The amount can be an exponential term like 1.13686837721616e-13 - + my $neg = ($amount =~ s/^-//); my $exp = ($amount =~ m/[e]/) ? 1 : 0; - + if (defined($places) && ($places ne '')) { if (not $exp) { if ($places < 0) { @@ -943,9 +884,9 @@ sub format_amount { $amount .= $d[0].$p[1].(0 x ($places - length $p[1])) if ($places || $p[1] ne ''); $amount = do { - ($dash =~ /-/) ? ($neg ? "($amount)" : "$amount" ) : - ($dash =~ /DRCR/) ? ($neg ? "$amount DR" : "$amount CR" ) : - ($neg ? "-$amount" : "$amount" ) ; + ($dash =~ /-/) ? ($neg ? "($amount)" : "$amount" ) : + ($dash =~ /DRCR/) ? ($neg ? "$amount " . $main::locale->text('DR') : "$amount " . $main::locale->text('CR') ) : + ($neg ? "-$amount" : "$amount" ) ; }; @@ -972,8 +913,7 @@ sub format_amount_units { return ''; } - AM->retrieve_all_units(); - my $all_units = $main::all_units; + my $all_units = AM->retrieve_all_units; if (('' eq ref $conv_units) && ($conv_units =~ /convertible/)) { $conv_units = AM->convertible_units($all_units, $part_unit_name, $conv_units eq 'convertible_not_smaller'); @@ -1047,7 +987,7 @@ sub parse_amount { if ( ($myconfig->{numberformat} eq '1.000,00') || ($myconfig->{numberformat} eq '1000,00')) { $amount =~ s/\.//g; - $amount =~ s/,/\./; + $amount =~ s/,/\./g; } if ($myconfig->{numberformat} eq "1'000.00") { @@ -1058,7 +998,9 @@ sub parse_amount { $main::lxdebug->leave_sub(2); - return ($amount * 1); + # Make sure no code wich is not a math expression ends up in eval(). + return 0 unless $amount =~ /^ [\s \d \( \) \- \+ \* \/ \. ]* $/x; + return scalar(eval($amount)) * 1 ; } sub round_amount { @@ -1067,13 +1009,13 @@ sub round_amount { my ($self, $amount, $places) = @_; my $round_amount; - # Rounding like "Kaufmannsrunden" - # Descr. http://de.wikipedia.org/wiki/Rundung - # Inspired by - # http://www.perl.com/doc/FAQs/FAQ/oldfaq-html/Q4.13.html - # Solves Bug: 189 - # Udo Spallek - $amount = $amount * (10**($places)); + # Rounding like "Kaufmannsrunden" (see http://de.wikipedia.org/wiki/Rundung ) + + # Round amounts to eight places before rounding to the requested + # number of places. This gets rid of errors due to internal floating + # point representation. + $amount = $self->round_amount($amount, 8) if $places < 8; + $amount = $amount * (10**($places)); $round_amount = int($amount + .5 * ($amount <=> 0)) / (10**($places)); $main::lxdebug->leave_sub(2); @@ -1085,37 +1027,59 @@ sub round_amount { sub parse_template { $main::lxdebug->enter_sub(); - my ($self, $myconfig, $userspath) = @_; - my ($template, $out); + my ($self, $myconfig) = @_; + my ($out, $out_mode); local (*IN, *OUT); + my $userspath = $::lx_office_conf{paths}->{userspath}; + $self->{"cwd"} = getcwd(); $self->{"tmpdir"} = $self->{cwd} . "/${userspath}"; + my $ext_for_format; + + my $template_type; if ($self->{"format"} =~ /(opendocument|oasis)/i) { - $template = OpenDocumentTemplate->new($self->{"IN"}, $self, $myconfig, $userspath); + $template_type = 'OpenDocument'; + $ext_for_format = $self->{"format"} =~ m/pdf/ ? 'pdf' : 'odt'; + } elsif ($self->{"format"} =~ /(postscript|pdf)/i) { $ENV{"TEXINPUTS"} = ".:" . getcwd() . "/" . $myconfig->{"templates"} . ":" . $ENV{"TEXINPUTS"}; - $template = LaTeXTemplate->new($self->{"IN"}, $self, $myconfig, $userspath); - } elsif (($self->{"format"} =~ /html/i) || - (!$self->{"format"} && ($self->{"IN"} =~ /html$/i))) { - $template = HTMLTemplate->new($self->{"IN"}, $self, $myconfig, $userspath); - } elsif (($self->{"format"} =~ /xml/i) || - (!$self->{"format"} && ($self->{"IN"} =~ /xml$/i))) { - $template = XMLTemplate->new($self->{"IN"}, $self, $myconfig, $userspath); - } elsif ( $self->{"format"} =~ /elsterwinston/i ) { - $template = XMLTemplate->new($self->{"IN"}, $self, $myconfig, $userspath); - } elsif ( $self->{"format"} =~ /elstertaxbird/i ) { - $template = XMLTemplate->new($self->{"IN"}, $self, $myconfig, $userspath); + $template_type = 'LaTeX'; + $ext_for_format = 'pdf'; + + } elsif (($self->{"format"} =~ /html/i) || (!$self->{"format"} && ($self->{"IN"} =~ /html$/i))) { + $template_type = 'HTML'; + $ext_for_format = 'html'; + + } elsif (($self->{"format"} =~ /xml/i) || (!$self->{"format"} && ($self->{"IN"} =~ /xml$/i))) { + $template_type = 'XML'; + $ext_for_format = 'xml'; + + } elsif ( $self->{"format"} =~ /elster(?:winston|taxbird)/i ) { + $template_type = 'XML'; + + } elsif ( $self->{"format"} =~ /excel/i ) { + $template_type = 'Excel'; + $ext_for_format = 'xls'; + } elsif ( defined $self->{'format'}) { $self->error("Outputformat not defined. This may be a future feature: $self->{'format'}"); + } elsif ( $self->{'format'} eq '' ) { $self->error("No Outputformat given: $self->{'format'}"); + } else { #Catch the rest $self->error("Outputformat not defined: $self->{'format'}"); } + my $template = SL::Template::create(type => $template_type, + file_name => $self->{IN}, + form => $self, + myconfig => $myconfig, + userspath => $userspath); + # Copy the notes from the invoice/sales order etc. back to the variable "notes" because that is where most templates expect it to be. $self->{"notes"} = $self->{ $self->{"formname"} . "notes" }; @@ -1124,34 +1088,41 @@ sub parse_template { } map { $self->{"${_}"} = $myconfig->{$_}; } qw(co_ustid); + map { $self->{"myconfig_${_}"} = $myconfig->{$_} } grep { $_ ne 'dbpasswd' } keys %{ $myconfig }; $self->{copies} = 1 if (($self->{copies} *= 1) <= 0); # OUT is used for the media, screen, printer, email # for postscript we store a copy in a temporary file - my $fileid = time; - my $prepend_userspath; - - if (!$self->{tmpfile}) { - $self->{tmpfile} = "${fileid}.$self->{IN}"; - $prepend_userspath = 1; - } - - $prepend_userspath = 1 if substr($self->{tmpfile}, 0, length $userspath) eq $userspath; - - $self->{tmpfile} =~ s|.*/||; - $self->{tmpfile} =~ s/[^a-zA-Z0-9\._\ \-]//g; - $self->{tmpfile} = "$userspath/$self->{tmpfile}" if $prepend_userspath; + my ($temp_fh, $suffix); + $suffix = $self->{IN}; + $suffix =~ s/.*\.//; + ($temp_fh, $self->{tmpfile}) = File::Temp::tempfile( + 'lx-office-printXXXXXX', + SUFFIX => '.' . ($suffix || 'tex'), + DIR => $userspath, + UNLINK => 1, + ); + close $temp_fh; if ($template->uses_temp_file() || $self->{media} eq 'email') { - $out = $self->{OUT}; - $self->{OUT} = ">$self->{tmpfile}"; + $out = $self->{OUT}; + $out_mode = $self->{OUT_MODE} || '>'; + $self->{OUT} = "$self->{tmpfile}"; + $self->{OUT_MODE} = '>'; } + my $result; + my $command_formatter = sub { + my ($out_mode, $out) = @_; + return $out_mode eq '|-' ? SL::Template::create(type => 'ShellCommand', form => $self)->parse($out) : $out; + }; + if ($self->{OUT}) { - open(OUT, "$self->{OUT}") or $self->error("$self->{OUT} : $!"); + $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT}); + open(OUT, $self->{OUT_MODE}, $self->{OUT}) or $self->error("error on opening $self->{OUT} with mode $self->{OUT_MODE} : $!"); } else { - open(OUT, ">-") or $self->error("STDOUT : $!"); + *OUT = ($::dispatcher->get_standard_filehandles)[1]; $self->header; } @@ -1160,7 +1131,17 @@ sub parse_template { $self->error("$self->{IN} : " . $template->get_error()); } - close(OUT); + close OUT if $self->{OUT}; + + if ($self->{media} eq 'file') { + copy(join('/', $self->{cwd}, $userspath, $self->{tmpfile}), $out =~ m|^/| ? $out : join('/', $self->{cwd}, $out)) if $template->uses_temp_file; + $self->cleanup; + chdir("$self->{cwd}"); + + $::lxdebug->leave_sub(); + + return; + } if ($template->uses_temp_file() || $self->{media} eq 'email') { @@ -1170,36 +1151,32 @@ sub parse_template { map { $mail->{$_} = $self->{$_} } qw(cc bcc subject message version format); - $mail->{charset} = $main::dbcharset ? $main::dbcharset : Common::DEFAULT_CHARSET; + $mail->{charset} = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET; $mail->{to} = $self->{EMAIL_RECIPIENT} ? $self->{EMAIL_RECIPIENT} : $self->{email}; $mail->{from} = qq|"$myconfig->{name}" <$myconfig->{email}>|; - $mail->{fileid} = "$fileid."; + $mail->{fileid} = time() . '.' . $$ . '.'; $myconfig->{signature} =~ s/\r//g; # if we send html or plain text inline if (($self->{format} eq 'html') && ($self->{sendmode} eq 'inline')) { - $mail->{contenttype} = "text/html"; + $mail->{contenttype} = "text/html"; + $mail->{message} =~ s/\r//g; + $mail->{message} =~ s/\n/
\n/g; + $myconfig->{signature} =~ s/\n/
\n/g; + $mail->{message} .= "
\n--
\n$myconfig->{signature}\n
"; - $mail->{message} =~ s/\r//g; - $mail->{message} =~ s/\n/
\n/g; - $myconfig->{signature} =~ s/\n/
\n/g; - $mail->{message} .= "
\n--
\n$myconfig->{signature}\n
"; - - open(IN, $self->{tmpfile}) + open(IN, "<", $self->{tmpfile}) or $self->error($self->cleanup . "$self->{tmpfile} : $!"); - while () { - $mail->{message} .= $_; - } - + $mail->{message} .= $_ while ; close(IN); } else { if (!$self->{"do_not_attach"}) { - @{ $mail->{attachments} } = - ({ "filename" => $self->{"tmpfile"}, - "name" => $self->{"attachment_filename"} ? - $self->{"attachment_filename"} : $self->{"tmpfile"} }); + my $attachment_name = $self->{attachment_filename} || $self->{tmpfile}; + $attachment_name =~ s/\.(.+?)$/.${ext_for_format}/ if ($ext_for_format); + $mail->{attachments} = [{ "filename" => $self->{tmpfile}, + "name" => $attachment_name }]; } $mail->{message} =~ s/\r//g; @@ -1212,11 +1189,13 @@ sub parse_template { } else { - $self->{OUT} = $out; + $self->{OUT} = $out; + $self->{OUT_MODE} = $out_mode; my $numbytes = (-s $self->{tmpfile}); - open(IN, $self->{tmpfile}) + open(IN, "<", $self->{tmpfile}) or $self->error($self->cleanup . "$self->{tmpfile} : $!"); + binmode IN; $self->{copies} = 1 unless $self->{media} eq 'printer'; @@ -1225,10 +1204,15 @@ sub parse_template { #print(STDERR "OUT $self->{OUT}\n"); for my $i (1 .. $self->{copies}) { if ($self->{OUT}) { - open(OUT, $self->{OUT}) - or $self->error($self->cleanup . "$self->{OUT} : $!"); + $self->{OUT} = $command_formatter->($self->{OUT_MODE}, $self->{OUT}); + + open OUT, $self->{OUT_MODE}, $self->{OUT} or $self->error($self->cleanup . "$self->{OUT} : $!"); + print OUT $_ while ; + close OUT; + seek IN, 0, 0; + } else { - $self->{attachment_filename} = ($self->{attachment_filename}) + $self->{attachment_filename} = ($self->{attachment_filename}) ? $self->{attachment_filename} : $self->generate_attachment_filename(); @@ -1239,17 +1223,8 @@ Content-Length: $numbytes |; - open(OUT, ">-") or $self->error($self->cleanup . "$!: STDOUT"); - + $::locale->with_raw_io(\*STDOUT, sub { print while }); } - - while () { - print OUT $_; - } - - close(OUT); - - seek IN, 0, 0; } close(IN); @@ -1264,6 +1239,7 @@ Content-Length: $numbytes } sub get_formname_translation { + $main::lxdebug->enter_sub(); my ($self, $formname) = @_; $formname ||= $self->{formname}; @@ -1272,7 +1248,6 @@ sub get_formname_translation { bin_list => $main::locale->text('Bin List'), credit_note => $main::locale->text('Credit Note'), invoice => $main::locale->text('Invoice'), - packing_list => $main::locale->text('Packing List'), pick_list => $main::locale->text('Pick List'), proforma => $main::locale->text('Proforma Invoice'), purchase_order => $main::locale->text('Purchase Order'), @@ -1280,15 +1255,17 @@ sub get_formname_translation { sales_order => $main::locale->text('Confirmation'), sales_quotation => $main::locale->text('Quotation'), storno_invoice => $main::locale->text('Storno Invoice'), - storno_packing_list => $main::locale->text('Storno Packing List'), sales_delivery_order => $main::locale->text('Delivery Order'), purchase_delivery_order => $main::locale->text('Delivery Order'), + dunning => $main::locale->text('Dunning'), ); + $main::lxdebug->leave_sub(); return $formname_translations{$formname} } sub get_number_prefix_for_type { + $main::lxdebug->enter_sub(); my ($self) = @_; my $prefix = @@ -1297,22 +1274,27 @@ sub get_number_prefix_for_type { : ($self->{type} =~ /_delivery_order$/) ? 'do' : 'ord'; + $main::lxdebug->leave_sub(); return $prefix; } sub get_extension_for_format { + $main::lxdebug->enter_sub(); my ($self) = @_; my $extension = $self->{format} =~ /pdf/i ? ".pdf" : $self->{format} =~ /postscript/i ? ".ps" : $self->{format} =~ /opendocument/i ? ".odt" + : $self->{format} =~ /excel/i ? ".xls" : $self->{format} =~ /html/i ? ".html" : ""; + $main::lxdebug->leave_sub(); return $extension; } sub generate_attachment_filename { + $main::lxdebug->enter_sub(); my ($self) = @_; my $attachment_filename = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation()); @@ -1331,10 +1313,12 @@ sub generate_attachment_filename { $attachment_filename = $main::locale->quote_special_chars('filenames', $attachment_filename); $attachment_filename =~ s|[\s/\\]+|_|g; + $main::lxdebug->leave_sub(); return $attachment_filename; } sub generate_email_subject { + $main::lxdebug->enter_sub(); my ($self) = @_; my $subject = $main::locale->unquote_special_chars('HTML', $self->get_formname_translation()); @@ -1344,24 +1328,30 @@ sub generate_email_subject { $subject .= " " . $self->{"${prefix}number"} } + $main::lxdebug->leave_sub(); return $subject; } sub cleanup { $main::lxdebug->enter_sub(); - my $self = shift; + my ($self, $application) = @_; + + my $error_code = $?; chdir("$self->{tmpdir}"); my @err = (); - if (-f "$self->{tmpfile}.err") { + if ((-1 == $error_code) || (127 == (($error_code) >> 8))) { + push @err, $::locale->text('The application "#1" was not found on the system.', $application || 'pdflatex') . ' ' . $::locale->text('Please contact your administrator.'); + + } elsif (-f "$self->{tmpfile}.err") { open(FH, "$self->{tmpfile}.err"); @err = ; close(FH); } - if ($self->{tmpfile}) { + if ($self->{tmpfile} && !($::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files})) { $self->{tmpfile} =~ s|.*/||g; # strip extension $self->{tmpfile} =~ s/\.\w+$//g; @@ -1412,15 +1402,21 @@ sub datetonum { # Database routines used throughout +sub _dbconnect_options { + my $self = shift; + my $options = { pg_enable_utf8 => $::locale->is_utf8, + @_ }; + + return $options; +} + sub dbconnect { $main::lxdebug->enter_sub(2); my ($self, $myconfig) = @_; # connect to database - my $dbh = - DBI->connect($myconfig->{dbconnect}, - $myconfig->{dbuser}, $myconfig->{dbpasswd}) + my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options) or $self->dberror; # set db options @@ -1437,11 +1433,9 @@ sub dbconnect_noauto { $main::lxdebug->enter_sub(); my ($self, $myconfig) = @_; - + # connect to database - my $dbh = - DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, - $myconfig->{dbpasswd}, { AutoCommit => 0 }) + my $dbh = SL::DBConnect->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options(AutoCommit => 0)) or $self->dberror; # set db options @@ -1457,10 +1451,11 @@ sub dbconnect_noauto { sub get_standard_dbh { $main::lxdebug->enter_sub(2); - my ($self, $myconfig) = @_; + my $self = shift; + my $myconfig = shift || \%::myconfig; if ($standard_dbh && !$standard_dbh->{Active}) { - $main::lxdebug->message(LXDebug::INFO, "get_standard_dbh: \$standard_dbh is defined but not Active anymore"); + $main::lxdebug->message(LXDebug->INFO(), "get_standard_dbh: \$standard_dbh is defined but not Active anymore"); undef $standard_dbh; } @@ -1478,7 +1473,24 @@ sub date_closed { my $dbh = $self->dbconnect($myconfig); my $query = "SELECT 1 FROM defaults WHERE ? < closedto"; - my $sth = prepare_execute_query($self, $dbh, $query, $date); + my $sth = prepare_execute_query($self, $dbh, $query, conv_date($date)); + + # Falls $date = '' - Fehlermeldung aus der Datenbank. Ich denke, + # es ist sicher ein conv_date vorher IMMER auszuführen. + # Testfälle ohne definiertes closedto: + # Leere Datumseingabe i.O. + # SELECT 1 FROM defaults WHERE '' < closedto + # normale Zahlungsbuchung über Rechnungsmaske i.O. + # SELECT 1 FROM defaults WHERE '10.05.2011' < closedto + # Testfälle mit definiertem closedto (30.04.2011): + # Leere Datumseingabe i.O. + # SELECT 1 FROM defaults WHERE '' < closedto + # normale Buchung im geschloßenem Zeitraum i.O. + # SELECT 1 FROM defaults WHERE '21.04.2011' < closedto + # Fehlermeldung: Es können keine Zahlungen für abgeschlossene Bücher gebucht werden! + # normale Buchung in aktiver Buchungsperiode i.O. + # SELECT 1 FROM defaults WHERE '01.05.2011' < closedto + my ($closed) = $sth->fetchrow_array; $main::lxdebug->leave_sub(); @@ -1518,7 +1530,7 @@ sub update_exchangerate { if ($curr eq '') { $main::lxdebug->leave_sub(); return; - } + } $query = qq|SELECT curr FROM defaults|; my ($currency) = selectrow_query($self, $dbh, $query); @@ -1559,7 +1571,7 @@ sub update_exchangerate { SET $set WHERE curr = ? AND transdate = ?|; - + } else { $query = qq|INSERT INTO exchangerate (curr, buy, sell, transdate) VALUES (?, $buy, $sell, ?)|; @@ -1628,6 +1640,10 @@ sub check_exchangerate { my ($self, $myconfig, $currency, $transdate, $fld) = @_; + if ($fld !~/^buy|sell$/) { + $self->error('Fatal: check_exchangerate called with invalid buy/sell argument'); + } + unless ($transdate) { $main::lxdebug->leave_sub(); return ""; @@ -1651,22 +1667,33 @@ sub check_exchangerate { return $exchangerate; } -sub get_default_currency { +sub get_all_currencies { $main::lxdebug->enter_sub(); - my ($self, $myconfig) = @_; - my $dbh = $self->get_standard_dbh($myconfig); + my $self = shift; + my $myconfig = shift || \%::myconfig; + my $dbh = $self->get_standard_dbh($myconfig); my $query = qq|SELECT curr FROM defaults|; - my ($curr) = selectrow_query($self, $dbh, $query); - my ($defaultcurrency) = split m/:/, $curr; + my ($curr) = selectrow_query($self, $dbh, $query); + my @currencies = grep { $_ } map { s/\s//g; $_ } split m/:/, $curr; $main::lxdebug->leave_sub(); - return $defaultcurrency; + return @currencies; } +sub get_default_currency { + $main::lxdebug->enter_sub(); + + my ($self, $myconfig) = @_; + my @currencies = $self->get_all_currencies($myconfig); + + $main::lxdebug->leave_sub(); + + return $currencies[0]; +} sub set_payment_options { $main::lxdebug->enter_sub(); @@ -1678,12 +1705,12 @@ sub set_payment_options { my $dbh = $self->get_standard_dbh($myconfig); my $query = - qq|SELECT p.terms_netto, p.terms_skonto, p.percent_skonto, p.description_long | . + qq|SELECT p.terms_netto, p.terms_skonto, p.percent_skonto, p.description_long , p.description | . qq|FROM payment_terms p | . qq|WHERE p.id = ?|; ($self->{terms_netto}, $self->{terms_skonto}, $self->{percent_skonto}, - $self->{payment_terms}) = + $self->{payment_terms}, $self->{payment_description}) = selectrow_query($self, $dbh, $query, $self->{payment_id}); if ($transdate eq "") { @@ -1715,6 +1742,7 @@ sub set_payment_options { $amounts{invtotal} = $self->{invtotal}; $amounts{total} = $self->{total}; } + $amounts{skonto_in_percent} = 100.0 * $self->{percent_skonto}; map { $amounts{$_} = $self->parse_amount($myconfig, $amounts{$_}) } keys %amounts; @@ -1729,10 +1757,12 @@ sub set_payment_options { if ($self->{"language_id"}) { $query = - qq|SELECT t.description_long, l.output_numberformat, l.output_dateformat, l.output_longdates | . - qq|FROM translation_payment_terms t | . + qq|SELECT t.translation, l.output_numberformat, l.output_dateformat, l.output_longdates | . + qq|FROM generic_translations t | . qq|LEFT JOIN language l ON t.language_id = l.id | . - qq|WHERE (t.language_id = ?) AND (t.payment_terms_id = ?)|; + qq|WHERE (t.language_id = ?) + AND (t.translation_id = ?) + AND (t.translation_type = 'SL::DB::PaymentTerm/description_long')|; my ($description_long, $output_numberformat, $output_dateformat, $output_longdates) = selectrow_query($self, $dbh, $query, @@ -1768,6 +1798,8 @@ sub set_payment_options { map { $self->{payment_terms} =~ s/<%${_}%>/$formatted_amounts{$_}/g; } keys %formatted_amounts; + $self->{skonto_in_percent} = $formatted_amounts{skonto_in_percent}; + $main::lxdebug->leave_sub(); } @@ -1834,7 +1866,7 @@ sub add_shipto { my @values; foreach my $item (qw(name department_1 department_2 street zipcode city country - contact phone fax email)) { + contact cp_gender phone fax email)) { if ($self->{"shipto$item"}) { $shipto = 1 if ($self->{$item} ne $self->{"shipto$item"}); } @@ -1852,6 +1884,7 @@ sub add_shipto { shiptocity = ?, shiptocountry = ?, shiptocontact = ?, + shiptocp_gender = ?, shiptophone = ?, shiptofax = ?, shiptoemail = ? @@ -1867,18 +1900,19 @@ sub add_shipto { shiptocity = ? AND shiptocountry = ? AND shiptocontact = ? AND + shiptocp_gender = ? AND shiptophone = ? AND shiptofax = ? AND shiptoemail = ? AND - module = ? AND + module = ? AND trans_id = ?|; my $insert_check = selectfirst_hashref_query($self, $dbh, $query, @values, $module, $id); if(!$insert_check){ $query = qq|INSERT INTO shipto (trans_id, shiptoname, shiptodepartment_1, shiptodepartment_2, shiptostreet, shiptozipcode, shiptocity, shiptocountry, - shiptocontact, shiptophone, shiptofax, shiptoemail, module) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|; + shiptocontact, shiptocp_gender, shiptophone, shiptofax, shiptoemail, module) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|; do_query($self, $dbh, $query, $id, @values, $module); } } @@ -1892,6 +1926,8 @@ sub get_employee { my ($self, $dbh) = @_; + $dbh ||= $self->get_standard_dbh(\%main::myconfig); + my $query = qq|SELECT id, name FROM employee WHERE login = ?|; ($self->{"employee_id"}, $self->{"employee"}) = selectrow_query($self, $dbh, $query, $self->{login}); $self->{"employee_id"} *= 1; @@ -1932,13 +1968,17 @@ sub get_employee_data { sub get_duedate { $main::lxdebug->enter_sub(); - my ($self, $myconfig) = @_; + my ($self, $myconfig, $reference_date) = @_; - my $dbh = $self->get_standard_dbh($myconfig); - my $query = qq|SELECT current_date + terms_netto FROM payment_terms WHERE id = ?|; - ($self->{duedate}) = selectrow_query($self, $dbh, $query, $self->{payment_id}); + $reference_date = $reference_date ? conv_dateq($reference_date) . '::DATE' : 'current_date'; + + my $dbh = $self->get_standard_dbh($myconfig); + my $query = qq|SELECT ${reference_date} + terms_netto FROM payment_terms WHERE id = ?|; + my ($duedate) = selectrow_query($self, $dbh, $query, $self->{payment_id}); $main::lxdebug->leave_sub(); + + return $duedate; } sub _get_contacts { @@ -2061,7 +2101,7 @@ sub _get_charts { my $transdate = quote_db_date($params->{transdate}); my $query = - qq|SELECT c.id, c.accno, c.description, c.link, tk.taxkey_id, tk.tax_id | . + qq|SELECT c.id, c.accno, c.description, c.link, c.charttype, tk.taxkey_id, tk.tax_id | . qq|FROM chart c | . qq|LEFT JOIN taxkeys tk ON | . qq|(tk.id = (SELECT id FROM taxkeys | . @@ -2077,11 +2117,27 @@ sub _get_charts { sub _get_taxcharts { $main::lxdebug->enter_sub(); - my ($self, $dbh, $key) = @_; + my ($self, $dbh, $params) = @_; + + my $key = "all_taxcharts"; + my @where; + + if (ref $params eq 'HASH') { + $key = $params->{key} if ($params->{key}); + if ($params->{module} eq 'AR') { + push @where, 'taxkey NOT IN (8, 9, 18, 19)'; + + } elsif ($params->{module} eq 'AP') { + push @where, 'taxkey NOT IN (1, 2, 3, 12, 13)'; + } + + } elsif ($params) { + $key = $params; + } - $key = "all_taxcharts" unless ($key); + my $where = @where ? ' WHERE ' . join(' AND ', map { "($_)" } @where) : ''; - my $query = qq|SELECT * FROM tax ORDER BY taxkey|; + my $query = qq|SELECT * FROM tax $where ORDER BY taxkey|; $self->{$key} = selectall_hashref_query($self, $dbh, $query); @@ -2118,9 +2174,15 @@ sub _get_business_types { my ($self, $dbh, $key) = @_; - $key = "all_business_types" unless ($key); - $self->{$key} = - selectall_hashref_query($self, $dbh, qq|SELECT * FROM business|); + my $options = ref $key eq 'HASH' ? $key : { key => $key }; + $options->{key} ||= "all_business_types"; + my $where = ''; + + if (exists $options->{salesman}) { + $where = 'WHERE ' . ($options->{salesman} ? '' : 'NOT ') . 'COALESCE(salesman)'; + } + + $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, qq|SELECT * FROM business $where ORDER BY lower(description)|); $main::lxdebug->leave_sub(); } @@ -2161,7 +2223,7 @@ $main::lxdebug->enter_sub(); $key = "all_currencies" unless ($key); my $query = qq|SELECT curr AS currency FROM defaults|; - + $self->{$key} = [split(/\:/ , selectfirst_hashref_query($self, $dbh, $query)->{currency})]; $main::lxdebug->leave_sub(); @@ -2174,8 +2236,8 @@ $main::lxdebug->enter_sub(); $key = "all_payments" unless ($key); - my $query = qq|SELECT * FROM payment_terms ORDER BY id|; - + my $query = qq|SELECT * FROM payment_terms ORDER BY sortkey|; + $self->{$key} = selectall_hashref_query($self, $dbh, $query); $main::lxdebug->leave_sub(); @@ -2184,14 +2246,19 @@ $main::lxdebug->enter_sub(); sub _get_customers { $main::lxdebug->enter_sub(); - my ($self, $dbh, $key, $limit) = @_; + my ($self, $dbh, $key) = @_; - $key = "all_customers" unless ($key); - my $limit_clause = "LIMIT $limit" if $limit; + my $options = ref $key eq 'HASH' ? $key : { key => $key }; + $options->{key} ||= "all_customers"; + my $limit_clause = $options->{limit} ? "LIMIT $options->{limit}" : ''; - my $query = qq|SELECT * FROM customer WHERE NOT obsolete ORDER BY name $limit_clause|; + my @where; + push @where, qq|business_id IN (SELECT id FROM business WHERE salesman)| if $options->{business_is_salesman}; + push @where, qq|NOT obsolete| if !$options->{with_obsolete}; + my $where_str = @where ? "WHERE " . join(" AND ", map { "($_)" } @where) : ''; - $self->{$key} = selectall_hashref_query($self, $dbh, $query); + my $query = qq|SELECT * FROM customer $where_str ORDER BY name $limit_clause|; + $self->{ $options->{key} } = selectall_hashref_query($self, $dbh, $query); $main::lxdebug->leave_sub(); } @@ -2247,7 +2314,8 @@ sub _get_warehouses { $self->{$key} = selectall_hashref_query($self, $dbh, $query); if ($bins_key) { - $query = qq|SELECT id, description FROM bin WHERE warehouse_id = ?|; + $query = qq|SELECT id, description FROM bin WHERE warehouse_id = ? + ORDER BY description|; my $sth = prepare_query($self, $dbh, $query); foreach my $warehouse (@{ $self->{$key} }) { @@ -2340,7 +2408,7 @@ sub get_lists { if ($params{"employees"}) { $self->_get_employees($dbh, "all_employees", $params{"employees"}); } - + if ($params{"salesmen"}) { $self->_get_employees($dbh, "all_salesmen", $params{"salesmen"}); } @@ -2352,19 +2420,15 @@ sub get_lists { if ($params{"dunning_configs"}) { $self->_get_dunning_configs($dbh, $params{"dunning_configs"}); } - + if($params{"currencies"}) { $self->_get_currencies($dbh, $params{"currencies"}); } - + if($params{"customers"}) { - if (ref $params{"customers"} eq 'HASH') { - $self->_get_customers($dbh, $params{"customers"}{key}, $params{"customers"}{limit}); - } else { - $self->_get_customers($dbh, $params{"customers"}); - } + $self->_get_customers($dbh, $params{"customers"}); } - + if($params{"vendors"}) { if (ref $params{"vendors"} eq 'HASH') { $self->_get_vendors($dbh, $params{"vendors"}{key}, $params{"vendors"}{limit}); @@ -2372,7 +2436,7 @@ sub get_lists { $self->_get_vendors($dbh, $params{"vendors"}); } } - + if($params{"payments"}) { $self->_get_payments($dbh, $params{"payments"}); } @@ -2456,15 +2520,15 @@ sub all_vc { my ($self, $myconfig, $table, $module) = @_; my $ref; - my $dbh = $self->get_standard_dbh($myconfig); + my $dbh = $self->get_standard_dbh; $table = $table eq "customer" ? "customer" : "vendor"; - my $query = qq|SELECT count(*) FROM $table|; + my $query = qq|SELECT count(*) FROM $table WHERE NOT obsolete|; my ($count) = selectrow_query($self, $dbh, $query); # build selection list - if ($count < $myconfig->{vclimit}) { + if ($count <= $myconfig->{vclimit}) { $query = qq|SELECT id, name, salesman_id FROM $table WHERE NOT obsolete ORDER BY name|; @@ -2489,20 +2553,12 @@ sub all_vc { @{ $self->{all_employees} } = sort { $a->{name} cmp $b->{name} } @{ $self->{all_employees} }; - if ($module eq 'AR') { # prepare query for departments $query = qq|SELECT id, description FROM department - WHERE role = 'P' ORDER BY description|; - } else { - $query = qq|SELECT id, description - FROM department - ORDER BY description|; - } - $self->{all_departments} = selectall_hashref_query($self, $dbh, $query); # get languages @@ -2572,19 +2628,13 @@ sub all_departments { my ($self, $myconfig, $table) = @_; my $dbh = $self->get_standard_dbh($myconfig); - my $where; - - if ($table eq 'customer') { - $where = "WHERE role = 'P' "; - } my $query = qq|SELECT id, description FROM department - $where ORDER BY description|; $self->{all_departments} = selectall_hashref_query($self, $dbh, $query); - delete($self->{all_departments}) unless (@{ $self->{all_departments} }); + delete($self->{all_departments}) unless (@{ $self->{all_departments} || [] }); $main::lxdebug->leave_sub(); } @@ -2620,11 +2670,28 @@ sub create_links { } # now get the account numbers - $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id - FROM chart c, taxkeys tk - WHERE (c.link LIKE ?) AND (c.id = tk.chart_id) AND tk.id = - (SELECT id FROM taxkeys WHERE (taxkeys.chart_id = c.id) AND (startdate <= $transdate) ORDER BY startdate DESC LIMIT 1) - ORDER BY c.accno|; +# $query = qq|SELECT c.accno, c.description, c.link, c.taxkey_id, tk.tax_id +# FROM chart c, taxkeys tk +# WHERE (c.link LIKE ?) AND (c.id = tk.chart_id) AND tk.id = +# (SELECT id FROM taxkeys WHERE (taxkeys.chart_id = c.id) AND (startdate <= $transdate) ORDER BY startdate DESC LIMIT 1) +# ORDER BY c.accno|; + +# same query as above, but without expensive subquery for each row. about 80% faster + $query = qq| + SELECT c.accno, c.description, c.link, c.taxkey_id, tk2.tax_id + FROM chart c + -- find newest entries in taxkeys + INNER JOIN ( + SELECT chart_id, MAX(startdate) AS startdate + FROM taxkeys + WHERE (startdate <= $transdate) + GROUP BY chart_id + ) tk ON (c.id = tk.chart_id) + -- and load all of those entries + INNER JOIN taxkeys tk2 + ON (tk.chart_id = tk2.chart_id AND tk.startdate = tk2.startdate) + WHERE (c.link LIKE ?) + ORDER BY c.accno|; $sth = $dbh->prepare($query); @@ -2668,6 +2735,7 @@ sub create_links { a.duedate, a.ordnumber, a.taxincluded, a.curr AS currency, a.notes, a.intnotes, a.department_id, a.amount AS oldinvtotal, a.paid AS oldtotalpaid, a.employee_id, a.gldate, a.type, + a.globalproject_id, c.name AS $table, d.description AS department, e.name AS employee @@ -2682,6 +2750,9 @@ sub create_links { $self->{$key} = $ref->{$key}; } + # remove any trailing whitespace + $self->{currency} =~ s/\s*$//; + my $transdate = "current_date"; if ($self->{transdate}) { $transdate = $dbh->quote($self->{transdate}); @@ -2724,7 +2795,7 @@ sub create_links { $query = qq|SELECT c.accno, c.description, - a.source, a.amount, a.memo, a.transdate, a.cleared, a.project_id, a.taxkey, + a.acc_trans_id, a.source, a.amount, a.memo, a.transdate, a.gldate, a.cleared, a.project_id, a.taxkey, p.projectnumber, t.rate, t.id FROM acc_trans a @@ -2740,7 +2811,7 @@ sub create_links { (startdate <= a.transdate) ORDER BY startdate DESC LIMIT 1)) WHERE a.trans_id = ? AND a.fx_transaction = '0' - ORDER BY a.oid, a.transdate|; + ORDER BY a.acc_trans_id, a.transdate|; $sth = $dbh->prepare($query); do_statement($self, $sth, $query, $self->{id}); @@ -2789,7 +2860,7 @@ sub create_links { if ($self->{"$self->{vc}_id"}) { # only setup currency - ($self->{currency}) = split(/:/, $self->{currencies}); + ($self->{currency}) = split(/:/, $self->{currencies}) if !$self->{currency}; } else { @@ -2819,12 +2890,14 @@ sub lastname_used { "a.department_id" => "department_id", "d.description" => "department", "ct.name" => $table, + "ct.curr" => "cv_curr", "current_date + ct.terms" => "duedate", ); if ($self->{type} =~ /delivery_order/) { $arap = 'delivery_orders'; delete $column_map{"a.curr"}; + delete $column_map{"ct.curr"}; } elsif ($self->{type} =~ /_order/) { $arap = 'oe'; @@ -2858,13 +2931,22 @@ sub lastname_used { map { $self->{$_} = $ref->{$_} } values %column_map; + # remove any trailing whitespace + $self->{currency} =~ s/\s*$// if $self->{currency}; + $self->{cv_curr} =~ s/\s*$// if $self->{cv_curr}; + + # if customer/vendor currency is set use this + $self->{currency} = $self->{cv_curr} if $self->{cv_curr}; + $main::lxdebug->leave_sub(); } sub current_date { $main::lxdebug->enter_sub(); - my ($self, $myconfig, $thisdate, $days) = @_; + my $self = shift; + my $myconfig = shift || \%::myconfig; + my ($thisdate, $days) = @_; my $dbh = $self->get_standard_dbh($myconfig); my $query; @@ -3056,7 +3138,6 @@ sub save_status { # $main::locale->text('invoice') # $main::locale->text('proforma') # $main::locale->text('sales_order') -# $main::locale->text('packing_list') # $main::locale->text('pick_list') # $main::locale->text('purchase_order') # $main::locale->text('bin_list') @@ -3066,8 +3147,8 @@ sub save_status { sub save_history { $main::lxdebug->enter_sub(); - my $self = shift(); - my $dbh = shift(); + my $self = shift; + my $dbh = shift || $self->get_standard_dbh; if(!exists $self->{employee_id}) { &get_employee($self, $dbh); @@ -3080,6 +3161,8 @@ sub save_history { $self->{addition}, $self->{what_done}, "$self->{snumbers}"); do_query($self, $dbh, $query, @values); + $dbh->commit; + $main::lxdebug->leave_sub(); } @@ -3096,10 +3179,9 @@ sub get_history { qq|SELECT h.employee_id, h.itime::timestamp(0) AS itime, h.addition, h.what_done, emp.name, h.snumbers, h.trans_id AS id | . qq|FROM history_erp h | . qq|LEFT JOIN employee emp ON (emp.id = h.employee_id) | . - qq|WHERE trans_id = | . $trans_id - . $restriction . qq| | - . $order; - + qq|WHERE (trans_id = | . $trans_id . qq|) $restriction | . + $order; + my $sth = $dbh->prepare($query) || $self->dberror($query); $sth->execute() || $self->dberror("$query"); @@ -3110,7 +3192,7 @@ sub get_history { $hash_ref->{snumbers} =~ s/^.+_(.*)$/$1/g; $tempArray[$i++] = $hash_ref; } - $main::lxdebug->leave_sub() and return \@tempArray + $main::lxdebug->leave_sub() and return \@tempArray if ($i > 0 && $tempArray[0] ne ""); } $main::lxdebug->leave_sub(); @@ -3173,6 +3255,8 @@ sub update_business { WHERE id = ? FOR UPDATE|; my ($var) = selectrow_query($self, $dbh, $query, $business_id); + return undef unless $var; + if ($var =~ m/\d+$/) { my $new_var = (substr $var, $-[0]) * 1 + 1; my $len_diff = length($var) - $-[0] - length($new_var); @@ -3311,7 +3395,7 @@ sub backup_vars { my $self = shift; my @vars = @_; - map { $self->{_VAR_BACKUP}->{$_} = $self->{$_} if $self->{$_} } @vars; + map { $self->{_VAR_BACKUP}->{$_} = $self->{$_} if exists $self->{$_} } @vars; $main::lxdebug->leave_sub(); } @@ -3322,9 +3406,251 @@ sub restore_vars { my $self = shift; my @vars = @_; - map { $self->{$_} = $self->{_VAR_BACKUP}->{$_} if $self->{_VAR_BACKUP}->{$_} } @vars; + map { $self->{$_} = $self->{_VAR_BACKUP}->{$_} if exists $self->{_VAR_BACKUP}->{$_} } @vars; $main::lxdebug->leave_sub(); } +sub prepare_for_printing { + my ($self) = @_; + + $self->{templates} ||= $::myconfig{templates}; + $self->{formname} ||= $self->{type}; + $self->{media} ||= 'email'; + + die "'media' other than 'email', 'file', 'printer' is not supported yet" unless $self->{media} =~ m/^(?:email|file|printer)$/; + + # set shipto from billto unless set + my $has_shipto = any { $self->{"shipto$_"} } qw(name street zipcode city country contact); + if (!$has_shipto && ($self->{type} =~ m/^(?:purchase_order|request_quotation)$/)) { + $self->{shiptoname} = $::myconfig{company}; + $self->{shiptostreet} = $::myconfig{address}; + } + + my $language = $self->{language} ? '_' . $self->{language} : ''; + + my ($language_tc, $output_numberformat, $output_dateformat, $output_longdates); + if ($self->{language_id}) { + ($language_tc, $output_numberformat, $output_dateformat, $output_longdates) = AM->get_language_details(\%::myconfig, $self, $self->{language_id}); + } else { + $output_dateformat = $::myconfig{dateformat}; + $output_numberformat = $::myconfig{numberformat}; + $output_longdates = 1; + } + + # Retrieve accounts for tax calculation. + IC->retrieve_accounts(\%::myconfig, $self, map { $_ => $self->{"id_$_"} } 1 .. $self->{rowcount}); + + if ($self->{type} =~ /_delivery_order$/) { + DO->order_details(); + } elsif ($self->{type} =~ /sales_order|sales_quotation|request_quotation|purchase_order/) { + OE->order_details(\%::myconfig, $self); + } else { + IS->invoice_details(\%::myconfig, $self, $::locale); + } + + # Chose extension & set source file name + my $extension = 'html'; + if ($self->{format} eq 'postscript') { + $self->{postscript} = 1; + $extension = 'tex'; + } elsif ($self->{"format"} =~ /pdf/) { + $self->{pdf} = 1; + $extension = $self->{'format'} =~ m/opendocument/i ? 'odt' : 'tex'; + } elsif ($self->{"format"} =~ /opendocument/) { + $self->{opendocument} = 1; + $extension = 'odt'; + } elsif ($self->{"format"} =~ /excel/) { + $self->{excel} = 1; + $extension = 'xls'; + } + + my $printer_code = $self->{printer_code} ? '_' . $self->{printer_code} : ''; + my $email_extension = -f "$::myconfig{templates}/$self->{formname}_email${language}.${extension}" ? '_email' : ''; + $self->{IN} = "$self->{formname}${email_extension}${language}${printer_code}.${extension}"; + + # Format dates. + $self->format_dates($output_dateformat, $output_longdates, + qw(invdate orddate quodate pldate duedate reqdate transdate shippingdate deliverydate validitydate paymentdate datepaid + transdate_oe deliverydate_oe employee_startdate employee_enddate), + grep({ /^(?:datepaid|transdate_oe|reqdate|deliverydate|deliverydate_oe|transdate)_\d+$/ } keys(%{$self}))); + + $self->reformat_numbers($output_numberformat, 2, + qw(invtotal ordtotal quototal subtotal linetotal listprice sellprice netprice discount tax taxbase total paid), + grep({ /^(?:linetotal|listprice|sellprice|netprice|taxbase|discount|paid|subtotal|total|tax)_\d+$/ } keys(%{$self}))); + + $self->reformat_numbers($output_numberformat, undef, qw(qty price_factor), grep({ /^qty_\d+$/} keys(%{$self}))); + + my ($cvar_date_fields, $cvar_number_fields) = CVar->get_field_format_list('module' => 'CT', 'prefix' => 'vc_'); + + if (scalar @{ $cvar_date_fields }) { + $self->format_dates($output_dateformat, $output_longdates, @{ $cvar_date_fields }); + } + + while (my ($precision, $field_list) = each %{ $cvar_number_fields }) { + $self->reformat_numbers($output_numberformat, $precision, @{ $field_list }); + } + + return $self; +} + +sub format_dates { + my ($self, $dateformat, $longformat, @indices) = @_; + + $dateformat ||= $::myconfig{dateformat}; + + foreach my $idx (@indices) { + if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) { + for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) { + $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $dateformat, $longformat); + } + } + + next unless defined $self->{$idx}; + + if (!ref($self->{$idx})) { + $self->{$idx} = $::locale->reformat_date(\%::myconfig, $self->{$idx}, $dateformat, $longformat); + + } elsif (ref($self->{$idx}) eq "ARRAY") { + for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) { + $self->{$idx}->[$i] = $::locale->reformat_date(\%::myconfig, $self->{$idx}->[$i], $dateformat, $longformat); + } + } + } +} + +sub reformat_numbers { + my ($self, $numberformat, $places, @indices) = @_; + + return if !$numberformat || ($numberformat eq $::myconfig{numberformat}); + + foreach my $idx (@indices) { + if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) { + for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) { + $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i]); + } + } + + next unless defined $self->{$idx}; + + if (!ref($self->{$idx})) { + $self->{$idx} = $self->parse_amount(\%::myconfig, $self->{$idx}); + + } elsif (ref($self->{$idx}) eq "ARRAY") { + for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) { + $self->{$idx}->[$i] = $self->parse_amount(\%::myconfig, $self->{$idx}->[$i]); + } + } + } + + my $saved_numberformat = $::myconfig{numberformat}; + $::myconfig{numberformat} = $numberformat; + + foreach my $idx (@indices) { + if ($self->{TEMPLATE_ARRAYS} && (ref($self->{TEMPLATE_ARRAYS}->{$idx}) eq "ARRAY")) { + for (my $i = 0; $i < scalar(@{ $self->{TEMPLATE_ARRAYS}->{$idx} }); $i++) { + $self->{TEMPLATE_ARRAYS}->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{TEMPLATE_ARRAYS}->{$idx}->[$i], $places); + } + } + + next unless defined $self->{$idx}; + + if (!ref($self->{$idx})) { + $self->{$idx} = $self->format_amount(\%::myconfig, $self->{$idx}, $places); + + } elsif (ref($self->{$idx}) eq "ARRAY") { + for (my $i = 0; $i < scalar(@{ $self->{$idx} }); $i++) { + $self->{$idx}->[$i] = $self->format_amount(\%::myconfig, $self->{$idx}->[$i], $places); + } + } + } + + $::myconfig{numberformat} = $saved_numberformat; +} + 1; + +__END__ + +=head1 NAME + +SL::Form.pm - main data object. + +=head1 SYNOPSIS + +This is the main data object of Lx-Office. +Unfortunately it also acts as a god object for certain data retrieval procedures used in the entry points. +Points of interest for a beginner are: + + - $form->error - renders a generic error in html. accepts an error message + - $form->get_standard_dbh - returns a database connection for the + +=head1 SPECIAL FUNCTIONS + +=head2 C PARAMS + +PARAMS (not named): + \%config, - config hashref + $business_id, - business id + $dbh - optional database handle + +handles business (thats customer/vendor types) sequences. + +special behaviour for empty strings in customerinitnumber field: +will in this case not increase the value, and return undef. + +=head2 C $url + +Generates a HTTP redirection header for the new C<$url>. Constructs an +absolute URL including scheme, host name and port. If C<$url> is a +relative URL then it is considered relative to Lx-Office base URL. + +This function Cs if headers have already been created with +C<$::form-Eheader>. + +Examples: + + print $::form->redirect_header('oe.pl?action=edit&id=1234'); + print $::form->redirect_header('http://www.lx-office.org/'); + +=head2 C
+ +Generates a general purpose http/html header and includes most of the scripts +and stylesheets needed. Stylesheets can be added with L. + +Only one header will be generated. If the method was already called in this +request it will not output anything and return undef. Also if no +HTTP_USER_AGENT is found, no header is generated. + +Although header does not accept parameters itself, it will honor special +hashkeys of its Form instance: + +=over 4 + +=item refresh_time + +=item refresh_url + +If one of these is set, a http-equiv refresh is generated. Missing parameters +default to 3 seconds and the refering url. + +=item stylesheet + +Either a scalar or an array ref. Will be inlined into the header. Add +stylesheets with the L function. + +=item landscape + +If true, a css snippet will be generated that sets the page in landscape mode. + +=item favicon + +Used to override the default favicon. + +=item title + +A html page title will be generated from this + +=back + +=cut