X-Git-Url: http://git.rot13.org/?p=BackupPC.git;a=blobdiff_plain;f=lib%2FBackupPC%2FCGI%2FEditConfig.pm;h=edc4526d0699fb04ccab4b218db5a07b47ec946d;hp=92f9cce2f7d4da17d8fd45c974519694dd1c138f;hb=fda25dc88a63ccac1c80efa2e4994bf0725ca9b7;hpb=fad8dd470868fb4db332cb8214852783e8c6e75b diff --git a/lib/BackupPC/CGI/EditConfig.pm b/lib/BackupPC/CGI/EditConfig.pm index 92f9cce..edc4526 100644 --- a/lib/BackupPC/CGI/EditConfig.pm +++ b/lib/BackupPC/CGI/EditConfig.pm @@ -10,7 +10,7 @@ # Craig Barratt # # COPYRIGHT -# Copyright (C) 2004 Craig Barratt +# Copyright (C) 2005-2007 Craig Barratt # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ # #======================================================================== # -# Version 2.1.0beta2pl1, released 30 May 2004. +# Version 3.1.0, released 25 Nov 2007. # # See http://backuppc.sourceforge.net. # @@ -41,34 +41,35 @@ use BackupPC::CGI::Lib qw(:all); use BackupPC::Config::Meta qw(:all); use BackupPC::Storage; use Data::Dumper; +use Encode; our %ConfigMenu = ( server => { - text => "Server", + text => "CfgEdit_Title_Server", param => [ - {text => "General Parameters"}, + {text => "CfgEdit_Title_General_Parameters"}, {name => "ServerHost"}, {name => "BackupPCUser"}, {name => "BackupPCUserVerify"}, {name => "MaxOldLogFiles"}, {name => "TrashCleanSleepSec"}, - {text => "Wakeup Schedule"}, + {text => "CfgEdit_Title_Wakeup_Schedule"}, {name => "WakeupSchedule"}, - {text => "Concurrent Jobs"}, + {text => "CfgEdit_Title_Concurrent_Jobs"}, {name => "MaxBackups"}, {name => "MaxUserBackups"}, {name => "MaxPendingCmds"}, {name => "MaxBackupPCNightlyJobs"}, {name => "BackupPCNightlyPeriod"}, - {text => "Pool Filesystem Limits"}, + {text => "CfgEdit_Title_Pool_Filesystem_Limits"}, {name => "DfCmd"}, {name => "DfMaxUsagePct"}, {name => "HardLinkMax"}, - {text => "Other Parameters"}, + {text => "CfgEdit_Title_Other_Parameters"}, {name => "UmaskMode"}, {name => "MyPath"}, {name => "DHCPAddressRanges"}, @@ -76,11 +77,11 @@ our %ConfigMenu = ( {name => "ServerInitdPath"}, {name => "ServerInitdStartCmd"}, - {text => "Remote Apache Settings"}, + {text => "CfgEdit_Title_Remote_Apache_Settings"}, {name => "ServerPort"}, {name => "ServerMesgSecret"}, - {text => "Program Paths"}, + {text => "CfgEdit_Title_Program_Paths"}, {name => "SshPath"}, {name => "NmbLookupPath"}, {name => "PingPath"}, @@ -91,22 +92,35 @@ our %ConfigMenu = ( {name => "GzipPath"}, {name => "Bzip2Path"}, - {text => "Install Paths"}, + {text => "CfgEdit_Title_Install_Paths"}, + # + # Can only edit TopDir and LogDir if we are in FHS mode. + # Otherwise they are hardcoded in lib/BackupPC/Lib.pm. + # + {name => "TopDir", + visible => sub { return $_[1]->useFHS(); } }, + {name => "LogDir", + visible => sub { return $_[1]->useFHS(); } }, {name => "CgiDir"}, - {name => "InstallDir"}, + # + # Cannot edit ConfDir or InstallDir, since the real value is hardcoded in + # lib/BackupPC/Lib.pm. + # {name => "ConfDir"}, + # {name => "InstallDir"}, + # ], }, email => { - text => "Email", + text => "CfgEdit_Title_Email", param => [ - {text => "Email settings"}, + {text => "CfgEdit_Title_Email_settings"}, {name => "SendmailPath"}, {name => "EMailNotifyMinDays"}, {name => "EMailFromUserName"}, {name => "EMailAdminUserName"}, {name => "EMailUserDestDomain"}, - {text => "Email User Messages"}, + {text => "CfgEdit_Title_Email_User_Messages"}, {name => "EMailNoBackupEverSubj"}, {name => "EMailNoBackupEverMesg"}, {name => "EMailNotifyOldBackupDays"}, @@ -115,19 +129,17 @@ our %ConfigMenu = ( {name => "EMailNotifyOldOutlookDays"}, {name => "EMailOutlookBackupSubj"}, {name => "EMailOutlookBackupMesg"}, + {name => "EMailHeaders"}, ], }, cgi => { - text => "CGI", + text => "CfgEdit_Title_CGI", param => [ - {text => "Admin Privileges"}, + {text => "CfgEdit_Title_Admin_Privileges"}, {name => "CgiAdminUserGroup"}, {name => "CgiAdminUsers"}, - {text => "Config Editing"}, - {name => "CgiUserConfigEdit"}, - - {text => "Page Rendering"}, + {text => "CfgEdit_Title_Page_Rendering"}, {name => "Language"}, {name => "CgiNavBarAdminAllHosts"}, {name => "CgiSearchBoxEnable"}, @@ -138,25 +150,30 @@ our %ConfigMenu = ( {name => "CgiExt2ContentType"}, {name => "CgiCSSFile"}, - {text => "Paths"}, + {text => "CfgEdit_Title_Paths"}, {name => "CgiURL"}, {name => "CgiImageDir"}, {name => "CgiImageDirURL"}, - {text => "User URLs"}, + {text => "CfgEdit_Title_User_URLs"}, {name => "CgiUserHomePageCheck"}, {name => "CgiUserUrlCreate"}, + {text => "CfgEdit_Title_User_Config_Editing"}, + {name => "CgiUserConfigEditEnable"}, + {name => "CgiUserConfigEdit"}, ], }, xfer => { - text => "Xfer", + text => "CfgEdit_Title_Xfer", param => [ - {text => "Xfer Settings"}, + {text => "CfgEdit_Title_Xfer_Settings"}, {name => "XferMethod", onchangeSubmit => 1}, {name => "XferLogLevel"}, + {name => "ClientCharset"}, + {name => "ClientCharsetLegacy"}, - {text => "Smb Settings", + {text => "CfgEdit_Title_Smb_Settings", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, {name => "SmbShareName", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, @@ -165,17 +182,19 @@ our %ConfigMenu = ( {name => "SmbSharePasswd", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, - {text => "Tar Settings", + {text => "CfgEdit_Title_Tar_Settings", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, {name => "TarShareName", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, - {text => "Rsync Settings", + {text => "CfgEdit_Title_Rsync_Settings", visible => sub { return $_[0]->{XferMethod} eq "rsync"; } }, - {text => "Rsyncd Settings", + {text => "CfgEdit_Title_Rsyncd_Settings", visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } }, {name => "RsyncShareName", visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } }, + {name => "RsyncdUserName", + visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } }, {name => "RsyncdPasswd", visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } }, {name => "RsyncdAuthRequired", @@ -183,7 +202,18 @@ our %ConfigMenu = ( {name => "RsyncCsumCacheVerifyProb", visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } }, - {text => "Archive Settings", + {text => "CfgEdit_Title_BackupPCd_Settings", + visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } }, + {name => "BackupPCdShareName", + visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } }, + {name => "BackupPCdPath", + visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } }, + {name => "BackupPCdCmd", + visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } }, + {name => "BackupPCdRestoreCmd", + visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } }, + + {text => "CfgEdit_Title_Archive_Settings", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, {name => "ArchiveDest", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, @@ -194,14 +224,14 @@ our %ConfigMenu = ( {name => "ArchiveSplit", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, - {text => "Include/Exclude", + {text => "CfgEdit_Title_Include_Exclude", visible => sub { return $_[0]->{XferMethod} ne "archive"; } }, {name => "BackupFilesOnly", visible => sub { return $_[0]->{XferMethod} ne "archive"; } }, {name => "BackupFilesExclude", visible => sub { return $_[0]->{XferMethod} ne "archive"; } }, - {text => "Smb Paths/Commands", + {text => "CfgEdit_Title_Smb_Paths_Commands", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, {name => "SmbClientPath", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, @@ -212,7 +242,7 @@ our %ConfigMenu = ( {name => "SmbClientRestoreCmd", visible => sub { return $_[0]->{XferMethod} eq "smb"; } }, - {text => "Tar Paths/Commands", + {text => "CfgEdit_Title_Tar_Paths_Commands", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, {name => "TarClientPath", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, @@ -225,9 +255,9 @@ our %ConfigMenu = ( {name => "TarClientRestoreCmd", visible => sub { return $_[0]->{XferMethod} eq "tar"; } }, - {text => "Rsync Paths/Commands/Args", + {text => "CfgEdit_Title_Rsync_Paths_Commands_Args", visible => sub { return $_[0]->{XferMethod} eq "rsync"; } }, - {text => "Rsyncd Port/Args", + {text => "CfgEdit_Title_Rsyncd_Port_Args", visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } }, {name => "RsyncClientPath", visible => sub { return $_[0]->{XferMethod} eq "rsync"; } }, @@ -242,7 +272,7 @@ our %ConfigMenu = ( {name => "RsyncRestoreArgs", visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } }, - {text => "Archive Paths/Commands", + {text => "CfgEdit_Title_Archive_Paths_Commands", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, {name => "ArchiveClientCmd", visible => sub { return $_[0]->{XferMethod} eq "archive"; } }, @@ -250,27 +280,29 @@ our %ConfigMenu = ( ], }, schedule => { - text => "Schedule", + text => "CfgEdit_Title_Schedule", param => [ - {text => "Full Backups"}, + {text => "CfgEdit_Title_Full_Backups"}, {name => "FullPeriod"}, {name => "FullKeepCnt"}, {name => "FullKeepCntMin"}, {name => "FullAgeMax"}, - {text => "Incremental Backups"}, + {text => "CfgEdit_Title_Incremental_Backups"}, {name => "IncrPeriod"}, {name => "IncrKeepCnt"}, {name => "IncrKeepCntMin"}, {name => "IncrAgeMax"}, + {name => "IncrLevels"}, {name => "IncrFill"}, - {text => "Blackouts"}, + {text => "CfgEdit_Title_Blackouts"}, + {name => "BackupsDisable"}, {name => "BlackoutBadPingLimit"}, {name => "BlackoutGoodCnt"}, {name => "BlackoutPeriods"}, - {text => "Other"}, + {text => "CfgEdit_Title_Other"}, {name => "PartialAgeMax"}, {name => "RestoreInfoKeepCnt"}, {name => "ArchiveInfoKeepCnt"}, @@ -278,9 +310,9 @@ our %ConfigMenu = ( ], }, backup => { - text => "Backup Settings", + text => "CfgEdit_Title_Backup_Settings", param => [ - {text => "Client Lookup"}, + {text => "CfgEdit_Title_Client_Lookup"}, {name => "ClientNameAlias"}, {name => "NmbLookupCmd"}, {name => "NmbLookupFindHostCmd"}, @@ -288,12 +320,12 @@ our %ConfigMenu = ( {name => "PingCmd"}, {name => "PingMaxMsec"}, - {text => "Other"}, + {text => "CfgEdit_Title_Other"}, {name => "ClientTimeout"}, {name => "MaxOldPerPCLogFiles"}, {name => "CompressLevel"}, - {text => "User Commands"}, + {text => "CfgEdit_Title_User_Commands"}, {name => "DumpPreUserCmd"}, {name => "DumpPostUserCmd"}, {name => "DumpPreShareCmd"}, @@ -302,8 +334,17 @@ our %ConfigMenu = ( {name => "RestorePostUserCmd"}, {name => "ArchivePreUserCmd"}, {name => "ArchivePostUserCmd"}, + {name => "UserCmdCheckStatus"}, ], }, + hosts => { + text => "CfgEdit_Title_Hosts", + param => [ + {text => "CfgEdit_Title_Hosts"}, + {name => "Hosts", + comment => "CfgEdit_Hosts_Comment"}, + ], + }, ); sub action @@ -318,35 +359,44 @@ sub action my $config_path = $host eq "" ? "$TopDir/conf/config.pl" : "$TopDir/pc/$host/config.pl"; - my $Privileged = CheckPermission(); - my $userHost = 1 if ( $Privileged && !$PrivAdmin && defined($host) ); + my $Privileged = CheckPermission($host) + && ($PrivAdmin || $Conf{CgiUserConfigEditEnable}); + my $userHost = 1 if ( defined($host) ); + my $debugText; if ( !$Privileged ) { - #ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_edit_config_files}}")); - ErrorExit("Only_privileged_users_can_edit_config_files"); + ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_edit_config_files}}")); } - if ( defined($In{menu}) || $In{editAction} eq "Save" ) { + if ( defined($In{menu}) || $In{saveAction} eq "Save" ) { $errors = errorCheck(); if ( %$errors ) { # # If there are errors, then go back to the same menu # - $In{editAction} = ""; - $In{newMenu} = ""; + $In{saveAction} = ""; + #$In{newMenu} = ""; } + if ( (my $var = $In{overrideUncheck}) ne "" ) { + # + # a compound variable was unchecked; delete or + # add extra variables to make the shape the same. + # + #print STDERR Dumper(\%In); + foreach my $v ( keys(%In) ) { + if ( $v =~ /^v_zZ_(\Q$var\E(_zZ_.*|$))/ ) { + delete($In{$v}) if ( !defined($In{"orig_zZ_$1"}) ); + } + if ( $v =~ /^orig_zZ_(\Q$var\E(_zZ_.*|$))/ ) { + $In{"v_zZ_$1"} = $In{$v}; + } + } + delete($In{"vflds.$var"}); + } + ($newConf, $override) = inputParse($bpc, $userHost); $override = undef if ( $host eq "" ); - # - # Copy all the orig_ input parameters - # - foreach my $var ( keys(%In) ) { - next if ( $var !~ /^orig_/ ); - $contentHidden .= < -EOF - } } else { # # First time: pick up the current config settings @@ -359,28 +409,14 @@ EOF $override->{$param} = 1; } } else { + my $hostInfo = $bpc->HostInfoRead(); $hostConf = {}; + $mainConf->{Hosts} = [map($hostInfo->{$_}, sort(keys(%$hostInfo)))]; } $newConf = { %$mainConf, %$hostConf }; - - # - # Emit all the original config settings - # - my $doneParam = {}; - foreach my $param ( keys(%ConfigMeta) ) { - next if ( $doneParam->{$param} ); - next if ( $userHost && !$bpc->{Conf}{CgiUserConfigEdit}{$param} ); - $contentHidden .= fieldHiddenBuild($ConfigMeta{$param}, - $param, - $mainConf->{$param}, - "orig", - ); - $doneParam->{$param} = 1; - } - } - if ( $In{editAction} ne "Save" && $In{newMenu} ne "" + if ( $In{saveAction} ne "Save" && $In{newMenu} ne "" && defined($ConfigMenu{$In{newMenu}}) ) { $menu = $In{newMenu}; } @@ -390,7 +426,8 @@ EOF # # For a non-admin user editing the host config, we need to # figure out which subsets of the menu tree will be visible, - # based on what is enabled + # based on what is enabled. Admin users can edit all the + # available per-host settings. # foreach my $m ( keys(%ConfigMenu) ) { my $enabled = 0; @@ -404,10 +441,12 @@ EOF $text = $n; $mask[$text] = 1; } else { - if ( $bpc->{Conf}{CgiUserConfigEdit}{$param} ) { + if ( $bpc->{Conf}{CgiUserConfigEdit}{$param} + || (defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + && $PrivAdmin) ) { $mask[$text] = 0 if ( $text >= 0 ); $mask[$n] = 0; - $enabled = 1; + $enabled ||= 1; } else { $mask[$n] = 1; } @@ -433,111 +472,132 @@ EOF my $groupText; foreach my $m ( keys(%ConfigMenu) ) { next if ( $menuDisable{$m}{top} ); - my $text = $ConfigMenu{$m}{text}; + my $text = eval("qq($Lang->{$ConfigMenu{$m}{text}})"); if ( $m eq $menu ) { $groupText .= <$text +$text EOF } else { $groupText .= <$text +$text EOF } } if ( $host eq "" ) { - $content .= <{CfgEdit_Header_Main})"); } else { - $content .= < -Note: Check Override if you want to modify a value specific to this host. -EOF + $content .= eval("qq($Lang->{CfgEdit_Header_Host})"); } - my $saveDisplay = "block"; - $saveDisplay = "none" if ( !$In{modified} ); + my $saveStyle = ""; + my $saveColor = "#ff0000"; + + if ( $In{modified} && $In{saveAction} ne "Save" && !%$errors ) { + $saveStyle = "style=\"color:$saveColor\""; + } else { + $In{modified} = 0; + } + + # + # Add action and host to the URL so the nav bar link is + # highlighted + # + my $url = "$MyURL?action=editConfig"; + $url .= "&host=$host" if ( $host ne "" ); $content .= < $groupText -
+ + - -$contentHidden + + +

