1 #============================================================= -*-perl-*-
3 # BackupPC::CGI::EditConfig package
7 # This module implements the EditConfig action for the CGI interface.
10 # Craig Barratt <cbarratt@users.sourceforge.net>
13 # Copyright (C) 2004 Craig Barratt
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.
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.
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
29 #========================================================================
31 # Version 2.1.0beta2pl1, released 30 May 2004.
33 # See http://backuppc.sourceforge.net.
35 #========================================================================
37 package BackupPC::CGI::EditConfig;
40 use BackupPC::CGI::Lib qw(:all);
41 use BackupPC::Config::Meta qw(:all);
42 use BackupPC::Storage;
49 {text => "General Parameters"},
50 {name => "ServerHost"},
51 {name => "BackupPCUser"},
52 {name => "BackupPCUserVerify"},
53 {name => "MaxOldLogFiles"},
54 {name => "TrashCleanSleepSec"},
56 {text => "Wakeup Schedule"},
57 {name => "WakeupSchedule"},
59 {text => "Concurrent Jobs"},
60 {name => "MaxBackups"},
61 {name => "MaxUserBackups"},
62 {name => "MaxPendingCmds"},
63 {name => "MaxBackupPCNightlyJobs"},
64 {name => "BackupPCNightlyPeriod"},
66 {text => "Pool Filesystem Limits"},
68 {name => "DfMaxUsagePct"},
69 {name => "HardLinkMax"},
71 {text => "Other Parameters"},
72 {name => "UmaskMode"},
74 {name => "DHCPAddressRanges"},
75 {name => "PerlModuleLoad"},
76 {name => "ServerInitdPath"},
77 {name => "ServerInitdStartCmd"},
79 {text => "Remote Apache Settings"},
80 {name => "ServerPort"},
81 {name => "ServerMesgSecret"},
83 {text => "Program Paths"},
85 {name => "NmbLookupPath"},
88 {name => "SplitPath"},
92 {name => "Bzip2Path"},
94 {text => "Install Paths"},
96 {name => "InstallDir"},
102 {text => "Email settings"},
103 {name => "SendmailPath"},
104 {name => "EMailNotifyMinDays"},
105 {name => "EMailFromUserName"},
106 {name => "EMailAdminUserName"},
107 {name => "EMailUserDestDomain"},
109 {text => "Email User Messages"},
110 {name => "EMailNoBackupEverSubj"},
111 {name => "EMailNoBackupEverMesg"},
112 {name => "EMailNotifyOldBackupDays"},
113 {name => "EMailNoBackupRecentSubj"},
114 {name => "EMailNoBackupRecentMesg"},
115 {name => "EMailNotifyOldOutlookDays"},
116 {name => "EMailOutlookBackupSubj"},
117 {name => "EMailOutlookBackupMesg"},
123 {text => "Admin Privileges"},
124 {name => "CgiAdminUserGroup"},
125 {name => "CgiAdminUsers"},
127 {text => "Config Editing"},
128 {name => "CgiUserConfigEdit"},
130 {text => "Page Rendering"},
131 {name => "Language"},
132 {name => "CgiNavBarAdminAllHosts"},
133 {name => "CgiSearchBoxEnable"},
134 {name => "CgiNavBarLinks"},
135 {name => "CgiStatusHilightColor"},
136 {name => "CgiDateFormatMMDD"},
137 {name => "CgiHeaders"},
138 {name => "CgiExt2ContentType"},
139 {name => "CgiCSSFile"},
143 {name => "CgiImageDir"},
144 {name => "CgiImageDirURL"},
146 {text => "User URLs"},
147 {name => "CgiUserHomePageCheck"},
148 {name => "CgiUserUrlCreate"},
155 {text => "Xfer Settings"},
156 {name => "XferMethod", onchangeSubmit => 1},
157 {name => "XferLogLevel"},
159 {text => "Smb Settings",
160 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
161 {name => "SmbShareName",
162 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
163 {name => "SmbShareUserName",
164 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
165 {name => "SmbSharePasswd",
166 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
168 {text => "Tar Settings",
169 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
170 {name => "TarShareName",
171 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
173 {text => "Rsync Settings",
174 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
175 {text => "Rsyncd Settings",
176 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
177 {name => "RsyncShareName",
178 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
179 {name => "RsyncdPasswd",
180 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
181 {name => "RsyncdAuthRequired",
182 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
183 {name => "RsyncCsumCacheVerifyProb",
184 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
186 {text => "Archive Settings",
187 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
188 {name => "ArchiveDest",
189 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
190 {name => "ArchiveComp",
191 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
192 {name => "ArchivePar",
193 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
194 {name => "ArchiveSplit",
195 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
197 {text => "Include/Exclude",
198 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
199 {name => "BackupFilesOnly",
200 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
201 {name => "BackupFilesExclude",
202 visible => sub { return $_[0]->{XferMethod} ne "archive"; } },
204 {text => "Smb Paths/Commands",
205 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
206 {name => "SmbClientPath",
207 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
208 {name => "SmbClientFullCmd",
209 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
210 {name => "SmbClientIncrCmd",
211 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
212 {name => "SmbClientRestoreCmd",
213 visible => sub { return $_[0]->{XferMethod} eq "smb"; } },
215 {text => "Tar Paths/Commands",
216 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
217 {name => "TarClientPath",
218 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
219 {name => "TarClientCmd",
220 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
221 {name => "TarFullArgs",
222 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
223 {name => "TarIncrArgs",
224 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
225 {name => "TarClientRestoreCmd",
226 visible => sub { return $_[0]->{XferMethod} eq "tar"; } },
228 {text => "Rsync Paths/Commands/Args",
229 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
230 {text => "Rsyncd Port/Args",
231 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
232 {name => "RsyncClientPath",
233 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
234 {name => "RsyncClientCmd",
235 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
236 {name => "RsyncClientRestoreCmd",
237 visible => sub { return $_[0]->{XferMethod} eq "rsync"; } },
238 {name => "RsyncdClientPort",
239 visible => sub { return $_[0]->{XferMethod} eq "rsyncd"; } },
240 {name => "RsyncArgs",
241 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
242 {name => "RsyncRestoreArgs",
243 visible => sub { return $_[0]->{XferMethod} =~ /rsync/; } },
245 {text => "Archive Paths/Commands",
246 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
247 {name => "ArchiveClientCmd",
248 visible => sub { return $_[0]->{XferMethod} eq "archive"; } },
255 {text => "Full Backups"},
256 {name => "FullPeriod"},
257 {name => "FullKeepCnt"},
258 {name => "FullKeepCntMin"},
259 {name => "FullAgeMax"},
261 {text => "Incremental Backups"},
262 {name => "IncrPeriod"},
263 {name => "IncrKeepCnt"},
264 {name => "IncrKeepCntMin"},
265 {name => "IncrAgeMax"},
266 {name => "IncrFill"},
268 {text => "Blackouts"},
269 {name => "BlackoutBadPingLimit"},
270 {name => "BlackoutGoodCnt"},
271 {name => "BlackoutPeriods"},
274 {name => "PartialAgeMax"},
275 {name => "RestoreInfoKeepCnt"},
276 {name => "ArchiveInfoKeepCnt"},
277 {name => "BackupZeroFilesIsFatal"},
281 text => "Backup Settings",
283 {text => "Client Lookup"},
284 {name => "ClientNameAlias"},
285 {name => "NmbLookupCmd"},
286 {name => "NmbLookupFindHostCmd"},
287 {name => "FixedIPNetBiosNameCheck"},
289 {name => "PingMaxMsec"},
292 {name => "ClientTimeout"},
293 {name => "MaxOldPerPCLogFiles"},
294 {name => "CompressLevel"},
296 {text => "User Commands"},
297 {name => "DumpPreUserCmd"},
298 {name => "DumpPostUserCmd"},
299 {name => "DumpPreShareCmd"},
300 {name => "DumpPostShareCmd"},
301 {name => "RestorePreUserCmd"},
302 {name => "RestorePostUserCmd"},
303 {name => "ArchivePreUserCmd"},
304 {name => "ArchivePostUserCmd"},
311 my $pc_dir = "$TopDir/pc";
312 my($content, $contentHidden, $newConf, $override, $mainConf, $hostConf);
315 my $host = $In{host};
316 my $menu = $In{menu} || "server";
317 my $hosts_path = $Hosts;
318 my $config_path = $host eq "" ? "$TopDir/conf/config.pl"
319 : "$TopDir/pc/$host/config.pl";
321 my $Privileged = CheckPermission();
322 my $userHost = 1 if ( $Privileged && !$PrivAdmin && defined($host) );
324 if ( !$Privileged ) {
325 #ErrorExit(eval("qq{$Lang->{Only_privileged_users_can_edit_config_files}}"));
326 ErrorExit("Only_privileged_users_can_edit_config_files");
329 if ( defined($In{menu}) || $In{editAction} eq "Save" ) {
330 $errors = errorCheck();
333 # If there are errors, then go back to the same menu
335 $In{editAction} = "";
338 ($newConf, $override) = inputParse($bpc, $userHost);
339 $override = undef if ( $host eq "" );
342 # Copy all the orig_ input parameters
344 foreach my $var ( keys(%In) ) {
345 next if ( $var !~ /^orig_/ );
346 $contentHidden .= <<EOF;
347 <input type="hidden" name="$var" value="${EscHTML($In{$var})}">
352 # First time: pick up the current config settings
354 $mainConf = $bpc->ConfigDataRead();
356 $hostConf = $bpc->ConfigDataRead($host);
358 foreach my $param ( keys(%$hostConf) ) {
359 $override->{$param} = 1;
364 $newConf = { %$mainConf, %$hostConf };
367 # Emit all the original config settings
370 foreach my $param ( keys(%ConfigMeta) ) {
371 next if ( $doneParam->{$param} );
372 next if ( $userHost && !$bpc->{Conf}{CgiUserConfigEdit}{$param} );
373 $contentHidden .= fieldHiddenBuild($ConfigMeta{$param},
378 $doneParam->{$param} = 1;
383 if ( $In{editAction} ne "Save" && $In{newMenu} ne ""
384 && defined($ConfigMenu{$In{newMenu}}) ) {
385 $menu = $In{newMenu};
391 # For a non-admin user editing the host config, we need to
392 # figure out which subsets of the menu tree will be visible,
393 # based on what is enabled
395 foreach my $m ( keys(%ConfigMenu) ) {
401 foreach my $paramInfo ( @{$ConfigMenu{$m}{param}} ) {
402 my $param = $paramInfo->{name};
403 if ( defined($paramInfo->{text}) ) {
407 if ( $bpc->{Conf}{CgiUserConfigEdit}{$param} ) {
408 $mask[$text] = 0 if ( $text >= 0 );
417 $menuDisable{$m}{mask} = \@mask;
418 $menuDisable{$m}{top} = !$enabled;
420 if ( $menuDisable{$menu}{top} ) {
422 # Find an enabled menu if the current menu is empty
424 foreach my $m ( sort(keys(%menuDisable)) ) {
425 if ( !$menuDisable{$m}{top} ) {
434 foreach my $m ( keys(%ConfigMenu) ) {
435 next if ( $menuDisable{$m}{top} );
436 my $text = $ConfigMenu{$m}{text};
439 <td bgcolor="grey"><a href="javascript:menuSubmit('$m')"><b>$text</b></a></td>
443 <td><a href="javascript:menuSubmit('$m')">$text</a></td>
450 ${h1("Main Configuration Editor")}
454 ${h1("Host $host Configuration Editor")}
456 Note: Check Override if you want to modify a value specific to this host.
460 my $saveDisplay = "block";
461 $saveDisplay = "none" if ( !$In{modified} );
463 <table border="0" cellpadding="2">
466 <form method="post" name="form1" action="$MyURL">
467 <input type="hidden" name="host" value="$host">
468 <input type="hidden" name="menu" value="$menu">
469 <input type="hidden" name="newMenu" value="">
470 <input type="hidden" name="modified" value="$In{modified}">
471 <input type="hidden" name="deleteVar" value="">
472 <input type="hidden" name="insertVar" value="">
473 <input type="hidden" name="addVar" value="">
474 <input type="hidden" name="action" value="editConfig">
475 <input type="submit" style="display: $saveDisplay" name="editAction" value="Save">
478 <script language="javascript" type="text/javascript">
481 function deleteSubmit(varName)
483 document.form1.deleteVar.value = varName;
484 document.form1.modified.value = 1;
485 document.form1.submit();
489 function insertSubmit(varName)
491 document.form1.insertVar.value = varName;
492 document.form1.modified.value = 1;
493 document.form1.submit();
497 function addSubmit(varName, checkKey)
499 if ( checkKey && document.form1.addVarKey.value == "" ) {
500 alert("New key must be non-empty");
503 document.form1.addVar.value = varName;
504 document.form1.modified.value = 1;
505 document.form1.submit();
509 function menuSubmit(menuName)
511 document.form1.newMenu.value = menuName;
512 document.form1.submit();
515 function varChange(varName)
517 document.form1.editAction.style.display = "block";
518 document.form1.modified.value = 1;
521 function checkboxChange(varName)
523 document.form1.editAction.style.display = "block";
524 document.form1.modified.value = 1;
525 // Do nothing if the checkbox is now set
526 if ( eval("document.form1.override_" + varName + ".checked") ) {
530 var varRE = new RegExp("^v_(" + varName + ".*)");
531 var origRE = new RegExp("^orig_(" + varName + ".*)");
532 for ( var i = 0 ; i < document.form1.elements.length ; i++ ) {
533 var e = document.form1.elements[i];
535 if ( (re = varRE.exec(e.name)) != null ) {
536 if ( allVars[re[1]] == null ) {
540 //debugMsg("found v_ match with " + re[1]);
541 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
542 } else if ( (re = origRE.exec(e.name)) != null ) {
543 if ( allVars[re[1]] == null ) {
547 //debugMsg("allVars[" + re[1] + "] = " + allVars[re[1]]);
551 for ( v in allVars ) {
552 if ( allVars[v] != 0 ) {
553 //debugMsg("Not the same shape because of " + v);
558 for ( v in allVars ) {
559 //debugMsg("setting " + v);
560 eval("document.form1.v_" + v + ".value = document.form1.orig_" + v + ".value");
564 document.form1.submit();
569 function checkboxSet(varName)
571 document.form1.editAction.style.display = "block";
572 document.form1.modified.value = 1;
573 eval("document.form1.override_" + varName + ".checked = 1;");
577 var debugCounter = 0;
578 function debugMsg(msg)
581 var t = document.createTextNode(debugCounter + ": " + msg);
582 var br = document.createElement("br");
583 var debug = document.getElementById("debug");
584 debug.appendChild(t);
585 debug.appendChild(br);
588 function displayHelp(varName)
590 var help = document.getElementById("id_" + varName);
591 help.style.display = help.style.display == "block" ? "none" : "block";
597 <span id="debug"></span>
602 <table border="1" cellspacing="0">
608 # There is a special case of the user deleting just the field
609 # that has the error(s). So if the delete variable is a match
610 # or parent to all the errors then ignore the errors.
612 if ( $In{deleteVar} ne "" && %$errors > 0 ) {
614 foreach my $v ( keys(%$errors) ) {
615 if ( $v ne $In{deleteVar} && $v !~ /^\Q$In{deleteVar}_/ ) {
620 $errors = {} if ( $matchAll );
623 my $isError = %$errors;
625 if ( !$isError && $In{editAction} eq "Save" ) {
628 $hostConf = $bpc->ConfigDataRead($host) if ( !defined($hostConf) );
629 $mesg = configDiffMesg($host, $hostConf, $newConf);
630 foreach my $param ( %$newConf ) {
631 $hostConf->{$param} = $newConf->{$param}
632 if ( $override->{param} );
634 $bpc->ConfigDataWrite($host, $hostConf);
636 $mainConf = $bpc->ConfigDataRead() if ( !defined($mainConf) );
637 $mesg = configDiffMesg(undef, $mainConf, $newConf);
638 $mainConf = { %$mainConf, %$newConf };
639 $bpc->ConfigDataWrite(undef, $mainConf);
642 $bpc->ServerConnect();
643 foreach my $str ( split(/\n/, $mesg) ) {
644 $bpc->ServerMesg($str);
649 my @mask = @{$menuDisable{$menu}{mask} || []};
651 foreach my $paramInfo ( @{$ConfigMenu{$menu}{param}} ) {
653 my $param = $paramInfo->{name};
654 my $disabled = shift(@mask);
656 next if ( $disabled || $menuDisable{$menu}{top} );
657 if ( ref($paramInfo->{visible}) eq "CODE"
658 && !&{$paramInfo->{visible}}($newConf) ) {
662 if ( defined(my $text = $paramInfo->{text}) ) {
664 <tr><td colspan="2" class="editHeader">$text</td></tr>
670 # TODO: get parameter documentation
675 $comment =~ s/\n/ /g;
677 $doneParam->{$param} = 1;
679 $content .= fieldEditBuild($ConfigMeta{$param},
686 $paramInfo->{onchangeSubmit},
687 defined($override) ? $param : undef,
688 defined($override) ? $override->{$param} : undef
693 # Emit any remaining errors - should not happen
695 foreach my $param ( sort(keys(%$errors)) ) {
697 <tr><td colspan="2" class="border">$errors->{$param}</td></tr>
699 delete($errors->{$param});
707 # Emit all the remaining editable config settings as hidden values
709 foreach my $param ( keys(%ConfigMeta) ) {
710 next if ( $doneParam->{$param} );
711 next if ( $userHost && !$bpc->{Conf}{CgiUserConfigEdit}{$param} );
712 $content .= fieldHiddenBuild($ConfigMeta{$param},
717 if ( defined($override) ) {
719 <input type="hidden" name="override_$param" value="$override->{$param}">
722 $doneParam->{$param} = 1;
731 Header("Config Edit", $content);
737 my($type, $varName, $varValue, $prefix) = @_;
740 $type = { type => $type } if ( ref($type) ne "HASH" );
742 if ( $type->{type} eq "list" ) {
743 $varValue = [] if ( !defined($varValue) );
744 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
746 for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
747 $content .= fieldHiddenBuild($type->{child}, "${varName}_$i",
748 $varValue->[$i], $prefix);
750 } elsif ( $type->{type} eq "hash" ) {
751 $varValue = {} if ( ref($varValue) ne "HASH" );
752 my(@order, $childType);
754 if ( defined($type->{child}) ) {
755 @order = sort(keys(%{$type->{child}}));
757 @order = sort(keys(%$varValue));
760 foreach my $fld ( @order ) {
761 if ( defined($type->{child}) ) {
762 $childType = $type->{child}{$fld};
764 $childType = $type->{childType};
766 # emit list of fields since they are user-defined
767 # rather than hard-coded
770 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
773 $content .= fieldHiddenBuild($childType, "${varName}_$fld",
774 $varValue->{$fld}, $prefix);
776 } elsif ( $type->{type} eq "shortlist" ) {
777 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
778 $varValue = join(", ", @$varValue);
780 <input type="hidden" name="${prefix}_$varName" value="${EscHTML($varValue)}">
784 <input type="hidden" name="${prefix}_$varName" value="${EscHTML($varValue)}">
792 my($type, $varName, $varValue, $errors, $level, $comment, $isError,
793 $onchangeSubmit, $overrideVar, $overrideSet) = @_;
796 my $size = 50 - 10 * $level;
797 $type = { type => $type } if ( ref($type) ne "HASH" );
801 <tr id="id_$varName" class="optionalComment"><td colspan="2">$comment</td></tr>
802 <tr><td class="border"><a href="javascript: displayHelp('$varName')">$varName</a>
804 if ( defined($overrideVar) ) {
805 my $override_checked = "";
806 if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_/
807 || !$isError && $In{insertVar} =~ /^\Q${varName}\E(_|$)/
808 || !$isError && $In{addVar} =~ /^\Q${varName}\E(_|$)/ ) {
811 if ( $overrideSet ) {
812 $override_checked = "checked";
815 <br><input type="checkbox" name="override_$varName" $override_checked value="1" onClick="checkboxChange('$varName')">\ Override
818 $content .= "</td>\n";
821 $content .= "<td class=\"border\">\n";
822 if ( $type->{type} eq "list" ) {
823 $varValue = [] if ( !defined($varValue) );
824 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
825 if ( !$isError && $In{deleteVar} =~ /^\Q${varName}_\E(\d+)$/
826 && $1 < @$varValue ) {
828 # User deleted entry in this array
830 splice(@$varValue, $1, 1) if ( @$varValue > 1 || $type->{emptyOk} );
833 if ( !$isError && $In{insertVar} =~ /^\Q${varName}_\E(\d+)$/
834 && $1 < @$varValue ) {
836 # User inserted entry in this array
838 splice(@$varValue, $1, 0, "")
839 if ( @$varValue > 1 || $type->{emptyOk} );
842 if ( !$isError && $In{addVar} eq $varName ) {
844 # User added entry to this array
846 push(@$varValue, undef);
849 $content .= "<table border=\"1\" cellspacing=\"0\">\n";
851 for ( my $i = 0 ; $i < @$varValue ; $i++ ) {
852 $content .= "<tr><td class=\"border\">\n";
853 if ( @$varValue > 1 || $type->{emptyOk} ) {
855 <input type="button" name="ins_${varName}_$i" value="Insert"
856 onClick="insertSubmit('${varName}_$i')">
857 <input type="button" name="del_${varName}_$i" value="Delete"
858 onClick="deleteSubmit('${varName}_$i')">
861 $content .= "</td>\n";
862 $content .= fieldEditBuild($type->{child}, "${varName}_$i",
863 $varValue->[$i], $errors, $level + 1, undef,
864 $isError, $onchangeSubmit,
865 $overrideVar, $overrideSet);
866 $content .= "</tr>\n";
869 <tr><td class="border"><input type="button" name="add_$varName" value="Add"
870 onClick="addSubmit('$varName')"></td></tr>
873 } elsif ( $type->{type} eq "hash" ) {
874 $content .= "<table border=\"1\" cellspacing=\"0\">\n";
875 $varValue = {} if ( ref($varValue) ne "HASH" );
877 if ( !$isError && !$type->{noKeyEdit}
878 && $In{deleteVar} =~ /^\Q${varName}_\E(\w+)$/ ) {
880 # User deleted entry in this array
882 delete($varValue->{$1}) if ( keys(%$varValue) > 1
883 || $type->{emptyOk} );
886 if ( !$isError && !defined($type->{child})
887 && $In{addVar} eq $varName ) {
889 # User added entry to this array
891 $varValue->{$In{addVarKey}} = ""
892 if ( !defined($varValue->{$In{addVarKey}}) );
895 my(@order, $childType);
897 if ( defined($type->{child}) ) {
898 @order = sort(keys(%{$type->{child}}));
900 @order = sort(keys(%$varValue));
903 foreach my $fld ( @order ) {
905 <tr><td class="border">$fld
907 if ( !$type->{noKeyEdit}
908 && (keys(%$varValue) > 1 || $type->{emptyOk}) ) {
910 <input type="submit" name="del_${varName}_$fld" value="Delete"
911 onClick="deleteSubmit('${varName}_$fld')">
914 if ( defined($type->{child}) ) {
915 $childType = $type->{child}{$fld};
917 $childType = $type->{childType};
919 # emit list of fields since they are user-defined
920 # rather than hard-coded
923 <input type="hidden" name="vflds.$varName" value="${EscHTML($fld)}">
926 $content .= "</td>\n";
927 $content .= fieldEditBuild($childType, "${varName}_$fld",
928 $varValue->{$fld}, $errors, $level + 1, undef,
929 $isError, $onchangeSubmit,
930 $overrideVar, $overrideSet);
931 $content .= "</tr>\n";
934 if ( !$type->{noKeyEdit} ) {
936 <tr><td class="border" colspan="2">
937 New key: <input type="text" name="addVarKey" size="20" maxlength="256" value="">
938 <input type="button" name="add_$varName" value="Add" onClick="addSubmit('$varName', 1)">
942 $content .= "</table>\n";
946 # If there was an error, we use the original post values
947 # in %In, rather than the parsed values in $varValue.
948 # This is so that the user's erroneous input is preserved.
950 $varValue = $In{"v_$varName"} if ( defined($In{"v_$varName"}) );
952 if ( defined($errors->{$varName}) ) {
954 $errors->{$varName}<br>
956 delete($errors->{$varName});
959 if ( defined($overrideVar) ) {
960 $onChange .= "checkboxSet('$overrideVar');";
962 $onChange .= "varChange('$overrideVar');";
964 if ( $onchangeSubmit ) {
965 $onChange .= "document.form1.submit();";
967 if ( $onChange ne "" ) {
968 $onChange = " onChange=\"$onChange\"";
970 if ( $varValue !~ /\n/ &&
971 ($type->{type} eq "integer"
972 || $type->{type} eq "string"
973 || $type->{type} eq "shortlist"
974 || $type->{type} eq "float") ) {
976 if ( $type->{type} eq "shortlist" ) {
977 $varValue = [$varValue] if ( ref($varValue) ne "ARRAY" );
978 $varValue = join(", ", @$varValue);
981 <input type="text" name="v_$varName" size="$size" maxlength="256" value="${EscHTML($varValue)}"$onChange>
983 } elsif ( $type->{type} eq "boolean" ) {
985 my $checked = "checked" if ( $varValue );
987 <input type="checkbox" name="v_$varName" $checked value="1">
989 } elsif ( $type->{type} eq "select" ) {
991 <select name="v_$varName"$onChange>
993 foreach my $option ( @{$type->{values}} ) {
994 my $sel = " selected" if ( $varValue eq $option );
995 $content .= "<option$sel>$option</option>\n";
997 $content .= "</select>\n";
999 # multi-line text area - count number of lines
1000 my $rowCnt = $varValue =~ tr/\n//;
1001 $rowCnt = 1 if ( $rowCnt < 1 );
1003 <textarea name="v_$varName" cols="$size" rows="$rowCnt"$onChange>${EscHTML($varValue)}</textarea>
1007 $content .= "</td>\n";
1015 foreach my $param ( keys(%ConfigMeta) ) {
1016 fieldErrorCheck($ConfigMeta{$param}, $param, $errors);
1023 my($type, $varName, $errors) = @_;
1025 $type = { type => $type } if ( ref($type) ne "HASH" );
1027 if ( $type->{type} eq "list" ) {
1028 for ( my $i = 0 ; ; $i++ ) {
1029 last if ( fieldErrorCheck($type->{child}, "${varName}_$i", $errors) );
1031 } elsif ( $type->{type} eq "hash" ) {
1032 my(@order, $childType);
1035 if ( defined($type->{child}) ) {
1036 @order = sort(keys(%{$type->{child}}));
1038 @order = split(/\0/, $In{"vflds.$varName"});
1040 foreach my $fld ( @order ) {
1041 if ( defined($type->{child}) ) {
1042 $childType = $type->{child}{$fld};
1044 $childType = $type->{childType};
1046 $ret ||= fieldErrorCheck($childType, "${varName}_$fld", $errors);
1050 return 1 if ( !exists($In{"v_$varName"}) );
1052 if ( $type->{type} eq "integer"
1053 || $type->{type} eq "boolean" ) {
1054 if ( $In{"v_$varName"} !~ /^-?\d+\s*$/s
1055 && $In{"v_$varName"} ne "" ) {
1056 $errors->{$varName} = "Error: $varName must be an integer";
1058 } elsif ( $type->{type} eq "float" ) {
1059 if ( $In{"v_$varName"} !~ /^-?\d*(\.\d*)?\s*$/s
1060 && $In{"v_$varName"} ne "" ) {
1062 = "Error: $varName must be a real-valued number";
1064 } elsif ( $type->{type} eq "shortlist" ) {
1065 my @vals = split(/[,\s]+/, $In{"v_$varName"});
1066 for ( my $i = 0 ; $i < @vals ; $i++ ) {
1067 if ( $type->{child} eq "integer"
1068 && $vals[$i] !~ /^-?\d+\s*$/s
1069 && $vals[$i] ne "" ) {
1071 $errors->{$varName} = "Error: $varName entry $k must"
1073 } elsif ( $type->{child} eq "float"
1074 && $vals[$i] !~ /^-?\d*(\.\d*)?\s*$/s
1075 && $vals[$i] ne "" ) {
1077 $errors->{$varName} = "Error: $varName entry $k must"
1078 . " be a real-valued number";
1081 } elsif ( $type->{type} eq "select" ) {
1083 foreach my $option ( @{$type->{values}} ) {
1084 if ( $In{"v_$varName"} eq $option ) {
1089 $errors->{$varName} = "Error: $varName must be a valid option"
1093 # $type->{type} eq "string": no error checking
1102 my($bpc, $userHost) = @_;
1106 foreach my $param ( keys(%ConfigMeta) ) {
1108 next if ( $userHost && !$bpc->{Conf}{CgiUserConfigEdit}{$param} );
1109 fieldInputParse($ConfigMeta{$param}, $param, \$value);
1110 $conf->{$param} = $value;
1111 $override->{$param} = $In{"override_$param"};
1113 return ($conf, $override);
1118 my($type, $varName, $value) = @_;
1120 $type = { type => $type } if ( ref($type) ne "HASH" );
1122 if ( $type->{type} eq "list" ) {
1124 for ( my $i = 0 ; ; $i++ ) {
1126 last if ( fieldInputParse($type->{child}, "${varName}_$i", \$val) );
1127 push(@$$value, $val);
1129 $$value = undef if ( $type->{undefIfEmpty} && @$$value == 0 );
1130 } elsif ( $type->{type} eq "hash" ) {
1131 my(@order, $childType);
1135 if ( defined($type->{child}) ) {
1136 @order = sort(keys(%{$type->{child}}));
1138 @order = split(/\0/, $In{"vflds.$varName"});
1141 foreach my $fld ( @order ) {
1143 if ( defined($type->{child}) ) {
1144 $childType = $type->{child}{$fld};
1146 $childType = $type->{childType};
1148 $ret ||= fieldInputParse($childType, "${varName}_$fld", \$val);
1150 $$value->{$fld} = $val;
1154 if ( $type->{type} eq "boolean" ) {
1155 $$value = 0 + $In{"v_$varName"};
1156 } elsif ( !exists($In{"v_$varName"}) ) {
1160 if ( $type->{type} eq "integer" ) {
1161 $$value = 0 + $In{"v_$varName"};
1162 } elsif ( $type->{type} eq "float" ) {
1163 $$value = 0 + $In{"v_$varName"};
1164 } elsif ( $type->{type} eq "shortlist" ) {
1165 $$value = [split(/[,\s]+/, $In{"v_$varName"})];
1166 if ( $type->{child} eq "float"
1167 || $type->{child} eq "integer"
1168 || $type->{child} eq "boolean" ) {
1169 foreach ( @$$value ) {
1174 $$value = $In{"v_$varName"};
1176 $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" );
1183 my($host, $oldConf, $newConf) = @_;
1187 if ( $host ne "" ) {
1188 $conf = "host $host config";
1190 $conf = "main config";
1193 foreach my $p ( keys(%ConfigMeta) ) {
1194 if ( !exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1196 } elsif ( exists($oldConf->{$p}) && !exists($newConf->{$p}) ) {
1197 $mesg .= "log Deleted $p from $conf\n";
1198 } elsif ( !exists($oldConf->{$p}) && exists($newConf->{$p}) ) {
1199 my $dump = Data::Dumper->new([$newConf->{$p}]);
1203 my $value = $dump->Dump;
1204 $mesg .= "log Added $p to $conf, set to $value\n";
1206 my $dump = Data::Dumper->new([$newConf->{$p}]);
1210 my $valueNew = $dump->Dump;
1212 my $v = $oldConf->{$p};
1213 if ( ref($newConf->{$p}) eq "ARRAY" && ref($v) eq "" ) {
1216 $dump = Data::Dumper->new([$v]);
1220 my $valueOld = $dump->Dump;
1222 $mesg .= "log Changed $p in $conf to $valueNew from $valueOld\n"
1223 if ( $valueOld ne $valueNew );