1 #=====================================================================
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
7 #=====================================================================
8 # SQL-Ledger Accounting
11 # Author: Dieter Simader
12 # Email: dsimader@sql-ledger.org
13 # Web: http://www.sql-ledger.org
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #======================================================================
31 # add/edit/delete users
33 #======================================================================
37 use English qw(-no_match_vars);
45 use POSIX qw(strftime);
49 use SL::Auth::PasswordPolicy;
50 use SL::DB::AuthClient;
62 require "bin/mozilla/common.pl";
63 require "bin/mozilla/admin_groups.pl";
64 require "bin/mozilla/admin_printer.pl";
70 # $locale->text('periodic')
71 # $locale->text('income')
72 # $locale->text('perpetual')
73 # $locale->text('balance')
81 $::lxdebug->enter_sub;
82 my $session_result = shift;
88 $::request->{layout} = SL::Layout::Dispatcher->new(style => 'admin');
89 $::request->{layout}->use_stylesheet("lx-office-erp.css");
90 $form->{favicon} = "favicon.ico";
92 if ($form->{action}) {
93 if ($auth->authenticate_root($form->{'{AUTH}admin_password'}) != $auth->OK()) {
94 $auth->punish_wrong_login;
95 $form->{error} = $locale->text('Incorrect Password!');
96 $auth->delete_session_value('admin_password');
99 if ($auth->session_tables_present()) {
100 delete $::form->{'{AUTH}admin_password'};
103 call_sub($locale->findsub($form->{action}));
106 # if there are no drivers bail out
107 $form->error($locale->text('No Database Drivers available!'))
108 unless (User->dbdrivers);
112 $::lxdebug->leave_sub;
116 my $form = $main::form;
117 my $locale = $main::locale;
119 $form->{title} = qq|kivitendo $form->{version} | . $locale->text('Administration');
122 print $form->parse_html_template('admin/adminlogin');
129 return ($login) ? $login : undef;
135 my ($null, $value) = split(/=/, $line, 2);
138 $value =~ s/\s#.*//g;
140 # remove any trailing whitespace
141 $value =~ s/^\s*(.*?)\s*$/$1/;
146 sub pg_database_administration {
147 my $form = $main::form;
149 $form->{dbdriver} = 'Pg';
154 sub dbselect_source {
155 my $form = $main::form;
156 my $locale = $main::locale;
158 $form->{dbport} = $::auth->{DB_config}->{port} || 5432;
159 $form->{dbuser} = $::auth->{DB_config}->{user} || 'lxoffice';
160 $form->{dbdefault} = 'template1';
161 $form->{dbhost} = $::auth->{DB_config}->{host} || 'localhost';
163 $form->{title} = "kivitendo / " . $locale->text('Database Administration');
165 # Intentionnaly disabled unless fixed to work with the authentication DB.
166 $form->{ALLOW_DBBACKUP} = 0; # "$pg_dump_exe" ne "DISABLED";
169 print $form->parse_html_template("admin/dbadmin");
172 sub test_db_connection {
173 my $form = $main::form;
174 my $locale = $main::locale;
176 $form->{dbdriver} = 'Pg';
177 User::dbconnect_vars($form, $form->{dbname});
179 my $dbh = DBI->connect($form->{dbconnect}, $form->{dbuser}, $form->{dbpasswd});
181 $form->{connection_ok} = $dbh ? 1 : 0;
182 $form->{errstr} = $DBI::errstr;
184 $dbh->disconnect() if ($dbh);
186 $form->{title} = $locale->text('Database Connection Test');
188 print $form->parse_html_template("admin/test_db_connection");
192 call_sub($main::form->{"nextsub"});
196 my $form = $main::form;
197 my $locale = $main::locale;
199 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Update Dataset');
201 my @need_updates = User->dbneedsupdate($form);
202 $form->{NEED_UPDATES} = \@need_updates;
203 $form->{ALL_UPDATED} = !scalar @need_updates;
206 print $form->parse_html_template("admin/update_dataset");
210 my $form = $main::form;
211 my $locale = $main::locale;
213 $::request->{layout}->use_stylesheet("lx-office-erp.css");
214 $form->{title} = $locale->text("Dataset upgrade");
217 my $rowcount = $form->{rowcount} * 1;
218 my @update_rows = grep { $form->{"update_$_"} } (1 .. $rowcount);
219 $form->{NOTHING_TO_DO} = !scalar @update_rows;
220 my $saved_form = save_form();
224 print $form->parse_html_template("admin/dbupgrade_all_header");
226 foreach my $i (@update_rows) {
227 restore_form($saved_form);
230 map { $form->{$_} = $::myconfig{$_} = $form->{"${_}_${i}"} } qw(dbname dbdriver dbhost dbport dbuser dbpasswd);
232 print $form->parse_html_template("admin/dbupgrade_header");
234 $form->{dbupdate} = $form->{dbname};
235 $form->{$form->{dbname}} = 1;
237 User->dbupdate($form);
238 User->dbupdate2($form, SL::DBUpgrade2->new(form => $form, dbdriver => $form->{dbdriver})->parse_dbupdate_controls);
240 print $form->parse_html_template("admin/dbupgrade_footer");
243 print $form->parse_html_template("admin/dbupgrade_all_done");
247 my $form = $main::form;
248 my $locale = $main::locale;
250 $form->{dbsources} = join " ", map { "[${_}]" } sort User->dbsources($form);
252 $form->{CHARTS} = [];
254 tie my %dir_h, 'IO::Dir', 'sql/';
255 foreach my $item (map { s/-chart\.sql$//; $_ } sort grep { /-chart\.sql\z/ && !/Default-chart.sql\z/ } keys %dir_h) {
256 push @{ $form->{CHARTS} }, { name => $item,
257 selected => $item eq "Germany-DATEV-SKR03EU" };
260 $form->{ACCOUNTING_METHODS} = [ map { { name => $_, selected => $_ eq 'cash' } } qw(accrual cash) ];
261 $form->{INVENTORY_SYSTEMS} = [ map { { name => $_, selected => $_ eq 'periodic' } } qw(perpetual periodic) ];
262 $form->{PROFIT_DETERMINATIONS} = [ map { { name => $_, selected => $_ eq 'income' } } qw(balance income) ];
264 my $default_charset = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
266 my $cluster_encoding = User->dbclusterencoding($form);
267 if ($cluster_encoding && ($cluster_encoding =~ m/^(?:UTF-?8|UNICODE)$/i)) {
268 if ($::lx_office_conf{system}->{dbcharset} !~ m/^UTF-?8$/i) {
269 $form->show_generic_error($locale->text('The selected PostgreSQL installation uses UTF-8 as its encoding. ' .
270 'Therefore you have to configure kivitendo to use UTF-8 as well.'),
274 $form->{FORCE_DBENCODING} = 'UNICODE';
277 $form->{DBENCODINGS} = [ map { { %{$_}, selected => $_->{charset} eq $default_charset } } @Common::db_encodings ];
280 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Create Dataset');
283 print $form->parse_html_template("admin/create_dataset");
287 my $form = $main::form;
288 my $locale = $main::locale;
290 $form->isblank("db", $locale->text('Dataset missing!'));
291 $form->isblank("defaultcurrency", $locale->text('Default currency missing!'));
293 User->dbcreate(\%$form);
295 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Create Dataset');
298 print $form->parse_html_template("admin/dbcreate");
302 my $form = $main::form;
303 my $locale = $main::locale;
305 my @dbsources = User->dbsources_unused($form);
306 $form->error($locale->text('Nothing to delete!')) unless @dbsources;
308 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Delete Dataset');
309 $form->{DBSOURCES} = [ map { { "name", $_ } } sort @dbsources ];
312 print $form->parse_html_template("admin/delete_dataset");
316 my $form = $main::form;
317 my $locale = $main::locale;
320 $form->error($locale->text('No Dataset selected!'));
323 User->dbdelete(\%$form);
325 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Delete Dataset');
327 print $form->parse_html_template("admin/dbdelete");
331 my $form = $main::form;
332 my $locale = $main::locale;
334 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Backup Dataset');
336 if ($::lx_office_conf{applications}->{pg_dump} eq "DISABLED") {
337 $form->error($locale->text('Database backups and restorations are disabled in the configuration.'));
340 my @dbsources = sort User->dbsources($form);
341 $form->{DATABASES} = [ map { { "dbname" => $_ } } @dbsources ];
342 $form->{NO_DATABASES} = !scalar @dbsources;
344 my $username = getpwuid $UID || "unknown-user";
345 my $hostname = hostname() || "unknown-host";
346 $form->{from} = "kivitendo Admin <${username}\@${hostname}>";
349 print $form->parse_html_template("admin/backup_dataset");
352 sub backup_dataset_start {
353 my $form = $main::form;
354 my $locale = $main::locale;
356 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Backup Dataset');
358 my $pg_dump_exe = $::lx_office_conf{applications}->{pg_dump} || "pg_dump";
360 if ("$pg_dump_exe" eq "DISABLED") {
361 $form->error($locale->text('Database backups and restorations are disabled in the configuration.'));
364 $form->isblank("dbname", $locale->text('The dataset name is missing.'));
365 $form->isblank("to", $locale->text('The email address is missing.')) if $form->{destination} eq "email";
367 my $tmpdir = "/tmp/lx_office_backup_" . Common->unique_id();
368 mkdir $tmpdir, 0700 || $form->error($locale->text('A temporary directory could not be created:') . " $ERRNO");
370 my $pgpass = IO::File->new("${tmpdir}/.pgpass", O_WRONLY | O_CREAT, 0600);
374 $form->error($locale->text('A temporary file could not be created:') . " $ERRNO");
377 print $pgpass "$form->{dbhost}:$form->{dbport}:$form->{dbname}:$form->{dbuser}:$form->{dbpasswd}\n";
380 $ENV{HOME} = $tmpdir;
382 my @args = ("-Ft", "-c", "-o", "-h", $form->{dbhost}, "-U", $form->{dbuser});
383 push @args, ("-p", $form->{dbport}) if ($form->{dbport});
384 push @args, $form->{dbname};
386 my $cmd = "$pg_dump_exe " . join(" ", map { s/\\/\\\\/g; s/\"/\\\"/g; $_ } @args);
387 my $name = "dataset_backup_$form->{dbname}_" . strftime("%Y%m%d", localtime()) . ".tar";
389 if ($form->{destination} ne "email") {
390 my $in = IO::File->new("$cmd |");
393 unlink "${tmpdir}/.pgpass";
396 $form->error($locale->text('The pg_dump process could not be started.'));
399 print "content-type: application/x-tar\n";
400 print "content-disposition: attachment; filename=\"${name}\"\n\n";
402 while (my $line = <$in>) {
408 unlink "${tmpdir}/.pgpass";
412 my $tmp = $tmpdir . "/dump_" . Common::unique_id();
414 if (system("$cmd > $tmp") != 0) {
415 unlink "${tmpdir}/.pgpass", $tmp;
418 $form->error($locale->text('The pg_dump process could not be started.'));
421 my $mail = new Mailer;
423 map { $mail->{$_} = $form->{$_} } qw(from to cc subject message);
425 $mail->{charset} = $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET;
426 $mail->{attachments} = [ { "filename" => $tmp, "name" => $name } ];
429 unlink "${tmpdir}/.pgpass", $tmp;
432 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Backup Dataset');
435 print $form->parse_html_template("admin/backup_dataset_email_done");
439 sub restore_dataset {
440 my $form = $main::form;
441 my $locale = $main::locale;
443 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Restore Dataset');
445 if ($::lx_office_conf{applications}->{pg_restore} eq "DISABLED") {
446 $form->error($locale->text('Database backups and restorations are disabled in the configuration.'));
449 my $default_charset = $::lx_office_conf{system}->{dbcharset};
450 $default_charset ||= Common::DEFAULT_CHARSET;
452 $form->{DBENCODINGS} = [];
454 foreach my $encoding (@Common::db_encodings) {
455 push @{ $form->{DBENCODINGS} }, { "dbencoding" => $encoding->{dbencoding},
456 "label" => $encoding->{label},
457 "selected" => $encoding->{charset} eq $default_charset };
461 print $form->parse_html_template("admin/restore_dataset");
464 sub restore_dataset_start {
465 my $form = $main::form;
466 my $locale = $main::locale;
468 $form->{title} = "kivitendo " . $locale->text('Database Administration') . " / " . $locale->text('Restore Dataset');
470 my $pg_restore_exe = $::lx_office_conf{applications}->{pg_restore} || "pg_restore";
472 if ("$pg_restore_exe" eq "DISABLED") {
473 $form->error($locale->text('Database backups and restorations are disabled in the configuration.'));
476 $form->isblank("new_dbname", $locale->text('The dataset name is missing.'));
477 $form->isblank("content", $locale->text('No backup file has been uploaded.'));
479 # Create temporary directories. Write the backup file contents to a temporary
480 # file. Create a .pgpass file with the username and password for the pg_restore
483 my $tmpdir = "/tmp/lx_office_backup_" . Common->unique_id();
484 mkdir $tmpdir, 0700 || $form->error($locale->text('A temporary directory could not be created:') . " $ERRNO");
486 my $pgpass = IO::File->new("${tmpdir}/.pgpass", O_WRONLY | O_CREAT, 0600);
490 $form->error($locale->text('A temporary file could not be created:') . " $ERRNO");
493 print $pgpass "$form->{dbhost}:$form->{dbport}:$form->{new_dbname}:$form->{dbuser}:$form->{dbpasswd}\n";
496 $ENV{HOME} = $tmpdir;
498 my $tmp = $tmpdir . "/dump_" . Common::unique_id();
501 if (substr($form->{content}, 0, 2) eq "\037\213") {
502 $tmpfile = IO::File->new("| gzip -d > $tmp");
506 $tmpfile = IO::File->new($tmp, O_WRONLY | O_CREAT | O_BINARY, 0600);
510 unlink "${tmpdir}/.pgpass";
513 $form->error($locale->text('A temporary file could not be created:') . " $ERRNO");
516 print $tmpfile $form->{content};
519 delete $form->{content};
521 # Try to connect to the database. Find out if a database with the same name exists.
522 # If yes, then drop the existing database. Create a new one with the name and encoding
525 User::dbconnect_vars($form, "template1");
527 my %myconfig = map { $_ => $form->{$_} } grep /^db/, keys %{ $form };
528 my $dbh = $form->dbconnect(\%myconfig) || $form->dberror();
532 $form->{new_dbname} =~ s|[^a-zA-Z0-9_\-]||g;
534 $query = qq|SELECT COUNT(*) FROM pg_database WHERE datname = ?|;
535 my ($count) = selectrow_query($form, $dbh, $query, $form->{new_dbname});
537 do_query($form, $dbh, qq|DROP DATABASE $form->{new_dbname}|);
541 foreach my $item (@Common::db_encodings) {
542 if ($item->{dbencoding} eq $form->{dbencoding}) {
547 $form->{dbencoding} = "LATIN9" unless $form->{dbencoding};
549 do_query($form, $dbh, qq|CREATE DATABASE $form->{new_dbname} ENCODING ? TEMPLATE template0|, $form->{dbencoding});
553 # Spawn pg_restore on the temporary file.
555 my @args = ("-h", $form->{dbhost}, "-U", $form->{dbuser}, "-d", $form->{new_dbname});
556 push @args, ("-p", $form->{dbport}) if ($form->{dbport});
559 my $cmd = "$pg_restore_exe " . join(" ", map { s/\\/\\\\/g; s/\"/\\\"/g; $_ } @args);
561 my $in = IO::File->new("$cmd 2>&1 |");
564 unlink "${tmpdir}/.pgpass", $tmp;
567 $form->error($locale->text('The pg_restore process could not be started.'));
570 $English::AUTOFLUSH = 1;
573 print $form->parse_html_template("admin/restore_dataset_start_header");
575 while (my $line = <$in>) {
580 $form->{retval} = $CHILD_ERROR >> 8;
581 print $form->parse_html_template("admin/restore_dataset_start_footer");
583 unlink "${tmpdir}/.pgpass", $tmp;
588 call_sub($main::form->{yes_nextsub});
592 call_sub($main::form->{no_nextsub});
596 call_sub($main::form->{add_nextsub});
600 my $form = $main::form;
602 $form->{edit_nextsub} ||= 'edit_user';
604 call_sub($form->{edit_nextsub});
608 my $form = $main::form;
610 $form->{delete_nextsub} ||= 'delete_user';
612 call_sub($form->{delete_nextsub});
616 my $form = $main::form;
618 $form->{save_nextsub} ||= 'save_user';
620 call_sub($form->{save_nextsub});
624 call_sub($main::form->{back_nextsub});
628 my $form = $main::form;
629 my $locale = $main::locale;
631 foreach my $action (qw(create_standard_group dont_create_standard_group
632 save_user delete_user save_user_as_new)) {
633 if ($form->{"action_${action}"}) {
639 call_sub($form->{default_action}) if ($form->{default_action});
641 $form->error($locale->text('No action defined.'));
644 sub _search_templates {
645 my %templates = SL::Template->available_templates;
647 return ($templates{print_templates}, $templates{master_templates});