--- loncom/interface/domainprefs.pm 2018/05/08 01:48:18 1.326 +++ loncom/interface/domainprefs.pm 2021/09/01 00:21:52 1.385 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.326 2018/05/08 01:48:18 raeburn Exp $ +# $Id: domainprefs.pm,v 1.385 2021/09/01 00:21:52 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -176,6 +176,7 @@ use Locale::Language; use DateTime::TimeZone; use DateTime::Locale; use Time::HiRes qw( sleep ); +use Net::CIDR; my $registered_cleanup; my $modified_urls; @@ -219,9 +220,10 @@ sub handler { 'serverstatuses','requestcourses','helpsettings', 'coursedefaults','usersessions','loadbalancing', 'requestauthor','selfenrollment','inststatus', - 'ltitools','ssl','trust','lti'],$dom); + 'ltitools','ssl','trust','lti','privacy','passwords', + 'proctoring','wafproxy'],$dom); my %encconfig = - &Apache::lonnet::get_dom('encconfig',['ltitools','lti'],$dom); + &Apache::lonnet::get_dom('encconfig',['ltitools','lti','proctoring'],$dom,undef,1); if (ref($domconfig{'ltitools'}) eq 'HASH') { if (ref($encconfig{'ltitools'}) eq 'HASH') { foreach my $id (keys(%{$domconfig{'ltitools'}})) { @@ -246,12 +248,25 @@ sub handler { } } } - my @prefs_order = ('rolecolors','login','defaults','quotas','autoenroll', - 'autoupdate','autocreate','directorysrch','contacts', - 'usercreation','selfcreation','usermodification','scantron', - 'requestcourses','requestauthor','coursecategories', - 'serverstatuses','helpsettings','coursedefaults', - 'ltitools','selfenrollment','usersessions','ssl','trust','lti'); + if (ref($domconfig{'proctoring'}) eq 'HASH') { + if (ref($encconfig{'proctoring'}) eq 'HASH') { + foreach my $provider (keys(%{$domconfig{'proctoring'}})) { + if ((ref($domconfig{'proctoring'}{$provider}) eq 'HASH') && + (ref($encconfig{'proctoring'}{$provider}) eq 'HASH')) { + foreach my $item ('key','secret') { + $domconfig{'proctoring'}{$provider}{$item} = $encconfig{'proctoring'}{$provider}{$item}; + } + } + } + } + } + my @prefs_order = ('rolecolors','login','defaults','wafproxy','passwords','quotas', + 'autoenroll','autoupdate','autocreate','directorysrch', + 'contacts','privacy','usercreation','selfcreation', + 'usermodification','scantron','requestcourses','requestauthor', + 'coursecategories','serverstatuses','helpsettings','coursedefaults', + 'ltitools','proctoring','selfenrollment','usersessions','ssl', + 'trust','lti'); my %existing; if (ref($domconfig{'loadbalancing'}) eq 'HASH') { %existing = %{$domconfig{'loadbalancing'}}; @@ -291,13 +306,36 @@ sub handler { help => 'Domain_Configuration_LangTZAuth', header => [{col1 => 'Setting', col2 => 'Value'}, - {col1 => 'Internal Authentication', - col2 => 'Value'}, {col1 => 'Institutional user types', col2 => 'Name displayed'}], print => \&print_defaults, modify => \&modify_defaults, }, + 'wafproxy' => + { text => 'Web Application Firewall/Reverse Proxy', + help => 'Domain_Configuration_WAF_Proxy', + header => [{col1 => 'Domain(s)', + col2 => 'Servers and WAF/Reverse Proxy alias(es)', + }, + {col1 => 'Domain(s)', + col2 => 'WAF Configuration',}], + print => \&print_wafproxy, + modify => \&modify_wafproxy, + }, + 'passwords' => + { text => 'Passwords (Internal authentication)', + help => 'Domain_Configuration_Passwords', + header => [{col1 => 'Resetting Forgotten Password', + col2 => 'Settings'}, + {col1 => 'Encryption of Stored Passwords (Internal Auth)', + col2 => 'Settings'}, + {col1 => 'Rules for LON-CAPA Passwords', + col2 => 'Settings'}, + {col1 => 'Course Owner Changing Student Passwords', + col2 => 'Settings'}], + print => \&print_passwords, + modify => \&modify_passwords, + }, 'quotas' => { text => 'Blogs, personal web pages, webDAV/quotas, portfolios', help => 'Domain_Configuration_Quotas', @@ -352,6 +390,8 @@ sub handler { col2 => 'Value',}, {col1 => 'Recipient(s) for notifications', col2 => 'Value',}, + {col1 => 'Nightly status check e-mail', + col2 => 'Settings',}, {col1 => 'Ask helpdesk form settings', col2 => 'Value',},], print => \&print_contacts, @@ -392,11 +432,12 @@ sub handler { modify => \&modify_usermodification, }, 'scantron' => - { text => 'Bubblesheet format file', + { text => 'Bubblesheet format', help => 'Domain_Configuration_Scantron_Format', - header => [ {col1 => 'Item', - col2 => '', - }], + header => [ {col1 => 'Bubblesheet format file', + col2 => ''}, + {col1 => 'Bubblesheet data upload formats', + col2 => 'Settings'}], print => \&print_scantron, modify => \&modify_scantron, }, @@ -482,10 +523,16 @@ sub handler { modify => \&modify_selfenrollment, }, 'privacy' => - {text => 'User Privacy', + {text => 'Availability of User Information', help => 'Domain_Configuration_User_Privacy', - header => [{col1 => 'Setting', - col2 => 'Value',}], + header => [{col1 => 'Role assigned in different domain', + col2 => 'Approval options'}, + {col1 => 'Role assigned in different domain to user of type', + col2 => 'User information available in that domain'}, + {col1 => "Role assigned in user's domain", + col2 => 'Information viewable by privileged user'}, + {col1 => "Role assigned in user's domain", + col2 => 'Information viewable by unprivileged user'}], print => \&print_privacy, modify => \&modify_privacy, }, @@ -520,6 +567,14 @@ sub handler { print => \&print_ltitools, modify => \&modify_ltitools, }, + 'proctoring' => + {text => 'Remote Proctoring Integration', + help => 'Domain_Configuration_Proctoring', + header => [{col1 => 'Name', + col2 => 'Configuration'}], + print => \&print_proctoring, + modify => \&modify_proctoring, + }, 'ssl' => {text => 'LON-CAPA Network (SSL)', help => 'Domain_Configuration_Network_SSL', @@ -625,6 +680,9 @@ END if (grep(/^contacts$/,@actions)) { $js .= &contacts_javascript(); } + if (grep(/^scantron$/,@actions)) { + $js .= &scantron_javascript(); + } &Apache::lonconfigsettings::display_settings($r,$dom,$phase,$context,\@prefs_order,\%prefs,\%domconfig,$confname,$js); } else { # check if domconfig user exists for the domain. @@ -747,12 +805,20 @@ sub process_changes { $output = &modify_loadbalancing($dom,%domconfig); } elsif ($action eq 'ltitools') { $output = &modify_ltitools($r,$dom,$action,$lastactref,%domconfig); + } elsif ($action eq 'proctoring') { + $output = &modify_proctoring($r,$dom,$action,$lastactref,%domconfig); } elsif ($action eq 'ssl') { $output = &modify_ssl($dom,$lastactref,%domconfig); } elsif ($action eq 'trust') { $output = &modify_trust($dom,$lastactref,%domconfig); } elsif ($action eq 'lti') { $output = &modify_lti($r,$dom,$action,$lastactref,%domconfig); + } elsif ($action eq 'privacy') { + $output = &modify_privacy($dom,%domconfig); + } elsif ($action eq 'passwords') { + $output = &modify_passwords($r,$dom,$confname,$lastactref,%domconfig); + } elsif ($action eq 'wafproxy') { + $output = &modify_wafproxy($dom,$action,$lastactref,%domconfig); } return $output; } @@ -765,6 +831,8 @@ sub print_config_box { $output = &coursecategories_javascript($settings); } elsif ($action eq 'defaults') { $output = &defaults_javascript($settings); + } elsif ($action eq 'passwords') { + $output = &passwords_javascript(); } elsif ($action eq 'helpsettings') { my (%privs,%levelscurrent); my %full=(); @@ -781,6 +849,16 @@ sub print_config_box { $output = &Apache::lonuserutils::custom_roledefs_js($context,$crstype,$formname,\%full, \@templateroles); + } elsif ($action eq 'ltitools') { + $output .= <itools_javascript($settings); + } elsif ($action eq 'lti') { + $output .= <i_javascript($settings); + } elsif ($action eq 'proctoring') { + $output .= &proctoring_javascript($settings); + } elsif ($action eq 'wafproxy') { + $output .= &wafproxy_javascript($dom); + } elsif ($action eq 'autoupdate') { + $output .= &autoupdate_javascript(); } $output .= ' @@ -797,6 +875,7 @@ sub print_config_box { if ($numheaders > 1) { my $colspan = ''; my $rightcolspan = ''; + my $leftnobr = ''; if (($action eq 'rolecolors') || ($action eq 'defaults') || ($action eq 'directorysrch') || (($action eq 'login') && ($numheaders < 4))) { @@ -805,12 +884,15 @@ sub print_config_box { if ($action eq 'usersessions') { $rightcolspan = ' colspan="3"'; } + if ($action eq 'passwords') { + $leftnobr = ' LC_nobreak'; + } $output .= ' + + + + + '; } else { $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); } @@ -907,8 +1023,10 @@ sub print_config_box { $rowtotal ++; } elsif (($action eq 'usermodification') || ($action eq 'coursedefaults') || ($action eq 'defaults') || ($action eq 'directorysrch') || - ($action eq 'helpsettings')) { + ($action eq 'helpsettings') || ($action eq 'wafproxy')) { $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); + } elsif ($action eq 'scantron') { + $output .= $item->{'print'}->($r,'bottom',$dom,$confname,$settings,\$rowtotal); } elsif ($action eq 'ssl') { $output .= $item->{'print'}->('connto',$dom,$settings,\$rowtotal).'
- + '; $rowtotal ++; @@ -818,10 +900,14 @@ sub print_config_box { ($action eq 'usermodification') || ($action eq 'defaults') || ($action eq 'coursedefaults') || ($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'ssl') || ($action eq 'directorysrch') || ($action eq 'trust') || ($action eq 'helpsettings') || - ($action eq 'contacts')) { + ($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'wafproxy')) { $output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal); + } elsif ($action eq 'passwords') { + $output .= $item->{'print'}->('top',$dom,$confname,$settings,\$rowtotal); } elsif ($action eq 'coursecategories') { $output .= $item->{'print'}->('top',$dom,$item,$settings,\$rowtotal); + } elsif ($action eq 'scantron') { + $output .= $item->{'print'}->($r,'top',$dom,$confname,$settings,\$rowtotal); } elsif ($action eq 'login') { if ($numheaders == 4) { $colspan = ' colspan="2"'; @@ -849,12 +935,15 @@ sub print_config_box { if (($action eq 'autoupdate') || ($action eq 'usercreation') || ($action eq 'selfcreation') || ($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'coursecategories') || - ($action eq 'trust') || ($action eq 'contacts') || ($action eq 'defaults')) { + ($action eq 'trust') || ($action eq 'contacts') || + ($action eq 'privacy') || ($action eq 'passwords')) { if ($action eq 'coursecategories') { $output .= &print_coursecategories('middle',$dom,$item,$settings,\$rowtotal); $colspan = ' colspan="2"'; } elsif ($action eq 'trust') { $output .= $item->{'print'}->('shared',$dom,$settings,\$rowtotal); + } elsif ($action eq 'passwords') { + $output .= $item->{'print'}->('middle',$dom,$confname,$settings,\$rowtotal); } else { $output .= $item->{'print'}->('middle',$dom,$settings,\$rowtotal); } @@ -900,6 +989,33 @@ sub print_config_box { '."\n"; if ($action eq 'coursecategories') { $output .= &print_coursecategories('bottom',$dom,$item,$settings,\$rowtotal); + } elsif (($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'passwords')) { + if ($action eq 'passwords') { + $output .= $item->{'print'}->('lower',$dom,$confname,$settings,\$rowtotal); + } else { + $output .= $item->{'print'}->('lower',$dom,$settings,\$rowtotal); + } + $output .= ' + +
'.&mt($item->{'header'}->[0]->{'col1'}).''.&mt($item->{'header'}->[0]->{'col1'}).' '.&mt($item->{'header'}->[0]->{'col2'}).'
+
+ + + + '."\n"; + if ($action eq 'passwords') { + $output .= $item->{'print'}->('bottom',$dom,$confname,$settings,\$rowtotal); + } else { + $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); + } + $output .= ' +
'.&mt($item->{'header'}->[3]->{'col1'}).''.&mt($item->{'header'}->[3]->{'col2'}).'
+
@@ -1088,10 +1206,9 @@ sub print_config_box { $output .= &print_quotas($dom,$settings,\$rowtotal,$action); } elsif (($action eq 'autoenroll') || ($action eq 'autocreate') || ($action eq 'serverstatuses') || ($action eq 'loadbalancing') || - ($action eq 'ltitools') || ($action eq 'lti')) { + ($action eq 'ltitools') || ($action eq 'lti') || + ($action eq 'proctoring')) { $output .= $item->{'print'}->($dom,$settings,\$rowtotal); - } elsif ($action eq 'scantron') { - $output .= &print_scantronformat($r,$dom,$confname,$settings,\$rowtotal); } } $output .= ' @@ -1436,7 +1553,7 @@ sub print_login { } else { $datatable .= ''; } - $datatable .= ''; + $datatable .= ''; } $datatable .= ''; } @@ -1596,7 +1713,7 @@ sub display_color_options { my $datatable = ''. ''.$choices->{'font'}.''; if (!$is_custom->{'font'}) { - $datatable .= ''.&mt('Default in use:').' '.$defaults->{'font'}.''; + $datatable .= ''.&mt('Default in use:').' '.$defaults->{'font'}.''; } else { $datatable .= ' '; } @@ -1605,12 +1722,12 @@ sub display_color_options { $datatable .= ''. ' '. - ' '; + ' '; unless ($role eq 'login') { $datatable .= ''. ''.$choices->{'fontmenu'}.''; if (!$is_custom->{'fontmenu'}) { - $datatable .= ''.&mt('Default in use:').' '.$defaults->{'fontmenu'}.''; + $datatable .= ''.&mt('Default in use:').' '.$defaults->{'fontmenu'}.''; } else { $datatable .= ' '; } @@ -1620,7 +1737,7 @@ sub display_color_options { ' '. - ' '; + ' '; } my $switchserver = &check_switchserver($dom,$confname); foreach my $img (@{$images}) { @@ -1738,7 +1855,7 @@ sub display_color_options { my $bgs_def; foreach my $item (@{$bgs}) { if (!$is_custom->{$item}) { - $bgs_def .= ''.$choices->{$item}.'    
'.$defaults->{'bgs'}{$item}.''; + $bgs_def .= ''.$choices->{$item}.'    
'.$defaults->{'bgs'}{$item}.''; } } if ($bgs_def) { @@ -1766,7 +1883,7 @@ sub display_color_options { my $links_def; foreach my $item (@{$links}) { if (!$is_custom->{$item}) { - $links_def .= ''.$choices->{$item}.'
'.$defaults->{'links'}{$item}.''; + $links_def .= ''.$choices->{$item}.'
'.$defaults->{'links'}{$item}.''; } } if ($links_def) { @@ -1852,17 +1969,15 @@ sub image_changes { my ($is_custom,$alt_text,$img_import,$showfile,$fullsize,$role,$img,$imgfile,$logincolors) = @_; my $output; if ($img eq 'login') { - # suppress image for Log-in header + $output = ''.$logincolors; # suppress image for Log-in header } elsif (!$is_custom) { if ($img ne 'domlogo') { - $output .= &mt('Default image:').'
'; + $output = &mt('Default image:').'
'; } else { - $output .= &mt('Default in use:').'
'; + $output = &mt('Default in use:').'
'; } } - if ($img eq 'login') { # suppress image for Log-in header - $output .= ''.$logincolors; - } else { + if ($img ne 'login') { if ($img_import) { $output .= ''; } @@ -2484,7 +2599,7 @@ sub print_textbookcourses { $datatable .= ''; } $datatable .= ' '."\n". - ''.&mt('Add').''."\n". + ''.&mt('Add').''."\n". ''. ''.&mt('Subject:').' '."\n". (' 'x2). @@ -2501,13 +2616,13 @@ sub print_textbookcourses { } else { $datatable .= ''; } + $datatable .= ''."\n"; } - $datatable .= ''."\n". - ''.&mt('LON-CAPA course:').' '. + $datatable .= ''.&mt('LON-CAPA course:').' '. &Apache::loncommon::select_dom_form($env{'request.role.domain'},$type.'_addbook_cdom'). ''. &Apache::loncommon::selectcourse_link - ('display',$type.'_addbook_cnum',$type.'_addbook_cdom',undef,undef,undef,'Course'); + ('display',$type.'_addbook_cnum',$type.'_addbook_cdom',undef,undef,undef,'Course'). ''."\n". ''."\n"; $itemcount ++; @@ -2733,6 +2848,217 @@ function toggleLTITools(form,setting,ite ENDSCRIPT } +sub wafproxy_javascript { + my ($dom) = @_; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + +sub proctoring_javascript { + my ($settings) = @_; + my (%ordered,$total,%jstext); + $total = 0; + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + $total = scalar(keys(%{$settings})); + } else { + %ordered = ( + 0 => 'proctorio', + 1 => 'examity', + ); + $total = 2; + } + my @jsarray = (); + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@jsarray,$ordered{$item}); + } + my $jstext = ' var proctors = Array('."'".join("','",@jsarray)."'".');'."\n"; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + + sub lti_javascript { my ($settings) = @_; my $togglejs = <i_toggle_js(); @@ -2815,27 +3141,67 @@ sub lti_toggle_js { // +// + + +ENDSCRIPT +} + sub print_autoenroll { my ($dom,$settings,$rowtotal) = @_; my $autorun = &Apache::lonnet::auto_run(undef,$dom), @@ -3026,49 +3427,76 @@ sub print_autoenroll { ''.&mt('Failsafe for no drops when institutional data missing').''. ''. ''; + ' value="'.$failsafe.'" size="4" />'; $$rowtotal += 4; return $datatable; } sub print_autoupdate { my ($position,$dom,$settings,$rowtotal) = @_; - my $datatable; + my ($enable,$datatable); if ($position eq 'top') { + my %choices = &Apache::lonlocal::texthash ( + run => 'Auto-update active?', + classlists => 'Update information in classlists?', + unexpired => 'Skip updates for users without active or future roles?', + lastactive => 'Skip updates for inactive users?', + ); + my $itemcount = 0; my $updateon = ' '; my $updateoff = ' checked="checked" '; - my $classlistson = ' '; - my $classlistsoff = ' checked="checked" '; if (ref($settings) eq 'HASH') { if ($settings->{'run'} eq '1') { $updateon = $updateoff; $updateoff = ' '; } - if ($settings->{'classlists'} eq '1') { - $classlistson = $classlistsoff; - $classlistsoff = ' '; - } } - my %title = ( - run => 'Auto-update active?', - classlists => 'Update information in classlists?', - ); - $datatable = ''. - ''.&mt($title{'run'}).''. - ' '. ''. - ''. - ''.&mt($title{'classlists'}).''. - ''. - ' '. - ''. + $updateon.'value="1" />'.&mt('Yes').''. ''; - $$rowtotal += 2; + my @toggles = ('classlists','unexpired'); + my %defaultchecked = ('classlists' => 'off', + 'unexpired' => 'off' + ); + $$rowtotal ++; + ($datatable,$itemcount) = &radiobutton_prefs($settings,\@toggles,\%defaultchecked, + \%choices,$itemcount,'','','left','no'); + $datatable = $enable.$datatable; + $$rowtotal += $itemcount; + my $lastactiveon = ' '; + my $lastactiveoff = ' checked="checked" '; + my $lastactivestyle = 'none'; + my $lastactivedays; + my $onclick = ' onclick="javascript:toggleLastActiveDays(this.form);"'; + if (ref($settings) eq 'HASH') { + if ($settings->{'lastactive'} =~ /^\d+$/) { + $lastactiveon = $lastactiveoff; + $lastactiveoff = ' '; + $lastactivestyle = 'inline-block'; + $lastactivedays = $settings->{'lastactive'}; + } + } + my $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''.$choices{'lastactive'}.''. + ''. + ' '. + '
'. + ': '.&mt('inactive = no activity in last [_1] days', + ''). + ''. + ''; + $$rowtotal ++; } elsif ($position eq 'middle') { my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); my $numinrow = 3; @@ -3308,7 +3736,7 @@ sub print_contacts { my $datatable; my @contacts = ('adminemail','supportemail'); my (%checked,%to,%otheremails,%bccemails,%includestr,%includeloc,%currfield, - $maxsize,$fields,$fieldtitles,$fieldoptions,$possoptions,@mailings); + $maxsize,$fields,$fieldtitles,$fieldoptions,$possoptions,@mailings,%lonstatus); if ($position eq 'top') { if (ref($settings) eq 'HASH') { foreach my $item (@contacts) { @@ -3319,10 +3747,16 @@ sub print_contacts { } } elsif ($position eq 'middle') { @mailings = ('errormail','packagesmail','lonstatusmail','requestsmail', - 'updatesmail','idconflictsmail'); + 'updatesmail','idconflictsmail','hostipmail'); foreach my $type (@mailings) { $otheremails{$type} = ''; } + } elsif ($position eq 'lower') { + if (ref($settings) eq 'HASH') { + if (ref($settings->{'lonstatus'}) eq 'HASH') { + %lonstatus = %{$settings->{'lonstatus'}}; + } + } } else { @mailings = ('helpdeskmail','otherdomsmail'); foreach my $type (@mailings) { @@ -3335,7 +3769,7 @@ sub print_contacts { ($fields,$fieldtitles,$fieldoptions,$possoptions) = &helpform_fields(); } if (ref($settings) eq 'HASH') { - unless ($position eq 'top') { + unless (($position eq 'top') || ($position eq 'lower')) { foreach my $type (@mailings) { if (exists($settings->{$type})) { if (ref($settings->{$type}) eq 'HASH') { @@ -3396,6 +3830,7 @@ sub print_contacts { $checked{'requestsmail'}{'adminemail'} = ' checked="checked" '; $checked{'updatesmail'}{'adminemail'} = ' checked="checked" '; $checked{'idconflictsmail'}{'adminemail'} = ' checked="checked" '; + $checked{'hostipmail'}{'adminemail'} = ' checked="checked" '; } elsif ($position eq 'bottom') { $checked{'helpdeskmail'}{'supportemail'} = ' checked="checked" '; $checked{'otherdomsmail'}{'supportemail'} = ' checked="checked" '; @@ -3456,7 +3891,7 @@ sub print_contacts { if ($currfield{$field} eq 'no') { $display = ' style="display:none"'; } - $datatable .= ''. + $datatable .= ''. ''.&mt('Maximum size for upload (MB)').''. ''; } @@ -3467,7 +3902,7 @@ sub print_contacts { $datatable .= ''."\n"; $rownum ++; } - unless ($position eq 'top') { + unless (($position eq 'top') || ($position eq 'lower')) { foreach my $type (@mailings) { $css_class = $rownum%2?' class="LC_odd_row"':''; $datatable .= ''. @@ -3501,7 +3936,7 @@ sub print_contacts { 'value="'.$bccemails{$type}.'" />'. '
'.&mt('Optional added text').''. &mt('Text automatically added to e-mail:').' '. - '
'. + '
'. ''.&mt('Location:').' '. ''. (' 'x2). @@ -3514,18 +3949,104 @@ sub print_contacts { } if ($position eq 'middle') { my %choices; - $choices{'reporterrors'} = &mt('E-mail error reports to [_1]', - &Apache::loncommon::modal_link('http://loncapa.org/core.html', - &mt('LON-CAPA core group - MSU'),600,500)); + my $corelink = &core_link_msu(); + $choices{'reporterrors'} = &mt('E-mail error reports to [_1]',$corelink); $choices{'reportupdates'} = &mt('E-mail record of completed LON-CAPA updates to [_1]', - &Apache::loncommon::modal_link('http://loncapa.org/core.html', - &mt('LON-CAPA core group - MSU'),600,500)); - my @toggles = ('reporterrors','reportupdates'); + $corelink); + $choices{'reportstatus'} = &mt('E-mail status if errors above threshold to [_1]',$corelink); + my @toggles = ('reporterrors','reportupdates','reportstatus'); my %defaultchecked = ('reporterrors' => 'on', - 'reportupdates' => 'on'); + 'reportupdates' => 'on', + 'reportstatus' => 'on'); (my $reports,$rownum) = &radiobutton_prefs($settings,\@toggles,\%defaultchecked, \%choices,$rownum); $datatable .= $reports; + } elsif ($position eq 'lower') { + my (%current,%excluded,%weights); + my ($defaults,$names) = &Apache::loncommon::lon_status_items(); + if ($lonstatus{'threshold'} =~ /^\d+$/) { + $current{'errorthreshold'} = $lonstatus{'threshold'}; + } else { + $current{'errorthreshold'} = $defaults->{'threshold'}; + } + if ($lonstatus{'sysmail'} =~ /^\d+$/) { + $current{'errorsysmail'} = $lonstatus{'sysmail'}; + } else { + $current{'errorsysmail'} = $defaults->{'sysmail'}; + } + if (ref($lonstatus{'weights'}) eq 'HASH') { + foreach my $type ('E','W','N','U') { + if ($lonstatus{'weights'}{$type} =~ /^\d+$/) { + $weights{$type} = $lonstatus{'weights'}{$type}; + } else { + $weights{$type} = $defaults->{$type}; + } + } + } else { + foreach my $type ('E','W','N','U') { + $weights{$type} = $defaults->{$type}; + } + } + if (ref($lonstatus{'excluded'}) eq 'ARRAY') { + if (@{$lonstatus{'excluded'}} > 0) { + map {$excluded{$_} = 1; } @{$lonstatus{'excluded'}}; + } + } + foreach my $item ('errorthreshold','errorsysmail') { + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''. + $titles->{$item}. + ''. + ''; + $rownum ++; + } + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''. + ''.$titles->{'errorweights'}. + ''; + foreach my $type ('E','W','N','U') { + $datatable .= ''; + } + $datatable .= '
'.$names->{$type}.'
'. + '
'; + $rownum ++; + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + $titles->{'errorexcluded'}.''. + ''; + my $numinrow = 4; + my @ids = sort(values(%Apache::lonnet::serverhomeIDs)); + for (my $i=0; $i<@ids; $i++) { + my $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + my $check; + if ($excluded{$ids[$i]}) { + $check = ' checked="checked" '; + } + $datatable .= ''; + } + my $colsleft = $numinrow - @ids%($numinrow); + if ($colsleft > 1 ) { + $datatable .= ''; + } elsif ($colsleft == 1) { + $datatable .= ''; + } + $datatable .= '
'. + ''. + '  
'; + $rownum ++; } elsif ($position eq 'bottom') { my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); my (@posstypes,%usertypeshash); @@ -3599,6 +4120,11 @@ sub print_contacts { return $datatable; } +sub core_link_msu { + return &Apache::loncommon::modal_link('http://loncapa.org/core.html', + &mt('LON-CAPA core group - MSU'),600,500); +} + sub overridden_helpdesk { my ($checked,$otheremails,$bccemails,$includeloc,$includestr,$type,$rowid, $typetitle,$css_class,$rowstyle,$contacts,$short_titles) = @_; @@ -3649,7 +4175,7 @@ sub overridden_helpdesk { 'value="'.$bccemails.'" />
'. '
'.&mt('Optional added text').''. &mt('Text automatically added to e-mail:').' '. - '
'. + '
'. ''.&mt('Location:').' '. ''. (' 'x2). @@ -3778,7 +4304,6 @@ sub print_helpsettings { push(@jsarray,('notinc','notexc')); } my $hiddenstr = join("','",@jsarray); - $datatable .= &helpsettings_javascript(\@roles_by_num,$maxnum,$hiddenstr,$formname); my $context = 'domprefs'; my $crstype = 'Course'; my $prefix = 'helproles_'; @@ -3881,7 +4406,9 @@ sub print_helpsettings { \@templateroles,$newcust). &Apache::lonuserutils::custom_role_table('Course',\%full,\%levels, \%levelscurrent,$newcust). - '
'; + ''. + &helpsettings_javascript(\@roles_by_num,$maxnum,$hiddenstr,$formname). + ''; $count ++; $$rowtotal += $count; } @@ -4098,7 +4625,7 @@ sub helpdeskroles_access { sub radiobutton_prefs { my ($settings,$toggles,$defaultchecked,$choices,$itemcount,$onclick, - $additional,$align) = @_; + $additional,$align,$firstval) = @_; return unless ((ref($toggles) eq 'ARRAY') && (ref($defaultchecked) eq 'HASH') && (ref($choices) eq 'HASH')); @@ -4138,15 +4665,21 @@ sub radiobutton_prefs { } else { $datatable .= ''; } - $datatable .= - ''. - ' '. - ''.$additional. - ''. - ''; + $datatable .= ''; + if ($firstval eq 'no') { + $datatable .= + ' '; + } else { + $datatable .= + ' '; + } + $datatable .= ''.$additional.''; $itemcount ++; } return ($datatable,$itemcount); @@ -4170,7 +4703,7 @@ sub print_ltitools { my $confname = $dom.'-domainconfig'; my $switchserver = &check_switchserver($dom,$confname); my $maxnum = scalar(keys(%ordered)); - my $datatable = <itools_javascript($settings); + my $datatable; my %lt = <itools_names(); my @courseroles = ('cc','in','ta','ep','st'); my @ltiroles = qw(Instructor ContentDeveloper TeachingAssistant Learner); @@ -4271,9 +4804,9 @@ sub print_ltitools { ''. (' 'x2); } - $datatable .= '
'. + $datatable .= '
'. '
'.$lt{'linktext'}.'
'. - '
'. + '
'. '
'.$lt{'explanation'}.'
'. '