- +$debugText EOF @@ -603,6 +666,7 @@ EOF EOF my $doneParam = {}; + my $tblContent; # # There is a special case of the user deleting just the field @@ -612,7 +676,7 @@ EOF if ( $In{deleteVar} ne "" && %$errors > 0 ) { my $matchAll = 1; foreach my $v ( keys(%$errors) ) { - if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_/ ) { + if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_zZ_/ ) { $matchAll = 0; last; } @@ -622,28 +686,87 @@ EOF my $isError = %$errors; - if ( !$isError && $In{editAction} eq "Save" ) { - my $mesg; + if ( !$isError && $In{saveAction} eq "Save" ) { + my($mesg, $err); if ( $host ne "" ) { $hostConf = $bpc->ConfigDataRead($host) if ( !defined($hostConf) ); - $mesg = configDiffMesg($host, $hostConf, $newConf); - foreach my $param ( %$newConf ) { - $hostConf->{$param} = $newConf->{$param} - if ( $override->{param} ); + my %hostConf2 = %$hostConf; + foreach my $param ( keys(%$newConf) ) { + if ( $override->{$param} ) { + $hostConf->{$param} = $newConf->{$param} + } else { + delete($hostConf->{$param}); + } } - $bpc->ConfigDataWrite($host, $hostConf); + $mesg = configDiffMesg($host, \%hostConf2, $hostConf); + $err .= $bpc->ConfigDataWrite($host, $hostConf); } else { $mainConf = $bpc->ConfigDataRead() if ( !defined($mainConf) ); - $mesg = configDiffMesg(undef, $mainConf, $newConf); + + my $hostsSave = []; + my($hostsNew, $allHosts, $copyConf); + foreach my $entry ( @{$newConf->{Hosts}} ) { + next if ( $entry->{host} eq "" ); + $allHosts->{$entry->{host}} = 1; + $allHosts->{$1} = 1 if ( $entry->{host} =~ /(.+?)\s*=/ ); + } + foreach my $entry ( @{$newConf->{Hosts}} ) { + next if ( $entry->{host} eq "" + || defined($hostsNew->{$entry->{host}}) ); + if ( $entry->{host} =~ /(.+?)\s*=\s*(.+)/ ) { + if ( defined($allHosts->{$2}) ) { + $entry->{host} = $1; + $copyConf->{$1} = $2; + } else { + my $fullHost = $entry->{host}; + my $copyHost = $2; + $err .= eval("qq($Lang->{CfgEdit_Error_Copy_host_does_not_exist})"); + } + } + push(@$hostsSave, $entry); + $hostsNew->{$entry->{host}} = $entry; + } + ($mesg, my $hostChange) = hostsDiffMesg($hostsNew); + $bpc->HostInfoWrite($hostsNew) if ( $hostChange ); + foreach my $host ( keys(%$copyConf) ) { + # + # Currently host names are forced to lc when they + # are read from the hosts file. Therefore we need + # to force the from and to hosts to lc. + # + my $confData = $bpc->ConfigDataRead(lc($copyConf->{$host})); + my $fromHost = $copyConf->{$host}; + $err .= $bpc->ConfigDataWrite(lc($host), $confData); + $mesg .= eval("qq($Lang->{CfgEdit_Log_Copy_host_config})"); + } + + delete($newConf->{Hosts}); + $mesg .= configDiffMesg(undef, $mainConf, $newConf); $mainConf = { %$mainConf, %$newConf }; - $bpc->ConfigDataWrite(undef, $mainConf); + $err .= $bpc->ConfigDataWrite(undef, $mainConf); + $newConf->{Hosts} = $hostsSave; } + if ( defined($err) ) { + $tblContent .= <$err +EOF + } + $bpc->ServerConnect(); if ( $mesg ne "" ) { - $bpc->ServerConnect(); + (my $mesgBR = $mesg) =~ s/\n/
\n/g; + # uncomment this if you want the changes to be displayed +# $tblContent .= <$mesgBR +#EOF foreach my $str ( split(/\n/, $mesg) ) { - $bpc->ServerMesg($str); + $bpc->ServerMesg("log $str") if ( $str ne "" ); } } + # + # Tell the server to reload, unless we only changed + # a client config + # + $bpc->ServerMesg("server reload") if ( $host eq "" ); } my @mask = @{$menuDisable{$menu}{mask} || []}; @@ -655,12 +778,13 @@ EOF next if ( $disabled || $menuDisable{$menu}{top} ); if ( ref($paramInfo->{visible}) eq "CODE" - && !&{$paramInfo->{visible}}($newConf) ) { + && !&{$paramInfo->{visible}}($newConf, $bpc) ) { next; } - if ( defined(my $text = $paramInfo->{text}) ) { - $content .= <{text}) ) { + my $text = eval("qq($Lang->{$paramInfo->{text}})"); + $tblContent .= <$text EOF next; @@ -670,36 +794,52 @@ EOF # TODO: get parameter documentation # my $comment = ""; - $comment =~ s/\'//g; - $comment =~ s/\"//g; - $comment =~ s/\n/ /g; + #$comment =~ s/\'//g; + #$comment =~ s/\"//g; + #$comment =~ s/\n/ /g; $doneParam->{$param} = 1; - $content .= fieldEditBuild($ConfigMeta{$param}, - $param, - $newConf->{$param}, - $errors, - 0, - $comment, - $isError, - $paramInfo->{onchangeSubmit}, - defined($override) ? $param : undef, - defined($override) ? $override->{$param} : undef + $tblContent .= fieldEditBuild($ConfigMeta{$param}, + $param, + $newConf->{$param}, + $errors, + 0, + $comment, + $isError, + $paramInfo->{onchangeSubmit}, + defined($override) ? $param : undef, + defined($override) ? $override->{$param} : undef ); + if ( defined($paramInfo->{comment}) ) { + my $topDir = $bpc->TopDir; + my $text = eval("qq($Lang->{$paramInfo->{comment}})"); + $tblContent .= <$text +EOF + } } # - # Emit any remaining errors - should not happen + # Emit a summary of all the errors # + my $errorTxt; + + if ( %$errors ) { + $errorTxt .= <$Lang->{CfgEdit_Error_No_Save} +EOF + } + foreach my $param ( sort(keys(%$errors)) ) { - $content .= <$errors->{$param} + $errorTxt .= <$errors->{$param} EOF - delete($errors->{$param}); } $content .= < EOF @@ -708,7 +848,10 @@ EOF # foreach my $param ( keys(%ConfigMeta) ) { next if ( $doneParam->{$param} ); - next if ( $userHost && !$bpc->{Conf}{CgiUserConfigEdit}{$param} ); + next if ( $userHost + && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + || (!$PrivAdmin + && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) ); $content .= fieldHiddenBuild($ConfigMeta{$param}, $param, $newConf->{$param}, @@ -722,7 +865,60 @@ EOF $doneParam->{$param} = 1; } + if ( defined($In{menu}) || $In{saveAction} eq "Save" ) { + if ( $In{saveAction} eq "Save" && !$userHost ) { + # + # Emit the new settings as orig_zZ_ parameters + # + $doneParam = {}; + foreach my $param ( keys(%ConfigMeta) ) { + next if ( $doneParam->{$param} ); + next if ( $userHost + && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + || (!$PrivAdmin + && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) ); + $contentHidden .= fieldHiddenBuild($ConfigMeta{$param}, + $param, + $newConf->{$param}, + "orig", + ); + $doneParam->{$param} = 1; + $In{modified} = 0; + } + } else { + # + # Just switching menus: copy all the orig_zZ_ input parameters + # + foreach my $var ( keys(%In) ) { + next if ( $var !~ /^orig_zZ_/ ); + my $val = decode_utf8($In{$var}); + $contentHidden .= < +EOF + } + } + } else { + # + # First time: emit all the original config settings + # + $doneParam = {}; + foreach my $param ( keys(%ConfigMeta) ) { + next if ( $doneParam->{$param} ); + next if ( $userHost + && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + || (!$PrivAdmin + && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) ); + $contentHidden .= fieldHiddenBuild($ConfigMeta{$param}, + $param, + $mainConf->{$param}, + "orig", + ); + $doneParam->{$param} = 1; + } + } + $content .= < @@ -744,14 +940,16 @@ sub fieldHiddenBuild $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" ); for ( my $i = 0 ; $i < @$varValue ; $i++ ) { - $content .= fieldHiddenBuild($type->{child}, "${varName}_$i", + $content .= fieldHiddenBuild($type->{child}, "${varName}_zZ_$i", $varValue->[$i], $prefix); } - } elsif ( $type->{type} eq "hash" ) { + } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) { $varValue = {} if ( ref($varValue) ne "HASH" ); my(@order, $childType); - if ( defined($type->{child}) ) { + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { @order = sort(keys(%{$type->{child}})); } else { @order = sort(keys(%$varValue)); @@ -770,18 +968,18 @@ sub fieldHiddenBuild EOF } - $content .= fieldHiddenBuild($childType, "${varName}_$fld", + $content .= fieldHiddenBuild($childType, "${varName}_zZ_$fld", $varValue->{$fld}, $prefix); } } elsif ( $type->{type} eq "shortlist" ) { $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" ); $varValue = join(", ", @$varValue); $content .= < + EOF } else { $content .= < + EOF } return $content; @@ -796,33 +994,42 @@ sub fieldEditBuild my $size = 50 - 10 * $level; $type = { type => $type } if ( ref($type) ne "HASH" ); + $size = $type->{size} if ( defined($type->{size}) ); + + # + # These fragments allow inline content to be turned on and off + # + # + # $varName + # + if ( $level == 0 ) { + my $lcVarName = lc($varName); $content .= <$comment -$varName +$varName EOF if ( defined($overrideVar) ) { my $override_checked = ""; - if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_/ - || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_|$)/ - || !$isError && $In{addVar} =~ /^\Q${varName}\E(_|$)/ ) { + if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_zZ_/ + || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_zZ_|$)/ + || !$isError && $In{addVar} =~ /^\Q${varName}\E(_zZ_|$)/ ) { $overrideSet = 1; } if ( $overrideSet ) { $override_checked = "checked"; } $content .= <\ Override +
\ ${EscHTML($Lang->{CfgEdit_Button_Override})} EOF } $content .= "\n"; } - $content .= "\n"; if ( $type->{type} eq "list" ) { + $content .= "\n"; $varValue = [] if ( !defined($varValue) ); $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" ); - if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_\E(\d+)$/ + if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_zZ_\E(\d+)$/ && $1 < @$varValue ) { # # User deleted entry in this array @@ -830,7 +1037,7 @@ EOF splice(@$varValue, $1, 1) if ( @$varValue > 1 || $type->{emptyOk} ); $In{deleteVar} = ""; } - if ( !$isError && $In{insertVar} =~ /^\Q${varName}_\E(\d+)$/ + if ( !$isError && $In{insertVar} =~ /^\Q${varName}_zZ_\E(\d+)$/ && $1 < @$varValue ) { # # User inserted entry in this array @@ -847,37 +1054,75 @@ EOF $In{addVar} = ""; } $content .= "\n"; + my $colspan; - for ( my $i = 0 ; $i < @$varValue ; $i++ ) { - $content .= "\n"; - $content .= fieldEditBuild($type->{child}, "${varName}_$i", - $varValue->[$i], $errors, $level + 1, undef, - $isError, $onchangeSubmit, - $overrideVar, $overrideSet); + if ( ref($type) eq "HASH" && ref($type->{child}) eq "HASH" + && $type->{child}{type} eq "horizHash" ) { + my @order; + if ( defined($type->{child}{order}) ) { + @order = @{$type->{child}{order}}; + } else { + @order = sort(keys(%{$type->{child}{child}})); + } + $content .= "\n"; + for ( my $i = 0 ; $i < @order ; $i++ ) { + $content .= "\n"; + } + $colspan = @order + 1; $content .= "\n"; + for ( my $i = 0 ; $i < @$varValue ; $i++ ) { + if ( @$varValue > 1 || $type->{emptyOk} ) { + $content .= < + + +EOF + } + $content .= fieldEditBuild($type->{child}, "${varName}_zZ_$i", + $varValue->[$i], $errors, $level + 1, undef, + $isError, $onchangeSubmit, + $overrideVar, $overrideSet); + $content .= "\n"; + } + } else { + for ( my $i = 0 ; $i < @$varValue ; $i++ ) { + $content .= <\n"; + $content .= fieldEditBuild($type->{child}, "${varName}_zZ_$i", + $varValue->[$i], $errors, $level + 1, undef, + $isError, $onchangeSubmit, + $overrideVar, $overrideSet); + $content .= "\n"; + } + $colspan = 2; } $content .= <
\n"; - if ( @$varValue > 1 || $type->{emptyOk} ) { - $content .= < - -EOF - } - $content .= "
$order[$i]
+ +EOF + if ( @$varValue > 1 || $type->{emptyOk} ) { + $content .= < +EOF + } + $content .= "
EOF + $content .= "\n"; } elsif ( $type->{type} eq "hash" ) { + $content .= "\n"; $content .= "\n"; $varValue = {} if ( ref($varValue) ne "HASH" ); if ( !$isError && !$type->{noKeyEdit} - && $In{deleteVar} =~ /^\Q${varName}_\E(\w+)$/ ) { + && $In{deleteVar} !~ /^\Q${varName}_zZ_\E.*_zZ_/ + && $In{deleteVar} =~ /^\Q${varName}_zZ_\E(.*)$/ ) { # - # User deleted entry in this array + # User deleted entry in this hash # delete($varValue->{$1}) if ( keys(%$varValue) > 1 || $type->{emptyOk} ); @@ -888,13 +1133,15 @@ EOF # # User added entry to this array # - $varValue->{$In{addVarKey}} = "" - if ( !defined($varValue->{$In{addVarKey}}) ); + $varValue->{$In{"addVarKey_$varName"}} = "" + if ( !defined($varValue->{$In{"addVarKey_$varName"}}) ); $In{addVar} = ""; } my(@order, $childType); - if ( defined($type->{child}) ) { + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { @order = sort(keys(%{$type->{child}})); } else { @order = sort(keys(%$varValue)); @@ -907,8 +1154,8 @@ EOF if ( !$type->{noKeyEdit} && (keys(%$varValue) > 1 || $type->{emptyOk}) ) { $content .= < + EOF } if ( defined($type->{child}) ) { @@ -924,7 +1171,7 @@ EOF EOF } $content .= "\n"; - $content .= fieldEditBuild($childType, "${varName}_$fld", + $content .= fieldEditBuild($childType, "${varName}_zZ_$fld", $varValue->{$fld}, $errors, $level + 1, undef, $isError, $onchangeSubmit, $overrideVar, $overrideSet); @@ -934,35 +1181,66 @@ EOF if ( !$type->{noKeyEdit} ) { $content .= < EOF } $content .= "
-New key: - +$Lang->{CfgEdit_Button_New_Key}: +
\n"; + $content .= "\n"; + } elsif ( $type->{type} eq "horizHash" ) { + $varValue = {} if ( ref($varValue) ne "HASH" ); + my(@order, $childType); + + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { + @order = sort(keys(%{$type->{child}})); + } else { + @order = sort(keys(%$varValue)); + } + + foreach my $fld ( @order ) { + if ( defined($type->{child}) ) { + $childType = $type->{child}{$fld}; + } else { + $childType = $type->{childType}; + # + # emit list of fields since they are user-defined + # rather than hard-coded + # + $content .= < +EOF + } + $content .= fieldEditBuild($childType, "${varName}_zZ_$fld", + $varValue->{$fld}, $errors, $level + 1, undef, + $isError, $onchangeSubmit, + $overrideVar, $overrideSet); + } } else { + $content .= "\n"; if ( $isError ) { # # If there was an error, we use the original post values # in %In, rather than the parsed values in $varValue. # This is so that the user's erroneous input is preserved. # - $varValue = $In{"v_$varName"} if ( defined($In{"v_$varName"}) ); + $varValue = $In{"v_zZ_$varName"} if ( defined($In{"v_zZ_$varName"}) ); } if ( defined($errors->{$varName}) ) { $content .= <{$varName}
+$errors->{$varName}
EOF - delete($errors->{$varName}); } my $onChange; if ( defined($overrideVar) ) { $onChange .= "checkboxSet('$overrideVar');"; } else { - $onChange .= "varChange('$overrideVar');"; + $onChange .= "varChange('$varName');"; } if ( $onchangeSubmit ) { - $onChange .= "document.form1.submit();"; + $onChange .= "document.editForm.submit();"; } if ( $onChange ne "" ) { $onChange = " onChange=\"$onChange\""; @@ -970,6 +1248,7 @@ EOF if ( $varValue !~ /\n/ && ($type->{type} eq "integer" || $type->{type} eq "string" + || $type->{type} eq "execPath" || $type->{type} eq "shortlist" || $type->{type} eq "float") ) { # simple input box @@ -977,18 +1256,19 @@ EOF $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" ); $varValue = join(", ", @$varValue); } + my $textType = ($varName =~ /Passwd/) ? "password" : "text"; $content .= < + EOF } elsif ( $type->{type} eq "boolean" ) { # checkbox my $checked = "checked" if ( $varValue ); $content .= < + EOF } elsif ( $type->{type} eq "select" ) { $content .= < + EOF } + $content .= "\n"; } - $content .= "\n"; return $content; } @@ -1026,13 +1306,15 @@ sub fieldErrorCheck if ( $type->{type} eq "list" ) { for ( my $i = 0 ; ; $i++ ) { - last if ( fieldErrorCheck($type->{child}, "${varName}_$i", $errors) ); + last if ( fieldErrorCheck($type->{child}, "${varName}_zZ_$i", $errors) ); } - } elsif ( $type->{type} eq "hash" ) { + } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) { my(@order, $childType); my $ret; - if ( defined($type->{child}) ) { + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { @order = sort(keys(%{$type->{child}})); } else { @order = split(/\0/, $In{"vflds.$varName"}); @@ -1043,51 +1325,58 @@ sub fieldErrorCheck } else { $childType = $type->{childType}; } - $ret ||= fieldErrorCheck($childType, "${varName}_$fld", $errors); + $ret ||= fieldErrorCheck($childType, "${varName}_zZ_$fld", $errors); } return $ret; } else { - return 1 if ( !exists($In{"v_$varName"}) ); + $In{"v_zZ_$varName"} = "0" if ( $type->{type} eq "boolean" + && $In{"v_zZ_$varName"} eq "" ); + + return 1 if ( !exists($In{"v_zZ_$varName"}) ); + + (my $var = $varName) =~ s/_zZ_/./g; if ( $type->{type} eq "integer" || $type->{type} eq "boolean" ) { - if ( $In{"v_$varName"} !~ /^-?\d+\s*$/s - && $In{"v_$varName"} ne "" ) { - $errors->{$varName} = "Error: $varName must be an integer"; + if ( $In{"v_zZ_$varName"} !~ /^-?\d+\s*$/s + && $In{"v_zZ_$varName"} ne "" ) { + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_an_integer}}"); } } elsif ( $type->{type} eq "float" ) { - if ( $In{"v_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s - && $In{"v_$varName"} ne "" ) { + if ( $In{"v_zZ_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s + && $In{"v_zZ_$varName"} ne "" ) { $errors->{$varName} - = "Error: $varName must be a real-valued number"; + = eval("qq{$Lang->{CfgEdit_Error__must_be_real_valued_number}}"); } } elsif ( $type->{type} eq "shortlist" ) { - my @vals = split(/[,\s]+/, $In{"v_$varName"}); + my @vals = split(/[,\s]+/, $In{"v_zZ_$varName"}); for ( my $i = 0 ; $i < @vals ; $i++ ) { if ( $type->{child} eq "integer" && $vals[$i] !~ /^-?\d+\s*$/s && $vals[$i] ne "" ) { my $k = $i + 1; - $errors->{$varName} = "Error: $varName entry $k must" - . " be an integer"; + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_an_integer}}"); } elsif ( $type->{child} eq "float" && $vals[$i] !~ /^-?\d*(\.\d*)?\s*$/s && $vals[$i] ne "" ) { my $k = $i + 1; - $errors->{$varName} = "Error: $varName entry $k must" - . " be a real-valued number"; + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_real_valued_number}}"); } } } elsif ( $type->{type} eq "select" ) { my $match = 0; foreach my $option ( @{$type->{values}} ) { - if ( $In{"v_$varName"} eq $option ) { + if ( $In{"v_zZ_$varName"} eq $option ) { $match = 1; last; } } - $errors->{$varName} = "Error: $varName must be a valid option" + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_valid_option}}") if ( !$match ); + } elsif ( $type->{type} eq "execPath" ) { + if ( $In{"v_zZ_$varName"} ne "" && !-x $In{"v_zZ_$varName"} ) { + $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_executable_program}}"); + } } else { # # $type->{type} eq "string": no error checking @@ -1105,11 +1394,14 @@ sub inputParse foreach my $param ( keys(%ConfigMeta) ) { my $value; - next if ( $userHost && !$bpc->{Conf}{CgiUserConfigEdit}{$param} ); + next if ( $userHost + && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param}) + || (!$PrivAdmin + && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) ); fieldInputParse($ConfigMeta{$param}, $param, \$value); $conf->{$param} = $value; $override->{$param} = $In{"override_$param"}; -} + } return ($conf, $override); } @@ -1123,16 +1415,18 @@ sub fieldInputParse $$value = []; for ( my $i = 0 ; ; $i++ ) { my $val; - last if ( fieldInputParse($type->{child}, "${varName}_$i", \$val) ); + last if ( fieldInputParse($type->{child}, "${varName}_zZ_$i", \$val) ); push(@$$value, $val); } $$value = undef if ( $type->{undefIfEmpty} && @$$value == 0 ); - } elsif ( $type->{type} eq "hash" ) { + } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) { my(@order, $childType); my $ret; $$value = {}; - if ( defined($type->{child}) ) { + if ( defined($type->{order}) ) { + @order = @{$type->{order}}; + } elsif ( defined($type->{child}) ) { @order = sort(keys(%{$type->{child}})); } else { @order = split(/\0/, $In{"vflds.$varName"}); @@ -1145,33 +1439,55 @@ sub fieldInputParse } else { $childType = $type->{childType}; } - $ret ||= fieldInputParse($childType, "${varName}_$fld", \$val); + $ret ||= fieldInputParse($childType, "${varName}_zZ_$fld", \$val); last if ( $ret ); $$value->{$fld} = $val; } return $ret; } else { if ( $type->{type} eq "boolean" ) { - $$value = 0 + $In{"v_$varName"}; - } elsif ( !exists($In{"v_$varName"}) ) { + $$value = 0 + $In{"v_zZ_$varName"}; + } elsif ( !exists($In{"v_zZ_$varName"}) ) { return 1; } + my $v = $In{"v_zZ_$varName"}; + if ( $type->{type} eq "integer" ) { - $$value = 0 + $In{"v_$varName"}; + if ( $v =~ /^-?\d+\s*$/s || $v eq "" ) { + $$value = 0 + $v; + } else { + # error value - keep in string form + $$value = $v; + } } elsif ( $type->{type} eq "float" ) { - $$value = 0 + $In{"v_$varName"}; + if ( $v =~ /^-?\d*(\.\d*)?\s*$/s || $v eq "" ) { + $$value = 0 + $v; + } else { + # error value - keep in string form + $$value = $v; + } } elsif ( $type->{type} eq "shortlist" ) { - $$value = [split(/[,\s]+/, $In{"v_$varName"})]; - if ( $type->{child} eq "float" - || $type->{child} eq "integer" - || $type->{child} eq "boolean" ) { + $$value = [split(/[,\s]+/, $v)]; + if ( $type->{child} eq "float" ) { foreach ( @$$value ) { - $_ += 0; + if ( /^-?\d*(\.\d*)?\s*$/s || $v eq "" ) { + $_ += 0; + } + } + } elsif ( $type->{child} eq "integer" + || $type->{child} eq "boolean" ) { + foreach ( @$$value ) { + if ( /^-?\d+\s*$/s || $v eq "" ) { + $_ += 0; + } } } } else { - $$value = $In{"v_$varName"}; + $$value = decode_utf8($In{"v_zZ_$varName"}); + $$value =~ s/\r\n/\n/g; + # remove leading space from exec paths + $$value =~ s/^\s+// if ( $type->{type} eq "execPath" ); } $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" ); } @@ -1194,14 +1510,16 @@ sub configDiffMesg if ( !exists($oldConf->{$p}) && !exists($newConf->{$p}) ) { next; } elsif ( exists($oldConf->{$p}) && !exists($newConf->{$p}) ) { - $mesg .= "log Deleted $p from $conf\n"; + $mesg .= eval("qq($Lang->{CfgEdit_Log_Delete_param})"); } elsif ( !exists($oldConf->{$p}) && exists($newConf->{$p}) ) { my $dump = Data::Dumper->new([$newConf->{$p}]); $dump->Indent(0); $dump->Sortkeys(1); $dump->Terse(1); my $value = $dump->Dump; - $mesg .= "log Added $p to $conf, set to $value\n"; + $value =~ s/\n/\\n/g; + $value =~ s/\r/\\r/g; + $mesg .= eval("qq($Lang->{CfgEdit_Log_Add_param_value})"); } else { my $dump = Data::Dumper->new([$newConf->{$p}]); $dump->Indent(0); @@ -1219,11 +1537,56 @@ sub configDiffMesg $dump->Terse(1); my $valueOld = $dump->Dump; - $mesg .= "log Changed $p in $conf to $valueNew from $valueOld\n" - if ( $valueOld ne $valueNew ); + (my $valueNew2 = $valueNew) =~ s/['\n\r]//g; + (my $valueOld2 = $valueOld) =~ s/['\n\r]//g; + + next if ( $valueOld2 eq $valueNew2 ); + + $valueNew =~ s/\n/\\n/g; + $valueOld =~ s/\n/\\n/g; + $valueNew =~ s/\r/\\r/g; + $valueOld =~ s/\r/\\r/g; + + $mesg .= eval("qq($Lang->{CfgEdit_Log_Change_param_value})"); } } return $mesg; } +sub hostsDiffMesg +{ + my($hostsNew) = @_; + my $hostsOld = $bpc->HostInfoRead(); + my($mesg, $hostChange); + + foreach my $host ( keys(%$hostsOld) ) { + if ( !defined($hostsNew->{$host}) ) { + $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Delete})"); + $hostChange++; + next; + } + foreach my $key ( keys(%{$hostsNew->{$host}}) ) { + next if ( $hostsNew->{$host}{$key} eq $hostsOld->{$host}{$key} ); + my $valueOld = $hostsOld->{$host}{$key}; + my $valueNew = $hostsNew->{$host}{$key}; + $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Change})"); + $hostChange++; + } + } + + foreach my $host ( keys(%$hostsNew) ) { + next if ( defined($hostsOld->{$host}) ); + my $dump = Data::Dumper->new([$hostsNew->{$host}]); + $dump->Indent(0); + $dump->Sortkeys(1); + $dump->Terse(1); + my $value = $dump->Dump; + $value =~ s/\n/\\n/g; + $value =~ s/\r/\\r/g; + $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Add})"); + $hostChange++; + } + return ($mesg, $hostChange); +} + 1;