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