'; @@ -4405,7 +4938,7 @@ sub print_ltitools { } $datatable .= ''.(' ' x2)."\n"; + $lt{'crs'.$item}.'  '."\n"; } $datatable .= '
'. '
'.&mt('Custom items sent on launch').''. @@ -4444,7 +4977,7 @@ sub print_ltitools { $datatable .= ''; } $datatable .= ' '."\n". - ''.&mt('Add').''."\n". + ''.&mt('Add').''."\n". ''. '
'.&mt('Required settings').''. ''.$lt{'title'}.': '."\n". @@ -4481,11 +5014,11 @@ sub print_ltitools { ''. (' 'x2); } - $datatable .= '
'. + $datatable .= '
'. '
'.$lt{'linktext'}.'
'. - '
'. + ''. '
'.$lt{'explanation'}.'
'. - ''. + ''. '

'; my %units = ( 'passback' => 'days', @@ -4564,7 +5097,7 @@ sub print_ltitools { ''. ''. - '
'."\n". + '
'."\n". ''."\n". ''."\n"; $itemcount ++; @@ -4607,6 +5140,640 @@ sub ltitools_names { return %lt; } +sub print_proctoring { + my ($dom,$settings,$rowtotal) = @_; + my $itemcount = 1; + my (%ordered,%providernames,%current,%currentdef); + my $confname = $dom.'-domainconfig'; + my $switchserver = &check_switchserver($dom,$confname); + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + } else { + %ordered = ( + 1 => 'proctorio', + 2 => 'examity', + ); + } + %providernames = &proctoring_providernames(); + my $maxnum = scalar(keys(%ordered)); + my (%requserfields,%optuserfields,%defaults,%extended,%crsconf,@courseroles,@ltiroles); + my ($requref,$opturef,$defref,$extref,$crsref,$rolesref,$ltiref) = &proctoring_data(); + if (ref($requref) eq 'HASH') { + %requserfields = %{$requref}; + } + if (ref($opturef) eq 'HASH') { + %optuserfields = %{$opturef}; + } + if (ref($defref) eq 'HASH') { + %defaults = %{$defref}; + } + if (ref($extref) eq 'HASH') { + %extended = %{$extref}; + } + if (ref($crsref) eq 'HASH') { + %crsconf = %{$crsref}; + } + if (ref($rolesref) eq 'ARRAY') { + @courseroles = @{$rolesref}; + } + if (ref($ltiref) eq 'ARRAY') { + @ltiroles = @{$ltiref}; + } + my $datatable; + my $css_class; + if (keys(%ordered)) { + my @items = sort { $a <=> $b } keys(%ordered); + for (my $i=0; $i<@items; $i++) { + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $provider = $ordered{$items[$i]}; + my $optionsty = 'none'; + my ($available,$version,$lifetime,$imgsrc,$userincdom,$showroles, + %checkedfields,%rolemaps,%inuse,%crsconfig,%current); + if (ref($settings) eq 'HASH') { + if (ref($settings->{$provider}) eq 'HASH') { + %current = %{$settings->{$provider}}; + if ($current{'available'}) { + $optionsty = 'block'; + $available = 1; + } + if ($current{'lifetime'} =~ /^\d+$/) { + $lifetime = $current{'lifetime'}; + } + if ($current{'version'} =~ /^\d+\.\d+$/) { + $version = $current{'version'}; + } + if ($current{'image'} ne '') { + $imgsrc = ''.&mt('Proctoring service icon').''; + } + if (ref($current{'fields'}) eq 'ARRAY') { + map { $checkedfields{$_} = 1; } @{$current{'fields'}}; + } + $userincdom = $current{'incdom'}; + if (ref($current{'roles'}) eq 'HASH') { + %rolemaps = %{$current{'roles'}}; + $checkedfields{'roles'} = 1; + } + if (ref($current{'defaults'}) eq 'ARRAY') { + foreach my $val (@{$current{'defaults'}}) { + if (grep(/^\Q$val\E$/,@{$defaults{$provider}})) { + $inuse{$val} = 1; + } else { + foreach my $poss (keys(%{$extended{$provider}})) { + if (ref($extended{$provider}{$poss}) eq 'ARRAY') { + if (grep(/^\Q$val\E$/,@{$extended{$provider}{$poss}})) { + $inuse{$poss} = $val; + last; + } + } + } + } + } + } elsif (ref($current{'defaults'}) eq 'HASH') { + foreach my $key (keys(%{$current{'defaults'}})) { + my $currval = $current{'defaults'}{$key}; + if (grep(/^\Q$key\E$/,@{$defaults{$provider}})) { + $inuse{$key} = 1; + } else { + my $match; + foreach my $poss (keys(%{$extended{$provider}})) { + if (ref($extended{$provider}{$poss}) eq 'ARRAY') { + if (grep(/^\Q$key\E$/,@{$extended{$provider}{$poss}})) { + $inuse{$poss} = $key; + last; + } + } elsif (ref($extended{$provider}{$poss}) eq 'HASH') { + foreach my $inner (sort(keys(%{$extended{$provider}{$poss}}))) { + if (ref($extended{$provider}{$poss}{$inner}) eq 'ARRAY') { + if (grep(/^\Q$currval\E$/,@{$extended{$provider}{$poss}{$inner}})) { + $currentdef{$inner} = $currval; + $match = 1; + last; + } + } elsif ($inner eq $key) { + $currentdef{$key} = $currval; + $match = 1; + last; + } + } + } + last if ($match); + } + } + } + } + if (ref($current{'crsconf'}) eq 'ARRAY') { + map { $crsconfig{$_} = 1; } @{$current{'crsconf'}}; + } + } + } + my %lt = &proctoring_titles($provider); + my %fieldtitles = &proctoring_fieldtitles($provider); + my $onclickavailable = ' onclick="toggleProctoring(this.form,'."'$provider'".');"'; + my %checkedavailable = ( + yes => '', + no => ' checked="checked"', + ); + if ($available) { + $checkedavailable{'yes'} = $checkedavailable{'no'}; + $checkedavailable{'no'} = ''; + } + my $chgstr = ' onchange="javascript:reorderProctoring(this.form,'."'proctoring_pos_".$provider."'".');"'; + $datatable .= '' + .''.(' 'x2).''.$providernames{$provider}.'
'. + ''.$lt{'avai'}.' '. + ' '."\n". + ''."\n". + ''. + ''. + '
'.$lt{'base'}.''. + ''.$lt{'version'}.': '."\n". + (' 'x2). + ''.$lt{'sigmethod'}.':'. + (' 'x2). + ''.$lt{'lifetime'}.': '."\n". + '
'. + ''.$lt{'url'}.': '."\n". + '
'. + ''.$lt{'key'}.': '."\n". + (' 'x2). + ''.$lt{'secret'}.':'. + '
'."\n"; + $datatable .= ''.$lt{'icon'}.': '; + if ($imgsrc) { + $datatable .= $imgsrc. + ' '. + ' '.&mt('Replace:'); + } + $datatable .= ' '; + if ($switchserver) { + $datatable .= &mt('Upload to library server: [_1]',$switchserver); + } else { + $datatable .= ''; + } + unless ($imgsrc) { + $datatable .= '
('.&mt('if larger than 21x21 pixels, image will be scaled').')'; + } + $datatable .= '
'."\n"; + if (ref($requserfields{$provider}) eq 'ARRAY') { + if (@{$requserfields{$provider}} > 0) { + $datatable .= '
'.$lt{'requ'}.''; + foreach my $field (@{$requserfields{$provider}}) { + $datatable .= ''. + ''; + if ($field eq 'user') { + my $seluserdom = ''; + my $unseluserdom = ' selected="selected"'; + if ($userincdom) { + $seluserdom = $unseluserdom; + $unseluserdom = ''; + } + $datatable .= ': '. + ' '; + } else { + $datatable .= ' '; + if ($field eq 'roles') { + $showroles = 1; + } + } + $datatable .= ' '; + } + } + $datatable .= '
'."\n"; + } + if (ref($optuserfields{$provider}) eq 'ARRAY') { + if (@{$optuserfields{$provider}} > 0) { + $datatable .= '
'.$lt{'optu'}.''; + foreach my $field (@{$optuserfields{$provider}}) { + my $checked; + if ($checkedfields{$field}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '  '; + } + $datatable .= '
'."\n"; + } + } + if (ref($defaults{$provider}) eq 'ARRAY') { + if (@{$defaults{$provider}}) { + my (%options,@selectboxes); + if (ref($extended{$provider}) eq 'HASH') { + %options = %{$extended{$provider}}; + } + $datatable .= '
'.$lt{'defa'}.''; + my ($rem,$numinrow,$dropdowns); + if ($provider eq 'proctorio') { + $datatable .= ''; + $numinrow = 4; + } + my $i = 0; + foreach my $field (@{$defaults{$provider}}) { + my $checked; + if ($inuse{$field}) { + $checked = ' checked="checked"'; + } + if ($provider eq 'examity') { + if ($field eq 'display') { + $datatable .= ''.&mt('Display target:'); + foreach my $option ('iframe','tab','window') { + my $checkdisp; + if ($currentdef{'target'} eq $option) { + $checkdisp = ' checked="checked"'; + } + $datatable .= ''.(' 'x2); + } + $datatable .= (' 'x4); + foreach my $dimen ('width','height') { + $datatable .= ''. + (' 'x2); + } + $datatable .= '
'. + '
'.$fieldtitles{'linktext'}.'
'. + '
'. + '
'.$fieldtitles{'explanation'}.'
'. + '

'; + } + } else { + if ((exists($options{$field})) && (ref($options{$field}) eq 'ARRAY')) { + my ($output,$selnone); + unless ($checked) { + $selnone = ' selected="selected"'; + } + $output .= ''.$fieldtitles{$field}.': '. + ''; + push(@selectboxes,$output); + } else { + $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + $datatable .= ''; + $i++; + } + } + } + if ($provider eq 'proctorio') { + if ($numinrow) { + $rem = $i%$numinrow; + } + my $colsleft = $numinrow - $rem; + if ($colsleft > 1) { + $datatable .= '
'. + ''. + ''; + } else { + $datatable .= ''; + } + $datatable .= ' '. + '
'; + if (@selectboxes) { + $datatable .= '
'; + $numinrow = 2; + for (my $i=0; $i<@selectboxes; $i++) { + $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + $datatable .= ''; + } + if ($numinrow) { + $rem = $i%$numinrow; + } + $colsleft = $numinrow - $rem; + if ($colsleft > 1) { + $datatable .= '
'. + $selectboxes[$i].''; + } else { + $datatable .= ''; + } + $datatable .= ' '. + '
'; + } + } + $datatable .= '
'; + } + if (ref($crsconf{$provider}) eq 'ARRAY') { + $datatable .= '
'. + ''.&mt('Configurable in course').''; + my ($rem,$numinrow); + if ($provider eq 'proctorio') { + $datatable .= ''; + $numinrow = 4; + } + my $i = 0; + foreach my $item (@{$crsconf{$provider}}) { + my $name; + if ($provider eq 'examity') { + $name = $lt{'crs'.$item}; + } elsif ($provider eq 'proctorio') { + $name = $fieldtitles{$item}; + $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + $datatable .= '
'. + $name.''; + if ($provider eq 'examity') { + $datatable .= '  '; + } + $datatable .= "\n"; + $i++; + } + if ($provider eq 'proctorio') { + if ($numinrow) { + $rem = $i%$numinrow; + } + my $colsleft = $numinrow - $rem; + if ($colsleft > 1) { + $datatable .= ''; + } else { + $datatable .= ''; + } + $datatable .= ' '. + '
'; + } + $datatable .= '
'; + } + if ($showroles) { + $datatable .= '
'. + ''.&mt('Role mapping').''; + foreach my $role (@courseroles) { + my ($selected,$selectnone); + if (!$rolemaps{$role}) { + $selectnone = ' selected="selected"'; + } + $datatable .= ''; + } + $datatable .= '
'. + &Apache::lonnet::plaintext($role,'Course').'
'. + '
'. + '
'. + ''.&mt('Custom items sent on launch').''. + ''. + ''. + ''; + if ((ref($settings) eq 'HASH') && (ref($settings->{$provider}) eq 'HASH') && + (ref($settings->{$provider}->{'custom'}) eq 'HASH')) { + my %custom = %{$settings->{$provider}->{'custom'}}; + if (keys(%custom) > 0) { + foreach my $key (sort(keys(%custom))) { + next if ($key eq 'lms'); + $datatable .= ''. + ''; + } + } + } + $datatable .= ''. + '
'.&mt('Action').''.&mt('Name').''.&mt('Value').'
lms
'. + ''.$key.'
'. + ''. + '
'."\n"; + } + $datatable .= ''; + } + $itemcount ++; + } + } + return $datatable; +} + +sub proctoring_data { + my $requserfields = { + proctorio => ['user'], + examity => ['roles','user'], + }; + my $optuserfields = { + proctorio => ['fullname'], + examity => ['fullname','firstname','lastname','email'], + }; + my $defaults = { + proctorio => ['recordvideo','recordaudio','recordscreen','recordwebtraffic', + 'recordroomstart','verifyvideo','verifyaudio','verifydesktop', + 'verifyid','verifysignature','fullscreen','clipboard','tabslinks', + 'closetabs','onescreen','print','downloads','cache','rightclick', + 'reentry','calculator','whiteboard'], + examity => ['display'], + }; + my $extended = { + proctorio => { + verifyid => ['verifyidauto','verifyidlive'], + fullscreen => ['fullscreenlenient','fullscreenmoderate','fullscreensever'], + tabslinks => ['notabs','linksonly'], + reentry => ['noreentry','agentreentry'], + calculator => ['calculatorbasic','calculatorsci'], + }, + examity => { + display => { + target => ['iframe','tab','window'], + width => '', + height => '', + linktext => '', + explanation => '', + }, + }, + }; + my $crsconf = { + proctorio => ['recordvideo','recordaudio','recordscreen','recordwebtraffic', + 'recordroomstart','verifyvideo','verifyaudio','verifydesktop', + 'verifyid','verifysignature','fullscreen','clipboard','tabslinks', + 'closetabs','onescreen','print','downloads','cache','rightclick', + 'reentry','calculator','whiteboard'], + examity => ['label','title','target','linktext','explanation','append'], + }; + my $courseroles = ['cc','in','ta','ep','st']; + my $ltiroles = ['Instructor','ContentDeveloper','TeachingAssistant','Learner']; + return ($requserfields,$optuserfields,$defaults,$extended,$crsconf,$courseroles,$ltiroles); +} + +sub proctoring_titles { + my ($item) = @_; + my (%common_lt,%custom_lt); + %common_lt = &Apache::lonlocal::texthash ( + 'avai' => 'Available?', + 'base' => 'Basic Settings', + 'requ' => 'User data required to be sent on launch', + 'optu' => 'User data optionally sent on launch', + 'udsl' => 'User data sent on launch', + 'defa' => 'Defaults for items configurable in course', + 'sigmethod' => 'Signature Method', + 'key' => 'Key', + 'lifetime' => 'Nonce lifetime (s)', + 'secret' => 'Secret', + 'icon' => 'Icon', + 'fullname' => 'Full Name', + 'visible' => 'Visible input', + 'username' => 'username', + 'user' => 'User', + ); + if ($item eq 'proctorio') { + %custom_lt = &Apache::lonlocal::texthash ( + 'version' => 'OAuth version', + 'url' => 'API URL', + 'uname:dom' => 'username-domain', + ); + } elsif ($item eq 'examity') { + %custom_lt = &Apache::lonlocal::texthash ( + 'version' => 'LTI Version', + 'url' => 'URL', + 'uname:dom' => 'username:domain', + 'msgtype' => 'Message Type', + 'firstname' => 'First Name', + 'lastname' => 'Last Name', + 'email' => 'E-mail', + 'roles' => 'Role', + 'crstarget' => 'Display target', + 'crslabel' => 'Course label', + 'crstitle' => 'Course title', + 'crslinktext' => 'Link Text', + 'crsexplanation' => 'Explanation', + 'crsappend' => 'Provider URL', + ); + } + my %lt = (%common_lt,%custom_lt); + return %lt; +} + +sub proctoring_fieldtitles { + my ($item) = @_; + if ($item eq 'proctorio') { + return &Apache::lonlocal::texthash ( + 'recordvideo' => 'Record video', + 'recordaudio' => 'Record audio', + 'recordscreen' => 'Record screen', + 'recordwebtraffic' => 'Record web traffic', + 'recordroomstart' => 'Record room scan', + 'verifyvideo' => 'Verify webcam', + 'verifyaudio' => 'Verify microphone', + 'verifydesktop' => 'Verify desktop recording', + 'verifyid' => 'Photo ID verification', + 'verifysignature' => 'Require signature', + 'fullscreen' => 'Fullscreen', + 'clipboard' => 'Disable copy/paste', + 'tabslinks' => 'New tabs/windows', + 'closetabs' => 'Close other tabs', + 'onescreen' => 'Limit to single screen', + 'print' => 'Disable Printing', + 'downloads' => 'Disable Downloads', + 'cache' => 'Empty cache after exam', + 'rightclick' => 'Disable right click', + 'reentry' => 'Re-entry to exam', + 'calculator' => 'Onscreen calculator', + 'whiteboard' => 'Onscreen whiteboard', + 'verifyidauto' => 'Automated verification', + 'verifyidlive' => 'Live agent verification', + 'fullscreenlenient' => 'Forced, but can navigate away for up to 30s', + 'fullscreenmoderate' => 'Forced, but can navigate away for up to 15s', + 'fullscreensever' => 'Forced, navigation away ends exam', + 'notabs' => 'Disaallowed', + 'linksonly' => 'Allowed from links in exam', + 'noreentry' => 'Disallowed', + 'agentreentry' => 'Agent required for re-entry', + 'calculatorbasic' => 'Basic', + 'calculatorsci' => 'Scientific', + ); + } elsif ($item eq 'examity') { + return &Apache::lonlocal::texthash ( + 'target' => 'Display target', + 'window' => 'Window', + 'tab' => 'Tab', + 'iframe' => 'iFrame', + 'height' => 'Height (pixels)', + 'width' => 'Width (pixels)', + 'linktext' => 'Default Link Text', + 'explanation' => 'Default Explanation', + 'append' => 'Provider URL', + ); + } +} + +sub proctoring_providernames { + return ( + proctorio => 'Proctorio', + examity => 'Examity', + ); +} + sub print_lti { my ($dom,$settings,$rowtotal) = @_; my $itemcount = 1; @@ -4622,21 +5789,31 @@ sub print_lti { } } my $maxnum = scalar(keys(%ordered)); - my $datatable = <i_javascript($settings); + my $datatable; my %lt = <i_names(); if (keys(%ordered)) { my @items = sort { $a <=> $b } keys(%ordered); for (my $i=0; $i<@items; $i++) { $css_class = $itemcount%2?' class="LC_odd_row"':''; my $item = $ordered{$items[$i]}; - my ($key,$secret,$lifetime,$consumer,$current); + my ($key,$secret,$lifetime,$consumer,$requser,$current); if (ref($settings->{$item}) eq 'HASH') { $key = $settings->{$item}->{'key'}; $secret = $settings->{$item}->{'secret'}; $lifetime = $settings->{$item}->{'lifetime'}; $consumer = $settings->{$item}->{'consumer'}; + $requser = $settings->{$item}->{'requser'}; $current = $settings->{$item}; } + my $onclickrequser = ' onclick="toggleLTI(this.form,'."'requser','$i'".');"'; + my %checkedrequser = ( + yes => ' checked="checked"', + no => '', + ); + if (!$requser) { + $checkedrequser{'no'} = $checkedrequser{'yes'}; + $checkedrequser{'yes'} = ''; + } my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'lti_pos_".$item."'".');"'; $datatable .= '' .' '. + ': '. (' 'x2). ''.$lt{'version'}.': '. (' 'x2). ''.$lt{'lifetime'}.':'. + 'value="'.$lifetime.'" size="3" />'. + (' 'x2). + ''.$lt{'requser'}.':'. + ' '."\n". + ''."\n". '

'. ''.$lt{'key'}. ': '. @@ -4687,16 +5868,20 @@ sub print_lti { $datatable .= ''; } $datatable .= ' '."\n". - ''.&mt('Add').''."\n". + ''.&mt('Add').''."\n". ''. '
'.&mt('Required settings').''. ''.$lt{'consumer'}. - ': '."\n". + ': '."\n". (' 'x2). ''.$lt{'version'}.': '."\n". (' 'x2). - ''.$lt{'lifetime'}.': '."\n". + ''.$lt{'lifetime'}.': '."\n". + (' 'x2). + ''.$lt{'requser'}.':'. + ' '."\n". + ''."\n". '

