--- loncom/interface/domainprefs.pm 2016/08/05 20:12:25 1.160.6.67 +++ loncom/interface/domainprefs.pm 2021/12/16 19:46:34 1.160.6.114 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.160.6.67 2016/08/05 20:12:25 raeburn Exp $ +# $Id: domainprefs.pm,v 1.160.6.114 2021/12/16 19:46:34 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -19,7 +19,8 @@ # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA# +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ @@ -173,6 +174,7 @@ use File::Copy; use Locale::Language; use DateTime::TimeZone; use DateTime::Locale; +use Net::CIDR; my $registered_cleanup; my $modified_urls; @@ -213,15 +215,16 @@ sub handler { 'quotas','autoenroll','autoupdate','autocreate', 'directorysrch','usercreation','usermodification', 'contacts','defaults','scantron','coursecategories', - 'serverstatuses','requestcourses','coursedefaults', - 'usersessions','loadbalancing','requestauthor', - 'selfenrollment','inststatus'],$dom); - my @prefs_order = ('rolecolors','login','defaults','quotas','autoenroll', - 'autoupdate','autocreate','directorysrch','contacts', - 'usercreation','selfcreation','usermodification','scantron', - 'requestcourses','requestauthor','coursecategories', - 'serverstatuses','coursedefaults','selfenrollment', - 'usersessions'); + 'serverstatuses','requestcourses','helpsettings', + 'coursedefaults','usersessions','loadbalancing', + '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; if (ref($domconfig{'loadbalancing'}) eq 'HASH') { %existing = %{$domconfig{'loadbalancing'}}; @@ -252,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, }, @@ -262,10 +268,35 @@ sub handler { header => [{col1 => 'Setting', col2 => 'Value'}, {col1 => 'Institutional user types', - col2 => 'Assignable to e-mail usernames'}], + col2 => 'Name displayed'}], print => \&print_defaults, modify => \&modify_defaults, }, + 'wafproxy' => + { text => 'Web Application Firewall/Reverse Proxy', + help => 'Domain_Configuration_WAF_Proxy', + header => [{col1 => 'Domain(s)', + col2 => 'Servers and WAF/Reverse Proxy alias(es)', + }, + {col1 => 'Domain(s)', + col2 => 'WAF Configuration',}], + print => \&print_wafproxy, + modify => \&modify_wafproxy, + }, + 'passwords' => + { text => 'Passwords (Internal authentication)', + help => 'Domain_Configuration_Passwords', + header => [{col1 => 'Resetting Forgotten Password', + col2 => 'Settings'}, + {col1 => 'Encryption of Stored Passwords (Internal Auth)', + col2 => 'Settings'}, + {col1 => 'Rules for LON-CAPA Passwords', + col2 => 'Settings'}, + {col1 => 'Course Owner Changing Student Passwords', + col2 => 'Settings'}], + print => \&print_passwords, + modify => \&modify_passwords, + }, 'quotas' => { text => 'Blogs, personal web pages, webDAV/quotas, portfolios', help => 'Domain_Configuration_Quotas', @@ -304,18 +335,26 @@ sub handler { modify => \&modify_autocreate, }, 'directorysrch' => - { text => 'Institutional directory searches', + { text => 'Directory searches', help => 'Domain_Configuration_InstDirectory_Search', - header => [{col1 => 'Setting', + header => [{col1 => 'Institutional Directory Setting', + col2 => 'Value',}, + {col1 => 'LON-CAPA Directory Setting', col2 => 'Value',}], print => \&print_directorysrch, modify => \&modify_directorysrch, }, 'contacts' => - { text => 'Contact Information', + { text => 'E-mail addresses and helpform', help => 'Domain_Configuration_Contact_Info', - header => [{col1 => 'Setting', - col2 => 'Value',}], + header => [{col1 => 'Default e-mail addresses', + 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, modify => \&modify_contacts, }, @@ -338,7 +377,7 @@ sub handler { col2 => 'Enabled?'}, {col1 => 'Institutional user type (login/SSO self-creation)', col2 => 'Information user can enter'}, - {col1 => 'Self-creation with e-mail as username', + {col1 => 'Self-creation with e-mail verification', col2 => 'Settings'}], print => \&print_selfcreation, modify => \&modify_selfcreation, @@ -354,11 +393,12 @@ sub handler { modify => \&modify_usermodification, }, 'scantron' => - { text => 'Bubblesheet format file', + { text => 'Bubblesheet format', help => 'Domain_Configuration_Scantron_Format', - header => [ {col1 => 'Item', - col2 => '', - }], + header => [ {col1 => 'Bubblesheet format file', + col2 => ''}, + {col1 => 'Bubblesheet data upload formats', + col2 => 'Settings'}], print => \&print_scantron, modify => \&modify_scantron, }, @@ -411,6 +451,16 @@ sub handler { print => \&print_serverstatuses, modify => \&modify_serverstatuses, }, + 'helpsettings' => + {text => 'Support settings', + help => 'Domain_Configuration_Help_Settings', + header => [{col1 => 'Help Page Settings (logged-in users)', + col2 => 'Value'}, + {col1 => 'Helpdesk Roles', + col2 => 'Settings'},], + print => \&print_helpsettings, + modify => \&modify_helpsettings, + }, 'coursedefaults' => {text => 'Course/Community defaults', help => 'Domain_Configuration_Course_Defaults', @@ -445,7 +495,7 @@ sub handler { print => \&print_usersessions, modify => \&modify_usersessions, }, - 'loadbalancing' => + 'loadbalancing' => {text => 'Dedicated Load Balancer(s)', help => 'Domain_Configuration_Load_Balancing', header => [{col1 => 'Balancers', @@ -456,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', @@ -467,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, }; @@ -508,6 +569,17 @@ $javascript_validations $coursebrowserjs END + } elsif (grep(/^ipaccess$/,@actions)) { + $js .= &Apache::loncommon::coursebrowser_javascript($env{'request.role.domain'}); + } + if (grep(/^selfcreation$/,@actions)) { + $js .= &selfcreate_javascript(); + } + if (grep(/^contacts$/,@actions)) { + $js .= &contacts_javascript(); + } + if (grep(/^scantron$/,@actions)) { + $js .= &scantron_javascript(); } &Apache::lonconfigsettings::display_settings($r,$dom,$phase,$context,\@prefs_order,\%prefs,\%domconfig,$confname,$js); } else { @@ -598,11 +670,11 @@ sub process_changes { } elsif ($action eq 'autocreate') { $output = &modify_autocreate($dom,%domconfig); } elsif ($action eq 'directorysrch') { - $output = &modify_directorysrch($dom,%domconfig); + $output = &modify_directorysrch($dom,$lastactref,%domconfig); } elsif ($action eq 'usercreation') { $output = &modify_usercreation($dom,%domconfig); } elsif ($action eq 'selfcreation') { - $output = &modify_selfcreation($dom,%domconfig); + $output = &modify_selfcreation($dom,$lastactref,%domconfig); } elsif ($action eq 'usermodification') { $output = &modify_usermodification($dom,%domconfig); } elsif ($action eq 'contacts') { @@ -619,6 +691,8 @@ sub process_changes { $output = &modify_quotas($r,$dom,$action,$lastactref,%domconfig); } elsif ($action eq 'requestauthor') { $output = &modify_quotas($r,$dom,$action,$lastactref,%domconfig); + } elsif ($action eq 'helpsettings') { + $output = &modify_helpsettings($r,$dom,$confname,$lastactref,%domconfig); } elsif ($action eq 'coursedefaults') { $output = &modify_coursedefaults($dom,$lastactref,%domconfig); } elsif ($action eq 'selfenrollment') { @@ -627,6 +701,12 @@ sub process_changes { $output = &modify_usersessions($dom,$lastactref,%domconfig); } elsif ($action eq 'loadbalancing') { $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; } @@ -639,11 +719,37 @@ sub print_config_box { $output = &coursecategories_javascript($settings); } elsif ($action eq 'defaults') { $output = &defaults_javascript($settings); + } elsif ($action eq 'passwords') { + $output = &passwords_javascript(); + } elsif ($action eq 'helpsettings') { + my (%privs,%levelscurrent); + my %full=(); + my %levels=( + course => {}, + domain => {}, + system => {}, + ); + my $context = 'domain'; + my $crstype = 'Course'; + my $formname = 'display'; + &Apache::lonuserutils::custom_role_privs(\%privs,\%full,\%levels,\%levelscurrent); + my @templateroles = &Apache::lonuserutils::custom_template_roles($context,$crstype); + $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 'login') { + $output .= &saml_javascript(); + } elsif ($action eq 'ipaccess') { + $output .= &ipaccess_javascript($settings); } $output .= ' - '."\n". ''; @@ -655,30 +761,40 @@ sub print_config_box { if ($numheaders > 1) { my $colspan = ''; my $rightcolspan = ''; + my $leftnobr = ''; if (($action eq 'rolecolors') || ($action eq 'defaults') || - (($action eq 'login') && ($numheaders < 4))) { + ($action eq 'directorysrch') || + (($action eq 'login') && ($numheaders < 5))) { $colspan = ' colspan="2"'; } if ($action eq 'usersessions') { $rightcolspan = ' colspan="3"'; } + if ($action eq 'passwords') { + $leftnobr = ' LC_nobreak'; + } $output .= ' + + + + + '; } else { $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); } $rowtotal ++; } elsif (($action eq 'usermodification') || ($action eq 'coursedefaults') || - ($action eq 'defaults')) { + ($action eq 'defaults') || ($action eq 'directorysrch') || + ($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 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'directorysrch') || + ($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); } elsif ($action eq 'coursecategories') { $output .= $item->{'print'}->('top',$dom,$item,$settings,\$rowtotal); + } elsif ($action eq 'scantron') { + $output .= $item->{'print'}->($r,'top',$dom,$confname,$settings,\$rowtotal); } elsif ($action eq 'login') { - if ($numheaders == 4) { + if ($numheaders == 5) { $colspan = ' colspan="2"'; $output .= &print_login('service',$dom,$confname,$phase,$settings,\$rowtotal); } else { @@ -703,10 +819,13 @@ sub print_config_box { $rowtotal ++; if (($action eq 'autoupdate') || ($action eq 'usercreation') || ($action eq 'selfcreation') || ($action eq 'selfenrollment') || - ($action eq 'usersessions') || ($action eq 'coursecategories')) { + ($action eq 'usersessions') || ($action eq 'coursecategories') || + ($action eq 'contacts') || ($action eq 'passwords')) { if ($action eq 'coursecategories') { $output .= &print_coursecategories('middle',$dom,$item,$settings,\$rowtotal); $colspan = ' colspan="2"'; + } elsif ($action eq 'passwords') { + $output .= $item->{'print'}->('middle',$dom,$confname,$settings,\$rowtotal); } else { $output .= $item->{'print'}->('middle',$dom,$settings,\$rowtotal); } @@ -723,15 +842,45 @@ sub print_config_box { '."\n"; if ($action eq 'coursecategories') { $output .= &print_coursecategories('bottom',$dom,$item,$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"; + 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'}).'
+
@@ -755,7 +904,7 @@ sub print_config_box { '; - if ($numheaders == 4) { + if ($numheaders == 5) { $output .= ' @@ -767,7 +916,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 ++; @@ -841,7 +1010,7 @@ sub print_config_box { '; } 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') { @@ -1208,14 +1370,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 .= ''; @@ -1236,7 +1390,89 @@ sub print_login { } else { $datatable .= ''; } - $datatable .= ''; + $datatable .= ''; + } + $datatable .= '
'.&mt($item->{'header'}->[4]->{'col1'}).''.&mt($item->{'header'}->[4]->{'col2'}).'
'.&mt($item->{'header'}->[3]->{'col1'}).''.&mt($item->{'header'}->[3]->{'col2'}).'
'; - if (($action eq 'login') || ($action eq 'directorysrch')) { + if ($action eq 'login') { $output .= ' '; } elsif ($action eq 'serverstatuses') { @@ -885,13 +1054,10 @@ sub print_config_box { $rowtotal ++; if ($action eq 'quotas') { $output .= &print_quotas($dom,$settings,\$rowtotal,$action); - } elsif (($action eq 'autoenroll') || ($action eq 'autocreate') || ($action eq 'directorysrch') || - ($action eq 'contacts') || ($action eq 'serverstatuses') || ($action eq 'loadbalancing')) { + } elsif (($action eq 'autoenroll') || ($action eq 'autocreate') || + ($action eq 'serverstatuses') || ($action eq 'loadbalancing') || + ($action eq 'ipaccess')) { $output .= $item->{'print'}->($dom,$settings,\$rowtotal); - } elsif ($action eq 'scantron') { - $output .= &print_scantronformat($r,$dom,$confname,$settings,\$rowtotal); - } elsif ($action eq 'helpsettings') { - $output .= &print_helpsettings($dom,$confname,$settings,\$rowtotal); } } $output .= ' @@ -904,8 +1070,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); @@ -1100,18 +1270,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'}->[0]->{'col1'}).'
'.$domservers{$lonhost}.'
'; + } 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 .= '
 
