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