'. ''.$lt{'key'}.': '."\n". (' 'x2). @@ -4715,22 +5900,23 @@ sub lti_names { 'url' => 'URL', 'key' => 'Key', 'lifetime' => 'Nonce lifetime (s)', - 'consumer' => 'LTI Consumer', + 'consumer' => 'Consumer', 'secret' => 'Secret', + 'requser' => "User's identity sent", 'email' => 'Email address', 'sourcedid' => 'User ID', 'other' => 'Other', 'passback' => 'Can return grades to Consumer:', 'roster' => 'Can retrieve roster from Consumer:', 'topmenu' => 'Display LON-CAPA page header', - 'inlinemenu'=> 'Display LON-CAPA inline menu', + 'inlinemenu'=> 'Display LON-CAPA inline menu', ); return %lt; } sub lti_options { my ($num,$current,$itemcount,%lt) = @_; - my (%checked,%rolemaps,$crssecsrc,$userfield,$cidfield); + my (%checked,%rolemaps,$crssecsrc,$userfield,$cidfield,$callback); $checked{'mapuser'}{'sourcedid'} = ' checked="checked"'; $checked{'mapcrs'}{'course_offering_sourcedid'} = ' checked="checked"'; $checked{'makecrs'}{'N'} = ' checked="checked"'; @@ -4748,6 +5934,9 @@ sub lti_options { my $crsfieldsty = 'none'; my $crssecfieldsty = 'none'; my $secsrcfieldsty = 'none'; + my $callbacksty = 'none'; + my $passbacksty = 'none'; + my $optionsty = 'block'; my $lcauthparm; my $lcauthparmstyle = 'display:none'; my $lcauthparmtext; @@ -4756,6 +5945,9 @@ sub lti_options { my %menutitles = <imenu_titles(); if (ref($current) eq 'HASH') { + if (!$current->{'requser'}) { + $optionsty = 'none'; + } if (($current->{'mapuser'} ne '') && ($current->{'mapuser'} ne 'lis_person_sourcedid')) { $checked{'mapuser'}{'sourcedid'} = ''; if ($current->{'mapuser'} eq 'lis_person_contact_email_primary') { @@ -4781,7 +5973,7 @@ sub lti_options { $checked{'mapcrstype'}{$type} = ' checked="checked"'; } } - if ($current->{'makecrs'}) { + if ($current->{'makecrs'}) { $checked{'makecrs'}{'Y'} = ' checked="checked"'; } if (ref($current->{'makeuser'}) eq 'ARRAY') { @@ -4822,6 +6014,13 @@ sub lti_options { } else { $checked{'crssec'}{'N'} = ' checked="checked"'; } + if ($current->{'callback'} ne '') { + $callback = $current->{'callback'}; + $checked{'callback'}{'Y'} = ' checked="checked"'; + $callbacksty = 'inline-block'; + } else { + $checked{'callback'}{'N'} = ' checked="checked"'; + } if ($current->{'topmenu'}) { $checked{'topmenu'}{'Y'} = ' checked="checked"'; } else { @@ -4847,6 +6046,7 @@ sub lti_options { } else { $checked{'makecrs'}{'N'} = ' checked="checked"'; $checked{'crssec'}{'N'} = ' checked="checked"'; + $checked{'callback'}{'N'} = ' checked="checked"'; $checked{'topmenu'}{'N'} = ' checked="checked"'; $checked{'inlinemenu'}{'Y'} = ' checked="checked"'; $checked{'menuitem'}{'grades'} = ' checked="checked"'; @@ -4875,10 +6075,11 @@ sub lti_options { my $onclickuser = ' onclick="toggleLTI(this.form,'."'user','$num'".');"'; my $onclickcrs = ' onclick="toggleLTI(this.form,'."'crs','$num'".');"'; my $onclicksec = ' onclick="toggleLTI(this.form,'."'sec','$num'".');"'; + my $onclickcallback = ' onclick="toggleLTI(this.form,'."'callback','$num'".');"'; my $onclicksecsrc = ' onclick="toggleLTI(this.form,'."'secsrc','$num'".')"'; my $onclicklcauth = ' onclick="toggleLTI(this.form,'."'lcauth','$num'".')"'; my $onclickmenu = ' onclick="toggleLTI(this.form,'."'lcmenu','$num'".');"'; - my $output = '
'.&mt('Mapping users').''. + my $output = '
'.&mt('Mapping users').''. '
'.&mt('LON-CAPA username').': '; foreach my $option ('sourcedid','email','other') { $output .= '
'. - '
'.&mt('Mapping course roles').''; + 'value="'.$userfield.'" />'. + '
'.&mt('Mapping course roles').'
'; foreach my $ltirole (@lticourseroles) { my ($selected,$selectnone); if ($rolemaps{$ltirole} eq '') { @@ -4913,13 +6114,13 @@ sub lti_options { $output .= ''; } $output .= '
'. - '
'.&mt('Roles which may create user accounts').''; + '
'.&mt('Roles which may create user accounts').''; foreach my $ltirole (@ltiroles) { $output .= '  '; } $output .= '
'. - '
'.&mt('New user accounts created for LTI users').''. + '
'.&mt('New user accounts created for LTI users').''. ''. &modifiable_userdata_row('lti','instdata_'.$num,$current,$numinrow,$itemcount). '
'. @@ -4942,7 +6143,7 @@ sub lti_options { ''.$lcauthparmtext.''. ''. '
'. - '
'.&mt('Mapping courses').''. + '
'.&mt('Mapping courses').''. '
'. &mt('Unique course identifier').': '; foreach my $option ('course_offering_sourcedid','context_id','other') { @@ -4950,7 +6151,7 @@ sub lti_options { $checked{'mapcrs'}{$option}.$onclickcrs.' />'.$option.''. ($option eq 'other' ? '' : (' 'x2) ); } - $output .= '
'. + $output .= '
'. ''. '
'. ''.&mt('LON-CAPA course type(s)').': '; @@ -4960,25 +6161,25 @@ sub lti_options { (' 'x2); } $output .= '
'. - '
'.&mt('Creating courses').''. + '
'.&mt('Creating courses').''. ''.&mt('Course created (if absent) on Instructor access').': '. ''.(' 'x2). ''. '
'. - '
'.&mt('Roles which may self-enroll').''; + '
'.&mt('Roles which may self-enroll').''; foreach my $lticrsrole (@lticourseroles) { $output .= '  '; } $output .= '
'. - '
'.&mt('Course options').''. + '
'.&mt('Course options').''. '
'.&mt('Assign users to sections').': '. ''.(' 'x2). '
'. + $checked{'crssec'}{'Y'}.$onclicksec.' />'.&mt('Yes').''. '
'. ''.&mt('From').':
'. + '
'. ''. - '
'; - foreach my $extra ('passback','roster') { + '
'; + my ($pb1p1chk,$pb1p0chk,$onclickpb); + foreach my $extra ('roster','passback') { my $checkedon = ''; my $checkedoff = ' checked="checked"'; + if ($extra eq 'passback') { + $pb1p1chk = ' checked="checked"'; + $pb1p0chk = ''; + $onclickpb = ' onclick="toggleLTI(this.form,'."'passback','$num'".');"'; + } else { + $onclickpb = ''; + } if (ref($current) eq 'HASH') { if (($current->{$extra})) { $checkedon = $checkedoff; $checkedoff = ''; + if ($extra eq 'passback') { + $passbacksty = 'inline-block'; + } + if ($current->{'passbackformat'} eq '1.0') { + $pb1p0chk = ' checked="checked"'; + $pb1p1chk = ''; + } } } $output .= $lt{$extra}.' '. - '
'. - '
'.&mt('Course defaults (Course Coordinator can override)').''. + $output .= '
'. + ''.&mt('Grade format'). + ''.(' 'x2). + '
'. + '
'. + '
'.&mt('Callback on logout').': '. + ''.(' 'x2). + '
'. + '
'. + ''.&mt('Parameter').': '. + ''. + '
'. + '
'.&mt('Course defaults (Course Coordinator can override)').''. '
'.$lt{'topmenu'}.': '. ''.(' 'x2). '
'. + $checked{'topmenu'}{'Y'}.$onclickmenu.' />'.&mt('Yes').''. '
'. '
'.$lt{'inlinemenu'}.': '. ''.(' 'x2). '
'; + $checked{'inlinemenu'}{'Y'}.$onclickmenu.' />'.&mt('Yes').''; $output .='
'. '
'. ''.&mt('Menu items').': '; @@ -5025,7 +6256,7 @@ sub lti_options { $checked{'menuitem'}{$type}.' />'.$menutitles{$type}.''. (' 'x2); } - $output .= '
'; + $output .= '
'; # '
'.&mt('Assigning author roles').''; # # $output .= '
'. @@ -5060,7 +6291,6 @@ sub print_coursedefaults { mysqltables => 'Lifetime (s) of "Temporary" MySQL tables (student performance data) on homeserver', ); my %staticdefaults = ( - texengine => 'MathJax', anonsurvey_threshold => 10, uploadquota => 500, postsubmit => 60, @@ -5074,7 +6304,7 @@ sub print_coursedefaults { 'canclone' => 'none', ); @toggles = ('canuse_pdfforms','uselcmath','usejsme'); - my $deftex = $staticdefaults{'texengine'}; + my $deftex = $Apache::lonnet::deftex; if (ref($settings) eq 'HASH') { if ($settings->{'texengine'}) { if ($settings->{'texengine'} =~ /^(MathJax|mimetex|tth)$/) { @@ -5112,7 +6342,7 @@ sub print_coursedefaults { my $currcanclone = 'none'; my $onclick; my @cloneoptions = ('none','domain'); - my %clonetitles = ( + my %clonetitles = &Apache::lonlocal::texthash ( none => 'No additional course requesters', domain => "Any course requester in course's domain", instcode => 'Course requests for official courses ...', @@ -5521,7 +6751,7 @@ sub print_validation_rows { ' '; } } elsif ($item eq 'markup') { - $datatable .= ''; } @@ -5543,7 +6773,7 @@ sub print_validation_rows { my ($numdc,$dctable,$rows) = &active_dc_picker($dom,$numinrow,'radio', 'validationdc',%currhash); my $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; - $datatable .= ''; + $datatable .= ''; if ($numdc > 1) { $datatable .= &mt('Course creation processed as: (choose Dom. Coord.)'); } else { @@ -5558,6 +6788,855 @@ sub print_validation_rows { return $datatable; } +sub print_privacy { + my ($position,$dom,$settings,$rowtotal) = @_; + my ($datatable,$css_class,$numinrow,@items,%names,$othertitle,$usertypes,$types); + my $itemcount = 0; + unless ($position eq 'top') { + @items = ('domain','author','course','community'); + %names = &Apache::lonlocal::texthash ( + domain => 'Assigned domain role(s)', + author => 'Assigned co-author role(s)', + course => 'Assigned course role(s)', + community => 'Assigned community role', + ); + $numinrow = 4; + ($othertitle,$usertypes,$types) = + &Apache::loncommon::sorted_inst_types($dom); + } + if (($position eq 'top') || ($position eq 'middle')) { + my (%by_ip,%by_location,@intdoms,@instdoms); + &build_location_hashes(\@intdoms,\%by_ip,\%by_location,\@instdoms); + if ($position eq 'top') { + my %curr; + my @options = ('none','user','domain','auto'); + my %titles = &Apache::lonlocal::texthash ( + none => 'Not allowed', + user => 'User authorizes', + domain => 'DC authorizes', + auto => 'Unrestricted', + instdom => 'Other domain shares institution/provider', + extdom => 'Other domain has different institution/provider', + ); + my %names = &Apache::lonlocal::texthash ( + domain => 'Domain role', + author => 'Co-author role', + course => 'Course role', + community => 'Community role', + ); + my $primary_id = &Apache::lonnet::domain($dom,'primary'); + my $intdom = &Apache::lonnet::internet_dom($primary_id); + foreach my $domtype ('instdom','extdom') { + my (%checked,$skip); + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{$domtype}.''. + ''; + if ($domtype eq 'instdom') { + unless (@instdoms > 1) { + $datatable .= &mt('Nothing to set, as no domains besides [_1] are hosted by [_2]',$dom,$intdom); + $skip = 1; + } + } elsif ($domtype eq 'extdom') { + if (keys(%by_location) == 0) { + $datatable .= &mt('Nothing to set, as no other hosts besides [_1]',$intdom); + $skip = 1; + } + } + unless ($skip) { + foreach my $roletype ('domain','author','course','community') { + $checked{'auto'} = ' checked="checked"'; + if (ref($settings) eq 'HASH') { + if (ref($settings->{approval}) eq 'HASH') { + if (ref($settings->{approval}->{$domtype}) eq 'HASH') { + if ($settings->{approval}->{$domtype}->{$roletype}=~ /^(none|user|domain)$/) { + $checked{$1} = ' checked="checked"'; + $checked{'auto'} = ''; + } + } + } + } + $datatable .= '
'.$names{$roletype}.''; + foreach my $option (@options) { + $datatable .= '  '; + } + $datatable .= '
'; + } + } + $datatable .= ''; + $itemcount ++; + } + } elsif ($position eq 'middle') { + if ((@instdoms > 1) || (keys(%by_location) > 0)) { + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + foreach my $item (@{$types}) { + $datatable .= &modifiable_userdata_row('privacy','othdom_'.$item,$settings, + $numinrow,$itemcount,'','','','','', + '',$usertypes->{$item}); + $itemcount ++; + } + } + $datatable .= &modifiable_userdata_row('privacy','othdom_default',$settings, + $numinrow,$itemcount,'','','','','', + '',$othertitle); + $itemcount ++; + } else { + my (@insttypes,%insttitles); + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + @insttypes = @{$types}; + %insttitles = %{$usertypes}; + } + foreach my $item (@insttypes,'default') { + my $title; + if ($item eq 'default') { + $title = $othertitle; + } else { + $title = $insttitles{$item}; + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''.$title.''. + ''. + &mt('Nothing to set here, as there are no other domains'). + ''; + $itemcount ++; + } + } + } + } else { + my $prefix; + if ($position eq 'lower') { + $prefix = 'priv'; + } else { + $prefix = 'unpriv'; + } + foreach my $item (@items) { + $datatable .= &modifiable_userdata_row('privacy',$prefix.'_'.$item,$settings, + $numinrow,$itemcount,'','','','','', + '',$names{$item}); + $itemcount ++; + } + } + if (ref($rowtotal)) { + $$rowtotal += $itemcount; + } + return $datatable; +} + +sub print_passwords { + my ($position,$dom,$confname,$settings,$rowtotal) = @_; + my ($datatable,$css_class); + my $itemcount = 0; + my %titles = &Apache::lonlocal::texthash ( + captcha => '"Forgot Password" CAPTCHA validation', + link => 'Reset link expiration (hours)', + case => 'Case-sensitive usernames/e-mail', + prelink => 'Information required (form 1)', + postlink => 'Information required (form 2)', + emailsrc => 'LON-CAPA e-mail address type(s)', + customtext => 'Domain specific text (HTML)', + intauth_cost => 'Encryption cost for bcrypt (positive integer)', + intauth_check => 'Check bcrypt cost if authenticated', + intauth_switch => 'Existing crypt-based switched to bcrypt on authentication', + permanent => 'Permanent e-mail address', + critical => 'Critical notification address', + notify => 'Notification address', + min => 'Minimum password length', + max => 'Maximum password length', + chars => 'Required characters', + expire => 'Password expiration (days)', + numsaved => 'Number of previous passwords to save and disallow reuse', + ); + if ($position eq 'top') { + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my $shownlinklife = 2; + my $prelink = 'both'; + my (%casesens,%postlink,%emailsrc,$nostdtext,$customurl); + if (ref($settings) eq 'HASH') { + if ($settings->{resetlink} =~ /^\d+(|\.\d*)$/) { + $shownlinklife = $settings->{resetlink}; + } + if (ref($settings->{resetcase}) eq 'ARRAY') { + map { $casesens{$_} = 1; } (@{$settings->{resetcase}}); + } + if ($settings->{resetprelink} =~ /^(both|either)$/) { + $prelink = $settings->{resetprelink}; + } + if (ref($settings->{resetpostlink}) eq 'HASH') { + %postlink = %{$settings->{resetpostlink}}; + } + if (ref($settings->{resetemail}) eq 'ARRAY') { + map { $emailsrc{$_} = 1; } (@{$settings->{resetemail}}); + } + if ($settings->{resetremove}) { + $nostdtext = 1; + } + if ($settings->{resetcustom}) { + $customurl = $settings->{resetcustom}; + } + } else { + if (ref($types) eq 'ARRAY') { + foreach my $item (@{$types}) { + $casesens{$item} = 1; + $postlink{$item} = ['username','email']; + } + } + $casesens{'default'} = 1; + $postlink{'default'} = ['username','email']; + $prelink = 'both'; + %emailsrc = ( + permanent => 1, + critical => 1, + notify => 1, + ); + } + $datatable = &captcha_choice('passwords',$settings,$$rowtotal); + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'link'}.''. + ''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'case'}.''. + ''; + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + foreach my $item (@{$types}) { + my $checkedcase; + if ($casesens{$item}) { + $checkedcase = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + } + my $checkedcase; + if ($casesens{'default'}) { + $checkedcase = ' checked="checked"'; + } + $datatable .= ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my %checkedpre = ( + both => ' checked="checked"', + either => '', + ); + if ($prelink eq 'either') { + $checkedpre{either} = ' checked="checked"'; + $checkedpre{both} = ''; + } + $datatable .= ''.$titles{'prelink'}.''. + ''. + '   '. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'postlink'}.''. + ''; + my %postlinked; + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + foreach my $item (@{$types}) { + undef(%postlinked); + $datatable .= '
'. + ''.$usertypes->{$item}.''; + if (ref($postlink{$item}) eq 'ARRAY') { + map { $postlinked{$_} = 1; } (@{$postlink{$item}}); + } + foreach my $field ('email','username') { + my $checked; + if ($postlinked{$field}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + $datatable .= '
'; + } + } + if (ref($postlink{'default'}) eq 'ARRAY') { + map { $postlinked{$_} = 1; } (@{$postlink{'default'}}); + } + $datatable .= '
'. + ''.$othertitle.''; + foreach my $field ('email','username') { + my $checked; + if ($postlinked{$field}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + $datatable .= '
'; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'emailsrc'}.''. + ''; + foreach my $type ('permanent','critical','notify') { + my $checkedemail; + if ($emailsrc{$type}) { + $checkedemail = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + $datatable .= ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $switchserver = &check_switchserver($dom,$confname); + my ($showstd,$noshowstd); + if ($nostdtext) { + $noshowstd = ' checked="checked"'; + } else { + $showstd = ' checked="checked"'; + } + $datatable .= ''.$titles{'customtext'}.''. + ''. + &mt('Retain standard text:'). + ''.' '. + '
'. + ''. + &mt('(If you use the same account ... reset a password from this page.)').'

'. + &mt('Include custom text:'); + if ($customurl) { + my $link = &Apache::loncommon::modal_link($customurl,&mt('custom text'),600,500, + undef,undef,undef,undef,'background-color:#ffffff'); + $datatable .= ' '.$link. + ''. + '  '.&mt('Replace:').''; + } + if ($switchserver) { + $datatable .= ' '.&mt('Upload to library server: [_1]',$switchserver).''; + } else { + $datatable .=' '. + ''; + } + $datatable .= ''; + } elsif ($position eq 'middle') { + my %domconf = &Apache::lonnet::get_dom('configuration',['defaults'],$dom); + my @items = ('intauth_cost','intauth_check','intauth_switch'); + my %defaults; + if (ref($domconf{'defaults'}) eq 'HASH') { + %defaults = %{$domconf{'defaults'}}; + if ($defaults{'intauth_cost'} !~ /^\d+$/) { + $defaults{'intauth_cost'} = 10; + } + if ($defaults{'intauth_check'} !~ /^(0|1|2)$/) { + $defaults{'intauth_check'} = 0; + } + if ($defaults{'intauth_switch'} !~ /^(0|1|2)$/) { + $defaults{'intauth_switch'} = 0; + } + } else { + %defaults = ( + 'intauth_cost' => 10, + 'intauth_check' => 0, + 'intauth_switch' => 0, + ); + } + foreach my $item (@items) { + if ($itemcount%2) { + $css_class = ''; + } else { + $css_class = ' class="LC_odd_row" '; + } + $datatable .= ''. + ''.$titles{$item}. + ''; + if ($item eq 'intauth_switch') { + my @options = (0,1,2); + my %optiondesc = &Apache::lonlocal::texthash ( + 0 => 'No', + 1 => 'Yes', + 2 => 'Yes, and copy existing passwd file to passwd.bak file', + ); + $datatable .= ''; + foreach my $option (@options) { + my $checked = ' '; + if ($defaults{$item} eq $option) { + $checked = ' checked="checked"'; + } + $datatable .= ''; + } + $datatable .= '
'. + '
'; + } elsif ($item eq 'intauth_check') { + my @options = (0,1,2); + my %optiondesc = &Apache::lonlocal::texthash ( + 0 => 'No', + 1 => 'Yes, allow login then update passwd file using default cost (if higher)', + 2 => 'Yes, disallow login if stored cost is less than domain default', + ); + $datatable .= ''; + foreach my $option (@options) { + my $checked = ' '; + my $onclick; + if ($defaults{$item} eq $option) { + $checked = ' checked="checked"'; + } + if ($option == 2) { + $onclick = ' onclick="javascript:warnIntAuth(this);"'; + } + $datatable .= ''; + } + $datatable .= '
'. + '
'; + } else { + $datatable .= ''; + } + $datatable .= ''; + $itemcount ++; + } + } elsif ($position eq 'lower') { + my ($min,$max,%chars,$expire,$numsaved); + $min = $Apache::lonnet::passwdmin; + if (ref($settings) eq 'HASH') { + if ($settings->{min}) { + $min = $settings->{min}; + } + if ($settings->{max}) { + $max = $settings->{max}; + } + if (ref($settings->{chars}) eq 'ARRAY') { + map { $chars{$_} = 1; } (@{$settings->{chars}}); + } + if ($settings->{expire}) { + $expire = $settings->{expire}; + } + if ($settings->{numsaved}) { + $numsaved = $settings->{numsaved}; + } + } + my %rulenames = &Apache::lonlocal::texthash( + uc => 'At least one upper case letter', + lc => 'At least one lower case letter', + num => 'At least one number', + spec => 'At least one non-alphanumeric', + ); + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'min'}.''. + ''. + ''. + ' '.&mt('(Enter an integer: 7 or larger)').''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'max'}.''. + ''. + ''. + ' '.&mt('(Leave blank for no maximum)').''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'chars'}.'
'. + ''.&mt('(Leave unchecked if not required)'). + ''; + my $numinrow = 2; + my @possrules = ('uc','lc','num','spec'); + $datatable .= ''; + for (my $i=0; $i<@possrules; $i++) { + my ($rem,$checked); + if ($chars{$possrules[$i]}) { + $checked = ' checked="checked"'; + } + $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + $datatable .= ''; + } + my $rem = @possrules%($numinrow); + my $colsleft = $numinrow - $rem; + if ($colsleft > 1 ) { + $datatable .= ''; + } elsif ($colsleft == 1) { + $datatable .= ''; + } + $datatable .='
'. + '  
'; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'expire'}.''. + ''. + ''. + ' '.&mt('(Leave blank for no expiration)').''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'numsaved'}.''. + ''. + ''. + ' '.&mt('(Leave blank to not save previous passwords)').''. + ''; + } else { + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my %ownerchg = ( + by => {}, + for => {}, + ); + my %ownertitles = &Apache::lonlocal::texthash ( + by => 'Course owner status(es) allowed', + for => 'Student status(es) allowed', + ); + if (ref($settings) eq 'HASH') { + if (ref($settings->{crsownerchg}) eq 'HASH') { + if (ref($settings->{crsownerchg}{'by'}) eq 'ARRAY') { + map { $ownerchg{by}{$_} = 1; } (@{$settings->{crsownerchg}{'by'}}); + } + if (ref($settings->{crsownerchg}{'for'}) eq 'ARRAY') { + map { $ownerchg{for}{$_} = 1; } (@{$settings->{crsownerchg}{'for'}}); + } + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''. + &mt('Requirements').'
    '. + '
  • '.&mt("Course 'type' is not a Community or Placement Test").'
  • '. + '
  • '.&mt('User is Course Coordinator and also course owner').'
  • '. + '
  • '.&mt("Student's only active roles are student role(s) in course(s) owned by this user").'
  • '. + '
  • '.&mt('User, course, and student share same domain').'
  • '. + '
'. + ''. + ''; + foreach my $item ('by','for') { + $datatable .= '
'. + ''.$ownertitles{$item}.''; + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + foreach my $type (@{$types}) { + my $checked; + if ($ownerchg{$item}{$type}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + } + my $checked; + if ($ownerchg{$item}{'default'}) { + $checked = ' checked="checked"'; + } + $datatable .= '
'; + } + $datatable .= ''; + } + return $datatable; +} + +sub print_wafproxy { + my ($position,$dom,$settings,$rowtotal) = @_; + my $css_class; + my $itemcount = 0; + my $datatable; + my %servers = &Apache::lonnet::internet_dom_servers($dom); + my (%othercontrol,%otherdoms,%aliases,%values,$setdom,$showdom); + my %lt = &wafproxy_titles(); + foreach my $server (sort(keys(%servers))) { + my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server}); + next if ($serverhome eq ''); + my $serverdom; + if ($serverhome ne $server) { + $serverdom = &Apache::lonnet::host_domain($serverhome); + if (($serverdom ne '') && (&Apache::lonnet::domain($serverdom) ne '')) { + $othercontrol{$server} = $serverdom; + } + } else { + $serverdom = &Apache::lonnet::host_domain($server); + next if (($serverdom eq '') || (&Apache::lonnet::domain($serverdom) eq '')); + if ($serverdom ne $dom) { + $othercontrol{$server} = $serverdom; + } else { + $setdom = 1; + if (ref($settings) eq 'HASH') { + if (ref($settings->{'alias'}) eq 'HASH') { + $aliases{$dom} = $settings->{'alias'}; + if ($aliases{$dom} ne '') { + $showdom = 1; + } + } + } + } + } + } + if ($setdom) { + %{$values{$dom}} = (); + if (ref($settings) eq 'HASH') { + foreach my $item ('remoteip','ipheader','trusted','vpnint','vpnext') { + $values{$dom}{$item} = $settings->{$item}; + } + } + } + if (keys(%othercontrol)) { + %otherdoms = reverse(%othercontrol); + foreach my $domain (keys(%otherdoms)) { + %{$values{$domain}} = (); + my %config = &Apache::lonnet::get_dom('configuration',['wafproxy'],$domain); + if (ref($config{'wafproxy'}) eq 'HASH') { + $aliases{$domain} = $config{'wafproxy'}{'alias'}; + foreach my $item ('remoteip','ipheader','trusted','vpnint','vpnext') { + $values{$domain}{$item} = $config{'wafproxy'}{$item}; + } + } + } + } + if ($position eq 'top') { + my %servers = &Apache::lonnet::internet_dom_servers($dom); + my %aliasinfo; + foreach my $server (sort(keys(%servers))) { + $itemcount ++; + my $dom_in_effect; + my $aliasrows = ''. + ''. + &mt('Hostname').': '. + ''.&Apache::lonnet::hostname($server).' '; + if ($othercontrol{$server}) { + $dom_in_effect = $othercontrol{$server}; + my $current; + if (ref($aliases{$dom_in_effect}) eq 'HASH') { + $current = $aliases{$dom_in_effect}{$server}; + } + $aliasrows .= ''. + &mt('Alias').': '; + if ($current) { + $aliasrows .= $current; + } else { + $aliasrows .= &mt('None'); + } + $aliasrows .= ' ('. + &mt('controlled by domain: [_1]', + ''.$dom_in_effect.'').')'; + } else { + $dom_in_effect = $dom; + my $current; + if (ref($aliases{$dom}) eq 'HASH') { + if ($aliases{$dom}{$server}) { + $current = $aliases{$dom}{$server}; + } + } + $aliasrows .= ''. + &mt('Alias').': '. + ''; + } + $aliasrows .= ''; + $aliasinfo{$dom_in_effect} .= $aliasrows; + } + if ($aliasinfo{$dom}) { + my ($onclick,$wafon,$wafoff,$showtable); + $onclick = ' onclick="javascript:toggleWAF();"'; + $wafoff = ' checked="checked"'; + $showtable = ' style="display:none";'; + if ($showdom) { + $wafon = $wafoff; + $wafoff = ''; + $showtable = ' style="display:inline;"'; + } + $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; + $datatable = ''. + ''.&mt('Domain: [_1]',''.$dom.'').'
'. + ''.&mt('WAF in use?').' '.(' 'x2).''. + ''. + ''.$aliasinfo{$dom}. + '
'; + $itemcount++; + } + if (keys(%otherdoms)) { + foreach my $key (sort(keys(%otherdoms))) { + $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; + $datatable .= ''. + ''.&mt('Domain: [_1]',''.$key.'').''. + ''.$aliasinfo{$key}. + '
'; + $itemcount++; + } + } + } else { + my %ip_methods = &remoteip_methods(); + if ($setdom) { + $itemcount ++; + $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; + my ($nowafstyle,$wafstyle,$curr_remotip,$currwafdisplay,$vpndircheck,$vpnaliascheck, + $currwafvpn,$wafrangestyle,$alltossl,$ssltossl); + $wafstyle = ' style="display:none;"'; + $nowafstyle = ' style="display:table-row;"'; + $currwafdisplay = ' style="display: none"'; + $wafrangestyle = ' style="display: none"'; + $curr_remotip = 'n'; + $ssltossl = ' checked="checked"'; + if ($showdom) { + $wafstyle = ' style="display:table-row;"'; + $nowafstyle = ' style="display:none;"'; + if (keys(%{$values{$dom}})) { + if ($values{$dom}{remoteip} =~ /^[nmh]$/) { + $curr_remotip = $values{$dom}{remoteip}; + } + if ($curr_remotip eq 'h') { + $currwafdisplay = ' style="display:table-row"'; + $wafrangestyle = ' style="display:inline-block;"'; + } + if ($values{$dom}{'sslopt'}) { + $alltossl = ' checked="checked"'; + $ssltossl = ''; + } + } + if (($values{$dom}{'vpnint'} ne '') || ($values{$dom}{'vpnext'} ne '')) { + $vpndircheck = ' checked="checked"'; + $currwafvpn = ' style="display:table-row;"'; + $wafrangestyle = ' style="display:inline-block;"'; + } else { + $vpnaliascheck = ' checked="checked"'; + $currwafvpn = ' style="display:none;"'; + } + } + $datatable .= ''. + ''.&mt('Domain: [_1]',''.$dom.'').''. + ''.&mt('WAF not in use, nothing to set').''. + ''. + ''. + ''.&mt('Domain: [_1]',''.$dom.'').'

'. + '
'.&mt('Format for comma separated IP ranges').':
'. + &mt('A.B.C.D/N or A.B.C.D-E.F.G.H').'
'. + ''. + ''. + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''. + ''; + foreach my $item ('vpnint','vpnext') { + $datatable .= ''. + ''."\n"; + } + $datatable .= ''."\n". + ''. + ''."\n". + '
'.$lt{'remoteip'}.': '. + '
'. + $lt{'ipheader'}.': '. + ''. + '
'. + $lt{'trusted'}.':
'. + ''. + '

'.$lt{'vpnaccess'}.':
'. + ''.(' 'x2). + '
'.$lt{$item}.':
'. + ''. + '

'.$lt{'sslopt'}.':
'. + ''.(' 'x2). + '
'; + } + if (keys(%otherdoms)) { + foreach my $domain (sort(keys(%otherdoms))) { + $itemcount ++; + $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; + $datatable .= ''. + ''.&mt('Domain: [_1]',''.$domain.'').''. + ''; + foreach my $item ('remoteip','ipheader','trusted','vpnint','vpnext','sslopt') { + my $showval = &mt('None'); + if ($item eq 'ssl') { + $showval = $lt{'ssltossl'}; + } + if ($values{$domain}{$item}) { + $showval = $values{$domain}{$item}; + if ($item eq 'ssl') { + $showval = $lt{'alltossl'}; + } elsif ($item eq 'remoteip') { + $showval = $ip_methods{$values{$domain}{$item}}; + } + } + $datatable .= ''. + ''; + } + $datatable .= '
'.$lt{$item}.': '.$showval.'
'; + } + } + } + $$rowtotal += $itemcount; + return $datatable; +} + +sub wafproxy_titles { + return &Apache::lonlocal::texthash( + remoteip => "Method for determining user's IP", + ipheader => 'Request header containing remote IP', + trusted => 'Trusted IP range(s)', + vpnaccess => 'Access from institutional VPN', + vpndirect => 'via regular hostname (no WAF)', + vpnaliased => 'via aliased hostname (WAF)', + vpnint => 'Internal IP Range(s) for VPN sessions', + vpnext => 'IP Range(s) for backend WAF connections', + sslopt => 'Forwarding http/https', + alltossl => 'WAF forwards both http and https requests to https', + ssltossl => 'WAF forwards http requests to http and https to https', + ); +} + +sub remoteip_methods { + return &Apache::lonlocal::texthash( + m => 'Use Apache mod_remoteip', + h => 'Use headers parsed by LON-CAPA', + n => 'Not in use', + ); +} + sub print_usersessions { my ($position,$dom,$settings,$rowtotal) = @_; my ($css_class,$datatable,$itemcount,%checked,%choices); @@ -5571,13 +7650,18 @@ sub print_usersessions { if ($position eq 'top') { if (keys(%serverhomes) > 1) { my %spareid = ¤t_offloads_to($dom,$settings,\%servers); - my $curroffloadnow; + my ($curroffloadnow,$curroffloadoth); if (ref($settings) eq 'HASH') { if (ref($settings->{'offloadnow'}) eq 'HASH') { $curroffloadnow = $settings->{'offloadnow'}; } + if (ref($settings->{'offloadoth'}) eq 'HASH') { + $curroffloadoth = $settings->{'offloadoth'}; + } } - $datatable .= &spares_row($dom,\%servers,\%spareid,\%serverhomes,\%altids,$curroffloadnow,$rowtotal); + my $other_insts = scalar(keys(%by_location)); + $datatable .= &spares_row($dom,\%servers,\%spareid,\%serverhomes,\%altids, + $other_insts,$curroffloadnow,$curroffloadoth,$rowtotal); } else { $datatable .= ''. &mt('Nothing to set here, as the cluster to which this domain belongs only contains one server.'). @@ -6021,7 +8105,8 @@ sub current_offloads_to { } sub spares_row { - my ($dom,$servers,$spareid,$serverhomes,$altids,$curroffloadnow,$rowtotal) = @_; + my ($dom,$servers,$spareid,$serverhomes,$altids,$other_insts, + $curroffloadnow,$curroffloadoth,$rowtotal) = @_; my $css_class; my $numinrow = 4; my $itemcount = 1; @@ -6041,12 +8126,17 @@ sub spares_row { } } next unless (ref($spareid->{$server}) eq 'HASH'); - my $checkednow; + my ($checkednow,$checkedoth); if (ref($curroffloadnow) eq 'HASH') { if ($curroffloadnow->{$server}) { $checkednow = ' checked="checked"'; } } + if (ref($curroffloadoth) eq 'HASH') { + if ($curroffloadoth->{$server}) { + $checkedoth = ' checked="checked"'; + } + } $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; $datatable .= ' @@ -6055,8 +8145,15 @@ sub spares_row { ,''.$server.'').'