'; } @@ -1275,10 +1511,201 @@ sub login_choices { headtag => "Custom markup", action => "Action", current => "Current", + samllanding => "Dual login?", + samloptions => "Options", ); 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', + ); + my $typeorder = ['com','chat','boards','port','groups','blogs','about','printout','grades','passwd']; + return ($typeorder,\%types); +} + sub print_rolecolors { my ($phase,$role,$dom,$confname,$settings,$rowtotal) = @_; my %choices = &color_font_choices(); @@ -1396,7 +1823,7 @@ sub display_color_options { my $datatable = ''. ''.$choices->{'font'}.''; if (!$is_custom->{'font'}) { - $datatable .= ''.&mt('Default in use:').' '.$defaults->{'font'}.''; + $datatable .= ''.&mt('Default in use:').' '.$defaults->{'font'}.''; } else { $datatable .= ' '; } @@ -1405,12 +1832,12 @@ sub display_color_options { $datatable .= ''. ' '. - ' '; + ' '; unless ($role eq 'login') { $datatable .= ''. ''.$choices->{'fontmenu'}.''; if (!$is_custom->{'fontmenu'}) { - $datatable .= ''.&mt('Default in use:').' '.$defaults->{'fontmenu'}.''; + $datatable .= ''.&mt('Default in use:').' '.$defaults->{'fontmenu'}.''; } else { $datatable .= ' '; } @@ -1420,7 +1847,7 @@ sub display_color_options { ' '. - ' '; + ' '; } my $switchserver = &check_switchserver($dom,$confname); foreach my $img (@{$images}) { @@ -1479,7 +1906,8 @@ sub display_color_options { if ($fullwidth ne '' && $fullheight ne '') { if ($fullwidth > $width && $fullheight > $height) { my $size = $width.'x'.$height; - system("convert -sample $size $input $output"); + my @args = ('convert','-sample',$size,$input,$output); + system({$args[0]} @args); $showfile = "/$imgdir/tn-".$filename; } } @@ -1537,7 +1965,7 @@ sub display_color_options { my $bgs_def; foreach my $item (@{$bgs}) { if (!$is_custom->{$item}) { - $bgs_def .= ''.$choices->{$item}.'    
'.$defaults->{'bgs'}{$item}.''; + $bgs_def .= ''.$choices->{$item}.'    
'.$defaults->{'bgs'}{$item}.''; } } if ($bgs_def) { @@ -1565,7 +1993,7 @@ sub display_color_options { my $links_def; foreach my $item (@{$links}) { if (!$is_custom->{$item}) { - $links_def .= ''.$choices->{$item}.'
'.$defaults->{'links'}{$item}.''; + $links_def .= ''.$choices->{$item}.'
'.$defaults->{'links'}{$item}.''; } } if ($links_def) { @@ -1651,17 +2079,15 @@ sub image_changes { my ($is_custom,$alt_text,$img_import,$showfile,$fullsize,$role,$img,$imgfile,$logincolors) = @_; my $output; if ($img eq 'login') { - # suppress image for Log-in header + $output = ''.$logincolors; # suppress image for Log-in header } elsif (!$is_custom) { if ($img ne 'domlogo') { - $output .= &mt('Default image:').'
'; + $output = &mt('Default image:').'
'; } else { - $output .= &mt('Default in use:').'
'; + $output = &mt('Default in use:').'
'; } } - if ($img eq 'login') { # suppress image for Log-in header - $output .= ''.$logincolors; - } else { + if ($img ne 'login') { if ($img_import) { $output .= ''; } @@ -2105,7 +2531,7 @@ sub print_quotas { } sub print_requestmail { - my ($dom,$action,$settings,$rowtotal) = @_; + my ($dom,$action,$settings,$rowtotal,$customcss,$rowstyle) = @_; my ($now,$datatable,%currapp); $now = time; if (ref($settings) eq 'HASH') { @@ -2117,7 +2543,19 @@ sub print_requestmail { } my $numinrow = 2; my $css_class; - $css_class = ($$rowtotal%2? ' class="LC_odd_row"':''); + if ($$rowtotal%2) { + $css_class = 'LC_odd_row'; + } + if ($customcss) { + $css_class .= " $customcss"; + } + $css_class =~ s/^\s+//; + if ($css_class) { + $css_class = ' class="'.$css_class.'"'; + } + if ($rowstyle) { + $css_class .= ' style="'.$rowstyle.'"'; + } my $text; if ($action eq 'requestcourses') { $text = &mt('Receive notification of course requests requiring approval'); @@ -2240,8 +2678,7 @@ sub print_textbookcourses { (' 'x2). ''.&mt('Thumbnail:'); if ($image) { - $datatable .= ''. - $imgsrc. + $datatable .= $imgsrc. ' '. ' '.&mt('Replace:').' '; @@ -2272,7 +2709,7 @@ sub print_textbookcourses { $datatable .= ''; } $datatable .= ' '."\n". - ''.&mt('Add').''."\n". + ''.&mt('Add').''."\n". ''. ''.&mt('Subject:').' '."\n". (' 'x2). @@ -2289,13 +2726,13 @@ sub print_textbookcourses { } else { $datatable .= ''; } + $datatable .= ''."\n"; } - $datatable .= ''."\n". - ''.&mt('LON-CAPA course:').' '. + $datatable .= ''.&mt('LON-CAPA course:').' '. &Apache::loncommon::select_dom_form($env{'request.role.domain'},$type.'_addbook_cdom'). ''. &Apache::loncommon::selectcourse_link - ('display',$type.'_addbook_cnum',$type.'_addbook_cdom',undef,undef,undef,'Course'); + ('display',$type.'_addbook_cnum',$type.'_addbook_cdom',undef,undef,undef,'Course'). ''."\n". ''."\n"; $itemcount ++; @@ -2395,10 +2832,270 @@ $jstext{'templates'}; ENDSCRIPT } +sub wafproxy_javascript { + my ($dom) = @_; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + +sub autoupdate_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); + my ($defdom,$runon,$runoff,$coownerson,$coownersoff,$failsafe); if (ref($settings) eq 'HASH') { if (exists($settings->{'run'})) { if ($settings->{'run'} eq '0') { @@ -2432,6 +3129,9 @@ sub print_autoenroll { if (exists($settings->{'sender_domain'})) { $defdom = $settings->{'sender_domain'}; } + if (exists($settings->{'autofailsafe'})) { + $failsafe = $settings->{'autofailsafe'}; + } } else { if ($autorun) { $runon = ' checked="checked" '; @@ -2467,56 +3167,87 @@ sub print_autoenroll { $coownerson.' value="1" />'.&mt('Yes').' '. ''. - ''; - $$rowtotal += 3; + ''. + ''.&mt('Failsafe for no drops when institutional data missing').''. + ''. + ''; + $$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; my $locknamesettings; $datatable .= &insttypes_row($settings,$types,$usertypes, $dom,$numinrow,$othertitle, - 'lockablenames'); + 'lockablenames',$rowtotal); $$rowtotal ++; } else { my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); @@ -2597,236 +3328,1048 @@ sub print_autocreate { } sub print_directorysrch { - my ($dom,$settings,$rowtotal) = @_; - my $srchon = ' '; - my $srchoff = ' checked="checked" '; - my ($exacton,$containson,$beginson); - my $localon = ' '; - my $localoff = ' checked="checked" '; - if (ref($settings) eq 'HASH') { - if ($settings->{'available'} eq '1') { - $srchon = $srchoff; - $srchoff = ' '; - } - if ($settings->{'localonly'} eq '1') { - $localon = $localoff; - $localoff = ' '; - } - if (ref($settings->{'searchtypes'}) eq 'ARRAY') { - foreach my $type (@{$settings->{'searchtypes'}}) { - if ($type eq 'exact') { + my ($position,$dom,$settings,$rowtotal) = @_; + my $datatable; + if ($position eq 'top') { + my $instsrchon = ' '; + my $instsrchoff = ' checked="checked" '; + my ($exacton,$containson,$beginson); + my $instlocalon = ' '; + my $instlocaloff = ' checked="checked" '; + if (ref($settings) eq 'HASH') { + if ($settings->{'available'} eq '1') { + $instsrchon = $instsrchoff; + $instsrchoff = ' '; + } + if ($settings->{'localonly'} eq '1') { + $instlocalon = $instlocaloff; + $instlocaloff = ' '; + } + if (ref($settings->{'searchtypes'}) eq 'ARRAY') { + foreach my $type (@{$settings->{'searchtypes'}}) { + if ($type eq 'exact') { + $exacton = ' checked="checked" '; + } elsif ($type eq 'contains') { + $containson = ' checked="checked" '; + } elsif ($type eq 'begins') { + $beginson = ' checked="checked" '; + } + } + } else { + if ($settings->{'searchtypes'} eq 'exact') { + $exacton = ' checked="checked" '; + } elsif ($settings->{'searchtypes'} eq 'contains') { + $containson = ' checked="checked" '; + } elsif ($settings->{'searchtypes'} eq 'specify') { $exacton = ' checked="checked" '; - } elsif ($type eq 'contains') { $containson = ' checked="checked" '; - } elsif ($type eq 'begins') { - $beginson = ' checked="checked" '; } } - } else { - if ($settings->{'searchtypes'} eq 'exact') { - $exacton = ' checked="checked" '; - } elsif ($settings->{'searchtypes'} eq 'contains') { - $containson = ' checked="checked" '; - } elsif ($settings->{'searchtypes'} eq 'specify') { - $exacton = ' checked="checked" '; - $containson = ' checked="checked" '; - } } - } - my ($searchtitles,$titleorder) = &sorted_searchtitles(); - my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my ($searchtitles,$titleorder) = &sorted_searchtitles(); + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); - my $numinrow = 4; - my $cansrchrow = 0; - my $datatable=''. - ''.&mt('Directory search available?').''. - ' '. - ''. - ''. - ''.&mt('Other domains can search?').''. - ' '. - ''. - ''; - $$rowtotal += 2; - if (ref($usertypes) eq 'HASH') { - if (keys(%{$usertypes}) > 0) { - $datatable .= &insttypes_row($settings,$types,$usertypes,$dom, - $numinrow,$othertitle,'cansearch'); - $cansrchrow = 1; + my $numinrow = 4; + my $cansrchrow = 0; + $datatable=''. + ''.&mt('Institutional directory search available?').''. + ' '. + ''. + ''. + ''.&mt('Other domains can search institution?').''. + ' '. + ''. + ''; + $$rowtotal += 2; + if (ref($usertypes) eq 'HASH') { + if (keys(%{$usertypes}) > 0) { + $datatable .= &insttypes_row($settings,$types,$usertypes,$dom, + $numinrow,$othertitle,'cansearch', + $rowtotal); + $cansrchrow = 1; + } } - } - if ($cansrchrow) { - $$rowtotal ++; - $datatable .= ''; - } else { - $datatable .= ''; - } - $datatable .= ''.&mt('Supported search methods'). - ''; - foreach my $title (@{$titleorder}) { - if (defined($searchtitles->{$title})) { - my $check = ' '; - if (ref($settings) eq 'HASH') { - if (ref($settings->{'searchby'}) eq 'ARRAY') { - if (grep(/^\Q$title\E$/,@{$settings->{'searchby'}})) { - $check = ' checked="checked" '; + if ($cansrchrow) { + $$rowtotal ++; + $datatable .= ''; + } else { + $datatable .= ''; + } + $datatable .= ''; - $$rowtotal ++; - if ($cansrchrow) { - $datatable .= ''; + $datatable .= '
'.&mt('Supported search methods'). + ''; + foreach my $title (@{$titleorder}) { + if (defined($searchtitles->{$title})) { + my $check = ' '; + if (ref($settings) eq 'HASH') { + if (ref($settings->{'searchby'}) eq 'ARRAY') { + if (grep(/^\Q$title\E$/,@{$settings->{'searchby'}})) { + $check = ' checked="checked" '; + } } } + $datatable .= ''; } - $datatable .= ''; } - } - $datatable .= '
'. + ''. - '
'; + $$rowtotal ++; + if ($cansrchrow) { + $datatable .= ''; + } else { + $datatable .= ''; + } + $datatable .= ''.&mt('Search latitude').''. + ''. + ' '. + ' '. + ''; + $$rowtotal ++; } else { - $datatable .= ''; + my $domsrchon = ' checked="checked" '; + my $domsrchoff = ' '; + my $domlocalon = ' '; + my $domlocaloff = ' checked="checked" '; + if (ref($settings) eq 'HASH') { + if ($settings->{'lclocalonly'} eq '1') { + $domlocalon = $domlocaloff; + $domlocaloff = ' '; + } + if ($settings->{'lcavailable'} eq '0') { + $domsrchoff = $domsrchon; + $domsrchon = ' '; + } + } + $datatable=''. + ''.&mt('LON-CAPA directory search available?').''. + ' '. + ''. + ''. + ''.&mt('Other domains can search LON-CAPA domain?').''. + ' '. + ''. + ''; + $$rowtotal += 2; } - $datatable .= ''.&mt('Search latitude').''. - ''. - ' '. - ' '. - ''; - $$rowtotal ++; return $datatable; } sub print_contacts { - my ($dom,$settings,$rowtotal) = @_; + my ($position,$dom,$settings,$rowtotal) = @_; my $datatable; my @contacts = ('adminemail','supportemail'); - my (%checked,%to,%otheremails,%bccemails); - my @mailings = ('errormail','packagesmail','lonstatusmail','helpdeskmail', - 'requestsmail','updatesmail','idconflictsmail'); - foreach my $type (@mailings) { - $otheremails{$type} = ''; - } - $bccemails{'helpdeskmail'} = ''; - if (ref($settings) eq 'HASH') { - foreach my $item (@contacts) { - if (exists($settings->{$item})) { - $to{$item} = $settings->{$item}; + my (%checked,%to,%otheremails,%bccemails,%includestr,%includeloc,%currfield, + $maxsize,$fields,$fieldtitles,$fieldoptions,$possoptions,@mailings,%lonstatus); + if ($position eq 'top') { + if (ref($settings) eq 'HASH') { + foreach my $item (@contacts) { + if (exists($settings->{$item})) { + $to{$item} = $settings->{$item}; + } } } + } elsif ($position eq 'middle') { + @mailings = ('errormail','packagesmail','lonstatusmail','requestsmail', + 'updatesmail','idconflictsmail','hostipmail'); foreach my $type (@mailings) { - if (exists($settings->{$type})) { - if (ref($settings->{$type}) eq 'HASH') { - foreach my $item (@contacts) { - if ($settings->{$type}{$item}) { - $checked{$type}{$item} = ' checked="checked" '; + $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) { + $otheremails{$type} = ''; + } + $bccemails{'helpdeskmail'} = ''; + $bccemails{'otherdomsmail'} = ''; + $includestr{'helpdeskmail'} = ''; + $includestr{'otherdomsmail'} = ''; + ($fields,$fieldtitles,$fieldoptions,$possoptions) = &helpform_fields(); + } + if (ref($settings) eq 'HASH') { + unless (($position eq 'top') || ($position eq 'lower')) { + foreach my $type (@mailings) { + if (exists($settings->{$type})) { + if (ref($settings->{$type}) eq 'HASH') { + foreach my $item (@contacts) { + if ($settings->{$type}{$item}) { + $checked{$type}{$item} = ' checked="checked" '; + } + } + $otheremails{$type} = $settings->{$type}{'others'}; + if (($type eq 'helpdeskmail') || ($type eq 'otherdomsmail')) { + $bccemails{$type} = $settings->{$type}{'bcc'}; + if ($settings->{$type}{'include'} ne '') { + ($includeloc{$type},$includestr{$type}) = split(/:/,$settings->{$type}{'include'},2); + $includestr{$type} = &unescape($includestr{$type}); + } } } - $otheremails{$type} = $settings->{$type}{'others'}; - if ($type eq 'helpdeskmail') { - $bccemails{$type} = $settings->{$type}{'bcc'}; + } elsif ($type eq 'lonstatusmail') { + $checked{'lonstatusmail'}{'adminemail'} = ' checked="checked" '; + } + } + } + if ($position eq 'bottom') { + foreach my $type (@mailings) { + $bccemails{$type} = $settings->{$type}{'bcc'}; + if ($settings->{$type}{'include'} ne '') { + ($includeloc{$type},$includestr{$type}) = split(/:/,$settings->{$type}{'include'},2); + $includestr{$type} = &unescape($includestr{$type}); + } + } + if (ref($settings->{'helpform'}) eq 'HASH') { + if (ref($fields) eq 'ARRAY') { + foreach my $field (@{$fields}) { + $currfield{$field} = $settings->{'helpform'}{$field}; + } + } + if (exists($settings->{'helpform'}{'maxsize'})) { + $maxsize = $settings->{'helpform'}{'maxsize'}; + } else { + $maxsize = '1.0'; + } + } else { + if (ref($fields) eq 'ARRAY') { + foreach my $field (@{$fields}) { + $currfield{$field} = 'yes'; } } - } elsif ($type eq 'lonstatusmail') { - $checked{'lonstatusmail'}{'adminemail'} = ' checked="checked" '; + $maxsize = '1.0'; } } } else { - $to{'supportemail'} = $Apache::lonnet::perlvar{'lonSupportEMail'}; - $to{'adminemail'} = $Apache::lonnet::perlvar{'lonAdmEMail'}; - $checked{'errormail'}{'adminemail'} = ' checked="checked" '; - $checked{'packagesmail'}{'adminemail'} = ' checked="checked" '; - $checked{'helpdeskmail'}{'supportemail'} = ' checked="checked" '; - $checked{'lonstatusmail'}{'adminemail'} = ' checked="checked" '; - $checked{'requestsmail'}{'adminemail'} = ' checked="checked" '; - $checked{'updatesmail'}{'adminemail'} = ' checked="checked" '; - $checked{'idconflictsmail'}{'adminemail'} = ' checked="checked" '; + if ($position eq 'top') { + $to{'supportemail'} = $Apache::lonnet::perlvar{'lonSupportEMail'}; + $to{'adminemail'} = $Apache::lonnet::perlvar{'lonAdmEMail'}; + $checked{'errormail'}{'adminemail'} = ' checked="checked" '; + $checked{'packagesmail'}{'adminemail'} = ' checked="checked" '; + $checked{'lonstatusmail'}{'adminemail'} = ' checked="checked" '; + $checked{'requestsmail'}{'adminemail'} = ' checked="checked" '; + $checked{'updatesmail'}{'adminemail'} = ' checked="checked" '; + $checked{'idconflictsmail'}{'adminemail'} = ' checked="checked" '; + $checked{'hostipmail'}{'adminemail'} = ' checked="checked" '; + } elsif ($position eq 'bottom') { + $checked{'helpdeskmail'}{'supportemail'} = ' checked="checked" '; + $checked{'otherdomsmail'}{'supportemail'} = ' checked="checked" '; + if (ref($fields) eq 'ARRAY') { + foreach my $field (@{$fields}) { + $currfield{$field} = 'yes'; + } + } + $maxsize = '1.0'; + } } my ($titles,$short_titles) = &contact_titles(); my $rownum = 0; my $css_class; - foreach my $item (@contacts) { + if ($position eq 'top') { + foreach my $item (@contacts) { + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''.$titles->{$item}. + ''. + ''; + $rownum ++; + } + } elsif ($position eq 'bottom') { $css_class = $rownum%2?' class="LC_odd_row"':''; - $datatable .= ''. - ''.$titles->{$item}. - ''. - ''; + $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 ++; } - foreach my $type (@mailings) { + unless (($position eq 'top') || ($position eq 'lower')) { + foreach my $type (@mailings) { + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''. + $titles->{$type}.': '. + ''; + if (($type eq 'helpdeskmail') || ($type eq 'otherdomsmail')) { + $datatable .= '
'.&mt('E-mail recipient(s)').''; + } + $datatable .= ''; + foreach my $item (@contacts) { + $datatable .= ' '; + } + $datatable .= '
'.&mt('Others').':  '. + ''; + my %locchecked; + if (($type eq 'helpdeskmail') || ($type eq 'otherdomsmail')) { + foreach my $loc ('s','b') { + if ($includeloc{$type} eq $loc) { + $locchecked{$loc} = ' checked="checked"'; + last; + } + } + $datatable .= '
'.&mt('Bcc:').(' 'x6). + '
'. + '
'.&mt('Optional added text').''. + &mt('Text automatically added to e-mail:').' '. + '
'. + ''.&mt('Location:').' '. + ''. + (' 'x2). + ''. + '
'; + } + $datatable .= ''."\n"; + $rownum ++; + } + } + if ($position eq 'middle') { + my %choices; + 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]', + $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', + 'reportstatus' => 'on'); + (my $reports,$rownum) = &radiobutton_prefs($settings,\@toggles,\%defaultchecked, + \%choices,$rownum); + $datatable .= $reports; + } elsif ($position eq 'lower') { + my (%current,%excluded,%weights); + my ($defaults,$names) = &Apache::loncommon::lon_status_items(); + if ($lonstatus{'threshold'} =~ /^\d+$/) { + $current{'errorthreshold'} = $lonstatus{'threshold'}; + } else { + $current{'errorthreshold'} = $defaults->{'threshold'}; + } + if ($lonstatus{'sysmail'} =~ /^\d+$/) { + $current{'errorsysmail'} = $lonstatus{'sysmail'}; + } else { + $current{'errorsysmail'} = $defaults->{'sysmail'}; + } + if (ref($lonstatus{'weights'}) eq 'HASH') { + foreach my $type ('E','W','N','U') { + if ($lonstatus{'weights'}{$type} =~ /^\d+$/) { + $weights{$type} = $lonstatus{'weights'}{$type}; + } else { + $weights{$type} = $defaults->{$type}; + } + } + } else { + foreach my $type ('E','W','N','U') { + $weights{$type} = $defaults->{$type}; + } + } + if (ref($lonstatus{'excluded'}) eq 'ARRAY') { + if (@{$lonstatus{'excluded'}} > 0) { + map {$excluded{$_} = 1; } @{$lonstatus{'excluded'}}; + } + } + foreach my $item ('errorthreshold','errorsysmail') { + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''. + $titles->{$item}. + ''. + ''; + $rownum ++; + } $css_class = $rownum%2?' class="LC_odd_row"':''; $datatable .= ''. - ''. - $titles->{$type}.': '. ''. - ''; - foreach my $item (@contacts) { - $datatable .= ' '; - } - $datatable .= '
'.&mt('Others').':  '. - ''; - if ($type eq 'helpdeskmail') { - $datatable .= '
'.&mt('Bcc:').(' 'x6). - ''; + ''.$titles->{'errorweights'}. + ''; + foreach my $type ('E','W','N','U') { + $datatable .= ''; } - $datatable .= ''."\n"; + $datatable .= '
'.$names->{$type}.'
'. + '
'; $rownum ++; + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + $titles->{'errorexcluded'}.''. + ''; + my $numinrow = 4; + my @ids = sort(values(%Apache::lonnet::serverhomeIDs)); + for (my $i=0; $i<@ids; $i++) { + my $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + my $check; + if ($excluded{$ids[$i]}) { + $check = ' checked="checked" '; + } + $datatable .= ''; + } + my $colsleft = $numinrow - @ids%($numinrow); + if ($colsleft > 1 ) { + $datatable .= ''; + } elsif ($colsleft == 1) { + $datatable .= ''; + } + $datatable .= '
'. + ''. + '  
'; + $rownum ++; + } elsif ($position eq 'bottom') { + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my (@posstypes,%usertypeshash); + 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}); + } + } + } + } + } + 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 ++; + } + } + } } - 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)); - $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'); - my %defaultchecked = ('reporterrors' => 'on', - 'reportupdates' => 'on'); - (my $reports,$rownum) = &radiobutton_prefs($settings,\@toggles,\%defaultchecked, - \%choices,$rownum); - $datatable .= $reports; $$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"; + + + +ENDSCRIPT +} + sub print_helpsettings { - my ($dom,$confname,$settings,$rowtotal) = @_; + my ($position,$dom,$settings,$rowtotal) = @_; + my $confname = $dom.'-domainconfig'; + my $formname = 'display'; my ($datatable,$itemcount); - $itemcount = 1; - my (%choices,%defaultchecked,@toggles); - $choices{'submitbugs'} = &mt('Display link to: [_1]?', - &Apache::loncommon::modal_link('http://bugs.loncapa.org', - &mt('LON-CAPA bug tracker'),600,500)); - %defaultchecked = ('submitbugs' => 'on'); - @toggles = ('submitbugs',); - - ($datatable,$itemcount) = &radiobutton_prefs($settings,\@toggles,\%defaultchecked, - \%choices,$itemcount); + if ($position eq 'top') { + $itemcount = 1; + my (%choices,%defaultchecked,@toggles); + $choices{'submitbugs'} = &mt('Display link to: [_1]?', + &Apache::loncommon::modal_link('http://bugs.loncapa.org', + &mt('LON-CAPA bug tracker'),600,500)); + %defaultchecked = ('submitbugs' => 'on'); + @toggles = ('submitbugs'); + ($datatable,$itemcount) = &radiobutton_prefs($settings,\@toggles,\%defaultchecked, + \%choices,$itemcount); + $$rowtotal ++; + } else { + my $css_class; + my %existing=&Apache::lonnet::dump('roles',$dom,$confname,'rolesdef_'); + my (%customroles,%ordered,%current); + if (ref($settings) eq 'HASH') { + if (ref($settings->{'adhoc'}) eq 'HASH') { + %current = %{$settings->{'adhoc'}}; + } + } + my $count = 0; + foreach my $key (sort(keys(%existing))) { + if ($key=~/^rolesdef\_(\w+)$/) { + my $rolename = $1; + my (%privs,$order); + ($privs{'system'},$privs{'domain'},$privs{'course'}) = split(/\_/,$existing{$key}); + $customroles{$rolename} = \%privs; + if (ref($current{$rolename}) eq 'HASH') { + $order = $current{$rolename}{'order'}; + } + if ($order eq '') { + $order = $count; + } + $ordered{$order} = $rolename; + $count++; + } + } + my $maxnum = scalar(keys(%ordered)); + my @roles_by_num = (); + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@roles_by_num,$item); + } + my $context = 'domprefs'; + my $crstype = 'Course'; + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my @accesstypes = ('all','dh','da','none'); + my ($numstatustypes,@jsarray); + if (ref($types) eq 'ARRAY') { + if (@{$types} > 0) { + $numstatustypes = scalar(@{$types}); + push(@accesstypes,'status'); + @jsarray = ('bystatus'); + } + } + my %domhelpdesk = &Apache::lonnet::get_active_domroles($dom,['dh','da']); + if (keys(%domhelpdesk)) { + push(@accesstypes,('inc','exc')); + push(@jsarray,('notinc','notexc')); + } + my $hiddenstr = join("','",@jsarray); + $datatable .= &helpsettings_javascript(\@roles_by_num,$maxnum,$hiddenstr,$formname); + my $context = 'domprefs'; + my $crstype = 'Course'; + my $prefix = 'helproles_'; + my $add_class = 'LC_hidden'; + foreach my $num (@roles_by_num) { + my $role = $ordered{$num}; + my ($desc,$access,@statuses); + if (ref($current{$role}) eq 'HASH') { + $desc = $current{$role}{'desc'}; + $access = $current{$role}{'access'}; + if (ref($current{$role}{'insttypes'}) eq 'ARRAY') { + @statuses = @{$current{$role}{'insttypes'}}; + } + } + if ($desc eq '') { + $desc = $role; + } + my $identifier = 'custhelp'.$num; + my %full=(); + my %levels= ( + course => {}, + domain => {}, + system => {}, + ); + my %levelscurrent=( + course => {}, + domain => {}, + system => {}, + ); + &Apache::lonuserutils::custom_role_privs($customroles{$role},\%full,\%levels,\%levelscurrent); + my @templateroles = &Apache::lonuserutils::custom_template_roles($context,$crstype); + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $chgstr = ' onchange="javascript:reorderHelpRoles(this.form,'."'helproles_".$num."_pos'".');"'; + $datatable .= ''.$role.'
'. + ''.(' 'x2). + ''. + ''. + '
'.&mt('Role name').''. + &mt('Name shown to users:'). + ''. + '
'. + &helpdeskroles_access($dom,$prefix,$num,$add_class,$current{$role},\@accesstypes, + $othertitle,$usertypes,$types,\%domhelpdesk). + '
'. + ''.&mt('Role privileges').&adhocbutton($prefix,$num,'privs','show').''. + &Apache::lonuserutils::custom_role_table($crstype,\%full,\%levels, + \%levelscurrent,$identifier, + 'LC_hidden',$prefix.$num.'_privs'). + '
'; + $itemcount ++; + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $newcust = 'custhelp'.$count; + my (%privs,%levelscurrent); + my %full=(); + my %levels= ( + course => {}, + domain => {}, + system => {}, + ); + &Apache::lonuserutils::custom_role_privs(\%privs,\%full,\%levels,\%levelscurrent); + my @templateroles = &Apache::lonuserutils::custom_template_roles($context,$crstype); + my $chgstr = ' onchange="javascript:reorderHelpRoles(this.form,'."'helproles_".$count."_pos'".');"'; + $datatable .= ''. + '
'.&mt('Role name').''. + ''. + &mt('Internal name:'). + ''. + ''.(' 'x4). + ''. + &mt('Name shown to users:'). + ''. + '
'. + &helpdeskroles_access($dom,$prefix,$count,'',undef,\@accesstypes,$othertitle, + $usertypes,$types,\%domhelpdesk). + '
'.&mt('Role privileges').''. + &Apache::lonuserutils::custom_role_header($context,$crstype, + \@templateroles,$newcust). + &Apache::lonuserutils::custom_role_table('Course',\%full,\%levels, + \%levelscurrent,$newcust). + '
'. + &helpsettings_javascript(\@roles_by_num,$maxnum,$hiddenstr,$formname). + ''; + $count ++; + $$rowtotal += $count; + } return $datatable; } +sub adhocbutton { + my ($prefix,$num,$field,$visibility) = @_; + my %lt = &Apache::lonlocal::texthash( + show => 'Show details', + hide => 'Hide details', + ); + return ''.(' 'x10). + ''.(' 'x2).''.(' 'x2); +} + +sub helpsettings_javascript { + my ($roles_by_num,$total,$hiddenstr,$formname) = @_; + return unless(ref($roles_by_num) eq 'ARRAY'); + my %html_js_lt = &Apache::lonlocal::texthash( + show => 'Show details', + hide => 'Hide details', + ); + &html_escape(\%html_js_lt); + my $jstext = ' var helproles = Array('."'".join("','",@{$roles_by_num})."'".');'."\n"; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + +sub helpdeskroles_access { + my ($dom,$prefix,$num,$add_class,$current,$accesstypes,$othertitle, + $usertypes,$types,$domhelpdesk) = @_; + return unless ((ref($accesstypes) eq 'ARRAY') && (ref($domhelpdesk) eq 'HASH')); + my %lt = &Apache::lonlocal::texthash( + 'rou' => 'Role usage', + 'whi' => 'Which helpdesk personnel may use this role?', + 'all' => 'All with domain helpdesk or helpdesk assistant role', + 'dh' => 'All with domain helpdesk role', + 'da' => 'All with domain helpdesk assistant role', + 'none' => 'None', + 'status' => 'Determined based on institutional status', + 'inc' => 'Include all, but exclude specific personnel', + 'exc' => 'Exclude all, but include specific personnel', + ); + my %usecheck = ( + all => ' checked="checked"', + ); + my %displaydiv = ( + status => 'none', + inc => 'none', + exc => 'none', + priv => 'block', + ); + my $output; + if (ref($current) eq 'HASH') { + if (($current->{'access'} ne '') && ($current->{'access'} ne 'all')) { + if (grep(/^\Q$current->{access}\E$/,@{$accesstypes})) { + $usecheck{$current->{access}} = $usecheck{'all'}; + delete($usecheck{'all'}); + if ($current->{access} =~ /^(status|inc|exc)$/) { + my $access = $1; + $displaydiv{$access} = 'inline'; + } elsif ($current->{access} eq 'none') { + $displaydiv{'priv'} = 'none'; + } + } + } + } + $output = '
'.$lt{'rou'}.''. + '

'.$lt{'whi'}.'

'; + foreach my $access (@{$accesstypes}) { + $output .= '

'; + if ($access eq 'status') { + $output .= '

'. + &Apache::lonuserutils::adhoc_status_types($dom,$prefix,$num,$current->{$access}, + $othertitle,$usertypes,$types). + '
'; + } elsif (($access eq 'inc') && (keys(%{$domhelpdesk}) > 0)) { + $output .= '
'. + &Apache::lonuserutils::adhoc_staff($access,$prefix,$num,$current->{$access},$domhelpdesk). + '
'; + } elsif (($access eq 'exc') && (keys(%{$domhelpdesk}) > 0)) { + $output .= '
'. + &Apache::lonuserutils::adhoc_staff($access,$prefix,$num,$current->{$access},$domhelpdesk). + '
'; + } + $output .= '

'; + } + $output .= '
'; + return $output; +} + 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')); @@ -2866,15 +4409,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); @@ -2890,13 +4439,16 @@ 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)', + 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)", + mysqltables => 'Lifetime (s) of "Temporary" MySQL tables (student performance data) on homeserver', ); my %staticdefaults = ( anonsurvey_threshold => 10, uploadquota => 500, postsubmit => 60, + mysqltables => 172800, ); if ($position eq 'top') { %defaultchecked = ( @@ -2905,8 +4457,36 @@ sub print_coursedefaults { 'canclone' => 'none', ); @toggles = ('uselcmath','usejsme'); + my $deftex = $Apache::lonnet::deftex; + if (ref($settings) eq 'HASH') { + if ($settings->{'texengine'}) { + if ($settings->{'texengine'} =~ /^(MathJax|mimetex|tth)$/) { + $deftex = $settings->{'texengine'}; + } + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $mathdisp = ''. + ''.$choices{'texengine'}. + ''. + ''."\n"; + $itemcount ++; ($datatable,$itemcount) = &radiobutton_prefs($settings,\@toggles,\%defaultchecked, \%choices,$itemcount); + $datatable = $mathdisp.$datatable; $css_class = $itemcount%2?' class="LC_odd_row"':''; $datatable .= ''. @@ -2915,7 +4495,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 ...', @@ -2980,7 +4560,7 @@ sub print_coursedefaults { $itemcount ++; } else { $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; - my ($currdefresponder,%defcredits,%curruploadquota,%deftimeout); + my ($currdefresponder,%defcredits,%curruploadquota,%deftimeout,%currmysql); my $currusecredits = 0; my $postsubmitclient = 1; my @types = ('official','unofficial','community','textbook'); @@ -3024,6 +4604,15 @@ sub print_coursedefaults { $deftimeout{$type} = $staticdefaults{'postsubmit'}; } } + if (ref($settings->{'mysqltables'}) eq 'HASH') { + foreach my $type (keys(%{$settings->{'mysqltables'}})) { + $currmysql{$type} = $settings->{'mysqltables'}{$type}; + } + } else { + foreach my $type (@types) { + $currmysql{$type} = $staticdefaults{'mysqltables'}; + } + } } else { foreach my $type (@types) { $deftimeout{$type} = $staticdefaults{'postsubmit'}; @@ -3101,13 +4690,27 @@ sub print_coursedefaults { $additional .= '
'."\n"; %defaultchecked = ('postsubmit' => 'on'); @toggles = ('postsubmit'); - my $current = { - 'postsubmit' => $postsubmitclient, - }; + $current = { + 'postsubmit' => $postsubmitclient, + }; ($table,$itemcount) = &radiobutton_prefs($current,\@toggles,\%defaultchecked, \%choices,$itemcount,$onclick,$additional,'left'); $datatable .= $table; + $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; + $datatable .= ''. + $choices{'mysqltables'}. + ''. + ''. + ''; + foreach my $type (@types) { + $datatable .= ''; + } + $datatable .= '
'.&mt($type).'
'. + '
'."\n"; + $itemcount ++; + } $$rowtotal += $itemcount; return $datatable; @@ -3301,7 +4904,7 @@ sub print_validation_rows { '
'; } } elsif ($item eq 'markup') { - $datatable .= ''; } @@ -3323,7 +4926,7 @@ sub print_validation_rows { my ($numdc,$dctable,$rows) = &active_dc_picker($dom,$numinrow,'radio', 'validationdc',%currhash); my $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; - $datatable .= ''; + $datatable .= ''; if ($numdc > 1) { $datatable .= &mt('Course creation processed as: (choose Dom. Coord.)'); } else { @@ -3338,6 +4941,735 @@ sub print_validation_rows { return $datatable; } +sub print_passwords { + my ($position,$dom,$confname,$settings,$rowtotal) = @_; + my ($datatable,$css_class); + my $itemcount = 0; + my %titles = &Apache::lonlocal::texthash ( + captcha => '"Forgot Password" CAPTCHA validation', + link => 'Reset link expiration (hours)', + case => 'Case-sensitive usernames/e-mail', + prelink => 'Information required (form 1)', + postlink => 'Information required (form 2)', + emailsrc => 'LON-CAPA e-mail address type(s)', + customtext => 'Domain specific text (HTML)', + intauth_cost => 'Encryption cost for bcrypt (positive integer)', + intauth_check => 'Check bcrypt cost if authenticated', + intauth_switch => 'Existing crypt-based switched to bcrypt on authentication', + permanent => 'Permanent e-mail address', + critical => 'Critical notification address', + notify => 'Notification address', + min => 'Minimum password length', + max => 'Maximum password length', + chars => 'Required characters', + numsaved => 'Number of previous passwords to save and disallow reuse', + ); + if ($position eq 'top') { + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my $shownlinklife = 2; + my $prelink = 'both'; + my (%casesens,%postlink,%emailsrc,$nostdtext,$customurl); + if (ref($settings) eq 'HASH') { + if ($settings->{resetlink} =~ /^\d+(|\.\d*)$/) { + $shownlinklife = $settings->{resetlink}; + } + if (ref($settings->{resetcase}) eq 'ARRAY') { + map { $casesens{$_} = 1; } (@{$settings->{resetcase}}); + } + if ($settings->{resetprelink} =~ /^(both|either)$/) { + $prelink = $settings->{resetprelink}; + } + if (ref($settings->{resetpostlink}) eq 'HASH') { + %postlink = %{$settings->{resetpostlink}}; + } + if (ref($settings->{resetemail}) eq 'ARRAY') { + map { $emailsrc{$_} = 1; } (@{$settings->{resetemail}}); + } + if ($settings->{resetremove}) { + $nostdtext = 1; + } + if ($settings->{resetcustom}) { + $customurl = $settings->{resetcustom}; + } + } else { + if (ref($types) eq 'ARRAY') { + foreach my $item (@{$types}) { + $casesens{$item} = 1; + $postlink{$item} = ['username','email']; + } + } + $casesens{'default'} = 1; + $postlink{'default'} = ['username','email']; + $prelink = 'both'; + %emailsrc = ( + permanent => 1, + critical => 1, + notify => 1, + ); + } + $datatable = &captcha_choice('passwords',$settings,$$rowtotal); + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'link'}.''. + ''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'case'}.''. + ''; + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + foreach my $item (@{$types}) { + my $checkedcase; + if ($casesens{$item}) { + $checkedcase = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + } + my $checkedcase; + if ($casesens{'default'}) { + $checkedcase = ' checked="checked"'; + } + $datatable .= ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my %checkedpre = ( + both => ' checked="checked"', + either => '', + ); + if ($prelink eq 'either') { + $checkedpre{either} = ' checked="checked"'; + $checkedpre{both} = ''; + } + $datatable .= ''.$titles{'prelink'}.''. + ''. + '   '. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'postlink'}.''. + ''; + my %postlinked; + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + foreach my $item (@{$types}) { + undef(%postlinked); + $datatable .= '
'. + ''.$usertypes->{$item}.''; + if (ref($postlink{$item}) eq 'ARRAY') { + map { $postlinked{$_} = 1; } (@{$postlink{$item}}); + } + foreach my $field ('email','username') { + my $checked; + if ($postlinked{$field}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + $datatable .= '
'; + } + } + if (ref($postlink{'default'}) eq 'ARRAY') { + map { $postlinked{$_} = 1; } (@{$postlink{'default'}}); + } + $datatable .= '
'. + ''.$othertitle.''; + foreach my $field ('email','username') { + my $checked; + if ($postlinked{$field}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + $datatable .= '
'; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''.$titles{'emailsrc'}.''. + ''; + foreach my $type ('permanent','critical','notify') { + my $checkedemail; + if ($emailsrc{$type}) { + $checkedemail = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + $datatable .= ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $switchserver = &check_switchserver($dom,$confname); + my ($showstd,$noshowstd); + if ($nostdtext) { + $noshowstd = ' checked="checked"'; + } else { + $showstd = ' checked="checked"'; + } + $datatable .= ''.$titles{'customtext'}.''. + ''. + &mt('Retain standard text:'). + ''.' '. + '
'. + ''. + &mt('(If you use the same account ... reset a password from this page.)').'

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

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

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

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

'. ''."\n". ''. + ' '.&mt('Switch any active user on next access').'
'. + "\n"; + if ($other_insts) { + $datatable .= '
'. + ''."\n". + ''. "\n"; + } my (%current,%canselect); my @choices = &possible_newspares($server,$spareid->{$server},$serverhomes,$altids); @@ -3767,13 +6117,13 @@ sub print_loadbalancing { my $numinrow = 1; my $datatable; my %servers = &Apache::lonnet::internet_dom_servers($dom); - my (%currbalancer,%currtargets,%currrules,%existing); + my (%currbalancer,%currtargets,%currrules,%existing,%currcookies); if (ref($settings) eq 'HASH') { %existing = %{$settings}; } if ((keys(%servers) > 1) || (keys(%existing) > 0)) { &get_loadbalancers_config(\%servers,\%existing,\%currbalancer, - \%currtargets,\%currrules); + \%currtargets,\%currrules,\%currcookies); } else { return; } @@ -3847,6 +6197,12 @@ sub print_loadbalancing { my ($numspares,@spares) = &count_servers($lonhost,%servers); my @sparestypes = ('primary','default'); my %typetitles = &sparestype_titles(); + my %hostherechecked = ( + no => ' checked="checked"', + ); + my %balcookiechecked = ( + no => ' checked="checked"', + ); foreach my $sparetype (@sparestypes) { my $targettable; for (my $i=0; $i<$numspares; $i++) { @@ -3892,8 +6248,35 @@ sub print_loadbalancing { $datatable .= ''.$typetitles{$sparetype}.'
'. ''.$targettable.'

'; } + $hostherechecked{$sparetype} = ''; + if (ref($currtargets{$lonhost}) eq 'HASH') { + if (ref($currtargets{$lonhost}{$sparetype}) eq 'ARRAY') { + if (grep(/^\Q$lonhost\E$/,@{$currtargets{$lonhost}{$sparetype}})) { + $hostherechecked{$sparetype} = ' checked="checked"'; + $hostherechecked{'no'} = ''; + } + } + } } - $datatable .= ''. + if ($currcookies{$lonhost}) { + %balcookiechecked = ( + yes => ' checked="checked"', + ); + } + $datatable .= &mt('Hosting on balancer itself').'
'. + '
'; + foreach my $sparetype (@sparestypes) { + $datatable .= '
'; + } + $datatable .= &mt('Use balancer cookie').'
'. + '
'. + '
'. + ''. &loadbalancing_rules($dom,$intdom,$currrules{$lonhost}, $othertitle,$usertypes,$types,\%servers, \%currbalancer,$lonhost, @@ -3907,10 +6290,11 @@ sub print_loadbalancing { } sub get_loadbalancers_config { - my ($servers,$existing,$currbalancer,$currtargets,$currrules) = @_; + my ($servers,$existing,$currbalancer,$currtargets,$currrules,$currcookies) = @_; return unless ((ref($servers) eq 'HASH') && (ref($existing) eq 'HASH') && (ref($currbalancer) eq 'HASH') && - (ref($currtargets) eq 'HASH') && (ref($currrules) eq 'HASH')); + (ref($currtargets) eq 'HASH') && (ref($currrules) eq 'HASH') && + (ref($currcookies) eq 'HASH')); if (keys(%{$existing}) > 0) { my $oldlonhost; foreach my $key (sort(keys(%{$existing}))) { @@ -3929,6 +6313,9 @@ sub get_loadbalancers_config { $currbalancer->{$key} = 1; $currtargets->{$key} = $existing->{$key}{'targets'}; $currrules->{$key} = $existing->{$key}{'rules'}; + if ($existing->{$key}{'cookie'}) { + $currcookies->{$key} = 1; + } } } } else { @@ -3984,9 +6371,14 @@ sub loadbalancing_titles { '_LC_ipchange' => &mt('Non-SSO users with IP mismatch'), ); my @alltypes = ('_LC_adv','_LC_author','_LC_internetdom','_LC_external','_LC_ipchangesso','_LC_ipchange'); + my @available; if (ref($types) eq 'ARRAY') { - unshift(@alltypes,@{$types},'default'); + @available = @{$types}; } + unless (grep(/^default$/,@available)) { + push(@available,'default'); + } + unshift(@alltypes,@available); my %titles; foreach my $type (@alltypes) { if ($type =~ /^_LC_/) { @@ -4109,15 +6501,21 @@ sub sparestype_titles { sub contact_titles { my %titles = &Apache::lonlocal::texthash ( - 'supportemail' => 'Support E-mail address', - '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 to be e-mailed to', - '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', + 'supportemail' => 'Support E-mail address', + '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 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', @@ -4126,6 +6524,34 @@ sub contact_titles { return (\%titles,\%short_titles); } +sub helpform_fields { + my %titles = &Apache::lonlocal::texthash ( + 'username' => 'Name', + 'user' => 'Username/domain', + 'phone' => 'Phone', + 'cc' => 'Cc e-mail', + 'course' => 'Course Details', + 'section' => 'Sections', + 'screenshot' => 'File upload', + ); + my @fields = ('username','phone','user','course','section','cc','screenshot'); + my %possoptions = ( + username => ['yes','no','req'], + phone => ['yes','no','req'], + user => ['yes','no'], + cc => ['yes','no'], + course => ['yes','no'], + section => ['yes','no'], + screenshot => ['yes','no'], + ); + my %fieldoptions = &Apache::lonlocal::texthash ( + 'yes' => 'Optional', + 'req' => 'Required', + 'no' => "Not shown", + ); + return (\@fields,\%titles,\%fieldoptions,\%possoptions); +} + sub tool_titles { my %titles = &Apache::lonlocal::texthash ( aboutme => 'Personal web page', @@ -4310,7 +6736,8 @@ sub print_usercreation { sub print_selfcreation { my ($position,$dom,$settings,$rowtotal) = @_; - my (@selfcreate,$createsettings,$processing,$datatable); + my (@selfcreate,$createsettings,$processing,$emailoptions,$emailverified, + $emaildomain,$datatable); if (ref($settings) eq 'HASH') { if (ref($settings->{'cancreate'}) eq 'HASH') { $createsettings = $settings->{'cancreate'}; @@ -4327,12 +6754,22 @@ sub print_selfcreation { if (ref($createsettings->{'selfcreateprocessing'}) eq 'HASH') { $processing = $createsettings->{'selfcreateprocessing'}; } + if (ref($createsettings->{'emailoptions'}) eq 'HASH') { + $emailoptions = $createsettings->{'emailoptions'}; + } + if (ref($createsettings->{'emailverified'}) eq 'HASH') { + $emailverified = $createsettings->{'emailverified'}; + } + if (ref($createsettings->{'emaildomain'}) eq 'HASH') { + $emaildomain = $createsettings->{'emaildomain'}; + } } } } my %radiohash; my $numinrow = 4; map { $radiohash{'cancreate_'.$_} = 1; } @selfcreate; + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); if ($position eq 'top') { my %choices = &Apache::lonlocal::texthash ( cancreate_login => 'Institutional Login', @@ -4347,14 +6784,12 @@ sub print_selfcreation { ($datatable,$itemcount) = &radiobutton_prefs(\%radiohash,\@toggles,\%defaultchecked, \%choices,$itemcount,$onclick); $$rowtotal += $itemcount; - - my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); if (ref($usertypes) eq 'HASH') { if (keys(%{$usertypes}) > 0) { $datatable .= &insttypes_row($createsettings,$types,$usertypes, $dom,$numinrow,$othertitle, - 'statustocreate',$$rowtotal); + 'statustocreate',$rowtotal); $$rowtotal ++; } } @@ -4367,7 +6802,7 @@ sub print_selfcreation { $datatable .= ''. ''.&mt('Mapping of Shibboleth environment variable names to user data fields (SSO auth)').''. ''."\n". - '\n". + '\n"; return $output; } sub captcha_choice { - my ($context,$settings,$itemcount) = @_; - my ($keyentry,$currpub,$currpriv,%checked,$rowname,$pubtext,$privtext); + my ($context,$settings,$itemcount,$customcss,$rowstyle) = @_; + my ($keyentry,$currpub,$currpriv,%checked,$rowname,$pubtext,$privtext, + $vertext,$currver); my %lt = &captcha_phrases(); $keyentry = 'hidden'; + my $colspan=2; if ($context eq 'cancreate') { $rowname = &mt('CAPTCHA validation'); } elsif ($context eq 'login') { $rowname = &mt('"Contact helpdesk" CAPTCHA validation'); + } elsif ($context eq 'passwords') { + $rowname = &mt('"Forgot Password" CAPTCHA validation'); + $colspan=1; } if (ref($settings) eq 'HASH') { if ($settings->{'captcha'}) { @@ -4553,6 +7269,11 @@ sub captcha_choice { $pubtext = $lt{'pub'}; $privtext = $lt{'priv'}; $keyentry = 'text'; + $vertext = $lt{'ver'}; + $currver = $settings->{'recaptchaversion'}; + if ($currver ne '2') { + $currver = 1; + } } if (ref($settings->{'recaptchakeys'}) eq 'HASH') { $currpub = $settings->{'recaptchakeys'}{'public'}; @@ -4561,9 +7282,22 @@ sub captcha_choice { } else { $checked{'original'} = ' checked="checked"'; } - my $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $css_class; + if ($itemcount%2) { + $css_class = 'LC_odd_row'; + } + if ($customcss) { + $css_class .= " $customcss"; + } + $css_class =~ s/^\s+//; + if ($css_class) { + $css_class = ' class="'.$css_class.'"'; + } + if ($rowstyle) { + $css_class .= ' style="'.$rowstyle.'"'; + } my $output = ''. - '
'."\n"; + ''."\n"; for (my $i=0; $i<@fields; $i++) { $rem = $i%($numperrow); if ($rem == 0) { @@ -4399,149 +6834,430 @@ sub print_selfcreation { $$rowtotal ++; } elsif ($position eq 'middle') { my %domconf = &Apache::lonnet::get_dom('configuration',['usermodification'],$dom); - my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); - $usertypes->{'default'} = $othertitle; + my @posstypes; if (ref($types) eq 'ARRAY') { - push(@{$types},'default'); - $usertypes->{'default'} = $othertitle; - foreach my $status (@{$types}) { - $datatable .= &modifiable_userdata_row('selfcreate',$status,$domconf{'usermodification'}, - $numinrow,$$rowtotal,$usertypes); - $$rowtotal ++; - } + @posstypes = @{$types}; + } + unless (grep(/^default$/,@posstypes)) { + push(@posstypes,'default'); + } + my %usertypeshash; + if (ref($usertypes) eq 'HASH') { + %usertypeshash = %{$usertypes}; + } + $usertypeshash{'default'} = $othertitle; + foreach my $status (@posstypes) { + $datatable .= &modifiable_userdata_row('selfcreate',$status,$domconf{'usermodification'}, + $numinrow,$$rowtotal,\%usertypeshash); + $$rowtotal ++; } } else { my %choices = &Apache::lonlocal::texthash ( - cancreate_email => 'E-mail address as username', + 'cancreate_email' => 'Non-institutional username (via e-mail verification)', ); my @toggles = sort(keys(%choices)); my %defaultchecked = ( 'cancreate_email' => 'off', ); - my $itemcount = 0; + my $customclass = 'LC_selfcreate_email'; + my $classprefix = 'LC_canmodify_emailusername_'; + my $optionsprefix = 'LC_options_emailusername_'; my $display = 'none'; + my $rowstyle = 'display:none'; if (grep(/^\Qemail\E$/,@selfcreate)) { $display = 'block'; + $rowstyle = 'display:table-row'; } - my $onclick = "toggleDisplay(this.form,'emailoptions');"; - my $additional = '
'; + my $onclick = "toggleRows(this.form,'cancreate_email','selfassign','$customclass','$classprefix','$optionsprefix');"; + ($datatable,$$rowtotal) = &radiobutton_prefs(\%radiohash,\@toggles,\%defaultchecked, + \%choices,$$rowtotal,$onclick); + $datatable .= &print_requestmail($dom,'selfcreation',$createsettings,$rowtotal,$customclass, + $rowstyle); + $$rowtotal ++; + $datatable .= &captcha_choice('cancreate',$createsettings,$$rowtotal,$customclass, + $rowstyle); + $$rowtotal ++; + my (@ordered,@posstypes,%usertypeshash); my %domdefaults = &Apache::lonnet::get_domain_defaults($dom); - my $usertypes = {}; - my $order = []; - if ((ref($domdefaults{'inststatustypes'}) eq 'HASH') && (ref($domdefaults{'inststatusguest'}) eq 'ARRAY')) { - $usertypes = $domdefaults{'inststatustypes'}; - $order = $domdefaults{'inststatusguest'}; - } - if (ref($order) eq 'ARRAY') { - push(@{$order},'default'); - if (@{$order} > 1) { - $usertypes->{'default'} = &mt('Other users'); - $additional .= '
'; - foreach my $status (@{$order}) { - $additional .= ''; - } - $additional .= ''; - foreach my $status (@{$order}) { - $additional .= ''; - } - $additional .= '
'.$usertypes->{$status}.'
'.&email_as_username($rowtotal,$processing,$status).'
'; + my ($emailrules,$emailruleorder) = + &Apache::lonnet::inst_userrules($dom,'email'); + my $primary_id = &Apache::lonnet::domain($dom,'primary'); + my $intdom = &Apache::lonnet::internet_dom($primary_id); + if (ref($types) eq 'ARRAY') { + @posstypes = @{$types}; + } + if (@posstypes) { + unless (grep(/^default$/,@posstypes)) { + push(@posstypes,'default'); + } + if (ref($usertypes) eq 'HASH') { + %usertypeshash = %{$usertypes}; + } + my $currassign; + if (ref($domdefaults{'inststatusguest'}) eq 'ARRAY') { + $currassign = { + selfassign => $domdefaults{'inststatusguest'}, + }; + @ordered = @{$domdefaults{'inststatusguest'}}; } else { - $usertypes->{'default'} = &mt('All users'); - $additional .= &email_as_username($rowtotal,$processing); + $currassign = { selfassign => [] }; + } + my $onclicktypes = "toggleDataRow(this.form,'selfassign','$customclass','$optionsprefix',);". + "toggleDataRow(this.form,'selfassign','$customclass','$classprefix',1);"; + $datatable .= &insttypes_row($currassign,$types,$usertypes,$dom, + $numinrow,$othertitle,'selfassign', + $rowtotal,$onclicktypes,$customclass, + $rowstyle); + $$rowtotal ++; + $usertypeshash{'default'} = $othertitle; + foreach my $status (@posstypes) { + my $css_class; + if ($$rowtotal%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$/,@ordered)) { + $currstyle = $rowstyle; + $hidden = 0; + } + $datatable .= &noninst_users($processing,$emailverified,$emailoptions,$emaildomain, + $emailrules,$emailruleorder,$settings,$status,$rowid, + $usertypeshash{$status},$css_class,$currstyle,$intdom); + unless ($hidden) { + $$rowtotal ++; + } + } + } else { + my $css_class; + if ($$rowtotal%2) { + $css_class = 'LC_odd_row '; } + $css_class .= $customclass; + $usertypeshash{'default'} = $othertitle; + $datatable .= &noninst_users($processing,$emailverified,$emailoptions,$emaildomain, + $emailrules,$emailruleorder,$settings,'default','', + $othertitle,$css_class,$rowstyle,$intdom); + $$rowtotal ++; } - $additional .= ''."\n"; - - ($datatable,$itemcount) = &radiobutton_prefs(\%radiohash,\@toggles,\%defaultchecked, - \%choices,$$rowtotal,$onclick,$additional); - $$rowtotal ++; - $datatable .= &print_requestmail($dom,'selfcreation',$createsettings,$rowtotal); - $$rowtotal ++; my ($infofields,$infotitles) = &Apache::loncommon::emailusername_info(); $numinrow = 1; - if (ref($order) eq 'ARRAY') { - foreach my $status (@{$order}) { + if (@posstypes) { + foreach my $status (@posstypes) { + my $rowid = $classprefix.$status; + my $datarowstyle = 'display:none'; + if (grep(/^\Q$status\E$/,@ordered)) { + $datarowstyle = $rowstyle; + } $datatable .= &modifiable_userdata_row('cancreate','emailusername_'.$status,$settings, - $numinrow,$$rowtotal,$usertypes,$infofields,$infotitles); - $$rowtotal ++; + $numinrow,$$rowtotal,\%usertypeshash,$infofields, + $infotitles,$rowid,$customclass,$datarowstyle); + unless ($datarowstyle eq 'display:none') { + $$rowtotal ++; + } } + } else { + $datatable .= &modifiable_userdata_row('cancreate','emailusername_default',$settings, + $numinrow,$$rowtotal,\%usertypeshash,$infofields, + $infotitles,'',$customclass,$rowstyle); } - my ($emailrules,$emailruleorder) = - &Apache::lonnet::inst_userrules($dom,'email'); - if (ref($emailrules) eq 'HASH') { - if (keys(%{$emailrules}) > 0) { - $datatable .= &user_formats_row('email',$settings,$emailrules, - $emailruleorder,$numinrow,$$rowtotal); - $$rowtotal ++; + } + return $datatable; +} + +sub selfcreate_javascript { + return <<"ENDSCRIPT"; + + + +ENDSCRIPT +} + +sub noninst_users { + my ($processing,$emailverified,$emailoptions,$emaildomain,$emailrules, + $emailruleorder,$settings,$type,$rowid,$typetitle,$css_class,$rowstyle,$intdom) = @_; + 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); + if ($type eq 'default') { + $description = &mt('Requests for: [_1]',$typetitle); + } else { + $description = &mt('Requests for: [_1] (status self-reported)',$typetitle); + } + $output = ''. + "
$description'. + ''; + my %headers = &Apache::lonlocal::texthash( + approve => 'Processing', + email => 'E-mail', + username => 'Username', + ); + foreach my $item ('approve','email','username') { + $output .= ''; + } + $output .= ''; + foreach my $item ('approve','email','username') { + $output .= ''."\n"; } - $$rowtotal ++; + $output .= "
'.$headers{$item}.'
'; + my (%choices,@options,$hashref,$defoption,$name,$onclick,$hascustom); + if ($item eq 'approve') { + %choices = &Apache::lonlocal::texthash ( + automatic => 'Automatically approved', + approval => 'Queued for approval', + ); + @options = ('automatic','approval'); + $hashref = $processing; + $defoption = 'automatic'; + $name = 'cancreate_emailprocess_'.$type; + } elsif ($item eq 'email') { + %choices = &Apache::lonlocal::texthash ( + any => 'Any e-mail', + inst => 'Institutional only', + noninst => 'Non-institutional only', + custom => 'Custom restrictions', + ); + @options = ('any','inst','noninst'); + my $showcustom; + if (ref($emailrules) eq 'HASH') { + if (keys(%{$emailrules}) > 0) { + push(@options,'custom'); + $showcustom = 'cancreate_emailrule'; + if (ref($settings) eq 'HASH') { + if (ref($settings->{'email_rule'}) eq 'ARRAY') { + foreach my $rule (@{$settings->{'email_rule'}}) { + if (exists($emailrules->{$rule})) { + $hascustom ++; + } + } + } elsif (ref($settings->{'email_rule'}) eq 'HASH') { + if (ref($settings->{'email_rule'}{$type}) eq 'ARRAY') { + foreach my $rule (@{$settings->{'email_rule'}{$type}}) { + if (exists($emailrules->{$rule})) { + $hascustom ++; + } + } + } + } + } + } + } + $onclick = ' onclick="toggleEmailOptions(this.form,'."'cancreate_emailoptions','$showcustom',". + "'cancreate_emaildomain','$type'".');"'; + $hashref = $emailoptions; + $defoption = 'any'; + $name = 'cancreate_emailoptions_'.$type; + } elsif ($item eq 'username') { + %choices = &Apache::lonlocal::texthash ( + all => 'Same as e-mail', + first => 'Omit @domain', + free => 'Free to choose', + ); + @options = ('all','first','free'); + $hashref = $emailverified; + $defoption = 'all'; + $name = 'cancreate_usernameoptions_'.$type; + } + foreach my $option (@options) { + my $checked; + if (ref($hashref) eq 'HASH') { + if ($type eq '') { + if (!exists($hashref->{'default'})) { + if ($option eq $defoption) { + $checked = ' checked="checked"'; + } + } else { + if ($hashref->{'default'} eq $option) { + $checked = ' checked="checked"'; + } } } else { - if ($processing->{$type} eq $option) { - $checked = ' checked="checked"'; + if (!exists($hashref->{$type})) { + if ($option eq $defoption) { + $checked = ' checked="checked"'; + } + } else { + if ($hashref->{$type} eq $option) { + $checked = ' checked="checked"'; + } } } + } elsif (($item eq 'email') && ($hascustom)) { + if ($option eq 'custom') { + $checked = ' checked="checked"'; + } + } elsif ($option eq $defoption) { + $checked = ' checked="checked"'; + } + $output .= '
'; + if ($item eq 'email') { + if ($option eq 'custom') { + my $id = 'cancreate_emailrule_'.$type; + my $display = 'none'; + if ($checked) { + $display = 'inline'; + } + my $numinrow = 2; + $output .= '
'. + ''.&mt('Disallow').''. + &user_formats_row('email',$settings,$emailrules, + $emailruleorder,$numinrow,'',$type); + '
'; + } elsif (($option eq 'inst') || ($option eq 'noninst')) { + my %text = &Apache::lonlocal::texthash ( + inst => 'must end:', + noninst => 'cannot end:', + ); + my $value; + if (ref($emaildomain) eq 'HASH') { + if (ref($emaildomain->{$type}) eq 'HASH') { + $value = $emaildomain->{$type}->{$option}; + } + } + if ($value eq '') { + $value = '@'.$intdom; + } + my $condition = 'cancreate_emaildomain_'.$option.'_'.$type; + my $display = 'none'; + if ($checked) { + $display = 'inline'; + } + $output .= '
'. + ''.$text{$option}.' '. + ''. + '
'; + } } - } elsif ($option eq 'automatic') { - $checked = ' checked="checked"'; - } - my $name = 'cancreate_emailprocess'; - if (($type ne '') && ($type ne 'default')) { - $name .= '_'.$type; - } - $output .= ''; - if ($type eq '') { - $output .= ' '; - } else { - $output .= '
'; } + $output .= '
'.$rowname.''."\n". + ''.$rowname.''."\n". '
'."\n"; foreach my $option ('original','recaptcha','notused') { $output .= ''."\n". + '
'."\n". ''.$pubtext.' '."\n". '
'."\n". ''.$privtext.' '."\n". '
'."\n". + $currpriv.'" size="40" />
'. + ''.$vertext.' '."\n". + '
'. + '
'."\n". ''; return $output; } sub user_formats_row { - my ($type,$settings,$rules,$ruleorder,$numinrow,$rowcount) = @_; + my ($type,$settings,$rules,$ruleorder,$numinrow,$rowcount,$status) = @_; my $output; my %text = ( 'username' => 'new usernames', 'id' => 'IDs', - 'email' => 'self-created accounts (e-mail)', ); - my $css_class = $rowcount%2?' class="LC_odd_row"':''; - $output = ''. - ''; - if ($type eq 'email') { - $output .= &mt("Formats disallowed for $text{$type}: "); - } else { - $output .= &mt("Format rules to check for $text{$type}: "); + unless ($type eq 'email') { + my $css_class = $rowcount%2?' class="LC_odd_row"':''; + $output = ''. + ''. + &mt("Format rules to check for $text{$type}: "). + ''; } - $output .= ''. - ''; + $output .= '
'; my $rem; if (ref($ruleorder) eq 'ARRAY') { for (my $i=0; $i<@{$ruleorder}; $i++) { @@ -4626,25 +7360,41 @@ sub user_formats_row { if (grep(/^\Q$ruleorder->[$i]\E$/,@{$settings->{$type.'_rule'}})) { $check = ' checked="checked" '; } + } elsif ((ref($settings->{$type.'_rule'}) eq 'HASH') && ($status ne '')) { + if (ref($settings->{$type.'_rule'}->{$status}) eq 'ARRAY') { + if (grep(/^\Q$ruleorder->[$i]\E$/,@{$settings->{$type.'_rule'}->{$status}})) { + $check = ' checked="checked" '; + } + } } } + my $name = $type.'_rule'; + if ($type eq 'email') { + $name .= '_'.$status; + } $output .= ''; } } $rem = @{$ruleorder}%($numinrow); } - my $colsleft = $numinrow - $rem; + my $colsleft; + if ($rem) { + $colsleft = $numinrow - $rem; + } if ($colsleft > 1 ) { $output .= ''; } elsif ($colsleft == 1) { $output .= ''; } - $output .= '
'. ''. '  
'; + unless ($type eq 'email') { + $output .= ''; + } return $output; } @@ -4721,7 +7471,10 @@ sub print_usermodification { sub print_defaults { my ($position,$dom,$settings,$rowtotal) = @_; my $rownum = 0; - my ($datatable,$css_class); + my ($datatable,$css_class,$titles); + unless ($position eq 'bottom') { + $titles = &defaults_titles($dom); + } if ($position eq 'top') { my @items = ('auth_def','auth_arg_def','lang_def','timezone_def', 'datelocale_def','portal_def'); @@ -4734,7 +7487,6 @@ sub print_defaults { $defaults{$item} = $domdefaults{$item}; } } - my $titles = &defaults_titles($dom); foreach my $item (@items) { if ($rownum%2) { $css_class = ''; @@ -4783,19 +7535,14 @@ sub print_defaults { $rownum ++; } } else { - my (%defaults); + my %defaults; if (ref($settings) eq 'HASH') { - if ((ref($settings->{'inststatusorder'}) eq 'ARRAY') && (ref($settings->{'inststatustypes'}) eq 'HASH') && - (ref($settings->{'inststatusguest'}) eq 'ARRAY')) { + if ((ref($settings->{'inststatusorder'}) eq 'ARRAY') && (ref($settings->{'inststatustypes'}) eq 'HASH')) { my $maxnum = @{$settings->{'inststatusorder'}}; for (my $i=0; $i<$maxnum; $i++) { $css_class = $rownum%2?' class="LC_odd_row"':''; my $item = $settings->{'inststatusorder'}->[$i]; my $title = $settings->{'inststatustypes'}->{$item}; - my $guestok; - if (grep(/^\Q$item\E$/,@{$settings->{'inststatusguest'}})) { - $guestok = 1; - } my $chgstr = ' onchange="javascript:reorderTypes(this.form,'."'$item'".');"'; $datatable .= ''. ''. @@ -4808,23 +7555,12 @@ sub print_defaults { } $datatable .= ''; } - my ($checkedon,$checkedoff); - $checkedoff = ' checked="checked"'; - if ($guestok) { - $checkedon = $checkedoff; - $checkedoff = ''; - } $datatable .= ' '.&mt('Internal ID:').' '.$item.' '. ''. &mt('delete').''. - ''.&mt('Name displayed:'). + ''.&mt('Name displayed').':'. ''. - ''. - ''. - ''.(' 'x2). - ''; + ''; } $css_class = $rownum%2?' class="LC_odd_row"':''; my $chgstr = ' onchange="javascript:reorderTypes(this.form,'."'addinststatus_pos'".');"'; @@ -4842,13 +7578,8 @@ sub print_defaults { ''. ' '.&mt('(new)'). '
'. - &mt('Name displayed:'). + &mt('Name displayed').':'. ''. - ''. - ''.(' 'x2). - ''; ''."\n"; $rownum ++; } @@ -4878,6 +7609,9 @@ sub defaults_titles { 'timezone_def' => 'Default timezone', 'datelocale_def' => 'Default locale for dates', 'portal_def' => 'Portal/Default URL', + 'intauth_cost' => 'Encryption cost for bcrypt (positive integer)', + 'intauth_check' => 'Check bcrypt cost if authenticated', + 'intauth_switch' => 'Existing crypt-based switched to bcrypt on authentication', ); if ($dom) { my $uprimary_id = &Apache::lonnet::domain($dom,'primary'); @@ -4892,6 +7626,58 @@ sub defaults_titles { return (\%titles); } +sub print_scantron { + my ($r,$position,$dom,$confname,$settings,$rowtotal) = @_; + if ($position eq 'top') { + return &print_scantronformat($r,$dom,$confname,$settings,\$rowtotal); + } else { + return &print_scantronconfig($dom,$settings,\$rowtotal); + } +} + +sub scantron_javascript { + return <<"ENDSCRIPT"; + + + +ENDSCRIPT + +} + sub print_scantronformat { my ($r,$dom,$confname,$settings,$rowtotal) = @_; my $itemcount = 1; @@ -4918,8 +7704,8 @@ sub print_scantronformat { if ($configuserok eq 'ok') { if ($author_ok eq 'ok') { my %legacyfile = ( - default => $Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab', - custom => $Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab', + default => $Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab', + custom => $Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab', ); my %md5chk; foreach my $type (keys(%legacyfile)) { @@ -4928,7 +7714,7 @@ sub print_scantronformat { } if ($md5chk{'default'} ne $md5chk{'custom'}) { foreach my $type (keys(%legacyfile)) { - ($scantronurls{$type},my $error) = + ($scantronurls{$type},my $error) = &legacy_scantronformat($r,$dom,$confname, $type,$legacyfile{$type}, $scantronurls{$type}, @@ -4939,13 +7725,13 @@ sub print_scantronformat { } if (keys(%error) == 0) { $is_custom = 1; - $confhash{'scantron'}{'scantronformat'} = + $confhash{'scantron'}{'scantronformat'} = $scantronurls{'custom'}; - my $putresult = + my $putresult = &Apache::lonnet::put_dom('configuration', \%confhash,$dom); if ($putresult ne 'ok') { - $error{'custom'} = + $error{'custom'} = ''. &mt('An error occurred updating the domain configuration: [_1]',$putresult).''; } @@ -5065,6 +7851,129 @@ sub legacy_scantronformat { return ($url,$error); } +sub print_scantronconfig { + my ($dom,$settings,$rowtotal) = @_; + my $itemcount = 2; + my $is_checked = ' checked="checked"'; + my %optionson = ( + hdr => ' checked="checked"', + pad => ' checked="checked"', + rem => ' checked="checked"', + ); + my %optionsoff = ( + hdr => '', + pad => '', + rem => '', + ); + my $currcsvsty = 'none'; + my ($datatable,%csvfields,%checked,%onclick,%csvoptions); + my @fields = &scantroncsv_fields(); + my %titles = &scantronconfig_titles(); + if (ref($settings) eq 'HASH') { + if (ref($settings->{config}) eq 'HASH') { + if ($settings->{config}->{dat}) { + $checked{'dat'} = $is_checked; + } + if (ref($settings->{config}->{csv}) eq 'HASH') { + if (ref($settings->{config}->{csv}->{fields}) eq 'HASH') { + %csvfields = %{$settings->{config}->{csv}->{fields}}; + if (keys(%csvfields) > 0) { + $checked{'csv'} = $is_checked; + $currcsvsty = 'block'; + } + } + if (ref($settings->{config}->{csv}->{options}) eq 'HASH') { + %csvoptions = %{$settings->{config}->{csv}->{options}}; + foreach my $option (keys(%optionson)) { + unless ($csvoptions{$option}) { + $optionsoff{$option} = $optionson{$option}; + $optionson{$option} = ''; + } + } + } + } + } else { + $checked{'dat'} = $is_checked; + } + } else { + $checked{'dat'} = $is_checked; + } + $onclick{'csv'} = ' onclick="toggleScantron(this.form);"'; + my $css_class = $itemcount%2? ' class="LC_odd_row"':''; + $datatable = ''.&mt('Supported formats').''. + ''; + foreach my $item ('dat','csv') { + my $id; + if ($item eq 'csv') { + $id = 'id="scantronconfcsv" '; + } + $datatable .= ''.(' 'x3); + if ($item eq 'csv') { + $datatable .= '
'. + ''.&mt('CSV Column Mapping').''. + ''."\n"; + foreach my $col (@fields) { + my $selnone; + if ($csvfields{$col} eq '') { + $selnone = ' selected="selected"'; + } + $datatable .= ''. + ''; + } + $datatable .= '
'.&mt('Field').''.&mt('Location').'
'.$titles{$col}.'
'. + '
'. + ''.&mt('CSV Options').''; + foreach my $option ('hdr','pad','rem') { + $datatable .= ''.$titles{$option}.':'. + ''.(' 'x2)."\n". + '
'; + } + $datatable .= '
'; + $itemcount ++; + } + } + $datatable .= ''; + $$rowtotal ++; + return $datatable; +} + +sub scantronconfig_titles { + return &Apache::lonlocal::texthash( + dat => 'Standard format (.dat)', + csv => 'Comma separated values (.csv)', + hdr => 'Remove first line in file (contains column titles)', + pad => 'Prepend 0s to PaperID', + rem => 'Remove leading spaces (except Question Response columns)', + CODE => 'CODE', + ID => 'Student ID', + PaperID => 'Paper ID', + FirstName => 'First Name', + LastName => 'Last Name', + FirstQuestion => 'First Question Response', + Section => 'Section', + ); +} + +sub scantroncsv_fields { + return ('PaperID','LastName','FirstName','ID','Section','CODE','FirstQuestion'); +} + sub print_coursecategories { my ($position,$dom,$hdritem,$settings,$rowtotal) = @_; my $datatable; @@ -5107,7 +8016,7 @@ sub print_coursecategories { ''.$lt{$type}.' '; } - $datatable .= ''; + $datatable .= '
'; $itemcount ++; } $$rowtotal += $itemcount; @@ -5318,7 +8227,7 @@ sub print_coursecategories { $datatable .= &initialize_categories($itemcount); } } else { - $datatable .= ''.$hdritem->{'header'}->[1]->{'col2'}.'' + $datatable .= ''.$hdritem->{'header'}->[1]->{'col2'}.'' .&initialize_categories($itemcount); } $$rowtotal += $itemcount; @@ -5366,7 +8275,7 @@ sub print_serverstatuses { ''. ''. - ''."\n"; + ''."\n"; } $$rowtotal += $rownum; return $datatable; @@ -5442,6 +8351,94 @@ ENDSCRIPT } } +sub passwords_javascript { + my %intalert = &Apache::lonlocal::texthash ( + authcheck => 'Warning: disallowing login for an authenticated user if the stored cost is less than the default will require a password reset by/for the user.', + authcost => 'Warning: bcrypt encryption cost for internal authentication must be an integer.', + passmin => 'Warning: minimum password length must be a positive integer greater than 6.', + passmax => 'Warning: maximum password length must be a positive integer (or blank).', + passexp => 'Warning: days before password expiration must be a positive integer (or blank).', + passnum => 'Warning: number of previous passwords to save must be a positive integer (or blank).', + ); + &js_escape(\%intalert); + my $defmin = $Apache::lonnet::passwdmin; + my $intauthjs = <<"ENDSCRIPT"; + +function warnIntAuth(field) { + if (field.name == 'intauth_check') { + if (field.value == '2') { + alert('$intalert{authcheck}'); + } + } + if (field.name == 'intauth_cost') { + field.value.replace(/\s/g,''); + if (field.value != '') { + var regexdigit=/^\\d+\$/; + if (!regexdigit.test(field.value)) { + alert('$intalert{authcost}'); + } + } + } + return; +} + +function warnIntPass(field) { + field.value.replace(/^\s+/,''); + field.value.replace(/\s+\$/,''); + var regexdigit=/^\\d+\$/; + if (field.name == 'passwords_min') { + if (field.value == '') { + alert('$intalert{passmin}'); + field.value = '$defmin'; + } else { + if (!regexdigit.test(field.value)) { + alert('$intalert{passmin}'); + field.value = '$defmin'; + } + var minval = parseInt(field.value,10); + if (minval < $defmin) { + alert('$intalert{passmin}'); + field.value = '$defmin'; + } + } + } else { + if (field.value == '0') { + field.value = ''; + } + if (field.value != '') { + if (field.name == 'passwords_expire') { + var regexpposnum=/^\\d+(|\\.\\d*)\$/; + if (!regexpposnum.test(field.value)) { + alert('$intalert{passexp}'); + field.value = ''; + } else { + var expval = parseFloat(field.value); + if (expval == 0) { + alert('$intalert{passexp}'); + field.value = ''; + } + } + } else { + if (!regexdigit.test(field.value)) { + if (field.name == 'passwords_max') { + alert('$intalert{passmax}'); + } else { + if (field.name == 'passwords_numsaved') { + alert('$intalert{passnum}'); + } + } + field.value = ''; + } + } + } + } + return; +} + +ENDSCRIPT + return &Apache::lonhtmlcommon::scripttag($intauthjs); +} + sub coursecategories_javascript { my ($settings) = @_; my ($output,$jstext,$cathash); @@ -5551,7 +8548,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', ); @@ -5559,7 +8556,7 @@ sub initialize_categories { my $select1 = ''; foreach my $default ('instcode','communities') { $css_class = $itemcount%2?' class="LC_odd_row"':''; - $chgstr = ' onchange="javascript:reorderCats(this.form,'."'',$default"."_pos','0'".');"'; + $chgstr = ' onchange="javascript:reorderCats(this.form,'."'','$default"."_pos','0'".');"'; if ($default eq 'communities') { $select1 = $select0; $select0 = ''; @@ -5584,8 +8581,9 @@ sub initialize_categories { .'' .'' .' ' - .&mt('Add category').''.&mt('Name:') - .' '; + .&mt('Add category').'
'.&mt('Name:') + .' ' + .''; return $datatable; } @@ -5640,7 +8638,7 @@ sub build_category_rows { pop(@{$path}); } } else { - $text .= &mt('Add subcategory:').' '.&mt('Add subcategory:').''; + $text .= ''.&mt('Add subcategory:').''; } } } @@ -5672,13 +8670,14 @@ sub build_category_rows { } sub modifiable_userdata_row { - my ($context,$item,$settings,$numinrow,$rowcount,$usertypes,$fieldsref,$titlesref) = @_; + my ($context,$item,$settings,$numinrow,$rowcount,$usertypes,$fieldsref,$titlesref, + $rowid,$customcss,$rowstyle) = @_; my ($role,$rolename,$statustype); $role = $item; if ($context eq 'cancreate') { - if ($item =~ /^emailusername_(.+)$/) { - $statustype = $1; - $role = 'emailusername'; + if ($item =~ /^(emailusername)_(.+)$/) { + $role = $1; + $statustype = $2; if (ref($usertypes) eq 'HASH') { if ($usertypes->{$statustype}) { $rolename = &mt('Data provided by [_1]',$usertypes->{$statustype}); @@ -5713,8 +8712,25 @@ sub modifiable_userdata_row { %fieldtitles = &Apache::loncommon::personal_data_fieldtitles(); } my $output; - my $css_class = $rowcount%2?' class="LC_odd_row"':''; - $output = ''. + my $css_class; + if ($rowcount%2) { + $css_class = 'LC_odd_row'; + } + if ($customcss) { + $css_class .= " $customcss"; + } + $css_class =~ s/^\s+//; + if ($css_class) { + $css_class = ' class="'.$css_class.'"'; + } + if ($rowstyle) { + $css_class .= ' style="'.$rowstyle.'"'; + } + if ($rowid) { + $rowid = ' id="'.$rowid.'"'; + } + + $output = ''. ''.$rolename.''. ''; my $rem; @@ -5748,9 +8764,10 @@ sub modifiable_userdata_row { } } } - - for (my $i=0; $i<@fields; $i++) { - my $rem = $i%($numinrow); + + my $total = scalar(@fields); + for (my $i=0; $i<$total; $i++) { + $rem = $i%($numinrow); if ($rem == 0) { if ($i > 0) { $output .= ''; @@ -5760,7 +8777,7 @@ sub modifiable_userdata_row { my $check = ' '; unless ($role eq 'emailusername') { if (exists($checks{$fields[$i]})) { - $check = $checks{$fields[$i]} + $check = $checks{$fields[$i]}; } else { if ($role eq 'st') { if (ref($settings) ne 'HASH') { @@ -5792,10 +8809,13 @@ sub modifiable_userdata_row { ''; } $output .= ''; - $rem = @fields%($numinrow); } - my $colsleft = $numinrow - $rem; - if ($colsleft > 1 ) { + $rem = $total%$numinrow; + my $colsleft; + if ($rem) { + $colsleft = $numinrow - $rem; + } + if ($colsleft > 1) { $output .= ''; } elsif ($colsleft == 1) { @@ -5806,11 +8826,14 @@ sub modifiable_userdata_row { } sub insttypes_row { - my ($settings,$types,$usertypes,$dom,$numinrow,$othertitle,$context,$rownum) = @_; + my ($settings,$types,$usertypes,$dom,$numinrow,$othertitle,$context,$rowtotal,$onclick, + $customcss,$rowstyle) = @_; my %lt = &Apache::lonlocal::texthash ( cansearch => 'Users allowed to search', 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') { @@ -5820,9 +8843,22 @@ sub insttypes_row { if ($context eq 'statustocreate') { $class = 'LC_right_item'; } - my $css_class = ' class="LC_odd_row"'; - if ($rownum ne '') { - $css_class = ($rownum%2? ' class="LC_odd_row"':''); + my $css_class; + if ($$rowtotal%2) { + $css_class = 'LC_odd_row'; + } + if ($customcss) { + $css_class .= ' '.$customcss; + } + $css_class =~ s/^\s+//; + if ($css_class) { + $css_class = ' class="'.$css_class.'"'; + } + if ($rowstyle) { + $css_class .= ' style="'.$rowstyle.'"'; + } + if ($onclick) { + $onclick = 'onclick="'.$onclick.'" '; } my $output = ''. ''; } } $rem = @{$types}%($numinrow); } my $colsleft = $numinrow - $rem; - if (($rem == 0) && (@{$types} > 0)) { - $output .= ''; - } - if ($colsleft > 1) { - $output .= ''; + } + if ($colsleft > 1) { + $output .= ''. - '
'. ' '.$lt{$context}.$showdom. @@ -5844,6 +8880,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" '; } @@ -5851,36 +8891,45 @@ sub insttypes_row { $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 .= '
'; + } 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; } @@ -5957,12 +9006,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') { @@ -5970,6 +9021,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); @@ -6123,7 +9188,6 @@ sub modify_login { $errors .= '
  • '.$puberror.'
  • '; if ((grep(/^\Q$lang\E$/,@currlangs)) && (!grep(/^\Q$lang\E$/,@delurls))) { - $loginhash{'login'}{'helpurl'}{$lang} = $domconfig{'login'}{'helpurl'}{$lang}; } } @@ -6147,7 +9211,7 @@ sub modify_login { if ($domservers{$lonhost}) { if (ref($domconfig{'login'}{'headtag'}{$lonhost}) eq 'HASH') { $currheadtagurls{$lonhost} = $domconfig{'login'}{'headtag'}{$lonhost}{'url'}; - $currexempt{$lonhost} = $domconfig{'login'}{'headtag'}{$lonhost}{'exempt'} + $currexempt{$lonhost} = $domconfig{'login'}{'headtag'}{$lonhost}{'exempt'}; } } } @@ -6217,7 +9281,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'; @@ -6258,6 +9404,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; } @@ -6337,6 +9508,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; @@ -6373,6 +9576,13 @@ sub modify_login { $chgtxt .= ''; $resulttext .= '
  • '.$chgtxt.'
  • '; } + } elsif ($item eq 'recaptchaversion') { + if (ref($loginhash{'login'}) eq 'HASH') { + if ($loginhash{'login'}{'captcha'} eq 'recaptcha') { + $resulttext .= '
  • '.&mt('ReCAPTCHA for helpdesk form set to version [_1]',$loginhash{'login'}{'recaptchaversion'}). + '
  • '; + } + } } else { $resulttext .= '
  • '.&mt("$title{$item} set to $offon[$env{'form.'.$item}]").'
  • '; } @@ -6432,6 +9642,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); @@ -6835,7 +10322,7 @@ sub check_configuser { my ($configuserok,%currroles); if ($uhome eq 'no_host') { srand( time() ^ ($$ + ($$ << 15)) ); # Seed rand. - my $configpass = &LONCAPA::Enrollment::create_password(); + my $configpass = &LONCAPA::Enrollment::create_password($dom); $configuserok = &Apache::lonnet::modifyuser($dom,$confname,'','internal', $configpass,'','','','','',undef,$servadm); @@ -6909,14 +10396,14 @@ sub publishlogo { } else { my $source = $filepath.'/'.$file; my $logfile; - if (!open($logfile,">>$source".'.log')) { + if (!open($logfile,">>",$source.'.log')) { return (&mt('No write permission to Authoring Space')); } print $logfile "\n================= Publish ".localtime()." ================\n". $env{'user.name'}.':'.$env{'user.domain'}."\n"; # Save the file - if (!open(FH,'>'.$source)) { + if (!open(FH,">",$source)) { &Apache::lonnet::logthis('Failed to create '.$source); return (&mt('Failed to create file')); } @@ -6977,7 +10464,8 @@ $env{'user.name'}.':'.$env{'user.domain' if ($fullwidth ne '' && $fullheight ne '') { if ($fullwidth > $thumbwidth && $fullheight > $thumbheight) { my $thumbsize = $thumbwidth.'x'.$thumbheight; - system("convert -sample $thumbsize $inputfile $outfile"); + my @args = ('convert','-sample',$thumbsize,$inputfile,$outfile); + system({$args[0]} @args); chmod(0660, $filepath.'/tn-'.$file); if (-e $outfile) { my $copyfile=$targetdir.'/tn-'.$file; @@ -7056,7 +10544,7 @@ sub write_metadata { { print $logfile "\nWrite metadata file for ".$targetdir.'/'.$file; my $mfh; - if (open($mfh,'>'.$targetdir.'/'.$file.'.meta')) { + if (open($mfh,">",$targetdir.'/'.$file.'.meta')) { foreach (sort(keys(%metadatafields))) { unless ($_=~/\./) { my $unikey=$_; @@ -7090,7 +10578,7 @@ sub notifysubscribed { next unless (ref($targetsource) eq 'ARRAY'); my ($target,$source)=@{$targetsource}; if ($source ne '') { - if (open(my $logfh,'>>'.$source.'.log')) { + if (open(my $logfh,">>",$source.'.log')) { print $logfh "\nCleanup phase: Notifications\n"; my @subscribed=&subscribed_hosts($target); foreach my $subhost (@subscribed) { @@ -7116,7 +10604,7 @@ sub notifysubscribed { sub subscribed_hosts { my ($target) = @_; my @subscribed; - if (open(my $fh,"<$target.subscription")) { + if (open(my $fh,"<","$target.subscription")) { while (my $subline=<$fh>) { if ($subline =~ /^($match_lonid):/) { my $host = $1; @@ -7315,16 +10803,20 @@ sub modify_quotas { #FIXME need to obsolete item in RES space } elsif ($env{'form.'.$type.'_image_'.$i.'.filename'}) { my ($cdom,$cnum) = split(/_/,$key); - my ($imgurl,$error) = &process_textbook_image($r,$dom,$confname,$type.'_image_'.$i, - $cdom,$cnum,$type,$configuserok, - $switchserver,$author_ok); - if ($imgurl) { - $confhash{$type}{$key}{'image'} = $imgurl; - $changes{$type}{$key} = 1; - } - if ($error) { - &Apache::lonnet::logthis($error); - $errors .= '
  • '.$error.'
  • '; + if (&Apache::lonnet::homeserver($cnum,$cdom) eq 'no_host') { + $errors .= '
  • '.&mt('Image not saved: could not find textbook course').'
  • '; + } else { + my ($imgurl,$error) = &process_textbook_image($r,$dom,$confname,$type.'_image_'.$i, + $cdom,$cnum,$type,$configuserok, + $switchserver,$author_ok); + if ($imgurl) { + $confhash{$type}{$key}{'image'} = $imgurl; + $changes{$type}{$key} = 1; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } } } elsif ($domconfig{$action}{$type}{$key}{'image'}) { $confhash{$type}{$key}{'image'} = @@ -7358,15 +10850,19 @@ sub modify_quotas { if ($type eq 'textbooks') { if ($env{'form.'.$type.'_addbook_image.filename'} ne '') { my ($cdom,$cnum) = split(/_/,$newbook{$type}); - my ($imageurl,$error) = - &process_textbook_image($r,$dom,$confname,$type.'_addbook_image',$cdom,$cnum,$type, - $configuserok,$switchserver,$author_ok); - if ($imageurl) { - $confhash{$type}{$newbook{$type}}{'image'} = $imageurl; - } - if ($error) { - &Apache::lonnet::logthis($error); - $errors .= '
  • '.$error.'
  • '; + if (&Apache::lonnet::homeserver($cnum,$cdom) eq 'no_host') { + $errors .= '
  • '.&mt('Image not saved: could not find textbook course').'
  • '; + } else { + my ($imageurl,$error) = + &process_textbook_image($r,$dom,$confname,$type.'_addbook_image',$cdom,$cnum,$type, + $configuserok,$switchserver,$author_ok); + if ($imageurl) { + $confhash{$type}{$newbook{$type}}{'image'} = $imageurl; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } } } } @@ -7446,7 +10942,7 @@ sub modify_quotas { } if ($env{'form.validationdc'}) { my $newval = $env{'form.validationdc'}; - my %domcoords = &get_active_dcs($dom); + my %domcoords = &Apache::lonnet::get_active_domroles($dom,['dc']); if (exists($domcoords{$newval})) { $confhash{'validation'}{'dc'} = $newval; } @@ -7858,7 +11354,7 @@ sub process_textbook_image { } elsif ($author_ok eq 'ok') { my ($result,$imageurl) = &publishlogo($r,'upload',$caller,$dom,$confname, - "$type/$dom/$cnum/cover",$width,$height); + "$type/$cdom/$cnum/cover",$width,$height); if ($result eq 'ok') { $url = $imageurl; } else { @@ -7885,7 +11381,8 @@ sub modify_autoenroll { my $autorun = &Apache::lonnet::auto_run(undef,$dom), my %title = ( run => 'Auto-enrollment active', sender => 'Sender for notification messages', - coowners => 'Automatic assignment of co-ownership to instructors of record (institutional data)'); + coowners => 'Automatic assignment of co-ownership to instructors of record (institutional data)', + failsafe => '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'}; @@ -7895,11 +11392,17 @@ sub modify_autoenroll { $sender_domain = ''; } my $coowners = $env{'form.autoassign_coowners'}; + my $failsafe = $env{'form.autoenroll_failsafe'}; + $failsafe =~ s{^\s+|\s+$}{}g; + if ($failsafe =~ /\D/) { + undef($failsafe); + } my %autoenrollhash = ( autoenroll => { 'run' => $env{'form.autoenroll_run'}, 'sender_uname' => $sender_uname, 'sender_domain' => $sender_domain, 'co-owners' => $coowners, + 'autofailsafe' => $failsafe, } ); my $putresult = &Apache::lonnet::put_dom('configuration',\%autoenrollhash, @@ -7926,7 +11429,10 @@ sub modify_autoenroll { } } elsif ($coowners) { $changes{'coowners'} = 1; - } + } + if ($currautoenroll{'autofailsafe'} ne $failsafe) { + $changes{'autofailsafe'} = 1; + } if (keys(%changes) > 0) { $resulttext = &mt('Changes made:').'
      '; if ($changes{'run'}) { @@ -7946,6 +11452,17 @@ sub modify_autoenroll { $lastactref->{'domainconfig'} = 1; } } + if ($changes{'autofailsafe'}) { + if ($failsafe ne '') { + $resulttext .= '
    • '.&mt('Failsafe for no drops if institutional data missing for a section set to: [_1]',$failsafe).'
    • '; + } else { + $resulttext .= '
    • '.&mt('Failsafe for no drops if institutional data missing for a section: deleted'); + } + &Apache::lonnet::get_domain_defaults($dom,1); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; + } + } $resulttext .= '
    '; } else { $resulttext = &mt('No changes made to auto-enrollment settings'); @@ -7967,8 +11484,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 ( @@ -8012,12 +11531,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; @@ -8063,6 +11593,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') { @@ -8125,6 +11665,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}]; } @@ -8161,7 +11706,7 @@ sub modify_autocreate { $newvals{$item} = 0 if ($newvals{$item} eq ''); } $newvals{'xmldc'} = $env{'form.autocreate_xmldc'}; - my %domcoords = &get_active_dcs($dom); + my %domcoords = &Apache::lonnet::get_active_domroles($dom,['dc']); unless (exists($domcoords{$newvals{'xmldc'}})) { $newvals{'xmldc'} = ''; } @@ -8218,7 +11763,7 @@ sub modify_autocreate { } sub modify_directorysrch { - my ($dom,%domconfig) = @_; + my ($dom,$lastactref,%domconfig) = @_; my ($resulttext,%changes); my %currdirsrch; if (ref($domconfig{'directorysrch'}) eq 'HASH') { @@ -8226,8 +11771,10 @@ sub modify_directorysrch { $currdirsrch{$key} = $domconfig{'directorysrch'}{$key}; } } - my %title = ( available => 'Directory search available', - localonly => 'Other domains can search', + my %title = ( available => 'Institutional directory search available', + localonly => 'Other domains can search institution', + lcavailable => 'LON-CAPA directory search available', + lclocalonly => 'Other domains can search LON-CAPA domain', searchby => 'Search types', searchtypes => 'Search latitude'); my @offon = ('off','on'); @@ -8301,7 +11848,9 @@ sub modify_directorysrch { my %dirsrch_hash = ( directorysrch => { available => $env{'form.dirsrch_available'}, cansearch => \@cansearch, - localonly => $env{'form.dirsrch_localonly'}, + localonly => $env{'form.dirsrch_instlocalonly'}, + lclocalonly => $env{'form.dirsrch_domlocalonly'}, + lcavailable => $env{'form.dirsrch_domavailable'}, searchby => \@searchby, searchtypes => \@searchtypes, } @@ -8318,24 +11867,47 @@ sub modify_directorysrch { $changes{'available'} = 1; } } + if (exists($currdirsrch{'lcavailable'})) { + if ($currdirsrch{'lcavailable'} ne $env{'form.dirsrch_domavailable'}) { + $changes{'lcavailable'} = 1; + } + } else { + if ($env{'form.dirsrch_lcavailable'} eq '1') { + $changes{'lcavailable'} = 1; + } + } if (exists($currdirsrch{'localonly'})) { - if ($currdirsrch{'localonly'} ne $env{'form.dirsrch_localonly'}) { - $changes{'localonly'} = 1; - } + if ($currdirsrch{'localonly'} ne $env{'form.dirsrch_instlocalonly'}) { + $changes{'localonly'} = 1; + } } else { - if ($env{'form.dirsrch_localonly'} eq '1') { + if ($env{'form.dirsrch_instlocalonly'} eq '1') { $changes{'localonly'} = 1; } } + if (exists($currdirsrch{'lclocalonly'})) { + if ($currdirsrch{'lclocalonly'} ne $env{'form.dirsrch_domlocalonly'}) { + $changes{'lclocalonly'} = 1; + } + } else { + if ($env{'form.dirsrch_domlocalonly'} eq '1') { + $changes{'lclocalonly'} = 1; + } + } if (keys(%changes) > 0) { $resulttext = &mt('Changes made:').'
      '; if ($changes{'available'}) { $resulttext .= '
    • '.&mt("$title{'available'} set to: $offon[$env{'form.dirsrch_available'}]").'
    • '; } + if ($changes{'lcavailable'}) { + $resulttext .= '
    • '.&mt("$title{'lcavailable'} set to: $offon[$env{'form.dirsrch_domavailable'}]").'
    • '; + } if ($changes{'localonly'}) { - $resulttext .= '
    • '.&mt("$title{'localonly'} set to: $otherdoms[$env{'form.dirsrch_localonly'}]").'
    • '; + $resulttext .= '
    • '.&mt("$title{'localonly'} set to: $otherdoms[$env{'form.dirsrch_instlocalonly'}]").'
    • '; + } + if ($changes{'lclocalonly'}) { + $resulttext .= '
    • '.&mt("$title{'lclocalonly'} set to: $otherdoms[$env{'form.dirsrch_domlocalonly'}]").'
    • '; } - if (ref($changes{'cansearch'}) eq 'ARRAY') { my $chgtext; if (ref($usertypes) eq 'HASH') { @@ -8385,8 +11957,12 @@ sub modify_directorysrch { $resulttext .= '
    • '.&mt($title{'searchtypes'}.' set to: "[_1]"',$chgtext).'
    • '; } $resulttext .= '
    '; + &Apache::lonnet::do_cache_new('directorysrch',$dom,$dirsrch_hash{'directorysrch'},3600); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'directorysrch'} = 1; + } } else { - $resulttext = &mt('No changes made to institution directory search settings'); + $resulttext = &mt('No changes made to directory search settings'); } } else { $resulttext = ''. @@ -8403,11 +11979,13 @@ sub modify_contacts { $currsetting{$key} = $domconfig{'contacts'}{$key}; } } - my (%others,%to,%bcc); + my (%others,%to,%bcc,%includestr,%includeloc); my @contacts = ('supportemail','adminemail'); - my @mailings = ('errormail','packagesmail','helpdeskmail','lonstatusmail', - 'requestsmail','updatesmail','idconflictsmail'); - my @toggles = ('reporterrors','reportupdates'); + my @mailings = ('errormail','packagesmail','helpdeskmail','otherdomsmail', + 'lonstatusmail','requestsmail','updatesmail','idconflictsmail','hostipmail'); + my @toggles = ('reporterrors','reportupdates','reportstatus'); + my @lonstatus = ('threshold','sysmail','weights','excluded'); + my ($fields,$fieldtitles,$fieldoptions,$possoptions) = &helpform_fields(); foreach my $type (@mailings) { @{$newsetting{$type}} = &Apache::loncommon::get_env_multiple('form.'.$type); @@ -8417,12 +11995,17 @@ sub modify_contacts { } else { $contacts_hash{contacts}{$type}{$item} = 0; } - } + } $others{$type} = $env{'form.'.$type.'_others'}; $contacts_hash{contacts}{$type}{'others'} = $others{$type}; - if ($type eq 'helpdeskmail') { + if (($type eq 'helpdeskmail') || ($type eq 'otherdomsmail')) { $bcc{$type} = $env{'form.'.$type.'_bcc'}; $contacts_hash{contacts}{$type}{'bcc'} = $bcc{$type}; + if (($env{'form.'.$type.'_includestr'} ne '') && ($env{'form.'.$type.'_includeloc'} =~ /^s|b$/)) { + $includestr{$type} = $env{'form.'.$type.'_includestr'}; + $includeloc{$type} = $env{'form.'.$type.'_includeloc'}; + $contacts_hash{contacts}{$type}{'include'} = $includeloc{$type}.':'.&escape($includestr{$type}); + } } } foreach my $item (@contacts) { @@ -8434,6 +12017,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; + 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'}; + } + } + } + } + } + } + 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}) { @@ -8453,10 +12128,136 @@ sub modify_contacts { if ($others{$type} ne $currsetting{$type}{'others'}) { push(@{$changes{$type}},'others'); } - if ($type eq 'helpdeskmail') { + if (($type eq 'helpdeskmail') || ($type eq 'otherdomsmail')) { if ($bcc{$type} ne $currsetting{$type}{'bcc'}) { push(@{$changes{$type}},'bcc'); } + my ($currloc,$currstr) = split(/:/,$currsetting{$type}{'include'},2); + if (($includeloc{$type} ne $currloc) || (&escape($includestr{$type}) ne $currstr)) { + push(@{$changes{$type}},'include'); + } + } + } + if (ref($fields) eq 'ARRAY') { + if (ref($currsetting{'helpform'}) eq 'HASH') { + foreach my $field (@{$fields}) { + if ($currsetting{'helpform'}{$field} ne $contacts_hash{'contacts'}{'helpform'}{$field}) { + push(@{$changes{'helpform'}},$field); + } + if (($field eq 'screenshot') && ($contacts_hash{'contacts'}{'helpform'}{'screenshot'} ne 'no')) { + if ($currsetting{'helpform'}{'maxsize'} ne $contacts_hash{'contacts'}{'helpform'}{'maxsize'}) { + push(@{$changes{'helpform'}},'maxsize'); + } + } + } + } else { + foreach my $field (@{$fields}) { + if ($contacts_hash{'contacts'}{'helpform'}{$field} ne 'yes') { + push(@{$changes{'helpform'}},$field); + } + if (($field eq 'screenshot') && ($contacts_hash{'contacts'}{'helpform'}{'screenshot'} ne 'no')) { + if ($contacts_hash{'contacts'}{'helpform'}{'maxsize'} != 1) { + push(@{$changes{'helpform'}},'maxsize'); + } + } + } + } + } + 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 { @@ -8466,26 +12267,49 @@ sub modify_contacts { $default{'errormail'} = 'adminemail'; $default{'packagesmail'} = 'adminemail'; $default{'helpdeskmail'} = 'supportemail'; + $default{'otherdomsmail'} = 'supportemail'; $default{'lonstatusmail'} = 'adminemail'; $default{'requestsmail'} = 'adminemail'; $default{'updatesmail'} = 'adminemail'; + $default{'hostipmail'} = 'adminemail'; foreach my $item (@contacts) { if ($to{$item} ne $default{$item}) { - $changes{$item} = 1; + $changes{$item} = 1; } } foreach my $type (@mailings) { if ((@{$newsetting{$type}} != 1) || ($newsetting{$type}[0] ne $default{$type})) { - push(@{$changes{$type}},@{$newsetting{$type}}); } if ($others{$type} ne '') { push(@{$changes{$type}},'others'); } - if ($type eq 'helpdeskmail') { + if (($type eq 'helpdeskmail') || ($type eq 'otherdomsmail')) { if ($bcc{$type} ne '') { push(@{$changes{$type}},'bcc'); } + if (($includeloc{$type} =~ /^b|s$/) && ($includestr{$type} ne '')) { + push(@{$changes{$type}},'include'); + } + } + } + if (ref($fields) eq 'ARRAY') { + foreach my $field (@{$fields}) { + if ($contacts_hash{'contacts'}{'helpform'}{$field} ne 'yes') { + push(@{$changes{'helpform'}},$field); + } + if (($field eq 'screenshot') && ($contacts_hash{'contacts'}{'helpform'}{'screenshot'} ne 'no')) { + if ($contacts_hash{'contacts'}{'helpform'}{'maxsize'} != 1) { + push(@{$changes{'helpform'}},'maxsize'); + } + } + } + } + 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); + } } } } @@ -8517,7 +12341,11 @@ sub modify_contacts { } foreach my $type (@mailings) { if (ref($changes{$type}) eq 'ARRAY') { - $resulttext .= '
  • '.$titles->{$type}.': '; + if (($type eq 'helpdeskmail') || ($type eq 'otherdomsmail')) { + $resulttext .= '
  • '.$titles->{$type}.' -- '.&mt('sent to').': '; + } else { + $resulttext .= '
  • '.$titles->{$type}.': '; + } my @text; foreach my $item (@{$newsetting{$type}}) { push(@text,$short_titles->{$item}); @@ -8525,36 +12353,206 @@ sub modify_contacts { if ($others{$type} ne '') { push(@text,$others{$type}); } - $resulttext .= ''. - join(', ',@text).''; - if ($type eq 'helpdeskmail') { + if (@text) { + $resulttext .= ''. + join(', ',@text).''; + } + if (($type eq 'helpdeskmail') || ($type eq 'otherdomsmail')) { if ($bcc{$type} ne '') { - $resulttext .= ' '.&mt('with Bcc to').': '.$bcc{$type}.''; + my $bcctext; + if (@text) { + $bcctext = ' '.&mt('with Bcc to'); + } else { + $bcctext = '(Bcc)'; + } + $resulttext .= $bcctext.': '.$bcc{$type}.''; + } elsif (!@text) { + $resulttext .= &mt('No one'); + } + if ($includestr{$type} ne '') { + if ($includeloc{$type} eq 'b') { + $resulttext .= '
    '.&mt('Text automatically added to e-mail body:').' '.$includestr{$type}; + } elsif ($includeloc{$type} eq 's') { + $resulttext .= '
    '.&mt('Text automatically added to e-mail subject:').' '.$includestr{$type}; + } } + } elsif (!@text) { + $resulttext .= &mt('No recipients'); } $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'}}) { + if ($field eq 'maxsize') { + $maxsizechg = 1; + next; + } + if ($contacts_hash{'contacts'}{'helpform'}{$field} eq 'yes') { + push(@optional,$field); + } elsif ($contacts_hash{'contacts'}{'helpform'}{$field} eq 'no') { + push(@unused,$field); + } elsif ($contacts_hash{'contacts'}{'helpform'}{$field} eq 'req') { + push(@required,$field); + } + } + if (@optional) { + $resulttext .= '
  • '. + &mt('Help form fields changed to "Optional": [_1].', + ''.join(', ',map { $fieldtitles->{$_}; } @optional)).''. + '
  • '; + } + if (@required) { + $resulttext .= '
  • '. + &mt('Help form fields changed to "Required": [_1].', + ''.join(', ',map { $fieldtitles->{$_}; } @required)).''. + '
  • '; + } + if (@unused) { + $resulttext .= '
  • '. + &mt('Help form fields changed to "Not shown": [_1].', + ''.join(', ',map { $fieldtitles->{$_}; } @unused)).''. + '
  • '; + } + if ($maxsizechg) { + $resulttext .= '
  • '. + &mt('Max size for file uploaded to help form by logged-in user set to [_1] MB.', + $contacts_hash{'contacts'}{'helpform'}{'maxsize'}). + '
  • '; + } + } $resulttext .= ''; } else { - $resulttext = &mt('No changes made to contact information'); + $resulttext = &mt('No changes made to contacts and form settings'); } } else { $resulttext = ''. @@ -8563,6 +12561,549 @@ sub modify_contacts { return $resulttext; } +sub modify_passwords { + my ($r,$dom,$confname,$lastactref,%domconfig) = @_; + my ($resulttext,%current,%changes,%newvalues,@oktypes,$errors, + $updatedefaults,$updateconf); + my $customfn = 'resetpw.html'; + if (ref($domconfig{'passwords'}) eq 'HASH') { + %current = %{$domconfig{'passwords'}}; + } + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + if (ref($types) eq 'ARRAY') { + @oktypes = @{$types}; + } + push(@oktypes,'default'); + + my %titles = &Apache::lonlocal::texthash ( + intauth_cost => 'Encryption cost for bcrypt (positive integer)', + intauth_check => 'Check bcrypt cost if authenticated', + intauth_switch => 'Existing crypt-based switched to bcrypt on authentication', + permanent => 'Permanent e-mail address', + critical => 'Critical notification address', + notify => 'Notification address', + min => 'Minimum password length', + max => 'Maximum password length', + chars => 'Required characters', + numsaved => 'Number of previous passwords to save', + reset => 'Resetting Forgotten Password', + intauth => 'Encryption of Stored Passwords (Internal Auth)', + rules => 'Rules for LON-CAPA Passwords', + crsownerchg => 'Course Owner Changing Student Passwords', + username => 'Username', + email => 'E-mail address', + ); + +# +# Retrieve current domain configuration for internal authentication from $domconfig{'defaults'}. +# + my (%curr_defaults,%save_defaults); + if (ref($domconfig{'defaults'}) eq 'HASH') { + foreach my $key (keys(%{$domconfig{'defaults'}})) { + if ($key =~ /^intauth_(cost|check|switch)$/) { + $curr_defaults{$key} = $domconfig{'defaults'}{$key}; + } else { + $save_defaults{$key} = $domconfig{'defaults'}{$key}; + } + } + } + my %staticdefaults = ( + 'resetlink' => 2, + 'resetcase' => \@oktypes, + 'resetprelink' => 'both', + 'resetemail' => ['critical','notify','permanent'], + 'intauth_cost' => 10, + 'intauth_check' => 0, + 'intauth_switch' => 0, + ); + $staticdefaults{'min'} = $Apache::lonnet::passwdmin; + foreach my $type (@oktypes) { + $staticdefaults{'resetpostlink'}{$type} = ['email','username']; + } + my $linklife = $env{'form.passwords_link'}; + $linklife =~ s/^\s+|\s+$//g; + if (($linklife =~ /^\d+(|\.\d*)$/) && ($linklife > 0)) { + $newvalues{'resetlink'} = $linklife; + if ($current{'resetlink'}) { + if ($current{'resetlink'} ne $linklife) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + if ($staticdefaults{'resetlink'} ne $linklife) { + $changes{'reset'} = 1; + } + } + } elsif ($current{'resetlink'}) { + $changes{'reset'} = 1; + } + my @casesens; + my @posscase = &Apache::loncommon::get_env_multiple('form.passwords_case_sensitive'); + foreach my $case (sort(@posscase)) { + if (grep(/^\Q$case\E$/,@oktypes)) { + push(@casesens,$case); + } + } + $newvalues{'resetcase'} = \@casesens; + if (ref($current{'resetcase'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'resetcase'},\@casesens); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetcase'},\@casesens); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } + if ($env{'form.passwords_prelink'} =~ /^(both|either)$/) { + $newvalues{'resetprelink'} = $env{'form.passwords_prelink'}; + if (exists($current{'resetprelink'})) { + if ($current{'resetprelink'} ne $newvalues{'resetprelink'}) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + if ($staticdefaults{'resetprelink'} ne $newvalues{'resetprelink'}) { + $changes{'reset'} = 1; + } + } + } elsif ($current{'resetprelink'}) { + $changes{'reset'} = 1; + } + foreach my $type (@oktypes) { + my @possplink = &Apache::loncommon::get_env_multiple('form.passwords_postlink_'.$type); + my @postlink; + foreach my $item (sort(@possplink)) { + if ($item =~ /^(email|username)$/) { + push(@postlink,$item); + } + } + $newvalues{'resetpostlink'}{$type} = \@postlink; + unless ($changes{'reset'}) { + if (ref($current{'resetpostlink'}) eq 'HASH') { + if (ref($current{'resetpostlink'}{$type}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'resetpostlink'}{$type},\@postlink); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } else { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetpostlink'}{$type},\@postlink); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } + } + } + my @possemailsrc = &Apache::loncommon::get_env_multiple('form.passwords_emailsrc'); + my @resetemail; + foreach my $item (sort(@possemailsrc)) { + if ($item =~ /^(permanent|critical|notify)$/) { + push(@resetemail,$item); + } + } + $newvalues{'resetemail'} = \@resetemail; + unless ($changes{'reset'}) { + if (ref($current{'resetemail'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'resetemail'},\@resetemail); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetemail'},\@resetemail); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } + } + if ($env{'form.passwords_stdtext'} == 0) { + $newvalues{'resetremove'} = 1; + unless ($current{'resetremove'}) { + $changes{'reset'} = 1; + } + } elsif ($current{'resetremove'}) { + $changes{'reset'} = 1; + } + if ($env{'form.passwords_customfile.filename'} ne '') { + my $servadm = $r->dir_config('lonAdmEMail'); + my $servadm = $r->dir_config('lonAdmEMail'); + my ($configuserok,$author_ok,$switchserver) = + &config_check($dom,$confname,$servadm); + my $error; + if ($configuserok eq 'ok') { + if ($switchserver) { + $error = &mt("Upload of file containing domain-specific text is not permitted to this server: [_1]",$switchserver); + } else { + if ($author_ok eq 'ok') { + my ($result,$customurl) = + &publishlogo($r,'upload','passwords_customfile',$dom, + $confname,'customtext/resetpw','','',$customfn); + if ($result eq 'ok') { + $newvalues{'resetcustom'} = $customurl; + $changes{'reset'} = 1; + } else { + $error = &mt("Upload of [_1] failed because an error occurred publishing the file in RES space. Error was: [_2].",$customfn,$result); + } + } else { + $error = &mt("Upload of [_1] failed because an author role could not be assigned to a Domain Configuration user ([_2]) in domain: [_3]. Error was: [_4].",$customfn,$confname,$dom,$author_ok); + } + } + } else { + $error = &mt("Upload of [_1] failed because a Domain Configuration user ([_2]) could not be created in domain: [_3]. Error was: [_4].",$customfn,$confname,$dom,$configuserok); + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } elsif ($current{'resetcustom'}) { + if ($env{'form.passwords_custom_del'}) { + $changes{'reset'} = 1; + } else { + $newvalues{'resetcustom'} = $current{'resetcustom'}; + } + } + $env{'form.intauth_cost'} =~ s/^\s+|\s+$//g; + if (($env{'form.intauth_cost'} ne '') && ($env{'form.intauth_cost'} =~ /^\d+$/)) { + $save_defaults{'intauth_cost'} = $env{'form.intauth_cost'}; + if ($save_defaults{'intauth_cost'} ne $curr_defaults{'intauth_cost'}) { + $changes{'intauth'} = 1; + } + } else { + $save_defaults{'intauth_cost'} = $curr_defaults{'intauth_cost'}; + } + if ($env{'form.intauth_check'} =~ /^(0|1|2)$/) { + $save_defaults{'intauth_check'} = $env{'form.intauth_check'}; + if ($save_defaults{'intauth_check'} ne $curr_defaults{'intauth_check'}) { + $changes{'intauth'} = 1; + } + } else { + $save_defaults{'intauth_check'} = $curr_defaults{'intauth_check'}; + } + if ($env{'form.intauth_switch'} =~ /^(0|1|2)$/) { + $save_defaults{'intauth_switch'} = $env{'form.intauth_switch'}; + if ($save_defaults{'intauth_switch'} ne $curr_defaults{'intauth_switch'}) { + $changes{'intauth'} = 1; + } + } else { + $save_defaults{'intauth_check'} = $curr_defaults{'intauth_check'}; + } + foreach my $item ('cost','check','switch') { + if ($save_defaults{'intauth_'.$item} ne $domdefaults{'intauth_'.$item}) { + $domdefaults{'intauth_'.$item} = $save_defaults{'intauth_'.$item}; + $updatedefaults = 1; + } + } + foreach my $rule ('min','max','numsaved') { + $env{'form.passwords_'.$rule} =~ s/^\s+|\s+$//g; + my $ruleok; + if ($rule eq 'min') { + if ($env{'form.passwords_'.$rule} =~ /^\d+$/) { + if ($env{'form.passwords_'.$rule} >= $Apache::lonnet::passwdmin) { + $ruleok = 1; + } + } + } elsif (($env{'form.passwords_'.$rule} =~ /^\d+$/) && + ($env{'form.passwords_'.$rule} ne '0')) { + $ruleok = 1; + } + if ($ruleok) { + $newvalues{$rule} = $env{'form.passwords_'.$rule}; + if (exists($current{$rule})) { + if ($newvalues{$rule} ne $current{$rule}) { + $changes{'rules'} = 1; + } + } elsif ($rule eq 'min') { + if ($staticdefaults{$rule} ne $newvalues{$rule}) { + $changes{'rules'} = 1; + } + } else { + $changes{'rules'} = 1; + } + } elsif (exists($current{$rule})) { + $changes{'rules'} = 1; + } + } + my @posschars = &Apache::loncommon::get_env_multiple('form.passwords_chars'); + my @chars; + foreach my $item (sort(@posschars)) { + if ($item =~ /^(uc|lc|num|spec)$/) { + push(@chars,$item); + } + } + $newvalues{'chars'} = \@chars; + unless ($changes{'rules'}) { + if (ref($current{'chars'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'chars'},\@chars); + if (@diffs > 0) { + $changes{'rules'} = 1; + } + } else { + if (@chars > 0) { + $changes{'rules'} = 1; + } + } + } + my %crsownerchg = ( + by => [], + for => [], + ); + foreach my $item ('by','for') { + my @posstypes = &Apache::loncommon::get_env_multiple('form.passwords_crsowner_'.$item); + foreach my $type (sort(@posstypes)) { + if (grep(/^\Q$type\E$/,@oktypes)) { + push(@{$crsownerchg{$item}},$type); + } + } + } + $newvalues{'crsownerchg'} = \%crsownerchg; + if (ref($current{'crsownerchg'}) eq 'HASH') { + foreach my $item ('by','for') { + if (ref($current{'crsownerchg'}{$item}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'crsownerchg'}{$item},$crsownerchg{$item}); + if (@diffs > 0) { + $changes{'crsownerchg'} = 1; + last; + } + } + } + } elsif (!(ref($domconfig{passwords}) eq 'HASH')) { + foreach my $item ('by','for') { + if (@{$crsownerchg{$item}} > 0) { + $changes{'crsownerchg'} = 1; + last; + } + } + } + + my %confighash = ( + defaults => \%save_defaults, + passwords => \%newvalues, + ); + &process_captcha('passwords',\%changes,$confighash{'passwords'},$domconfig{'passwords'}); + + my $putresult = &Apache::lonnet::put_dom('configuration',\%confighash,$dom); + if ($putresult eq 'ok') { + if (keys(%changes) > 0) { + $resulttext = &mt('Changes made: ').'
      '; + foreach my $key ('reset','intauth','rules','crsownerchg') { + if ($changes{$key}) { + unless ($key eq 'intauth') { + $updateconf = 1; + } + $resulttext .= '
    • '.$titles{$key}.':
        '; + if ($key eq 'reset') { + if ($confighash{'passwords'}{'captcha'} eq 'original') { + $resulttext .= '
      • '.&mt('CAPTCHA validation set to use: original CAPTCHA').'
      • '; + } elsif ($confighash{'passwords'}{'captcha'} eq 'recaptcha') { + $resulttext .= '
      • '.&mt('CAPTCHA validation set to use: reCAPTCHA').' '. + &mt('version: [_1]',$confighash{'passwords'}{'recaptchaversion'}).'
        '; + if (ref($confighash{'passwords'}{'recaptchakeys'}) eq 'HASH') { + $resulttext .= &mt('Public key: [_1]',$confighash{'passwords'}{'recaptchakeys'}{'public'}).'
        '. + &mt('Private key: [_1]',$confighash{'passwords'}{'recaptchakeys'}{'private'}).'
      • '; + } + } else { + $resulttext .= '
      • '.&mt('No CAPTCHA validation').'
      • '; + } + if ($confighash{'passwords'}{'resetlink'}) { + $resulttext .= '
      • '.&mt('Reset link expiration set to [quant,_1,hour]',$confighash{'passwords'}{'resetlink'}).'
      • '; + } else { + $resulttext .= '
      • '.&mt('No reset link expiration set.').' '. + &mt('Will default to 2 hours').'
      • '; + } + if (ref($confighash{'passwords'}{'resetcase'}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'resetcase'}} == 0) { + $resulttext .= '
      • '.&mt('User input for username and/or e-mail address not case sensitive for "Forgot Password" web form').'
      • '; + } else { + my $casesens; + foreach my $type (@{$confighash{'passwords'}{'resetcase'}}) { + if ($type eq 'default') { + $casesens .= $othertitle.', '; + } elsif ($usertypes->{$type} ne '') { + $casesens .= $usertypes->{$type}.', '; + } + } + $casesens =~ s/\Q, \E$//; + $resulttext .= '
      • '.&mt('"Forgot Password" web form input for username and/or e-mail address is case-sensitive for: [_1]',$casesens).'
      • '; + } + } else { + $resulttext .= '
      • '.&mt('Case-sensitivity not set for "Forgot Password" web form').' '.&mt('Will default to case-sensitive for username and/or e-mail address for all').'
      • '; + } + if ($confighash{'passwords'}{'resetprelink'} eq 'either') { + $resulttext .= '
      • '.&mt('Users can enter either a username or an e-mail address in "Forgot Password" web form').'
      • '; + } else { + $resulttext .= '
      • '.&mt('Users can enter both a username and an e-mail address in "Forgot Password" web form').'
      • '; + } + if (ref($confighash{'passwords'}{'resetpostlink'}) eq 'HASH') { + my $output; + if (ref($types) eq 'ARRAY') { + foreach my $type (@{$types}) { + if (ref($confighash{'passwords'}{'resetpostlink'}{$type}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'resetpostlink'}{$type}} == 0) { + $output .= $usertypes->{$type}.' -- '.&mt('none'); + } else { + $output .= $usertypes->{$type}.' -- '. + join(', ',map { $titles{$_}; } (@{$confighash{'passwords'}{'resetpostlink'}{$type}})).'; '; + } + } + } + } + if (ref($confighash{'passwords'}{'resetpostlink'}{'default'}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'resetpostlink'}{'default'}} == 0) { + $output .= $othertitle.' -- '.&mt('none'); + } else { + $output .= $othertitle.' -- '. + join(', ',map { $titles{$_}; } (@{$confighash{'passwords'}{'resetpostlink'}{'default'}})); + } + } + if ($output) { + $resulttext .= '
      • '.&mt('Information required for new password form (by user type) set to: [_1]',$output).'
      • '; + } else { + $resulttext .= '
      • '.&mt('Information required for new password form not set.').' '.&mt('Will default to requiring both the username and an e-mail address').'
      • '; + } + } else { + $resulttext .= '
      • '.&mt('Information required for new password form not set.').' '.&mt('Will default to requiring both the username and an e-mail address').'
      • '; + } + if (ref($confighash{'passwords'}{'resetemail'}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'resetemail'}} > 0) { + $resulttext .= '
      • '.&mt('E-mail address(es) in LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } @{$confighash{'passwords'}{'resetemail'}})).'
      • '; + } else { + $resulttext .= '
      • '.&mt('E-mail address(es) in LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } @{$staticdefaults{'resetemail'}})).'
      • '; + } + } else { + $resulttext .= '
      • '.&mt('E-mail address(es) in LON-CAPA used for verification will include: [_1]',join(', ',map { $titles{$_}; } @{$staticdefaults{'resetemail'}})).'
      • '; + } + if ($confighash{'passwords'}{'resetremove'}) { + $resulttext .= '
      • '.&mt('Preamble to "Forgot Password" web form not shown').'
      • '; + } else { + $resulttext .= '
      • '.&mt('Preamble to "Forgot Password" web form is shown').'
      • '; + } + if ($confighash{'passwords'}{'resetcustom'}) { + my $customlink = &Apache::loncommon::modal_link($confighash{'passwords'}{'resetcustom'}, + &mt('custom text'),600,500,undef,undef, + undef,undef,'background-color:#ffffff'); + $resulttext .= '
      • '.&mt('Preamble to "Forgot Password" form includes: [_1]',$customlink).'
      • '; + } else { + $resulttext .= '
      • '.&mt('No custom text included in preamble to "Forgot Password" form').'
      • '; + } + } elsif ($key eq 'intauth') { + foreach my $item ('cost','switch','check') { + my $value = $save_defaults{$key.'_'.$item}; + if ($item eq 'switch') { + my %optiondesc = &Apache::lonlocal::texthash ( + 0 => 'No', + 1 => 'Yes', + 2 => 'Yes, and copy existing passwd file to passwd.bak file', + ); + if ($value =~ /^(0|1|2)$/) { + $value = $optiondesc{$value}; + } else { + $value = &mt('none -- defaults to No'); + } + } elsif ($item eq 'check') { + my %optiondesc = &Apache::lonlocal::texthash ( + 0 => 'No', + 1 => 'Yes, allow login then update passwd file using default cost (if higher)', + 2 => 'Yes, disallow login if stored cost is less than domain default', + ); + if ($value =~ /^(0|1|2)$/) { + $value = $optiondesc{$value}; + } else { + $value = &mt('none -- defaults to No'); + } + } + $resulttext .= '
      • '.&mt('[_1] set to "[_2]"',$titles{$key.'_'.$item},$value).'
      • '; + } + } elsif ($key eq 'rules') { + foreach my $rule ('min','max','numsaved') { + if ($confighash{'passwords'}{$rule} eq '') { + if ($rule eq 'min') { + $resulttext .= '
      • '.&mt('[_1] not set.',$titles{$rule}); + ' '.&mt('Default of [_1] will be used', + $Apache::lonnet::passwdmin).'
      • '; + } else { + $resulttext .= '
      • '.&mt('[_1] set to none',$titles{$rule}).'
      • '; + } + } else { + $resulttext .= '
      • '.&mt('[_1] set to [_2]',$titles{$rule},$confighash{'passwords'}{$rule}).'
      • '; + } + } + if (ref($confighash{'passwords'}{'chars'}) eq 'ARRAY') { + if (@{$confighash{'passwords'}{'chars'}} > 0) { + my %rulenames = &Apache::lonlocal::texthash( + uc => 'At least one upper case letter', + lc => 'At least one lower case letter', + num => 'At least one number', + spec => 'At least one non-alphanumeric', + ); + my $needed = '
        • '. + join('
        • ',map {$rulenames{$_} } @{$confighash{'passwords'}{'chars'}}). + '
        '; + $resulttext .= '
      • '.&mt('[_1] set to: [_2]',$titles{'chars'},$needed).'
      • '; + } else { + $resulttext .= '
      • '.&mt('[_1] set to none',$titles{'chars'}).'
      • '; + } + } else { + $resulttext .= '
      • '.&mt('[_1] set to none',$titles{'chars'}).'
      • '; + } + } elsif ($key eq 'crsownerchg') { + if (ref($confighash{'passwords'}{'crsownerchg'}) eq 'HASH') { + if ((@{$confighash{'passwords'}{'crsownerchg'}{'by'}} == 0) || + (@{$confighash{'passwords'}{'crsownerchg'}{'for'}} == 0)) { + $resulttext .= '
      • '.&mt('Course owner may not change student passwords.').'
      • '; + } else { + my %crsownerstr; + foreach my $item ('by','for') { + if (ref($confighash{'passwords'}{'crsownerchg'}{$item}) eq 'ARRAY') { + foreach my $type (@{$confighash{'passwords'}{'crsownerchg'}{$item}}) { + if ($type eq 'default') { + $crsownerstr{$item} .= $othertitle.', '; + } elsif ($usertypes->{$type} ne '') { + $crsownerstr{$item} .= $usertypes->{$type}.', '; + } + } + $crsownerstr{$item} =~ s/\Q, \E$//; + } + } + $resulttext .= '
      • '.&mt('Course owner (with status: [_1]) may change passwords for students (with status: [_2]).', + $crsownerstr{'by'},$crsownerstr{'for'}).'
      • '; + } + } else { + $resulttext .= '
      • '.&mt('Course owner may not change student passwords.').'
      • '; + } + } + $resulttext .= '
    • '; + } + } + $resulttext .= '
    '; + } else { + $resulttext = &mt('No changes made to password settings'); + } + my $cachetime = 24*60*60; + if ($updatedefaults) { + &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; + } + } + if ($updateconf) { + &Apache::lonnet::do_cache_new('passwdconf',$dom,$confighash{'passwords'},$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'passwdconf'} = 1; + } + } + } else { + $resulttext = ''. + &mt('An error occurred: [_1]',$putresult).''; + } + if ($errors) { + $resulttext .= '

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

      '. + $errors.'

    '; + } + return $resulttext; +} + sub modify_usercreation { my ($dom,%domconfig) = @_; my ($resulttext,%curr_usercreation,%changes,%authallowed,%cancreate,%save_usercreate); @@ -8572,11 +13113,10 @@ sub modify_usercreation { if ($key eq 'cancreate') { if (ref($domconfig{'usercreation'}{$key}) eq 'HASH') { foreach my $item (keys(%{$domconfig{'usercreation'}{$key}})) { - if (($item eq 'selfcreate') || ($item eq 'statustocreate') || - ($item eq 'captcha') || ($item eq 'recaptchakeys')) { - $save_usercreate{$key}{$item} = $domconfig{'usercreation'}{$key}{$item}; - } else { + if (($item eq 'requestcrs') || ($item eq 'course') || ($item eq 'author')) { $curr_usercreation{$key}{$item} = $domconfig{'usercreation'}{$key}{$item}; + } else { + $save_usercreate{$key}{$item} = $domconfig{'usercreation'}{$key}{$item}; } } } @@ -8779,14 +13319,18 @@ sub modify_usercreation { } sub modify_selfcreation { - my ($dom,%domconfig) = @_; - my ($resulttext,$warningmsg,%curr_usercreation,%curr_usermodify,%changes,%cancreate); - my (%save_usercreate,%save_usermodify); - my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); - if (ref($types) eq 'ARRAY') { - $usertypes->{'default'} = $othertitle; - push(@{$types},'default'); + my ($dom,$lastactref,%domconfig) = @_; + my ($resulttext,$warningmsg,%curr_usercreation,%curr_usermodify,%curr_inststatus,%changes,%cancreate); + my (%save_usercreate,%save_usermodify,%save_inststatus,@types,%usertypes); + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my ($othertitle,$usertypesref,$typesref) = &Apache::loncommon::sorted_inst_types($dom); + if (ref($typesref) eq 'ARRAY') { + @types = @{$typesref}; + } + if (ref($usertypesref) eq 'HASH') { + %usertypes = %{$usertypesref}; } + $usertypes{'default'} = $othertitle; # # Retrieve current domain configuration for self-creation of usernames from $domconfig{'usercreation'}. # @@ -8796,9 +13340,11 @@ sub modify_selfcreation { if (ref($domconfig{'usercreation'}{$key}) eq 'HASH') { foreach my $item (keys(%{$domconfig{'usercreation'}{$key}})) { if (($item eq 'selfcreate') || ($item eq 'statustocreate') || - ($item eq 'captcha') || ($item eq 'recaptchakeys') || - ($item eq 'emailusername') || ($item eq 'notify') || - ($item eq 'selfcreateprocessing') || ($item eq 'shibenv')) { + ($item eq 'captcha') || ($item eq 'recaptchakeys') || + ($item eq 'recaptchaversion') || ($item eq 'notify') || + ($item eq 'emailusername') || ($item eq 'shibenv') || + ($item eq 'selfcreateprocessing') || ($item eq 'emailverified') || + ($item eq 'emailoptions') || ($item eq 'emaildomain')) { $curr_usercreation{$key}{$item} = $domconfig{'usercreation'}{$key}{$item}; } else { $save_usercreate{$key}{$item} = $domconfig{'usercreation'}{$key}{$item}; @@ -8824,41 +13370,161 @@ sub modify_selfcreation { } } } +# +# Retrieve current domain configuration for institutional status types from $domconfig{'inststatus'}. +# + if (ref($domconfig{'inststatus'}) eq 'HASH') { + foreach my $key (keys(%{$domconfig{'inststatus'}})) { + if ($key eq 'inststatusguest') { + $curr_inststatus{$key} = $domconfig{'inststatus'}{$key}; + } else { + $save_inststatus{$key} = $domconfig{'inststatus'}{$key}; + } + } + } my @contexts = ('selfcreate'); @{$cancreate{'selfcreate'}} = (); %{$cancreate{'emailusername'}} = (); - @{$cancreate{'statustocreate'}} = (); + if (@types) { + @{$cancreate{'statustocreate'}} = (); + } %{$cancreate{'selfcreateprocessing'}} = (); %{$cancreate{'shibenv'}} = (); + %{$cancreate{'emailverified'}} = (); + %{$cancreate{'emailoptions'}} = (); + %{$cancreate{'emaildomain'}} = (); my %selfcreatetypes = ( sso => 'users authenticated by institutional single sign on', login => 'users authenticated by institutional log-in', - email => 'users who provide a valid e-mail address for use as username', + email => 'users verified by e-mail', ); # # Populate $cancreate{'selfcreate'} array reference with types of user, for which self-creation of user accounts # is permitted. # - my @statuses; - if (ref($domconfig{'inststatus'}) eq 'HASH') { - if (ref($domconfig{'inststatus'}{'inststatusguest'}) eq 'ARRAY') { - @statuses = @{$domconfig{'inststatus'}{'inststatusguest'}}; - } - } - push(@statuses,'default'); + my ($emailrules,$emailruleorder) = &Apache::lonnet::inst_userrules($dom,'email'); + my (@statuses,%email_rule); foreach my $item ('login','sso','email') { if ($item eq 'email') { if ($env{'form.cancreate_email'}) { - push(@{$cancreate{'selfcreate'}},'email'); - push(@contexts,'selfcreateprocessing'); - foreach my $type (@statuses) { - if ($type eq 'default') { - $cancreate{'selfcreateprocessing'}{$type} = $env{'form.cancreate_emailprocess'}; - } else { - $cancreate{'selfcreateprocessing'}{$type} = $env{'form.cancreate_emailprocess_'.$type}; + if (@types) { + my @poss_statuses = &Apache::loncommon::get_env_multiple('form.selfassign'); + foreach my $status (@poss_statuses) { + if (grep(/^\Q$status\E$/,(@types,'default'))) { + push(@statuses,$status); + } + } + $save_inststatus{'inststatusguest'} = \@statuses; + } else { + push(@statuses,'default'); + } + if (@statuses) { + my %curr_rule; + if (ref($curr_usercreation{'email_rule'}) eq 'ARRAY') { + foreach my $type (@statuses) { + $curr_rule{$type} = $curr_usercreation{'email_rule'}; + } + } elsif (ref($curr_usercreation{'email_rule'}) eq 'HASH') { + foreach my $type (@statuses) { + $curr_rule{$type} = $curr_usercreation{'email_rule'}{$type}; + } + } + push(@{$cancreate{'selfcreate'}},'email'); + push(@contexts,('selfcreateprocessing','emailverified','emailoptions')); + my %curremaildom; + if (ref($curr_usercreation{'cancreate'}{'emaildomain'}) eq 'HASH') { + %curremaildom = %{$curr_usercreation{'cancreate'}{'emaildomain'}}; + } + foreach my $type (@statuses) { + if ($env{'form.cancreate_emailprocess_'.$type} =~ /^(?:approval|automatic)$/) { + $cancreate{'selfcreateprocessing'}{$type} = $env{'form.cancreate_emailprocess_'.$type}; + } + if ($env{'form.cancreate_usernameoptions_'.$type} =~ /^(?:all|first|free)$/) { + $cancreate{'emailverified'}{$type} = $env{'form.cancreate_usernameoptions_'.$type}; + } + if ($env{'form.cancreate_emailoptions_'.$type} =~ /^(any|inst|noninst|custom)$/) { +# +# Retrieve rules (if any) governing types of e-mail address which may be used to verify a username. +# + my $chosen = $1; + if (($chosen eq 'inst') || ($chosen eq 'noninst')) { + my $emaildom; + if ($env{'form.cancreate_emaildomain_'.$chosen.'_'.$type} =~ /^\@[^\@]+$/) { + $emaildom = $env{'form.cancreate_emaildomain_'.$chosen.'_'.$type}; + $cancreate{'emaildomain'}{$type}{$chosen} = $emaildom; + if (ref($curremaildom{$type}) eq 'HASH') { + if (exists($curremaildom{$type}{$chosen})) { + if ($curremaildom{$type}{$chosen} ne $emaildom) { + push(@{$changes{'cancreate'}},'emaildomain'); + } + } elsif ($emaildom ne '') { + push(@{$changes{'cancreate'}},'emaildomain'); + } + } elsif ($emaildom ne '') { + push(@{$changes{'cancreate'}},'emaildomain'); + } + } + $cancreate{'emailoptions'}{$type} = $env{'form.cancreate_emailoptions_'.$type}; + } elsif ($chosen eq 'custom') { + my @possemail_rules = &Apache::loncommon::get_env_multiple('form.email_rule_'.$type); + $email_rule{$type} = []; + if (ref($emailrules) eq 'HASH') { + foreach my $rule (@possemail_rules) { + if (exists($emailrules->{$rule})) { + push(@{$email_rule{$type}},$rule); + } + } + } + if (@{$email_rule{$type}}) { + $cancreate{'emailoptions'}{$type} = 'custom'; + if (ref($curr_rule{$type}) eq 'ARRAY') { + if (@{$curr_rule{$type}} > 0) { + foreach my $rule (@{$curr_rule{$type}}) { + if (!grep(/^\Q$rule\E$/,@{$email_rule{$type}})) { + push(@{$changes{'email_rule'}},$type); + } + } + } + foreach my $type (@{$email_rule{$type}}) { + if (!grep(/^\Q$type\E$/,@{$curr_rule{$type}})) { + push(@{$changes{'email_rule'}},$type); + } + } + } else { + push(@{$changes{'email_rule'}},$type); + } + } + } else { + $cancreate{'emailoptions'}{$type} = $env{'form.cancreate_emailoptions_'.$type}; + } + } + } + if (@types) { + if (ref($curr_inststatus{'inststatusguest'}) eq 'ARRAY') { + my @changed = &Apache::loncommon::compare_arrays(\@statuses,$curr_inststatus{'inststatusguest'}); + if (@changed) { + push(@{$changes{'inststatus'}},'inststatusguest'); + } + } else { + push(@{$changes{'inststatus'}},'inststatusguest'); + } + } + } else { + delete($env{'form.cancreate_email'}); + if (ref($curr_inststatus{'inststatusguest'}) eq 'ARRAY') { + if (@{$curr_inststatus{'inststatusguest'}} > 0) { + push(@{$changes{'inststatus'}},'inststatusguest'); + } + } + } + } else { + $save_inststatus{'inststatusguest'} = []; + if (ref($curr_inststatus{'inststatusguest'}) eq 'ARRAY') { + if (@{$curr_inststatus{'inststatusguest'}} > 0) { + push(@{$changes{'inststatus'}},'inststatusguest'); } } } @@ -8868,7 +13534,7 @@ sub modify_selfcreation { } } } - my (@email_rule,%userinfo,%savecaptcha); + my (%userinfo,%savecaptcha); my ($infofields,$infotitles) = &Apache::loncommon::emailusername_info(); # # Populate $cancreate{'emailusername'}{$type} hash ref with information fields (if new user will provide data @@ -8877,8 +13543,8 @@ sub modify_selfcreation { if ($env{'form.cancreate_email'}) { push(@contexts,'emailusername'); - if (ref($types) eq 'ARRAY') { - foreach my $type (@{$types}) { + if (@statuses) { + foreach my $type (@statuses) { if (ref($infofields) eq 'ARRAY') { foreach my $field (@{$infofields}) { if ($env{'form.canmodify_emailusername_'.$type.'_'.$field} =~ /^(required|optional)$/) { @@ -8890,7 +13556,7 @@ sub modify_selfcreation { } # # Populate $cancreate{'notify'} hash ref with names of Domain Coordinators who are to be notified of -# queued requests for self-creation of account using e-mail address as username +# queued requests for self-creation of account verified by e-mail. # my @approvalnotify = &Apache::loncommon::get_env_multiple('form.selfcreationnotifyapproval'); @@ -8910,36 +13576,13 @@ sub modify_selfcreation { push(@{$changes{'cancreate'}},'notify'); } -# -# Retrieve rules (if any) governing types of e-mail address which may be used as a username -# - @email_rule = &Apache::loncommon::get_env_multiple('form.email_rule'); &process_captcha('cancreate',\%changes,\%savecaptcha,$curr_usercreation{'cancreate'}); - if (ref($curr_usercreation{'email_rule'}) eq 'ARRAY') { - if (@{$curr_usercreation{'email_rule'}} > 0) { - foreach my $type (@{$curr_usercreation{'email_rule'}}) { - if (!grep(/^\Q$type\E$/,@email_rule)) { - push(@{$changes{'email_rule'}},$type); - } - } - } - if (@email_rule > 0) { - foreach my $type (@email_rule) { - if (!grep(/^\Q$type\E$/,@{$curr_usercreation{'email_rule'}})) { - push(@{$changes{'email_rule'}},$type); - } - } - } - } elsif (@email_rule > 0) { - push(@{$changes{'email_rule'}},@email_rule); - } } # # Check if domain default is set appropriately, if self-creation of accounts is to be available for # institutional log-in. # if (grep(/^login$/,@{$cancreate{'selfcreate'}})) { - my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); if (!((($domdefaults{'auth_def'} =~/^krb/) && ($domdefaults{'auth_arg_def'} ne '')) || ($domdefaults{'auth_def'} eq 'localauth'))) { $warningmsg = &mt('Although account creation has been set to be available for institutional logins, currently default authentication in this domain has not been set to support this.').' '. @@ -8958,14 +13601,10 @@ sub modify_selfcreation { # which the user may supply, if institutional data is unavailable. # if (($env{'form.cancreate_login'}) || ($env{'form.cancreate_sso'})) { - if (ref($types) eq 'ARRAY') { - if (@{$types} > 1) { - @{$cancreate{'statustocreate'}} = &Apache::loncommon::get_env_multiple('form.statustocreate'); - push(@contexts,'statustocreate'); - } else { - undef($cancreate{'statustocreate'}); - } - foreach my $type (@{$types}) { + if (@types) { + @{$cancreate{'statustocreate'}} = &Apache::loncommon::get_env_multiple('form.statustocreate'); + push(@contexts,'statustocreate'); + foreach my $type (@types) { my @modifiable = &Apache::loncommon::get_env_multiple('form.canmodify_'.$type); foreach my $field (@fields) { if (grep(/^\Q$field\E$/,@modifiable)) { @@ -8976,7 +13615,7 @@ sub modify_selfcreation { } } if (ref($curr_usermodify{'selfcreate'}) eq 'HASH') { - foreach my $type (@{$types}) { + foreach my $type (@types) { if (ref($curr_usermodify{'selfcreate'}{$type}) eq 'HASH') { foreach my $field (@fields) { if ($save_usermodify{'selfcreate'}{$type}{$field} ne @@ -8988,7 +13627,7 @@ sub modify_selfcreation { } } } else { - foreach my $type (@{$types}) { + foreach my $type (@types) { push(@{$changes{'selfcreate'}},$type); } } @@ -9037,34 +13676,28 @@ sub modify_selfcreation { } } elsif (ref($curr_usercreation{'cancreate'}{$item}) eq 'HASH') { if (ref($cancreate{$item}) eq 'HASH') { - foreach my $curr (keys(%{$curr_usercreation{'cancreate'}{$item}})) { - if (ref($curr_usercreation{'cancreate'}{$item}{$curr}) eq 'HASH') { - foreach my $field (keys(%{$curr_usercreation{'cancreate'}{$item}{$curr}})) { - unless ($curr_usercreation{'cancreate'}{$item}{$curr}{$field} eq $cancreate{$item}{$curr}{$field}) { + foreach my $type (keys(%{$curr_usercreation{'cancreate'}{$item}})) { + if (ref($curr_usercreation{'cancreate'}{$item}{$type}) eq 'HASH') { + foreach my $field (keys(%{$curr_usercreation{'cancreate'}{$item}{$type}})) { + unless ($curr_usercreation{'cancreate'}{$item}{$type}{$field} eq $cancreate{$item}{$type}{$field}) { if (!grep(/^$item$/,@{$changes{'cancreate'}})) { push(@{$changes{'cancreate'}},$item); } } } - } elsif ($item eq 'selfcreateprocessing') { - if ($cancreate{$item}{$curr} ne $curr_usercreation{'cancreate'}{$item}{$curr}) { - if (!grep(/^$item$/,@{$changes{'cancreate'}})) { - push(@{$changes{'cancreate'}},$item); - } - } - } else { - if (!$cancreate{$item}{$curr}) { + } elsif (($item eq 'selfcreateprocessing') || ($item eq 'emailverified') || ($item eq 'emailoptions')) { + if ($cancreate{$item}{$type} ne $curr_usercreation{'cancreate'}{$item}{$type}) { if (!grep(/^$item$/,@{$changes{'cancreate'}})) { push(@{$changes{'cancreate'}},$item); } } } } - foreach my $field (keys(%{$cancreate{$item}})) { - if (ref($cancreate{$item}{$field}) eq 'HASH') { - foreach my $inner (keys(%{$cancreate{$item}{$field}})) { - if (ref($curr_usercreation{'cancreate'}{$item}{$field}) eq 'HASH') { - unless ($curr_usercreation{'cancreate'}{$item}{$field}{$inner} eq $cancreate{$item}{$field}{$inner}) { + foreach my $type (keys(%{$cancreate{$item}})) { + if (ref($cancreate{$item}{$type}) eq 'HASH') { + foreach my $field (keys(%{$cancreate{$item}{$type}})) { + if (ref($curr_usercreation{'cancreate'}{$item}{$type}) eq 'HASH') { + unless ($curr_usercreation{'cancreate'}{$item}{$type}{$field} eq $cancreate{$item}{$type}{$field}) { if (!grep(/^$item$/,@{$changes{'cancreate'}})) { push(@{$changes{'cancreate'}},$item); } @@ -9075,14 +13708,8 @@ sub modify_selfcreation { } } } - } elsif ($item eq 'selfcreateprocessing') { - if ($cancreate{$item}{$field} ne $curr_usercreation{'cancreate'}{$item}{$field}) { - if (!grep(/^$item$/,@{$changes{'cancreate'}})) { - push(@{$changes{'cancreate'}},$item); - } - } - } else { - if (!$curr_usercreation{'cancreate'}{$item}{$field}) { + } elsif (($item eq 'selfcreateprocessing') || ($item eq 'emailverified') || ($item eq 'emailoptions')) { + if ($cancreate{$item}{$type} ne $curr_usercreation{'cancreate'}{$item}{$type}) { if (!grep(/^$item$/,@{$changes{'cancreate'}})) { push(@{$changes{'cancreate'}},$item); } @@ -9097,11 +13724,11 @@ sub modify_selfcreation { push(@{$changes{'cancreate'}},$item); } } - } elsif (ref($cancreate{$item}) eq 'HASH') { - if (!$cancreate{$item}{$curr_usercreation{'cancreate'}{$item}}) { - if (!grep(/^$item$/,@{$changes{'cancreate'}})) { - push(@{$changes{'cancreate'}},$item); - } + } + } elsif (($item eq 'selfcreateprocessing') || ($item eq 'emailverified') || ($item eq 'emailoptions')) { + if (ref($cancreate{$item}) eq 'HASH') { + if (!grep(/^$item$/,@{$changes{'cancreate'}})) { + push(@{$changes{'cancreate'}},$item); } } } elsif ($item eq 'emailusername') { @@ -9126,6 +13753,7 @@ sub modify_selfcreation { # $save_usercreate{'cancreate'}{'captcha'} = $savecaptcha{'captcha'}; $save_usercreate{'cancreate'}{'recaptchakeys'} = $savecaptcha{'recaptchakeys'}; + $save_usercreate{'cancreate'}{'recaptchaversion'} = $savecaptcha{'recaptchaversion'}; $save_usercreate{'cancreate'}{'selfcreate'} = $cancreate{'selfcreate'}; if (ref($cancreate{'notify'}) eq 'HASH') { $save_usercreate{'cancreate'}{'notify'} = $cancreate{'notify'}; @@ -9133,6 +13761,15 @@ sub modify_selfcreation { if (ref($cancreate{'selfcreateprocessing'}) eq 'HASH') { $save_usercreate{'cancreate'}{'selfcreateprocessing'} = $cancreate{'selfcreateprocessing'}; } + if (ref($cancreate{'emailverified'}) eq 'HASH') { + $save_usercreate{'cancreate'}{'emailverified'} = $cancreate{'emailverified'}; + } + if (ref($cancreate{'emailoptions'}) eq 'HASH') { + $save_usercreate{'cancreate'}{'emailoptions'} = $cancreate{'emailoptions'}; + } + if (ref($cancreate{'emaildomain'}) eq 'HASH') { + $save_usercreate{'cancreate'}{'emaildomain'} = $cancreate{'emaildomain'}; + } if (ref($cancreate{'statustocreate'}) eq 'ARRAY') { $save_usercreate{'cancreate'}{'statustocreate'} = $cancreate{'statustocreate'}; } @@ -9140,16 +13777,18 @@ sub modify_selfcreation { $save_usercreate{'cancreate'}{'shibenv'} = $cancreate{'shibenv'}; } $save_usercreate{'cancreate'}{'emailusername'} = $cancreate{'emailusername'}; - $save_usercreate{'emailrule'} = \@email_rule; + $save_usercreate{'email_rule'} = \%email_rule; my %userconfig_hash = ( usercreation => \%save_usercreate, usermodification => \%save_usermodify, + inststatus => \%save_inststatus, ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%userconfig_hash, $dom); # -# Accumulate details of changes to domain cofiguration for self-creation of usernames in $resulttext +# Accumulate details of changes to domain configuration for self-creation of usernames in $resulttext # if ($putresult eq 'ok') { if (keys(%changes) > 0) { @@ -9157,7 +13796,7 @@ sub modify_selfcreation { if (ref($changes{'cancreate'}) eq 'ARRAY') { my %lt = &selfcreation_types(); foreach my $type (@{$changes{'cancreate'}}) { - my $chgtext; + my $chgtext = ''; if ($type eq 'selfcreate') { if (@{$cancreate{$type}} == 0) { $chgtext .= &mt('Self creation of a new user account is not permitted.'); @@ -9172,18 +13811,25 @@ sub modify_selfcreation { if (grep(/^(login|sso)$/,@{$cancreate{$type}})) { if (ref($cancreate{'statustocreate'}) eq 'ARRAY') { if (@{$cancreate{'statustocreate'}} == 0) { - $chgtext .= '
    '. - ''. - &mt("However, no institutional affiliations (including 'other') are currently permitted to create accounts."). - ''; + $chgtext .= ''. + &mt("However, no institutional affiliations (including 'other') are currently permitted to create accounts via log-in or single sign-on."). + '
    '; } } } + if (grep(/^email$/,@{$cancreate{$type}})) { + if (!@statuses) { + $chgtext .= ''. + &mt("However, e-mail verification is currently set to 'unavailable' for all user types (including 'other'), so self-creation of accounts is not possible for non-institutional log-in."). + '
    '; + + } + } } } } elsif ($type eq 'shibenv') { if (keys(%{$cancreate{$type}}) == 0) { - $chgtext .= &mt('Shibboleth-autheticated user does not use environment variables to set user information'); + $chgtext .= &mt('Shibboleth-autheticated user does not use environment variables to set user information').'
    '; } else { $chgtext .= &mt('Shibboleth-autheticated user information set from environment variables, as follows:'). '
      '; @@ -9196,7 +13842,7 @@ sub modify_selfcreation { } } $chgtext .= '
    '; - } + } } elsif ($type eq 'statustocreate') { if ((ref($cancreate{'selfcreate'}) eq 'ARRAY') && (ref($cancreate{'statustocreate'}) eq 'ARRAY')) { @@ -9209,7 +13855,7 @@ sub modify_selfcreation { &mt("However, no institutional affiliations (including 'other') are currently permitted to create accounts."). '
    '; } - } elsif (ref($usertypes) eq 'HASH') { + } elsif (keys(%usertypes) > 0) { if (grep(/^(login|sso)$/,@{$cancreate{'selfcreate'}})) { $chgtext .= &mt('Creation of a new account for an institutional user is restricted to the following institutional affiliation(s):'); } else { @@ -9220,12 +13866,12 @@ sub modify_selfcreation { if ($case eq 'default') { $chgtext .= '
  • '.$othertitle.'
  • '; } else { - $chgtext .= '
  • '.$usertypes->{$case}.'
  • '; + $chgtext .= '
  • '.$usertypes{$case}.'
  • '; } } $chgtext .= ''; if (!grep(/^(login|sso)$/,@{$cancreate{'selfcreate'}})) { - $chgtext .= '
    '. + $chgtext .= ''. &mt('However, users authenticated by institutional login/single sign on are not currently permitted to create accounts.'). ''; } @@ -9237,26 +13883,129 @@ sub modify_selfcreation { $chgtext .= &mt('Although institutional affiliations permitted to create accounts were changed, self creation of accounts is not currently permitted for any authentication types.'); } } + $chgtext .= '
    '; } } elsif ($type eq 'selfcreateprocessing') { my %choices = &Apache::lonlocal::texthash ( automatic => 'Automatic approval', approval => 'Queued for approval', ); - if (@statuses > 1) { - $chgtext .= &mt('Processing of requests to create account with e-mail address as username set as follows:'). - '
      '; - foreach my $type (@statuses) { - if ($type eq 'default') { - $chgtext .= '
    • '.$othertitle.' -- '.$choices{$cancreate{'selfcreateprocessing'}{$type}}.'
    • '; - } else { - $chgtext .= '
    • '.$usertypes->{$type}.' -- '.$choices{$cancreate{'selfcreateprocessing'}{$type}}.'
    • '; - } - } - $chgtext .= '
    '; + if (@types) { + if (@statuses) { + $chgtext .= &mt('Processing of requests to create account with e-mail verification set as follows:'). + '
      '; + foreach my $status (@statuses) { + if ($status eq 'default') { + $chgtext .= '
    • '.$othertitle.' -- '.$choices{$cancreate{'selfcreateprocessing'}{$status}}.'
    • '; + } else { + $chgtext .= '
    • '.$usertypes{$status}.' -- '.$choices{$cancreate{'selfcreateprocessing'}{$status}}.'
    • '; + } + } + $chgtext .= '
    '; + } } else { - $chgtext .= &mt('Processing of requests to create account with e-mail address as username set to: "[_1]"', - $choices{$cancreate{'selfcreateprocessing'}{'default'}}); + $chgtext .= &mt('Processing of requests to create account with e-mail verification set to: "[_1]"', + $choices{$cancreate{'selfcreateprocessing'}{'default'}}); + } + } elsif ($type eq 'emailverified') { + my %options = &Apache::lonlocal::texthash ( + all => 'Same as e-mail', + first => 'Omit @domain', + free => 'Free to choose', + ); + if (@types) { + if (@statuses) { + $chgtext .= &mt('For self-created accounts verified by e-mail address, username is set as follows:'). + '
      '; + foreach my $status (@statuses) { + if ($status eq 'default') { + $chgtext .= '
    • '.$othertitle.' -- '.$options{$cancreate{'emailverified'}{$status}}.'
    • '; + } else { + $chgtext .= '
    • '.$usertypes{$status}.' -- '.$options{$cancreate{'emailverified'}{$status}}.'
    • '; + } + } + $chgtext .= '
    '; + } + } else { + $chgtext .= &mt("For self-created accounts verified by e-mail address, user's username is: '[_1]'", + $options{$cancreate{'emailverified'}{'default'}}); + } + } elsif ($type eq 'emailoptions') { + my %options = &Apache::lonlocal::texthash ( + any => 'Any e-mail', + inst => 'Institutional only', + noninst => 'Non-institutional only', + custom => 'Custom restrictions', + ); + if (@types) { + if (@statuses) { + $chgtext .= &mt('For self-created accounts verified by e-mail address, requirements for e-mail address are as follows:'). + '
      '; + foreach my $status (@statuses) { + if ($type eq 'default') { + $chgtext .= '
    • '.$othertitle.' -- '.$options{$cancreate{'emailoptions'}{$status}}.'
    • '; + } else { + $chgtext .= '
    • '.$usertypes{$status}.' -- '.$options{$cancreate{'emailoptions'}{$status}}.'
    • '; + } + } + $chgtext .= '
    '; + } + } else { + if ($cancreate{'emailoptions'}{'default'} eq 'any') { + $chgtext .= &mt('For self-created accounts verified by e-mail address, any e-mail may be used'); + } else { + $chgtext .= &mt('For self-created accounts verified by e-mail address, e-mail restricted to: "[_1]"', + $options{$cancreate{'emailoptions'}{'default'}}); + } + } + } elsif ($type eq 'emaildomain') { + my $output; + if (@statuses) { + foreach my $type (@statuses) { + if (ref($cancreate{'emaildomain'}{$type}) eq 'HASH') { + if ($cancreate{'emailoptions'}{$type} eq 'inst') { + if ($type eq 'default') { + if ((ref($cancreate{'emaildomain'}{$type}) ne 'HASH') || + ($cancreate{'emaildomain'}{$type}{'inst'} eq '')) { + $output = '
  • '.$othertitle.' -- '.&mt('No restriction on e-mail domain').'
  • '; + } else { + $output = '
  • '.$othertitle.' -- '.&mt("User's e-mail address needs to end: [_1]", + $cancreate{'emaildomain'}{$type}{'inst'}).'
  • '; + } + } else { + if ((ref($cancreate{'emaildomain'}{$type}) ne 'HASH') || + ($cancreate{'emaildomain'}{$type}{'inst'} eq '')) { + $output = '
  • '.$usertypes{$type}.' -- '.&mt('No restriction on e-mail domain').'
  • '; + } else { + $output = '
  • '.$usertypes{$type}.' -- '.&mt("User's e-mail address needs to end: [_1]", + $cancreate{'emaildomain'}{$type}{'inst'}).'
  • '; + } + } + } elsif ($cancreate{'emailoptions'}{$type} eq 'noninst') { + if ($type eq 'default') { + if ((ref($cancreate{'emaildomain'}{$type}) ne 'HASH') || + ($cancreate{'emaildomain'}{$type}{'noninst'} eq '')) { + $output = '
  • '.$othertitle.' -- '.&mt('No restriction on e-mail domain').'
  • '; + } else { + $output = '
  • '.$othertitle.' -- '.&mt("User's e-mail address must not end: [_1]", + $cancreate{'emaildomain'}{$type}{'noninst'}).'
  • '; + } + } else { + if ((ref($cancreate{'emaildomain'}{$type}) ne 'HASH') || + ($cancreate{'emaildomain'}{$type}{'noninst'} eq '')) { + $output = '
  • '.$usertypes{$type}.' -- '.&mt('No restriction on e-mail domain').'
  • '; + } else { + $output = '
  • '.$usertypes{$type}.' -- '.&mt("User's e-mail address must not end: [_1]", + $cancreate{'emaildomain'}{$type}{'noninst'}).'
  • '; + } + } + } + } + } + } + if ($output ne '') { + $chgtext .= &mt('For self-created accounts verified by e-mail address:'). + '
      '.$output.'
    '; } } elsif ($type eq 'captcha') { if ($savecaptcha{$type} eq 'notused') { @@ -9287,13 +14036,17 @@ sub modify_selfcreation { $chgtext .= '
  • '.&mt('Private key set to [_1]',$pubkey).'
  • '; } $chgtext .= ''; + } elsif ($type eq 'recaptchaversion') { + if ($savecaptcha{'captcha'} eq 'recaptcha') { + $chgtext .= &mt('ReCAPTCHA set to version [_1]',$savecaptcha{$type}); + } } elsif ($type eq 'emailusername') { if (ref($cancreate{'emailusername'}) eq 'HASH') { - if (ref($types) eq 'ARRAY') { - foreach my $type (@{$types}) { + if (@statuses) { + foreach my $type (@statuses) { if (ref($cancreate{'emailusername'}{$type}) eq 'HASH') { if (keys(%{$cancreate{'emailusername'}{$type}}) > 0) { - $chgtext .= &mt('When self-creating account with e-mail as username, the following information will be provided by [_1]:',"'$usertypes->{$type}'"). + $chgtext .= &mt('When self-creating account with e-mail verification, the following information will be provided by [_1]:',"'$usertypes{$type}'"). '
      '; foreach my $field (@{$infofields}) { if ($cancreate{'emailusername'}{$type}{$field}) { @@ -9302,48 +14055,86 @@ sub modify_selfcreation { } $chgtext .= '
    '; } else { - $chgtext .= &mt('When self creating account with e-mail as username, no information besides e-mail address will be provided by [_1].',"'$usertypes->{$type}'").'
    '; + $chgtext .= &mt('When self creating account with e-mail verification, no information besides e-mail address will be provided by [_1].',"'$usertypes{$type}'").'
    '; } } else { - $chgtext .= &mt('When self creating account with e-mail as username, no information besides e-mail address will be provided by [_1].',"'$usertypes->{$type}'").'
    '; + $chgtext .= &mt('When self creating account with e-mail verification, no information besides e-mail address will be provided by [_1].',"'$usertypes{$type}'").'
    '; } } } } } elsif ($type eq 'notify') { - $chgtext = &mt('No Domain Coordinators will receive notification of username requests requiring approval.'); + my $numapprove = 0; if (ref($changes{'cancreate'}) eq 'ARRAY') { if ((grep(/^notify$/,@{$changes{'cancreate'}})) && (ref($cancreate{'notify'}) eq 'HASH')) { if ($cancreate{'notify'}{'approval'}) { - $chgtext = &mt('Notification of username requests requiring approval will be sent to: ').$cancreate{'notify'}{'approval'}; + $chgtext .= &mt('Notification of username requests requiring approval will be sent to: ').$cancreate{'notify'}{'approval'}; + $numapprove ++; } } } + unless ($numapprove) { + $chgtext .= &mt('No Domain Coordinators will receive notification of username requests requiring approval.'); + } } if ($chgtext) { $resulttext .= '
  • '.$chgtext.'
  • '; } } } - if (ref($changes{'email_rule'}) eq 'ARRAY') { + if ((ref($changes{'email_rule'}) eq 'ARRAY') && (@{$changes{'email_rule'}} > 0)) { my ($emailrules,$emailruleorder) = &Apache::lonnet::inst_userrules($dom,'email'); - my $chgtext = '
      '; - foreach my $type (@email_rule) { - if (ref($emailrules->{$type}) eq 'HASH') { - $chgtext .= '
    • '.$emailrules->{$type}{'name'}.'
    • '; + foreach my $type (@{$changes{'email_rule'}}) { + if (ref($email_rule{$type}) eq 'ARRAY') { + my $chgtext = '
        '; + foreach my $rule (@{$email_rule{$type}}) { + if (ref($emailrules->{$rule}) eq 'HASH') { + $chgtext .= '
      • '.$emailrules->{$rule}{'name'}.'
      • '; + } + } + $chgtext .= '
      '; + my $typename; + if (@types) { + if ($type eq 'default') { + $typename = $othertitle; + } else { + $typename = $usertypes{$type}; + } + $chgtext .= &mt('(Affiliation: [_1])',$typename); + } + if (@{$email_rule{$type}} > 0) { + $resulttext .= '
    • '. + &mt('Accounts may not be created by users verified by e-mail, for e-mail addresses of the following types: ', + $usertypes{$type}). + $chgtext. + '
    • '; + } else { + $resulttext .= '
    • '. + &mt('There are now no restrictions on e-mail addresses which may be used for verification when a user requests an account.'). + '
    • '. + &mt('(Affiliation: [_1])',$typename); + } } } - $chgtext .= '
    '; - if (@email_rule > 0) { - $resulttext .= '
  • '. - &mt('Accounts may not be created by users self-enrolling with e-mail addresses of the following types: '). - $chgtext. - '
  • '; - } else { - $resulttext .= '
  • '. - &mt('There are now no restrictions on e-mail addresses which may be used as a username when self-enrolling.'). - '
  • '; + } + if (ref($changes{'inststatus'}) eq 'ARRAY') { + if (ref($save_inststatus{'inststatusguest'}) eq 'ARRAY') { + if (@{$save_inststatus{'inststatusguest'}} > 0) { + my $chgtext = '
      '; + foreach my $type (@{$save_inststatus{'inststatusguest'}}) { + $chgtext .= '
    • '.$usertypes{$type}.'
    • '; + } + $chgtext .= '
    '; + $resulttext .= '
  • '. + &mt('A user will self-report one of the following affiliations when requesting an account verified by e-mail: '). + $chgtext. + '
  • '; + } else { + $resulttext .= '
  • '. + &mt('No affiliations available for self-reporting when requesting an account verified by e-mail.'). + '
  • '; + } } } if (ref($changes{'selfcreate'}) eq 'ARRAY') { @@ -9351,9 +14142,9 @@ sub modify_selfcreation { my %fieldtitles = &Apache::loncommon::personal_data_fieldtitles(); foreach my $type (@{$changes{'selfcreate'}}) { my $typename = $type; - if (ref($usertypes) eq 'HASH') { - if ($usertypes->{$type} ne '') { - $typename = $usertypes->{$type}; + if (keys(%usertypes) > 0) { + if ($usertypes{$type} ne '') { + $typename = $usertypes{$type}; } } my @modifiable; @@ -9376,6 +14167,12 @@ sub modify_selfcreation { $resulttext .= ''; } $resulttext .= ''; + my $cachetime = 24*60*60; + $domdefaults{'inststatusguest'} = $save_inststatus{'inststatusguest'}; + &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; + } } else { $resulttext = &mt('No changes made to self-creation settings'); } @@ -9390,24 +14187,30 @@ 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; } } - my ($newpub,$newpriv,$currpub,$currpriv); + my ($newpub,$newpriv,$currpub,$currpriv,$newversion,$currversion); if ($newsettings->{'captcha'} eq 'recaptcha') { $newpub = $env{'form.'.$container.'_recaptchapub'}; $newpriv = $env{'form.'.$container.'_recaptchapriv'}; @@ -9417,10 +14220,16 @@ sub process_captcha { public => $newpub, private => $newpriv, }; - } - if (ref($current->{'recaptchakeys'}) eq 'HASH') { - $currpub = $current->{'recaptchakeys'}{'public'}; - $currpriv = $current->{'recaptchakeys'}{'private'}; + $newversion = $env{'form.'.$container.'_recaptchaversion'}; + $newversion =~ s/\D//g; + if ($newversion ne '2') { + $newversion = 1; + } + $newsettings->{'recaptchaversion'} = $newversion; + } + if (ref($current{'recaptchakeys'}) eq 'HASH') { + $currpub = $current{'recaptchakeys'}{'public'}; + $currpriv = $current{'recaptchakeys'}{'private'}; unless ($newsettings->{'captcha'} eq 'recaptcha') { $newsettings->{'recaptchakeys'} = { public => '', @@ -9428,6 +14237,25 @@ sub process_captcha { } } } + if ($current{'captcha'} eq 'recaptcha') { + $currversion = $current{'recaptchaversion'}; + if ($currversion ne '2') { + $currversion = 1; + } + } + if ($currversion ne $newversion) { + if ($container eq 'cancreate') { + if (ref($changes->{'cancreate'}) eq 'ARRAY') { + push(@{$changes->{'cancreate'}},'recaptchaversion'); + } elsif (!defined($changes->{'cancreate'})) { + $changes->{'cancreate'} = ['recaptchaversion']; + } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; + } else { + $changes->{'recaptchaversion'} = 1; + } + } if (($newpub ne $currpub) || ($newpriv ne $currpriv)) { if ($container eq 'cancreate') { if (ref($changes->{'cancreate'}) eq 'ARRAY') { @@ -9435,6 +14263,8 @@ sub process_captcha { } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['recaptchakeys']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'recaptchakeys'} = 1; } @@ -9549,7 +14379,8 @@ sub modify_defaults { my ($dom,$lastactref,%domconfig) = @_; my ($resulttext,$mailmsgtxt,%newvalues,%changes,@errors); my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); - my @items = ('auth_def','auth_arg_def','lang_def','timezone_def','datelocale_def','portal_def'); + my @items = ('auth_def','auth_arg_def','lang_def','timezone_def','datelocale_def', + 'portal_def'); my @authtypes = ('internal','krb4','krb5','localauth'); foreach my $item (@items) { $newvalues{$item} = $env{'form.'.$item}; @@ -9599,6 +14430,18 @@ sub modify_defaults { } $domdefaults{$item} = $newvalues{$item}; } + my %staticdefaults = ( + 'intauth_cost' => 10, + 'intauth_check' => 0, + 'intauth_switch' => 0, + ); + foreach my $item ('intauth_cost','intauth_check','intauth_switch') { + if (exists($domdefaults{$item})) { + $newvalues{$item} = $domdefaults{$item}; + } else { + $newvalues{$item} = $staticdefaults{$item}; + } + } my %defaults_hash = ( defaults => \%newvalues, ); @@ -9617,9 +14460,18 @@ sub modify_defaults { } my @todelete = &Apache::loncommon::get_env_multiple('form.inststatus_delete'); my @allpos; - my %guests; my %alltypes; - my ($currtitles,$currguests,$currorder); + my @inststatusguest; + if (ref($currinststatus) eq 'HASH') { + if (ref($currinststatus->{'inststatusguest'}) eq 'ARRAY') { + foreach my $type (@{$currinststatus->{'inststatusguest'}}) { + unless (grep(/^\Q$type\E$/,@todelete)) { + push(@inststatusguest,$type); + } + } + } + } + my ($currtitles,$currorder); if (ref($currinststatus) eq 'HASH') { if (ref($currinststatus->{'inststatusorder'}) eq 'ARRAY') { foreach my $type (@{$currinststatus->{'inststatusorder'}}) { @@ -9634,14 +14486,8 @@ sub modify_defaults { $allpos[$position] = $type; $alltypes{$type} = $env{'form.inststatus_title_'.$type}; $alltypes{$type} =~ s/`//g; - if ($env{'form.inststatus_guest_'.$type}) { - $guests{$type} = 1; - } } } - if (ref($currinststatus->{'inststatusguest'}) eq 'ARRAY') { - $currguests = join(',',@{$currinststatus->{'inststatusguest'}}); - } $currorder = join(',',@{$currinststatus->{'inststatusorder'}}); $currtitles =~ s/,$//; } @@ -9650,9 +14496,6 @@ sub modify_defaults { my $newtype = $env{'form.addinststatus'}; $newtype =~ s/\W//g; unless (exists($alltypes{$newtype})) { - if ($env{'form.addinststatus_guest'}) { - $guests{$newtype} = 1; - } $alltypes{$newtype} = $env{'form.addinststatus_title'}; $alltypes{$newtype} =~ s/`//g; my $position = $env{'form.addinststatus_pos'}; @@ -9662,13 +14505,10 @@ sub modify_defaults { } } } - my (@orderedstatus,@orderedguests); + my @orderedstatus; foreach my $type (@allpos) { unless (($type eq '') || (grep(/^\Q$type\E$/,@orderedstatus))) { push(@orderedstatus,$type); - if ($guests{$type}) { - push(@orderedguests,$type); - } } } foreach my $type (keys(%alltypes)) { @@ -9679,7 +14519,7 @@ sub modify_defaults { $defaults_hash{'inststatus'} = { inststatustypes => \%alltypes, inststatusorder => \@orderedstatus, - inststatusguest => \@orderedguests, + inststatusguest => \@inststatusguest, }; if (ref($defaults_hash{'inststatus'}) eq 'HASH') { foreach my $item ('inststatustypes','inststatusorder','inststatusguest') { @@ -9689,9 +14529,6 @@ sub modify_defaults { if ($currorder ne join(',',@orderedstatus)) { $changes{'inststatus'}{'inststatusorder'} = 1; } - if ($currguests ne join(',',@orderedguests)) { - $changes{'inststatus'}{'inststatusguest'} = 1; - } my $newtitles; foreach my $item (@orderedstatus) { $newtitles .= $alltypes{$item}.','; @@ -9710,26 +14547,15 @@ sub modify_defaults { foreach my $item (sort(keys(%changes))) { if ($item eq 'inststatus') { if (ref($changes{'inststatus'}) eq 'HASH') { - if (($changes{'inststatus'}{'inststatustypes'}) || $changes{'inststatus'}{'inststatusorder'}) { + if (@orderedstatus) { $resulttext .= '
  • '.&mt('Institutional user status types set to:').' '; foreach my $type (@orderedstatus) { $resulttext .= $alltypes{$type}.', '; } $resulttext =~ s/, $//; $resulttext .= '
  • '; - } - if ($changes{'inststatus'}{'inststatusguest'}) { - $resulttext .= '
  • '; - if (@orderedguests) { - $resulttext .= &mt('Types assignable to "non-institutional" usernames set to:').' '; - foreach my $type (@orderedguests) { - $resulttext .= $alltypes{$type}.', '; - } - $resulttext =~ s/, $//; - } else { - $resulttext .= &mt('Types assignable to "non-institutional" usernames set to none.'); - } - $resulttext .= '
  • '; + } else { + $resulttext .= '
  • '.&mt('Institutional user status types deleted').'
  • '; } } } else { @@ -9793,7 +14619,7 @@ sub modify_scantron { my $custom = 'custom.tab'; my $default = 'default.tab'; my $servadm = $r->dir_config('lonAdmEMail'); - my ($configuserok,$author_ok,$switchserver) = + my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm); if ($env{'form.scantronformat.filename'} ne '') { my $error; @@ -9828,6 +14654,67 @@ sub modify_scantron { if ($env{'form.scantronformat_del'}) { $confhash{'scantron'}{'scantronformat'} = ''; $changes{'scantronformat'} = 1; + } else { + $confhash{'scantron'}{'scantronformat'} = $domconfig{'scantron'}{'scantronformat'}; + } + } + } + my @options = ('hdr','pad','rem'); + my @fields = &scantroncsv_fields(); + my %titles = &scantronconfig_titles(); + my @formats = &Apache::loncommon::get_env_multiple('form.scantronconfig'); + my ($newdat,$currdat,%newcol,%currcol); + if (grep(/^dat$/,@formats)) { + $confhash{'scantron'}{config}{dat} = 1; + $newdat = 1; + } else { + $newdat = 0; + } + if (grep(/^csv$/,@formats)) { + my %bynum; + foreach my $field (@fields) { + if ($env{'form.scantronconfig_csv_'.$field} =~ /^(\d+)$/) { + my $posscol = $1; + if (($posscol < 20) && (!$bynum{$posscol})) { + $confhash{'scantron'}{config}{csv}{fields}{$field} = $posscol; + $bynum{$posscol} = $field; + $newcol{$field} = $posscol; + } + } + } + if (keys(%newcol)) { + foreach my $option (@options) { + if ($env{'form.scantroncsv_'.$option}) { + $confhash{'scantron'}{config}{csv}{options}{$option} = 1; + } + } + } + } + $currdat = 1; + if (ref($domconfig{'scantron'}) eq 'HASH') { + if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') { + unless (exists($domconfig{'scantron'}{'config'}{'dat'})) { + $currdat = 0; + } + if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') { + if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') { + %currcol = %{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}}; + } + } + } + } + if ($currdat != $newdat) { + $changes{'config'} = 1; + } else { + foreach my $field (@fields) { + if ($currcol{$field} ne '') { + if ($currcol{$field} ne $newcol{$field}) { + $changes{'config'} = 1; + last; + } + } elsif ($newcol{$field} ne '') { + $changes{'config'} = 1; + last; } } } @@ -9838,29 +14725,64 @@ sub modify_scantron { if (keys(%changes) > 0) { if (ref($confhash{'scantron'}) eq 'HASH') { $resulttext = &mt('Changes made:').'
      '; - if ($confhash{'scantron'}{'scantronformat'} eq '') { - $resulttext .= '
    • '.&mt('[_1] bubblesheet format file removed; [_2] file will be used for courses in this domain.',$custom,$default).'
    • '; - } else { - $resulttext .= '
    • '.&mt('Custom bubblesheet format file ([_1]) uploaded for use with courses in this domain.',$custom).'
    • '; + if ($changes{'scantronformat'}) { + if ($confhash{'scantron'}{'scantronformat'} eq '') { + $resulttext .= '
    • '.&mt('[_1] bubblesheet format file removed; [_2] file will be used for courses in this domain.',$custom,$default).'
    • '; + } else { + $resulttext .= '
    • '.&mt('Custom bubblesheet format file ([_1]) uploaded for use with courses in this domain.',$custom).'
    • '; + } + } + if ($changes{'config'}) { + if (ref($confhash{'scantron'}{'config'}) eq 'HASH') { + if ($confhash{'scantron'}{'config'}{'dat'}) { + $resulttext .= '
    • '.&mt('Bubblesheet data upload formats includes .dat format').'
    • '; + } + if (ref($confhash{'scantron'}{'config'}{'csv'}) eq 'HASH') { + if (ref($confhash{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') { + if (keys(%{$confhash{'scantron'}{'config'}{'csv'}{'fields'}})) { + $resulttext .= '
    • '.&mt('Bubblesheet data upload formats includes .csv format, with following fields/column numbers supported:').'
        '; + foreach my $field (@fields) { + if ($confhash{'scantron'}{'config'}{'csv'}{'fields'}{$field} ne '') { + my $showcol = $confhash{'scantron'}{'config'}{'csv'}{'fields'}{$field} + 1; + $resulttext .= '
      • '.$titles{$field}.': '.$showcol.'
      • '; + } + } + $resulttext .= '
    • '; + if (ref($confhash{'scantron'}{'config'}{'csv'}{'options'}) eq 'HASH') { + if (keys(%{$confhash{'scantron'}{'config'}{'csv'}{'options'}})) { + $resulttext .= '
    • '.&mt('Bubblesheet data upload formats includes .csv format, with following options:').'
        '; + foreach my $option (@options) { + if ($confhash{'scantron'}{'config'}{'csv'}{'options'}{$option} ne '') { + $resulttext .= '
      • '.$titles{$option}.'
      • '; + } + } + $resulttext .= '
    • '; + } + } + } + } + } + } else { + $resulttext .= '
    • '.&mt('No bubblesheet data upload formats set -- will default to assuming .dat format').'
    • '; + } } $resulttext .= '
    '; } else { $resulttext = &mt('Changes made to bubblesheet format file.'); } - $resulttext .= ''; &Apache::loncommon::devalidate_domconfig_cache($dom); if (ref($lastactref) eq 'HASH') { $lastactref->{'domainconfig'} = 1; } } else { - $resulttext = &mt('No changes made to bubblesheet format file'); + $resulttext = &mt('No changes made to bubblesheet format settings'); } } else { $resulttext = ''. &mt('An error occurred: [_1]',$putresult).''; } } else { - $resulttext = &mt('No changes made to bubblesheet format file'); + $resulttext = &mt('No changes made to bubblesheet format settings'); } if ($errors) { $resulttext .= &mt('The following errors occurred: ').'
      '. @@ -10109,6 +15031,10 @@ sub modify_coursecategories { } $resulttext .= '
    '; } + &Apache::lonnet::do_cache_new('cats',$dom,$cathash,3600); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'cats'} = 1; + } } $resulttext .= ''; if ($changes{'unauth'} || $changes{'auth'}) { @@ -10260,32 +15186,210 @@ sub modify_serverstatuses { } sub modify_helpsettings { - my ($r,$dom,$confname,%domconfig) = @_; + my ($r,$dom,$confname,$lastactref,%domconfig) = @_; my ($resulttext,$errors,%changes,%helphash); my %defaultchecked = ('submitbugs' => 'on'); my @offon = ('off','on'); my @toggles = ('submitbugs'); + my %current = ('submitbugs' => '', + 'adhoc' => {}, + ); if (ref($domconfig{'helpsettings'}) eq 'HASH') { - foreach my $item (@toggles) { - if ($defaultchecked{$item} eq 'on') { - if ($domconfig{'helpsettings'}{$item} eq '') { - if ($env{'form.'.$item} eq '0') { - $changes{$item} = 1; - } - } elsif ($domconfig{'helpsettings'}{$item} ne $env{'form.'.$item}) { + %current = %{$domconfig{'helpsettings'}}; + } + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + foreach my $item (@toggles) { + if ($defaultchecked{$item} eq 'on') { + if ($current{$item} eq '') { + if ($env{'form.'.$item} eq '0') { $changes{$item} = 1; } - } elsif ($defaultchecked{$item} eq 'off') { - if ($domconfig{'helpsettings'}{$item} eq '') { - if ($env{'form.'.$item} eq '1') { - $changes{$item} = 1; - } - } elsif ($domconfig{'helpsettings'}{$item} ne $env{'form.'.$item}) { + } elsif ($current{$item} ne $env{'form.'.$item}) { + $changes{$item} = 1; + } + } elsif ($defaultchecked{$item} eq 'off') { + if ($current{$item} eq '') { + if ($env{'form.'.$item} eq '1') { $changes{$item} = 1; } + } elsif ($current{$item} ne $env{'form.'.$item}) { + $changes{$item} = 1; } - if (($env{'form.'.$item} eq '0') || ($env{'form.'.$item} eq '1')) { - $helphash{'helpsettings'}{$item} = $env{'form.'.$item}; + } + if (($env{'form.'.$item} eq '0') || ($env{'form.'.$item} eq '1')) { + $helphash{'helpsettings'}{$item} = $env{'form.'.$item}; + } + } + my $maxnum = $env{'form.helproles_maxnum'}; + my $confname = $dom.'-domainconfig'; + my %existing=&Apache::lonnet::dump('roles',$dom,$confname,'rolesdef_'); + my (@allpos,%newsettings,%changedprivs,$newrole); + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my @accesstypes = ('all','dh','da','none','status','inc','exc'); + my %domhelpdesk = &Apache::lonnet::get_active_domroles($dom,['dh','da']); + my %lt = &Apache::lonlocal::texthash( + s => 'system', + d => 'domain', + order => 'Display order', + access => 'Role usage', + all => 'All with domain helpdesk or helpdesk assistant role', + dh => 'All with domain helpdesk role', + da => 'All with domain helpdesk assistant role', + none => 'None', + status => 'Determined based on institutional status', + inc => 'Include all, but exclude specific personnel', + exc => 'Exclude all, but include specific personnel', + ); + for (my $num=0; $num<=$maxnum; $num++) { + my ($prefix,$identifier,$rolename,%curr); + if ($num == $maxnum) { + next unless ($env{'form.newcusthelp'} == $maxnum); + $identifier = 'custhelp'.$num; + $prefix = 'helproles_'.$num; + $rolename = $env{'form.custhelpname'.$num}; + $rolename=~s/[^A-Za-z0-9]//gs; + next if ($rolename eq ''); + next if (exists($existing{'rolesdef_'.$rolename})); + my %newprivs = &Apache::lonuserutils::custom_role_update($rolename,$identifier); + my $result = &Apache::lonnet::definerole($rolename,$newprivs{'s'},$newprivs{'d'}, + $newprivs{'c'},$confname,$dom); + if ($result ne 'ok') { + $errors .= '
  • '. + &mt('An error occurred storing the new custom role: [_1]', + $result).'
  • '; + next; + } else { + $changedprivs{$rolename} = \%newprivs; + $newrole = $rolename; + } + } else { + $prefix = 'helproles_'.$num; + $rolename = $env{'form.'.$prefix}; + next if ($rolename eq ''); + next unless (exists($existing{'rolesdef_'.$rolename})); + $identifier = 'custhelp'.$num; + my %newprivs = &Apache::lonuserutils::custom_role_update($rolename,$identifier); + my %currprivs; + ($currprivs{'s'},$currprivs{'d'},$currprivs{'c'}) = + split(/\_/,$existing{'rolesdef_'.$rolename}); + foreach my $level ('c','d','s') { + if ($newprivs{$level} ne $currprivs{$level}) { + my $result = &Apache::lonnet::definerole($rolename,$newprivs{'s'},$newprivs{'d'}, + $newprivs{'c'},$confname,$dom); + if ($result ne 'ok') { + $errors .= '
  • '. + &mt('An error occurred storing privileges for existing role [_1]: [_2]', + $rolename,$result).'
  • '; + } else { + $changedprivs{$rolename} = \%newprivs; + } + last; + } + } + if (ref($current{'adhoc'}) eq 'HASH') { + if (ref($current{'adhoc'}{$rolename}) eq 'HASH') { + %curr = %{$current{'adhoc'}{$rolename}}; + } + } + } + my $newpos = $env{'form.'.$prefix.'_pos'}; + $newpos =~ s/\D+//g; + $allpos[$newpos] = $rolename; + my $newdesc = $env{'form.'.$prefix.'_desc'}; + $helphash{'helpsettings'}{'adhoc'}{$rolename}{'desc'} = $newdesc; + if ($curr{'desc'}) { + if ($curr{'desc'} ne $newdesc) { + $changes{'customrole'}{$rolename}{'desc'} = 1; + $newsettings{$rolename}{'desc'} = $newdesc; + } + } elsif ($newdesc ne '') { + $changes{'customrole'}{$rolename}{'desc'} = 1; + $newsettings{$rolename}{'desc'} = $newdesc; + } + my $access = $env{'form.'.$prefix.'_access'}; + if (grep(/^\Q$access\E$/,@accesstypes)) { + $helphash{'helpsettings'}{'adhoc'}{$rolename}{'access'} = $access; + if ($access eq 'status') { + my @statuses = &Apache::loncommon::get_env_multiple('form.'.$prefix.'_status'); + if (scalar(@statuses) == 0) { + $helphash{'helpsettings'}{'adhoc'}{$rolename}{'access'} = 'none'; + } else { + my (@shownstatus,$numtypes); + $helphash{'helpsettings'}{'adhoc'}{$rolename}{$access} = []; + if (ref($types) eq 'ARRAY') { + $numtypes = scalar(@{$types}); + foreach my $type (sort(@statuses)) { + if ($type eq 'default') { + push(@{$helphash{'helpsettings'}{'adhoc'}{$rolename}{$access}},$type); + } elsif (grep(/^\Q$type\E$/,@{$types})) { + push(@{$helphash{'helpsettings'}{'adhoc'}{$rolename}{$access}},$type); + push(@shownstatus,$usertypes->{$type}); + } + } + } + if (grep(/^default$/,@statuses)) { + push(@shownstatus,$othertitle); + } + if (scalar(@shownstatus) == 1+$numtypes) { + $helphash{'helpsettings'}{'adhoc'}{$rolename}{'access'} = 'all'; + delete($helphash{'helpsettings'}{'adhoc'}{$rolename}{'status'}); + } else { + $newsettings{$rolename}{'status'} = join(' '.&mt('or').' ',@shownstatus); + if (ref($curr{'status'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($helphash{'helpsettings'}{'adhoc'}{$rolename}{$access},$curr{$access}); + if (@diffs) { + $changes{'customrole'}{$rolename}{$access} = 1; + } + } elsif (@{$helphash{'helpsettings'}{'adhoc'}{$rolename}{$access}}) { + $changes{'customrole'}{$rolename}{$access} = 1; + } + } + } + } elsif (($access eq 'inc') || ($access eq 'exc')) { + my @personnel = &Apache::loncommon::get_env_multiple('form.'.$prefix.'_staff_'.$access); + my @newspecstaff; + $helphash{'helpsettings'}{'adhoc'}{$rolename}{$access} = []; + foreach my $person (sort(@personnel)) { + if ($domhelpdesk{$person}) { + push(@{$helphash{'helpsettings'}{'adhoc'}{$rolename}{$access}},$person); + } + } + if (ref($curr{$access}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($helphash{'helpsettings'}{'adhoc'}{$rolename}{$access},$curr{$access}); + if (@diffs) { + $changes{'customrole'}{$rolename}{$access} = 1; + } + } elsif (@{$helphash{'helpsettings'}{'adhoc'}{$rolename}{$access}}) { + $changes{'customrole'}{$rolename}{$access} = 1; + } + foreach my $person (@{$helphash{'helpsettings'}{'adhoc'}{$rolename}{$access}}) { + my ($uname,$udom) = split(/:/,$person); + push(@newspecstaff,&Apache::loncommon::aboutmewrapper(&Apache::loncommon::plainname($uname,$udom,'lastname'),$uname,$udom)); + } + $newsettings{$rolename}{$access} = join(', ',sort(@newspecstaff)); + } + } else { + $helphash{'helpsettings'}{'adhoc'}{$rolename}{'access'}= 'all'; + } + unless ($curr{'access'} eq $access) { + $changes{'customrole'}{$rolename}{'access'} = 1; + $newsettings{$rolename}{'access'} = $lt{$helphash{'helpsettings'}{'adhoc'}{$rolename}{'access'}}; + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $rolename (@allpos) { + if ($rolename ne '') { + $helphash{'helpsettings'}{'adhoc'}{$rolename}{'order'} = $idx; + if (ref($current{'adhoc'}) eq 'HASH') { + if (ref($current{'adhoc'}{$rolename}) eq 'HASH') { + if ($current{'adhoc'}{$rolename}{'order'} ne $idx) { + $changes{'customrole'}{$rolename}{'order'} = 1; + $newsettings{$rolename}{'order'} = $idx+1; + } + } + } + $idx ++; } } } @@ -10293,25 +15397,115 @@ sub modify_helpsettings { if (keys(%changes) > 0) { $putresult = &Apache::lonnet::put_dom('configuration',\%helphash,$dom); if ($putresult eq 'ok') { - $resulttext = &mt('Changes made:').'
      '; + if (ref($helphash{'helpsettings'}) eq 'HASH') { + $domdefaults{'submitbugs'} = $helphash{'helpsettings'}{'submitbugs'}; + if (ref($helphash{'helpsettings'}{'adhoc'}) eq 'HASH') { + $domdefaults{'adhocroles'} = $helphash{'helpsettings'}{'adhoc'}; + } + } + my $cachetime = 24*60*60; + &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; + } + } else { + $errors .= '
    • '. + &mt('An error occurred storing the settings: [_1]', + $putresult).'
    • '; + } + } + if ((keys(%changes) && ($putresult eq 'ok')) || (keys(%changedprivs))) { + $resulttext = &mt('Changes made:').'
        '; + my (%shownprivs,@levelorder); + @levelorder = ('c','d','s'); + if ((keys(%changes)) && ($putresult eq 'ok')) { foreach my $item (sort(keys(%changes))) { if ($item eq 'submitbugs') { $resulttext .= '
      • '.&mt('Display link to: [_1] set to "'.$offon[$env{'form.'.$item}].'".', &Apache::loncommon::modal_link('http://bugs.loncapa.org', &mt('LON-CAPA bug tracker'),600,500)).'
      • '; + } elsif ($item eq 'customrole') { + if (ref($changes{'customrole'}) eq 'HASH') { + my @keyorder = ('order','desc','access','status','exc','inc'); + my %keytext = &Apache::lonlocal::texthash( + order => 'Order', + desc => 'Role description', + access => 'Role usage', + status => 'Allowed institutional types', + exc => 'Allowed personnel', + inc => 'Disallowed personnel', + ); + foreach my $role (sort(keys(%{$changes{'customrole'}}))) { + if (ref($changes{'customrole'}{$role}) eq 'HASH') { + if ($role eq $newrole) { + $resulttext .= '
      • '.&mt('New custom role added: [_1]', + $role).'
          '; + } else { + $resulttext .= '
        • '.&mt('Existing custom role modified: [_1]', + $role).'
            '; + } + foreach my $key (@keyorder) { + if ($changes{'customrole'}{$role}{$key}) { + $resulttext .= '
          • '.&mt("[_1] set to: [_2]", + $keytext{$key},$newsettings{$role}{$key}). + '
          • '; + } + } + if (ref($changedprivs{$role}) eq 'HASH') { + $shownprivs{$role} = 1; + $resulttext .= '
          • '.&mt('Privileges set to :').'
              '; + foreach my $level (@levelorder) { + foreach my $item (split(/\:/,$changedprivs{$role}{$level})) { + next if ($item eq ''); + my ($priv) = split(/\&/,$item,2); + if (&Apache::lonnet::plaintext($priv)) { + $resulttext .= '
            • '.&Apache::lonnet::plaintext($priv); + unless ($level eq 'c') { + $resulttext .= ' ('.$lt{$level}.')'; + } + $resulttext .= '
            • '; + } + } + } + $resulttext .= '
            '; + } + $resulttext .= '
        • '; + } + } + } + } + } + } + if (keys(%changedprivs)) { + foreach my $role (sort(keys(%changedprivs))) { + unless ($shownprivs{$role}) { + $resulttext .= '
        • '.&mt('Existing custom role modified: [_1]', + $role).'
            '. + '
          • '.&mt('Privileges set to :').'
              '; + foreach my $level (@levelorder) { + foreach my $item (split(/\:/,$changedprivs{$role}{$level})) { + next if ($item eq ''); + my ($priv) = split(/\&/,$item,2); + if (&Apache::lonnet::plaintext($priv)) { + $resulttext .= '
            • '.&Apache::lonnet::plaintext($priv); + unless ($level eq 'c') { + $resulttext .= ' ('.$lt{$level}.')'; + } + $resulttext .= '
            • '; + } + } + } + $resulttext .= '
        • '; } } - $resulttext .= '
        '; - } else { - $resulttext = &mt('No changes made to help settings'); - $errors .= '
      • '. - &mt('An error occurred storing the settings: [_1]', - $putresult).'
      • '; } + $resulttext .= '
      '; + } else { + $resulttext = &mt('No changes made to help settings'); } if ($errors) { $resulttext .= '
      '.&mt('The following errors occurred: ').'
        '. - $errors.'
      '; + $errors.'
    '; } return $resulttext; } @@ -10325,14 +15519,20 @@ sub modify_coursedefaults { ); my @toggles = ('uselcmath','usejsme'); my @numbers = ('anonsurvey_threshold','uploadquota_official','uploadquota_unofficial', - 'uploadquota_community','uploadquota_textbook'); + 'uploadquota_community','uploadquota_textbook','mysqltables_official', + 'mysqltables_unofficial','mysqltables_community','mysqltables_textbook'); my @types = ('official','unofficial','community','textbook'); my %staticdefaults = ( anonsurvey_threshold => 10, uploadquota => 500, postsubmit => 60, + mysqltables => 172800, ); - + my %texoptions = ( + MathJax => 'MathJax', + mimetex => &mt('Convert to Images'), + tth => &mt('TeX to HTML'), + ); $defaultshash{'coursedefaults'} = {}; if (ref($domconfig{'coursedefaults'}) ne 'HASH') { @@ -10371,26 +15571,41 @@ sub modify_coursedefaults { } $defaultshash{'coursedefaults'}{$item} = $newdef; } else { - my ($type) = ($item =~ /^\Quploadquota_\E(\w+)$/); - if (ref($domconfig{'coursedefaults'}{'uploadquota'}) eq 'HASH') { - $currdef = $domconfig{'coursedefaults'}{'uploadquota'}{$type}; + my ($setting,$type) = ($item =~ /^(uploadquota|mysqltables)_(\w+)$/); + if (ref($domconfig{'coursedefaults'}{$setting}) eq 'HASH') { + $currdef = $domconfig{'coursedefaults'}{$setting}{$type}; } $newdef =~ s/[^\w.\-]//g; - $defaultshash{'coursedefaults'}{'uploadquota'}{$type} = $newdef; + $defaultshash{'coursedefaults'}{$setting}{$type} = $newdef; } if ($currdef ne $newdef) { - my $staticdef; if ($item eq 'anonsurvey_threshold') { unless (($currdef eq '') && ($newdef == $staticdefaults{$item})) { $changes{$item} = 1; } - } else { - unless (($currdef eq '') && ($newdef == $staticdefaults{'uploadquota'})) { - $changes{'uploadquota'} = 1; + } elsif ($item =~ /^(uploadquota|mysqltables)_/) { + my $setting = $1; + unless (($currdef eq '') && ($newdef == $staticdefaults{$setting})) { + $changes{$setting} = 1; } } } } + my $texengine; + if ($env{'form.texengine'} =~ /^(MathJax|mimetex|tth)$/) { + $texengine = $env{'form.texengine'}; + my $currdef = $domconfig{'coursedefaults'}{'texengine'}; + if ($currdef eq '') { + unless ($texengine eq $Apache::lonnet::deftex) { + $changes{'texengine'} = 1; + } + } elsif ($currdef ne $texengine) { + $changes{'texengine'} = 1; + } + } + if ($texengine ne '') { + $defaultshash{'coursedefaults'}{'texengine'} = $texengine; + } my $currclone = $domconfig{'coursedefaults'}{'canclone'}; my @currclonecode; if (ref($currclone) eq 'HASH') { @@ -10511,8 +15726,8 @@ 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'})) { - foreach my $item ('uselcmath','usejsme') { + ($changes{'canclone'}) || ($changes{'mysqltables'}) || ($changes{'texengine'})) { + foreach my $item ('uselcmath','usejsme','texengine') { if ($changes{$item}) { $domdefaults{$item}=$defaultshash{'coursedefaults'}{$item}; } @@ -10575,6 +15790,11 @@ sub modify_coursedefaults { } else { $resulttext .= '
  • '.&mt('Molecule editor uses JME (Java), if supported by client OS.').'
  • '; } + } elsif ($item eq 'texengine') { + if ($defaultshash{'coursedefaults'}{'texengine'} ne '') { + $resulttext .= '
  • '.&mt('Default method to display mathematics set to: "[_1]"', + $texoptions{$defaultshash{'coursedefaults'}{'texengine'}}).'
  • '; + } } elsif ($item eq 'anonsurvey_threshold') { $resulttext .= '
  • '.&mt('Responder count required for display of anonymous survey submissions set to [_1].',$defaultshash{'coursedefaults'}{'anonsurvey_threshold'}).'
  • '; } elsif ($item eq 'uploadquota') { @@ -10590,6 +15810,18 @@ sub modify_coursedefaults { } else { $resulttext .= '
  • '.&mt('Default quota for content uploaded via Course Editor remains default: [_1] MB',$staticdefaults{'uploadquota'}).'
  • '; } + } elsif ($item eq 'mysqltables') { + if (ref($defaultshash{'coursedefaults'}{'mysqltables'}) eq 'HASH') { + $resulttext .= '
  • '.&mt('Lifetime of "Temporary" MySQL tables (student performance data) on homeserver').'
      '. + '
    • '.&mt('Official courses: [_1] s',''.$defaultshash{'coursedefaults'}{'mysqltables'}{'official'}.'').'
    • '. + '
    • '.&mt('Unofficial courses: [_1] s',''.$defaultshash{'coursedefaults'}{'mysqltables'}{'unofficial'}.'').'
    • '. + '
    • '.&mt('Textbook courses: [_1] s',''.$defaultshash{'coursedefaults'}{'mysqltables'}{'textbook'}.'').'
    • '. + '
    • '.&mt('Communities: [_1] s',''.$defaultshash{'coursedefaults'}{'mysqltables'}{'community'}.'').'
    • '. + '
    '. + '
  • '; + } else { + $resulttext .= '
  • '.&mt('Lifetime of "Temporary" MySQL tables (student performance data) on homeserver remains default: [_1] s',$staticdefaults{'uploadquota'}).'
  • '; + } } elsif ($item eq 'postsubmit') { if ($domdefaults{'postsubmit'} eq 'off') { $resulttext .= '
  • '.&mt('Submit button(s) remain enabled on page after student makes submission.'); @@ -10887,12 +16119,12 @@ sub modify_selfenrollment { $resulttext .= '
  • '; } } - if ((exists($changes{'admin'})) || (exists($changes{'default'}))) { - my $cachetime = 24*60*60; - &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); - if (ref($lastactref) eq 'HASH') { - $lastactref->{'domdefaults'} = 1; - } + } + if ((exists($changes{'admin'})) || (exists($changes{'default'}))) { + my $cachetime = 24*60*60; + &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; } } $resulttext .= ''; @@ -10906,6 +16138,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','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'); @@ -11073,6 +16643,7 @@ sub modify_usersessions { } } $defaultshash{'usersessions'}{'offloadnow'} = {}; + $defaultshash{'usersessions'}{'offloadoth'} = {}; my @offloadnow = &Apache::loncommon::get_env_multiple('form.offloadnow'); my @okoffload; if (@offloadnow) { @@ -11089,6 +16660,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') { @@ -11099,26 +16686,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)) { @@ -11135,11 +16734,16 @@ 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); + &Apache::lonnet::do_cache_new('usersessions',$dom,$defaultshash{'usersessions'},3600); if (ref($lastactref) eq 'HASH') { $lastactref->{'domdefaults'} = 1; + $lastactref->{'usersessions'} = 1; } if (keys(%changes) > 0) { my %lt = &usersession_titles(); @@ -11206,16 +16810,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 .= '
      '; @@ -11243,12 +16862,12 @@ sub modify_loadbalancing { my @sparestypes = ('primary','default'); my %typetitles = &sparestype_titles(); my $resulttext; - my (%currbalancer,%currtargets,%currrules,%existing); + my (%currbalancer,%currtargets,%currrules,%existing,%currcookies); if (ref($domconfig{'loadbalancing'}) eq 'HASH') { %existing = %{$domconfig{'loadbalancing'}}; } &get_loadbalancers_config(\%servers,\%existing,\%currbalancer, - \%currtargets,\%currrules); + \%currtargets,\%currrules,\%currcookies); my ($saveloadbalancing,%defaultshash,%changes); my ($alltypes,$othertypes,$titles) = &loadbalancing_titles($dom,$intdom,$usertypes,$types); @@ -11292,7 +16911,24 @@ sub modify_loadbalancing { push(@offloadto,$target); } } - $defaultshash{'loadbalancing'}{$balancer}{'targets'}{$sparetype} = \@offloadto; + } + if ($env{'form.loadbalancing_target_'.$i.'_hosthere'} eq $sparetype) { + unless(grep(/^\Q$balancer\E$/,@offloadto)) { + push(@offloadto,$balancer); + } + } + $defaultshash{'loadbalancing'}{$balancer}{'targets'}{$sparetype} = \@offloadto; + } + if ($env{'form.loadbalancing_cookie_'.$i}) { + $defaultshash{'loadbalancing'}{$balancer}{'cookie'} = 1; + if (exists($currbalancer{$balancer})) { + unless ($currcookies{$balancer}) { + $changes{'curr'}{$balancer}{'cookie'} = 1; + } + } + } elsif (exists($currbalancer{$balancer})) { + if ($currcookies{$balancer}) { + $changes{'curr'}{$balancer}{'cookie'} = 1; } } if (ref($currtargets{$balancer}) eq 'HASH') { @@ -11448,24 +17084,28 @@ sub modify_loadbalancing { } } } - if (keys(%toupdate)) { - my %thismachine; - my $updatedhere; - my $cachetime = 60*60*24; - map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); - foreach my $lonhost (keys(%toupdate)) { - if ($thismachine{$lonhost}) { - unless ($updatedhere) { - &Apache::lonnet::do_cache_new('loadbalancing',$dom, - $defaultshash{'loadbalancing'}, - $cachetime); - $updatedhere = 1; - } - } else { - my $cachekey = &escape('loadbalancing').':'.&escape($dom); - &Apache::lonnet::remote_devalidate_cache($lonhost,[$cachekey]); - } + if ($changes{'curr'}{$balancer}{'cookie'}) { + $resulttext .= '
    • '.&mt('Load Balancer: [_1] -- cookie use enabled', + $balancer).'
    • '; + } + } + } + if (keys(%toupdate)) { + my %thismachine; + my $updatedhere; + my $cachetime = 60*60*24; + map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); + foreach my $lonhost (keys(%toupdate)) { + if ($thismachine{$lonhost}) { + unless ($updatedhere) { + &Apache::lonnet::do_cache_new('loadbalancing',$dom, + $defaultshash{'loadbalancing'}, + $cachetime); + $updatedhere = 1; } + } else { + my $cachekey = &escape('loadbalancing').':'.&escape($dom); + &Apache::lonnet::remote_devalidate_cache($lonhost,[$cachekey]); } } } @@ -11527,24 +17167,9 @@ sub recurse_cat_deletes { return; } -sub get_active_dcs { - my ($dom) = @_; - my $now = time; - my %dompersonnel = &Apache::lonnet::get_domain_roles($dom,['dc'],$now,$now); - my %domcoords; - my $numdcs = 0; - foreach my $server (keys(%dompersonnel)) { - foreach my $user (sort(keys(%{$dompersonnel{$server}}))) { - my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,$user); - $domcoords{$uname.':'.$udom} = $dompersonnel{$server}{$user}; - } - } - return %domcoords; -} - sub active_dc_picker { my ($dom,$numinrow,$inputtype,$name,%currhash) = @_; - my %domcoords = &get_active_dcs($dom); + my %domcoords = &Apache::lonnet::get_active_domroles($dom,['dc']); my @domcoord = keys(%domcoords); if (keys(%currhash)) { foreach my $dc (keys(%currhash)) { @@ -11692,12 +17317,12 @@ sub lonbalance_targets_js { } push(@alltypes,'default','_LC_adv','_LC_author','_LC_internetdom','_LC_external'); $allinsttypes = join("','",@alltypes); - my (%currbalancer,%currtargets,%currrules,%existing); + my (%currbalancer,%currtargets,%currrules,%existing,%currcookies); if (ref($settings) eq 'HASH') { %existing = %{$settings}; } &get_loadbalancers_config($servers,\%existing,\%currbalancer, - \%currtargets,\%currrules); + \%currtargets,\%currrules,\%currcookies); my $balancers = join("','",sort(keys(%currbalancer))); return <<"END"; @@ -12108,6 +17733,8 @@ function updateCaptcha(caller,context) { var pubitem; var privtext; var pubtext; + var versionitem; + var versiontext; if (document.getElementById(context+'_recaptchapub')) { pubitem = document.getElementById(context+'_recaptchapub'); } else { @@ -12128,6 +17755,16 @@ function updateCaptcha(caller,context) { } else { return; } + if (document.getElementById(context+'_recaptchaversion')) { + versionitem = document.getElementById(context+'_recaptchaversion'); + } else { + return; + } + if (document.getElementById(context+'_recaptchavertxt')) { + versiontext = document.getElementById(context+'_recaptchavertxt'); + } else { + return; + } if (caller.checked) { if (caller.value == 'recaptcha') { pubitem.type = 'text'; @@ -12136,11 +17773,16 @@ function updateCaptcha(caller,context) { privitem.size = '40'; pubtext.innerHTML = "$lt{'pub'}"; privtext.innerHTML = "$lt{'priv'}"; + versionitem.type = 'text'; + versionitem.size = '3'; + versiontext.innerHTML = "$lt{'ver'}"; } else { pubitem.type = 'hidden'; privitem.type = 'hidden'; + versionitem.type = 'hidden'; pubtext.innerHTML = ''; privtext.innerHTML = ''; + versiontext.innerHTML = ''; } } return; @@ -12165,6 +17807,7 @@ function toggleDisplay(domForm,caller) { var optionsElement = domForm.coursecredits; var checkval = 1; var dispval = 'block'; + var selfcreateRegExp = /^cancreate_emailverified/; if (caller == 'emailoptions') { optionsElement = domForm.cancreate_email; } @@ -12175,6 +17818,11 @@ function toggleDisplay(domForm,caller) { optionsElement = domForm.canclone; checkval = 'instcode'; } + if (selfcreateRegExp.test(caller)) { + optionsElement = domForm.elements[caller]; + checkval = 'other'; + dispval = 'inline' + } if (optionsElement.length) { var currval; for (var i=0; i 'original (CAPTCHA)', recaptcha => 'successor (ReCAPTCHA)', notused => 'unused', + ver => 'ReCAPTCHA version (1 or 2)', ); } @@ -12215,16 +17864,49 @@ sub devalidate_remote_domconfs { my %servers = &Apache::lonnet::internet_dom_servers($dom); my %thismachine; map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); - my @posscached = ('domainconfig','domdefaults'); + my @posscached = ('domainconfig','domdefaults','usersessions', + '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); }