--- loncom/interface/domainprefs.pm 2019/08/25 14:35:21 1.160.6.99 +++ loncom/interface/domainprefs.pm 2022/01/16 19:04:04 1.160.6.119 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.160.6.99 2019/08/25 14:35:21 raeburn Exp $ +# $Id: domainprefs.pm,v 1.160.6.119 2022/01/16 19:04:04 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -174,6 +174,7 @@ use File::Copy; use Locale::Language; use DateTime::TimeZone; use DateTime::Locale; +use Net::CIDR; my $registered_cleanup; my $modified_urls; @@ -216,11 +217,12 @@ sub handler { 'contacts','defaults','scantron','coursecategories', 'serverstatuses','requestcourses','helpsettings', 'coursedefaults','usersessions','loadbalancing', - 'requestauthor','selfenrollment','inststatus','passwords'],$dom); - my @prefs_order = ('rolecolors','login','defaults','passwords','quotas','autoenroll', - 'autoupdate','autocreate','directorysrch','contacts', - 'usercreation','selfcreation','usermodification','scantron', - 'requestcourses','requestauthor','coursecategories', + 'requestauthor','selfenrollment','inststatus', + 'passwords','wafproxy','ipaccess'],$dom); + my @prefs_order = ('rolecolors','login','ipaccess','defaults','wafproxy','passwords', + 'quotas','autoenroll','autoupdate','autocreate','directorysrch', + 'contacts','usercreation','selfcreation','usermodification', + 'scantron','requestcourses','requestauthor','coursecategories', 'serverstatuses','helpsettings','coursedefaults', 'selfenrollment','usersessions'); my %existing; @@ -253,7 +255,10 @@ sub handler { {col1 => 'Log-in Help', col2 => 'Value'}, {col1 => 'Custom HTML in document head', - col2 => 'Value'}], + col2 => 'Value'}, + {col1 => 'SSO', + col2 => 'Dual login: SSO and non-SSO options'}, + ], print => \&print_login, modify => \&modify_login, }, @@ -267,6 +272,17 @@ sub handler { 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', @@ -335,6 +351,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, @@ -488,6 +506,14 @@ sub handler { print => \&print_loadbalancing, modify => \&modify_loadbalancing, }, + 'ipaccess' => + {text => 'IP-based access control', + help => 'Domain_Configuration_IP_Access', + header => [{col1 => 'Setting', + col2 => 'Value'},], + print => \&print_ipaccess, + modify => \&modify_ipaccess, + }, ); if (keys(%servers) > 1) { $prefs{'login'} = { text => 'Log-in page options', @@ -499,7 +525,10 @@ sub handler { {col1 => 'Log-in Help', col2 => 'Value'}, {col1 => 'Custom HTML in document head', - col2 => 'Value'}], + col2 => 'Value'}, + {col1 => 'SSO', + col2 => 'Dual login: SSO and non-SSO options'}, + ], print => \&print_login, modify => \&modify_login, }; @@ -540,6 +569,8 @@ $javascript_validations $coursebrowserjs END + } elsif (grep(/^ipaccess$/,@actions)) { + $js .= &Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}); } if (grep(/^selfcreation$/,@actions)) { $js .= &selfcreate_javascript(); @@ -672,6 +703,10 @@ sub process_changes { $output = &modify_loadbalancing($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); + } elsif ($action eq 'ipaccess') { + $output = &modify_ipaccess($dom,$lastactref,%domconfig); } return $output; } @@ -702,11 +737,21 @@ sub print_config_box { $output = &Apache::lonuserutils::custom_roledefs_js($context,$crstype,$formname,\%full, \@templateroles); + } elsif ($action eq 'wafproxy') { + $output .= &wafproxy_javascript($dom); + } elsif ($action eq 'autoupdate') { + $output .= &autoupdate_javascript(); + } elsif ($action eq 'autoenroll') { + $output .= &autoenroll_javascript(); + } elsif ($action eq 'login') { + $output .= &saml_javascript(); + } elsif ($action eq 'ipaccess') { + $output .= &ipaccess_javascript($settings); } $output .= ' - '."\n". ''; @@ -718,27 +763,31 @@ 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))) { + (($action eq 'login') && ($numheaders < 5))) { $colspan = ' colspan="2"'; } if ($action eq 'usersessions') { $rightcolspan = ' colspan="3"'; } + if ($action eq 'passwords') { + $leftnobr = ' LC_nobreak'; + } $output .= ' @@ -805,9 +859,14 @@ sub print_config_box { @@ -818,12 +877,12 @@ 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 'login') { - if ($numheaders == 4) { + if ($numheaders == 5) { $output .= &print_login('page',$dom,$confname,$phase,$settings,\$rowtotal).'
'. + '. &mt($item->{text}).' '. &Apache::loncommon::help_open_topic($item->{'help'}).'
- + '; $rowtotal ++; if (($action eq 'autoupdate') || ($action eq 'usercreation') || ($action eq 'selfcreation') || ($action eq 'usermodification') || ($action eq 'defaults') || ($action eq 'coursedefaults') || ($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'directorysrch') || - ($action eq 'helpsettings') || ($action eq 'contacts')) { + ($action eq 'helpsettings') || ($action eq 'contacts') || ($action eq 'wafproxy')) { $output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal); } elsif ($action eq 'passwords') { $output .= $item->{'print'}->('top',$dom,$confname,$settings,\$rowtotal); @@ -747,7 +796,7 @@ sub print_config_box { } elsif ($action eq 'scantron') { $output .= $item->{'print'}->($r,'top',$dom,$confname,$settings,\$rowtotal); } elsif ($action eq 'login') { - if ($numheaders == 4) { + if ($numheaders == 5) { $colspan = ' colspan="2"'; $output .= &print_login('service',$dom,$confname,$phase,$settings,\$rowtotal); } else { @@ -795,8 +844,13 @@ sub print_config_box { '."\n"; if ($action eq 'coursecategories') { $output .= &print_coursecategories('bottom',$dom,$item,$settings,\$rowtotal); - } elsif ($action eq 'passwords') { - $output .= $item->{'print'}->('lower',$dom,$confname,$settings,\$rowtotal).' + } elsif (($action eq 'contacts') || ($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". - $item->{'print'}->('bottom',$dom,$confname,$settings,\$rowtotal).' + + '."\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'}).'
'.&mt($item->{'header'}->[3]->{'col1'}).''.&mt($item->{'header'}->[3]->{'col2'}).'
@@ -847,7 +906,7 @@ sub print_config_box { '; - if ($numheaders == 4) { + if ($numheaders == 5) { $output .= ' @@ -859,7 +918,27 @@ sub print_config_box { '; } $rowtotal ++; - $output .= &print_login('headtag',$dom,$confname,$phase,$settings,\$rowtotal); + $output .= &print_login('headtag',$dom,$confname,$phase,$settings,\$rowtotal).' +
'.&mt($item->{'header'}->[3]->{'col1'}).' '.&mt($item->{'header'}->[3]->{'col2'}).'
+ + + + + + '; + if ($numheaders == 5) { + $output .= ' + + + '; + } else { + $output .= ' + + + '; + } + $rowtotal ++; + $output .= &print_login('saml',$dom,$confname,$phase,$settings,\$rowtotal); } elsif ($action eq 'requestcourses') { $output .= &print_requestmail($dom,$action,$settings,\$rowtotal); $rowtotal ++; @@ -978,7 +1057,8 @@ sub print_config_box { if ($action eq 'quotas') { $output .= &print_quotas($dom,$settings,\$rowtotal,$action); } elsif (($action eq 'autoenroll') || ($action eq 'autocreate') || - ($action eq 'serverstatuses') || ($action eq 'loadbalancing')) { + ($action eq 'serverstatuses') || ($action eq 'loadbalancing') || + ($action eq 'ipaccess')) { $output .= $item->{'print'}->($dom,$settings,\$rowtotal); } } @@ -992,8 +1072,12 @@ sub print_config_box { sub print_login { my ($caller,$dom,$confname,$phase,$settings,$rowtotal) = @_; - my ($css_class,$datatable); + my ($css_class,$datatable,$switchserver,%lt); my %choices = &login_choices(); + if (($caller eq 'help') || ($caller eq 'headtag') || ($caller eq 'saml')) { + %lt = &login_file_options(); + $switchserver = &check_switchserver($dom,$confname); + } if ($caller eq 'service') { my %servers = &Apache::lonnet::internet_dom_servers($dom); @@ -1081,6 +1165,7 @@ sub print_login { } } my @images = ('img','logo','domlogo','login'); + my @alttext = ('img','logo','domlogo'); my @logintext = ('textcol','bgcol'); my @bgs = ('pgbg','mainbg','sidebg'); my @links = ('link','alink','vlink'); @@ -1122,6 +1207,13 @@ sub print_login { $designs{'showlogo'}{$item} = $settings->{'showlogo'}{$item}; } } + foreach my $item (@alttext) { + if (ref($settings->{'alttext'}) eq 'HASH') { + if ($settings->{'alttext'}->{$item} ne '') { + $designs{'alttext'}{$item} = $settings->{'alttext'}{$item}; + } + } + } foreach my $item (@logintext) { if ($settings->{$item} ne '') { $designs{'logintext'}{$item} = $settings->{$item}; @@ -1188,18 +1280,10 @@ sub print_login { $datatable .= &display_color_options($dom,$confname,$phase,'login',$itemcount,\%choices,\%is_custom,\%defaults,\%designs,\@images,\@bgs,\@links,\%alt_text,$rowtotal,\@logintext); $datatable .= '
'.&mt($item->{'header'}->[4]->{'col1'}).''.&mt($item->{'header'}->[4]->{'col2'}).'
'.&mt($item->{'header'}->[3]->{'col1'}).''.&mt($item->{'header'}->[3]->{'col2'}).'
'; } elsif ($caller eq 'help') { - my ($defaulturl,$defaulttype,%url,%type,%lt,%langchoices); - my $switchserver = &check_switchserver($dom,$confname); + my ($defaulturl,$defaulttype,%url,%type,%langchoices); my $itemcount = 1; $defaulturl = '/adm/loginproblems.html'; $defaulttype = 'default'; - %lt = &Apache::lonlocal::texthash ( - del => 'Delete?', - rep => 'Replace:', - upl => 'Upload:', - default => 'Default', - custom => 'Custom', - ); %langchoices = &Apache::lonlocal::texthash(&get_languages_hash()); my @currlangs; if (ref($settings) eq 'HASH') { @@ -1296,14 +1380,6 @@ sub print_login { } } } - my %lt = &Apache::lonlocal::texthash( - del => 'Delete?', - rep => 'Replace:', - upl => 'Upload:', - curr => 'View contents', - none => 'None', - ); - my $switchserver = &check_switchserver($dom,$confname); foreach my $lonhost (sort(keys(%domservers))) { my $exempt = &check_exempt_addresses($currexempt{$lonhost}); $datatable .= ''.$domservers{$lonhost}.''; @@ -1327,6 +1403,88 @@ sub print_login { $datatable .= ''; } $datatable .= ''; + } elsif ($caller eq 'saml') { + my %domservers = &Apache::lonnet::get_servers($dom); + $datatable .= ''. + ''. + ''. + ''."\n"; + my (%saml,%samltext,%samlimg,%samlalt,%samlurl,%samltitle,%samlnotsso,%styleon,%styleoff); + foreach my $lonhost (keys(%domservers)) { + $samlurl{$lonhost} = '/adm/sso'; + $styleon{$lonhost} = 'display:none'; + $styleoff{$lonhost} = ''; + } + if (ref($settings->{'saml'}) eq 'HASH') { + foreach my $lonhost (keys(%{$settings->{'saml'}})) { + if (ref($settings->{'saml'}{$lonhost}) eq 'HASH') { + $saml{$lonhost} = 1; + $samltext{$lonhost} = $settings->{'saml'}{$lonhost}{'text'}; + $samlimg{$lonhost} = $settings->{'saml'}{$lonhost}{'img'}; + $samlalt{$lonhost} = $settings->{'saml'}{$lonhost}{'alt'}; + $samlurl{$lonhost} = $settings->{'saml'}{$lonhost}{'url'}; + $samltitle{$lonhost} = $settings->{'saml'}{$lonhost}{'title'}; + $samlnotsso{$lonhost} = $settings->{'saml'}{$lonhost}{'notsso'}; + $styleon{$lonhost} = ''; + $styleoff{$lonhost} = 'display:none'; + } else { + $styleon{$lonhost} = 'display:none'; + $styleoff{$lonhost} = ''; + } + } + } + my $itemcount = 1; + foreach my $lonhost (sort(keys(%domservers))) { + my $samlon = ' '; + my $samloff = ' checked="checked" '; + if ($saml{$lonhost}) { + $samlon = $samloff; + $samloff = ' '; + } + my $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''. + ''. + ''; + $itemcount ++; + } + $datatable .= '
'.$choices{'hostid'}.''.$choices{'samllanding'}.''.$choices{'samloptions'}.'
'.$domservers{$lonhost}.''.(' 'x2). + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + '
'.&mt('SSO').''. + ''.&mt('Non-SSO').'
'.&mt('Text').''.&mt('Image').''.&mt('Alt Text').''.&mt('URL').''.&mt('Tool Tip').''.&mt('Text').'
'; + if ($samlimg{$lonhost}) { + $datatable .= '
'. + ' '.$lt{'rep'}.''; + } else { + $datatable .= $lt{'upl'}; + } + $datatable .='
'; + if ($switchserver) { + $datatable .= &mt('Upload to library server: [_1]',$switchserver); + } else { + $datatable .= ''; + } + $datatable .= '
 
'; } return $datatable; } @@ -1363,10 +1521,205 @@ sub login_choices { headtag => "Custom markup", action => "Action", current => "Current", + samllanding => "Dual login?", + samloptions => "Options", + alttext => "Alt text", ); return %choices; } +sub login_file_options { + return &Apache::lonlocal::texthash( + del => 'Delete?', + rep => 'Replace:', + upl => 'Upload:', + curr => 'View contents', + default => 'Default', + custom => 'Custom', + none => 'None', + ); +} + +sub print_ipaccess { + my ($dom,$settings,$rowtotal) = @_; + my $css_class; + my $itemcount = 0; + my $datatable; + my %ordered; + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + if ($num eq '') { + $num = scalar(keys(%{$settings})); + } + $ordered{$num} = $item; + } + } + } + my $maxnum = scalar(keys(%ordered)); + 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 ($name,$ipranges,%commblocks,%courses); + if (ref($settings->{$item}) eq 'HASH') { + $name = $settings->{$item}->{'name'}; + $ipranges = $settings->{$item}->{'ip'}; + if (ref($settings->{$item}->{'commblocks'}) eq 'HASH') { + %commblocks = %{$settings->{$item}->{'commblocks'}}; + } + if (ref($settings->{$item}->{'courses'}) eq 'HASH') { + %courses = %{$settings->{$item}->{'courses'}}; + } + } + my $chgstr = ' onchange="javascript:reorderIPaccess(this.form,'."'ipaccess_pos_".$item."'".');"'; + $datatable .= '' + .''.(' 'x2). + ''. + ''. + &ipaccess_options($i,$itemcount,$dom,$name,$ipranges,\%commblocks,\%courses). + ''; + $itemcount ++; + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $chgstr = ' onchange="javascript:reorderIPaccess(this.form,'."'ipaccess_pos_add'".');"'; + $datatable .= ''."\n". + ''."\n". + ' '."\n". + ''.&mt('Add').''."\n". + ''. + &ipaccess_options('add',$itemcount,$dom). + ''."\n". + ''."\n"; + $$rowtotal ++; + return $datatable; +} + +sub ipaccess_options { + my ($num,$itemcount,$dom,$name,$ipranges,$blocksref,$coursesref) = @_; + my (%currblocks,%currcourses,$output); + if (ref($blocksref) eq 'HASH') { + %currblocks = %{$blocksref}; + } + if (ref($coursesref) eq 'HASH') { + %currcourses = %{$coursesref}; + } + $output = '
'.&mt('Location(s)').''. + ''.&mt('Name').': '. + ''. + '
'. + '
'.&mt('IP Range(s)').''. + &mt('Format for each IP range').': '.&mt('A.B.C.D/N or A.B.C.D-E.F.G.H').'
'. + &mt('Range(s) will be stored as IP netblock(s) in CIDR notation (comma separated)').'
'. + '
'. + '
'.&mt('Functionality Blocked?').''. + &blocker_checkboxes($num,$blocksref).'
'. + '
'.&mt('Courses/Communities allowed').''. + ''; + foreach my $cid (sort(keys(%currcourses))) { + my %courseinfo = &Apache::lonnet::coursedescription($cid,{'one_time' => 1}); + $output .= ''; + } + $output .= '
'. + ''. + ' ('.$cid.')
'.&mt('Add').': '. + ''. + &Apache::loncommon::selectcourse_link('display','ipaccess_cnum_'.$num,'ipaccess_cdom_'.$num,'ipaccess_cdesc_'.$num,$dom,undef,'Course/Community'). + ''. + ''. + '
'."\n". + '
'; + return $output; +} + +sub blocker_checkboxes { + my ($num,$blocks) = @_; + my ($typeorder,$types) = &commblocktype_text(); + my $numinrow = 6; + my $output = ''; + for (my $i=0; $i<@{$typeorder}; $i++) { + my $block = $typeorder->[$i]; + my $blockstatus; + if (ref($blocks) eq 'HASH') { + if ($blocks->{$block} eq 'on') { + $blockstatus = 'checked="checked"'; + } + } + my $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $output .= ''; + } + $output .= ''; + } + if ($i == scalar(@{$typeorder})-1) { + my $colsleft = $numinrow-$rem; + if ($colsleft > 1) { + $output .= ''; + } + $output .= '
'; + } else { + $output .= ''; + } + } else { + $output .= ''; + } + my $item = 'ipaccess_block_'.$num; + if ($blockstatus) { + $blockstatus = ' '.$blockstatus; + } + $output .= ''."\n". + '
'; + return $output; +} + +sub commblocktype_text { + my %types = &Apache::lonlocal::texthash( + 'com' => 'Messaging', + 'chat' => 'Chat Room', + 'boards' => 'Discussion', + 'port' => 'Portfolio', + 'groups' => 'Groups', + 'blogs' => 'Blogs', + 'about' => 'User Information', + 'printout' => 'Printouts', + 'passwd' => 'Change Password', + 'grades' => 'Gradebook', + 'search' => 'Course search', + 'wishlist' => 'Stored links', + 'annotate' => 'Annotations', + ); + my $typeorder = ['com','chat','boards','port','groups','blogs','about','wishlist','printout','grades','search','annotate','passwd']; + return ($typeorder,\%types); +} + sub print_rolecolors { my ($phase,$role,$dom,$confname,$settings,$rowtotal) = @_; my %choices = &color_font_choices(); @@ -1516,7 +1869,7 @@ sub display_color_options { $css_class = $itemcount%2?' class="LC_odd_row"':''; $datatable .= ''. ''.$choices->{$img}; - my ($imgfile,$img_import,$login_hdr_pick,$logincolors); + my ($imgfile,$img_import,$login_hdr_pick,$logincolors,$alttext); if ($role eq 'login') { if ($img eq 'login') { $login_hdr_pick = @@ -1524,8 +1877,13 @@ sub display_color_options { $logincolors = &login_text_colors($img,$role,$logintext,$phase,$choices, $designs,$defaults); - } elsif ($img ne 'domlogo') { - $datatable.= &logo_display_options($img,$defaults,$designs); + } else + if ($img ne 'domlogo') { + $datatable.= &logo_display_options($img,$defaults,$designs); + } + if (ref($designs->{'alttext'}) eq 'HASH') { + $alttext = $designs->{'alttext'}{$img}; + } } } $datatable .= ''; @@ -1617,6 +1975,11 @@ sub display_color_options { $datatable .=' '; } } + if (($role eq 'login') && ($img ne 'login')) { + $datatable .= (' ' x2).' '; + } $datatable .= ''; } $itemcount ++; @@ -2339,8 +2702,7 @@ sub print_textbookcourses { (' 'x2). ''.&mt('Thumbnail:'); if ($image) { - $datatable .= ''. - $imgsrc. + $datatable .= $imgsrc. ' '. ' '.&mt('Replace:').' '; @@ -2494,10 +2856,310 @@ $jstext{'templates'}; ENDSCRIPT } +sub wafproxy_javascript { + my ($dom) = @_; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + +sub autoupdate_javascript { + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + +sub autoenroll_javascript { + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + +sub saml_javascript { + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + +sub ipaccess_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})); + } + my @jsarray = (); + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@jsarray,$ordered{$item}); + } + my $jstext = ' var ipaccess = Array('."'".join("','",@jsarray)."'".');'."\n"; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + sub print_autoenroll { my ($dom,$settings,$rowtotal) = @_; my $autorun = &Apache::lonnet::auto_run(undef,$dom), - my ($defdom,$runon,$runoff,$coownerson,$coownersoff,$failsafe); + my ($defdom,$runon,$runoff,$coownerson,$coownersoff, + $failsafe,$autofailsafe,$failsafesty,%failsafechecked); + $failsafesty = 'none'; + %failsafechecked = ( + off => ' checked="checked"', + ); if (ref($settings) eq 'HASH') { if (exists($settings->{'run'})) { if ($settings->{'run'} eq '0') { @@ -2531,8 +3193,24 @@ sub print_autoenroll { if (exists($settings->{'sender_domain'})) { $defdom = $settings->{'sender_domain'}; } - if (exists($settings->{'autofailsafe'})) { - $failsafe = $settings->{'autofailsafe'}; + if (exists($settings->{'failsafe'})) { + $failsafe = $settings->{'failsafe'}; + if ($failsafe eq 'zero') { + $failsafechecked{'zero'} = ' checked="checked"'; + $failsafechecked{'off'} = ''; + $failsafesty = 'inline-block'; + } elsif ($failsafe eq 'any') { + $failsafechecked{'any'} = ' checked="checked"'; + $failsafechecked{'off'} = ''; + } + $autofailsafe = $settings->{'autofailsafe'}; + } elsif (exists($settings->{'autofailsafe'})) { + $autofailsafe = $settings->{'autofailsafe'}; + if ($autofailsafe ne '') { + $failsafechecked{'zero'} = ' checked="checked"'; + $failsafe = 'zero'; + $failsafechecked{'off'} = ''; + } } } else { if ($autorun) { @@ -2571,51 +3249,84 @@ sub print_autoenroll { $coownersoff.' value="0" />'.&mt('No').''. ''. ''.&mt('Failsafe for no drops when institutional data missing').''. - ''. - ''; + ''. + '    '. + '
'. + ''. + '
'. + ''. + &mt('Threshold for number of students in section to drop: [_1]', + ''). + '
'; $$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; @@ -2855,7 +3566,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) { @@ -2870,6 +3581,12 @@ sub print_contacts { 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) { @@ -2882,7 +3599,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') { @@ -2968,7 +3685,54 @@ sub print_contacts { $to{$item}.'" />'; $rownum ++; } - } else { + } elsif ($position eq 'bottom') { + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''.&mt('Extra helpdesk form fields:').'
'. + &mt('(e-mail, subject, and description always shown)'). + ''; + if ((ref($fields) eq 'ARRAY') && (ref($fieldtitles) eq 'HASH') && + (ref($fieldoptions) eq 'HASH') && (ref($possoptions) eq 'HASH')) { + $datatable .= ''; + foreach my $field (@{$fields}) { + $datatable .= ''. + ''; + } + $datatable .= '
'.&mt('Field').''.&mt('Status').'
'.$fieldtitles->{$field}; + if (($field eq 'screenshot') || ($field eq 'cc')) { + $datatable .= ' '.&mt('(logged-in users)'); + } + $datatable .=''; + my $clickaction; + if ($field eq 'screenshot') { + $clickaction = ' onclick="screenshotSize(this);"'; + } + if (ref($possoptions->{$field}) eq 'ARRAY') { + foreach my $option (@{$possoptions->{$field}}) { + my $checked; + if ($currfield{$field} eq $option) { + $checked = ' checked="checked"'; + } + $datatable .= ''.(' 'x2); + } + } + if ($field eq 'screenshot') { + my $display; + if ($currfield{$field} eq 'no') { + $display = ' style="display:none"'; + } + $datatable .= '
'.&mt('Maximum size for upload (MB)').''. + ''; + } + $datatable .= '
'; + } + $datatable .= ''."\n"; + $rownum ++; + } + unless (($position eq 'top') || ($position eq 'lower')) { foreach my $type (@mailings) { $css_class = $rownum%2?' class="LC_odd_row"':''; $datatable .= ''. @@ -3015,69 +3779,242 @@ 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 'bottom') { + } 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 .= ''. - ''.&mt('Extra helpdesk form fields:').'
'. - &mt('(e-mail, subject, and description always shown)'). - ''; - if ((ref($fields) eq 'ARRAY') && (ref($fieldtitles) eq 'HASH') && - (ref($fieldoptions) eq 'HASH') && (ref($possoptions) eq 'HASH')) { - $datatable .= ''; - foreach my $field (@{$fields}) { - $datatable .= ''; + $rownum ++; + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''; + $rownum ++; + } elsif ($position eq 'bottom') { + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my (@posstypes,%usertypeshash); + if (ref($types) eq 'ARRAY') { + @posstypes = @{$types}; + } + if (@posstypes) { + if (ref($usertypes) eq 'HASH') { + %usertypeshash = %{$usertypes}; + } + my @overridden; + my $numinrow = 4; + if (ref($settings) eq 'HASH') { + if (ref($settings->{'overrides'}) eq 'HASH') { + foreach my $key (sort(keys(%{$settings->{'overrides'}}))) { + if (ref($settings->{'overrides'}{$key}) eq 'HASH') { + push(@overridden,$key); + foreach my $item (@contacts) { + if ($settings->{'overrides'}{$key}{$item}) { + $checked{'override_'.$key}{$item} = ' checked="checked" '; + } + } + $otheremails{'override_'.$key} = $settings->{'overrides'}{$key}{'others'}; + $bccemails{'override_'.$key} = $settings->{'overrides'}{$key}{'bcc'}; + $includeloc{'override_'.$key} = ''; + $includestr{'override_'.$key} = ''; + if ($settings->{'overrides'}{$key}{'include'} ne '') { + ($includeloc{'override_'.$key},$includestr{'override_'.$key}) = + split(/:/,$settings->{'overrides'}{$key}{'include'},2); + $includestr{'override_'.$key} = &unescape($includestr{'override_'.$key}); + } } - $datatable .= ''.(' 'x2); } } - if ($field eq 'screenshot') { - my $display; - if ($currfield{$field} eq 'no') { - $display = ' style="display:none"'; - } - $datatable .= ''. - ''; } - $datatable .= '
'.&mt('Field').''.&mt('Status').'
'.$fieldtitles->{$field}; - if (($field eq 'screenshot') || ($field eq 'cc')) { - $datatable .= ' '.&mt('(logged-in users)'); - } - $datatable .=''; - my $clickaction; - if ($field eq 'screenshot') { - $clickaction = ' onclick="screenshotSize(this);"'; + ''. + ''.$titles->{'errorweights'}. + ''; + foreach my $type ('E','W','N','U') { + $datatable .= ''; + } + $datatable .= '
'.$names->{$type}.'
'. + '
'. + $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 .= ''; } - if (ref($possoptions->{$field}) eq 'ARRAY') { - foreach my $option (@{$possoptions->{$field}}) { - my $checked; - if ($currfield{$field} eq $option) { - $checked = ' checked="checked"'; + $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 .= '
'. + ''. + '  
'.&mt('Maximum size for upload (MB)').''. - ''; + } + my $customclass = 'LC_helpdesk_override'; + my $optionsprefix = 'LC_options_helpdesk_'; + + my $onclicktypes = "toggleHelpdeskRow(this.form,'overrides','$customclass','$optionsprefix');"; + + $datatable .= &insttypes_row($settings,$types,$usertypes,$dom, + $numinrow,$othertitle,'overrides', + \$rownum,$onclicktypes,$customclass); + $rownum ++; + $usertypeshash{'default'} = $othertitle; + foreach my $status (@posstypes) { + my $css_class; + if ($rownum%2) { + $css_class = 'LC_odd_row '; + } + $css_class .= $customclass; + my $rowid = $optionsprefix.$status; + my $hidden = 1; + my $currstyle = 'display:none'; + if (grep(/^\Q$status\E$/,@overridden)) { + $currstyle = 'display:table-row'; + $hidden = 0; + } + my $key = 'override_'.$status; + $datatable .= &overridden_helpdesk($checked{$key},$otheremails{$key},$bccemails{$key}, + $includeloc{$key},$includestr{$key},$status,$rowid, + $usertypeshash{$status},$css_class,$currstyle, + \@contacts,$short_titles); + unless ($hidden) { + $rownum ++; } - $datatable .= '
'; } - $datatable .= ''."\n"; - $rownum ++; } $$rowtotal += $rownum; 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) = @_; + my $class = 'LC_left_item'; + if ($css_class) { + $css_class = ' class="'.$css_class.'"'; + } + if ($rowid) { + $rowid = ' id="'.$rowid.'"'; + } + if ($rowstyle) { + $rowstyle = ' style="'.$rowstyle.'"'; + } + my ($output,$description); + $description = &mt('Helpdesk requests from: [_1] in this domain (overrides default)',"$typetitle"); + $output = ''. + "$description\n". + ''. + '
'.&mt('E-mail recipient(s)').''. + ''; + if (ref($contacts) eq 'ARRAY') { + foreach my $item (@{$contacts}) { + my $check; + if (ref($checked) eq 'HASH') { + $check = $checked->{$item}; + } + my $title; + if (ref($short_titles) eq 'HASH') { + $title = $short_titles->{$item}; + } + $output .= ' '; + } + } + $output .= '
'.&mt('Others').':  '. + ''; + my %locchecked; + foreach my $loc ('s','b') { + if ($includeloc eq $loc) { + $locchecked{$loc} = ' checked="checked"'; + last; + } + } + $output .= '
'.&mt('Bcc:').(' 'x6). + '
'. + '
'.&mt('Optional added text').''. + &mt('Text automatically added to e-mail:').' '. + '
'. + ''.&mt('Location:').' '. + ''. + (' 'x2). + ''. + '
'. + ''."\n"; + return $output; +} + sub contacts_javascript { return <<"ENDSCRIPT"; @@ -3095,6 +4032,36 @@ function screenshotSize(field) { return; } +function toggleHelpdeskRow(form,checkbox,target,prefix,docount) { + if (form.elements[checkbox].length != undefined) { + var count = 0; + if (docount) { + for (var i=0; i @@ -3488,7 +4455,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')); @@ -3528,15 +4495,21 @@ sub radiobutton_prefs { } else { $datatable .= ''; } - $datatable .= - ''. + $datatable .= ''; + if ($firstval eq 'no') { + $datatable .= + ' '; + } else { + $datatable .= ' '. - ''.$additional. - ''. - ''; + $checkedoff{$item}.' value="0"'.$onclick.' />'.&mt('No').''; + } + $datatable .= ''.$additional.''; $itemcount ++; } return ($datatable,$itemcount); @@ -3552,6 +4525,7 @@ sub print_coursedefaults { coursecredits => 'Credits can be specified for courses', uselcmath => 'Math preview uses LON-CAPA previewer (javascript) in place of DragMath (Java)', usejsme => 'Molecule editor uses JSME (HTML5) in place of JME (Java)', + inline_chem => 'Use inline previewer for chemical reaction response in place of pop-up', texengine => 'Default method to display mathematics', postsubmit => 'Disable submit button/keypress following student submission', canclone => "People who may clone a course (besides course's owner and coordinators)", @@ -3567,9 +4541,10 @@ sub print_coursedefaults { %defaultchecked = ( 'uselcmath' => 'on', 'usejsme' => 'on', + 'inline_chem' => 'on', 'canclone' => 'none', ); - @toggles = ('uselcmath','usejsme'); + @toggles = ('uselcmath','usejsme','inline_chem'); my $deftex = $Apache::lonnet::deftex; if (ref($settings) eq 'HASH') { if ($settings->{'texengine'}) { @@ -3608,7 +4583,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 ...', @@ -4110,7 +5085,6 @@ sub print_passwords { $casesens{$item} = 1; $postlink{$item} = ['username','email']; } - } } $casesens{'default'} = 1; $postlink{'default'} = ['username','email']; @@ -4141,7 +5115,7 @@ sub print_passwords { $datatable .= ''. - '   '; + '   '; } } my $checkedcase; @@ -4245,7 +5219,7 @@ sub print_passwords { &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 file'),600,500, + my $link = &Apache::loncommon::modal_link($customurl,&mt('custom text'),600,500, undef,undef,undef,undef,'background-color:#ffffff'); $datatable .= ' '.$link. ''. "\n"; + if ($other_insts) { + $datatable .= '
'. + ''."\n". + ''. + "\n"; + } my (%current,%canselect); my @choices = &possible_newspares($server,$spareid->{$server},$serverhomes,$altids); @@ -5294,13 +6593,17 @@ sub contact_titles { 'adminemail' => 'Default Server Admin E-mail address', 'errormail' => 'Error reports to be e-mailed to', 'packagesmail' => 'Package update alerts to be e-mailed to', - 'helpdeskmail' => "Helpdesk requests for this domain's users", - 'otherdomsmail' => 'Helpdesk requests for other (unconfigured) domains', + 'helpdeskmail' => "Helpdesk requests from all users in this domain", + 'otherdomsmail' => 'Helpdesk requests from users in other (unconfigured) domains', 'lonstatusmail' => 'E-mail from nightly status check (warnings/errors)', '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', @@ -6343,7 +7646,7 @@ sub print_defaults { $datatable .= ' '.&mt('Internal ID:').' '.$item.' '. ''. &mt('delete').''. - ''.&mt('Name displayed:'). + ''.&mt('Name displayed').':'. ''. ''; } @@ -6363,7 +7666,7 @@ sub print_defaults { ''. ' '.&mt('(new)'). ''. - &mt('Name displayed:'). + &mt('Name displayed').':'. ''. ''."\n"; $rownum ++; @@ -7212,8 +8515,8 @@ function warnIntPass(field) { alert('$intalert{passnum}'); } } + field.value = ''; } - field.value = ''; } } } @@ -7333,7 +8636,7 @@ 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', ); @@ -7366,7 +8669,7 @@ sub initialize_categories { .'' .'' .' ' - .&mt('Add category').'>'.&mt('Name:') + .&mt('Add category').''.&mt('Name:') .' ' .''; return $datatable; @@ -7618,6 +8921,7 @@ sub insttypes_row { statustocreate => 'Institutional affiliation(s) able to create own account (login/SSO)', lockablenames => 'User preference to lock name', selfassign => 'Self-reportable affiliations', + overrides => "Override domain's helpdesk settings based on requester's affiliation", ); my $showdom; if ($context eq 'cansearch') { @@ -7664,6 +8968,10 @@ sub insttypes_row { if (grep(/^\Q$types->[$i]\E$/,@{$settings->{$context}})) { $check = ' checked="checked" '; } + } elsif (ref($settings->{$context}) eq 'HASH') { + if (ref($settings->{$context}->{$types->[$i]}) eq 'HASH') { + $check = ' checked="checked" '; + } } elsif ($context eq 'statustocreate') { $check = ' checked="checked" '; } @@ -7678,29 +8986,38 @@ sub insttypes_row { $rem = @{$types}%($numinrow); } my $colsleft = $numinrow - $rem; - if ($rem == 0) { - $output .= ''; - } - if ($colsleft > 1) { - $output .= ''; + if ($context eq 'overrides') { + if ($colsleft > 1) { + $output .= ''; + } else { + $output .= ''; + } + $output .= ' '; } else { - $output .= ''; - } - my $defcheck = ' '; - if (ref($settings) eq 'HASH') { - if (ref($settings->{$context}) eq 'ARRAY') { - if (grep(/^default$/,@{$settings->{$context}})) { + if ($rem == 0) { + $output .= ''; + } + if ($colsleft > 1) { + $output .= ''; + } else { + $output .= ''; + } + my $defcheck = ' '; + if (ref($settings) eq 'HASH') { + if (ref($settings->{$context}) eq 'ARRAY') { + if (grep(/^default$/,@{$settings->{$context}})) { + $defcheck = ' checked="checked" '; + } + } elsif ($context eq 'statustocreate') { $defcheck = ' checked="checked" '; } - } elsif ($context eq 'statustocreate') { - $defcheck = ' checked="checked" '; } + $output .= ''; } - $output .= ''. - ''; + $output .= ''; return $output; } @@ -7777,12 +9094,14 @@ sub usertype_update_row { sub modify_login { my ($r,$dom,$confname,$lastactref,%domconfig) = @_; my ($resulttext,$errors,$colchgtext,%changes,%colchanges,%newfile,%newurl, - %curr_loginvia,%loginhash,@currlangs,@newlangs,$addedfile,%title,@offon); + %curr_loginvia,%loginhash,@currlangs,@newlangs,$addedfile,%title,@offon, + %currsaml,%saml,%samltext,%samlimg,%samlalt,%samlurl,%samltitle,%samlnotsso); %title = ( coursecatalog => 'Display course catalog', adminmail => 'Display administrator E-mail address', helpdesk => 'Display "Contact Helpdesk" link', newuser => 'Link for visitors to create a user account', - loginheader => 'Log-in box header'); + loginheader => 'Log-in box header', + saml => 'Dual SSO and non-SSO login'); @offon = ('off','on'); if (ref($domconfig{login}) eq 'HASH') { if (ref($domconfig{login}{loginvia}) eq 'HASH') { @@ -7790,6 +9109,20 @@ sub modify_login { $curr_loginvia{$lonhost} = $domconfig{login}{loginvia}{$lonhost}; } } + if (ref($domconfig{login}{'saml'}) eq 'HASH') { + foreach my $lonhost (keys(%{$domconfig{login}{'saml'}})) { + if (ref($domconfig{login}{'saml'}{$lonhost}) eq 'HASH') { + $currsaml{$lonhost} = $domconfig{login}{'saml'}{$lonhost}; + $saml{$lonhost} = 1; + $samltext{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'text'}; + $samlurl{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'url'}; + $samlalt{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'alt'}; + $samlimg{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'img'}; + $samltitle{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'title'}; + $samlnotsso{$lonhost} = $domconfig{login}{'saml'}{$lonhost}{'notsso'}; + } + } + } } ($errors,%colchanges) = &modify_colors($r,$dom,$confname,['login'], \%domconfig,\%loginhash); @@ -8036,6 +9369,89 @@ sub modify_login { $errors .= '
  • '.$error.'
  • '; } } + my @delsamlimg = &Apache::loncommon::get_env_multiple('form.saml_img_del'); + my @newsamlimgs; + foreach my $lonhost (keys(%domservers)) { + if ($env{'form.saml_'.$lonhost}) { + if ($env{'form.saml_img_'.$lonhost.'.filename'}) { + push(@newsamlimgs,$lonhost); + } + foreach my $item ('text','alt','url','title','notsso') { + $env{'form.saml_'.$item.'_'.$lonhost} =~ s/^\s+|\s+$//g; + } + if ($saml{$lonhost}) { + if (grep(/^\Q$lonhost\E$/,@delsamlimg)) { +#FIXME Need to obsolete published image + delete($currsaml{$lonhost}{'img'}); + $changes{'saml'}{$lonhost} = 1; + } + if ($env{'form.saml_alt_'.$lonhost} ne $samlalt{$lonhost}) { + $changes{'saml'}{$lonhost} = 1; + } + if ($env{'form.saml_text_'.$lonhost} ne $samltext{$lonhost}) { + $changes{'saml'}{$lonhost} = 1; + } + if ($env{'form.saml_url_'.$lonhost} ne $samlurl{$lonhost}) { + $changes{'saml'}{$lonhost} = 1; + } + if ($env{'form.saml_title_'.$lonhost} ne $samltitle{$lonhost}) { + $changes{'saml'}{$lonhost} = 1; + } + if ($env{'form.saml_notsso_'.$lonhost} ne $samlnotsso{$lonhost}) { + $changes{'saml'}{$lonhost} = 1; + } + } else { + $changes{'saml'}{$lonhost} = 1; + } + foreach my $item ('text','alt','url','title','notsso') { + $currsaml{$lonhost}{$item} = $env{'form.saml_'.$item.'_'.$lonhost}; + } + } else { + if ($saml{$lonhost}) { + $changes{'saml'}{$lonhost} = 1; + delete($currsaml{$lonhost}); + } + } + } + foreach my $posshost (keys(%currsaml)) { + unless (exists($domservers{$posshost})) { + delete($currsaml{$posshost}); + } + } + %{$loginhash{'login'}{'saml'}} = %currsaml; + if (@newsamlimgs) { + my $error; + my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm); + if ($configuserok eq 'ok') { + if ($switchserver) { + $error = &mt("Upload of SSO Button Image is not permitted to this server: [_1].",$switchserver); + } elsif ($author_ok eq 'ok') { + foreach my $lonhost (@newsamlimgs) { + my $formelem = 'saml_img_'.$lonhost; + my ($result,$imgurl) = &publishlogo($r,'upload',$formelem,$dom,$confname, + "login/saml/$lonhost",'','', + $env{'form.saml_img_'.$lonhost.'.filename'}); + if ($result eq 'ok') { + $currsaml{$lonhost}{'img'} = $imgurl; + $loginhash{'login'}{'saml'}{$lonhost}{'img'} = $imgurl; + $changes{'saml'}{$lonhost} = 1; + } else { + my $puberror = &mt("Upload of SSO button image failed for [_1] because an error occurred publishing the file in RES space. Error was: [_2].", + $lonhost,$result); + $errors .= '
  • '.$puberror.'
  • '; + } + } + } else { + $error = &mt("Upload of SSO button image file(s) failed because an author role could not be assigned to a Domain Configuration user ([_1]) in domain: [_2]. Error was: [_3].",$confname,$dom,$author_ok); + } + } else { + $error = &mt("Upload of SSO button image file(s) failed because a Domain Configuration user ([_1]) could not be created in domain: [_2]. Error was: [_3].",$confname,$dom,$configuserok); + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } &process_captcha('login',\%changes,$loginhash{'login'},$domconfig{'login'}); my $defaulthelpfile = '/adm/loginproblems.html'; @@ -8076,6 +9492,31 @@ sub modify_login { } if (keys(%changes) > 0 || $colchgtext) { &Apache::loncommon::devalidate_domconfig_cache($dom); + if (exists($changes{'saml'})) { + my $hostid_in_use; + my @hosts = &Apache::lonnet::current_machine_ids(); + if (@hosts > 1) { + foreach my $hostid (@hosts) { + if (&Apache::lonnet::host_domain($hostid) eq $dom) { + $hostid_in_use = $hostid; + last; + } + } + } else { + $hostid_in_use = $r->dir_config('lonHostID'); + } + if (($hostid_in_use) && + (&Apache::lonnet::host_domain($hostid_in_use) eq $dom)) { + &Apache::lonnet::devalidate_cache_new('samllanding',$hostid_in_use); + } + if (ref($lastactref) eq 'HASH') { + if (ref($changes{'saml'}) eq 'HASH') { + my %updates; + map { $updates{$_} = 1; } keys(%{$changes{'saml'}}); + $lastactref->{'samllanding'} = \%updates; + } + } + } if (ref($lastactref) eq 'HASH') { $lastactref->{'domainconfig'} = 1; } @@ -8155,6 +9596,38 @@ sub modify_login { } } } + } elsif ($item eq 'saml') { + if (ref($changes{$item}) eq 'HASH') { + my %notlt = ( + text => 'Text for log-in by SSO', + img => 'SSO button image', + alt => 'Alt text for button image', + url => 'SSO URL', + title => 'Tooltip for SSO link', + notsso => 'Text for non-SSO log-in', + ); + foreach my $lonhost (sort(keys(%{$changes{$item}}))) { + if (ref($currsaml{$lonhost}) eq 'HASH') { + $resulttext .= '
  • '.&mt("$title{$item} in use for [_1]","$lonhost"). + '
      '; + foreach my $key ('text','img','alt','url','title','notsso') { + if ($currsaml{$lonhost}{$key} eq '') { + $resulttext .= '
    • '.&mt("$notlt{$key} not in use").'
    • '; + } else { + my $value = "'$currsaml{$lonhost}{$key}'"; + if ($key eq 'img') { + $value = ''; + } + $resulttext .= '
    • '.&mt("$notlt{$key} set to: [_1]", + $value).'
    • '; + } + } + $resulttext .= '
  • '; + } else { + $resulttext .= '
  • '.&mt("$title{$item} not in use for [_1]",$lonhost).'
  • '; + } + } + } } elsif ($item eq 'captcha') { if (ref($loginhash{'login'}) eq 'HASH') { my $chgtxt; @@ -8257,6 +9730,283 @@ sub color_font_choices { return %choices; } +sub modify_ipaccess { + my ($dom,$lastactref,%domconfig) = @_; + my (@allpos,%changes,%confhash,$errors,$resulttext); + my (@items,%deletions,%itemids,@warnings); + my ($typeorder,$types) = &commblocktype_text(); + if ($env{'form.ipaccess_add'}) { + my $name = $env{'form.ipaccess_name_add'}; + my ($newid,$error) = &get_ipaccess_id($dom,$name); + if ($newid) { + $itemids{'add'} = $newid; + push(@items,'add'); + $changes{$newid} = 1; + } else { + $error = &mt('Failed to acquire unique ID for new IP access control item'); + $errors .= '
  • '.$error.'
  • '; + } + } + if (ref($domconfig{'ipaccess'}) eq 'HASH') { + my @todelete = &Apache::loncommon::get_env_multiple('form.ipaccess_del'); + if (@todelete) { + map { $deletions{$_} = 1; } @todelete; + } + my $maxnum = $env{'form.ipaccess_maxnum'}; + for (my $i=0; $i<$maxnum; $i++) { + my $itemid = $env{'form.ipaccess_id_'.$i}; + $itemid =~ s/\D+//g; + if (ref($domconfig{'ipaccess'}{$itemid}) eq 'HASH') { + if ($deletions{$itemid}) { + $changes{$itemid} = $domconfig{'ipaccess'}{$itemid}{'name'}; + } else { + push(@items,$i); + $itemids{$i} = $itemid; + } + } + } + } + foreach my $idx (@items) { + my $itemid = $itemids{$idx}; + next unless ($itemid); + my %current; + unless ($idx eq 'add') { + if (ref($domconfig{'ipaccess'}{$itemid}) eq 'HASH') { + %current = %{$domconfig{'ipaccess'}{$itemid}}; + } + } + my $position = $env{'form.ipaccess_pos_'.$itemid}; + $position =~ s/\D+//g; + if ($position ne '') { + $allpos[$position] = $itemid; + } + my $name = $env{'form.ipaccess_name_'.$idx}; + $name =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'name'} = $name; + my $possrange = $env{'form.ipaccess_range_'.$idx}; + $possrange =~ s/^\s+|\s+$//g; + unless ($possrange eq '') { + $possrange =~ s/[\r\n]+/\s/g; + $possrange =~ s/\s*-\s*/-/g; + $possrange =~ s/\s+/,/g; + $possrange =~ s/,+/,/g; + if ($possrange ne '') { + my (@ok,$count); + $count = 0; + foreach my $poss (split(/\,/,$possrange)) { + $count ++; + $poss = &validate_ip_pattern($poss); + if ($poss ne '') { + push(@ok,$poss); + } + } + my $diff = $count - scalar(@ok); + if ($diff) { + $errors .= '
  • '. + &mt('[quant,_1,IP] invalid and excluded from saved value for IP range(s) for [_2]', + $diff,$name). + '
  • '; + } + if (@ok) { + my @cidr_list; + foreach my $item (@ok) { + @cidr_list = &Net::CIDR::cidradd($item,@cidr_list); + } + $confhash{$itemid}{'ip'} = join(',',@cidr_list); + } + } + } + foreach my $field ('name','ip') { + unless (($idx eq 'add') || ($changes{$itemid})) { + if ($current{$field} ne $confhash{$itemid}{$field}) { + $changes{$itemid} = 1; + last; + } + } + } + $confhash{$itemid}{'commblocks'} = {}; + + my %commblocks; + map { $commblocks{$_} = 1; } &Apache::loncommon::get_env_multiple('form.ipaccess_block_'.$idx); + foreach my $type (@{$typeorder}) { + if ($commblocks{$type}) { + $confhash{$itemid}{'commblocks'}{$type} = 'on'; + } + unless (($idx eq 'add') || ($changes{$itemid})) { + if (ref($current{'commblocks'}) eq 'HASH') { + if ($confhash{$itemid}{'commblocks'}{$type} ne $current{'commblocks'}{$type}) { + $changes{$itemid} = 1; + } + } elsif ($confhash{$itemid}{'commblocks'}{$type}) { + $changes{$itemid} = 1; + } + } + } + $confhash{$itemid}{'courses'} = {}; + my %crsdeletions; + my @delcrs = &Apache::loncommon::get_env_multiple('form.ipaccess_course_delete_'.$idx); + if (@delcrs) { + map { $crsdeletions{$_} = 1; } @delcrs; + } + if (ref($current{'courses'}) eq 'HASH') { + foreach my $cid (sort(keys(%{$current{'courses'}}))) { + if ($crsdeletions{$cid}) { + $changes{$itemid} = 1; + } else { + $confhash{$itemid}{'courses'}{$cid} = 1; + } + } + } + $env{'form.ipaccess_cnum_'.$idx} =~ s/^\s+|\s+$//g; + $env{'form.ipaccess_cdom_'.$idx} =~ s/^\s+|\s+$//g; + if (($env{'form.ipaccess_cnum_'.$idx} =~ /^$match_courseid$/) && + ($env{'form.ipaccess_cdom_'.$idx} =~ /^$match_domain$/)) { + if (&Apache::lonnet::homeserver($env{'form.ipaccess_cnum_'.$idx}, + $env{'form.ipaccess_cdom_'.$idx}) eq 'no_host') { + $errors .= '
  • '. + &mt('Invalid courseID [_1] omitted from list of allowed courses', + $env{'form.ipaccess_cdom_'.$idx}.'_'.$env{'form.ipaccess_cnum_'.$idx}). + '
  • '; + } else { + $confhash{$itemid}{'courses'}{$env{'form.ipaccess_cdom_'.$idx}.'_'.$env{'form.ipaccess_cnum_'.$idx}} = 1; + $changes{$itemid} = 1; + } + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $itemid (@allpos) { + if ($itemid ne '') { + $confhash{$itemid}{'order'} = $idx; + unless ($changes{$itemid}) { + if (ref($domconfig{'ipaccess'}) eq 'HASH') { + if (ref($domconfig{'ipaccess'}{$itemid}) eq 'HASH') { + if ($domconfig{'ipaccess'}{$itemid}{'order'} ne $idx) { + $changes{$itemid} = 1; + } + } + } + } + $idx ++; + } + } + } + if (keys(%changes)) { + my %defaultshash = ( + ipaccess => \%confhash, + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash, + $dom); + if ($putresult eq 'ok') { + my $cachetime = 1800; + &Apache::lonnet::do_cache_new('ipaccess',$dom,\%confhash,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'ipaccess'} = 1; + } + $resulttext = &mt('Changes made:').'
      '; + my %bynum; + foreach my $itemid (sort(keys(%changes))) { + if (ref($confhash{$itemid}) eq 'HASH') { + my $position = $confhash{$itemid}{'order'}; + if ($position =~ /^\d+$/) { + $bynum{$position} = $itemid; + } + } + } + if (keys(%deletions)) { + foreach my $itemid (sort { $a <=> $b } keys(%deletions)) { + $resulttext .= '
    • '.&mt('Deleted: [_1]',$changes{$itemid}).'
    • '; + } + } + foreach my $pos (sort { $a <=> $b } keys(%bynum)) { + my $itemid = $bynum{$pos}; + if (ref($confhash{$itemid}) eq 'HASH') { + $resulttext .= '
    • '.$confhash{$itemid}{'name'}.'
        '; + my $position = $pos + 1; + $resulttext .= '
      • '.&mt('Order: [_1]',$position).'
      • '; + if ($confhash{$itemid}{'ip'} eq '') { + $resulttext .= '
      • '.&mt('No IP Range(s) set').'
      • '; + } else { + $resulttext .= '
      • '.&mt('IP Range(s): [_1]',$confhash{$itemid}{'ip'}).'
      • '; + } + if (keys(%{$confhash{$itemid}{'commblocks'}})) { + $resulttext .= '
      • '.&mt('Functionality Blocked: [_1]', + join(', ', map { $types->{$_}; } sort(keys(%{$confhash{$itemid}{'commblocks'}})))). + '
      • '; + } else { + $resulttext .= '
      • '.&mt('No functionality blocked').'
      • '; + } + if (keys(%{$confhash{$itemid}{'courses'}})) { + my @courses; + foreach my $cid (sort(keys(%{$confhash{$itemid}{'courses'}}))) { + my %courseinfo = &Apache::lonnet::coursedescription($cid,{'one_time' => 1}); + push(@courses,$courseinfo{'description'}.' ('.$cid.')'); + } + $resulttext .= '
      • '.&mt('Courses/Communities allowed').':
        • '. + join('
        • ',@courses).'
        '; + } else { + $resulttext .= '
      • '.&mt('No courses allowed').'
      • '; + } + $resulttext .= '
    • '; + } + } + $resulttext .= '
    '; + } else { + $errors .= '
  • '.&mt('Failed to save changes').'
  • '; + } + } else { + $resulttext = &mt('No changes made'); + } + if ($errors) { + $resulttext .= '

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

      '. + $errors.'

    '; + } + return $resulttext; +} + +sub get_ipaccess_id { + my ($domain,$location) = @_; + # get lock on ipaccess db + my $lockhash = { + lock => $env{'user.name'}. + ':'.$env{'user.domain'}, + }; + my $tries = 0; + my $gotlock = &Apache::lonnet::newput_dom('ipaccess',$lockhash,$domain); + my ($id,$error); + + while (($gotlock ne 'ok') && ($tries<10)) { + $tries ++; + sleep (0.1); + $gotlock = &Apache::lonnet::newput_dom('ipaccess',$lockhash,$domain); + } + if ($gotlock eq 'ok') { + my %currids = &Apache::lonnet::dump_dom('ipaccess',$domain); + if ($currids{'lock'}) { + delete($currids{'lock'}); + if (keys(%currids)) { + my @curr = sort { $a <=> $b } keys(%currids); + if ($curr[-1] =~ /^\d+$/) { + $id = 1 + $curr[-1]; + } + } else { + $id = 1; + } + if ($id) { + unless (&Apache::lonnet::newput_dom('ipaccess',{ $id => $location },$domain) eq 'ok') { + $error = 'nostore'; + } + } else { + $error = 'nonumber'; + } + } + my $dellockoutcome = &Apache::lonnet::del_dom('ipaccess',['lock'],$domain); + } else { + $error = 'nolock'; + } + return ($id,$error); +} + sub modify_rolecolors { my ($r,$dom,$confname,$roles,$lastactref,%domconfig) = @_; my ($resulttext,%rolehash); @@ -8364,13 +10114,18 @@ sub modify_colors { $domconfig->{$role} = {}; } foreach my $img (@images) { - if (($role eq 'login') && (($img eq 'img') || ($img eq 'logo'))) { - if (defined($env{'form.login_showlogo_'.$img})) { - $confhash->{$role}{'showlogo'}{$img} = 1; - } else { - $confhash->{$role}{'showlogo'}{$img} = 0; + if ($role eq 'login') { + if (($img eq 'img') || ($img eq 'logo')) { + if (defined($env{'form.login_showlogo_'.$img})) { + $confhash->{$role}{'showlogo'}{$img} = 1; + } else { + $confhash->{$role}{'showlogo'}{$img} = 0; + } } - } + if ($env{'form.login_alt_'.$img} ne '') { + $confhash->{$role}{'alttext'}{$img} = $env{'form.login_alt_'.$img}; + } + } if ( ! $env{'form.'.$role.'_'.$img.'.filename'} && !defined($domconfig->{$role}{$img}) && !$env{'form.'.$role.'_del_'.$img} @@ -8445,15 +10200,29 @@ sub modify_colors { $changes{$role}{'images'}{$img} = 1; } } - if (($role eq 'login') && (($img eq 'logo') || ($img eq 'img'))) { - if (ref($domconfig->{'login'}{'showlogo'}) eq 'HASH') { - if ($confhash->{$role}{'showlogo'}{$img} ne - $domconfig->{$role}{'showlogo'}{$img}) { - $changes{$role}{'showlogo'}{$img} = 1; + if ($role eq 'login') { + if (($img eq 'logo') || ($img eq 'img')) { + if (ref($domconfig->{'login'}{'showlogo'}) eq 'HASH') { + if ($confhash->{$role}{'showlogo'}{$img} ne + $domconfig->{$role}{'showlogo'}{$img}) { + $changes{$role}{'showlogo'}{$img} = 1; + } + } else { + if ($confhash->{$role}{'showlogo'}{$img} == 0) { + $changes{$role}{'showlogo'}{$img} = 1; + } } - } else { - if ($confhash->{$role}{'showlogo'}{$img} == 0) { - $changes{$role}{'showlogo'}{$img} = 1; + } + if ($img ne 'login') { + if (ref($domconfig->{$role}{'alttext'}) eq 'HASH') { + if ($confhash->{$role}{'alttext'}{$img} ne + $domconfig->{$role}{'alttext'}{$img}) { + $changes{$role}{'alttext'}{$img} = 1; + } + } else { + if ($confhash->{$role}{'alttext'}{$img} ne '') { + $changes{$role}{'alttext'}{$img} = 1; + } } } } @@ -8564,6 +10333,11 @@ sub default_change_checker { if ($confhash->{$role}{'showlogo'}{$img} == 0) { $changes->{$role}{'showlogo'}{$img} = 1; } + if (ref($confhash->{$role}{'alttext'}) eq 'HASH') { + if ($confhash->{$role}{'alttext'}{$img} ne '') { + $changes->{$role}{'alttext'}{$img} = 1; + } + } } } if ($confhash->{$role}{'font'}) { @@ -8602,6 +10376,13 @@ sub display_colorchgs { } else { $resulttext .= '
  • '.&mt("$choices{$item} set to not be displayed").'
  • '; } + } elsif (($role eq 'login') && ($key eq 'alttext')) { + if ($confhash->{$role}{$key}{$item} ne '') { + $resulttext .= '
  • '.&mt("$choices{$key for $choices{$item} set to [_1].", + $confhash->{$role}{$key}{$item}).'
  • '; + } else { + $resulttext .= '
  • '.&mt("$choices{$key} for $choices{$item} deleted.").'
  • '; + } } elsif ($confhash->{$role}{$item} eq '') { $resulttext .= '
  • '.&mt("$choices{$item} set to default").'
  • '; } else { @@ -9720,7 +11501,7 @@ sub modify_autoenroll { my %title = ( run => 'Auto-enrollment active', sender => 'Sender for notification messages', coowners => 'Automatic assignment of co-ownership to instructors of record (institutional data)', - failsafe => 'Failsafe for no drops if institutional data missing for a section'); + autofailsafe => 'Failsafe for no drops if institutional data missing for a section'); my @offon = ('off','on'); my $sender_uname = $env{'form.sender_uname'}; my $sender_domain = $env{'form.sender_domain'}; @@ -9730,17 +11511,23 @@ sub modify_autoenroll { $sender_domain = ''; } my $coowners = $env{'form.autoassign_coowners'}; + my $autofailsafe = $env{'form.autoenroll_autofailsafe'}; + $autofailsafe =~ s{^\s+|\s+$}{}g; + if ($autofailsafe =~ /\D/) { + undef($autofailsafe); + } my $failsafe = $env{'form.autoenroll_failsafe'}; - $failsafe =~ s{^\s+|\s+$}{}g; - if ($failsafe =~ /\D/) { - undef($failsafe); + unless (($failsafe eq 'zero') || ($failsafe eq 'any')) { + $failsafe = 'off'; + undef($autofailsafe); } my %autoenrollhash = ( autoenroll => { 'run' => $env{'form.autoenroll_run'}, 'sender_uname' => $sender_uname, 'sender_domain' => $sender_domain, 'co-owners' => $coowners, - 'autofailsafe' => $failsafe, + 'autofailsafe' => $autofailsafe, + 'failsafe' => $failsafe, } ); my $putresult = &Apache::lonnet::put_dom('configuration',\%autoenrollhash, @@ -9768,9 +11555,12 @@ sub modify_autoenroll { } elsif ($coowners) { $changes{'coowners'} = 1; } - if ($currautoenroll{'autofailsafe'} ne $failsafe) { + if ($currautoenroll{'autofailsafe'} ne $autofailsafe) { $changes{'autofailsafe'} = 1; } + if ($currautoenroll{'failsafe'} ne $failsafe) { + $changes{'failsafe'} = 1; + } if (keys(%changes) > 0) { $resulttext = &mt('Changes made:').'
      '; if ($changes{'run'}) { @@ -9791,11 +11581,24 @@ sub modify_autoenroll { } } if ($changes{'autofailsafe'}) { - if ($failsafe ne '') { - $resulttext .= '
    • '.&mt('Failsafe for no drops if institutional data missing for a section set to: [_1]',$failsafe).'
    • '; + if ($autofailsafe ne '') { + $resulttext .= '
    • '.&mt('Failsafe for no drops if institutional data missing for a section set to: [_1]',$autofailsafe).'
    • '; + } else { + $resulttext .= '
    • '.&mt('Failsafe for no drops if institutional data missing for a section not in use').'
    • '; + } + } + if ($changes{'failsafe'}) { + if ($failsafe eq 'off') { + unless ($changes{'autofailsafe'}) { + $resulttext .= '
    • '.&mt('Failsafe for no drops if institutional data missing for a section not in use').'
    • '; + } + } elsif ($failsafe eq 'zero') { + $resulttext .= '
    • '.&mt('Failsafe applies if retrieved section enrollment is zero').'
    • '; } else { - $resulttext .= '
    • '.&mt('Failsafe for no drops if institutional data missing for a section: deleted'); + $resulttext .= '
    • '.&mt('Failsafe applies if retrieved section enrollment is zero or greater').'
    • '; } + } + if (($changes{'autofailsafe'}) || ($changes{'failsafe'})) { &Apache::lonnet::get_domain_defaults($dom,1); if (ref($lastactref) eq 'HASH') { $lastactref->{'domdefaults'} = 1; @@ -9822,8 +11625,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 ( @@ -9867,12 +11672,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; @@ -9918,6 +11734,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') { @@ -9980,6 +11806,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}]; } @@ -10293,7 +12124,8 @@ sub modify_contacts { my @contacts = ('supportemail','adminemail'); my @mailings = ('errormail','packagesmail','helpdeskmail','otherdomsmail', 'lonstatusmail','requestsmail','updatesmail','idconflictsmail','hostipmail'); - my @toggles = ('reporterrors','reportupdates'); + my @toggles = ('reporterrors','reportupdates','reportstatus'); + my @lonstatus = ('threshold','sysmail','weights','excluded'); my ($fields,$fieldtitles,$fieldoptions,$possoptions) = &helpform_fields(); foreach my $type (@mailings) { @{$newsetting{$type}} = @@ -10326,23 +12158,98 @@ 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') { my $value = $env{'form.helpform_'.$field}; $value =~ s/^\s+|\s+$//g; if (grep(/^\Q$value\E$/,@{$possoptions->{$field}})) { - $contacts_hash{contacts}{'helpform'}{$field} = $value; + $contacts_hash{'contacts'}{'helpform'}{$field} = $value; if ($field eq 'screenshot') { $env{'form.helpform_maxsize'} =~ s/^\s+|\s+$//g; if ($env{'form.helpform_maxsize'} =~ /^\d+\.?\d*$/) { - $contacts_hash{contacts}{'helpform'}{'maxsize'} = $env{'form.helpform_maxsize'}; + $contacts_hash{'contacts'}{'helpform'}{'maxsize'} = $env{'form.helpform_maxsize'}; } } } } } } + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my (@statuses,%usertypeshash,@overrides); + if ((ref($types) eq 'ARRAY') && (@{$types} > 0)) { + @statuses = @{$types}; + if (ref($usertypes) eq 'HASH') { + %usertypeshash = %{$usertypes}; + } + } + if (@statuses) { + my @possoverrides = &Apache::loncommon::get_env_multiple('form.overrides'); + foreach my $type (@possoverrides) { + if (($type ne '') && (grep(/^\Q$type\E$/,@statuses))) { + push(@overrides,$type); + } + } + if (@overrides) { + foreach my $type (@overrides) { + my @standard = &Apache::loncommon::get_env_multiple('form.override_'.$type); + foreach my $item (@contacts) { + if (grep(/^\Q$item\E$/,@standard)) { + $contacts_hash{'contacts'}{'overrides'}{$type}{$item} = 1; + $newsetting{'override_'.$type}{$item} = 1; + } else { + $contacts_hash{'contacts'}{'overrides'}{$type}{$item} = 0; + $newsetting{'override_'.$type}{$item} = 0; + } + } + $contacts_hash{'contacts'}{'overrides'}{$type}{'others'} = $env{'form.override_'.$type.'_others'}; + $contacts_hash{'contacts'}{'overrides'}{$type}{'bcc'} = $env{'form.override_'.$type.'_bcc'}; + $newsetting{'override_'.$type}{'others'} = $env{'form.override_'.$type.'_others'}; + $newsetting{'override_'.$type}{'bcc'} = $env{'form.override_'.$type.'_bcc'}; + if (($env{'form.override_'.$type.'_includestr'} ne '') && ($env{'form.override_'.$type.'_includeloc'} =~ /^s|b$/)) { + $includestr{$type} = $env{'form.override_'.$type.'_includestr'}; + $includeloc{$type} = $env{'form.override_'.$type.'_includeloc'}; + $contacts_hash{'contacts'}{'overrides'}{$type}{'include'} = $includeloc{$type}.':'.&escape($includestr{$type}); + $newsetting{'override_'.$type}{'include'} = $contacts_hash{'contacts'}{'overrides'}{$type}{'include'}; + } + } + } + } if (keys(%currsetting) > 0) { foreach my $item (@contacts) { if ($to{$item} ne $currsetting{$item}) { @@ -10397,6 +12304,103 @@ sub modify_contacts { } } } + if (@statuses) { + if (ref($currsetting{'overrides'}) eq 'HASH') { + foreach my $key (keys(%{$currsetting{'overrides'}})) { + if (ref($currsetting{'overrides'}{$key}) eq 'HASH') { + if (ref($newsetting{'override_'.$key}) eq 'HASH') { + foreach my $item (@contacts,'bcc','others','include') { + if ($currsetting{'overrides'}{$key}{$item} ne $newsetting{'override_'.$key}{$item}) { + push(@{$changes{'overrides'}},$key); + last; + } + } + } else { + push(@{$changes{'overrides'}},$key); + } + } + } + foreach my $key (@overrides) { + unless (exists($currsetting{'overrides'}{$key})) { + push(@{$changes{'overrides'}},$key); + } + } + } else { + foreach my $key (@overrides) { + push(@{$changes{'overrides'}},$key); + } + } + } + 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'}; @@ -10442,6 +12446,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)) { @@ -10512,23 +12523,134 @@ sub modify_contacts { $resulttext .= ''; } } + if (ref($changes{'overrides'}) eq 'ARRAY') { + my @deletions; + foreach my $type (@{$changes{'overrides'}}) { + if ($usertypeshash{$type}) { + if (grep(/^\Q$type\E/,@overrides)) { + $resulttext .= '
    • '.&mt("Overrides based on requester's affiliation set for [_1]", + $usertypeshash{$type}).'
      • '; + if (ref($newsetting{'override_'.$type}) eq 'HASH') { + my @text; + foreach my $item (@contacts) { + if ($newsetting{'override_'.$type}{$item}) { + push(@text,$short_titles->{$item}); + } + } + if ($newsetting{'override_'.$type}{'others'} ne '') { + push(@text,$newsetting{'override_'.$type}{'others'}); + } + + if (@text) { + $resulttext .= &mt('Helpdesk e-mail sent to: [_1]', + ''.join(', ',@text).''); + } + if ($newsetting{'override_'.$type}{'bcc'} ne '') { + my $bcctext; + if (@text) { + $bcctext = ' '.&mt('with Bcc to'); + } else { + $bcctext = '(Bcc)'; + } + $resulttext .= $bcctext.': '.$newsetting{'override_'.$type}{'bcc'}.''; + } elsif (!@text) { + $resulttext .= &mt('Helpdesk e-mail sent to no one'); + } + $resulttext .= '
      • '; + if ($newsetting{'override_'.$type}{'include'} ne '') { + my ($loc,$str) = split(/:/,$newsetting{'override_'.$type}{'include'}); + if ($loc eq 'b') { + $resulttext .= '
      • '.&mt('Text automatically added to e-mail body:').' '.&unescape($str).'
      • '; + } elsif ($loc eq 's') { + $resulttext .= '
      • '.&mt('Text automatically added to e-mail subject:').' '.&unescape($str).'
      • '; + } + } + } + $resulttext .= '
    • '; + } else { + push(@deletions,$usertypeshash{$type}); + } + } + } + if (@deletions) { + $resulttext .= '
    • '.&mt("Overrides based on requester's affiliation discontinued for: [_1]", + join(', ',@deletions)).'
    • '; + } + } 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'}}) { @@ -10567,7 +12689,6 @@ sub modify_contacts { &mt('Max size for file uploaded to help form by logged-in user set to [_1] MB.', $contacts_hash{'contacts'}{'helpform'}{'maxsize'}). ''; - } } $resulttext .= '
    '; @@ -10649,7 +12770,7 @@ sub modify_passwords { if ($current{'resetlink'} ne $linklife) { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { if ($staticdefaults{'resetlink'} ne $linklife) { $changes{'reset'} = 1; } @@ -10670,7 +12791,7 @@ sub modify_passwords { if (@diffs > 0) { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetcase'},\@casesens); if (@diffs > 0) { $changes{'reset'} = 1; @@ -10682,7 +12803,7 @@ sub modify_passwords { if ($current{'resetprelink'} ne $newvalues{'resetprelink'}) { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { if ($staticdefaults{'resetprelink'} ne $newvalues{'resetprelink'}) { $changes{'reset'} = 1; } @@ -10709,7 +12830,7 @@ sub modify_passwords { } else { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetpostlink'}{$type},\@postlink); if (@diffs > 0) { $changes{'reset'} = 1; @@ -10731,7 +12852,7 @@ sub modify_passwords { if (@diffs > 0) { $changes{'reset'} = 1; } - } elsif (!exists($domconfig{passwords})) { + } elsif (!ref($domconfig{passwords}) eq 'HASH') { my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetemail'},\@resetemail); if (@diffs > 0) { $changes{'reset'} = 1; @@ -10838,6 +12959,8 @@ sub modify_passwords { if ($staticdefaults{$rule} ne $newvalues{$rule}) { $changes{'rules'} = 1; } + } else { + $changes{'rules'} = 1; } } elsif (exists($current{$rule})) { $changes{'rules'} = 1; @@ -10886,7 +13009,7 @@ sub modify_passwords { } } } - } elsif (!exists($domconfig{passwords})) { + } elsif (!(ref($domconfig{passwords}) eq 'HASH')) { foreach my $item ('by','for') { if (@{$crsownerchg{$item}} > 0) { $changes{'crsownerchg'} = 1; @@ -10916,9 +13039,11 @@ sub modify_passwords { $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'}).'
    '. - &mt('Public key: [_1]',$confighash{'passwords'}{'recaptchapub'}).'
    '. - &mt('Private key: [_1]',$confighash{'passwords'}{'recaptchapriv'}).'
  • '; + &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').'
  • '; } @@ -10988,7 +13113,7 @@ sub modify_passwords { $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 usedfor verification will include: [_1]',join(', ',map { $titles{$_}; } @{$staticdefaults{'resetemail'}})).'
  • '; + $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').'
  • '; @@ -10997,8 +13122,9 @@ sub modify_passwords { } if ($confighash{'passwords'}{'resetcustom'}) { my $customlink = &Apache::loncommon::modal_link($confighash{'passwords'}{'resetcustom'}, - $titles{custom},600,500); - $resulttext .= '
  • '.&mt('Preamble to "Forgot Password" form includes [_1]',$customlink).'
  • '; + &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').'
  • '; } @@ -11044,6 +13170,24 @@ sub modify_passwords { $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) || @@ -12184,19 +14328,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; } @@ -12218,9 +14368,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 => '', @@ -12228,8 +14378,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; } @@ -12241,6 +14391,8 @@ sub process_captcha { } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['recaptchaversion']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'recaptchaversion'} = 1; } @@ -12252,6 +14404,8 @@ sub process_captcha { } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['recaptchakeys']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'recaptchakeys'} = 1; } @@ -13502,9 +15656,10 @@ sub modify_coursedefaults { my ($resulttext,$errors,%changes,%defaultshash); my %defaultchecked = ( 'uselcmath' => 'on', - 'usejsme' => 'on' + 'usejsme' => 'on', + 'inline_chem' => 'on', ); - my @toggles = ('uselcmath','usejsme'); + my @toggles = ('uselcmath','usejsme','inline_chem'); my @numbers = ('anonsurvey_threshold','uploadquota_official','uploadquota_unofficial', 'uploadquota_community','uploadquota_textbook','mysqltables_official', 'mysqltables_unofficial','mysqltables_community','mysqltables_textbook'); @@ -13713,8 +15868,9 @@ sub modify_coursedefaults { my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); if (($changes{'uploadquota'}) || ($changes{'postsubmit'}) || ($changes{'coursecredits'}) || ($changes{'uselcmath'}) || ($changes{'usejsme'}) || - ($changes{'canclone'}) || ($changes{'mysqltables'}) || ($changes{'texengine'})) { - foreach my $item ('uselcmath','usejsme','texengine') { + ($changes{'canclone'}) || ($changes{'mysqltables'}) || ($changes{'texengine'}) || + ($changes{'inline_chem'})) { + foreach my $item ('uselcmath','usejsme','inline_chem','texengine') { if ($changes{$item}) { $domdefaults{$item}=$defaultshash{'coursedefaults'}{$item}; } @@ -13777,6 +15933,12 @@ sub modify_coursedefaults { } else { $resulttext .= '
  • '.&mt('Molecule editor uses JME (Java), if supported by client OS.').'
  • '; } + } elsif ($item eq 'inline_chem') { + if ($env{'form.'.$item} eq '1') { + $resulttext .= '
  • '.&mt('Chemical Reaction Response uses inline previewer').'
  • '; + } else { + $resulttext .= '
  • '.&mt('Chemical Reaction Response uses pop-up previewer').'
  • '; + } } elsif ($item eq 'texengine') { if ($defaultshash{'coursedefaults'}{'texengine'} ne '') { $resulttext .= '
  • '.&mt('Default method to display mathematics set to: "[_1]"', @@ -14125,6 +16287,344 @@ 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,%currsaml,%currvalue,@warnings, + %wafproxy,%changes,%expirecache,%expiresaml); + 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'}}; + } + if (ref($domconfig{'wafproxy'}{'saml'}) eq 'HASH') { + %currsaml = %{$domconfig{'wafproxy'}{'saml'}}; + } + foreach my $item ('remoteip','ipheader','trusted','vpnint','vpnext','sslopt') { + $currvalue{$item} = $domconfig{'wafproxy'}{$item}; + } + } + my $output; + if (keys(%canset)) { + %{$wafproxy{'alias'}} = (); + %{$wafproxy{'saml'}} = (); + 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; + } + if ($env{'form.wafproxy_alias_saml_'.$key}) { + $wafproxy{'saml'}{$key} = 1; + } + if ($wafproxy{'saml'}{$key} ne $currsaml{$key}) { + $changes{'saml'} = 1; + } + } else { + $wafproxy{'alias'}{$key} = ''; + $wafproxy{'saml'}{$key} = ''; + if ($curralias{$key}) { + $changes{'alias'} = 1; + } + if ($currsaml{$key}) { + $changes{'saml'} = 1; + } + } + if ($wafproxy{'alias'}{$key} eq '') { + if ($curralias{$key}) { + $expirecache{$key} = 1; + } + delete($wafproxy{'alias'}{$key}); + } + if ($wafproxy{'saml'}{$key} eq '') { + if ($currsaml{$key}) { + $expiresaml{$key} = 1; + } + delete($wafproxy{'saml'}{$key}); + } + } + unless (keys(%{$wafproxy{'alias'}})) { + delete($wafproxy{'alias'}); + } + unless (keys(%{$wafproxy{'saml'}})) { + delete($wafproxy{'saml'}); + } + # Localization for values in %warn occurs 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; + $possible =~ s/,+/,/g; + } + $count = 0; + if ($possible ne '') { + foreach my $poss (split(/\,/,$possible)) { + $count ++; + $poss = &validate_ip_pattern($poss); + if ($poss ne '') { + push(@ok,$poss); + } + } + 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 (@ok) { + my @cidr_list; + foreach my $item (@ok) { + @cidr_list = &Net::CIDR::cidradd($item,@cidr_list); + } + $wafproxy{$item} = join(',',@cidr_list); + } + } + } + if ($wafproxy{$item} ne $currvalue{$item}) { + $changes{$item} = 1; + } + } elsif ($currvalue{$item}) { + $changes{$item} = 1; + } + } + } else { + if (keys(%curralias)) { + $changes{'alias'} = 1; + } + if (keys(%currsaml)) { + $changes{'saml'} = 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; + } + } + if ((exists($wafproxy{'saml'})) || (keys(%expiresaml))) { + my %samlupdates = %expiresaml; + foreach my $key (keys(%expiresaml)) { + &Apache::lonnet::devalidate_cache_new('proxysaml',$key); + } + if (ref($wafproxy{'saml'}) eq 'HASH') { + my $cachetime = 24*60*60; + foreach my $key (keys(%{$wafproxy{'saml'}})) { + $samlupdates{$key} = 1; + &Apache::lonnet::do_cache_new('proxysaml',$key,$wafproxy{'saml'}{$key}, + $cachetime); + } + } + if (ref($lastactref) eq 'HASH') { + $lastactref->{'proxysaml'} = \%samlupdates; + } + } + $output = &mt('Changes were made to Web Application Firewall/Reverse Proxy').'
      '; + foreach my $item ('alias','saml','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').'
    • '; + } + } elsif ($item eq 'saml') { + my $shown; + if (ref($wafproxy{'saml'}) eq 'HASH') { + if (keys(%{$wafproxy{'saml'}})) { + $shown = join(', ',sort(keys(%{$wafproxy{'saml'}}))); + } + } + if ($shown) { + $output .= '
    • '.&mt('Alias used by SSO Auth for: [_1]', + $shown).'
    • '; + } else { + $output .= '
    • '.&mt('No alias used for SSO Auth').'
    • '; + } + } 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))) { + if (($start !~ m{/}) && ($end !~ m{/})) { + return $start.'-'.$end; + } + } + } elsif ($pattern ne '') { + $pattern = &Net::CIDR::cidrvalidate($pattern); + if ($pattern ne '') { + return $pattern; + } + } + return; +} + sub modify_usersessions { my ($dom,$lastactref,%domconfig) = @_; my @hostingtypes = ('version','excludedomain','includedomain'); @@ -14292,6 +16792,7 @@ sub modify_usersessions { } } $defaultshash{'usersessions'}{'offloadnow'} = {}; + $defaultshash{'usersessions'}{'offloadoth'} = {}; my @offloadnow = &Apache::loncommon::get_env_multiple('form.offloadnow'); my @okoffload; if (@offloadnow) { @@ -14308,6 +16809,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') { @@ -14318,26 +16835,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; } } + 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; + } } - } elsif (@okoffload) { + } + } 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)) { @@ -14354,6 +16883,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); @@ -14427,16 +16959,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 .= '
      '; @@ -14687,27 +17234,32 @@ sub modify_loadbalancing { } } if ($changes{'curr'}{$balancer}{'cookie'}) { - $resulttext .= '
    • '.&mt('Load Balancer: [_1] -- cookie use enabled', - $balancer).'
    • '; + if ($currcookies{$balancer}) { + $resulttext .= '
    • '.&mt('Load Balancer: [_1] -- cookie use disabled', + $balancer).'
    • '; + } else { + $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]); - } + } + } + 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]); } } } @@ -15467,16 +18019,48 @@ sub devalidate_remote_domconfs { my %thismachine; map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); my @posscached = ('domainconfig','domdefaults','usersessions', - 'directorysrch','passwdconf','cats'); + 'directorysrch','passwdconf','cats','proxyalias','proxysaml', + 'ipaccess'); + my %cache_by_lonhost; + if (exists($cachekeys->{'samllanding'})) { + if (ref($cachekeys->{'samllanding'}) eq 'HASH') { + my %landing = %{$cachekeys->{'samllanding'}}; + my %domservers = &Apache::lonnet::get_servers($dom); + if (keys(%domservers)) { + foreach my $server (keys(%domservers)) { + my @cached; + next if ($thismachine{$server}); + if ($landing{$server}) { + push(@cached,&escape('samllanding').':'.&escape($server)); + } + if (@cached) { + $cache_by_lonhost{$server} = \@cached; + } + } + } + } + } 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') || ($name eq 'proxysaml')) { + 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 ((exists($cache_by_lonhost{$server})) && + (ref($cache_by_lonhost{$server}) eq 'ARRAY')) { + push(@cached,@{$cache_by_lonhost{$server}}); + } if (@cached) { &Apache::lonnet::remote_devalidate_cache($server,\@cached); }