multibox part 2.
[kivitendo-erp.git] / SL / Form.pm
index c578b29..56e46f7 100644 (file)
@@ -54,6 +54,7 @@ use SL::Menu;
 use SL::Template;
 use SL::User;
 use Template;
+use URI;
 use List::Util qw(first max min sum);
 use List::MoreUtils qw(any);
 
@@ -62,10 +63,13 @@ use strict;
 my $standard_dbh;
 
 END {
-  if ($standard_dbh) {
-    $standard_dbh->disconnect();
-    undef $standard_dbh;
-  }
+  disconnect_standard_dbh();
+}
+
+sub disconnect_standard_dbh {
+  return unless $standard_dbh;
+  $standard_dbh->disconnect();
+  undef $standard_dbh;
 }
 
 sub _store_value {
@@ -203,7 +207,10 @@ sub _recode_recursively {
   if (any { ref $param eq $_ } qw(Form HASH)) {
     foreach my $key (keys %{ $param }) {
       if (!ref $param->{$key}) {
-        $param->{$key} = $iconv->convert($param->{$key});
+        # Workaround for a bug: converting $param->{$key} directly
+        # leads to 'undef'. I don't know why. Converting a copy works,
+        # though.
+        $param->{$key} = $iconv->convert("" . $param->{$key});
       } else {
         _recode_recursively($iconv, $param->{$key});
       }
@@ -212,7 +219,10 @@ sub _recode_recursively {
   } elsif (ref $param eq 'ARRAY') {
     foreach my $idx (0 .. scalar(@{ $param }) - 1) {
       if (!ref $param->[$idx]) {
-        $param->[$idx] = $iconv->convert($param->[$idx]);
+        # Workaround for a bug: converting $param->[$idx] directly
+        # leads to 'undef'. I don't know why. Converting a copy works,
+        # though.
+        $param->[$idx] = $iconv->convert("" . $param->[$idx]);
       } else {
         _recode_recursively($iconv, $param->[$idx]);
       }
@@ -233,38 +243,33 @@ sub new {
     tie %{ $self }, 'SL::Watchdog';
   }
 
-  read(STDIN, $_, $ENV{CONTENT_LENGTH});
+  bless $self, $type;
 
-  if ($ENV{QUERY_STRING}) {
-    $_ = $ENV{QUERY_STRING};
-  }
+  $self->_input_to_hash($ENV{QUERY_STRING}) if $ENV{QUERY_STRING};
+  $self->_input_to_hash($ARGV[0])           if @ARGV && $ARGV[0];
 
-  if ($ARGV[0]) {
-    $_ = $ARGV[0];
+  if ($ENV{CONTENT_LENGTH}) {
+    my $content;
+    read STDIN, $content, $ENV{CONTENT_LENGTH};
+    $self->_request_to_hash($content);
   }
 
-  bless $self, $type;
-
-  $self->_request_to_hash($_);
-
   my $db_charset   = $main::dbcharset;
   $db_charset    ||= Common::DEFAULT_CHARSET;
 
-  if ($self->{INPUT_ENCODING}) {
-    if (lc $self->{INPUT_ENCODING} ne lc $db_charset) {
-      require Text::Iconv;
-      my $iconv = Text::Iconv->new($self->{INPUT_ENCODING}, $db_charset);
-
-      _recode_recursively($iconv, $self);
-    }
+  my $encoding     = $self->{INPUT_ENCODING} || $db_charset;
+  delete $self->{INPUT_ENCODING};
 
-    delete $self->{INPUT_ENCODING};
-  }
+  _recode_recursively(SL::Iconv->new($encoding, $db_charset), $self);
 
   $self->{action}  =  lc $self->{action};
   $self->{action}  =~ s/( |-|,|\#)/_/g;
 
-  $self->{version} =  "2.6.0";
+  #$self->{version} =  "2.6.1";                 # Old hardcoded but secure style
+  open VERSION_FILE, "VERSION";                 # New but flexible code reads version from VERSION-file
+  $self->{version} =  <VERSION_FILE>;
+  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();
 
@@ -448,8 +453,8 @@ sub error {
     $self->show_generic_error($msg);
 
   } else {
-
-    die "Error: $msg\n";
+    print STDERR "Error: $msg\n";
+    ::end_of_request();
   }
 
   $main::lxdebug->leave_sub();
@@ -465,13 +470,22 @@ sub info {
 
     if (!$self->{header}) {
       $self->header;
-      print qq|
-      <body>|;
+      print qq|<body>|;
     }
 
     print qq|
+    <p class="message_ok"><b>$msg</b></p>
 
-    <p><b>$msg</b>
+    <script type="text/javascript">
+    <!--
+    // If JavaScript is enabled, the whole thing will be reloaded.
+    // The reason is: When one changes his menu setup (HTML / XUL / CSS ...)
+    // it now loads the correct code into the browser instead of do nothing.
+    setTimeout("top.frames.location.href='login.pl'",500);
+    //-->
+    </script>
+
+</body>
     |;
 
   } else {
@@ -528,6 +542,40 @@ sub isblank {
   $main::lxdebug->leave_sub();
 }
 
+sub _get_request_uri {
+  my $self = shift;
+
+  return URI->new($ENV{HTTP_REFERER})->canonical() if $ENV{HTTP_X_FORWARDED_FOR};
+
+  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 $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('');
+
+  return $uri;
+}
+
+sub _add_to_request_uri {
+  my $self              = shift;
+
+  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;
+
+  my $new_uri           = $request_uri->clone;
+  $new_uri->path_segments(@request_segments[0..scalar(@request_segments) - 2], $relative_new_uri->path_segments);
+
+  return $new_uri;
+}
+
 sub create_http_response {
   $main::lxdebug->enter_sub();
 
@@ -537,25 +585,19 @@ sub create_http_response {
   my $cgi      = $main::cgi;
   $cgi       ||= CGI->new('');
 
-  my $base_path;
-
-  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 $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();
     $session_cookie_value    ||= 'NO_SESSION';
 
     $session_cookie = $cgi->cookie('-name'   => $main::auth->get_session_cookie_name(),
                                    '-value'  => $session_cookie_value,
-                                   '-path'   => $base_path,
+                                   '-path'   => $uri->path,
                                    '-secure' => $ENV{HTTPS});
   }
 
@@ -630,13 +672,26 @@ sub header {
     </script>
     | if $self->{"fokus"};
 
+  # 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|
+               <script type="text/javascript">
+               <!--
+                 // Write a meaningful title-tag for our frameset.
+                 top.document.title="| . $self->{"title"} . qq| - | . $self->{"login"} . qq| - | . $::myconfig{dbname} . qq| - V| . $self->{"version"} . qq|";
+               //-->
+               </script>
+               |;
+       }
+
     #Set Calendar
     my $jsscript = "";
     if ($self->{jsscript} == 1) {
 
       $jsscript = qq|
         <script type="text/javascript" src="js/jquery.js"></script>
-        <script type='text/javascript' src='js/jquery.autocomplete.js'></script>
         <script type="text/javascript" src="js/common.js"></script>
         <style type="text/css">\@import url(js/jscalendar/calendar-win2k-1.css);</style>
         <script type="text/javascript" src="js/jscalendar/calendar.js"></script>
@@ -651,7 +706,7 @@ sub header {
       ? "$self->{title} - $self->{titlebar}"
       : $self->{titlebar};
     my $ajax = "";
-    foreach my $item (@ { $self->{AJAX} }) {
+    for my $item (@ { $self->{AJAX} || [] }) {
       $ajax .= $item->show_javascript();
     }
 
@@ -666,13 +721,12 @@ sub header {
   $favicon
   $jsscript
   $ajax
-
   $fokus
+  $title_hack
 
   <link rel="stylesheet" href="css/jquery.autocomplete.css" type="text/css" />
 
   <meta name="robots" content="noindex,nofollow" />
-  <script type="text/javascript" src="js/highlight_input.js"></script>
 
   <link rel="stylesheet" type="text/css" href="css/tabcontent.css" />
   <script type="text/javascript" src="js/tabcontent.js">
@@ -709,38 +763,59 @@ sub ajax_response_header {
   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;
+
+  my $cgi = $main::cgi || CGI->new('');
+  return $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"})) {
+  if (!%::myconfig || !$::myconfig{"countrycode"}) {
     $language = $main::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" .
+  if (-f "templates/webpages/${file}.html") {
+    if ((-f ".developer") && ((stat("templates/webpages/${file}.html"))[9] > (stat("locale/${language}/all"))[9])) {
+      my $info = "Developer information: templates/webpages/${file}.html is newer than the translation file locale/${language}/all.\n" .
         "Please re-run 'locales.pl' in 'locale/${language}'.";
       print(qq|<pre>$info</pre>|);
-      die($info);
+      ::end_of_request();
     }
 
-    $file = "templates/webpages/${file}_${language}.html";
-  } elsif (-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|<pre>$info</pre>|);
-    die($info);
+    ::end_of_request();
   }
 
   if ($self->{"DEBUG"}) {
@@ -759,6 +834,7 @@ sub _prepare_html_template {
     $jsc_dateformat =~ s/m+/\%m/gi;
     $jsc_dateformat =~ s/y+/\%Y/gi;
     $additional_params->{"myconfig_jsc_dateformat"} = $jsc_dateformat;
+    $additional_params->{"myconfig"} ||= \%::myconfig;
   }
 
   $additional_params->{"conf_dbcharset"}              = $main::dbcharset;
@@ -766,6 +842,8 @@ sub _prepare_html_template {
   $additional_params->{"conf_lizenzen"}               = $main::lizenzen;
   $additional_params->{"conf_latex_templates"}        = $main::latex;
   $additional_params->{"conf_opendocument_templates"} = $main::opendocument_templates;
+  $additional_params->{"conf_vertreter"}              = $main::vertreter;
+  $additional_params->{"conf_show_best_before"}       = $main::show_best_before;
 
   if (%main::debug_options) {
     map { $additional_params->{'DEBUG_' . uc($_)} = $main::debug_options{$_} } keys %main::debug_options;
@@ -789,41 +867,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 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'  => $::userspath . '/templates-cache',
+  })) || die;
+}
 
-  return $output;
+sub template {
+  my $self = shift;
+  $self->{template_object} = shift if @_;
+  return $self->{template_object};
 }
 
 sub show_generic_error {
@@ -855,9 +932,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 {
@@ -877,7 +956,7 @@ sub show_generic_information {
 
   $main::lxdebug->leave_sub();
 
-  die("Information: $text\n");
+  ::end_of_request();
 }
 
 # write Trigger JavaScript-Code ($qty = quantity of Triggers)
@@ -932,19 +1011,19 @@ sub redirect {
 
   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);
-
-  } else {
+  if (!$self->{callback}) {
 
     $self->info($msg);
-    exit;
+    ::end_of_request();
   }
 
+#  my ($script, $argv) = split(/\?/, $self->{callback}, 2);
+#  $script =~ s|.*/||;
+#  $script =~ s|[^a-zA-Z0-9_\.]||g;
+#  exec("perl", "$script", $argv);
+
+  print $::form->redirect_header($self->{callback});
+
   $main::lxdebug->leave_sub();
 }
 
@@ -1120,13 +1199,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);
@@ -1139,7 +1218,7 @@ sub parse_template {
   $main::lxdebug->enter_sub();
 
   my ($self, $myconfig, $userspath) = @_;
-  my ($template, $out);
+  my $out;
 
   local (*IN, *OUT);
 
@@ -1148,28 +1227,30 @@ sub parse_template {
 
   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);
+    $template_type    = 'LaTeX';
     $ext_for_format   = 'pdf';
 
   } elsif (($self->{"format"} =~ /html/i) || (!$self->{"format"} && ($self->{"IN"} =~ /html$/i))) {
-    $template       = HTMLTemplate->new($self->{"IN"}, $self, $myconfig, $userspath);
+    $template_type  = 'HTML';
     $ext_for_format = 'html';
 
   } elsif (($self->{"format"} =~ /xml/i) || (!$self->{"format"} && ($self->{"IN"} =~ /xml$/i))) {
-    $template       = XMLTemplate->new($self->{"IN"}, $self, $myconfig, $userspath);
+    $template_type  = 'XML';
     $ext_for_format = 'xml';
 
-  } elsif ( $self->{"format"} =~ /elsterwinston/i ) {
-    $template = XMLTemplate->new($self->{"IN"}, $self, $myconfig, $userspath);
+  } elsif ( $self->{"format"} =~ /elster(?:winston|taxbird)/i ) {
+    $template_type = 'xml';
 
-  } elsif ( $self->{"format"} =~ /elstertaxbird/i ) {
-    $template = XMLTemplate->new($self->{"IN"}, $self, $myconfig, $userspath);
+  } 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'}");
@@ -1181,6 +1262,12 @@ sub parse_template {
     $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" };
 
@@ -1213,20 +1300,23 @@ sub parse_template {
     $self->{OUT} = ">$self->{tmpfile}";
   }
 
+  my $result;
+
   if ($self->{OUT}) {
-    open(OUT, "$self->{OUT}") or $self->error("$self->{OUT} : $!");
+    open OUT, "$self->{OUT}" or $self->error("$self->{OUT} : $!");
+    $result = $template->parse(*OUT);
+    close OUT;
+
   } else {
-    open(OUT, ">-") or $self->error("STDOUT : $!");
     $self->header;
+    $result = $template->parse(*STDOUT);
   }
 
-  if (!$template->parse(*OUT)) {
+  if (!$result) {
     $self->cleanup();
     $self->error("$self->{IN} : " . $template->get_error());
   }
 
-  close(OUT);
-
   if ($template->uses_temp_file() || $self->{media} eq 'email') {
 
     if ($self->{media} eq 'email') {
@@ -1290,8 +1380,11 @@ 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} : $!");
+          open OUT, $self->{OUT} or $self->error($self->cleanup . "$self->{OUT} : $!");
+          print OUT while <IN>;
+          close OUT;
+          seek IN, 0, 0;
+
         } else {
           $self->{attachment_filename} = ($self->{attachment_filename})
                                        ? $self->{attachment_filename}
@@ -1304,18 +1397,8 @@ Content-Length: $numbytes
 
 |;
 
-          open(OUT, ">-") or $self->error($self->cleanup . "$!: STDOUT");
-
-        }
-
-        while (<IN>) {
-          print OUT $_;
-
+          $::locale->with_raw_io(\*STDOUT, sub { print while <IN> });
         }
-
-        close(OUT);
-
-        seek IN, 0, 0;
       }
 
       close(IN);
@@ -1378,6 +1461,7 @@ sub get_extension_for_format {
   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"
                 :                                      "";
 
@@ -1438,7 +1522,7 @@ sub cleanup {
     close(FH);
   }
 
-  if ($self->{tmpfile}) {
+  if ($self->{tmpfile} && ! $::keep_temp_files) {
     $self->{tmpfile} =~ s|.*/||g;
     # strip extension
     $self->{tmpfile} =~ s/\.\w+$//g;
@@ -1489,15 +1573,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 = DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options)
     or $self->dberror;
 
   # set db options
@@ -1516,9 +1606,7 @@ sub dbconnect_noauto {
   my ($self, $myconfig) = @_;
 
   # connect to database
-  my $dbh =
-    DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser},
-                 $myconfig->{dbpasswd}, { AutoCommit => 0 })
+  my $dbh = DBI->connect($myconfig->{dbconnect}, $myconfig->{dbuser}, $myconfig->{dbpasswd}, $self->_dbconnect_options(AutoCommit => 0))
     or $self->dberror;
 
   # set db options
@@ -1534,7 +1622,8 @@ 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");
@@ -1732,22 +1821,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();
@@ -1796,6 +1896,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;
 
@@ -1849,6 +1950,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();
 
 }
@@ -2221,9 +2324,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();
 }
@@ -2287,14 +2396,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   = "LIMIT $options->{limit}" if $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();
 }
@@ -2461,11 +2575,7 @@ sub get_lists {
   }
 
   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"}) {
@@ -2559,7 +2669,7 @@ 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";
 
@@ -2567,7 +2677,7 @@ sub all_vc {
   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|;
@@ -2967,7 +3077,9 @@ sub lastname_used {
 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;
@@ -3169,8 +3281,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);
@@ -3183,6 +3295,8 @@ sub save_history {
                 $self->{addition}, $self->{what_done}, "$self->{snumbers}");
   do_query($self, $dbh, $query, @values);
 
+  $dbh->commit;
+
   $main::lxdebug->leave_sub();
 }
 
@@ -3519,6 +3633,20 @@ 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.
 
+=item redirect_header $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 C<die>s if headers have already been created with
+C<$::form-E<gt>header>.
+
+Examples:
+
+  print $::form->redirect_header('oe.pl?action=edit&id=1234');
+  print $::form->redirect_header('http://www.lx-office.org/');
+
 =back
 
 =cut