68c77a8c3958ec0c68c53b4f316ee8cf69e93ac6
[BackupPC.git] / lib / BackupPC / CGI / EditConfig.pm
1 #============================================================= -*-perl-*-
2 #
3 # BackupPC::CGI::EditConfig package
4 #
5 # DESCRIPTION
6 #
7 #   This module implements the EditConfig action for the CGI interface.
8 #
9 # AUTHOR
10 #   Craig Barratt  <cbarratt@users.sourceforge.net>
11 #
12 # COPYRIGHT
13 #   Copyright (C) 2005  Craig Barratt
14 #
15 #   This program is free software; you can redistribute it and/or modify
16 #   it under the terms of the GNU General Public License as published by
17 #   the Free Software Foundation; either version 2 of the License, or
18 #   (at your option) any later version.
19 #
20 #   This program is distributed in the hope that it will be useful,
21 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
22 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 #   GNU General Public License for more details.
24 #
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28 #
29 #========================================================================
30 #
31 # Version 3.0.0beta1, released 30 Jul 2006.
32 #
33 # See http://backuppc.sourceforge.net.
34 #
35 #========================================================================
36
37 package BackupPC::CGI::EditConfig;
38
39 use strict;
40 use BackupPC::CGI::Lib qw(:all);
41 use BackupPC::Config::Meta qw(:all);
42 use BackupPC::Storage;
43 use Data::Dumper;
44 use Encode;
45
46 our %ConfigMenu = (
47     server => {
48         text  => "CfgEdit_Title_Server",
49         param => [
50             {text => "CfgEdit_Title_General_Parameters"},
51             {name => "ServerHost"},
52             {name => "BackupPCUser"},
53             {name => "BackupPCUserVerify"},
54             {name => "MaxOldLogFiles"},
55             {name => "TrashCleanSleepSec"},
56
57             {text => "CfgEdit_Title_Wakeup_Schedule"},
58             {name => "WakeupSchedule"},
59
60             {text => "CfgEdit_Title_Concurrent_Jobs"},
61             {name => "MaxBackups"},
62             {name => "MaxUserBackups"},
63             {name => "MaxPendingCmds"},
64             {name => "MaxBackupPCNightlyJobs"},
65             {name => "BackupPCNightlyPeriod"},
66
67             {text => "CfgEdit_Title_Pool_Filesystem_Limits"},
68             {name => "DfCmd"},
69             {name => "DfMaxUsagePct"},
70             {name => "HardLinkMax"},
71
72             {text => "CfgEdit_Title_Other_Parameters"},
73             {name => "UmaskMode"},
74             {name => "MyPath"},
75             {name => "DHCPAddressRanges"},
76             {name => "PerlModuleLoad"},
77             {name => "ServerInitdPath"},
78             {name => "ServerInitdStartCmd"},
79
80             {text => "CfgEdit_Title_Remote_Apache_Settings"},
81             {name => "ServerPort"},
82             {name => "ServerMesgSecret"},
83
84             {text => "CfgEdit_Title_Program_Paths"},
85             {name => "SshPath"},
86             {name => "NmbLookupPath"},
87             {name => "PingPath"},
88             {name => "DfPath"},
89             {name => "SplitPath"},
90             {name => "ParPath"},
91             {name => "CatPath"},
92             {name => "GzipPath"},
93             {name => "Bzip2Path"},
94
95             {text => "CfgEdit_Title_Install_Paths"},
96             {name => "TopDir"},
97             {name => "ConfDir"},
98             {name => "LogDir"},
99             {name => "CgiDir"},
100             {name => "InstallDir"},
101         ],
102     },
103     email => {
104         text  => "CfgEdit_Title_Email",
105         param => [
106             {text => "CfgEdit_Title_Email_settings"},
107             {name => "SendmailPath"},
108             {name => "EMailNotifyMinDays"},
109             {name => "EMailFromUserName"},
110             {name => "EMailAdminUserName"},
111             {name => "EMailUserDestDomain"},
112
113             {text => "CfgEdit_Title_Email_User_Messages"},
114             {name => "EMailNoBackupEverSubj"},
115             {name => "EMailNoBackupEverMesg"},
116             {name => "EMailNotifyOldBackupDays"},
117             {name => "EMailNoBackupRecentSubj"},
118             {name => "EMailNoBackupRecentMesg"},
119             {name => "EMailNotifyOldOutlookDays"},
120             {name => "EMailOutlookBackupSubj"},
121             {name => "EMailOutlookBackupMesg"},
122             {name => "EMailHeaders"},
123         ],
124     },
125     cgi => {
126         text => "CfgEdit_Title_CGI",
127         param => [
128             {text => "CfgEdit_Title_Admin_Privileges"},
129             {name => "CgiAdminUserGroup"},
130             {name => "CgiAdminUsers"},
131
132             {text => "CfgEdit_Title_Page_Rendering"},
133             {name => "Language"},
134             {name => "CgiNavBarAdminAllHosts"},
135             {name => "CgiSearchBoxEnable"},
136             {name => "CgiNavBarLinks"},
137             {name => "CgiStatusHilightColor"},
138             {name => "CgiDateFormatMMDD"},
139             {name => "CgiHeaders"},
140             {name => "CgiExt2ContentType"},
141             {name => "CgiCSSFile"},
142
143             {text => "CfgEdit_Title_Paths"},
144             {name => "CgiURL"},
145             {name => "CgiImageDir"},
146             {name => "CgiImageDirURL"},
147
148             {text => "CfgEdit_Title_User_URLs"},
149             {name => "CgiUserHomePageCheck"},
150             {name => "CgiUserUrlCreate"},
151
152             {text => "CfgEdit_Title_User_Config_Editing"},
153             {name => "CgiUserConfigEditEnable"},
154             {name => "CgiUserConfigEdit"},
155         ],
156     },
157     xfer => {
158         text => "CfgEdit_Title_Xfer",
159         param => [
160             {text => "CfgEdit_Title_Xfer_Settings"},
161             {name => "XferMethod", onchangeSubmit => 1},
162             {name => "XferLogLevel"},
163             {name => "ClientCharset"},
164
165             {text => "CfgEdit_Title_Smb_Settings",
166                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
167             {name => "SmbShareName",
168                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
169             {name => "SmbShareUserName",
170                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
171             {name => "SmbSharePasswd",
172                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
173
174             {text => "CfgEdit_Title_Tar_Settings",
175                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
176             {name => "TarShareName",
177                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
178
179             {text => "CfgEdit_Title_Rsync_Settings",
180                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
181             {text => "CfgEdit_Title_Rsyncd_Settings",
182                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
183             {name => "RsyncShareName",
184                 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
185             {name => "RsyncdUserName",
186                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
187             {name => "RsyncdPasswd",
188                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
189             {name => "RsyncdAuthRequired",
190                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
191             {name => "RsyncCsumCacheVerifyProb",
192                 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
193
194             {text => "CfgEdit_Title_BackupPCd_Settings",
195                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
196             {name => "BackupPCdShareName",
197                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
198             {name => "BackupPCdPath",
199                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
200             {name => "BackupPCdCmd",
201                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
202             {name => "BackupPCdRestoreCmd",
203                 visible => sub { return $_[0]->{XferMethod} eq "backuppcd"; } },
204
205             {text => "CfgEdit_Title_Archive_Settings",
206                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
207             {name => "ArchiveDest",
208                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
209             {name => "ArchiveComp",
210                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
211             {name => "ArchivePar",
212                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
213             {name => "ArchiveSplit",
214                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
215
216             {text => "CfgEdit_Title_Include_Exclude",
217                 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
218             {name => "BackupFilesOnly",
219                 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
220             {name => "BackupFilesExclude",
221                 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
222
223             {text => "CfgEdit_Title_Smb_Paths_Commands",
224                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
225             {name => "SmbClientPath",
226                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
227             {name => "SmbClientFullCmd",
228                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
229             {name => "SmbClientIncrCmd",
230                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
231             {name => "SmbClientRestoreCmd",
232                 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
233
234             {text => "CfgEdit_Title_Tar_Paths_Commands",
235                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
236             {name => "TarClientPath",
237                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
238             {name => "TarClientCmd",
239                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
240             {name => "TarFullArgs",
241                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
242             {name => "TarIncrArgs",
243                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
244             {name => "TarClientRestoreCmd",
245                 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
246
247             {text => "CfgEdit_Title_Rsync_Paths_Commands_Args",
248                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
249             {text => "CfgEdit_Title_Rsyncd_Port_Args",
250                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
251             {name => "RsyncClientPath",
252                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
253             {name => "RsyncClientCmd",
254                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
255             {name => "RsyncClientRestoreCmd",
256                 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
257             {name => "RsyncdClientPort",
258                 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
259             {name => "RsyncArgs",
260                 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
261             {name => "RsyncRestoreArgs",
262                 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
263
264             {text => "CfgEdit_Title_Archive_Paths_Commands",
265                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
266             {name => "ArchiveClientCmd",
267                 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
268
269         ],
270     },
271     schedule => {
272         text => "CfgEdit_Title_Schedule",
273         param => [
274             {text => "CfgEdit_Title_Full_Backups"},
275             {name => "FullPeriod"},
276             {name => "FullKeepCnt"},
277             {name => "FullKeepCntMin"},
278             {name => "FullAgeMax"},
279
280             {text => "CfgEdit_Title_Incremental_Backups"},
281             {name => "IncrPeriod"},
282             {name => "IncrKeepCnt"},
283             {name => "IncrKeepCntMin"},
284             {name => "IncrAgeMax"},
285             {name => "IncrLevels"},
286             {name => "IncrFill"},
287
288             {text => "CfgEdit_Title_Blackouts"},
289             {name => "BackupsDisable"},
290             {name => "BlackoutBadPingLimit"},
291             {name => "BlackoutGoodCnt"},
292             {name => "BlackoutPeriods"},
293
294             {text => "CfgEdit_Title_Other"},
295             {name => "PartialAgeMax"},
296             {name => "RestoreInfoKeepCnt"},
297             {name => "ArchiveInfoKeepCnt"},
298             {name => "BackupZeroFilesIsFatal"},
299         ],
300     },
301     backup => {
302         text => "CfgEdit_Title_Backup_Settings",
303         param => [
304             {text => "CfgEdit_Title_Client_Lookup"},
305             {name => "ClientNameAlias"},
306             {name => "NmbLookupCmd"},
307             {name => "NmbLookupFindHostCmd"},
308             {name => "FixedIPNetBiosNameCheck"},
309             {name => "PingCmd"},
310             {name => "PingMaxMsec"},
311             
312             {text => "CfgEdit_Title_Other"},
313             {name => "ClientTimeout"},
314             {name => "MaxOldPerPCLogFiles"},
315             {name => "CompressLevel"},
316
317             {text => "CfgEdit_Title_User_Commands"},
318             {name => "DumpPreUserCmd"},
319             {name => "DumpPostUserCmd"},
320             {name => "DumpPreShareCmd"},
321             {name => "DumpPostShareCmd"},
322             {name => "RestorePreUserCmd"},
323             {name => "RestorePostUserCmd"},
324             {name => "ArchivePreUserCmd"},
325             {name => "ArchivePostUserCmd"},
326             {name => "UserCmdCheckStatus"},
327         ],
328     },
329     hosts => {
330         text => "CfgEdit_Title_Hosts",
331         param => [
332             {text    => "CfgEdit_Title_Hosts"},
333             {name    => "Hosts",
334              comment => "CfgEdit_Hosts_Comment"},
335         ],
336     },
337 );
338
339 sub action
340 {
341     my $pc_dir = "$TopDir/pc";
342     my($content, $contentHidden, $newConf, $override, $mainConf, $hostConf);
343     my $errors = {};
344
345     my $host = $In{host};
346     my $menu = $In{menu} || "server";
347     my $hosts_path = $Hosts;
348     my $config_path = $host eq "" ? "$TopDir/conf/config.pl"
349                                   : "$TopDir/pc/$host/config.pl";
350
351     my $Privileged = CheckPermission($host)
352                        && ($PrivAdmin || $Conf{CgiUserConfigEditEnable});
353     my $userHost = 1 if ( defined($host) );
354     my $debugText;
355
356     if ( !$Privileged ) {
357         ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_edit_config_files}}"));
358     }
359
360     if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
361         $errors = errorCheck();
362         if ( %$errors ) {
363             #
364             # If there are errors, then go back to the same menu
365             #
366             $In{editAction} = "";
367             $In{newMenu} = "";
368         }
369         if ( (my $var = $In{overrideUncheck}) ne "" ) {
370             #
371             # a compound variable was unchecked; delete extra
372             # variables to make the shape the same.
373             #
374             #print STDERR Dumper(\%In);
375             foreach my $v ( keys(%In) ) {
376                 next if ( $v !~ /^v_z_(\Q$var\E(_z_.*|$))/ );
377                 delete($In{$v}) if ( !defined($In{"orig_z_$1"}) );
378             }
379             delete($In{"vflds.$var"});
380         }
381
382         ($newConf, $override) = inputParse($bpc, $userHost);
383         $override = undef if ( $host eq "" );
384
385     } else {
386         #
387         # First time: pick up the current config settings
388         #
389         $mainConf = $bpc->ConfigDataRead();
390         if ( $host ne "" ) {
391             $hostConf = $bpc->ConfigDataRead($host);
392             $override = {};
393             foreach my $param ( keys(%$hostConf) ) {
394                 $override->{$param} = 1;
395             }
396         } else {
397             my $hostInfo = $bpc->HostInfoRead();
398             $hostConf = {};
399             $mainConf->{Hosts} = [map($hostInfo->{$_}, sort(keys(%$hostInfo)))];
400         }
401         $newConf = { %$mainConf, %$hostConf };
402     }
403
404     if ( $In{editAction} ne $Lang->{CfgEdit_Button_Save} && $In{newMenu} ne ""
405                     && defined($ConfigMenu{$In{newMenu}}) ) {
406         $menu = $In{newMenu};
407     }
408
409     my %menuDisable;
410     if ( $userHost ) {
411         #
412         # For a non-admin user editing the host config, we need to
413         # figure out which subsets of the menu tree will be visible,
414         # based on what is enabled.  Admin users can edit all the
415         # available per-host settings.
416         #
417         foreach my $m ( keys(%ConfigMenu) ) {
418             my $enabled = 0;
419             my $text = -1;
420             my $n = 0;
421             my @mask = ();
422
423             foreach my $paramInfo ( @{$ConfigMenu{$m}{param}} ) {
424                 my $param = $paramInfo->{name};
425                 if ( defined($paramInfo->{text}) ) {
426                     $text = $n;
427                     $mask[$text] = 1;
428                 } else {
429                     if ( $bpc->{Conf}{CgiUserConfigEdit}{$param}
430                           || (defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
431                                 && $PrivAdmin) ) {
432                         $mask[$text] = 0 if ( $text >= 0 );
433                         $mask[$n] = 0;
434                         $enabled ||= 1;
435                     } else {
436                         $mask[$n] = 1;
437                     }
438                 }
439                 $n++;
440             }
441             $menuDisable{$m}{mask} = \@mask;
442             $menuDisable{$m}{top}  = !$enabled;
443         }
444         if ( $menuDisable{$menu}{top} ) {
445             #
446             # Find an enabled menu if the current menu is empty
447             #
448             foreach my $m ( sort(keys(%menuDisable)) ) {
449                 if ( !$menuDisable{$m}{top} ) {
450                     $menu = $m;
451                     last;
452                 }
453             }
454         }
455     }
456
457     my $groupText;
458     foreach my $m ( keys(%ConfigMenu) ) {
459         next if ( $menuDisable{$m}{top} );
460         my $text = eval("qq($Lang->{$ConfigMenu{$m}{text}})");
461         if ( $m eq $menu ) {
462             $groupText .= <<EOF;
463 <td class="editTabSel"><a href="javascript:menuSubmit('$m')"><b>$text</b></a></td>
464 EOF
465         } else {
466             $groupText .= <<EOF;
467 <td class="editTabNoSel"><a href="javascript:menuSubmit('$m')">$text</a></td>
468 EOF
469         }
470     }
471
472     if ( $host eq "" ) {
473         $content .= eval("qq($Lang->{CfgEdit_Header_Main})");
474     } else {
475         $content .= eval("qq($Lang->{CfgEdit_Header_Host})");
476     }
477
478     my $saveDisplay = "block";
479     $saveDisplay = "none" if ( !$In{modified}
480                           || $In{editAction} eq $Lang->{CfgEdit_Button_Save} );
481     #
482     # Add action and host to the URL so the nav bar link is
483     # highlighted
484     #
485     my $url = "$MyURL?action=editConfig";
486     $url .= "&host=$host" if ( $host ne "" );
487     $content .= <<EOF;
488 <table border="0" cellpadding="2">
489 <tr>$groupText</tr>
490 <tr>
491 <form method="post" name="form1" action="$url">
492 <input type="hidden" name="host" value="$host">
493 <input type="hidden" name="menu" value="$menu">
494 <input type="hidden" name="newMenu" value="">
495 <input type="hidden" name="modified" value="$In{modified}">
496 <input type="hidden" name="deleteVar" value="">
497 <input type="hidden" name="insertVar" value="">
498 <input type="hidden" name="overrideUncheck" value="">
499 <input type="hidden" name="addVar" value="">
500 <input type="hidden" name="action" value="editConfig">
501 <input type="submit" class="editSaveButton" style="display: $saveDisplay" name="editAction" value="${EscHTML($Lang->{CfgEdit_Button_Save})}">
502
503 <script language="javascript" type="text/javascript">
504 <!--
505
506     function deleteSubmit(varName)
507     {
508         document.form1.deleteVar.value = varName;
509         document.form1.modified.value = 1;
510         document.form1.submit();
511         return;
512     }
513
514     function insertSubmit(varName)
515     {
516         document.form1.insertVar.value = varName;
517         document.form1.modified.value = 1;
518         document.form1.submit();
519         return;
520     }
521
522     function addSubmit(varName, checkKey)
523     {
524         if ( checkKey
525             && eval("document.form1.addVarKey_" + varName + ".value") == "" ) {
526             alert("New key must be non-empty");
527             return;
528         }
529         document.form1.addVar.value = varName;
530         document.form1.modified.value = 1;
531         document.form1.submit();
532         return;
533     }
534
535     function menuSubmit(menuName)
536     {
537         document.form1.newMenu.value = menuName;
538         document.form1.submit();
539     }
540
541     function varChange(varName)
542     {
543         document.form1.editAction.style.display = "block";
544         document.form1.modified.value = 1;
545     }
546
547     function checkboxChange(varName)
548     {
549         document.form1.editAction.style.display = "block";
550         document.form1.modified.value = 1;
551         // Do nothing if the checkbox is now set
552         if ( eval("document.form1.override_" + varName + ".checked") ) {
553             return false;
554         }
555         var allVars = {};
556         var varRE  = new RegExp("^v_z_(" + varName + ".*)");
557         var origRE = new RegExp("^orig_z_(" + varName + ".*)");
558         for ( var i = 0 ; i < document.form1.elements.length ; i++ ) {
559             var e = document.form1.elements[i];
560             var re;
561             if ( (re = varRE.exec(e.name)) != null ) {
562                 if ( allVars[re[1]] == null ) {
563                     allVars[re[1]] = 0;
564                 }
565                 allVars[re[1]]++;
566                 //debugMsg("found v_z_ match with " + re[1]);
567                 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
568             } else if ( (re = origRE.exec(e.name)) != null ) {
569                 if ( allVars[re[1]] == null ) {
570                     allVars[re[1]] = 0;
571                 }
572                 allVars[re[1]]--;
573                 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
574             }
575         }
576         var sameShape = 1;
577         for ( v in allVars ) {
578             if ( allVars[v] != 0 ) {
579                 //debugMsg("Not the same shape because of " + v);
580                 sameShape = 0;
581             } else {
582                 // copy the original variable values
583                 //debugMsg("setting " + v);
584                 eval("document.form1.v_z_" + v + ".value = document.form1.orig_z_" + v + ".value");
585             }
586         }
587         if ( sameShape ) {
588             return true;
589         } else {
590             // need to rebuild the form since the compound variable
591             // has changed shape
592             document.form1.overrideUncheck.value = varName;
593             document.form1.submit();
594             return false;
595         }
596     }
597
598     function checkboxSet(varName)
599     {
600         document.form1.editAction.style.display = "block";
601         document.form1.modified.value = 1;
602         eval("document.form1.override_" + varName + ".checked = 1;");
603         return false;
604     }
605
606     var debugCounter = 0;
607     function debugMsg(msg)
608     {
609         debugCounter++;
610         var t = document.createTextNode(debugCounter + ": " + msg);
611         var br = document.createElement("br");
612         var debug = document.getElementById("debug");
613         debug.appendChild(t);
614         debug.appendChild(br);
615     }
616
617     function displayHelp(varName)
618     {
619         var help = document.getElementById("id_" + varName);
620         help.style.display = help.style.display == "block" ? "none" : "block";
621     }
622
623 //-->
624 </script>
625
626 <span id="debug">$debugText</span>
627
628 EOF
629
630     $content .= <<EOF;
631 <table border="1" cellspacing="0">
632 EOF
633
634     my $doneParam = {};
635
636     #
637     # There is a special case of the user deleting just the field
638     # that has the error(s).  So if the delete variable is a match
639     # or parent to all the errors then ignore the errors.
640     #
641     if ( $In{deleteVar} ne "" && %$errors > 0 ) {
642         my $matchAll = 1;
643         foreach my $v ( keys(%$errors) ) {
644             if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_z_/ ) {
645                 $matchAll = 0;
646                 last;
647             }
648         }
649         $errors = {} if ( $matchAll );
650     }
651
652     my $isError = %$errors;
653
654     if ( !$isError && $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
655         my($mesg, $err);
656         if ( $host ne "" ) {
657             $hostConf = $bpc->ConfigDataRead($host) if ( !defined($hostConf) );
658             my %hostConf2 = %$hostConf;
659             foreach my $param ( keys(%$newConf) ) {
660                 if ( $override->{$param} ) {
661                     $hostConf->{$param} = $newConf->{$param}
662                 } else {
663                     delete($hostConf->{$param});
664                 }
665             }
666             $mesg = configDiffMesg($host, \%hostConf2, $hostConf);
667             $err .= $bpc->ConfigDataWrite($host, $hostConf);
668         } else {
669             $mainConf = $bpc->ConfigDataRead() if ( !defined($mainConf) );
670
671             my $hostsSave = [];
672             my($hostsNew, $allHosts, $copyConf);
673             foreach my $entry ( @{$newConf->{Hosts}} ) {
674                 next if ( $entry->{host} eq "" );
675                 $allHosts->{$entry->{host}} = 1;
676                 $allHosts->{$1} = 1 if ( $entry->{host} =~ /(.+?)\s*=/ );
677             }
678             foreach my $entry ( @{$newConf->{Hosts}} ) {
679                 next if ( $entry->{host} eq ""
680                            || defined($hostsNew->{$entry->{host}}) );
681                 if ( $entry->{host} =~ /(.+?)\s*=\s*(.+)/ ) {
682                     if ( defined($allHosts->{$2}) ) {
683                         $entry->{host} = $1;
684                         $copyConf->{$1} = $2;
685                     } else {
686                         my $fullHost = $entry->{host};
687                         my $copyHost = $2;
688                         $err .= eval("qq($Lang->{CfgEdit_Error_Copy_host_does_not_exist})");
689                     }
690                 }
691                 push(@$hostsSave, $entry);
692                 $hostsNew->{$entry->{host}} = $entry;
693             }
694             ($mesg, my $hostChange) = hostsDiffMesg($hostsNew);
695             $bpc->HostInfoWrite($hostsNew) if ( $hostChange );
696             foreach my $host ( keys(%$copyConf) ) {
697                 my $confData = $bpc->ConfigDataRead($copyConf->{$host});
698                 my $fromHost = $copyConf->{$host};
699                 $err  .= $bpc->ConfigDataWrite($host, $confData);
700                 $mesg .= eval("qq($Lang->{CfgEdit_Log_Copy_host_config})");
701             }
702
703             delete($newConf->{Hosts});
704             $mesg .= configDiffMesg(undef, $mainConf, $newConf);
705             $mainConf = { %$mainConf, %$newConf };
706             $err .= $bpc->ConfigDataWrite(undef, $mainConf);
707             $newConf->{Hosts} = $hostsSave;
708         }
709         if ( defined($err) ) {
710             $content .= <<EOF;
711 <tr><td colspan="2" class="border"><span class="editError">$err</span></td></tr>
712 EOF
713         }
714         $bpc->ServerConnect();
715         if ( $mesg ne "" ) {
716             (my $mesgBR = $mesg) =~ s/\n/<br>\n/g;
717              # uncomment this if you want the changes to be displayed
718 #            $content .= <<EOF;
719 #<tr><td colspan="2" class="border"><span class="editComment">$mesgBR</span></td></tr>
720 #EOF
721             foreach my $str ( split(/\n/, $mesg) ) {
722                 $bpc->ServerMesg("log $str") if ( $str ne "" );
723             }
724         }
725         #
726         # Tell the server to reload, unless we only changed
727         # a client config
728         #
729         $bpc->ServerMesg("server reload") if ( $host eq "" );
730     }
731
732     my @mask = @{$menuDisable{$menu}{mask} || []};
733
734     foreach my $paramInfo ( @{$ConfigMenu{$menu}{param}} ) {
735
736         my $param    = $paramInfo->{name};
737         my $disabled = shift(@mask);
738
739         next if ( $disabled || $menuDisable{$menu}{top} );
740         if ( ref($paramInfo->{visible}) eq "CODE"
741                         && !&{$paramInfo->{visible}}($newConf) ) {
742             next;
743         }
744
745         if ( defined($paramInfo->{text}) ) {
746             my $text = eval("qq($Lang->{$paramInfo->{text}})");
747             $content .= <<EOF;
748 <tr><td colspan="2" class="editHeader">$text</td></tr>
749 EOF
750             next;
751         }
752
753         #
754         # TODO: get parameter documentation
755         #
756         my $comment = "";
757         #$comment =~ s/\'//g;
758         #$comment =~ s/\"//g;
759         #$comment =~ s/\n/ /g;
760
761         $doneParam->{$param} = 1;
762
763         $content .= fieldEditBuild($ConfigMeta{$param},
764                               $param,
765                               $newConf->{$param},
766                               $errors,
767                               0,
768                               $comment,
769                               $isError,
770                               $paramInfo->{onchangeSubmit},
771                               defined($override) ? $param : undef,
772                               defined($override) ? $override->{$param} : undef
773                         );
774         if ( defined($paramInfo->{comment}) ) {
775             my $topDir = $bpc->TopDir;
776             my $text = eval("qq($Lang->{$paramInfo->{comment}})");
777             $content .= <<EOF;
778 <tr><td colspan="2" class="editComment">$text</td></tr>
779 EOF
780         }
781     }
782
783     #
784     # Emit any remaining errors - should not happen
785     #
786     foreach my $param ( sort(keys(%$errors)) ) {
787         $content .= <<EOF;
788 <tr><td colspan="2" class="border"><span class="editError">$errors->{$param}</span></td></tr>
789 EOF
790         delete($errors->{$param});
791     }
792
793     $content .= <<EOF;
794 </table>
795 EOF
796
797     #
798     # Emit all the remaining editable config settings as hidden values
799     #
800     foreach my $param ( keys(%ConfigMeta) ) {
801         next if ( $doneParam->{$param} );
802         next if ( $userHost
803                       && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
804                          || (!$PrivAdmin
805                              && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
806         $content .= fieldHiddenBuild($ConfigMeta{$param},
807                             $param,
808                             $newConf->{$param},
809                             "v"
810                         );
811         if ( defined($override) ) {
812             $content .= <<EOF;
813 <input type="hidden" name="override_$param" value="$override->{$param}">
814 EOF
815         }
816         $doneParam->{$param} = 1;
817     }
818
819     if ( defined($In{menu}) || $In{editAction} eq $Lang->{CfgEdit_Button_Save} ) {
820         if ( $In{editAction} eq $Lang->{CfgEdit_Button_Save}
821                 && !$userHost ) {
822             #
823             # Emit the new settings as orig_z_ parameters
824             #
825             $doneParam = {};
826             foreach my $param ( keys(%ConfigMeta) ) {
827                 next if ( $doneParam->{$param} );
828                 next if ( $userHost
829                           && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
830                              || (!$PrivAdmin
831                                 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
832                 $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
833                                         $param,
834                                         $newConf->{$param},
835                                         "orig",
836                                     );
837                 $doneParam->{$param} = 1;
838                 $In{modified} = 0;
839             }
840         } else {
841             #
842             # Just switching menus: copy all the orig_z_ input parameters
843             #
844             foreach my $var ( keys(%In) ) {
845                 next if ( $var !~ /^orig_z_/ );
846                 my $val = decode_utf8($In{$var});
847                 $contentHidden .= <<EOF;
848 <input type="hidden" name="$var" value="${EscHTML($val)}">
849 EOF
850             }
851         }
852     } else {
853         #
854         # First time: emit all the original config settings
855         #
856         $doneParam = {};
857         foreach my $param ( keys(%ConfigMeta) ) {
858             next if ( $doneParam->{$param} );
859             next if ( $userHost
860                           && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
861                              || (!$PrivAdmin
862                                 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
863             $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
864                                     $param,
865                                     $mainConf->{$param},
866                                     "orig",
867                                 );
868             $doneParam->{$param} = 1;
869         }
870     }
871
872     $content .= <<EOF;
873 $contentHidden
874 </form>
875 </tr>
876 </table>
877 EOF
878
879     Header("Config Edit", $content);
880     Trailer();
881 }
882
883 sub fieldHiddenBuild
884 {
885     my($type, $varName, $varValue, $prefix) = @_;
886     my $content;
887
888     $type = { type => $type } if ( ref($type) ne "HASH" );
889
890     if ( $type->{type} eq "list" ) {
891         $varValue = [] if ( !defined($varValue) );
892         $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
893
894         for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
895             $content .= fieldHiddenBuild($type->{child}, "${varName}_z_$i",
896                                          $varValue->[$i], $prefix);
897         }
898     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
899         $varValue = {} if ( ref($varValue) ne "HASH" );
900         my(@order, $childType);
901
902         if ( defined($type->{order}) ) {
903             @order = @{$type->{order}};
904         } elsif ( defined($type->{child}) ) {
905             @order = sort(keys(%{$type->{child}}));
906         } else {
907             @order = sort(keys(%$varValue));
908         }
909
910         foreach my $fld ( @order ) {
911             if ( defined($type->{child}) ) {
912                 $childType = $type->{child}{$fld};
913             } else {
914                 $childType = $type->{childType};
915                 #
916                 # emit list of fields since they are user-defined
917                 # rather than hard-coded
918                 #
919                 $content .= <<EOF;
920 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
921 EOF
922             }
923             $content .= fieldHiddenBuild($childType, "${varName}_z_$fld",
924                                          $varValue->{$fld}, $prefix);
925         }
926     } elsif ( $type->{type} eq "shortlist" ) {
927         $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
928         $varValue = join(", ", @$varValue);
929         $content .= <<EOF;
930 <input type="hidden" name="${prefix}_z_$varName" value="${EscHTML($varValue)}">
931 EOF
932     } else {
933         $content .= <<EOF;
934 <input type="hidden" name="${prefix}_z_$varName" value="${EscHTML($varValue)}">
935 EOF
936     }
937     return $content;
938 }
939
940 sub fieldEditBuild
941 {
942     my($type, $varName, $varValue, $errors, $level, $comment, $isError,
943        $onchangeSubmit, $overrideVar, $overrideSet) = @_;
944
945     my $content;
946     my $size = 50 - 10 * $level;
947     $type = { type => $type } if ( ref($type) ne "HASH" );
948
949     $size = $type->{size} if ( defined($type->{size}) );
950
951     #
952     # These fragments allow inline content to be turned on and off
953     #
954     # <tr><td colspan="2"><span id="id_$varName" style="display: none" class="editComment">$comment</span></td></tr>
955     # <tr><td class="border"><a href="javascript: displayHelp('$varName')">$varName</a>
956     #
957
958     if ( $level == 0 ) {
959         my $lcVarName = lc($varName);
960         $content .= <<EOF;
961 <tr><td class="border"><a href="?action=view&type=docs#item_%24conf%7b$lcVarName%7d">$varName</a>
962 EOF
963         if ( defined($overrideVar) ) {
964             my $override_checked = "";
965             if ( !$isError && $In{deleteVar}      =~ /^\Q${varName}_z_/
966                    || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_z_|$)/
967                    || !$isError && $In{addVar}    =~ /^\Q${varName}\E(_z_|$)/ ) {
968                 $overrideSet = 1;
969             }
970             if ( $overrideSet ) {
971                 $override_checked = "checked";
972             }
973             $content .= <<EOF;
974 <br><input type="checkbox" name="override_$varName" $override_checked value="1" onClick="checkboxChange('$varName')">\&nbsp;${EscHTML($Lang->{CfgEdit_Button_Override})}
975 EOF
976         }
977         $content .= "</td>\n";
978     }
979
980     if ( $type->{type} eq "list" ) {
981         $content .= "<td class=\"border\">\n";
982         $varValue = [] if ( !defined($varValue) );
983         $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
984         if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_z_\E(\d+)$/
985                 && $1 < @$varValue ) {
986             #
987             # User deleted entry in this array
988             #
989             splice(@$varValue, $1, 1) if ( @$varValue > 1 || $type->{emptyOk} );
990             $In{deleteVar} = "";
991         }
992         if ( !$isError && $In{insertVar} =~ /^\Q${varName}_z_\E(\d+)$/
993                 && $1 < @$varValue ) {
994             #
995             # User inserted entry in this array
996             #
997             splice(@$varValue, $1, 0, "")
998                         if ( @$varValue > 1 || $type->{emptyOk} );
999             $In{insertVar} = "";
1000         }
1001         if ( !$isError && $In{addVar} eq $varName ) {
1002             #
1003             # User added entry to this array
1004             #
1005             push(@$varValue, undef);
1006             $In{addVar} = "";
1007         }
1008         $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1009
1010         if ( ref($type) eq "HASH" && ref($type->{child}) eq "HASH"
1011                     && $type->{child}{type} eq "horizHash" ) {
1012             my @order;
1013             if ( defined($type->{child}{order}) ) {
1014                 @order = @{$type->{child}{order}};
1015             } else {
1016                 @order = sort(keys(%{$type->{child}{child}}));
1017             }
1018             $content .= "<tr><td class=\"border\"></td>\n";
1019             for ( my $i = 0 ; $i < @order ; $i++ ) {
1020                 $content .= "<td class=\"border\">$order[$i]</td>\n";
1021             }
1022             $content .= "</tr>\n";
1023             for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1024                 if ( @$varValue > 1 || $type->{emptyOk} ) {
1025                     $content .= <<EOF;
1026 <td class="border">
1027 <input type="button" name="del_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1028     onClick="deleteSubmit('${varName}_z_$i')">
1029 </td>
1030 EOF
1031                 }
1032                 $content .= fieldEditBuild($type->{child}, "${varName}_z_$i",
1033                                   $varValue->[$i], $errors, $level + 1, undef,
1034                                   $isError, $onchangeSubmit,
1035                                   $overrideVar, $overrideSet);
1036                 $content .= "</tr>\n";
1037             }
1038         } else {
1039             for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1040                 $content .= <<EOF;
1041 <tr><td class="border">
1042 <input type="button" name="ins_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Insert})}"
1043     onClick="insertSubmit('${varName}_z_$i')">
1044 EOF
1045                 if ( @$varValue > 1 || $type->{emptyOk} ) {
1046                     $content .= <<EOF;
1047 <input type="button" name="del_${varName}_z_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1048     onClick="deleteSubmit('${varName}_z_$i')">
1049 EOF
1050                 }
1051                 $content .= "</td>\n";
1052                 $content .= fieldEditBuild($type->{child}, "${varName}_z_$i",
1053                                     $varValue->[$i], $errors, $level + 1, undef,
1054                                     $isError, $onchangeSubmit,
1055                                     $overrideVar, $overrideSet);
1056                 $content .= "</tr>\n";
1057             }
1058         }
1059         $content .= <<EOF;
1060 <tr><td class="border"><input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}"
1061     onClick="addSubmit('$varName')"></td></tr>
1062 </table>
1063 EOF
1064         $content .= "</td>\n";
1065     } elsif ( $type->{type} eq "hash" ) {
1066         $content .= "<td class=\"border\">\n";
1067         $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1068         $varValue = {} if ( ref($varValue) ne "HASH" );
1069
1070         if ( !$isError && !$type->{noKeyEdit}
1071                         && $In{deleteVar} !~ /^\Q${varName}_z_\E.*_z_/
1072                         && $In{deleteVar} =~ /^\Q${varName}_z_\E(\w+)$/ ) {
1073             #
1074             # User deleted entry in this hash
1075             #
1076             delete($varValue->{$1}) if ( keys(%$varValue) > 1
1077                                             || $type->{emptyOk} );
1078             $In{deleteVar} = "";
1079         }
1080         if ( !$isError && !defined($type->{child})
1081                         && $In{addVar} eq $varName ) {
1082             #
1083             # User added entry to this array
1084             #
1085             $varValue->{$In{"addVarKey_$varName"}} = ""
1086                         if ( !defined($varValue->{$In{"addVarKey_$varName"}}) );
1087             $In{addVar} = "";
1088         }
1089         my(@order, $childType);
1090
1091         if ( defined($type->{order}) ) {
1092             @order = @{$type->{order}};
1093         } elsif ( defined($type->{child}) ) {
1094             @order = sort(keys(%{$type->{child}}));
1095         } else {
1096             @order = sort(keys(%$varValue));
1097         }
1098
1099         foreach my $fld ( @order ) {
1100             $content .= <<EOF;
1101 <tr><td class="border">$fld
1102 EOF
1103             if ( !$type->{noKeyEdit}
1104                     && (keys(%$varValue) > 1 || $type->{emptyOk}) ) {
1105                 $content .= <<EOF;
1106 <input type="submit" name="del_${varName}_z_$fld" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1107         onClick="deleteSubmit('${varName}_z_$fld')">
1108 EOF
1109             }
1110             if ( defined($type->{child}) ) {
1111                 $childType = $type->{child}{$fld};
1112             } else {
1113                 $childType = $type->{childType};
1114                 #
1115                 # emit list of fields since they are user-defined
1116                 # rather than hard-coded
1117                 #
1118                 $content .= <<EOF;
1119 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1120 EOF
1121             }
1122             $content .= "</td>\n";
1123             $content .= fieldEditBuild($childType, "${varName}_z_$fld",
1124                             $varValue->{$fld}, $errors, $level + 1, undef,
1125                             $isError, $onchangeSubmit,
1126                             $overrideVar, $overrideSet);
1127             $content .= "</tr>\n";
1128         }
1129
1130         if ( !$type->{noKeyEdit} ) {
1131             $content .= <<EOF;
1132 <tr><td class="border" colspan="2">
1133 $Lang->{CfgEdit_Button_New_Key}: <input type="text" class="editTextInput" name="addVarKey_$varName" size="20" maxlength="256" value="">
1134 <input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}" onClick="addSubmit('$varName', 1)">
1135 </td></tr>
1136 EOF
1137         }
1138         $content .= "</table>\n";
1139         $content .= "</td>\n";
1140     } elsif ( $type->{type} eq "horizHash" ) {
1141         $varValue = {} if ( ref($varValue) ne "HASH" );
1142         my(@order, $childType);
1143
1144         if ( defined($type->{order}) ) {
1145             @order = @{$type->{order}};
1146         } elsif ( defined($type->{child}) ) {
1147             @order = sort(keys(%{$type->{child}}));
1148         } else {
1149             @order = sort(keys(%$varValue));
1150         }
1151
1152         foreach my $fld ( @order ) {
1153             if ( defined($type->{child}) ) {
1154                 $childType = $type->{child}{$fld};
1155             } else {
1156                 $childType = $type->{childType};
1157                 #
1158                 # emit list of fields since they are user-defined
1159                 # rather than hard-coded
1160                 #
1161                 $content .= <<EOF;
1162 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1163 EOF
1164             }
1165             $content .= fieldEditBuild($childType, "${varName}_z_$fld",
1166                             $varValue->{$fld}, $errors, $level + 1, undef,
1167                             $isError, $onchangeSubmit,
1168                             $overrideVar, $overrideSet);
1169         }
1170     } else {
1171         $content .= "<td class=\"border\">\n";
1172         if ( $isError ) {
1173             #
1174             # If there was an error, we use the original post values
1175             # in %In, rather than the parsed values in $varValue.
1176             # This is so that the user's erroneous input is preserved.
1177             #
1178             $varValue = $In{"v_z_$varName"} if ( defined($In{"v_z_$varName"}) );
1179         }
1180         if ( defined($errors->{$varName}) ) {
1181             $content .= <<EOF;
1182 <span class="editError">$errors->{$varName}</span><br>
1183 EOF
1184             delete($errors->{$varName});
1185         }
1186         my $onChange;
1187         if ( defined($overrideVar) ) {
1188             $onChange .= "checkboxSet('$overrideVar');";
1189         } else {
1190             $onChange .= "varChange('$overrideVar');";
1191         }
1192         if ( $onchangeSubmit ) {
1193             $onChange .= "document.form1.submit();";
1194         }
1195         if ( $onChange ne "" ) {
1196             $onChange = " onChange=\"$onChange\"";
1197         }
1198         if ( $varValue !~ /\n/ &&
1199                 ($type->{type} eq "integer"
1200                     || $type->{type} eq "string"
1201                     || $type->{type} eq "execPath"
1202                     || $type->{type} eq "shortlist"
1203                     || $type->{type} eq "float") ) {
1204             # simple input box
1205             if ( $type->{type} eq "shortlist" ) {
1206                 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
1207                 $varValue = join(", ", @$varValue);
1208             }
1209             my $textType = ($varName =~ /Passwd/) ? "password" : "text";
1210             $content .= <<EOF;
1211 <input type="$textType" class="editTextInput" name="v_z_$varName" size="$size" maxlength="256" value="${EscHTML($varValue)}"$onChange>
1212 EOF
1213         } elsif ( $type->{type} eq "boolean" ) {
1214             # checkbox
1215             my $checked = "checked" if ( $varValue );
1216             $content .= <<EOF;
1217 <input type="checkbox" name="v_z_$varName" $checked value="1"$onChange>
1218 EOF
1219         } elsif ( $type->{type} eq "select" ) {
1220             $content .= <<EOF;
1221 <select name="v_z_$varName"$onChange>
1222 EOF
1223             foreach my $option ( @{$type->{values}} ) {
1224                 my $sel = " selected" if ( $varValue eq $option );
1225                 $content .= "<option$sel>$option</option>\n";
1226             }
1227             $content .= "</select>\n";
1228         } else {
1229             # multi-line text area - count number of lines
1230             my $rowCnt = $varValue =~ tr/\n//;
1231             $rowCnt = 1 if ( $rowCnt < 1 );
1232             $content .= <<EOF;
1233 <textarea name="v_z_$varName" class="editTextArea" cols="$size" rows="$rowCnt"$onChange>${EscHTML($varValue)}</textarea>
1234 EOF
1235         }
1236         $content .= "</td>\n";
1237     }
1238     return $content;
1239 }
1240
1241 sub errorCheck
1242 {
1243     my $errors = {};
1244
1245     foreach my $param ( keys(%ConfigMeta) ) {
1246         fieldErrorCheck($ConfigMeta{$param}, $param, $errors);
1247     }
1248     return $errors;
1249 }
1250
1251 sub fieldErrorCheck
1252 {
1253     my($type, $varName, $errors) = @_;
1254
1255     $type = { type => $type } if ( ref($type) ne "HASH" );
1256
1257     if ( $type->{type} eq "list" ) {
1258         for ( my $i = 0 ; ; $i++ ) {
1259             last if ( fieldErrorCheck($type->{child}, "${varName}_z_$i", $errors) );
1260         }
1261     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1262         my(@order, $childType);
1263         my $ret;
1264
1265         if ( defined($type->{order}) ) {
1266             @order = @{$type->{order}};
1267         } elsif ( defined($type->{child}) ) {
1268             @order = sort(keys(%{$type->{child}}));
1269         } else {
1270             @order = split(/\0/, $In{"vflds.$varName"});
1271         }
1272         foreach my $fld ( @order ) {
1273             if ( defined($type->{child}) ) {
1274                 $childType = $type->{child}{$fld};
1275             } else {
1276                 $childType = $type->{childType};
1277             }
1278             $ret ||= fieldErrorCheck($childType, "${varName}_z_$fld", $errors);
1279         }
1280         return $ret;
1281     } else {
1282         $In{"v_z_$varName"} = "0" if ( $type->{type} eq "boolean"
1283                                         && $In{"v_z_$varName"} eq "" );
1284
1285         return 1 if ( !exists($In{"v_z_$varName"}) );
1286
1287         (my $var = $varName) =~ s/_z_/./g;
1288
1289         if ( $type->{type} eq "integer"
1290                 || $type->{type} eq "boolean" ) {
1291             if ( $In{"v_z_$varName"} !~ /^-?\d+\s*$/s
1292                             && $In{"v_z_$varName"} ne "" ) {
1293                 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_an_integer}}");
1294             }
1295         } elsif ( $type->{type} eq "float" ) {
1296             if ( $In{"v_z_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s
1297                             && $In{"v_z_$varName"} ne "" ) {
1298                 $errors->{$varName}
1299                         = eval("qq{$Lang->{CfgEdit_Error__must_be_real_valued_number}}");
1300             }
1301         } elsif ( $type->{type} eq "shortlist" ) {
1302             my @vals = split(/[,\s]+/, $In{"v_z_$varName"});
1303             for ( my $i = 0 ; $i < @vals ; $i++ ) {
1304                 if ( $type->{child} eq "integer"
1305                         && $vals[$i] !~ /^-?\d+\s*$/s
1306                         && $vals[$i] ne "" ) {
1307                     my $k = $i + 1;
1308                     $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_an_integer}}");
1309                 } elsif ( $type->{child} eq "float"
1310                         && $vals[$i] !~ /^-?\d*(\.\d*)?\s*$/s
1311                         && $vals[$i] ne "" ) {
1312                     my $k = $i + 1;
1313                     $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_real_valued_number}}");
1314                 }
1315             }
1316         } elsif ( $type->{type} eq "select" ) {
1317             my $match = 0;
1318             foreach my $option ( @{$type->{values}} ) {
1319                 if ( $In{"v_z_$varName"} eq $option ) {
1320                     $match = 1;
1321                     last;
1322                 }
1323             }
1324             $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_valid_option}}")
1325                             if ( !$match );
1326         } elsif ( $type->{type} eq "execPath" ) {
1327             if ( $In{"v_z_$varName"} ne "" && !-x $In{"v_z_$varName"} ) {
1328                 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_executable_program}}");
1329             }
1330         } else {
1331             #
1332             # $type->{type} eq "string": no error checking
1333             #
1334         }
1335     }
1336     return 0;
1337 }
1338
1339 sub inputParse
1340 {
1341     my($bpc, $userHost) = @_;
1342     my $conf     = {};
1343     my $override = {};
1344
1345     foreach my $param ( keys(%ConfigMeta) ) {
1346         my $value;
1347         next if ( $userHost
1348                       && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
1349                          || (!$PrivAdmin
1350                             && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
1351         fieldInputParse($ConfigMeta{$param}, $param, \$value);
1352         $conf->{$param}     = $value;
1353         $override->{$param} = $In{"override_$param"};
1354     }
1355     return ($conf, $override);
1356 }
1357
1358 sub fieldInputParse
1359 {
1360     my($type, $varName, $value) = @_;
1361
1362     $type = { type => $type } if ( ref($type) ne "HASH" );
1363
1364     if ( $type->{type} eq "list" ) {
1365         $$value = [];
1366         for ( my $i = 0 ; ; $i++ ) {
1367             my $val;
1368             last if ( fieldInputParse($type->{child}, "${varName}_z_$i", \$val) );
1369             push(@$$value, $val);
1370         }
1371         $$value = undef if ( $type->{undefIfEmpty} && @$$value == 0 );
1372     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1373         my(@order, $childType);
1374         my $ret;
1375         $$value = {};
1376
1377         if ( defined($type->{order}) ) {
1378             @order = @{$type->{order}};
1379         } elsif ( defined($type->{child}) ) {
1380             @order = sort(keys(%{$type->{child}}));
1381         } else {
1382             @order = split(/\0/, $In{"vflds.$varName"});
1383         }
1384
1385         foreach my $fld ( @order ) {
1386             my $val;
1387             if ( defined($type->{child}) ) {
1388                 $childType = $type->{child}{$fld};
1389             } else {
1390                 $childType = $type->{childType};
1391             }
1392             $ret ||= fieldInputParse($childType, "${varName}_z_$fld", \$val);
1393             last if ( $ret );
1394             $$value->{$fld} = $val;
1395         }
1396         return $ret;
1397     } else {
1398         if ( $type->{type} eq "boolean" ) {
1399             $$value = 0 + $In{"v_z_$varName"};
1400         } elsif ( !exists($In{"v_z_$varName"}) ) {
1401             return 1;
1402         }
1403
1404         if ( $type->{type} eq "integer" ) {
1405             $$value = 0 + $In{"v_z_$varName"};
1406         } elsif ( $type->{type} eq "float" ) {
1407             $$value = 0 + $In{"v_z_$varName"};
1408         } elsif ( $type->{type} eq "shortlist" ) {
1409             $$value = [split(/[,\s]+/, $In{"v_z_$varName"})];
1410             if ( $type->{child} eq "float"
1411                     || $type->{child} eq "integer"
1412                     || $type->{child} eq "boolean" ) {
1413                 foreach ( @$$value ) {
1414                     $_ += 0;
1415                 }
1416             }
1417         } else {
1418             $$value = decode_utf8($In{"v_z_$varName"});
1419             $$value =~ s/\r\n/\n/g;
1420         }
1421         $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" );
1422     }
1423     return 0;
1424 }
1425
1426 sub configDiffMesg
1427 {
1428     my($host, $oldConf, $newConf) = @_;
1429     my $mesg;
1430     my $conf;
1431
1432     if ( $host ne "" ) {
1433         $conf = "host $host config";
1434     } else {
1435         $conf = "main config";
1436     }
1437
1438     foreach my $p ( keys(%ConfigMeta) ) {
1439         if ( !exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1440             next;
1441         } elsif ( exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1442             $mesg .= eval("qq($Lang->{CfgEdit_Log_Delete_param})");
1443         } elsif ( !exists($oldConf->{$p}) && exists($newConf->{$p}) ) {
1444             my $dump = Data::Dumper->new([$newConf->{$p}]);
1445             $dump->Indent(0);
1446             $dump->Sortkeys(1);
1447             $dump->Terse(1);
1448             my $value = $dump->Dump;
1449             $value =~ s/\n/\\n/g;
1450             $value =~ s/\r/\\r/g;
1451             $mesg .= eval("qq($Lang->{CfgEdit_Log_Add_param_value})");
1452         } else {
1453             my $dump = Data::Dumper->new([$newConf->{$p}]);
1454             $dump->Indent(0);
1455             $dump->Sortkeys(1);
1456             $dump->Terse(1);
1457             my $valueNew = $dump->Dump;
1458
1459             my $v = $oldConf->{$p};
1460             if ( ref($newConf->{$p}) eq "ARRAY" && ref($v) eq "" ) {
1461                 $v = [$v];
1462             }
1463             $dump = Data::Dumper->new([$v]);
1464             $dump->Indent(0);
1465             $dump->Sortkeys(1);
1466             $dump->Terse(1);
1467             my $valueOld = $dump->Dump;
1468
1469             (my $valueNew2 = $valueNew) =~ s/['\n\r]//g;
1470             (my $valueOld2 = $valueOld) =~ s/['\n\r]//g;
1471
1472             next if ( $valueOld2 eq $valueNew2 );
1473
1474             $valueNew =~ s/\n/\\n/g;
1475             $valueOld =~ s/\n/\\n/g;
1476             $valueNew =~ s/\r/\\r/g;
1477             $valueOld =~ s/\r/\\r/g;
1478
1479             $mesg .= eval("qq($Lang->{CfgEdit_Log_Change_param_value})");
1480         }
1481     }
1482     return $mesg;
1483 }
1484
1485 sub hostsDiffMesg
1486 {
1487     my($hostsNew) = @_;
1488     my $hostsOld = $bpc->HostInfoRead();
1489     my($mesg, $hostChange);
1490
1491     foreach my $host ( keys(%$hostsOld) ) {
1492         if ( !defined($hostsNew->{$host}) ) {
1493             $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Delete})");
1494             $hostChange++;
1495             next;
1496         }
1497         foreach my $key ( keys(%{$hostsNew->{$host}}) ) {
1498             next if ( $hostsNew->{$host}{$key} eq $hostsOld->{$host}{$key} );
1499             my $valueOld = $hostsOld->{$host}{$key};
1500             my $valueNew = $hostsNew->{$host}{$key};
1501             $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Change})");
1502             $hostChange++;
1503         }
1504     }
1505
1506     foreach my $host ( keys(%$hostsNew) ) {
1507         next if ( defined($hostsOld->{$host}) );
1508         my $dump = Data::Dumper->new([$hostsNew->{$host}]);
1509         $dump->Indent(0);
1510         $dump->Sortkeys(1);
1511         $dump->Terse(1);
1512         my $value = $dump->Dump;
1513         $value =~ s/\n/\\n/g;
1514         $value =~ s/\r/\\r/g;
1515         $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Add})");
1516         $hostChange++;
1517     }
1518     return ($mesg, $hostChange);
1519 }
1520
1521 1;