* Removed default paths from conf/config.pl so configure.pl will
[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.0beta2, released 18 Nov 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{saveAction} eq "Save" ) {
361         $errors = errorCheck();
362         if ( %$errors ) {
363             #
364             # If there are errors, then go back to the same menu
365             #
366             $In{saveAction} = "";
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_zZ_(\Q$var\E(_zZ_.*|$))/ );
377                 delete($In{$v}) if ( !defined($In{"orig_zZ_$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{saveAction} ne "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 $saveStyle = "";
479     my $saveColor = "#ff0000";
480     
481     if ( $In{modified} && $In{saveAction} ne "Save" && !%$errors ) {
482         $saveStyle = "style=\"color:$saveColor\"";
483     } else {
484         $In{modified} = 0;
485     }
486
487     #
488     # Add action and host to the URL so the nav bar link is
489     # highlighted
490     #
491     my $url = "$MyURL?action=editConfig";
492     $url .= "&host=$host" if ( $host ne "" );
493     $content .= <<EOF;
494 <table border="0" cellpadding="2">
495 <tr>$groupText</tr>
496 <tr>
497 <form method="post" name="editForm" action="$url">
498 <input type="hidden" name="host" value="$host">
499 <input type="hidden" name="menu" value="$menu">
500 <input type="hidden" name="newMenu" value="">
501 <input type="hidden" name="modified" value="$In{modified}">
502 <input type="hidden" name="deleteVar" value="">
503 <input type="hidden" name="insertVar" value="">
504 <input type="hidden" name="overrideUncheck" value="">
505 <input type="hidden" name="addVar" value="">
506 <input type="hidden" name="action" value="editConfig">
507 <input type="hidden" name="saveAction" value="">
508 <input type="button" class="editSaveButton" name="editAction"
509     value="${EscHTML($Lang->{CfgEdit_Button_Save})}" $saveStyle
510     onClick="saveSubmit();">
511 <p>
512
513 <script language="javascript" type="text/javascript">
514 <!--
515
516     function saveSubmit()
517     {
518         if ( document.editForm.modified.value != 0 ) {
519             document.editForm.saveAction.value = 'Save';
520             document.editForm.submit();
521         }
522         return false;
523     }
524
525     function deleteSubmit(varName)
526     {
527         document.editForm.deleteVar.value = varName;
528         document.editForm.modified.value = 1;
529         document.editForm.submit();
530         return;
531     }
532
533     function insertSubmit(varName)
534     {
535         document.editForm.insertVar.value = varName;
536         document.editForm.modified.value = 1;
537         document.editForm.submit();
538         return;
539     }
540
541     function addSubmit(varName, checkKey)
542     {
543         if ( checkKey
544             && eval("document.editForm.addVarKey_" + varName + ".value") == "" ) {
545             alert("New key must be non-empty");
546             return;
547         }
548         document.editForm.addVar.value = varName;
549         document.editForm.modified.value = 1;
550         document.editForm.submit();
551         return;
552     }
553
554     function menuSubmit(menuName)
555     {
556         document.editForm.newMenu.value = menuName;
557         document.editForm.submit();
558     }
559
560     function varChange(varName)
561     {
562         document.editForm.modified.value = 1;
563         document.editForm.editAction.style.color = '$saveColor';
564     }
565
566     function checkboxChange(varName)
567     {
568         document.editForm.modified.value = 1;
569         document.editForm.editAction.style.color = '$saveColor';
570         // Do nothing if the checkbox is now set
571         if ( eval("document.editForm.override_" + varName + ".checked") ) {
572             return false;
573         }
574         var allVars = {};
575         var varRE  = new RegExp("^v_zZ_(" + varName + ".*)");
576         var origRE = new RegExp("^orig_zZ_(" + varName + ".*)");
577         for ( var i = 0 ; i < document.editForm.elements.length ; i++ ) {
578             var e = document.editForm.elements[i];
579             var re;
580             if ( (re = varRE.exec(e.name)) != null ) {
581                 if ( allVars[re[1]] == null ) {
582                     allVars[re[1]] = 0;
583                 }
584                 allVars[re[1]]++;
585                 //debugMsg("found v_zZ_ match with " + re[1]);
586                 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
587             } else if ( (re = origRE.exec(e.name)) != null ) {
588                 if ( allVars[re[1]] == null ) {
589                     allVars[re[1]] = 0;
590                 }
591                 allVars[re[1]]--;
592                 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
593             }
594         }
595         var sameShape = 1;
596         for ( v in allVars ) {
597             if ( allVars[v] != 0 ) {
598                 //debugMsg("Not the same shape because of " + v);
599                 sameShape = 0;
600             } else {
601                 // copy the original variable values
602                 //debugMsg("setting " + v);
603                 eval("document.editForm.v_zZ_" + v + ".value = document.editForm.orig_zZ_" + v + ".value");
604             }
605         }
606         if ( sameShape ) {
607             return true;
608         } else {
609             // need to rebuild the form since the compound variable
610             // has changed shape
611             document.editForm.overrideUncheck.value = varName;
612             document.editForm.submit();
613             return false;
614         }
615     }
616
617     function checkboxSet(varName)
618     {
619         document.editForm.modified.value = 1;
620         document.editForm.editAction.style.color = '$saveColor';
621         eval("document.editForm.override_" + varName + ".checked = 1;");
622         return false;
623     }
624
625     var debugCounter = 0;
626     function debugMsg(msg)
627     {
628         debugCounter++;
629         var t = document.createTextNode(debugCounter + ": " + msg);
630         var br = document.createElement("br");
631         var debug = document.getElementById("debug");
632         debug.appendChild(t);
633         debug.appendChild(br);
634     }
635
636     function displayHelp(varName)
637     {
638         var help = document.getElementById("id_" + varName);
639         help.style.display = help.style.display == "block" ? "none" : "block";
640     }
641
642 //-->
643 </script>
644
645 <span id="debug">$debugText</span>
646
647 EOF
648
649     $content .= <<EOF;
650 <table border="1" cellspacing="0">
651 EOF
652
653     my $doneParam = {};
654     my $tblContent;
655
656     #
657     # There is a special case of the user deleting just the field
658     # that has the error(s).  So if the delete variable is a match
659     # or parent to all the errors then ignore the errors.
660     #
661     if ( $In{deleteVar} ne "" && %$errors > 0 ) {
662         my $matchAll = 1;
663         foreach my $v ( keys(%$errors) ) {
664             if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_zZ_/ ) {
665                 $matchAll = 0;
666                 last;
667             }
668         }
669         $errors = {} if ( $matchAll );
670     }
671
672     my $isError = %$errors;
673
674     if ( !$isError && $In{saveAction} eq "Save" ) {
675         my($mesg, $err);
676         if ( $host ne "" ) {
677             $hostConf = $bpc->ConfigDataRead($host) if ( !defined($hostConf) );
678             my %hostConf2 = %$hostConf;
679             foreach my $param ( keys(%$newConf) ) {
680                 if ( $override->{$param} ) {
681                     $hostConf->{$param} = $newConf->{$param}
682                 } else {
683                     delete($hostConf->{$param});
684                 }
685             }
686             $mesg = configDiffMesg($host, \%hostConf2, $hostConf);
687             $err .= $bpc->ConfigDataWrite($host, $hostConf);
688         } else {
689             $mainConf = $bpc->ConfigDataRead() if ( !defined($mainConf) );
690
691             my $hostsSave = [];
692             my($hostsNew, $allHosts, $copyConf);
693             foreach my $entry ( @{$newConf->{Hosts}} ) {
694                 next if ( $entry->{host} eq "" );
695                 $allHosts->{$entry->{host}} = 1;
696                 $allHosts->{$1} = 1 if ( $entry->{host} =~ /(.+?)\s*=/ );
697             }
698             foreach my $entry ( @{$newConf->{Hosts}} ) {
699                 next if ( $entry->{host} eq ""
700                            || defined($hostsNew->{$entry->{host}}) );
701                 if ( $entry->{host} =~ /(.+?)\s*=\s*(.+)/ ) {
702                     if ( defined($allHosts->{$2}) ) {
703                         $entry->{host} = $1;
704                         $copyConf->{$1} = $2;
705                     } else {
706                         my $fullHost = $entry->{host};
707                         my $copyHost = $2;
708                         $err .= eval("qq($Lang->{CfgEdit_Error_Copy_host_does_not_exist})");
709                     }
710                 }
711                 push(@$hostsSave, $entry);
712                 $hostsNew->{$entry->{host}} = $entry;
713             }
714             ($mesg, my $hostChange) = hostsDiffMesg($hostsNew);
715             $bpc->HostInfoWrite($hostsNew) if ( $hostChange );
716             foreach my $host ( keys(%$copyConf) ) {
717                 my $confData = $bpc->ConfigDataRead($copyConf->{$host});
718                 my $fromHost = $copyConf->{$host};
719                 $err  .= $bpc->ConfigDataWrite($host, $confData);
720                 $mesg .= eval("qq($Lang->{CfgEdit_Log_Copy_host_config})");
721             }
722
723             delete($newConf->{Hosts});
724             $mesg .= configDiffMesg(undef, $mainConf, $newConf);
725             $mainConf = { %$mainConf, %$newConf };
726             $err .= $bpc->ConfigDataWrite(undef, $mainConf);
727             $newConf->{Hosts} = $hostsSave;
728         }
729         if ( defined($err) ) {
730             $tblContent .= <<EOF;
731 <tr><td colspan="2" class="border"><span class="editError">$err</span></td></tr>
732 EOF
733         }
734         $bpc->ServerConnect();
735         if ( $mesg ne "" ) {
736             (my $mesgBR = $mesg) =~ s/\n/<br>\n/g;
737              # uncomment this if you want the changes to be displayed
738 #            $tblContent .= <<EOF;
739 #<tr><td colspan="2" class="border"><span class="editComment">$mesgBR</span></td></tr>
740 #EOF
741             foreach my $str ( split(/\n/, $mesg) ) {
742                 $bpc->ServerMesg("log $str") if ( $str ne "" );
743             }
744         }
745         #
746         # Tell the server to reload, unless we only changed
747         # a client config
748         #
749         $bpc->ServerMesg("server reload") if ( $host eq "" );
750     }
751
752     my @mask = @{$menuDisable{$menu}{mask} || []};
753
754     foreach my $paramInfo ( @{$ConfigMenu{$menu}{param}} ) {
755
756         my $param    = $paramInfo->{name};
757         my $disabled = shift(@mask);
758
759         next if ( $disabled || $menuDisable{$menu}{top} );
760         if ( ref($paramInfo->{visible}) eq "CODE"
761                         && !&{$paramInfo->{visible}}($newConf) ) {
762             next;
763         }
764
765         if ( defined($paramInfo->{text}) ) {
766             my $text = eval("qq($Lang->{$paramInfo->{text}})");
767             $tblContent .= <<EOF;
768 <tr><td colspan="2" class="editHeader">$text</td></tr>
769 EOF
770             next;
771         }
772
773         #
774         # TODO: get parameter documentation
775         #
776         my $comment = "";
777         #$comment =~ s/\'//g;
778         #$comment =~ s/\"//g;
779         #$comment =~ s/\n/ /g;
780
781         $doneParam->{$param} = 1;
782
783         $tblContent .= fieldEditBuild($ConfigMeta{$param},
784                               $param,
785                               $newConf->{$param},
786                               $errors,
787                               0,
788                               $comment,
789                               $isError,
790                               $paramInfo->{onchangeSubmit},
791                               defined($override) ? $param : undef,
792                               defined($override) ? $override->{$param} : undef
793                         );
794         if ( defined($paramInfo->{comment}) ) {
795             my $topDir = $bpc->TopDir;
796             my $text = eval("qq($Lang->{$paramInfo->{comment}})");
797             $tblContent .= <<EOF;
798 <tr><td colspan="2" class="editComment">$text</td></tr>
799 EOF
800         }
801     }
802
803     #
804     # Emit a summary of all the errors
805     #
806     my $errorTxt;
807
808     if ( %$errors ) {
809         $errorTxt .= <<EOF;
810 <tr><td colspan="2" class="border"><span class="editError">$Lang->{CfgEdit_Error_No_Save}</span></td></tr>
811 EOF
812     }
813
814     foreach my $param ( sort(keys(%$errors)) ) {
815         $errorTxt .= <<EOF;
816 <tr><td colspan="2" class="border"><span class="editError">$errors->{$param}</span></td></tr>
817 EOF
818     }
819
820     $content .= <<EOF;
821 $errorTxt
822 $tblContent
823 </table>
824 EOF
825
826     #
827     # Emit all the remaining editable config settings as hidden values
828     #
829     foreach my $param ( keys(%ConfigMeta) ) {
830         next if ( $doneParam->{$param} );
831         next if ( $userHost
832                       && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
833                          || (!$PrivAdmin
834                              && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
835         $content .= fieldHiddenBuild($ConfigMeta{$param},
836                             $param,
837                             $newConf->{$param},
838                             "v"
839                         );
840         if ( defined($override) ) {
841             $content .= <<EOF;
842 <input type="hidden" name="override_$param" value="$override->{$param}">
843 EOF
844         }
845         $doneParam->{$param} = 1;
846     }
847
848     if ( defined($In{menu}) || $In{saveAction} eq "Save" ) {
849         if ( $In{saveAction} eq "Save" && !$userHost ) {
850             #
851             # Emit the new settings as orig_zZ_ parameters
852             #
853             $doneParam = {};
854             foreach my $param ( keys(%ConfigMeta) ) {
855                 next if ( $doneParam->{$param} );
856                 next if ( $userHost
857                           && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
858                              || (!$PrivAdmin
859                                 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
860                 $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
861                                         $param,
862                                         $newConf->{$param},
863                                         "orig",
864                                     );
865                 $doneParam->{$param} = 1;
866                 $In{modified} = 0;
867             }
868         } else {
869             #
870             # Just switching menus: copy all the orig_zZ_ input parameters
871             #
872             foreach my $var ( keys(%In) ) {
873                 next if ( $var !~ /^orig_zZ_/ );
874                 my $val = decode_utf8($In{$var});
875                 $contentHidden .= <<EOF;
876 <input type="hidden" name="$var" value="${EscHTML($val)}">
877 EOF
878             }
879         }
880     } else {
881         #
882         # First time: emit all the original config settings
883         #
884         $doneParam = {};
885         foreach my $param ( keys(%ConfigMeta) ) {
886             next if ( $doneParam->{$param} );
887             next if ( $userHost
888                           && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
889                              || (!$PrivAdmin
890                                 && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
891             $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
892                                     $param,
893                                     $mainConf->{$param},
894                                     "orig",
895                                 );
896             $doneParam->{$param} = 1;
897         }
898     }
899
900     $content .= <<EOF;
901 $contentHidden
902 </form>
903 </tr>
904 </table>
905 EOF
906
907     Header("Config Edit", $content);
908     Trailer();
909 }
910
911 sub fieldHiddenBuild
912 {
913     my($type, $varName, $varValue, $prefix) = @_;
914     my $content;
915
916     $type = { type => $type } if ( ref($type) ne "HASH" );
917
918     if ( $type->{type} eq "list" ) {
919         $varValue = [] if ( !defined($varValue) );
920         $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
921
922         for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
923             $content .= fieldHiddenBuild($type->{child}, "${varName}_zZ_$i",
924                                          $varValue->[$i], $prefix);
925         }
926     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
927         $varValue = {} if ( ref($varValue) ne "HASH" );
928         my(@order, $childType);
929
930         if ( defined($type->{order}) ) {
931             @order = @{$type->{order}};
932         } elsif ( defined($type->{child}) ) {
933             @order = sort(keys(%{$type->{child}}));
934         } else {
935             @order = sort(keys(%$varValue));
936         }
937
938         foreach my $fld ( @order ) {
939             if ( defined($type->{child}) ) {
940                 $childType = $type->{child}{$fld};
941             } else {
942                 $childType = $type->{childType};
943                 #
944                 # emit list of fields since they are user-defined
945                 # rather than hard-coded
946                 #
947                 $content .= <<EOF;
948 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
949 EOF
950             }
951             $content .= fieldHiddenBuild($childType, "${varName}_zZ_$fld",
952                                          $varValue->{$fld}, $prefix);
953         }
954     } elsif ( $type->{type} eq "shortlist" ) {
955         $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
956         $varValue = join(", ", @$varValue);
957         $content .= <<EOF;
958 <input type="hidden" name="${prefix}_zZ_$varName" value="${EscHTML($varValue)}">
959 EOF
960     } else {
961         $content .= <<EOF;
962 <input type="hidden" name="${prefix}_zZ_$varName" value="${EscHTML($varValue)}">
963 EOF
964     }
965     return $content;
966 }
967
968 sub fieldEditBuild
969 {
970     my($type, $varName, $varValue, $errors, $level, $comment, $isError,
971        $onchangeSubmit, $overrideVar, $overrideSet) = @_;
972
973     my $content;
974     my $size = 50 - 10 * $level;
975     $type = { type => $type } if ( ref($type) ne "HASH" );
976
977     $size = $type->{size} if ( defined($type->{size}) );
978
979     #
980     # These fragments allow inline content to be turned on and off
981     #
982     # <tr><td colspan="2"><span id="id_$varName" style="display: none" class="editComment">$comment</span></td></tr>
983     # <tr><td class="border"><a href="javascript: displayHelp('$varName')">$varName</a>
984     #
985
986     if ( $level == 0 ) {
987         my $lcVarName = lc($varName);
988         $content .= <<EOF;
989 <tr><td class="border"><a href="?action=view&type=docs#item_%24conf%7b$lcVarName%7d">$varName</a>
990 EOF
991         if ( defined($overrideVar) ) {
992             my $override_checked = "";
993             if ( !$isError && $In{deleteVar}      =~ /^\Q${varName}_zZ_/
994                    || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_zZ_|$)/
995                    || !$isError && $In{addVar}    =~ /^\Q${varName}\E(_zZ_|$)/ ) {
996                 $overrideSet = 1;
997             }
998             if ( $overrideSet ) {
999                 $override_checked = "checked";
1000             }
1001             $content .= <<EOF;
1002 <br><input type="checkbox" name="override_$varName" $override_checked value="1" onClick="checkboxChange('$varName')">\&nbsp;${EscHTML($Lang->{CfgEdit_Button_Override})}
1003 EOF
1004         }
1005         $content .= "</td>\n";
1006     }
1007
1008     if ( $type->{type} eq "list" ) {
1009         $content .= "<td class=\"border\">\n";
1010         $varValue = [] if ( !defined($varValue) );
1011         $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
1012         if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_zZ_\E(\d+)$/
1013                 && $1 < @$varValue ) {
1014             #
1015             # User deleted entry in this array
1016             #
1017             splice(@$varValue, $1, 1) if ( @$varValue > 1 || $type->{emptyOk} );
1018             $In{deleteVar} = "";
1019         }
1020         if ( !$isError && $In{insertVar} =~ /^\Q${varName}_zZ_\E(\d+)$/
1021                 && $1 < @$varValue ) {
1022             #
1023             # User inserted entry in this array
1024             #
1025             splice(@$varValue, $1, 0, "")
1026                         if ( @$varValue > 1 || $type->{emptyOk} );
1027             $In{insertVar} = "";
1028         }
1029         if ( !$isError && $In{addVar} eq $varName ) {
1030             #
1031             # User added entry to this array
1032             #
1033             push(@$varValue, undef);
1034             $In{addVar} = "";
1035         }
1036         $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1037         my $colspan;
1038
1039         if ( ref($type) eq "HASH" && ref($type->{child}) eq "HASH"
1040                     && $type->{child}{type} eq "horizHash" ) {
1041             my @order;
1042             if ( defined($type->{child}{order}) ) {
1043                 @order = @{$type->{child}{order}};
1044             } else {
1045                 @order = sort(keys(%{$type->{child}{child}}));
1046             }
1047             $content .= "<tr><td class=\"border\"></td>\n";
1048             for ( my $i = 0 ; $i < @order ; $i++ ) {
1049                 $content .= "<td class=\"tableheader\">$order[$i]</td>\n";
1050             }
1051             $colspan = @order + 1;
1052             $content .= "</tr>\n";
1053             for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1054                 if ( @$varValue > 1 || $type->{emptyOk} ) {
1055                     $content .= <<EOF;
1056 <td class="border">
1057 <input type="button" name="del_${varName}_zZ_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1058     onClick="deleteSubmit('${varName}_zZ_$i')">
1059 </td>
1060 EOF
1061                 }
1062                 $content .= fieldEditBuild($type->{child}, "${varName}_zZ_$i",
1063                                   $varValue->[$i], $errors, $level + 1, undef,
1064                                   $isError, $onchangeSubmit,
1065                                   $overrideVar, $overrideSet);
1066                 $content .= "</tr>\n";
1067             }
1068         } else {
1069             for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
1070                 $content .= <<EOF;
1071 <tr><td class="border">
1072 <input type="button" name="ins_${varName}_zZ_$i" value="${EscHTML($Lang->{CfgEdit_Button_Insert})}"
1073     onClick="insertSubmit('${varName}_zZ_$i')">
1074 EOF
1075                 if ( @$varValue > 1 || $type->{emptyOk} ) {
1076                     $content .= <<EOF;
1077 <input type="button" name="del_${varName}_zZ_$i" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1078     onClick="deleteSubmit('${varName}_zZ_$i')">
1079 EOF
1080                 }
1081                 $content .= "</td>\n";
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             $colspan = 2;
1089         }
1090         $content .= <<EOF;
1091 <tr><td class="border" colspan="$colspan"><input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}"
1092     onClick="addSubmit('$varName')"></td></tr>
1093 </table>
1094 EOF
1095         $content .= "</td>\n";
1096     } elsif ( $type->{type} eq "hash" ) {
1097         $content .= "<td class=\"border\">\n";
1098         $content .= "<table border=\"1\" cellspacing=\"0\">\n";
1099         $varValue = {} if ( ref($varValue) ne "HASH" );
1100
1101         if ( !$isError && !$type->{noKeyEdit}
1102                         && $In{deleteVar} !~ /^\Q${varName}_zZ_\E.*_zZ_/
1103                         && $In{deleteVar} =~ /^\Q${varName}_zZ_\E(.*)$/ ) {
1104             #
1105             # User deleted entry in this hash
1106             #
1107             delete($varValue->{$1}) if ( keys(%$varValue) > 1
1108                                             || $type->{emptyOk} );
1109             $In{deleteVar} = "";
1110         }
1111         if ( !$isError && !defined($type->{child})
1112                         && $In{addVar} eq $varName ) {
1113             #
1114             # User added entry to this array
1115             #
1116             $varValue->{$In{"addVarKey_$varName"}} = ""
1117                         if ( !defined($varValue->{$In{"addVarKey_$varName"}}) );
1118             $In{addVar} = "";
1119         }
1120         my(@order, $childType);
1121
1122         if ( defined($type->{order}) ) {
1123             @order = @{$type->{order}};
1124         } elsif ( defined($type->{child}) ) {
1125             @order = sort(keys(%{$type->{child}}));
1126         } else {
1127             @order = sort(keys(%$varValue));
1128         }
1129
1130         foreach my $fld ( @order ) {
1131             $content .= <<EOF;
1132 <tr><td class="border">$fld
1133 EOF
1134             if ( !$type->{noKeyEdit}
1135                     && (keys(%$varValue) > 1 || $type->{emptyOk}) ) {
1136                 $content .= <<EOF;
1137 <input type="submit" name="del_${varName}_zZ_$fld" value="${EscHTML($Lang->{CfgEdit_Button_Delete})}"
1138         onClick="deleteSubmit('${varName}_zZ_$fld')">
1139 EOF
1140             }
1141             if ( defined($type->{child}) ) {
1142                 $childType = $type->{child}{$fld};
1143             } else {
1144                 $childType = $type->{childType};
1145                 #
1146                 # emit list of fields since they are user-defined
1147                 # rather than hard-coded
1148                 #
1149                 $content .= <<EOF;
1150 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1151 EOF
1152             }
1153             $content .= "</td>\n";
1154             $content .= fieldEditBuild($childType, "${varName}_zZ_$fld",
1155                             $varValue->{$fld}, $errors, $level + 1, undef,
1156                             $isError, $onchangeSubmit,
1157                             $overrideVar, $overrideSet);
1158             $content .= "</tr>\n";
1159         }
1160
1161         if ( !$type->{noKeyEdit} ) {
1162             $content .= <<EOF;
1163 <tr><td class="border" colspan="2">
1164 $Lang->{CfgEdit_Button_New_Key}: <input type="text" class="editTextInput" name="addVarKey_$varName" size="20" maxlength="256" value="">
1165 <input type="button" name="add_$varName" value="${EscHTML($Lang->{CfgEdit_Button_Add})}" onClick="addSubmit('$varName', 1)">
1166 </td></tr>
1167 EOF
1168         }
1169         $content .= "</table>\n";
1170         $content .= "</td>\n";
1171     } elsif ( $type->{type} eq "horizHash" ) {
1172         $varValue = {} if ( ref($varValue) ne "HASH" );
1173         my(@order, $childType);
1174
1175         if ( defined($type->{order}) ) {
1176             @order = @{$type->{order}};
1177         } elsif ( defined($type->{child}) ) {
1178             @order = sort(keys(%{$type->{child}}));
1179         } else {
1180             @order = sort(keys(%$varValue));
1181         }
1182
1183         foreach my $fld ( @order ) {
1184             if ( defined($type->{child}) ) {
1185                 $childType = $type->{child}{$fld};
1186             } else {
1187                 $childType = $type->{childType};
1188                 #
1189                 # emit list of fields since they are user-defined
1190                 # rather than hard-coded
1191                 #
1192                 $content .= <<EOF;
1193 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
1194 EOF
1195             }
1196             $content .= fieldEditBuild($childType, "${varName}_zZ_$fld",
1197                             $varValue->{$fld}, $errors, $level + 1, undef,
1198                             $isError, $onchangeSubmit,
1199                             $overrideVar, $overrideSet);
1200         }
1201     } else {
1202         $content .= "<td class=\"border\">\n";
1203         if ( $isError ) {
1204             #
1205             # If there was an error, we use the original post values
1206             # in %In, rather than the parsed values in $varValue.
1207             # This is so that the user's erroneous input is preserved.
1208             #
1209             $varValue = $In{"v_zZ_$varName"} if ( defined($In{"v_zZ_$varName"}) );
1210         }
1211         if ( defined($errors->{$varName}) ) {
1212             $content .= <<EOF;
1213 <span class="editError">$errors->{$varName}</span><br>
1214 EOF
1215         }
1216         my $onChange;
1217         if ( defined($overrideVar) ) {
1218             $onChange .= "checkboxSet('$overrideVar');";
1219         } else {
1220             $onChange .= "varChange('$varName');";
1221         }
1222         if ( $onchangeSubmit ) {
1223             $onChange .= "document.editForm.submit();";
1224         }
1225         if ( $onChange ne "" ) {
1226             $onChange = " onChange=\"$onChange\"";
1227         }
1228         if ( $varValue !~ /\n/ &&
1229                 ($type->{type} eq "integer"
1230                     || $type->{type} eq "string"
1231                     || $type->{type} eq "execPath"
1232                     || $type->{type} eq "shortlist"
1233                     || $type->{type} eq "float") ) {
1234             # simple input box
1235             if ( $type->{type} eq "shortlist" ) {
1236                 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
1237                 $varValue = join(", ", @$varValue);
1238             }
1239             my $textType = ($varName =~ /Passwd/) ? "password" : "text";
1240             $content .= <<EOF;
1241 <input type="$textType" class="editTextInput" name="v_zZ_$varName" size="$size" maxlength="256" value="${EscHTML($varValue)}"$onChange>
1242 EOF
1243         } elsif ( $type->{type} eq "boolean" ) {
1244             # checkbox
1245             my $checked = "checked" if ( $varValue );
1246             $content .= <<EOF;
1247 <input type="checkbox" name="v_zZ_$varName" $checked value="1"$onChange>
1248 EOF
1249         } elsif ( $type->{type} eq "select" ) {
1250             $content .= <<EOF;
1251 <select name="v_zZ_$varName"$onChange>
1252 EOF
1253             foreach my $option ( @{$type->{values}} ) {
1254                 my $sel = " selected" if ( $varValue eq $option );
1255                 $content .= "<option$sel>$option</option>\n";
1256             }
1257             $content .= "</select>\n";
1258         } else {
1259             # multi-line text area - count number of lines
1260             my $rowCnt = $varValue =~ tr/\n//;
1261             $rowCnt = 1 if ( $rowCnt < 1 );
1262             $content .= <<EOF;
1263 <textarea name="v_zZ_$varName" class="editTextArea" cols="$size" rows="$rowCnt"$onChange>${EscHTML($varValue)}</textarea>
1264 EOF
1265         }
1266         $content .= "</td>\n";
1267     }
1268     return $content;
1269 }
1270
1271 sub errorCheck
1272 {
1273     my $errors = {};
1274
1275     foreach my $param ( keys(%ConfigMeta) ) {
1276         fieldErrorCheck($ConfigMeta{$param}, $param, $errors);
1277     }
1278     return $errors;
1279 }
1280
1281 sub fieldErrorCheck
1282 {
1283     my($type, $varName, $errors) = @_;
1284
1285     $type = { type => $type } if ( ref($type) ne "HASH" );
1286
1287     if ( $type->{type} eq "list" ) {
1288         for ( my $i = 0 ; ; $i++ ) {
1289             last if ( fieldErrorCheck($type->{child}, "${varName}_zZ_$i", $errors) );
1290         }
1291     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1292         my(@order, $childType);
1293         my $ret;
1294
1295         if ( defined($type->{order}) ) {
1296             @order = @{$type->{order}};
1297         } elsif ( defined($type->{child}) ) {
1298             @order = sort(keys(%{$type->{child}}));
1299         } else {
1300             @order = split(/\0/, $In{"vflds.$varName"});
1301         }
1302         foreach my $fld ( @order ) {
1303             if ( defined($type->{child}) ) {
1304                 $childType = $type->{child}{$fld};
1305             } else {
1306                 $childType = $type->{childType};
1307             }
1308             $ret ||= fieldErrorCheck($childType, "${varName}_zZ_$fld", $errors);
1309         }
1310         return $ret;
1311     } else {
1312         $In{"v_zZ_$varName"} = "0" if ( $type->{type} eq "boolean"
1313                                         && $In{"v_zZ_$varName"} eq "" );
1314
1315         return 1 if ( !exists($In{"v_zZ_$varName"}) );
1316
1317         (my $var = $varName) =~ s/_zZ_/./g;
1318
1319         if ( $type->{type} eq "integer"
1320                 || $type->{type} eq "boolean" ) {
1321             if ( $In{"v_zZ_$varName"} !~ /^-?\d+\s*$/s
1322                             && $In{"v_zZ_$varName"} ne "" ) {
1323                 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_an_integer}}");
1324             }
1325         } elsif ( $type->{type} eq "float" ) {
1326             if ( $In{"v_zZ_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s
1327                             && $In{"v_zZ_$varName"} ne "" ) {
1328                 $errors->{$varName}
1329                         = eval("qq{$Lang->{CfgEdit_Error__must_be_real_valued_number}}");
1330             }
1331         } elsif ( $type->{type} eq "shortlist" ) {
1332             my @vals = split(/[,\s]+/, $In{"v_zZ_$varName"});
1333             for ( my $i = 0 ; $i < @vals ; $i++ ) {
1334                 if ( $type->{child} eq "integer"
1335                         && $vals[$i] !~ /^-?\d+\s*$/s
1336                         && $vals[$i] ne "" ) {
1337                     my $k = $i + 1;
1338                     $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_an_integer}}");
1339                 } elsif ( $type->{child} eq "float"
1340                         && $vals[$i] !~ /^-?\d*(\.\d*)?\s*$/s
1341                         && $vals[$i] ne "" ) {
1342                     my $k = $i + 1;
1343                     $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__entry__must_be_real_valued_number}}");
1344                 }
1345             }
1346         } elsif ( $type->{type} eq "select" ) {
1347             my $match = 0;
1348             foreach my $option ( @{$type->{values}} ) {
1349                 if ( $In{"v_zZ_$varName"} eq $option ) {
1350                     $match = 1;
1351                     last;
1352                 }
1353             }
1354             $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_valid_option}}")
1355                             if ( !$match );
1356         } elsif ( $type->{type} eq "execPath" ) {
1357             if ( $In{"v_zZ_$varName"} ne "" && !-x $In{"v_zZ_$varName"} ) {
1358                 $errors->{$varName} = eval("qq{$Lang->{CfgEdit_Error__must_be_executable_program}}");
1359             }
1360         } else {
1361             #
1362             # $type->{type} eq "string": no error checking
1363             #
1364         }
1365     }
1366     return 0;
1367 }
1368
1369 sub inputParse
1370 {
1371     my($bpc, $userHost) = @_;
1372     my $conf     = {};
1373     my $override = {};
1374
1375     foreach my $param ( keys(%ConfigMeta) ) {
1376         my $value;
1377         next if ( $userHost
1378                       && (!defined($bpc->{Conf}{CgiUserConfigEdit}{$param})
1379                          || (!$PrivAdmin
1380                             && !$bpc->{Conf}{CgiUserConfigEdit}{$param})) );
1381         fieldInputParse($ConfigMeta{$param}, $param, \$value);
1382         $conf->{$param}     = $value;
1383         $override->{$param} = $In{"override_$param"};
1384     }
1385     return ($conf, $override);
1386 }
1387
1388 sub fieldInputParse
1389 {
1390     my($type, $varName, $value) = @_;
1391
1392     $type = { type => $type } if ( ref($type) ne "HASH" );
1393
1394     if ( $type->{type} eq "list" ) {
1395         $$value = [];
1396         for ( my $i = 0 ; ; $i++ ) {
1397             my $val;
1398             last if ( fieldInputParse($type->{child}, "${varName}_zZ_$i", \$val) );
1399             push(@$$value, $val);
1400         }
1401         $$value = undef if ( $type->{undefIfEmpty} && @$$value == 0 );
1402     } elsif ( $type->{type} eq "hash" || $type->{type} eq "horizHash" ) {
1403         my(@order, $childType);
1404         my $ret;
1405         $$value = {};
1406
1407         if ( defined($type->{order}) ) {
1408             @order = @{$type->{order}};
1409         } elsif ( defined($type->{child}) ) {
1410             @order = sort(keys(%{$type->{child}}));
1411         } else {
1412             @order = split(/\0/, $In{"vflds.$varName"});
1413         }
1414
1415         foreach my $fld ( @order ) {
1416             my $val;
1417             if ( defined($type->{child}) ) {
1418                 $childType = $type->{child}{$fld};
1419             } else {
1420                 $childType = $type->{childType};
1421             }
1422             $ret ||= fieldInputParse($childType, "${varName}_zZ_$fld", \$val);
1423             last if ( $ret );
1424             $$value->{$fld} = $val;
1425         }
1426         return $ret;
1427     } else {
1428         if ( $type->{type} eq "boolean" ) {
1429             $$value = 0 + $In{"v_zZ_$varName"};
1430         } elsif ( !exists($In{"v_zZ_$varName"}) ) {
1431             return 1;
1432         }
1433
1434         my $v = $In{"v_zZ_$varName"};
1435
1436         if ( $type->{type} eq "integer" ) {
1437             if ( $v =~ /^-?\d+\s*$/s || $v eq "" ) {
1438                 $$value = 0 + $v;
1439             } else {
1440                 # error value - keep in string form
1441                 $$value = $v;
1442             }
1443         } elsif ( $type->{type} eq "float" ) {
1444             if ( $v =~ /^-?\d*(\.\d*)?\s*$/s || $v eq "" ) {
1445                 $$value = 0 + $v;
1446             } else {
1447                 # error value - keep in string form
1448                 $$value = $v;
1449             }
1450         } elsif ( $type->{type} eq "shortlist" ) {
1451             $$value = [split(/[,\s]+/, $v)];
1452             if ( $type->{child} eq "float" ) {
1453                 foreach ( @$$value ) {
1454                     if ( /^-?\d*(\.\d*)?\s*$/s || $v eq "" ) {
1455                         $_ += 0;
1456                     }
1457                 }
1458             } elsif ( $type->{child} eq "integer"
1459                         || $type->{child} eq "boolean" ) {
1460                 foreach ( @$$value ) {
1461                     if ( /^-?\d+\s*$/s || $v eq "" ) {
1462                         $_ += 0;
1463                     }
1464                 }
1465             }
1466         } else {
1467             $$value = decode_utf8($In{"v_zZ_$varName"});
1468             $$value =~ s/\r\n/\n/g;
1469         }
1470         $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" );
1471     }
1472     return 0;
1473 }
1474
1475 sub configDiffMesg
1476 {
1477     my($host, $oldConf, $newConf) = @_;
1478     my $mesg;
1479     my $conf;
1480
1481     if ( $host ne "" ) {
1482         $conf = "host $host config";
1483     } else {
1484         $conf = "main config";
1485     }
1486
1487     foreach my $p ( keys(%ConfigMeta) ) {
1488         if ( !exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1489             next;
1490         } elsif ( exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1491             $mesg .= eval("qq($Lang->{CfgEdit_Log_Delete_param})");
1492         } elsif ( !exists($oldConf->{$p}) && exists($newConf->{$p}) ) {
1493             my $dump = Data::Dumper->new([$newConf->{$p}]);
1494             $dump->Indent(0);
1495             $dump->Sortkeys(1);
1496             $dump->Terse(1);
1497             my $value = $dump->Dump;
1498             $value =~ s/\n/\\n/g;
1499             $value =~ s/\r/\\r/g;
1500             $mesg .= eval("qq($Lang->{CfgEdit_Log_Add_param_value})");
1501         } else {
1502             my $dump = Data::Dumper->new([$newConf->{$p}]);
1503             $dump->Indent(0);
1504             $dump->Sortkeys(1);
1505             $dump->Terse(1);
1506             my $valueNew = $dump->Dump;
1507
1508             my $v = $oldConf->{$p};
1509             if ( ref($newConf->{$p}) eq "ARRAY" && ref($v) eq "" ) {
1510                 $v = [$v];
1511             }
1512             $dump = Data::Dumper->new([$v]);
1513             $dump->Indent(0);
1514             $dump->Sortkeys(1);
1515             $dump->Terse(1);
1516             my $valueOld = $dump->Dump;
1517
1518             (my $valueNew2 = $valueNew) =~ s/['\n\r]//g;
1519             (my $valueOld2 = $valueOld) =~ s/['\n\r]//g;
1520
1521             next if ( $valueOld2 eq $valueNew2 );
1522
1523             $valueNew =~ s/\n/\\n/g;
1524             $valueOld =~ s/\n/\\n/g;
1525             $valueNew =~ s/\r/\\r/g;
1526             $valueOld =~ s/\r/\\r/g;
1527
1528             $mesg .= eval("qq($Lang->{CfgEdit_Log_Change_param_value})");
1529         }
1530     }
1531     return $mesg;
1532 }
1533
1534 sub hostsDiffMesg
1535 {
1536     my($hostsNew) = @_;
1537     my $hostsOld = $bpc->HostInfoRead();
1538     my($mesg, $hostChange);
1539
1540     foreach my $host ( keys(%$hostsOld) ) {
1541         if ( !defined($hostsNew->{$host}) ) {
1542             $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Delete})");
1543             $hostChange++;
1544             next;
1545         }
1546         foreach my $key ( keys(%{$hostsNew->{$host}}) ) {
1547             next if ( $hostsNew->{$host}{$key} eq $hostsOld->{$host}{$key} );
1548             my $valueOld = $hostsOld->{$host}{$key};
1549             my $valueNew = $hostsNew->{$host}{$key};
1550             $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Change})");
1551             $hostChange++;
1552         }
1553     }
1554
1555     foreach my $host ( keys(%$hostsNew) ) {
1556         next if ( defined($hostsOld->{$host}) );
1557         my $dump = Data::Dumper->new([$hostsNew->{$host}]);
1558         $dump->Indent(0);
1559         $dump->Sortkeys(1);
1560         $dump->Terse(1);
1561         my $value = $dump->Dump;
1562         $value =~ s/\n/\\n/g;
1563         $value =~ s/\r/\\r/g;
1564         $mesg .= eval("qq($Lang->{CfgEdit_Log_Host_Add})");
1565         $hostChange++;
1566     }
1567     return ($mesg, $hostChange);
1568 }
1569
1570 1;