'. ''."\n". ''. + ' '.&mt('Switch any active user on next access').'
'. "\n"; + if ($other_insts) { + $datatable .= '
'. + ''."\n". + ''. + "\n"; + } my (%current,%canselect); my @choices = &possible_newspares($server,$spareid->{$server},$serverhomes,$altids); @@ -6180,13 +8277,13 @@ sub print_loadbalancing { my $numinrow = 1; my $datatable; my %servers = &Apache::lonnet::internet_dom_servers($dom); - my (%currbalancer,%currtargets,%currrules,%existing); + my (%currbalancer,%currtargets,%currrules,%existing,%currcookies); if (ref($settings) eq 'HASH') { %existing = %{$settings}; } if ((keys(%servers) > 1) || (keys(%existing) > 0)) { &get_loadbalancers_config(\%servers,\%existing,\%currbalancer, - \%currtargets,\%currrules); + \%currtargets,\%currrules,\%currcookies); } else { return; } @@ -6263,6 +8360,9 @@ sub print_loadbalancing { my %hostherechecked = ( no => ' checked="checked"', ); + my %balcookiechecked = ( + no => ' checked="checked"', + ); foreach my $sparetype (@sparestypes) { my $targettable; for (my $i=0; $i<$numspares; $i++) { @@ -6318,6 +8418,11 @@ sub print_loadbalancing { } } } + if ($currcookies{$lonhost}) { + %balcookiechecked = ( + yes => ' checked="checked"', + ); + } $datatable .= &mt('Hosting on balancer itself').'
'. '
'; @@ -6326,7 +8431,12 @@ sub print_loadbalancing { 'value="'.$sparetype.'"'.$hostherechecked{$sparetype}.' />'.$typetitles{$sparetype}. '
'; } - $datatable .= ''. + $datatable .= &mt('Use balancer cookie').'
'. + '
'. + '
'. + ''. &loadbalancing_rules($dom,$intdom,$currrules{$lonhost}, $othertitle,$usertypes,$types,\%servers, \%currbalancer,$lonhost, @@ -6340,10 +8450,11 @@ sub print_loadbalancing { } sub get_loadbalancers_config { - my ($servers,$existing,$currbalancer,$currtargets,$currrules) = @_; + my ($servers,$existing,$currbalancer,$currtargets,$currrules,$currcookies) = @_; return unless ((ref($servers) eq 'HASH') && (ref($existing) eq 'HASH') && (ref($currbalancer) eq 'HASH') && - (ref($currtargets) eq 'HASH') && (ref($currrules) eq 'HASH')); + (ref($currtargets) eq 'HASH') && (ref($currrules) eq 'HASH') && + (ref($currcookies) eq 'HASH')); if (keys(%{$existing}) > 0) { my $oldlonhost; foreach my $key (sort(keys(%{$existing}))) { @@ -6362,6 +8473,9 @@ sub get_loadbalancers_config { $currbalancer->{$key} = 1; $currtargets->{$key} = $existing->{$key}{'targets'}; $currrules->{$key} = $existing->{$key}{'rules'}; + if ($existing->{$key}{'cookie'}) { + $currcookies->{$key} = 1; + } } } } else { @@ -6557,6 +8671,11 @@ sub contact_titles { 'requestsmail' => 'E-mail from course requests requiring approval', 'updatesmail' => 'E-mail from nightly check of LON-CAPA module integrity/updates', 'idconflictsmail' => 'E-mail from bi-nightly check for multiple users sharing same student/employee ID', + 'hostipmail' => 'E-mail from nightly check of hostname/IP network changes', + 'errorthreshold' => 'Error count threshold for status e-mail to admin(s)', + 'errorsysmail' => 'Error count threshold for e-mail to developer group', + 'errorweights' => 'Weights used to compute error count', + 'errorexcluded' => 'Servers with unsent updates excluded from count', ); my %short_titles = &Apache::lonlocal::texthash ( adminemail => 'Admin E-mail address', @@ -6846,7 +8965,7 @@ sub print_selfcreation { $datatable .= ''. ''.&mt('Mapping of Shibboleth environment variable names to user data fields (SSO auth)').''. ''."\n". - '
'."\n"; + ''."\n"; for (my $i=0; $i<@fields; $i++) { $rem = $i%($numperrow); if ($rem == 0) { @@ -7294,10 +9413,14 @@ sub captcha_choice { $vertext,$currver); my %lt = &captcha_phrases(); $keyentry = 'hidden'; + my $colspan=2; if ($context eq 'cancreate') { $rowname = &mt('CAPTCHA validation'); } elsif ($context eq 'login') { $rowname = &mt('"Contact helpdesk" CAPTCHA validation'); + } elsif ($context eq 'passwords') { + $rowname = &mt('"Forgot Password" CAPTCHA validation'); + $colspan=1; } if (ref($settings) eq 'HASH') { if ($settings->{'captcha'}) { @@ -7337,7 +9460,7 @@ sub captcha_choice { $css_class .= ' style="'.$rowstyle.'"'; } my $output = ''. - '
'.$rowname.''."\n". + ''.$rowname.''."\n". ''; - $rownum ++; - } } else { my %defaults; if (ref($settings) eq 'HASH') { @@ -7679,7 +9723,7 @@ sub print_defaults { $datatable .= ' '.&mt('Internal ID:').' '.$item.' '. ''. &mt('delete').''. - ''; } @@ -7699,7 +9743,7 @@ sub print_defaults { ''. ' '.&mt('(new)'). ''. ''."\n"; $rownum ++; @@ -7747,6 +9791,58 @@ sub defaults_titles { return (\%titles); } +sub print_scantron { + my ($r,$position,$dom,$confname,$settings,$rowtotal) = @_; + if ($position eq 'top') { + return &print_scantronformat($r,$dom,$confname,$settings,\$rowtotal); + } else { + return &print_scantronconfig($dom,$settings,\$rowtotal); + } +} + +sub scantron_javascript { + return <<"ENDSCRIPT"; + + + +ENDSCRIPT + +} + sub print_scantronformat { my ($r,$dom,$confname,$settings,$rowtotal) = @_; my $itemcount = 1; @@ -7773,8 +9869,8 @@ sub print_scantronformat { if ($configuserok eq 'ok') { if ($author_ok eq 'ok') { my %legacyfile = ( - default => $Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab', - custom => $Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab', + default => $Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab', + custom => $Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab', ); my %md5chk; foreach my $type (keys(%legacyfile)) { @@ -7783,7 +9879,7 @@ sub print_scantronformat { } if ($md5chk{'default'} ne $md5chk{'custom'}) { foreach my $type (keys(%legacyfile)) { - ($scantronurls{$type},my $error) = + ($scantronurls{$type},my $error) = &legacy_scantronformat($r,$dom,$confname, $type,$legacyfile{$type}, $scantronurls{$type}, @@ -7794,13 +9890,13 @@ sub print_scantronformat { } if (keys(%error) == 0) { $is_custom = 1; - $confhash{'scantron'}{'scantronformat'} = + $confhash{'scantron'}{'scantronformat'} = $scantronurls{'custom'}; - my $putresult = + my $putresult = &Apache::lonnet::put_dom('configuration', \%confhash,$dom); if ($putresult ne 'ok') { - $error{'custom'} = + $error{'custom'} = ''. &mt('An error occurred updating the domain configuration: [_1]',$putresult).''; } @@ -7920,6 +10016,129 @@ sub legacy_scantronformat { return ($url,$error); } +sub print_scantronconfig { + my ($dom,$settings,$rowtotal) = @_; + my $itemcount = 2; + my $is_checked = ' checked="checked"'; + my %optionson = ( + hdr => ' checked="checked"', + pad => ' checked="checked"', + rem => ' checked="checked"', + ); + my %optionsoff = ( + hdr => '', + pad => '', + rem => '', + ); + my $currcsvsty = 'none'; + my ($datatable,%csvfields,%checked,%onclick,%csvoptions); + my @fields = &scantroncsv_fields(); + my %titles = &scantronconfig_titles(); + if (ref($settings) eq 'HASH') { + if (ref($settings->{config}) eq 'HASH') { + if ($settings->{config}->{dat}) { + $checked{'dat'} = $is_checked; + } + if (ref($settings->{config}->{csv}) eq 'HASH') { + if (ref($settings->{config}->{csv}->{fields}) eq 'HASH') { + %csvfields = %{$settings->{config}->{csv}->{fields}}; + if (keys(%csvfields) > 0) { + $checked{'csv'} = $is_checked; + $currcsvsty = 'block'; + } + } + if (ref($settings->{config}->{csv}->{options}) eq 'HASH') { + %csvoptions = %{$settings->{config}->{csv}->{options}}; + foreach my $option (keys(%optionson)) { + unless ($csvoptions{$option}) { + $optionsoff{$option} = $optionson{$option}; + $optionson{$option} = ''; + } + } + } + } + } else { + $checked{'dat'} = $is_checked; + } + } else { + $checked{'dat'} = $is_checked; + } + $onclick{'csv'} = ' onclick="toggleScantron(this.form);"'; + my $css_class = $itemcount%2? ' class="LC_odd_row"':''; + $datatable = ''. + ''; + $$rowtotal ++; + return $datatable; +} + +sub scantronconfig_titles { + return &Apache::lonlocal::texthash( + dat => 'Standard format (.dat)', + csv => 'Comma separated values (.csv)', + hdr => 'Remove first line in file (contains column titles)', + pad => 'Prepend 0s to PaperID', + rem => 'Remove leading spaces (except Question Response columns)', + CODE => 'CODE', + ID => 'Student ID', + PaperID => 'Paper ID', + FirstName => 'First Name', + LastName => 'Last Name', + FirstQuestion => 'First Question Response', + Section => 'Section', + ); +} + +sub scantroncsv_fields { + return ('PaperID','LastName','FirstName','ID','Section','CODE','FirstQuestion'); +} + sub print_coursecategories { my ($position,$dom,$hdritem,$settings,$rowtotal) = @_; my $datatable; @@ -7962,7 +10181,7 @@ sub print_coursecategories { ''.$lt{$type}.' '; } - $datatable .= ''; + $datatable .= ''; $itemcount ++; } $$rowtotal += $itemcount; @@ -8048,7 +10267,7 @@ sub print_coursecategories { $can_catcomm_dom.' value="dom" />'.$level{'dom'}.' '. ''. - ''. + ''. ''. '' + $datatable .= '' .&initialize_categories($itemcount); } $$rowtotal += $itemcount; @@ -8253,7 +10472,7 @@ sub print_serverstatuses { ''. ''. - ''."\n"; + ''."\n"; } $$rowtotal += $rownum; return $datatable; @@ -8268,35 +10487,7 @@ sub serverstatus_pages { sub defaults_javascript { my ($settings) = @_; - my $intauthcheck = &mt('Warning: disallowing login for an authenticated user if the stored cost is less than the default will require a password reset by/for the user.'); - my $intauthcost = &mt('Warning: bcrypt encryption cost for internal authentication must be an integer.'); - &js_escape(\$intauthcheck); - &js_escape(\$intauthcost); - my $intauthjs = <<"ENDSCRIPT"; - -function warnIntAuth(field) { - if (field.name == 'intauth_check') { - if (field.value == '2') { - alert('$intauthcheck'); - } - } - if (field.name == 'intauth_cost') { - field.value.replace(/\s/g,''); - if (field.value != '') { - var regexdigit=/^\\d+\$/; - if (!regexdigit.test(field.value)) { - alert('$intauthcost'); - } - } - } - return; -} - -ENDSCRIPT - - if (ref($settings) ne 'HASH') { - return &Apache::lonhtmlcommon::scripttag($intauthjs); - } + return unless (ref($settings) eq 'HASH'); if ((ref($settings->{'inststatusorder'}) eq 'ARRAY') && (ref($settings->{'inststatustypes'}) eq 'HASH')) { my $maxnum = scalar(@{$settings->{'inststatusorder'}}); if ($maxnum eq '') { @@ -8350,15 +10541,100 @@ $jstext return; } -$intauthjs - // ]]> ENDSCRIPT + } + return; +} + +sub passwords_javascript { + my %intalert = &Apache::lonlocal::texthash ( + authcheck => 'Warning: disallowing login for an authenticated user if the stored cost is less than the default will require a password reset by/for the user.', + authcost => 'Warning: bcrypt encryption cost for internal authentication must be an integer.', + passmin => 'Warning: minimum password length must be a positive integer greater than 6.', + passmax => 'Warning: maximum password length must be a positive integer (or blank).', + passexp => 'Warning: days before password expiration must be a positive integer (or blank).', + passnum => 'Warning: number of previous passwords to save must be a positive integer (or blank).', + ); + &js_escape(\%intalert); + my $defmin = $Apache::lonnet::passwdmin; + my $intauthjs = <<"ENDSCRIPT"; + +function warnIntAuth(field) { + if (field.name == 'intauth_check') { + if (field.value == '2') { + alert('$intalert{authcheck}'); + } + } + if (field.name == 'intauth_cost') { + field.value.replace(/\s/g,''); + if (field.value != '') { + var regexdigit=/^\\d+\$/; + if (!regexdigit.test(field.value)) { + alert('$intalert{authcost}'); + } + } + } + return; +} + +function warnIntPass(field) { + field.value.replace(/^\s+/,''); + field.value.replace(/\s+\$/,''); + var regexdigit=/^\\d+\$/; + if (field.name == 'passwords_min') { + if (field.value == '') { + alert('$intalert{passmin}'); + field.value = '$defmin'; + } else { + if (!regexdigit.test(field.value)) { + alert('$intalert{passmin}'); + field.value = '$defmin'; + } + var minval = parseInt(field.value,10); + if (minval < $defmin) { + alert('$intalert{passmin}'); + field.value = '$defmin'; + } + } } else { - return &Apache::lonhtmlcommon::scripttag($intauthjs); + if (field.value == '0') { + field.value = ''; + } + if (field.value != '') { + if (field.name == 'passwords_expire') { + var regexpposnum=/^\\d+(|\\.\\d*)\$/; + if (!regexpposnum.test(field.value)) { + alert('$intalert{passexp}'); + field.value = ''; + } else { + var expval = parseFloat(field.value); + if (expval == 0) { + alert('$intalert{passexp}'); + field.value = ''; + } + } + } else { + if (!regexdigit.test(field.value)) { + if (field.name == 'passwords_max') { + alert('$intalert{passmax}'); + } else { + if (field.name == 'passwords_numsaved') { + alert('$intalert{passnum}'); + } + } + field.value = ''; + } + } + } } + return; +} + +ENDSCRIPT + return &Apache::lonhtmlcommon::scripttag($intauthjs); } sub coursecategories_javascript { @@ -8476,25 +10752,28 @@ ENDSCRIPT sub initialize_categories { my ($itemcount) = @_; my ($datatable,$css_class,$chgstr); - my %default_names = ( + my %default_names = &Apache::lonlocal::texthash ( instcode => 'Official courses (with institutional codes)', communities => 'Communities', placement => 'Placement Tests', ); - my $select0 = ' selected="selected"'; - my $select1 = ''; + my %selnum = ( + instcode => '0', + communities => '1', + placement => '2', + ); + my %selected; foreach my $default ('instcode','communities','placement') { $css_class = $itemcount%2?' class="LC_odd_row"':''; - $chgstr = ' onchange="javascript:reorderCats(this.form,'."'',$default"."_pos','0'".');"'; - if (($default eq 'communities') || ($default eq 'placement')) { - $select1 = $select0; - $select0 = ''; - } + $chgstr = ' onchange="javascript:reorderCats(this.form,'."'','$default"."_pos','0'".');"'; + map { $selected{$selnum{$_}} = '' } keys(%selnum); + $selected{$selnum{$default}} = ' selected="selected"'; $datatable .= ''; + .'' + .' ' + .&mt('Add category').''; return $datatable; } @@ -8566,7 +10847,7 @@ sub build_category_rows { pop(@{$path}); } } else { - $text .= &mt('Add subcategory:').' '.&mt('Add subcategory:').''; + $text .= ''; } } } @@ -8599,7 +10880,7 @@ sub build_category_rows { sub modifiable_userdata_row { my ($context,$item,$settings,$numinrow,$rowcount,$usertypes,$fieldsref,$titlesref, - $rowid,$customcss,$rowstyle) = @_; + $rowid,$customcss,$rowstyle,$itemdesc) = @_; my ($role,$rolename,$statustype); $role = $item; if ($context eq 'cancreate') { @@ -8622,6 +10903,8 @@ sub modifiable_userdata_row { } } elsif ($context eq 'lti') { $rolename = &mt('Institutional data used (if available)'); + } elsif ($context eq 'privacy') { + $rolename = $itemdesc; } else { if ($role eq 'cr') { $rolename = &mt('Custom role'); @@ -8671,6 +10954,13 @@ sub modifiable_userdata_row { if (ref($settings) eq 'HASH') { $hashref = $settings->{'instdata'}; } + } elsif ($context eq 'privacy') { + my ($key,$inner) = split(/_/,$role); + if (ref($settings) eq 'HASH') { + if (ref($settings->{$key}) eq 'HASH') { + $hashref = $settings->{$key}->{$inner}; + } + } } elsif (ref($settings->{$context}) eq 'HASH') { if (ref($settings->{$context}->{$role}) eq 'HASH') { $hashref = $settings->{'lti_instdata'}; @@ -8708,7 +10998,25 @@ sub modifiable_userdata_row { my $check = ' '; unless ($role eq 'emailusername') { if (exists($checks{$fields[$i]})) { - $check = $checks{$fields[$i]} + $check = $checks{$fields[$i]}; + } elsif ($context eq 'privacy') { + if ($role =~ /^priv_(domain|course)$/) { + if (ref($settings) ne 'HASH') { + $check = ' checked="checked" '; + } + } elsif ($role =~ /^priv_(author|community)$/) { + if (ref($settings) ne 'HASH') { + unless ($fields[$i] eq 'id') { + $check = ' checked="checked" '; + } + } + } elsif ($role =~ /^(unpriv|othdom)_/) { + if (ref($settings) ne 'HASH') { + if (($fields[$i] eq 'lastname') || ($fields[$i] eq 'firstname')) { + $check = ' checked="checked" '; + } + } + } } elsif ($context ne 'lti') { if ($role eq 'st') { if (ref($settings) ne 'HASH') { @@ -8737,6 +11045,8 @@ sub modifiable_userdata_row { } else { if ($context eq 'lti') { $prefix = 'lti'; + } elsif ($context eq 'privacy') { + $prefix = 'privacy'; } $output .= ''; } if ($colsleft > 1) { @@ -9824,7 +12134,7 @@ sub check_configuser { my ($configuserok,%currroles); if ($uhome eq 'no_host') { srand( time() ^ ($$ + ($$ << 15)) ); # Seed rand. - my $configpass = &LONCAPA::Enrollment::create_password(); + my $configpass = &LONCAPA::Enrollment::create_password($dom); $configuserok = &Apache::lonnet::modifyuser($dom,$confname,'','internal', $configpass,'','','','','',undef,$servadm); @@ -11120,7 +13430,7 @@ sub modify_ltitools { if ($env{'form.ltitools_'.$extra.'_'.$i}) { $confhash{$itemid}{$extra} = 1; if ($env{'form.ltitools_'.$extra.'valid_'.$i} ne '') { - my $lifetime = $env{'form.ltitools_'.$extra.'valid_add'}; + my $lifetime = $env{'form.ltitools_'.$extra.'valid_'.$i}; $lifetime =~ s/^\s+|\s+$//g; if ($lifetime =~ /^\d+\.?\d*$/) { $confhash{$itemid}{$extra.'valid'} = $lifetime; @@ -11287,7 +13597,7 @@ sub modify_ltitools { my %ltienchash = ( $action => { %encconfig } ); - &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom); + &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom,undef,1); if (keys(%changes) > 0) { my $cachetime = 24*60*60; my %ltiall = %confhash; @@ -11347,7 +13657,7 @@ sub modify_ltitools { } } if (!$numconfig) { - $resulttext .= &mt('None'); + $resulttext .= ' '.&mt('None'); } $resulttext .= ''; foreach my $item ('passback','roster') { @@ -11526,6 +13836,536 @@ sub get_ltitools_id { return ($id,$error); } +sub modify_proctoring { + my ($r,$dom,$action,$lastactref,%domconfig) = @_; + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my (@allpos,%changes,%confhash,%encconfhash,$errors,$resulttext,%imgdeletions); + my $confname = $dom.'-domainconfig'; + my $servadm = $r->dir_config('lonAdmEMail'); + my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm); + my %providernames = &proctoring_providernames(); + my $maxnum = scalar(keys(%providernames)); + + my (%requserfields,%optuserfields,%defaults,%extended,%crsconf,@courseroles,@ltiroles); + my ($requref,$opturef,$defref,$extref,$crsref,$rolesref,$ltiref) = &proctoring_data(); + if (ref($requref) eq 'HASH') { + %requserfields = %{$requref}; + } + if (ref($opturef) eq 'HASH') { + %optuserfields = %{$opturef}; + } + if (ref($defref) eq 'HASH') { + %defaults = %{$defref}; + } + if (ref($extref) eq 'HASH') { + %extended = %{$extref}; + } + if (ref($crsref) eq 'HASH') { + %crsconf = %{$crsref}; + } + if (ref($rolesref) eq 'ARRAY') { + @courseroles = @{$rolesref}; + } + if (ref($ltiref) eq 'ARRAY') { + @ltiroles = @{$ltiref}; + } + + if (ref($domconfig{$action}) eq 'HASH') { + my @todeleteimages = &Apache::loncommon::get_env_multiple('form.proctoring_image_del'); + if (@todeleteimages) { + map { $imgdeletions{$_} = 1; } @todeleteimages; + } + } + my %customadds; + my @newcustom = &Apache::loncommon::get_env_multiple('form.proctoring_customadd'); + if (@newcustom) { + map { $customadds{$_} = 1; } @newcustom; + } + foreach my $provider (sort(keys(%providernames))) { + $confhash{$provider} = {}; + my $pos = $env{'form.proctoring_pos_'.$provider}; + $pos =~ s/\D+//g; + $allpos[$pos] = $provider; + my (%current,%currentenc); + my $showroles = 0; + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$provider}) eq 'HASH') { + %current = %{$domconfig{$action}{$provider}}; + foreach my $item ('key','secret') { + $currentenc{$item} = $current{$item}; + delete($current{$item}); + } + } + } + if ($env{'form.proctoring_available_'.$provider}) { + $confhash{$provider}{'available'} = 1; + unless ($current{'available'}) { + $changes{$provider} = 1; + } + } else { + %{$confhash{$provider}} = %current; + %{$encconfhash{$provider}} = %currentenc; + $confhash{$provider}{'available'} = 0; + if ($current{'available'}) { + $changes{$provider} = 1; + } + } + if ($confhash{$provider}{'available'}) { + foreach my $field ('lifetime','version','sigmethod','url','key','secret') { + my $possval = $env{'form.proctoring_'.$provider.'_'.$field}; + if ($field eq 'lifetime') { + if ($possval =~ /^\d+$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'version') { + if ($possval =~ /^\d+\.\d+$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'sigmethod') { + if ($possval =~ /^\QHMAC-SHA\E(1|256)$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'url') { + $confhash{$provider}{$field} = $possval; + } elsif (($field eq 'key') || ($field eq 'secret')) { + $encconfhash{$provider}{$field} = $possval; + unless ($currentenc{$field} eq $possval) { + $changes{$provider} = 1; + } + } + unless (($field eq 'key') || ($field eq 'secret')) { + unless ($current{$field} eq $confhash{$provider}{$field}) { + $changes{$provider} = 1; + } + } + } + if ($imgdeletions{$provider}) { + $changes{$provider} = 1; + } elsif ($env{'form.proctoring_image_'.$provider.'.filename'} ne '') { + my ($imageurl,$error) = + &process_proctoring_image($r,$dom,$confname,'proctoring_image_'.$provider,$provider, + $configuserok,$switchserver,$author_ok); + if ($imageurl) { + $confhash{$provider}{'image'} = $imageurl; + $changes{$provider} = 1; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } elsif (exists($current{'image'})) { + $confhash{$provider}{'image'} = $current{'image'}; + } + if (ref($requserfields{$provider}) eq 'ARRAY') { + if (@{$requserfields{$provider}} > 0) { + if (grep(/^user$/,@{$requserfields{$provider}})) { + if ($env{'form.proctoring_userincdom_'.$provider}) { + $confhash{$provider}{'incdom'} = 1; + } + unless ($current{'incdom'} eq $confhash{$provider}{'incdom'}) { + $changes{$provider} = 1; + } + } + if (grep(/^roles$/,@{$requserfields{$provider}})) { + $showroles = 1; + } + } + } + $confhash{$provider}{'fields'} = []; + if (ref($optuserfields{$provider}) eq 'ARRAY') { + if (@{$optuserfields{$provider}} > 0) { + my @optfields = &Apache::loncommon::get_env_multiple('form.proctoring_optional_'.$provider); + foreach my $field (@{$optuserfields{$provider}}) { + if (grep(/^\Q$field\E$/,@optfields)) { + push(@{$confhash{$provider}{'fields'}},$field); + } + } + } + if (ref($current{'fields'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'fields'}}); + my @old = sort(@{$current{'fields'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (@{$confhash{$provider}{'fields'}}) { + $changes{$provider} = 1; + } + } + if (ref($defaults{$provider}) eq 'ARRAY') { + if (@{$defaults{$provider}} > 0) { + my %options; + if (ref($extended{$provider}) eq 'HASH') { + %options = %{$extended{$provider}}; + } + my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_defaults_'.$provider); + foreach my $field (@{$defaults{$provider}}) { + if ((exists($options{$field})) && (ref($options{$field}) eq 'ARRAY')) { + my $poss = $env{'form.proctoring_defaults_'.$field.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@{$options{$field}})) { + push(@{$confhash{$provider}{'defaults'}},$poss); + } + } elsif ((exists($options{$field})) && (ref($options{$field}) eq 'HASH')) { + foreach my $inner (keys(%{$options{$field}})) { + if (ref($options{$field}{$inner}) eq 'ARRAY') { + my $poss = $env{'form.proctoring_'.$inner.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@{$options{$field}{$inner}})) { + $confhash{$provider}{'defaults'}{$inner} = $poss; + } + } else { + $confhash{$provider}{'defaults'}{$inner} = $env{'form.proctoring_'.$inner.'_'.$provider}; + } + } + } else { + if (grep(/^\Q$field\E$/,@checked)) { + push(@{$confhash{$provider}{'defaults'}},$field); + } + } + } + if (ref($confhash{$provider}{'defaults'}) eq 'ARRAY') { + if (ref($current{'defaults'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'defaults'}}); + my @old = sort(@{$current{'defaults'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (ref($current{'defaults'}) eq 'ARRAY') { + if (@{$current{'defaults'}}) { + $changes{$provider} = 1; + } + } + } elsif (ref($confhash{$provider}{'defaults'}) eq 'HASH') { + if (ref($current{'defaults'}) eq 'HASH') { + unless ($changes{$provider}) { + foreach my $key (keys(%{$confhash{$provider}{'defaults'}})) { + unless ($confhash{$provider}{'defaults'}{$key} eq $current{'defaults'}{$key}) { + $changes{$provider} = 1; + last; + } + } + } + unless ($changes{$provider}) { + foreach my $key (keys(%{$current{'defaults'}})) { + unless ($current{'defaults'}{$key} eq $confhash{$provider}{'defaults'}{$key}) { + $changes{$provider} = 1; + last; + } + } + } + } elsif (keys(%{$confhash{$provider}{'defaults'}})) { + $changes{$provider} = 1; + } + } + } + } + if (ref($crsconf{$provider}) eq 'ARRAY') { + if (@{$crsconf{$provider}} > 0) { + $confhash{$provider}{'crsconf'} = []; + my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_crsconf_'.$provider); + foreach my $crsfield (@{$crsconf{$provider}}) { + if (grep(/^\Q$crsfield\E$/,@checked)) { + push(@{$confhash{$provider}{'crsconf'}},$crsfield); + } + } + if (ref($current{'crsconf'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'crsconf'}}); + my @old = sort(@{$current{'crsconf'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (@{$confhash{$provider}{'crsconf'}}) { + $changes{$provider} = 1; + } + } + } + if ($showroles) { + $confhash{$provider}{'roles'} = {}; + foreach my $role (@courseroles) { + my $poss = $env{'form.proctoring_roles_'.$role.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@ltiroles)) { + $confhash{$provider}{'roles'}{$role} = $poss; + } + } + unless ($changes{$provider}) { + if (ref($current{'roles'}) eq 'HASH') { + foreach my $role (keys(%{$current{'roles'}})) { + unless ($current{'roles'}{$role} eq $confhash{$provider}{'roles'}{$role}) { + $changes{$provider} = 1; + last + } + } + unless ($changes{$provider}) { + foreach my $role (keys(%{$confhash{$provider}{'roles'}})) { + unless ($confhash{$provider}{'roles'}{$role} eq $current{'roles'}{$role}) { + $changes{$provider} = 1; + last; + } + } + } + } elsif (keys(%{$confhash{$provider}{'roles'}})) { + $changes{$provider} = 1; + } + } + } + if (ref($current{'custom'}) eq 'HASH') { + my @customdels = &Apache::loncommon::get_env_multiple('form.proctoring_customdel_'.$provider); + foreach my $key (keys(%{$current{'custom'}})) { + if (grep(/^\Q$key\E$/,@customdels)) { + $changes{$provider} = 1; + } else { + $confhash{$provider}{'custom'}{$key} = $env{'form.proctoring_customval_'.$key.'_'.$provider}; + if ($confhash{$provider}{'custom'}{$key} ne $current{'custom'}{$key}) { + $changes{$provider} = 1; + } + } + } + } + if ($customadds{$provider}) { + my $name = $env{'form.proctoring_custom_name_'.$provider}; + $name =~ s/(`)/'/g; + $name =~ s/^\s+//; + $name =~ s/\s+$//; + my $value = $env{'form.proctoring_custom_value_'.$provider}; + $value =~ s/(`)/'/g; + $value =~ s/^\s+//; + $value =~ s/\s+$//; + if ($name ne '') { + $confhash{$provider}{'custom'}{$name} = $value; + $changes{$provider} = 1; + } + } + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $provider (@allpos) { + if ($provider ne '') { + $confhash{$provider}{'order'} = $idx; + unless ($changes{$provider}) { + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$provider}) eq 'HASH') { + if ($domconfig{$action}{$provider}{'order'} ne $idx) { + $changes{$provider} = 1; + } + } + } + } + $idx ++; + } + } + } + my %proc_hash = ( + $action => { %confhash } + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%proc_hash, + $dom); + if ($putresult eq 'ok') { + my %proc_enchash = ( + $action => { %encconfhash } + ); + &Apache::lonnet::put_dom('encconfig',\%proc_enchash,$dom,undef,1); + if (keys(%changes) > 0) { + my $cachetime = 24*60*60; + my %procall = %confhash; + foreach my $provider (keys(%procall)) { + if (ref($encconfhash{$provider}) eq 'HASH') { + foreach my $key ('key','secret') { + $procall{$provider}{$key} = $encconfhash{$provider}{$key}; + } + } + } + &Apache::lonnet::do_cache_new('proctoring',$dom,\%procall,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'proctoring'} = 1; + } + $resulttext = &mt('Configuration for Provider(s) with changes:').'
      '; + my %bynum; + foreach my $provider (sort(keys(%changes))) { + my $position = $confhash{$provider}{'order'}; + $bynum{$position} = $provider; + } + foreach my $pos (sort { $a <=> $b } keys(%bynum)) { + my $provider = $bynum{$pos}; + my %lt = &proctoring_titles($provider); + my %fieldtitles = &proctoring_fieldtitles($provider); + if (!$confhash{$provider}{'available'}) { + $resulttext .= '
    • '.&mt('Proctoring integration unavailable for: [_1]',''.$providernames{$provider}.'').'
    • '; + } else { + $resulttext .= '
    • '.&mt('Proctoring integration available for: [_1]',''.$providernames{$provider}.''); + if ($confhash{$provider}{'image'}) { + $resulttext .= ' '. + ''.&mt('Proctoring icon').''; + } + $resulttext .= '
        '; + my $position = $pos + 1; + $resulttext .= '
      • '.&mt('Order: [_1]',$position).'
      • '; + foreach my $key ('version','sigmethod','url','lifetime') { + if ($confhash{$provider}{$key} ne '') { + $resulttext .= '
      • '.$lt{$key}.': '.$confhash{$provider}{$key}.'
      • '; + } + } + if ($encconfhash{$provider}{'key'} ne '') { + $resulttext .= '
      • '.$lt{'key'}.': '.$encconfhash{$provider}{'key'}.'
      • '; + } + if ($encconfhash{$provider}{'secret'} ne '') { + $resulttext .= '
      • '.$lt{'secret'}.': '; + my $num = length($encconfhash{$provider}{'secret'}); + $resulttext .= ('*'x$num).'
      • '; + } + my (@fields,$showroles); + if (ref($requserfields{$provider}) eq 'ARRAY') { + push(@fields,@{$requserfields{$provider}}); + } + if (ref($confhash{$provider}{'fields'}) eq 'ARRAY') { + push(@fields,@{$confhash{$provider}{'fields'}}); + } elsif (ref($confhash{$provider}{'fields'}) eq 'HASH') { + push(@fields,(keys(%{$confhash{$provider}{'fields'}}))); + } + if (@fields) { + if (grep(/^roles$/,@fields)) { + $showroles = 1; + } + $resulttext .= '
      • '.$lt{'udsl'}.': "'. + join('", "', map { $lt{$_}; } @fields).'"
      • '; + } + if (ref($requserfields{$provider}) eq 'ARRAY') { + if (grep(/^user$/,@{$requserfields{$provider}})) { + if ($confhash{$provider}{'incdom'}) { + $resulttext .= '
      • '.&mt('[_1] sent as [_2]',$lt{'user'},$lt{'uname:dom'}).'
      • '; + } else { + $resulttext .= '
      • '.&mt('[_1] sent as [_2]',$lt{'user'},$lt{'username'}).'
      • '; + } + } + } + if (ref($confhash{$provider}{'defaults'}) eq 'ARRAY') { + if (@{$confhash{$provider}{'defaults'}} > 0) { + $resulttext .= '
      • '.$lt{'defa'}; + foreach my $field (@{$confhash{$provider}{'defaults'}}) { + $resulttext .= ' "'.$fieldtitles{$field}.'",'; + } + $resulttext =~ s/,$//; + $resulttext .= '
      • '; + } + } elsif (ref($confhash{$provider}{'defaults'}) eq 'HASH') { + if (keys(%{$confhash{$provider}{'defaults'}})) { + $resulttext .= '
      • '.$lt{'defa'}.': 
          '; + foreach my $key (sort(keys(%{$confhash{$provider}{'defaults'}}))) { + if ($confhash{$provider}{'defaults'}{$key} ne '') { + $resulttext .= '
        • '.$fieldtitles{$key}.' = '.$confhash{$provider}{'defaults'}{$key}.'
        • '; + } + } + $resulttext .= '
      • '; + } + } + if (ref($crsconf{$provider}) eq 'ARRAY') { + if (@{$crsconf{$provider}} > 0) { + $resulttext .= '
      • '.&mt('Configurable in course:'); + my $numconfig = 0; + if (ref($confhash{$provider}{'crsconf'}) eq 'ARRAY') { + if (@{$confhash{$provider}{'crsconf'}} > 0) { + foreach my $field (@{$confhash{$provider}{'crsconf'}}) { + $numconfig ++; + if ($provider eq 'examity') { + $resulttext .= ' "'.$lt{'crs'.$field}.'",'; + } else { + $resulttext .= ' "'.$fieldtitles{$field}.'",'; + } + } + $resulttext =~ s/,$//; + } + } + if (!$numconfig) { + $resulttext .= ' '.&mt('None'); + } + $resulttext .= '
      • '; + } + } + if ($showroles) { + if (ref($confhash{$provider}{'roles'}) eq 'HASH') { + my $rolemaps; + foreach my $role (@courseroles) { + if ($confhash{$provider}{'roles'}{$role}) { + $rolemaps .= (' 'x2).&Apache::lonnet::plaintext($role,'Course').'='. + $confhash{$provider}{'roles'}{$role}.','; + } + } + if ($rolemaps) { + $rolemaps =~ s/,$//; + $resulttext .= '
      • '.&mt('Role mapping:').$rolemaps.'
      • '; + } + } + } + if (ref($confhash{$provider}{'custom'}) eq 'HASH') { + my $customlist; + if (keys(%{$confhash{$provider}{'custom'}})) { + foreach my $key (sort(keys(%{$confhash{$provider}{'custom'}}))) { + $customlist .= $key.'='.$confhash{$provider}{'custom'}{$key}.', '; + } + $customlist =~ s/,$//; + } + if ($customlist) { + $resulttext .= '
      • '.&mt('Custom items').': '.$customlist.'
      • '; + } + } + $resulttext .= '
    • '; + } + } + $resulttext .= '
    '; + } else { + $resulttext = &mt('No changes made.'); + } + } else { + $errors .= '
  • '.&mt('Failed to save changes').'
  • '; + } + if ($errors) { + $resulttext .= &mt('The following errors occurred: ').'
      '. + $errors.'
    '; + } + return $resulttext; +} + +sub process_proctoring_image { + my ($r,$dom,$confname,$caller,$provider,$configuserok,$switchserver,$author_ok) = @_; + my $filename = $env{'form.'.$caller.'.filename'}; + my ($error,$url); + my ($width,$height) = (21,21); + if ($configuserok eq 'ok') { + if ($switchserver) { + $error = &mt('Upload of Remote Proctoring Provider icon is not permitted to this server: [_1]', + $switchserver); + } elsif ($author_ok eq 'ok') { + my ($result,$imageurl,$madethumb) = + &publishlogo($r,'upload',$caller,$dom,$confname, + "proctoring/$provider/icon",$width,$height); + if ($result eq 'ok') { + if ($madethumb) { + my ($path,$imagefile) = ($imageurl =~ m{^(.+)/([^/]+)$}); + my $imagethumb = "$path/tn-".$imagefile; + $url = $imagethumb; + } else { + $url = $imageurl; + } + } else { + $error = &mt("Upload of [_1] failed because an error occurred publishing the file in RES space. Error was: [_2].",$filename,$result); + } + } else { + $error = &mt("Upload of [_1] failed because an author role could not be assigned to a Domain Configuration user ([_2]) in domain: [_3]. Error was: [_4].",$filename,$confname,$dom,$author_ok); + } + } else { + $error = &mt("Upload of [_1] failed because a Domain Configuration user ([_2]) could not be created in domain: [_3]. Error was: [_4].",$filename,$confname,$dom,$configuserok); + } + return ($url,$error); +} + sub modify_lti { my ($r,$dom,$action,$lastactref,%domconfig) = @_; my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); @@ -11591,7 +14431,7 @@ sub modify_lti { if ($position ne '') { $allpos[$position] = $itemid; } - foreach my $item ('consumer','key','secret','lifetime') { + foreach my $item ('consumer','key','secret','lifetime','requser') { my $formitem = 'form.lti_'.$item.'_'.$idx; $env{$formitem} =~ s/(`)/'/g; if ($item eq 'lifetime') { @@ -11613,164 +14453,186 @@ sub modify_lti { if ($env{'form.lti_version_'.$idx} eq 'LTI-1p0') { $confhash{$itemid}{'version'} = $env{'form.lti_version_'.$idx}; } - if ($env{'form.lti_mapuser_'.$idx} eq 'sourcedid') { - $confhash{$itemid}{'mapuser'} = 'lis_person_sourcedid'; - } elsif ($env{'form.lti_mapuser_'.$idx} eq 'email') { - $confhash{$itemid}{'mapuser'} = 'lis_person_contact_email_primary'; - } elsif ($env{'form.lti_mapuser_'.$idx} eq 'other') { - my $mapuser = $env{'form.lti_customuser_'.$idx}; - $mapuser =~ s/(`)/'/g; - $mapuser =~ s/^\s+|\s+$//g; - $confhash{$itemid}{'mapuser'} = $mapuser; - } - foreach my $ltirole (@lticourseroles) { - my $possrole = $env{'form.lti_maprole_'.$ltirole.'_'.$idx}; - if (grep(/^\Q$possrole\E$/,@courseroles)) { - $confhash{$itemid}{'maproles'}{$ltirole} = $possrole; - } - } - my @possmakeuser = &Apache::loncommon::get_env_multiple('form.lti_makeuser_'.$idx); - my @makeuser; - foreach my $ltirole (sort(@possmakeuser)) { - if ($posslti{$ltirole}) { - push(@makeuser,$ltirole); - } - } - $confhash{$itemid}{'makeuser'} = \@makeuser; - if (@makeuser) { - my $lcauth = $env{'form.lti_lcauth_'.$idx}; - if ($lcauth =~ /^(internal|krb4|krb5|localauth)$/) { - $confhash{$itemid}{'lcauth'} = $lcauth; - if ($lcauth ne 'internal') { - my $lcauthparm = $env{'form.lti_lcauthparm_'.$idx}; - $lcauthparm =~ s/^(\s+|\s+)$//g; - $lcauthparm =~ s/`//g; - if ($lcauthparm ne '') { - $confhash{$itemid}{'lcauthparm'} = $lcauthparm; + if ($confhash{$itemid}{'requser'}) { + if ($env{'form.lti_mapuser_'.$idx} eq 'sourcedid') { + $confhash{$itemid}{'mapuser'} = 'lis_person_sourcedid'; + } elsif ($env{'form.lti_mapuser_'.$idx} eq 'email') { + $confhash{$itemid}{'mapuser'} = 'lis_person_contact_email_primary'; + } elsif ($env{'form.lti_mapuser_'.$idx} eq 'other') { + my $mapuser = $env{'form.lti_customuser_'.$idx}; + $mapuser =~ s/(`)/'/g; + $mapuser =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'mapuser'} = $mapuser; + } + foreach my $ltirole (@lticourseroles) { + my $possrole = $env{'form.lti_maprole_'.$ltirole.'_'.$idx}; + if (grep(/^\Q$possrole\E$/,@courseroles)) { + $confhash{$itemid}{'maproles'}{$ltirole} = $possrole; + } + } + my @possmakeuser = &Apache::loncommon::get_env_multiple('form.lti_makeuser_'.$idx); + my @makeuser; + foreach my $ltirole (sort(@possmakeuser)) { + if ($posslti{$ltirole}) { + push(@makeuser,$ltirole); + } + } + $confhash{$itemid}{'makeuser'} = \@makeuser; + if (@makeuser) { + my $lcauth = $env{'form.lti_lcauth_'.$idx}; + if ($lcauth =~ /^(internal|krb4|krb5|localauth)$/) { + $confhash{$itemid}{'lcauth'} = $lcauth; + if ($lcauth ne 'internal') { + my $lcauthparm = $env{'form.lti_lcauthparm_'.$idx}; + $lcauthparm =~ s/^(\s+|\s+)$//g; + $lcauthparm =~ s/`//g; + if ($lcauthparm ne '') { + $confhash{$itemid}{'lcauthparm'} = $lcauthparm; + } } - } - } else { - $confhash{$itemid}{'lcauth'} = 'lti'; - } - } - my @possinstdata = &Apache::loncommon::get_env_multiple('form.lti_instdata_'.$idx); - if (@possinstdata) { - foreach my $field (@possinstdata) { - if (exists($fieldtitles{$field})) { - push(@{$confhash{$itemid}{'instdata'}}); + } else { + $confhash{$itemid}{'lcauth'} = 'lti'; } } - } - if (($env{'form.lti_mapcrs_'.$idx} eq 'course_offering_sourcedid') || - ($env{'form.lti_mapcrs_'.$idx} eq 'context_id')) { - $confhash{$itemid}{'mapcrs'} = $env{'form.lti_mapcrs_'.$idx}; - } elsif ($env{'form.lti_mapcrs_'.$idx} eq 'other') { - my $mapcrs = $env{'form.lti_mapcrsfield_'.$idx}; - $mapcrs =~ s/(`)/'/g; - $mapcrs =~ s/^\s+|\s+$//g; - $confhash{$itemid}{'mapcrs'} = $mapcrs; - } - my @posstypes = &Apache::loncommon::get_env_multiple('form.lti_mapcrstype_'.$idx); - my @crstypes; - foreach my $type (sort(@posstypes)) { - if ($posscrstype{$type}) { - push(@crstypes,$type); + my @possinstdata = &Apache::loncommon::get_env_multiple('form.lti_instdata_'.$idx); + if (@possinstdata) { + foreach my $field (@possinstdata) { + if (exists($fieldtitles{$field})) { + push(@{$confhash{$itemid}{'instdata'}}); + } + } } - } - $confhash{$itemid}{'mapcrstype'} = \@crstypes; - if ($env{'form.lti_makecrs_'.$idx}) { - $confhash{$itemid}{'makecrs'} = 1; - } - my @possenroll = &Apache::loncommon::get_env_multiple('form.lti_selfenroll_'.$idx); - my @selfenroll; - foreach my $type (sort(@possenroll)) { - if ($posslticrs{$type}) { - push(@selfenroll,$type); + if (($env{'form.lti_mapcrs_'.$idx} eq 'course_offering_sourcedid') || + ($env{'form.lti_mapcrs_'.$idx} eq 'context_id')) { + $confhash{$itemid}{'mapcrs'} = $env{'form.lti_mapcrs_'.$idx}; + } elsif ($env{'form.lti_mapcrs_'.$idx} eq 'other') { + my $mapcrs = $env{'form.lti_mapcrsfield_'.$idx}; + $mapcrs =~ s/(`)/'/g; + $mapcrs =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'mapcrs'} = $mapcrs; } - } - $confhash{$itemid}{'selfenroll'} = \@selfenroll; - if ($env{'form.lti_crssec_'.$idx}) { - if ($env{'form.lti_crssecsrc_'.$idx} eq 'course_section_sourcedid') { - $confhash{$itemid}{'section'} = $env{'form.lti_crssecsrc_'.$idx}; - } elsif ($env{'form.lti_crssecsrc_'.$idx} eq 'other') { - my $section = $env{'form.lti_customsection_'.$idx}; - $section =~ s/(`)/'/g; - $section =~ s/^\s+|\s+$//g; - if ($section ne '') { - $confhash{$itemid}{'section'} = $section; + my @posstypes = &Apache::loncommon::get_env_multiple('form.lti_mapcrstype_'.$idx); + my @crstypes; + foreach my $type (sort(@posstypes)) { + if ($posscrstype{$type}) { + push(@crstypes,$type); } } - } - foreach my $field ('passback','roster','topmenu','inlinemenu') { - if ($env{'form.lti_'.$field.'_'.$idx}) { - $confhash{$itemid}{$field} = 1; + $confhash{$itemid}{'mapcrstype'} = \@crstypes; + if ($env{'form.lti_makecrs_'.$idx}) { + $confhash{$itemid}{'makecrs'} = 1; } - } - - if ($env{'form.lti_topmenu_'.$idx} || $env{'form.lti_inlinemenu_'.$idx}) { - $confhash{$itemid}{lcmenu} = []; - my @possmenu = &Apache::loncommon::get_env_multiple('form.lti_menuitem_'.$idx); - foreach my $field (@possmenu) { - if (exists($menutitles{$field})) { - if ($field eq 'grades') { - next unless ($env{'form.lti_inlinemenu_'.$idx}); + my @possenroll = &Apache::loncommon::get_env_multiple('form.lti_selfenroll_'.$idx); + my @selfenroll; + foreach my $type (sort(@possenroll)) { + if ($posslticrs{$type}) { + push(@selfenroll,$type); + } + } + $confhash{$itemid}{'selfenroll'} = \@selfenroll; + if ($env{'form.lti_crssec_'.$idx}) { + if ($env{'form.lti_crssecsrc_'.$idx} eq 'course_section_sourcedid') { + $confhash{$itemid}{'section'} = $env{'form.lti_crssecsrc_'.$idx}; + } elsif ($env{'form.lti_crssecsrc_'.$idx} eq 'other') { + my $section = $env{'form.lti_customsection_'.$idx}; + $section =~ s/(`)/'/g; + $section =~ s/^\s+|\s+$//g; + if ($section ne '') { + $confhash{$itemid}{'section'} = $section; } - push(@{$confhash{$itemid}{lcmenu}},$field); } } - } - unless (($idx eq 'add') || ($changes{$itemid})) { - foreach my $field ('mapuser','mapcrs','makecrs','section','passback','roster','lcauth','lcauthparm','topmenu','inlinemenu') { - if ($domconfig{$action}{$itemid}{$field} ne $confhash{$itemid}{$field}) { - $changes{$itemid} = 1; + if ($env{'form.lti_callback_'.$idx}) { + if ($env{'form.lti_callbackparam_'.$idx}) { + my $callback = $env{'form.lti_callbackparam_'.$idx}; + $callback =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'callback'} = $callback; } } - foreach my $field ('makeuser','mapcrstype','selfenroll','instdata','lcmenu') { - unless ($changes{$itemid}) { - if (ref($domconfig{$action}{$itemid}{$field}) eq 'ARRAY') { - if (ref($confhash{$itemid}{$field}) eq 'ARRAY') { - my @diffs = &Apache::loncommon::compare_arrays($domconfig{$action}{$itemid}{$field}, - $confhash{$itemid}{$field}); - if (@diffs) { - $changes{$itemid} = 1; - } - } elsif (@{$domconfig{$action}{$itemid}{$field}} > 0) { - $changes{$itemid} = 1; + foreach my $field ('passback','roster','topmenu','inlinemenu') { + if ($env{'form.lti_'.$field.'_'.$idx}) { + $confhash{$itemid}{$field} = 1; + } + } + if ($env{'form.lti_passback_'.$idx}) { + if ($env{'form.lti_passbackformat_'.$idx} eq '1.0') { + $confhash{$itemid}{'passbackformat'} = '1.0'; + } else { + $confhash{$itemid}{'passbackformat'} = '1.1'; + } + } + if ($env{'form.lti_topmenu_'.$idx} || $env{'form.lti_inlinemenu_'.$idx}) { + $confhash{$itemid}{lcmenu} = []; + my @possmenu = &Apache::loncommon::get_env_multiple('form.lti_menuitem_'.$idx); + foreach my $field (@possmenu) { + if (exists($menutitles{$field})) { + if ($field eq 'grades') { + next unless ($env{'form.lti_inlinemenu_'.$idx}); } - } elsif (ref($confhash{$itemid}{$field}) eq 'ARRAY') { - if (@{$confhash{$itemid}{$field}} > 0) { + push(@{$confhash{$itemid}{lcmenu}},$field); + } + } + } + unless (($idx eq 'add') || ($changes{$itemid})) { + foreach my $field ('mapuser','mapcrs','makecrs','section','passback','roster','lcauth','lcauthparm','topmenu','inlinemenu','callback') { + if ($domconfig{$action}{$itemid}{$field} ne $confhash{$itemid}{$field}) { + $changes{$itemid} = 1; + } + } + unless ($changes{$itemid}) { + if ($domconfig{$action}{$itemid}{'passback'} eq $confhash{$itemid}{'passback'}) { + if ($domconfig{$action}{$itemid}{'passbackformat'} ne $confhash{$itemid}{'passbackformat'}) { $changes{$itemid} = 1; } - } + } } - } - unless ($changes{$itemid}) { - if (ref($domconfig{$action}{$itemid}{'maproles'}) eq 'HASH') { - if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { - foreach my $ltirole (keys(%{$domconfig{$action}{$itemid}{'maproles'}})) { - if ($domconfig{$action}{$itemid}{'maproles'}{$ltirole} ne - $confhash{$itemid}{'maproles'}{$ltirole}) { + foreach my $field ('makeuser','mapcrstype','selfenroll','instdata','lcmenu') { + unless ($changes{$itemid}) { + if (ref($domconfig{$action}{$itemid}{$field}) eq 'ARRAY') { + if (ref($confhash{$itemid}{$field}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($domconfig{$action}{$itemid}{$field}, + $confhash{$itemid}{$field}); + if (@diffs) { + $changes{$itemid} = 1; + } + } elsif (@{$domconfig{$action}{$itemid}{$field}} > 0) { + $changes{$itemid} = 1; + } + } elsif (ref($confhash{$itemid}{$field}) eq 'ARRAY') { + if (@{$confhash{$itemid}{$field}} > 0) { $changes{$itemid} = 1; - last; } } - unless ($changes{$itemid}) { - foreach my $ltirole (keys(%{$confhash{$itemid}{'maproles'}})) { - if ($confhash{$itemid}{'maproles'}{$ltirole} ne - $domconfig{$action}{$itemid}{'maproles'}{$ltirole}) { + } + } + unless ($changes{$itemid}) { + if (ref($domconfig{$action}{$itemid}{'maproles'}) eq 'HASH') { + if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { + foreach my $ltirole (keys(%{$domconfig{$action}{$itemid}{'maproles'}})) { + if ($domconfig{$action}{$itemid}{'maproles'}{$ltirole} ne + $confhash{$itemid}{'maproles'}{$ltirole}) { $changes{$itemid} = 1; last; } } - } - } elsif (keys(%{$domconfig{$action}{$itemid}{'maproles'}}) > 0) { - $changes{$itemid} = 1; - } - } elsif (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { - unless ($changes{$itemid}) { - if (keys(%{$confhash{$itemid}{'maproles'}}) > 0) { + unless ($changes{$itemid}) { + foreach my $ltirole (keys(%{$confhash{$itemid}{'maproles'}})) { + if ($confhash{$itemid}{'maproles'}{$ltirole} ne + $domconfig{$action}{$itemid}{'maproles'}{$ltirole}) { + $changes{$itemid} = 1; + last; + } + } + } + } elsif (keys(%{$domconfig{$action}{$itemid}{'maproles'}}) > 0) { $changes{$itemid} = 1; } + } elsif (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { + unless ($changes{$itemid}) { + if (keys(%{$confhash{$itemid}{'maproles'}}) > 0) { + $changes{$itemid} = 1; + } + } } } } @@ -11801,7 +14663,7 @@ sub modify_lti { my %ltienchash = ( $action => { %encconfig } ); - &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom); + &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom,undef,1); if (keys(%changes) > 0) { my $cachetime = 24*60*60; my %ltiall = %confhash; @@ -11843,115 +14705,129 @@ sub modify_lti { my $num = length($encconfig{$itemid}{'secret'}); $resulttext .= ('*'x$num).''; } - if ($confhash{$itemid}{'mapuser'}) { - my $shownmapuser; - if ($confhash{$itemid}{'mapuser'} eq 'lis_person_sourcedid') { - $shownmapuser = $lt{'sourcedid'}.' (lis_person_sourcedid)'; - } elsif ($confhash{$itemid}{'mapuser'} eq 'lis_person_contact_email_primary') { - $shownmapuser = $lt{'email'}.' (lis_person_contact_email_primary)'; - } else { - $shownmapuser = &mt('Other').' ('.$confhash{$itemid}{'mapuser'}.')'; - } - $resulttext .= '
  • '.&mt('LON-CAPA username').': '.$shownmapuser.'
  • '; - } - if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { - my $rolemaps; - foreach my $role (@ltiroles) { - if ($confhash{$itemid}{'maproles'}{$role}) { - $rolemaps .= (' 'x2).$role.'='. - &Apache::lonnet::plaintext($confhash{$itemid}{'maproles'}{$role}, - 'Course').','; + if ($confhash{$itemid}{'requser'}) { + if ($confhash{$itemid}{'mapuser'}) { + my $shownmapuser; + if ($confhash{$itemid}{'mapuser'} eq 'lis_person_sourcedid') { + $shownmapuser = $lt{'sourcedid'}.' (lis_person_sourcedid)'; + } elsif ($confhash{$itemid}{'mapuser'} eq 'lis_person_contact_email_primary') { + $shownmapuser = $lt{'email'}.' (lis_person_contact_email_primary)'; + } else { + $shownmapuser = &mt('Other').' ('.$confhash{$itemid}{'mapuser'}.')'; } + $resulttext .= '
  • '.&mt('LON-CAPA username').': '.$shownmapuser.'
  • '; } - if ($rolemaps) { - $rolemaps =~ s/,$//; - $resulttext .= '
  • '.&mt('Role mapping:').$rolemaps.'
  • '; - } - } - if (ref($confhash{$itemid}{'makeuser'}) eq 'ARRAY') { - if (@{$confhash{$itemid}{'makeuser'}} > 0) { - $resulttext .= '
  • '.&mt('Following roles may create user accounts: [_1]', - join(', ',@{$confhash{$itemid}{'makeuser'}})).'
    '; - if ($confhash{$itemid}{'lcauth'} eq 'lti') { - $resulttext .= &mt('New users will only be able to authenticate via LTI').'
  • '; - } else { - $resulttext .= &mt('New users will be assigned LON-CAPA authentication: [_1]', - $confhash{$itemid}{'lcauth'}); - if ($confhash{$itemid}{'lcauth'} eq 'internal') { - $resulttext .= '; '.&mt('a randomly generated password will be created'); - } elsif ($confhash{$itemid}{'lcauth'} eq 'localauth') { - if ($confhash{$itemid}{'lcauthparm'} ne '') { - $resulttext .= ' '.&mt('with argument: [_1]',$confhash{$itemid}{'lcauthparm'}); - } + if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { + my $rolemaps; + foreach my $role (@ltiroles) { + if ($confhash{$itemid}{'maproles'}{$role}) { + $rolemaps .= (' 'x2).$role.'='. + &Apache::lonnet::plaintext($confhash{$itemid}{'maproles'}{$role}, + 'Course').','; + } + } + if ($rolemaps) { + $rolemaps =~ s/,$//; + $resulttext .= '
  • '.&mt('Role mapping:').$rolemaps.'
  • '; + } + } + if (ref($confhash{$itemid}{'makeuser'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'makeuser'}} > 0) { + $resulttext .= '
  • '.&mt('Following roles may create user accounts: [_1]', + join(', ',@{$confhash{$itemid}{'makeuser'}})).'
    '; + if ($confhash{$itemid}{'lcauth'} eq 'lti') { + $resulttext .= &mt('New users will only be able to authenticate via LTI').'
  • '; } else { - $resulttext .= '; '.&mt('Kerberos domain: [_1]',$confhash{$itemid}{'lcauthparm'}); + $resulttext .= &mt('New users will be assigned LON-CAPA authentication: [_1]', + $confhash{$itemid}{'lcauth'}); + if ($confhash{$itemid}{'lcauth'} eq 'internal') { + $resulttext .= '; '.&mt('a randomly generated password will be created'); + } elsif ($confhash{$itemid}{'lcauth'} eq 'localauth') { + if ($confhash{$itemid}{'lcauthparm'} ne '') { + $resulttext .= ' '.&mt('with argument: [_1]',$confhash{$itemid}{'lcauthparm'}); + } + } else { + $resulttext .= '; '.&mt('Kerberos domain: [_1]',$confhash{$itemid}{'lcauthparm'}); + } } + $resulttext .= ''; + } else { + $resulttext .= '
  • '.&mt('User account creation not permitted.').'
  • '; } - $resulttext .= ''; - } else { - $resulttext .= '
  • '.&mt('User account creation not permitted.').'
  • '; } - } - if (ref($confhash{$itemid}{'instdata'}) eq 'ARRAY') { - if (@{$confhash{$itemid}{'instdata'}} > 0) { - $resulttext .= '
  • '.&mt('Institutional data will be used when creating a new user for: [_1]', - join(', ',map { $fieldtitles{$_}; } @{$confhash{$itemid}{'instdata'}})).'
  • '; - } else { - $resulttext .= '
  • '.&mt('No institutional data used when creating a new user.').'
  • '; + if (ref($confhash{$itemid}{'instdata'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'instdata'}} > 0) { + $resulttext .= '
  • '.&mt('Institutional data will be used when creating a new user for: [_1]', + join(', ',map { $fieldtitles{$_}; } @{$confhash{$itemid}{'instdata'}})).'
  • '; + } else { + $resulttext .= '
  • '.&mt('No institutional data used when creating a new user.').'
  • '; + } } - } - if ($confhash{$itemid}{'mapcrs'}) { - $resulttext .= '
  • '.&mt('Unique course identifier').': '.$confhash{$itemid}{'mapcrs'}.'
  • '; - } - if (ref($confhash{$itemid}{'mapcrstype'}) eq 'ARRAY') { - if (@{$confhash{$itemid}{'mapcrstype'}} > 0) { - $resulttext .= '
  • '.&mt('Mapping for the following LON-CAPA course types: [_1]', - join(', ',map { $coursetypetitles{$_}; } @coursetypes)). - '
  • '; - } else { - $resulttext .= '
  • '.&mt('No mapping to LON-CAPA courses').'
  • '; + if ($confhash{$itemid}{'mapcrs'}) { + $resulttext .= '
  • '.&mt('Unique course identifier').': '.$confhash{$itemid}{'mapcrs'}.'
  • '; } - } - if ($confhash{$itemid}{'makecrs'}) { - $resulttext .= '
  • '.&mt('Instructor may create course (if absent).').'
  • '; - } else { - $resulttext .= '
  • '.&mt('Instructor may not create course (if absent).').'
  • '; - } - if (ref($confhash{$itemid}{'selfenroll'}) eq 'ARRAY') { - if (@{$confhash{$itemid}{'selfenroll'}} > 0) { - $resulttext .= '
  • '.&mt('Self-enrollment for following roles: [_1]', - join(', ',@{$confhash{$itemid}{'selfenroll'}})). - '
  • '; - } else { - $resulttext .= '
  • '.&mt('Self-enrollment not permitted').'
  • '; + if (ref($confhash{$itemid}{'mapcrstype'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'mapcrstype'}} > 0) { + $resulttext .= '
  • '.&mt('Mapping for the following LON-CAPA course types: [_1]', + join(', ',map { $coursetypetitles{$_}; } @coursetypes)). + '
  • '; + } else { + $resulttext .= '
  • '.&mt('No mapping to LON-CAPA courses').'
  • '; + } } - } - if ($confhash{$itemid}{'section'}) { - if ($confhash{$itemid}{'section'} eq 'course_section_sourcedid') { - $resulttext .= '
  • '.&mt('User section from standard field:'). - ' (course_section_sourcedid)'.'
  • '; + if ($confhash{$itemid}{'makecrs'}) { + $resulttext .= '
  • '.&mt('Instructor may create course (if absent).').'
  • '; } else { - $resulttext .= '
  • '.&mt('User section from:').' '. - $confhash{$itemid}{'section'}.'
  • '; + $resulttext .= '
  • '.&mt('Instructor may not create course (if absent).').'
  • '; } - } else { - $resulttext .= '
  • '.&mt('No section assignment').'
  • '; - } - foreach my $item ('passback','roster','topmenu','inlinemenu') { - $resulttext .= '
  • '.$lt{$item}.': '; - if ($confhash{$itemid}{$item}) { - $resulttext .= &mt('Yes'); + if (ref($confhash{$itemid}{'selfenroll'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'selfenroll'}} > 0) { + $resulttext .= '
  • '.&mt('Self-enrollment for following roles: [_1]', + join(', ',@{$confhash{$itemid}{'selfenroll'}})). + '
  • '; + } else { + $resulttext .= '
  • '.&mt('Self-enrollment not permitted').'
  • '; + } + } + if ($confhash{$itemid}{'section'}) { + if ($confhash{$itemid}{'section'} eq 'course_section_sourcedid') { + $resulttext .= '
  • '.&mt('User section from standard field:'). + ' (course_section_sourcedid)'.'
  • '; + } else { + $resulttext .= '
  • '.&mt('User section from:').' '. + $confhash{$itemid}{'section'}.'
  • '; + } } else { - $resulttext .= &mt('No'); + $resulttext .= '
  • '.&mt('No section assignment').'
  • '; } - $resulttext .= ''; - } - if (ref($confhash{$itemid}{'lcmenu'}) eq 'ARRAY') { - if (@{$confhash{$itemid}{'lcmenu'}} > 0) { - $resulttext .= '
  • '.&mt('Menu items:').' '. - join(', ', map { $menutitles{$_}; } (@{$confhash{$itemid}{'lcmenu'}})).'
  • '; + if ($confhash{$itemid}{'callback'}) { + $resulttext .= '
  • '.&mt('Callback setting').': '.$confhash{$itemid}{'callback'}.'
  • '; } else { - $resulttext .= '
  • '.&mt('No menu items displayed in header or online menu').'
  • '; + $resulttext .= '
  • '.&mt('No callback to logout LON-CAPA session when user logs out of Comsumer'); + } + foreach my $item ('passback','roster','topmenu','inlinemenu') { + $resulttext .= '
  • '.$lt{$item}.': '; + if ($confhash{$itemid}{$item}) { + $resulttext .= &mt('Yes'); + if ($item eq 'passback') { + if ($confhash{$itemid}{'passbackformat'} eq '1.0') { + $resulttext .= ' ('.&mt('Outcomes Extension (1.0)').')'; + } elsif ($confhash{$itemid}{'passbackformat'} eq '1.1') { + $resulttext .= ' ('.&mt('Outcomes Service (1.1)').')'; + } + } + } else { + $resulttext .= &mt('No'); + } + $resulttext .= '
  • '; + } + if (ref($confhash{$itemid}{'lcmenu'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'lcmenu'}} > 0) { + $resulttext .= '
  • '.&mt('Menu items:').' '. + join(', ', map { $menutitles{$_}; } (@{$confhash{$itemid}{'lcmenu'}})).'
  • '; + } else { + $resulttext .= '
  • '.&mt('No menu items displayed in header or online menu').'
  • '; + } } } $resulttext .= ''; @@ -12129,8 +15005,10 @@ sub modify_autoupdate { } my @offon = ('off','on'); my %title = &Apache::lonlocal::texthash ( - run => 'Auto-update:', - classlists => 'Updates to user information in classlists?' + run => 'Auto-update:', + classlists => 'Updates to user information in classlists?', + unexpired => 'Skip updates for users without active or future roles?', + lastactive => 'Skip updates for inactive users?', ); my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); my %fieldtitles = &Apache::lonlocal::texthash ( @@ -12174,12 +15052,23 @@ sub modify_autoupdate { my %updatehash = ( autoupdate => { run => $env{'form.autoupdate_run'}, classlists => $env{'form.classlists'}, + unexpired => $env{'form.unexpired'}, fields => {%fields}, lockablenames => \@lockablenames, } ); + my $lastactivedays; + if ($env{'form.lastactive'}) { + $lastactivedays = $env{'form.lastactivedays'}; + $lastactivedays =~ s/^\s+|\s+$//g; + unless ($lastactivedays =~ /^\d+$/) { + undef($lastactivedays); + $env{'form.lastactive'} = 0; + } + } + $updatehash{'autoupdate'}{'lastactive'} = $lastactivedays; foreach my $key (keys(%currautoupdate)) { - if (($key eq 'run') || ($key eq 'classlists')) { + if (($key eq 'run') || ($key eq 'classlists') || ($key eq 'unexpired') || ($key eq 'lastactive')) { if (exists($updatehash{autoupdate}{$key})) { if ($currautoupdate{$key} ne $updatehash{autoupdate}{$key}) { $changes{$key} = 1; @@ -12225,6 +15114,16 @@ sub modify_autoupdate { $changes{'lockablenames'} = 1; } } + unless (grep(/^unexpired$/,keys(%currautoupdate))) { + if ($updatehash{'autoupdate'}{'unexpired'}) { + $changes{'unexpired'} = 1; + } + } + unless (grep(/^lastactive$/,keys(%currautoupdate))) { + if ($updatehash{'autoupdate'}{'lastactive'} ne '') { + $changes{'lastactive'} = 1; + } + } foreach my $item (@{$types},'default') { if (defined($fields{$item})) { if (ref($currautoupdate{'fields'}) eq 'HASH') { @@ -12287,6 +15186,11 @@ sub modify_autoupdate { my $newvalue; if ($key eq 'run') { $newvalue = $offon[$env{'form.autoupdate_run'}]; + } elsif ($key eq 'lastactive') { + $newvalue = $offon[$env{'form.lastactive'}]; + unless ($lastactivedays eq '') { + $newvalue .= '; '.&mt('inactive = no activity in last [quant,_1,day]',$lastactivedays); + } } else { $newvalue = $offon[$env{'form.'.$key}]; } @@ -12599,8 +15503,9 @@ sub modify_contacts { my (%others,%to,%bcc,%includestr,%includeloc); my @contacts = ('supportemail','adminemail'); my @mailings = ('errormail','packagesmail','helpdeskmail','otherdomsmail', - 'lonstatusmail','requestsmail','updatesmail','idconflictsmail'); - my @toggles = ('reporterrors','reportupdates'); + 'lonstatusmail','requestsmail','updatesmail','idconflictsmail','hostipmail'); + my @toggles = ('reporterrors','reportupdates','reportstatus'); + my @lonstatus = ('threshold','sysmail','weights','excluded'); my ($fields,$fieldtitles,$fieldoptions,$possoptions) = &helpform_fields(); foreach my $type (@mailings) { @{$newsetting{$type}} = @@ -12633,6 +15538,41 @@ sub modify_contacts { $contacts_hash{'contacts'}{$item} = $env{'form.'.$item}; } } + my ($lonstatus_defs,$lonstatus_names) = &Apache::loncommon::lon_status_items(); + foreach my $item (@lonstatus) { + if ($item eq 'excluded') { + my (%serverhomes,@excluded); + map { $serverhomes{$_} = 1; } values(%Apache::lonnet::serverhomeIDs); + my @possexcluded = &Apache::loncommon::get_env_multiple('form.errorexcluded'); + if (@possexcluded) { + foreach my $id (sort(@possexcluded)) { + if ($serverhomes{$id}) { + push(@excluded,$id); + } + } + } + if (@excluded) { + $contacts_hash{'contacts'}{'lonstatus'}{$item} = \@excluded; + } + } elsif ($item eq 'weights') { + foreach my $type ('E','W','N','U') { + $env{'form.error'.$item.'_'.$type} =~ s/^\s+|\s+$//g; + if ($env{'form.error'.$item.'_'.$type} =~ /^\d+$/) { + unless ($env{'form.error'.$item.'_'.$type} == $lonstatus_defs->{$type}) { + $contacts_hash{'contacts'}{'lonstatus'}{$item}{$type} = + $env{'form.error'.$item.'_'.$type}; + } + } + } + } elsif (($item eq 'threshold') || ($item eq 'sysmail')) { + $env{'form.error'.$item} =~ s/^\s+|\s+$//g; + if ($env{'form.error'.$item} =~ /^\d+$/) { + unless ($env{'form.error'.$item} == $lonstatus_defs->{$item}) { + $contacts_hash{'contacts'}{'lonstatus'}{$item} = $env{'form.error'.$item}; + } + } + } + } if ((ref($fields) eq 'ARRAY') && (ref($possoptions) eq 'HASH')) { foreach my $field (@{$fields}) { if (ref($possoptions->{$field}) eq 'ARRAY') { @@ -12771,6 +15711,76 @@ sub modify_contacts { } } } + if (ref($currsetting{'lonstatus'}) eq 'HASH') { + foreach my $key ('excluded','weights','threshold','sysmail') { + if ($key eq 'excluded') { + if ((ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') && + (ref($contacts_hash{contacts}{lonstatus}{excluded}) eq 'ARRAY')) { + if ((ref($currsetting{'lonstatus'}{$key}) eq 'ARRAY') && + (@{$currsetting{'lonstatus'}{$key}})) { + my @diffs = + &Apache::loncommon::compare_arrays($contacts_hash{contacts}{lonstatus}{excluded}, + $currsetting{'lonstatus'}{$key}); + if (@diffs) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif (@{$contacts_hash{contacts}{lonstatus}{excluded}}) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif ((ref($currsetting{'lonstatus'}{$key}) eq 'ARRAY') && + (@{$currsetting{'lonstatus'}{$key}})) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif ($key eq 'weights') { + if ((ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') && + (ref($contacts_hash{contacts}{lonstatus}{$key}) eq 'HASH')) { + if (ref($currsetting{'lonstatus'}{$key}) eq 'HASH') { + foreach my $type ('E','W','N','U') { + unless ($contacts_hash{contacts}{lonstatus}{$key}{$type} eq + $currsetting{'lonstatus'}{$key}{$type}) { + push(@{$changes{'lonstatus'}},$key); + last; + } + } + } else { + foreach my $type ('E','W','N','U') { + if ($contacts_hash{contacts}{lonstatus}{$key}{$type} ne '') { + push(@{$changes{'lonstatus'}},$key); + last; + } + } + } + } elsif (ref($currsetting{'lonstatus'}{$key}) eq 'HASH') { + foreach my $type ('E','W','N','U') { + if ($currsetting{'lonstatus'}{$key}{$type} ne '') { + push(@{$changes{'lonstatus'}},$key); + last; + } + } + } + } elsif (($key eq 'threshold') || ($key eq 'sysmail')) { + if (ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') { + if ($currsetting{'lonstatus'}{$key} =~ /^\d+$/) { + if ($currsetting{'lonstatus'}{$key} != $contacts_hash{contacts}{lonstatus}{$key}) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif ($contacts_hash{contacts}{lonstatus}{$key} =~ /^\d+$/) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif ($currsetting{'lonstatus'}{$key} =~ /^\d+$/) { + push(@{$changes{'lonstatus'}},$key); + } + } + } + } else { + if (ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') { + foreach my $key ('excluded','weights','threshold','sysmail') { + if (exists($contacts_hash{contacts}{lonstatus}{$key})) { + push(@{$changes{'lonstatus'}},$key); + } + } + } + } } else { my %default; $default{'supportemail'} = $Apache::lonnet::perlvar{'lonSupportEMail'}; @@ -12782,6 +15792,7 @@ sub modify_contacts { $default{'lonstatusmail'} = 'adminemail'; $default{'requestsmail'} = 'adminemail'; $default{'updatesmail'} = 'adminemail'; + $default{'hostipmail'} = 'adminemail'; foreach my $item (@contacts) { if ($to{$item} ne $default{$item}) { $changes{$item} = 1; @@ -12815,6 +15826,13 @@ sub modify_contacts { } } } + if (ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') { + foreach my $key ('excluded','weights','threshold','sysmail') { + if (exists($contacts_hash{contacts}{lonstatus}{$key})) { + push(@{$changes{'lonstatus'}},$key); + } + } + } } foreach my $item (@toggles) { if (($env{'form.'.$item} == 1) && ($currsetting{$item} == 0)) { @@ -12940,22 +15958,79 @@ sub modify_contacts { } } my @offon = ('off','on'); + my $corelink = &core_link_msu(); if ($changes{'reporterrors'}) { $resulttext .= '
  • '. &mt('E-mail error reports to [_1] set to "'. $offon[$env{'form.reporterrors'}].'".', - &Apache::loncommon::modal_link('http://loncapa.org/core.html', - &mt('LON-CAPA core group - MSU'),600,500)). + $corelink). '
  • '; } if ($changes{'reportupdates'}) { $resulttext .= '
  • '. &mt('E-mail record of completed LON-CAPA updates to [_1] set to "'. $offon[$env{'form.reportupdates'}].'".', - &Apache::loncommon::modal_link('http://loncapa.org/core.html', - &mt('LON-CAPA core group - MSU'),600,500)). + $corelink). + '
  • '; + } + if ($changes{'reportstatus'}) { + $resulttext .= '
  • '. + &mt('E-mail status if errors above threshold to [_1] set to "'. + $offon[$env{'form.reportstatus'}].'".', + $corelink). '
  • '; } + if (ref($changes{'lonstatus'}) eq 'ARRAY') { + $resulttext .= '
  • '. + &mt('Nightly status check e-mail settings').':
      '; + my (%defval,%use_def,%shown); + $defval{'threshold'} = $lonstatus_defs->{'threshold'}; + $defval{'sysmail'} = $lonstatus_defs->{'sysmail'}; + $defval{'weights'} = + join(', ',map { $lonstatus_names->{$_}.'='.$lonstatus_defs->{$_}; } ('E','W','N','U')); + $defval{'excluded'} = &mt('None'); + if (ref($contacts_hash{'contacts'}{'lonstatus'}) eq 'HASH') { + foreach my $item ('threshold','sysmail','weights','excluded') { + if (exists($contacts_hash{'contacts'}{'lonstatus'}{$item})) { + if (($item eq 'threshold') || ($item eq 'sysmail')) { + $shown{$item} = $contacts_hash{'contacts'}{'lonstatus'}{$item}; + } elsif ($item eq 'weights') { + if (ref($contacts_hash{'contacts'}{'lonstatus'}{$item}) eq 'HASH') { + foreach my $type ('E','W','N','U') { + $shown{$item} .= $lonstatus_names->{$type}.'='; + if (exists($contacts_hash{'contacts'}{'lonstatus'}{$item}{$type})) { + $shown{$item} .= $contacts_hash{'contacts'}{'lonstatus'}{$item}{$type}; + } else { + $shown{$item} .= $lonstatus_defs->{$type}; + } + $shown{$item} .= ', '; + } + $shown{$item} =~ s/, $//; + } else { + $shown{$item} = $defval{$item}; + } + } elsif ($item eq 'excluded') { + if (ref($contacts_hash{'contacts'}{'lonstatus'}{$item}) eq 'ARRAY') { + $shown{$item} = join(', ',@{$contacts_hash{'contacts'}{'lonstatus'}{$item}}); + } else { + $shown{$item} = $defval{$item}; + } + } + } else { + $shown{$item} = $defval{$item}; + } + } + } else { + foreach my $item ('threshold','weights','excluded','sysmail') { + $shown{$item} = $defval{$item}; + } + } + foreach my $item ('threshold','weights','excluded','sysmail') { + $resulttext .= '
    • '.&mt($titles->{'error'.$item}.' -- [_1]', + $shown{$item}).'
    • '; + } + $resulttext .= '
  • '; + } if ((ref($changes{'helpform'}) eq 'ARRAY') && (ref($fields) eq 'ARRAY')) { my (@optional,@required,@unused,$maxsizechg); foreach my $field (@{$changes{'helpform'}}) { @@ -13007,6 +16082,778 @@ sub modify_contacts { return $resulttext; } +sub modify_privacy { + my ($dom,%domconfig) = @_; + my ($resulttext,%current,%changes); + if (ref($domconfig{'privacy'}) eq 'HASH') { + %current = %{$domconfig{'privacy'}}; + } + my @fields = ('lastname','firstname','middlename','generation','permanentemail','id'); + my @items = ('domain','author','course','community'); + my %names = &Apache::lonlocal::texthash ( + domain => 'Assigned domain role(s)', + author => 'Assigned co-author role(s)', + course => 'Assigned course role(s)', + community => 'Assigned community role', + ); + my %roles = &Apache::lonlocal::texthash ( + domain => 'Domain role', + author => 'Co-author role', + course => 'Course role', + community => 'Community role', + ); + my %titles = &Apache::lonlocal::texthash ( + approval => 'Approval for role in different domain', + othdom => 'User information available in other domain', + priv => 'Information viewable by privileged user in same domain', + unpriv => 'Information viewable by unprivileged user in same domain', + instdom => 'Other domain shares institution/provider', + extdom => 'Other domain has different institution/provider', + none => 'Not allowed', + user => 'User authorizes', + domain => 'Domain Coordinator authorizes', + auto => 'Unrestricted', + ); + my %fieldnames = &Apache::lonlocal::texthash ( + id => 'Student/Employee ID', + permanentemail => 'E-mail address', + lastname => 'Last Name', + firstname => 'First Name', + middlename => 'Middle Name', + generation => 'Generation', + ); + my ($othertitle,$usertypes,$types) = + &Apache::loncommon::sorted_inst_types($dom); + my (%by_ip,%by_location,@intdoms,@instdoms); + &build_location_hashes(\@intdoms,\%by_ip,\%by_location,\@instdoms); + + my %privacyhash = ( + 'approval' => { + instdom => {}, + extdom => {}, + }, + 'othdom' => {}, + 'priv' => {}, + 'unpriv' => {}, + ); + foreach my $item (@items) { + if (@instdoms > 1) { + if ($env{'form.privacy_approval_instdom'.$item} =~ /^(none|user|domain|auto)$/) { + $privacyhash{'approval'}{'instdom'}{$item} = $env{'form.privacy_approval_instdom_'.$item}; + } + if (ref($current{'approval'}) eq 'HASH') { + if (ref($current{'approval'}{'instdom'}) eq 'HASH') { + unless ($privacyhash{'approval'}{'instdom'}{$item} eq $current{'approval'}{'instdom'}{$item}) { + $changes{'approval'} = 1; + } + } + } elsif ($privacyhash{'approval'}{'instdom'}{$item} ne 'auto') { + $changes{'approval'} = 1; + } + } + if (keys(%by_location) > 0) { + if ($env{'form.privacy_approval_extdom_'.$item} =~ /^(none|user|domain|auto)$/) { + $privacyhash{'approval'}{'extdom'}{$item} = $env{'form.privacy_approval_extdom_'.$item}; + } + if (ref($current{'approval'}) eq 'HASH') { + if (ref($current{'approval'}{'extdom'}) eq 'HASH') { + unless ($privacyhash{'approval'}{'extdom'}{$item} eq $current{'approval'}{'extdom'}{$item}) { + $changes{'approval'} = 1; + } + } + } elsif ($privacyhash{'approval'}{'extdom'}{$item} ne 'auto') { + $changes{'approval'} = 1; + } + } + foreach my $status ('priv','unpriv') { + my @possibles = sort(&Apache::loncommon::get_env_multiple('form.privacy_'.$status.'_'.$item)); + my @newvalues; + foreach my $field (@possibles) { + if (grep(/^\Q$field\E$/,@fields)) { + $privacyhash{$status}{$item}{$field} = 1; + push(@newvalues,$field); + } + } + @newvalues = sort(@newvalues); + if (ref($current{$status}) eq 'HASH') { + if (ref($current{$status}{$item}) eq 'HASH') { + my @currvalues = sort(keys(%{$current{$status}{$item}})); + my @diffs = &Apache::loncommon::compare_arrays(\@currvalues,\@newvalues); + if (@diffs > 0) { + $changes{$status} = 1; + } + } + } else { + my @stdfields; + foreach my $field (@fields) { + if ($field eq 'id') { + next if ($status eq 'unpriv'); + next if (($status eq 'priv') && ($item eq 'community')); + } + push(@stdfields,$field); + } + my @diffs = &Apache::loncommon::compare_arrays(\@stdfields,\@newvalues); + if (@diffs > 0) { + $changes{$status} = 1; + } + } + } + } + if ((@instdoms > 1) || (keys(%by_location) > 0)) { + my @statuses; + if (ref($types) eq 'ARRAY') { + @statuses = @{$types}; + } + foreach my $type (@statuses,'default') { + my @possfields = &Apache::loncommon::get_env_multiple('form.privacy_othdom_'.$type); + my @newvalues; + foreach my $field (sort(@possfields)) { + if (grep(/^\Q$field\E$/,@fields)) { + $privacyhash{'othdom'}{$type}{$field} = 1; + push(@newvalues,$field); + } + } + @newvalues = sort(@newvalues); + if (ref($current{'othdom'}) eq 'HASH') { + if (ref($current{'othdom'}{$type}) eq 'HASH') { + my @currvalues = sort(keys(%{$current{'othdom'}{$type}})); + my @diffs = &Apache::loncommon::compare_arrays(\@currvalues,\@newvalues); + if (@diffs > 0) { + $changes{'othdom'} = 1; + } + } + } else { + my @stdfields = ('lastname','firstname','middlename','generation','permanentemail'); + my @diffs = &Apache::loncommon::compare_arrays(\@stdfields,\@newvalues); + if (@diffs > 0) { + $changes{'othdom'} = 1; + } + } + } + } + my %confighash = ( + privacy => \%privacyhash, + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%confighash,$dom); + if ($putresult eq 'ok') { + if (keys(%changes) > 0) { + $resulttext = &mt('Changes made: ').'
      '; + foreach my $key ('approval','othdom','priv','unpriv') { + if ($changes{$key}) { + $resulttext .= '
    • '.$titles{$key}.':
        '; + if ($key eq 'approval') { + if (keys(%{$privacyhash{$key}{instdom}})) { + $resulttext .= '
      • '.$titles{'instdom'}.'
          '; + foreach my $item (@items) { + $resulttext .= '
        • '.$roles{$item}.': '.$titles{$privacyhash{$key}{instdom}{$item}}.'
        • '; + } + $resulttext .= '
      • '; + } + if (keys(%{$privacyhash{$key}{extdom}})) { + $resulttext .= '
      • '.$titles{'extdom'}.'
          '; + foreach my $item (@items) { + $resulttext .= '
        • '.$roles{$item}.': '.$titles{$privacyhash{$key}{extdom}{$item}}.'
        • '; + } + $resulttext .= '
      • '; + } + } elsif ($key eq 'othdom') { + my @statuses; + if (ref($types) eq 'ARRAY') { + @statuses = @{$types}; + } + if (ref($privacyhash{$key}) eq 'HASH') { + foreach my $status (@statuses,'default') { + if ($status eq 'default') { + $resulttext .= '
      • '.$othertitle.': '; + } elsif (ref($usertypes) eq 'HASH') { + $resulttext .= '
      • '.$usertypes->{$status}.': '; + } else { + next; + } + if (ref($privacyhash{$key}{$status}) eq 'HASH') { + if (keys(%{$privacyhash{$key}{$status}})) { + $resulttext .= join(', ', map { $fieldnames{$_}; } (sort(keys(%{$privacyhash{$key}{$status}})))); + } else { + $resulttext .= &mt('none'); + } + } + $resulttext .= '
      • '; + } + } + } else { + foreach my $item (@items) { + if (ref($privacyhash{$key}{$item}) eq 'HASH') { + $resulttext .= '
      • '.$names{$item}.': '; + if (keys(%{$privacyhash{$key}{$item}})) { + $resulttext .= join(', ', map { $fieldnames{$_}; } (sort(keys(%{$privacyhash{$key}{$item}})))); + } else { + $resulttext .= &mt('none'); + } + $resulttext .= '
      • '; + } + } + } + $resulttext .= '
    • '; + } + } + } else { + $resulttext = &mt('No changes made to user information settings'); + } + } else { + $resulttext = ''. + &mt('An error occurred: [_1]',$putresult).''; + } + return $resulttext; +} + +sub modify_passwords { + my ($r,$dom,$confname,$lastactref,%domconfig) = @_; + my ($resulttext,%current,%changes,%newvalues,@oktypes,$errors, + $updatedefaults,$updateconf); + my $customfn = 'resetpw.html'; + if (ref($domconfig{'passwords'}) eq 'HASH') { + %current = %{$domconfig{'passwords'}}; + } + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + if (ref($types) eq 'ARRAY') { + @oktypes = @{$types}; + } + push(@oktypes,'default'); + + my %titles = &Apache::lonlocal::texthash ( + intauth_cost => 'Encryption cost for bcrypt (positive integer)', + intauth_check => 'Check bcrypt cost if authenticated', + intauth_switch => 'Existing crypt-based switched to bcrypt on authentication', + permanent => 'Permanent e-mail address', + critical => 'Critical notification address', + notify => 'Notification address', + min => 'Minimum password length', + max => 'Maximum password length', + chars => 'Required characters', + expire => 'Password expiration (days)', + numsaved => 'Number of previous passwords to save', + reset => 'Resetting Forgotten Password', + intauth => 'Encryption of Stored Passwords (Internal Auth)', + rules => 'Rules for LON-CAPA Passwords', + crsownerchg => 'Course Owner Changing Student Passwords', + username => 'Username', + email => 'E-mail address', + ); + +# +# Retrieve current domain configuration for internal authentication from $domconfig{'defaults'}. +# + my (%curr_defaults,%save_defaults); + if (ref($domconfig{'defaults'}) eq 'HASH') { + foreach my $key (keys(%{$domconfig{'defaults'}})) { + if ($key =~ /^intauth_(cost|check|switch)$/) { + $curr_defaults{$key} = $domconfig{'defaults'}{$key}; + } else { + $save_defaults{$key} = $domconfig{'defaults'}{$key}; + } + } + } + my %staticdefaults = ( + 'resetlink' => 2, + 'resetcase' => \@oktypes, + 'resetprelink' => 'both', + 'resetemail' => ['critical','notify','permanent'], + 'intauth_cost' => 10, + 'intauth_check' => 0, + 'intauth_switch' => 0, + ); + $staticdefaults{'min'} = $Apache::lonnet::passwdmin; + foreach my $type (@oktypes) { + $staticdefaults{'resetpostlink'}{$type} = ['email','username']; + } + my $linklife = $env{'form.passwords_link'}; + $linklife =~ s/^\s+|\s+$//g; + if (($linklife =~ /^\d+(|\.\d*)$/) && ($linklife > 0)) { + $newvalues{'resetlink'} = $linklife; + if ($current{'resetlink'}) { + if ($current{'resetlink'} ne $linklife) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + if ($staticdefaults{'resetlink'} ne $linklife) { + $changes{'reset'} = 1; + } + } + } elsif ($current{'resetlink'}) { + $changes{'reset'} = 1; + } + my @casesens; + my @posscase = &Apache::loncommon::get_env_multiple('form.passwords_case_sensitive'); + foreach my $case (sort(@posscase)) { + if (grep(/^\Q$case\E$/,@oktypes)) { + push(@casesens,$case); + } + } + $newvalues{'resetcase'} = \@casesens; + if (ref($current{'resetcase'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'resetcase'},\@casesens); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetcase'},\@casesens); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } + if ($env{'form.passwords_prelink'} =~ /^(both|either)$/) { + $newvalues{'resetprelink'} = $env{'form.passwords_prelink'}; + if (exists($current{'resetprelink'})) { + if ($current{'resetprelink'} ne $newvalues{'resetprelink'}) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + if ($staticdefaults{'resetprelink'} ne $newvalues{'resetprelink'}) { + $changes{'reset'} = 1; + } + } + } elsif ($current{'resetprelink'}) { + $changes{'reset'} = 1; + } + foreach my $type (@oktypes) { + my @possplink = &Apache::loncommon::get_env_multiple('form.passwords_postlink_'.$type); + my @postlink; + foreach my $item (sort(@possplink)) { + if ($item =~ /^(email|username)$/) { + push(@postlink,$item); + } + } + $newvalues{'resetpostlink'}{$type} = \@postlink; + unless ($changes{'reset'}) { + if (ref($current{'resetpostlink'}) eq 'HASH') { + if (ref($current{'resetpostlink'}{$type}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'resetpostlink'}{$type},\@postlink); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } else { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetpostlink'}{$type},\@postlink); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } + } + } + my @possemailsrc = &Apache::loncommon::get_env_multiple('form.passwords_emailsrc'); + my @resetemail; + foreach my $item (sort(@possemailsrc)) { + if ($item =~ /^(permanent|critical|notify)$/) { + push(@resetemail,$item); + } + } + $newvalues{'resetemail'} = \@resetemail; + unless ($changes{'reset'}) { + if (ref($current{'resetemail'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'resetemail'},\@resetemail); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetemail'},\@resetemail); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } + } + if ($env{'form.passwords_stdtext'} == 0) { + $newvalues{'resetremove'} = 1; + unless ($current{'resetremove'}) { + $changes{'reset'} = 1; + } + } elsif ($current{'resetremove'}) { + $changes{'reset'} = 1; + } + if ($env{'form.passwords_customfile.filename'} ne '') { + my $servadm = $r->dir_config('lonAdmEMail'); + my ($configuserok,$author_ok,$switchserver) = + &config_check($dom,$confname,$servadm); + my $error; + if ($configuserok eq 'ok') { + if ($switchserver) { + $error = &mt("Upload of file containing domain-specific text is not permitted to this server: [_1]",$switchserver); + } else { + if ($author_ok eq 'ok') { + my ($result,$customurl) = + &publishlogo($r,'upload','passwords_customfile',$dom, + $confname,'customtext/resetpw','','',$customfn); + if ($result eq 'ok') { + $newvalues{'resetcustom'} = $customurl; + $changes{'reset'} = 1; + } else { + $error = &mt("Upload of [_1] failed because an error occurred publishing the file in RES space. Error was: [_2].",$customfn,$result); + } + } else { + $error = &mt("Upload of [_1] failed because an author role could not be assigned to a Domain Configuration user ([_2]) in domain: [_3]. Error was: [_4].",$customfn,$confname,$dom,$author_ok); + } + } + } else { + $error = &mt("Upload of [_1] failed because a Domain Configuration user ([_2]) could not be created in domain: [_3]. Error was: [_4].",$customfn,$confname,$dom,$configuserok); + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
    • '.$error.'
    • '; + } + } elsif ($current{'resetcustom'}) { + if ($env{'form.passwords_custom_del'}) { + $changes{'reset'} = 1; + } else { + $newvalues{'resetcustom'} = $current{'resetcustom'}; + } + } + $env{'form.intauth_cost'} =~ s/^\s+|\s+$//g; + if (($env{'form.intauth_cost'} ne '') && ($env{'form.intauth_cost'} =~ /^\d+$/)) { + $save_defaults{'intauth_cost'} = $env{'form.intauth_cost'}; + if ($save_defaults{'intauth_cost'} ne $curr_defaults{'intauth_cost'}) { + $changes{'intauth'} = 1; + } + } else { + $save_defaults{'intauth_cost'} = $curr_defaults{'intauth_cost'}; + } + if ($env{'form.intauth_check'} =~ /^(0|1|2)$/) { + $save_defaults{'intauth_check'} = $env{'form.intauth_check'}; + if ($save_defaults{'intauth_check'} ne $curr_defaults{'intauth_check'}) { + $changes{'intauth'} = 1; + } + } else { + $save_defaults{'intauth_check'} = $curr_defaults{'intauth_check'}; + } + if ($env{'form.intauth_switch'} =~ /^(0|1|2)$/) { + $save_defaults{'intauth_switch'} = $env{'form.intauth_switch'}; + if ($save_defaults{'intauth_switch'} ne $curr_defaults{'intauth_switch'}) { + $changes{'intauth'} = 1; + } + } else { + $save_defaults{'intauth_check'} = $curr_defaults{'intauth_check'}; + } + foreach my $item ('cost','check','switch') { + if ($save_defaults{'intauth_'.$item} ne $domdefaults{'intauth_'.$item}) { + $domdefaults{'intauth_'.$item} = $save_defaults{'intauth_'.$item}; + $updatedefaults = 1; + } + } + foreach my $rule ('min','max','expire','numsaved') { + $env{'form.passwords_'.$rule} =~ s/^\s+|\s+$//g; + my $ruleok; + if ($rule eq 'expire') { + if (($env{'form.passwords_'.$rule} =~ /^\d+(|\.\d*)$/) && + ($env{'form.passwords_'.$rule} ne '0')) { + $ruleok = 1; + } + } elsif ($rule eq 'min') { + if ($env{'form.passwords_'.$rule} =~ /^\d+$/) { + if ($env{'form.passwords_'.$rule} >= $Apache::lonnet::passwdmin) { + $ruleok = 1; + } + } + } elsif (($env{'form.passwords_'.$rule} =~ /^\d+$/) && + ($env{'form.passwords_'.$rule} ne '0')) { + $ruleok = 1; + } + if ($ruleok) { + $newvalues{$rule} = $env{'form.passwords_'.$rule}; + if (exists($current{$rule})) { + if ($newvalues{$rule} ne $current{$rule}) { + $changes{'rules'} = 1; + } + } elsif ($rule eq 'min') { + if ($staticdefaults{$rule} ne $newvalues{$rule}) { + $changes{'rules'} = 1; + } + } else { + $changes{'rules'} = 1; + } + } elsif (exists($current{$rule})) { + $changes{'rules'} = 1; + } + } + my @posschars = &Apache::loncommon::get_env_multiple('form.passwords_chars'); + my @chars; + foreach my $item (sort(@posschars)) { + if ($item =~ /^(uc|lc|num|spec)$/) { + push(@chars,$item); + } + } + $newvalues{'chars'} = \@chars; + unless ($changes{'rules'}) { + if (ref($current{'chars'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'chars'},\@chars); + if (@diffs > 0) { + $changes{'rules'} = 1; + } + } else { + if (@chars > 0) { + $changes{'rules'} = 1; + } + } + } + my %crsownerchg = ( + by => [], + for => [], + ); + foreach my $item ('by','for') { + my @posstypes = &Apache::loncommon::get_env_multiple('form.passwords_crsowner_'.$item); + foreach my $type (sort(@posstypes)) { + if (grep(/^\Q$type\E$/,@oktypes)) { + push(@{$crsownerchg{$item}},$type); + } + } + } + $newvalues{'crsownerchg'} = \%crsownerchg; + if (ref($current{'crsownerchg'}) eq 'HASH') { + foreach my $item ('by','for') { + if (ref($current{'crsownerchg'}{$item}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'crsownerchg'}{$item},$crsownerchg{$item}); + if (@diffs > 0) { + $changes{'crsownerchg'} = 1; + last; + } + } + } + } elsif (!(ref($domconfig{passwords}) eq 'HASH')) { + foreach my $item ('by','for') { + if (@{$crsownerchg{$item}} > 0) { + $changes{'crsownerchg'} = 1; + last; + } + } + } + + my %confighash = ( + defaults => \%save_defaults, + passwords => \%newvalues, + ); + &process_captcha('passwords',\%changes,$confighash{'passwords'},$domconfig{'passwords'}); + + my $putresult = &Apache::lonnet::put_dom('configuration',\%confighash,$dom); + if ($putresult eq 'ok') { + if (keys(%changes) > 0) { + $resulttext = &mt('Changes made: ').'
        '; + foreach my $key ('reset','intauth','rules','crsownerchg') { + if ($changes{$key}) { + unless ($key eq 'intauth') { + $updateconf = 1; + } + $resulttext .= '
      • '.$titles{$key}.':
          '; + if ($key eq 'reset') { + if ($confighash{'passwords'}{'captcha'} eq 'original') { + $resulttext .= '
        • '.&mt('CAPTCHA validation set to use: original CAPTCHA').'
        • '; + } elsif ($confighash{'passwords'}{'captcha'} eq 'recaptcha') { + $resulttext .= '
        • '.&mt('CAPTCHA validation set to use: reCAPTCHA').' '. + &mt('version: [_1]',$confighash{'passwords'}{'recaptchaversion'}).'
          '; + if (ref($confighash{'passwords'}{'recaptchakeys'}) eq 'HASH') { + $resulttext .= &mt('Public key: [_1]',$confighash{'passwords'}{'recaptchakeys'}{'public'}).'
          '. + &mt('Private key: [_1]',$confighash{'passwords'}{'recaptchakeys'}{'private'}).'
        • '; + } + } else { + $resulttext .= '
        • '.&mt('No CAPTCHA validation').'
        • '; + } + if ($confighash{'passwords'}{'resetlink'}) { + $resulttext .= '
        • '.&mt('Reset link expiration set to [quant,_1,hour]',$confighash{'passwords'}{'resetlink'}).'
        • '; + } else { + $resulttext .= '
        • '.&mt('No reset link expiration set.').' '. + &mt('Will default to 2 hours').'
        • '; + } + if (ref($confighash{'passwords'}{'resetcase'}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'resetcase'}} == 0) { + $resulttext .= '
        • '.&mt('User input for username and/or e-mail address not case sensitive for "Forgot Password" web form').'
        • '; + } else { + my $casesens; + foreach my $type (@{$confighash{'passwords'}{'resetcase'}}) { + if ($type eq 'default') { + $casesens .= $othertitle.', '; + } elsif ($usertypes->{$type} ne '') { + $casesens .= $usertypes->{$type}.', '; + } + } + $casesens =~ s/\Q, \E$//; + $resulttext .= '
        • '.&mt('"Forgot Password" web form input for username and/or e-mail address is case-sensitive for: [_1]',$casesens).'
        • '; + } + } else { + $resulttext .= '
        • '.&mt('Case-sensitivity not set for "Forgot Password" web form').' '.&mt('Will default to case-sensitive for username and/or e-mail address for all').'
        • '; + } + if ($confighash{'passwords'}{'resetprelink'} eq 'either') { + $resulttext .= '
        • '.&mt('Users can enter either a username or an e-mail address in "Forgot Password" web form').'
        • '; + } else { + $resulttext .= '
        • '.&mt('Users can enter both a username and an e-mail address in "Forgot Password" web form').'
        • '; + } + if (ref($confighash{'passwords'}{'resetpostlink'}) eq 'HASH') { + my $output; + if (ref($types) eq 'ARRAY') { + foreach my $type (@{$types}) { + if (ref($confighash{'passwords'}{'resetpostlink'}{$type}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'resetpostlink'}{$type}} == 0) { + $output .= $usertypes->{$type}.' -- '.&mt('none'); + } else { + $output .= $usertypes->{$type}.' -- '. + join(', ',map { $titles{$_}; } (@{$confighash{'passwords'}{'resetpostlink'}{$type}})).'; '; + } + } + } + } + if (ref($confighash{'passwords'}{'resetpostlink'}{'default'}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'resetpostlink'}{'default'}} == 0) { + $output .= $othertitle.' -- '.&mt('none'); + } else { + $output .= $othertitle.' -- '. + join(', ',map { $titles{$_}; } (@{$confighash{'passwords'}{'resetpostlink'}{'default'}})); + } + } + if ($output) { + $resulttext .= '
        • '.&mt('Information required for new password form (by user type) set to: [_1]',$output).'
        • '; + } else { + $resulttext .= '
        • '.&mt('Information required for new password form not set.').' '.&mt('Will default to requiring both the username and an e-mail address').'
        • '; + } + } else { + $resulttext .= '
        • '.&mt('Information required for new password form not set.').' '.&mt('Will default to requiring both the username and an e-mail address').'
        • '; + } + if (ref($confighash{'passwords'}{'resetemail'}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'resetemail'}} > 0) { + $resulttext .= '
        • '.&mt('E-mail address(es) in LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } @{$confighash{'passwords'}{'resetemail'}})).'
        • '; + } else { + $resulttext .= '
        • '.&mt('E-mail address(es) in LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } @{$staticdefaults{'resetemail'}})).'
        • '; + } + } else { + $resulttext .= '
        • '.&mt('E-mail address(es) in LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } @{$staticdefaults{'resetemail'}})).'
        • '; + } + if ($confighash{'passwords'}{'resetremove'}) { + $resulttext .= '
        • '.&mt('Preamble to "Forgot Password" web form not shown').'
        • '; + } else { + $resulttext .= '
        • '.&mt('Preamble to "Forgot Password" web form is shown').'
        • '; + } + if ($confighash{'passwords'}{'resetcustom'}) { + my $customlink = &Apache::loncommon::modal_link($confighash{'passwords'}{'resetcustom'}, + &mt('custom text'),600,500,undef,undef, + undef,undef,'background-color:#ffffff'); + $resulttext .= '
        • '.&mt('Preamble to "Forgot Password" form includes: [_1]',$customlink).'
        • '; + } else { + $resulttext .= '
        • '.&mt('No custom text included in preamble to "Forgot Password" form').'
        • '; + } + } elsif ($key eq 'intauth') { + foreach my $item ('cost','switch','check') { + my $value = $save_defaults{$key.'_'.$item}; + if ($item eq 'switch') { + my %optiondesc = &Apache::lonlocal::texthash ( + 0 => 'No', + 1 => 'Yes', + 2 => 'Yes, and copy existing passwd file to passwd.bak file', + ); + if ($value =~ /^(0|1|2)$/) { + $value = $optiondesc{$value}; + } else { + $value = &mt('none -- defaults to No'); + } + } elsif ($item eq 'check') { + my %optiondesc = &Apache::lonlocal::texthash ( + 0 => 'No', + 1 => 'Yes, allow login then update passwd file using default cost (if higher)', + 2 => 'Yes, disallow login if stored cost is less than domain default', + ); + if ($value =~ /^(0|1|2)$/) { + $value = $optiondesc{$value}; + } else { + $value = &mt('none -- defaults to No'); + } + } + $resulttext .= '
        • '.&mt('[_1] set to "[_2]"',$titles{$key.'_'.$item},$value).'
        • '; + } + } elsif ($key eq 'rules') { + foreach my $rule ('min','max','expire','numsaved') { + if ($confighash{'passwords'}{$rule} eq '') { + if ($rule eq 'min') { + $resulttext .= '
        • '.&mt('[_1] not set.',$titles{$rule}); + ' '.&mt('Default of [_1] will be used', + $Apache::lonnet::passwdmin).'
        • '; + } else { + $resulttext .= '
        • '.&mt('[_1] set to none',$titles{$rule}).'
        • '; + } + } else { + $resulttext .= '
        • '.&mt('[_1] set to [_2]',$titles{$rule},$confighash{'passwords'}{$rule}).'
        • '; + } + } + if (ref($confighash{'passwords'}{'chars'}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'chars'}} > 0) { + my %rulenames = &Apache::lonlocal::texthash( + uc => 'At least one upper case letter', + lc => 'At least one lower case letter', + num => 'At least one number', + spec => 'At least one non-alphanumeric', + ); + my $needed = '
          • '. + join('
          • ',map {$rulenames{$_} } @{$confighash{'passwords'}{'chars'}}). + '
          '; + $resulttext .= '
        • '.&mt('[_1] set to: [_2]',$titles{'chars'},$needed).'
        • '; + } else { + $resulttext .= '
        • '.&mt('[_1] set to none',$titles{'chars'}).'
        • '; + } + } else { + $resulttext .= '
        • '.&mt('[_1] set to none',$titles{'chars'}).'
        • '; + } + } elsif ($key eq 'crsownerchg') { + if (ref($confighash{'passwords'}{'crsownerchg'}) eq 'HASH') { + if ((@{$confighash{'passwords'}{'crsownerchg'}{'by'}} == 0) || + (@{$confighash{'passwords'}{'crsownerchg'}{'for'}} == 0)) { + $resulttext .= '
        • '.&mt('Course owner may not change student passwords.').'
        • '; + } else { + my %crsownerstr; + foreach my $item ('by','for') { + if (ref($confighash{'passwords'}{'crsownerchg'}{$item}) eq 'ARRAY') { + foreach my $type (@{$confighash{'passwords'}{'crsownerchg'}{$item}}) { + if ($type eq 'default') { + $crsownerstr{$item} .= $othertitle.', '; + } elsif ($usertypes->{$type} ne '') { + $crsownerstr{$item} .= $usertypes->{$type}.', '; + } + } + $crsownerstr{$item} =~ s/\Q, \E$//; + } + } + $resulttext .= '
        • '.&mt('Course owner (with status: [_1]) may change passwords for students (with status: [_2]).', + $crsownerstr{'by'},$crsownerstr{'for'}).'
        • '; + } + } else { + $resulttext .= '
        • '.&mt('Course owner may not change student passwords.').'
        • '; + } + } + $resulttext .= '
      • '; + } + } + $resulttext .= '
      '; + } else { + $resulttext = &mt('No changes made to password settings'); + } + my $cachetime = 24*60*60; + if ($updatedefaults) { + &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; + } + } + if ($updateconf) { + &Apache::lonnet::do_cache_new('passwdconf',$dom,$confighash{'passwords'},$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'passwdconf'} = 1; + } + } + } else { + $resulttext = ''. + &mt('An error occurred: [_1]',$putresult).''; + } + if ($errors) { + $resulttext .= '

      '.&mt('The following errors occurred: ').'

        '. + $errors.'

      '; + } + return $resulttext; +} + sub modify_usercreation { my ($dom,%domconfig) = @_; my ($resulttext,%curr_usercreation,%changes,%authallowed,%cancreate,%save_usercreate); @@ -13820,7 +17667,7 @@ sub modify_selfcreation { $chgtext .= &mt('For self-created accounts verified by e-mail address, username is set as follows:'). '
        '; foreach my $status (@statuses) { - if ($type eq 'default') { + if ($status eq 'default') { $chgtext .= '
      • '.$othertitle.' -- '.$options{$cancreate{'emailverified'}{$status}}.'
      • '; } else { $chgtext .= '
      • '.$usertypes{$status}.' -- '.$options{$cancreate{'emailverified'}{$status}}.'
      • '; @@ -14089,19 +17936,25 @@ sub modify_selfcreation { } sub process_captcha { - my ($container,$changes,$newsettings,$current) = @_; - return unless ((ref($changes) eq 'HASH') && (ref($newsettings) eq 'HASH') || (ref($current) eq 'HASH')); + my ($container,$changes,$newsettings,$currsettings) = @_; + return unless ((ref($changes) eq 'HASH') && (ref($newsettings) eq 'HASH')); $newsettings->{'captcha'} = $env{'form.'.$container.'_captcha'}; unless ($newsettings->{'captcha'} eq 'recaptcha' || $newsettings->{'captcha'} eq 'notused') { $newsettings->{'captcha'} = 'original'; } - if ($current->{'captcha'} ne $newsettings->{'captcha'}) { + my %current; + if (ref($currsettings) eq 'HASH') { + %current = %{$currsettings}; + } + if ($current{'captcha'} ne $newsettings->{'captcha'}) { if ($container eq 'cancreate') { if (ref($changes->{'cancreate'}) eq 'ARRAY') { push(@{$changes->{'cancreate'}},'captcha'); } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['captcha']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'captcha'} = 1; } @@ -14123,9 +17976,9 @@ sub process_captcha { } $newsettings->{'recaptchaversion'} = $newversion; } - if (ref($current->{'recaptchakeys'}) eq 'HASH') { - $currpub = $current->{'recaptchakeys'}{'public'}; - $currpriv = $current->{'recaptchakeys'}{'private'}; + if (ref($current{'recaptchakeys'}) eq 'HASH') { + $currpub = $current{'recaptchakeys'}{'public'}; + $currpriv = $current{'recaptchakeys'}{'private'}; unless ($newsettings->{'captcha'} eq 'recaptcha') { $newsettings->{'recaptchakeys'} = { public => '', @@ -14133,8 +17986,8 @@ sub process_captcha { } } } - if ($current->{'captcha'} eq 'recaptcha') { - $currversion = $current->{'recaptchaversion'}; + if ($current{'captcha'} eq 'recaptcha') { + $currversion = $current{'recaptchaversion'}; if ($currversion ne '2') { $currversion = 1; } @@ -14146,6 +17999,8 @@ sub process_captcha { } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['recaptchaversion']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'recaptchaversion'} = 1; } @@ -14157,6 +18012,8 @@ sub process_captcha { } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['recaptchakeys']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'recaptchakeys'} = 1; } @@ -14272,7 +18129,7 @@ sub modify_defaults { my ($resulttext,$mailmsgtxt,%newvalues,%changes,@errors); my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); my @items = ('auth_def','auth_arg_def','lang_def','timezone_def','datelocale_def', - 'portal_def','intauth_cost','intauth_check','intauth_switch'); + 'portal_def'); my @authtypes = ('internal','krb4','krb5','localauth','lti'); foreach my $item (@items) { $newvalues{$item} = $env{'form.'.$item}; @@ -14314,24 +18171,6 @@ sub modify_defaults { push(@errors,$item); } } - } elsif ($item eq 'intauth_cost') { - if ($newvalues{$item} ne '') { - if ($newvalues{$item} =~ /\D/) { - push(@errors,$item); - } - } - } elsif ($item eq 'intauth_check') { - if ($newvalues{$item} ne '') { - unless ($newvalues{$item} =~ /^(0|1|2)$/) { - push(@errors,$item); - } - } - } elsif ($item eq 'intauth_switch') { - if ($newvalues{$item} ne '') { - unless ($newvalues{$item} =~ /^(0|1|2)$/) { - push(@errors,$item); - } - } } if (grep(/^\Q$item\E$/,@errors)) { $newvalues{$item} = $domdefaults{$item}; @@ -14340,6 +18179,18 @@ sub modify_defaults { } $domdefaults{$item} = $newvalues{$item}; } + my %staticdefaults = ( + 'intauth_cost' => 10, + 'intauth_check' => 0, + 'intauth_switch' => 0, + ); + foreach my $item ('intauth_cost','intauth_check','intauth_switch') { + if (exists($domdefaults{$item})) { + $newvalues{$item} = $domdefaults{$item}; + } else { + $newvalues{$item} = $staticdefaults{$item}; + } + } my %defaults_hash = ( defaults => \%newvalues, ); @@ -14468,28 +18319,6 @@ sub modify_defaults { lti => 'lti', ); $value = $authnames{$shortauth{$value}}; - } elsif ($item eq 'intauth_switch') { - my %optiondesc = &Apache::lonlocal::texthash ( - 0 => 'No', - 1 => 'Yes', - 2 => 'Yes, and copy existing passwd file to passwd.bak file', - ); - if ($value =~ /^(0|1|2)$/) { - $value = $optiondesc{$value}; - } else { - $value = &mt('none -- defaults to No'); - } - } elsif ($item eq 'intauth_check') { - my %optiondesc = &Apache::lonlocal::texthash ( - 0 => 'No', - 1 => 'Yes, allow login then update passwd file using default cost (if higher)', - 2 => 'Yes, disallow login if stored cost is less than domain default', - ); - if ($value =~ /^(0|1|2)$/) { - $value = $optiondesc{$value}; - } else { - $value = &mt('none -- defaults to No'); - } } $resulttext .= '
      • '.&mt('[_1] set to "[_2]"',$title->{$item},$value).'
      • '; $mailmsgtext .= "$title->{$item} set to $value\n"; @@ -14538,7 +18367,7 @@ sub modify_scantron { my $custom = 'custom.tab'; my $default = 'default.tab'; my $servadm = $r->dir_config('lonAdmEMail'); - my ($configuserok,$author_ok,$switchserver) = + my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm); if ($env{'form.scantronformat.filename'} ne '') { my $error; @@ -14573,6 +18402,67 @@ sub modify_scantron { if ($env{'form.scantronformat_del'}) { $confhash{'scantron'}{'scantronformat'} = ''; $changes{'scantronformat'} = 1; + } else { + $confhash{'scantron'}{'scantronformat'} = $domconfig{'scantron'}{'scantronformat'}; + } + } + } + my @options = ('hdr','pad','rem'); + my @fields = &scantroncsv_fields(); + my %titles = &scantronconfig_titles(); + my @formats = &Apache::loncommon::get_env_multiple('form.scantronconfig'); + my ($newdat,$currdat,%newcol,%currcol); + if (grep(/^dat$/,@formats)) { + $confhash{'scantron'}{config}{dat} = 1; + $newdat = 1; + } else { + $newdat = 0; + } + if (grep(/^csv$/,@formats)) { + my %bynum; + foreach my $field (@fields) { + if ($env{'form.scantronconfig_csv_'.$field} =~ /^(\d+)$/) { + my $posscol = $1; + if (($posscol < 20) && (!$bynum{$posscol})) { + $confhash{'scantron'}{config}{csv}{fields}{$field} = $posscol; + $bynum{$posscol} = $field; + $newcol{$field} = $posscol; + } + } + } + if (keys(%newcol)) { + foreach my $option (@options) { + if ($env{'form.scantroncsv_'.$option}) { + $confhash{'scantron'}{config}{csv}{options}{$option} = 1; + } + } + } + } + $currdat = 1; + if (ref($domconfig{'scantron'}) eq 'HASH') { + if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') { + unless (exists($domconfig{'scantron'}{'config'}{'dat'})) { + $currdat = 0; + } + if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') { + if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') { + %currcol = %{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}}; + } + } + } + } + if ($currdat != $newdat) { + $changes{'config'} = 1; + } else { + foreach my $field (@fields) { + if ($currcol{$field} ne '') { + if ($currcol{$field} ne $newcol{$field}) { + $changes{'config'} = 1; + last; + } + } elsif ($newcol{$field} ne '') { + $changes{'config'} = 1; + last; } } } @@ -14583,22 +18473,57 @@ sub modify_scantron { if (keys(%changes) > 0) { if (ref($confhash{'scantron'}) eq 'HASH') { $resulttext = &mt('Changes made:').'
          '; - if ($confhash{'scantron'}{'scantronformat'} eq '') { - $resulttext .= '
        • '.&mt('[_1] bubblesheet format file removed; [_2] file will be used for courses in this domain.',$custom,$default).'
        • '; - } else { - $resulttext .= '
        • '.&mt('Custom bubblesheet format file ([_1]) uploaded for use with courses in this domain.',$custom).'
        • '; + if ($changes{'scantronformat'}) { + if ($confhash{'scantron'}{'scantronformat'} eq '') { + $resulttext .= '
        • '.&mt('[_1] bubblesheet format file removed; [_2] file will be used for courses in this domain.',$custom,$default).'
        • '; + } else { + $resulttext .= '
        • '.&mt('Custom bubblesheet format file ([_1]) uploaded for use with courses in this domain.',$custom).'
        • '; + } + } + if ($changes{'config'}) { + if (ref($confhash{'scantron'}{'config'}) eq 'HASH') { + if ($confhash{'scantron'}{'config'}{'dat'}) { + $resulttext .= '
        • '.&mt('Bubblesheet data upload formats includes .dat format').'
        • '; + } + if (ref($confhash{'scantron'}{'config'}{'csv'}) eq 'HASH') { + if (ref($confhash{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') { + if (keys(%{$confhash{'scantron'}{'config'}{'csv'}{'fields'}})) { + $resulttext .= '
        • '.&mt('Bubblesheet data upload formats includes .csv format, with following fields/column numbers supported:').'
            '; + foreach my $field (@fields) { + if ($confhash{'scantron'}{'config'}{'csv'}{'fields'}{$field} ne '') { + my $showcol = $confhash{'scantron'}{'config'}{'csv'}{'fields'}{$field} + 1; + $resulttext .= '
          • '.$titles{$field}.': '.$showcol.'
          • '; + } + } + $resulttext .= '
        • '; + if (ref($confhash{'scantron'}{'config'}{'csv'}{'options'}) eq 'HASH') { + if (keys(%{$confhash{'scantron'}{'config'}{'csv'}{'options'}})) { + $resulttext .= '
        • '.&mt('Bubblesheet data upload formats includes .csv format, with following options:').'
            '; + foreach my $option (@options) { + if ($confhash{'scantron'}{'config'}{'csv'}{'options'}{$option} ne '') { + $resulttext .= '
          • '.$titles{$option}.'
          • '; + } + } + $resulttext .= '
        • '; + } + } + } + } + } + } else { + $resulttext .= '
        • '.&mt('No bubblesheet data upload formats set -- will default to assuming .dat format').'
        • '; + } } $resulttext .= '
        '; } else { $resulttext = &mt('Changes made to bubblesheet format file.'); } - $resulttext .= '
      '; &Apache::loncommon::devalidate_domconfig_cache($dom); if (ref($lastactref) eq 'HASH') { $lastactref->{'domainconfig'} = 1; } } else { - $resulttext = &mt('No changes made to bubblesheet format file'); + $resulttext = &mt('No changes made to bubblesheet format settings'); } } else { $resulttext = ''. @@ -14608,8 +18533,8 @@ sub modify_scantron { $resulttext = &mt('No changes made to bubblesheet format file'); } if ($errors) { - $resulttext .= &mt('The following errors occurred: ').'
        '. - $errors.'
      '; + $resulttext .= '

      '.&mt('The following errors occurred: ').'

        '. + $errors.'

      '; } return $resulttext; } @@ -14884,6 +18809,10 @@ sub modify_coursecategories { } $resulttext .= '
    '; } + &Apache::lonnet::do_cache_new('cats',$dom,$cathash,3600); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'cats'} = 1; + } } $resulttext .= ''; if ($changes{'unauth'} || $changes{'auth'}) { @@ -15430,7 +19359,6 @@ sub modify_coursedefaults { $defaultshash{'coursedefaults'}{$setting}{$type} = $newdef; } if ($currdef ne $newdef) { - my $staticdef; if ($item eq 'anonsurvey_threshold') { unless (($currdef eq '') && ($newdef == $staticdefaults{$item})) { $changes{$item} = 1; @@ -15446,11 +19374,12 @@ sub modify_coursedefaults { my $texengine; if ($env{'form.texengine'} =~ /^(MathJax|mimetex|tth)$/) { $texengine = $env{'form.texengine'}; - if ($defaultshash{'coursedefaults'}{'texengine'} eq '') { - unless ($texengine eq 'MathJax') { + my $currdef = $domconfig{'coursedefaults'}{'texengine'}; + if ($currdef eq '') { + unless ($texengine eq $Apache::lonnet::deftex) { $changes{'texengine'} = 1; } - } elsif ($defaultshash{'coursedefaults'}{'texengine'} ne $texengine) { + } elsif ($currdef ne $texengine) { $changes{'texengine'} = 1; } } @@ -15998,6 +19927,277 @@ sub modify_selfenrollment { return $resulttext; } +sub modify_wafproxy { + my ($dom,$action,$lastactref,%domconfig) = @_; + my %servers = &Apache::lonnet::internet_dom_servers($dom); + my (%othercontrol,%canset,%values,%curralias,%currvalue,@warnings,%wafproxy, + %changes,%expirecache); + foreach my $server (sort(keys(%servers))) { + my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server}); + if ($serverhome eq $server) { + my $serverdom = &Apache::lonnet::host_domain($server); + if ($serverdom eq $dom) { + $canset{$server} = 1; + } + } + } + if (ref($domconfig{'wafproxy'}) eq 'HASH') { + %{$values{$dom}} = (); + if (ref($domconfig{'wafproxy'}{'alias'}) eq 'HASH') { + %curralias = %{$domconfig{'wafproxy'}{'alias'}}; + } + foreach my $item ('remoteip','ipheader','trusted','vpnint','vpnext','sslopt') { + $currvalue{$item} = $domconfig{'wafproxy'}{$item}; + } + } + my $output; + if (keys(%canset)) { + %{$wafproxy{'alias'}} = (); + foreach my $key (sort(keys(%canset))) { + if ($env{'form.wafproxy_'.$dom}) { + $wafproxy{'alias'}{$key} = $env{'form.wafproxy_alias_'.$key}; + $wafproxy{'alias'}{$key} =~ s/^\s+|\s+$//g; + if ($wafproxy{'alias'}{$key} ne $curralias{$key}) { + $changes{'alias'} = 1; + } + } else { + $wafproxy{'alias'}{$key} = ''; + if ($curralias{$key}) { + $changes{'alias'} = 1; + } + } + if ($wafproxy{'alias'}{$key} eq '') { + if ($curralias{$key}) { + $expirecache{$key} = 1; + } + delete($wafproxy{'alias'}{$key}); + } + } + unless (keys(%{$wafproxy{'alias'}})) { + delete($wafproxy{'alias'}); + } + # Localization for values in %warn occus in &mt() calls separately. + my %warn = ( + trusted => 'trusted IP range(s)', + vpnint => 'internal IP range(s) for VPN sessions(s)', + vpnext => 'IP range(s) for backend WAF connections', + ); + foreach my $item ('remoteip','ipheader','trusted','vpnint','vpnext','sslopt') { + my $possible = $env{'form.wafproxy_'.$item}; + $possible =~ s/^\s+|\s+$//g; + if ($possible ne '') { + if ($item eq 'remoteip') { + if ($possible =~ /^[mhn]$/) { + $wafproxy{$item} = $possible; + } + } elsif ($item eq 'ipheader') { + if ($wafproxy{'remoteip'} eq 'h') { + $wafproxy{$item} = $possible; + } + } elsif ($item eq 'sslopt') { + if ($possible =~ /^0|1$/) { + $wafproxy{$item} = $possible; + } + } else { + my (@ok,$count); + if (($item eq 'vpnint') || ($item eq 'vpnext')) { + unless ($env{'form.wafproxy_vpnaccess'}) { + $possible = ''; + } + } elsif ($item eq 'trusted') { + unless ($wafproxy{'remoteip'} eq 'h') { + $possible = ''; + } + } + unless ($possible eq '') { + $possible =~ s/[\r\n]+/\s/g; + $possible =~ s/\s*-\s*/-/g; + $possible =~ s/\s+/,/g; + } + $count = 0; + if ($possible ne '') { + foreach my $poss (split(/\,/,$possible)) { + $count ++; + if (&validate_ip_pattern($poss)) { + push(@ok,$poss); + } + } + if (@ok) { + $wafproxy{$item} = join(',',@ok); + } + my $diff = $count - scalar(@ok); + if ($diff) { + push(@warnings,'
  • '. + &mt('[quant,_1,IP] invalid and excluded from saved value for [_2]', + $diff,$warn{$item}). + '
  • '); + } + } + } + if ($wafproxy{$item} ne $currvalue{$item}) { + $changes{$item} = 1; + } + } elsif ($currvalue{$item}) { + $changes{$item} = 1; + } + } + } else { + if (keys(%curralias)) { + $changes{'alias'} = 1; + } + if (keys(%currvalue)) { + foreach my $key (keys(%currvalue)) { + $changes{$key} = 1; + } + } + } + if (keys(%changes)) { + my %defaultshash = ( + wafproxy => \%wafproxy, + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash, + $dom); + if ($putresult eq 'ok') { + my $cachetime = 24*60*60; + my (%domdefaults,$updatedomdefs); + foreach my $item ('ipheader','trusted','vpnint','vpnext','sslopt') { + if ($changes{$item}) { + unless ($updatedomdefs) { + %domdefaults = &Apache::lonnet::get_domain_defaults($dom); + $updatedomdefs = 1; + } + if ($wafproxy{$item}) { + $domdefaults{'waf_'.$item} = $wafproxy{$item}; + } elsif (exists($domdefaults{'waf_'.$item})) { + delete($domdefaults{'waf_'.$item}); + } + } + } + if ($updatedomdefs) { + &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; + } + } + if ((exists($wafproxy{'alias'})) || (keys(%expirecache))) { + my %updates = %expirecache; + foreach my $key (keys(%expirecache)) { + &Apache::lonnet::devalidate_cache_new('proxyalias',$key); + } + if (ref($wafproxy{'alias'}) eq 'HASH') { + my $cachetime = 24*60*60; + foreach my $key (keys(%{$wafproxy{'alias'}})) { + $updates{$key} = 1; + &Apache::lonnet::do_cache_new('proxyalias',$key,$wafproxy{'alias'}{$key}, + $cachetime); + } + } + if (ref($lastactref) eq 'HASH') { + $lastactref->{'proxyalias'} = \%updates; + } + } + $output = &mt('Changes were made to Web Application Firewall/Reverse Proxy').'
      '; + foreach my $item ('alias','remoteip','ipheader','trusted','vpnint','vpnext','sslopt') { + if ($changes{$item}) { + if ($item eq 'alias') { + my $numaliased = 0; + if (ref($wafproxy{'alias'}) eq 'HASH') { + my $shown; + if (keys(%{$wafproxy{'alias'}})) { + foreach my $server (sort(keys(%{$wafproxy{'alias'}}))) { + $shown .= '
    • '.&mt('[_1] aliased by [_2]', + &Apache::lonnet::hostname($server), + $wafproxy{'alias'}{$server}).'
    • '; + $numaliased ++; + } + if ($numaliased) { + $output .= '
    • '.&mt('Aliases for hostnames set to: [_1]', + '
        '.$shown.'
      ').'
    • '; + } + } + } + unless ($numaliased) { + $output .= '
    • '.&mt('Aliases deleted for hostnames').'
    • '; + } + } else { + if ($item eq 'remoteip') { + my %ip_methods = &remoteip_methods(); + if ($wafproxy{$item} =~ /^[mh]$/) { + $output .= '
    • '.&mt("Method for determining user's IP set to: [_1]", + $ip_methods{$wafproxy{$item}}).'
    • '; + } else { + if (($env{'form.wafproxy_'.$dom}) && (ref($wafproxy{'alias'}) eq 'HASH')) { + $output .= '
    • '.&mt("No method in use to get user's real IP (will report IP used by WAF)."). + '
    • '; + } else { + $output .= '
    • '.&mt('WAF/Reverse Proxy not in use').'
    • '; + } + } + } elsif ($item eq 'ipheader') { + if ($wafproxy{$item}) { + $output .= '
    • '.&mt('Request header with remote IP set to: [_1]', + $wafproxy{$item}).'
    • '; + } else { + $output .= '
    • '.&mt('Request header with remote IP deleted').'
    • '; + } + } elsif ($item eq 'trusted') { + if ($wafproxy{$item}) { + $output .= '
    • '.&mt('Trusted IP range(s) set to: [_1]', + $wafproxy{$item}).'
    • '; + } else { + $output .= '
    • '.&mt('Trusted IP range(s) deleted').'
    • '; + } + } elsif ($item eq 'vpnint') { + if ($wafproxy{$item}) { + $output .= '
    • '.&mt('Internal IP Range(s) for VPN sessions set to: [_1]', + $wafproxy{$item}).'
    • '; + } else { + $output .= '
    • '.&mt('Internal IP Range(s) for VPN sessions deleted').'
    • '; + } + } elsif ($item eq 'vpnext') { + if ($wafproxy{$item}) { + $output .= '
    • '.&mt('IP Range(s) for backend WAF connections set to: [_1]', + $wafproxy{$item}).'
    • '; + } else { + $output .= '
    • '.&mt('IP Range(s) for backend WAF connections deleted').'
    • '; + } + } elsif ($item eq 'sslopt') { + if ($wafproxy{$item}) { + $output .= '
    • '.&mt('WAF/Reverse Proxy expected to forward requests to https on LON-CAPA node, regardless of original protocol in web browser (http or https).').'
    • '; + } else { + $output .= '
    • '.&mt('WAF/Reverse Proxy expected to preserve original protocol in web browser (either http or https) when forwarding to LON-CAPA node.').'
    • '; + } + } + } + } + } + } else { + $output = ''. + &mt('An error occurred: [_1]',$putresult).''; + } + } elsif (keys(%canset)) { + $output = &mt('No changes made to Web Application Firewall/Reverse Proxy settings'); + } + if (@warnings) { + $output .= '
      '.&mt('Warnings:').'
        '. + join("\n",@warnings).'
      '; + } + return $output; +} + +sub validate_ip_pattern { + my ($pattern) = @_; + if ($pattern =~ /^([^-]+)\-([^-]+)$/) { + my ($start,$end) = ($1,$2); + if ((&Net::CIDR::cidrvalidate($start)) && (&Net::CIDR::cidrvalidate($end))) { + return 1; + } + } elsif (&Net::CIDR::cidrvalidate($pattern)) { + return 1; + } + return +} + sub modify_usersessions { my ($dom,$lastactref,%domconfig) = @_; my @hostingtypes = ('version','excludedomain','includedomain'); @@ -16165,6 +20365,7 @@ sub modify_usersessions { } } $defaultshash{'usersessions'}{'offloadnow'} = {}; + $defaultshash{'usersessions'}{'offloadoth'} = {}; my @offloadnow = &Apache::loncommon::get_env_multiple('form.offloadnow'); my @okoffload; if (@offloadnow) { @@ -16181,6 +20382,22 @@ sub modify_usersessions { } } } + my @offloadoth = &Apache::loncommon::get_env_multiple('form.offloadoth'); + my @okoffloadoth; + if (@offloadoth) { + foreach my $server (@offloadoth) { + if (&Apache::lonnet::hostname($server) ne '') { + unless (grep(/^\Q$server\E$/,@okoffloadoth)) { + push(@okoffloadoth,$server); + } + } + } + if (@okoffloadoth) { + foreach my $lonhost (@okoffloadoth) { + $defaultshash{'usersessions'}{'offloadoth'}{$lonhost} = 1; + } + } + } if (ref($domconfig{'usersessions'}) eq 'HASH') { if (ref($domconfig{'usersessions'}{'spares'}) eq 'HASH') { if (ref($changes{'spares'}) eq 'HASH') { @@ -16191,26 +20408,38 @@ sub modify_usersessions { } else { $savespares = 1; } - if (ref($domconfig{'usersessions'}{'offloadnow'}) eq 'HASH') { - foreach my $lonhost (keys(%{$domconfig{'usersessions'}{'offloadnow'}})) { - unless ($defaultshash{'usersessions'}{'offloadnow'}{$lonhost}) { - $changes{'offloadnow'} = 1; - last; - } - } - unless ($changes{'offloadnow'}) { - foreach my $lonhost (keys(%{$defaultshash{'usersessions'}{'offloadnow'}})) { - unless ($domconfig{'usersessions'}{'offloadnow'}{$lonhost}) { - $changes{'offloadnow'} = 1; + foreach my $offload ('offloadnow','offloadoth') { + if (ref($domconfig{'usersessions'}{$offload}) eq 'HASH') { + foreach my $lonhost (keys(%{$domconfig{'usersessions'}{$offload}})) { + unless ($defaultshash{'usersessions'}{$offload}{$lonhost}) { + $changes{$offload} = 1; last; } } - } - } elsif (@okoffload) { + unless ($changes{$offload}) { + foreach my $lonhost (keys(%{$defaultshash{'usersessions'}{$offload}})) { + unless ($domconfig{'usersessions'}{$offload}{$lonhost}) { + $changes{$offload} = 1; + last; + } + } + } + } else { + if (($offload eq 'offloadnow') && (@okoffload)) { + $changes{'offloadnow'} = 1; + } + if (($offload eq 'offloadoth') && (@okoffloadoth)) { + $changes{'offloadoth'} = 1; + } + } + } + } else { + if (@okoffload) { $changes{'offloadnow'} = 1; } - } elsif (@okoffload) { - $changes{'offloadnow'} = 1; + if (@okoffloadoth) { + $changes{'offloadoth'} = 1; + } } my $nochgmsg = &mt('No changes made to settings for user session hosting/offloading.'); if ((keys(%changes) > 0) || ($savespares)) { @@ -16227,6 +20456,9 @@ sub modify_usersessions { if (ref($defaultshash{'usersessions'}{'offloadnow'}) eq 'HASH') { $domdefaults{'offloadnow'} = $defaultshash{'usersessions'}{'offloadnow'}; } + if (ref($defaultshash{'usersessions'}{'offloadoth'}) eq 'HASH') { + $domdefaults{'offloadoth'} = $defaultshash{'usersessions'}{'offloadoth'}; + } } my $cachetime = 24*60*60; &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); @@ -16267,14 +20499,18 @@ sub modify_usersessions { } else { foreach my $type (@{$types{$prefix}}) { if (defined($changes{$prefix}{$type})) { - my $newvalue; + my ($newvalue,$notinuse); if (ref($defaultshash{'usersessions'}) eq 'HASH') { if (ref($defaultshash{'usersessions'}{$prefix})) { if ($type eq 'version') { $newvalue = $defaultshash{'usersessions'}{$prefix}{$type}; - } elsif (ref($defaultshash{'usersessions'}{$prefix}{$type}) eq 'ARRAY') { - if (@{$defaultshash{'usersessions'}{$prefix}{$type}} > 0) { - $newvalue = join(', ',@{$defaultshash{'usersessions'}{$prefix}{$type}}); + } else { + if (ref($defaultshash{'usersessions'}{$prefix}{$type}) eq 'ARRAY') { + if (@{$defaultshash{'usersessions'}{$prefix}{$type}} > 0) { + $newvalue = join(', ',@{$defaultshash{'usersessions'}{$prefix}{$type}}); + } + } else { + $notinuse = 1; } } } @@ -16282,12 +20518,14 @@ sub modify_usersessions { if ($newvalue eq '') { if ($type eq 'version') { $resulttext .= '
    • '.&mt('[_1] set to: off',$lt{$type}).'
    • '; + } elsif ($notinuse) { + $resulttext .= '
    • '.&mt('[_1] set to: not in use',$lt{$type}).'
    • '; } else { $resulttext .= '
    • '.&mt('[_1] set to: none',$lt{$type}).'
    • '; } } else { if ($type eq 'version') { - $newvalue .= ' '.&mt('(or later)'); + $newvalue .= ' '.&mt('(or later)'); } $resulttext .= '
    • '.&mt('[_1] set to: [_2].',$lt{$type},$newvalue).'
    • '; } @@ -16300,16 +20538,31 @@ sub modify_usersessions { if ($changes{'offloadnow'}) { if (ref($defaultshash{'usersessions'}{'offloadnow'}) eq 'HASH') { if (keys(%{$defaultshash{'usersessions'}{'offloadnow'}}) > 0) { - $resulttext .= '
    • '.&mt('Switch active users on next access, for server(s):').'
        '; + $resulttext .= '
      • '.&mt('Switch any active user on next access, for server(s):').'
          '; foreach my $lonhost (sort(keys(%{$defaultshash{'usersessions'}{'offloadnow'}}))) { $resulttext .= '
        • '.$lonhost.'
        • '; } $resulttext .= '
        '; } else { - $resulttext .= '
      • '.&mt('No servers now set to switch active users on next access.'); + $resulttext .= '
      • '.&mt('No servers now set to switch any active user on next access.'); } } else { - $resulttext .= '
      • '.&mt('No servers now set to switch active users on next access.').'
      • '; + $resulttext .= '
      • '.&mt('No servers now set to switch any active user on next access.').'
      • '; + } + } + if ($changes{'offloadoth'}) { + if (ref($defaultshash{'usersessions'}{'offloadoth'}) eq 'HASH') { + if (keys(%{$defaultshash{'usersessions'}{'offloadoth'}}) > 0) { + $resulttext .= '
      • '.&mt('Switch other institutions on next access, for server(s):').'
          '; + foreach my $lonhost (sort(keys(%{$defaultshash{'usersessions'}{'offloadoth'}}))) { + $resulttext .= '
        • '.$lonhost.'
        • '; + } + $resulttext .= '
        '; + } else { + $resulttext .= '
      • '.&mt('No servers now set to switch other institutions on next access.'); + } + } else { + $resulttext .= '
      • '.&mt('No servers now set to switch other institutions on next access.').'
      • '; } } $resulttext .= '
      '; @@ -16355,12 +20608,17 @@ sub modify_ssl { if ($env{'form.'.$prefix.'_'.$type} =~ /^(no|req)$/) { $value = $env{'form.'.$prefix.'_'.$type}; } - if (ref($domconfig{$action}{$prefix}) eq 'HASH') { - if ($domconfig{$action}{$prefix}{$type} ne '') { - if ($value ne $domconfig{$action}{$prefix}{$type}) { + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$prefix}) eq 'HASH') { + if ($domconfig{$action}{$prefix}{$type} ne '') { + if ($value ne $domconfig{$action}{$prefix}{$type}) { + $changes{$prefix}{$type} = 1; + } + $defaultshash{$action}{$prefix}{$type} = $value; + } else { + $defaultshash{$action}{$prefix}{$type} = $value; $changes{$prefix}{$type} = 1; } - $defaultshash{$action}{$prefix}{$type} = $value; } else { $defaultshash{$action}{$prefix}{$type} = $value; $changes{$prefix}{$type} = 1; @@ -16430,6 +20688,17 @@ sub modify_ssl { } } } + if (keys(%changes)) { + foreach my $prefix (keys(%changes)) { + if (ref($changes{$prefix}) eq 'HASH') { + if (scalar(keys(%{$changes{$prefix}})) == 0) { + delete($changes{$prefix}); + } + } else { + delete($changes{$prefix}); + } + } + } my $nochgmsg = &mt('No changes made to LON-CAPA SSL settings'); if (keys(%changes) > 0) { my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash, @@ -16440,10 +20709,10 @@ sub modify_ssl { $domdefaults{'replication'} = $defaultshash{$action}{'replication'}; } if (ref($defaultshash{$action}{'connto'}) eq 'HASH') { - $domdefaults{'connto'} = $domconfig{$action}{'connto'}; + $domdefaults{'connto'} = $defaultshash{$action}{'connto'}; } if (ref($defaultshash{$action}{'connfrom'}) eq 'HASH') { - $domdefaults{'connfrom'} = $domconfig{$action}{'connfrom'}; + $domdefaults{'connfrom'} = $defaultshash{$action}{'connfrom'}; } } my $cachetime = 24*60*60; @@ -16459,18 +20728,24 @@ sub modify_ssl { $resulttext .= '
    • '.$titles{$prefix}.'
        '; foreach my $type (@{$types{$prefix}}) { if (defined($changes{$prefix}{$type})) { - my $newvalue; + my ($newvalue,$notinuse); if (ref($defaultshash{$action}) eq 'HASH') { if (ref($defaultshash{$action}{$prefix})) { if (($prefix eq 'connto') || ($prefix eq 'connfrom')) { $newvalue = $titles{$defaultshash{$action}{$prefix}{$type}}; - } elsif (ref($defaultshash{$action}{$prefix}{$type}) eq 'ARRAY') { - if (@{$defaultshash{$action}{$prefix}{$type}} > 0) { - $newvalue = join(', ',@{$defaultshash{$action}{$prefix}{$type}}); + } else { + if (ref($defaultshash{$action}{$prefix}{$type}) eq 'ARRAY') { + if (@{$defaultshash{$action}{$prefix}{$type}} > 0) { + $newvalue = join(', ',@{$defaultshash{$action}{$prefix}{$type}}); + } + } else { + $notinuse = 1; } } } - if ($newvalue eq '') { + if ($notinuse) { + $resulttext .= '
      • '.&mt('[_1] set to: not in use',$titles{$type}).'
      • '; + } elsif ($newvalue eq '') { $resulttext .= '
      • '.&mt('[_1] set to: none',$titles{$type}).'
      • '; } else { $resulttext .= '
      • '.&mt('[_1] set to: [_2].',$titles{$type},$newvalue).'
      • '; @@ -16584,17 +20859,21 @@ sub modify_trust { $resulttext .= '
      • '.$lt{$prefix}.'
          '; foreach my $type (@types) { if (defined($changes{$prefix}{$type})) { - my $newvalue; + my ($newvalue,$notinuse); if (ref($defaultshash{'trust'}) eq 'HASH') { if (ref($defaultshash{'trust'}{$prefix})) { if (ref($defaultshash{'trust'}{$prefix}{$type}) eq 'ARRAY') { if (@{$defaultshash{'trust'}{$prefix}{$type}} > 0) { $newvalue = join(', ',@{$defaultshash{'trust'}{$prefix}{$type}}); } + } else { + $notinuse = 1; } } } - if ($newvalue eq '') { + if ($notinuse) { + $resulttext .= '
        • '.&mt('[_1] set to: not in use',$lt{$type}).'
        • '; + } elsif ($newvalue eq '') { $resulttext .= '
        • '.&mt('[_1] set to: none',$lt{$type}).'
        • '; } else { $resulttext .= '
        • '.&mt('[_1] set to: [_2].',$lt{$type},$newvalue).'
        • '; @@ -16629,12 +20908,12 @@ sub modify_loadbalancing { my @sparestypes = ('primary','default'); my %typetitles = &sparestype_titles(); my $resulttext; - my (%currbalancer,%currtargets,%currrules,%existing); + my (%currbalancer,%currtargets,%currrules,%existing,%currcookies); if (ref($domconfig{'loadbalancing'}) eq 'HASH') { %existing = %{$domconfig{'loadbalancing'}}; } &get_loadbalancers_config(\%servers,\%existing,\%currbalancer, - \%currtargets,\%currrules); + \%currtargets,\%currrules,\%currcookies); my ($saveloadbalancing,%defaultshash,%changes); my ($alltypes,$othertypes,$titles) = &loadbalancing_titles($dom,$intdom,$usertypes,$types); @@ -16686,6 +20965,18 @@ sub modify_loadbalancing { } $defaultshash{'loadbalancing'}{$balancer}{'targets'}{$sparetype} = \@offloadto; } + if ($env{'form.loadbalancing_cookie_'.$i}) { + $defaultshash{'loadbalancing'}{$balancer}{'cookie'} = 1; + if (exists($currbalancer{$balancer})) { + unless ($currcookies{$balancer}) { + $changes{'curr'}{$balancer}{'cookie'} = 1; + } + } + } elsif (exists($currbalancer{$balancer})) { + if ($currcookies{$balancer}) { + $changes{'curr'}{$balancer}{'cookie'} = 1; + } + } if (ref($currtargets{$balancer}) eq 'HASH') { foreach my $sparetype (@sparestypes) { if (ref($currtargets{$balancer}{$sparetype}) eq 'ARRAY') { @@ -16839,24 +21130,28 @@ sub modify_loadbalancing { } } } - if (keys(%toupdate)) { - my %thismachine; - my $updatedhere; - my $cachetime = 60*60*24; - map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); - foreach my $lonhost (keys(%toupdate)) { - if ($thismachine{$lonhost}) { - unless ($updatedhere) { - &Apache::lonnet::do_cache_new('loadbalancing',$dom, - $defaultshash{'loadbalancing'}, - $cachetime); - $updatedhere = 1; - } - } else { - my $cachekey = &escape('loadbalancing').':'.&escape($dom); - &Apache::lonnet::remote_devalidate_cache($lonhost,[$cachekey]); - } + if ($changes{'curr'}{$balancer}{'cookie'}) { + $resulttext .= '
        • '.&mt('Load Balancer: [_1] -- cookie use enabled', + $balancer).'
        • '; + } + } + } + if (keys(%toupdate)) { + my %thismachine; + my $updatedhere; + my $cachetime = 60*60*24; + map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); + foreach my $lonhost (keys(%toupdate)) { + if ($thismachine{$lonhost}) { + unless ($updatedhere) { + &Apache::lonnet::do_cache_new('loadbalancing',$dom, + $defaultshash{'loadbalancing'}, + $cachetime); + $updatedhere = 1; } + } else { + my $cachekey = &escape('loadbalancing').':'.&escape($dom); + &Apache::lonnet::remote_devalidate_cache($lonhost,[$cachekey]); } } } @@ -17068,12 +21363,12 @@ sub lonbalance_targets_js { } push(@alltypes,'default','_LC_adv','_LC_author','_LC_internetdom','_LC_external'); $allinsttypes = join("','",@alltypes); - my (%currbalancer,%currtargets,%currrules,%existing); + my (%currbalancer,%currtargets,%currrules,%existing,%currcookies); if (ref($settings) eq 'HASH') { %existing = %{$settings}; } &get_loadbalancers_config($servers,\%existing,\%currbalancer, - \%currtargets,\%currrules); + \%currtargets,\%currrules,\%currcookies); my $balancers = join("','",sort(keys(%currbalancer))); return <<"END"; @@ -17353,6 +21648,7 @@ function balancerChange(balnum,baltotal, END } + sub new_spares_js { my @sparestypes = ('primary','default'); my $types = join("','",@sparestypes); @@ -17426,7 +21722,7 @@ function updateNewSpares(formname,lonhos function checkNewSpares(lonhost,type) { var newSpare = document.getElementById('newspare_'+type+'_'+lonhost); var chosen = newSpare.options[newSpare.selectedIndex].value; - if (chosen != '') { + if (chosen != '') { var othertype; var othernewSpare; if (type == 'primary') { @@ -17560,7 +21856,7 @@ function toggleDisplay(domForm,caller) { var dispval = 'block'; var selfcreateRegExp = /^cancreate_emailverified/; if (caller == 'emailoptions') { - optionsElement = domForm.cancreate_email; + optionsElement = domForm.cancreate_email; } if (caller == 'studentsubmission') { optionsElement = domForm.postsubmit; @@ -17615,14 +21911,23 @@ sub devalidate_remote_domconfs { my %servers = &Apache::lonnet::internet_dom_servers($dom); my %thismachine; map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); - my @posscached = ('domainconfig','domdefaults','ltitools','usersessions','directorysrch'); + my @posscached = ('domainconfig','domdefaults','ltitools','usersessions', + 'directorysrch','passwdconf','cats','proxyalias'); if (keys(%servers)) { foreach my $server (keys(%servers)) { next if ($thismachine{$server}); my @cached; foreach my $name (@posscached) { if ($cachekeys->{$name}) { - push(@cached,&escape($name).':'.&escape($dom)); + if ($name eq 'proxyalias') { + if (ref($cachekeys->{$name}) eq 'HASH') { + foreach my $key (keys(%{$cachekeys->{$name}})) { + push(@cached,&escape($name).':'.&escape($key)); + } + } + } else { + push(@cached,&escape($name).':'.&escape($dom)); + } } } if (@cached) { 500 Internal Server Error

          Internal Server Error

          The server encountered an internal error or misconfiguration and was unable to complete your request.

          Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

          More information about this error may be available in the server error log.

    '."\n"; foreach my $option ('original','recaptcha','notused') { $output .= ''.$titles->{$item}. - ''; - if ($item eq 'intauth_switch') { - my @options = (0,1,2); - my %optiondesc = &Apache::lonlocal::texthash ( - 0 => 'No', - 1 => 'Yes', - 2 => 'Yes, and copy existing passwd file to passwd.bak file', - ); - $datatable .= ''; - foreach my $option (@options) { - my $checked = ' '; - if ($defaults{$item} eq $option) { - $checked = ' checked="checked"'; - } - $datatable .= ''; - } - $datatable .= '
    '. - '
    '; - } elsif ($item eq 'intauth_check') { - my @options = (0,1,2); - my %optiondesc = &Apache::lonlocal::texthash ( - 0 => 'No', - 1 => 'Yes, allow login then update passwd file using default cost (if higher)', - 2 => 'Yes, disallow login if stored cost is less than domain default', - ); - $datatable .= ''; - foreach my $option (@options) { - my $checked = ' '; - my $onclick; - if ($defaults{$item} eq $option) { - $checked = ' checked="checked"'; - } - if ($option == 2) { - $onclick = ' onclick="javascript:warnIntAuth(this);"'; - } - $datatable .= ''; - } - $datatable .= '
    '. - '
    '; - } else { - $datatable .= ''; - } - $datatable .= '
    '.&mt('Name displayed:'). + ''.&mt('Name displayed').':'. ''. '
    '. - &mt('Name displayed:'). + &mt('Name displayed').':'. '
    '.&mt('Supported formats').''; + foreach my $item ('dat','csv') { + my $id; + if ($item eq 'csv') { + $id = 'id="scantronconfcsv" '; + } + $datatable .= ''.(' 'x3); + if ($item eq 'csv') { + $datatable .= '
    '. + ''.&mt('CSV Column Mapping').''. + ''."\n"; + foreach my $col (@fields) { + my $selnone; + if ($csvfields{$col} eq '') { + $selnone = ' selected="selected"'; + } + $datatable .= ''. + ''; + } + $datatable .= '
    '.&mt('Field').''.&mt('Location').'
    '.$titles{$col}.'
    '. + '
    '. + ''.&mt('CSV Options').''; + foreach my $option ('hdr','pad','rem') { + $datatable .= ''.$titles{$option}.':'. + ''.(' 'x2)."\n". + '
    '; + } + $datatable .= '
    '; + $itemcount ++; + } + } + $datatable .= '
    '.$title{'togglecatsplace'}.'
    '.$hdritem->{'header'}->[1]->{'col2'}.'
    ' - .' ' + .' ' .$default_names{$default} .'' .''.&mt('Name:') - .' 
    '.&mt('Name:') + .' ' + .'
    '.&mt('Add subcategory:').'