--- loncom/interface/domainprefs.pm 2017/12/30 14:03:53 1.323 +++ loncom/interface/domainprefs.pm 2021/04/18 02:08:46 1.381 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.323 2017/12/30 14:03:53 raeburn Exp $ +# $Id: domainprefs.pm,v 1.381 2021/04/18 02:08:46 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -104,7 +104,7 @@ $datatable - HTML containing form eleme In the case of course requests, radio buttons are displayed for each institutional affiliate type (and also default, and _LC_adv) for each of the course types -(official, unofficial, community, textbook, and placement). +(official, unofficial, community, textbook, placement, and lti). In each case the radio buttons allow the selection of one of four values: 0, approval, validate, autolimit=N (where N is blank, or a positive integer). @@ -176,6 +176,7 @@ use Locale::Language; use DateTime::TimeZone; use DateTime::Locale; use Time::HiRes qw( sleep ); +use Net::CIDR; my $registered_cleanup; my $modified_urls; @@ -219,9 +220,10 @@ sub handler { 'serverstatuses','requestcourses','helpsettings', 'coursedefaults','usersessions','loadbalancing', 'requestauthor','selfenrollment','inststatus', - 'ltitools','ssl','trust','lti'],$dom); + 'ltitools','ssl','trust','lti','privacy','passwords', + 'proctoring','wafproxy'],$dom); my %encconfig = - &Apache::lonnet::get_dom('encconfig',['ltitools','lti'],$dom); + &Apache::lonnet::get_dom('encconfig',['ltitools','lti','proctoring'],$dom); if (ref($domconfig{'ltitools'}) eq 'HASH') { if (ref($encconfig{'ltitools'}) eq 'HASH') { foreach my $id (keys(%{$domconfig{'ltitools'}})) { @@ -246,12 +248,25 @@ sub handler { } } } - my @prefs_order = ('rolecolors','login','defaults','quotas','autoenroll', - 'autoupdate','autocreate','directorysrch','contacts', - 'usercreation','selfcreation','usermodification','scantron', - 'requestcourses','requestauthor','coursecategories', - 'serverstatuses','helpsettings','coursedefaults', - 'ltitools','selfenrollment','usersessions','ssl','trust','lti'); + if (ref($domconfig{'proctoring'}) eq 'HASH') { + if (ref($encconfig{'proctoring'}) eq 'HASH') { + foreach my $provider (keys(%{$domconfig{'proctoring'}})) { + if ((ref($domconfig{'proctoring'}{$provider}) eq 'HASH') && + (ref($encconfig{'proctoring'}{$provider}) eq 'HASH')) { + foreach my $item ('key','secret') { + $domconfig{'proctoring'}{$provider}{$item} = $encconfig{'proctoring'}{$provider}{$item}; + } + } + } + } + } + my @prefs_order = ('rolecolors','login','defaults','wafproxy','passwords','quotas', + 'autoenroll','autoupdate','autocreate','directorysrch', + 'contacts','privacy','usercreation','selfcreation', + 'usermodification','scantron','requestcourses','requestauthor', + 'coursecategories','serverstatuses','helpsettings','coursedefaults', + 'ltitools','proctoring','selfenrollment','usersessions','ssl', + 'trust','lti'); my %existing; if (ref($domconfig{'loadbalancing'}) eq 'HASH') { %existing = %{$domconfig{'loadbalancing'}}; @@ -291,13 +306,36 @@ sub handler { help => 'Domain_Configuration_LangTZAuth', header => [{col1 => 'Setting', col2 => 'Value'}, - {col1 => 'Internal Authentication', - col2 => 'Value'}, {col1 => 'Institutional user types', col2 => 'Name displayed'}], print => \&print_defaults, modify => \&modify_defaults, }, + 'wafproxy' => + { text => 'Web Application Firewall/Reverse Proxy', + help => 'Domain_Configuration_WAF_Proxy', + header => [{col1 => 'Domain(s)', + col2 => 'Servers and WAF/Reverse Proxy alias(es)', + }, + {col1 => 'Domain(s)', + col2 => 'WAF Configuration',}], + print => \&print_wafproxy, + modify => \&modify_wafproxy, + }, + 'passwords' => + { text => 'Passwords (Internal authentication)', + help => 'Domain_Configuration_Passwords', + header => [{col1 => 'Resetting Forgotten Password', + col2 => 'Settings'}, + {col1 => 'Encryption of Stored Passwords (Internal Auth)', + col2 => 'Settings'}, + {col1 => 'Rules for LON-CAPA Passwords', + col2 => 'Settings'}, + {col1 => 'Course Owner Changing Student Passwords', + col2 => 'Settings'}], + print => \&print_passwords, + modify => \&modify_passwords, + }, 'quotas' => { text => 'Blogs, personal web pages, webDAV/quotas, portfolios', help => 'Domain_Configuration_Quotas', @@ -352,6 +390,8 @@ sub handler { col2 => 'Value',}, {col1 => 'Recipient(s) for notifications', col2 => 'Value',}, + {col1 => 'Nightly status check e-mail', + col2 => 'Settings',}, {col1 => 'Ask helpdesk form settings', col2 => 'Value',},], print => \&print_contacts, @@ -392,11 +432,12 @@ sub handler { modify => \&modify_usermodification, }, 'scantron' => - { text => 'Bubblesheet format file', + { text => 'Bubblesheet format', help => 'Domain_Configuration_Scantron_Format', - header => [ {col1 => 'Item', - col2 => '', - }], + header => [ {col1 => 'Bubblesheet format file', + col2 => ''}, + {col1 => 'Bubblesheet data upload formats', + col2 => 'Settings'}], print => \&print_scantron, modify => \&modify_scantron, }, @@ -482,10 +523,16 @@ sub handler { modify => \&modify_selfenrollment, }, 'privacy' => - {text => 'User Privacy', + {text => 'Availability of User Information', help => 'Domain_Configuration_User_Privacy', - header => [{col1 => 'Setting', - col2 => 'Value',}], + header => [{col1 => 'Role assigned in different domain', + col2 => 'Approval options'}, + {col1 => 'Role assigned in different domain to user of type', + col2 => 'User information available in that domain'}, + {col1 => "Role assigned in user's domain", + col2 => 'Information viewable by privileged user'}, + {col1 => "Role assigned in user's domain", + col2 => 'Information viewable by unprivileged user'}], print => \&print_privacy, modify => \&modify_privacy, }, @@ -520,6 +567,14 @@ sub handler { print => \&print_ltitools, modify => \&modify_ltitools, }, + 'proctoring' => + {text => 'Remote Proctoring Integration', + help => 'Domain_Configuration_Proctoring', + header => [{col1 => 'Name', + col2 => 'Configuration'}], + print => \&print_proctoring, + modify => \&modify_proctoring, + }, 'ssl' => {text => 'LON-CAPA Network (SSL)', help => 'Domain_Configuration_Network_SSL', @@ -625,6 +680,9 @@ END if (grep(/^contacts$/,@actions)) { $js .= &contacts_javascript(); } + if (grep(/^scantron$/,@actions)) { + $js .= &scantron_javascript(); + } &Apache::lonconfigsettings::display_settings($r,$dom,$phase,$context,\@prefs_order,\%prefs,\%domconfig,$confname,$js); } else { # check if domconfig user exists for the domain. @@ -747,12 +805,20 @@ sub process_changes { $output = &modify_loadbalancing($dom,%domconfig); } elsif ($action eq 'ltitools') { $output = &modify_ltitools($r,$dom,$action,$lastactref,%domconfig); + } elsif ($action eq 'proctoring') { + $output = &modify_proctoring($r,$dom,$action,$lastactref,%domconfig); } elsif ($action eq 'ssl') { $output = &modify_ssl($dom,$lastactref,%domconfig); } elsif ($action eq 'trust') { $output = &modify_trust($dom,$lastactref,%domconfig); } elsif ($action eq 'lti') { $output = &modify_lti($r,$dom,$action,$lastactref,%domconfig); + } elsif ($action eq 'privacy') { + $output = &modify_privacy($dom,%domconfig); + } elsif ($action eq 'passwords') { + $output = &modify_passwords($r,$dom,$confname,$lastactref,%domconfig); + } elsif ($action eq 'wafproxy') { + $output = &modify_wafproxy($dom,$action,$lastactref,%domconfig); } return $output; } @@ -765,6 +831,8 @@ sub print_config_box { $output = &coursecategories_javascript($settings); } elsif ($action eq 'defaults') { $output = &defaults_javascript($settings); + } elsif ($action eq 'passwords') { + $output = &passwords_javascript(); } elsif ($action eq 'helpsettings') { my (%privs,%levelscurrent); my %full=(); @@ -781,6 +849,14 @@ sub print_config_box { $output = &Apache::lonuserutils::custom_roledefs_js($context,$crstype,$formname,\%full, \@templateroles); + } elsif ($action eq 'ltitools') { + $output .= <itools_javascript($settings); + } elsif ($action eq 'lti') { + $output .= <i_javascript($settings); + } elsif ($action eq 'proctoring') { + $output .= &proctoring_javascript($settings); + } elsif ($action eq 'wafproxy') { + $output .= &wafproxy_javascript($dom); } $output .= ' @@ -797,6 +873,7 @@ sub print_config_box { if ($numheaders > 1) { my $colspan = ''; my $rightcolspan = ''; + my $leftnobr = ''; if (($action eq 'rolecolors') || ($action eq 'defaults') || ($action eq 'directorysrch') || (($action eq 'login') && ($numheaders < 4))) { @@ -805,12 +882,15 @@ sub print_config_box { if ($action eq 'usersessions') { $rightcolspan = ' colspan="3"'; } + if ($action eq 'passwords') { + $leftnobr = ' LC_nobreak'; + } $output .= ' + + + + + '; } else { $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); } @@ -907,8 +1021,10 @@ sub print_config_box { $rowtotal ++; } elsif (($action eq 'usermodification') || ($action eq 'coursedefaults') || ($action eq 'defaults') || ($action eq 'directorysrch') || - ($action eq 'helpsettings')) { + ($action eq 'helpsettings') || ($action eq 'wafproxy')) { $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); + } elsif ($action eq 'scantron') { + $output .= $item->{'print'}->($r,'bottom',$dom,$confname,$settings,\$rowtotal); } elsif ($action eq 'ssl') { $output .= $item->{'print'}->('connto',$dom,$settings,\$rowtotal).'
- + '; $rowtotal ++; @@ -818,10 +898,14 @@ sub print_config_box { ($action eq 'usermodification') || ($action eq 'defaults') || ($action eq 'coursedefaults') || ($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'ssl') || ($action eq 'directorysrch') || ($action eq 'trust') || ($action eq 'helpsettings') || - ($action eq 'contacts')) { + ($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'wafproxy')) { $output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal); + } elsif ($action eq 'passwords') { + $output .= $item->{'print'}->('top',$dom,$confname,$settings,\$rowtotal); } elsif ($action eq 'coursecategories') { $output .= $item->{'print'}->('top',$dom,$item,$settings,\$rowtotal); + } elsif ($action eq 'scantron') { + $output .= $item->{'print'}->($r,'top',$dom,$confname,$settings,\$rowtotal); } elsif ($action eq 'login') { if ($numheaders == 4) { $colspan = ' colspan="2"'; @@ -849,12 +933,15 @@ sub print_config_box { if (($action eq 'autoupdate') || ($action eq 'usercreation') || ($action eq 'selfcreation') || ($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'coursecategories') || - ($action eq 'trust') || ($action eq 'contacts') || ($action eq 'defaults')) { + ($action eq 'trust') || ($action eq 'contacts') || + ($action eq 'privacy') || ($action eq 'passwords')) { if ($action eq 'coursecategories') { $output .= &print_coursecategories('middle',$dom,$item,$settings,\$rowtotal); $colspan = ' colspan="2"'; } elsif ($action eq 'trust') { $output .= $item->{'print'}->('shared',$dom,$settings,\$rowtotal); + } elsif ($action eq 'passwords') { + $output .= $item->{'print'}->('middle',$dom,$confname,$settings,\$rowtotal); } else { $output .= $item->{'print'}->('middle',$dom,$settings,\$rowtotal); } @@ -900,6 +987,33 @@ sub print_config_box { '."\n"; if ($action eq 'coursecategories') { $output .= &print_coursecategories('bottom',$dom,$item,$settings,\$rowtotal); + } elsif (($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'passwords')) { + if ($action eq 'passwords') { + $output .= $item->{'print'}->('lower',$dom,$confname,$settings,\$rowtotal); + } else { + $output .= $item->{'print'}->('lower',$dom,$settings,\$rowtotal); + } + $output .= ' + +
'.&mt($item->{'header'}->[0]->{'col1'}).''.&mt($item->{'header'}->[0]->{'col1'}).' '.&mt($item->{'header'}->[0]->{'col2'}).'
+
+ + + + '."\n"; + if ($action eq 'passwords') { + $output .= $item->{'print'}->('bottom',$dom,$confname,$settings,\$rowtotal); + } else { + $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); + } + $output .= ' +
'.&mt($item->{'header'}->[3]->{'col1'}).''.&mt($item->{'header'}->[3]->{'col2'}).'
+
@@ -1088,10 +1204,9 @@ sub print_config_box { $output .= &print_quotas($dom,$settings,\$rowtotal,$action); } elsif (($action eq 'autoenroll') || ($action eq 'autocreate') || ($action eq 'serverstatuses') || ($action eq 'loadbalancing') || - ($action eq 'ltitools') || ($action eq 'lti')) { + ($action eq 'ltitools') || ($action eq 'lti') || + ($action eq 'proctoring')) { $output .= $item->{'print'}->($dom,$settings,\$rowtotal); - } elsif ($action eq 'scantron') { - $output .= &print_scantronformat($r,$dom,$confname,$settings,\$rowtotal); } } $output .= ' @@ -1436,7 +1551,7 @@ sub print_login { } else { $datatable .= ''; } - $datatable .= ''; + $datatable .= ''; } $datatable .= ''; } @@ -1596,7 +1711,7 @@ sub display_color_options { my $datatable = ''. ''.$choices->{'font'}.''; if (!$is_custom->{'font'}) { - $datatable .= ''.&mt('Default in use:').' '.$defaults->{'font'}.''; + $datatable .= ''.&mt('Default in use:').' '.$defaults->{'font'}.''; } else { $datatable .= ' '; } @@ -1605,12 +1720,12 @@ sub display_color_options { $datatable .= ''. ' '. - ' '; + ' '; unless ($role eq 'login') { $datatable .= ''. ''.$choices->{'fontmenu'}.''; if (!$is_custom->{'fontmenu'}) { - $datatable .= ''.&mt('Default in use:').' '.$defaults->{'fontmenu'}.''; + $datatable .= ''.&mt('Default in use:').' '.$defaults->{'fontmenu'}.''; } else { $datatable .= ' '; } @@ -1620,7 +1735,7 @@ sub display_color_options { ' '. - ' '; + ' '; } my $switchserver = &check_switchserver($dom,$confname); foreach my $img (@{$images}) { @@ -1738,7 +1853,7 @@ sub display_color_options { my $bgs_def; foreach my $item (@{$bgs}) { if (!$is_custom->{$item}) { - $bgs_def .= ''.$choices->{$item}.'    
'.$defaults->{'bgs'}{$item}.''; + $bgs_def .= ''.$choices->{$item}.'    
'.$defaults->{'bgs'}{$item}.''; } } if ($bgs_def) { @@ -1766,7 +1881,7 @@ sub display_color_options { my $links_def; foreach my $item (@{$links}) { if (!$is_custom->{$item}) { - $links_def .= ''.$choices->{$item}.'
'.$defaults->{'links'}{$item}.''; + $links_def .= ''.$choices->{$item}.'
'.$defaults->{'links'}{$item}.''; } } if ($links_def) { @@ -1852,17 +1967,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 .= ''; } @@ -1893,7 +2006,7 @@ sub print_quotas { my $typecount = 0; my ($css_class,%titles); if ($context eq 'requestcourses') { - @usertools = ('official','unofficial','community','textbook','placement'); + @usertools = ('official','unofficial','community','textbook','placement','lti'); @options =('norequest','approval','validate','autolimit'); %validations = &Apache::lonnet::auto_courserequest_checks($dom); %titles = &courserequest_titles(); @@ -2357,7 +2470,7 @@ sub print_studentcode { my ($settings,$rowtotal) = @_; my $rownum = 0; my ($output,%current); - my @crstypes = ('official','unofficial','community','textbook','placement'); + my @crstypes = ('official','unofficial','community','textbook','placement','lti'); if (ref($settings) eq 'HASH') { if (ref($settings->{'uniquecode'}) eq 'HASH') { foreach my $type (@crstypes) { @@ -2484,7 +2597,7 @@ sub print_textbookcourses { $datatable .= ''; } $datatable .= ' '."\n". - ''.&mt('Add').''."\n". + ''.&mt('Add').''."\n". ''. ''.&mt('Subject:').' '."\n". (' 'x2). @@ -2501,13 +2614,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 ++; @@ -2712,6 +2825,19 @@ function toggleLTITools(form,setting,ite } } } + if (setting == 'user') { + divid = 'ltitools_'+setting+'_div_'+item; + var checkid = 'ltitools_'+setting+'_field_'+item; + if (document.getElementById(divid)) { + if (document.getElementById(checkid)) { + if (document.getElementById(checkid).checked) { + document.getElementById(divid).style.display = 'inline-block'; + } else { + document.getElementById(divid).style.display = 'none'; + } + } + } + } return; } // ]]> @@ -2720,6 +2846,217 @@ function toggleLTITools(form,setting,ite ENDSCRIPT } +sub wafproxy_javascript { + my ($dom) = @_; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + +sub proctoring_javascript { + my ($settings) = @_; + my (%ordered,$total,%jstext); + $total = 0; + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + $total = scalar(keys(%{$settings})); + } else { + %ordered = ( + 0 => 'proctorio', + 1 => 'examity', + ); + $total = 2; + } + my @jsarray = (); + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@jsarray,$ordered{$item}); + } + my $jstext = ' var proctors = Array('."'".join("','",@jsarray)."'".');'."\n"; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + + sub lti_javascript { my ($settings) = @_; my $togglejs = <i_toggle_js(); @@ -2793,32 +3130,76 @@ ENDSCRIPT } sub lti_toggle_js { + my %lcauthparmtext = &Apache::lonlocal::texthash ( + localauth => 'Local auth argument', + krb => 'Kerberos domain', + ); return <<"ENDSCRIPT"; + +ENDSCRIPT + +} + sub print_scantronformat { my ($r,$dom,$confname,$settings,$rowtotal) = @_; my $itemcount = 1; @@ -7548,8 +9777,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)) { @@ -7558,7 +9787,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}, @@ -7569,13 +9798,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).''; } @@ -7695,6 +9924,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; @@ -7737,7 +10089,7 @@ sub print_coursecategories { ''.$lt{$type}.' '; } - $datatable .= ''; + $datatable .= '
'; $itemcount ++; } $$rowtotal += $itemcount; @@ -7823,7 +10175,7 @@ sub print_coursecategories { $can_catcomm_dom.' value="dom" />'.$level{'dom'}.' '. '
'. - ''. + ''. ''.$title{'togglecatsplace'}.''. ''.&mt('Add subcategory:').''; + $text .= ''.&mt('Add subcategory:').''; } } } @@ -8374,7 +10788,7 @@ sub build_category_rows { sub modifiable_userdata_row { my ($context,$item,$settings,$numinrow,$rowcount,$usertypes,$fieldsref,$titlesref, - $rowid,$customcss,$rowstyle) = @_; + $rowid,$customcss,$rowstyle,$itemdesc) = @_; my ($role,$rolename,$statustype); $role = $item; if ($context eq 'cancreate') { @@ -8395,6 +10809,10 @@ sub modifiable_userdata_row { } else { $rolename = $role; } + } elsif ($context eq 'lti') { + $rolename = &mt('Institutional data used (if available)'); + } elsif ($context eq 'privacy') { + $rolename = $itemdesc; } else { if ($role eq 'cr') { $rolename = &mt('Custom role'); @@ -8437,30 +10855,39 @@ sub modifiable_userdata_row { ''; my $rem; my %checks; + my %current; if (ref($settings) eq 'HASH') { - if (ref($settings->{$context}) eq 'HASH') { + my $hashref; + if ($context eq 'lti') { + if (ref($settings) eq 'HASH') { + $hashref = $settings->{'instdata'}; + } + } elsif ($context eq 'privacy') { + my ($key,$inner) = split(/_/,$role); + if (ref($settings) eq 'HASH') { + if (ref($settings->{$key}) eq 'HASH') { + $hashref = $settings->{$key}->{$inner}; + } + } + } elsif (ref($settings->{$context}) eq 'HASH') { if (ref($settings->{$context}->{$role}) eq 'HASH') { - my $hashref = $settings->{$context}->{$role}; - if ($role eq 'emailusername') { - if ($statustype) { - if (ref($settings->{$context}->{$role}->{$statustype}) eq 'HASH') { - $hashref = $settings->{$context}->{$role}->{$statustype}; - if (ref($hashref) eq 'HASH') { - foreach my $field (@fields) { - if ($hashref->{$field}) { - $checks{$field} = $hashref->{$field}; - } - } - } - } + $hashref = $settings->{'lti_instdata'}; + } + if ($role eq 'emailusername') { + if ($statustype) { + if (ref($settings->{$context}->{$role}->{$statustype}) eq 'HASH') { + $hashref = $settings->{$context}->{$role}->{$statustype}; } - } else { - if (ref($hashref) eq 'HASH') { - foreach my $field (@fields) { - if ($hashref->{$field}) { - $checks{$field} = ' checked="checked" '; - } - } + } + } + } + if (ref($hashref) eq 'HASH') { + foreach my $field (@fields) { + if ($hashref->{$field}) { + if ($role eq 'emailusername') { + $checks{$field} = $hashref->{$field}; + } else { + $checks{$field} = ' checked="checked" '; } } } @@ -8479,8 +10906,26 @@ sub modifiable_userdata_row { my $check = ' '; unless ($role eq 'emailusername') { if (exists($checks{$fields[$i]})) { - $check = $checks{$fields[$i]} - } else { + $check = $checks{$fields[$i]}; + } elsif ($context eq 'privacy') { + if ($role =~ /^priv_(domain|course)$/) { + if (ref($settings) ne 'HASH') { + $check = ' checked="checked" '; + } + } elsif ($role =~ /^priv_(author|community)$/) { + if (ref($settings) ne 'HASH') { + unless ($fields[$i] eq 'id') { + $check = ' checked="checked" '; + } + } + } elsif ($role =~ /^(unpriv|othdom)_/) { + if (ref($settings) ne 'HASH') { + if (($fields[$i] eq 'lastname') || ($fields[$i] eq 'firstname')) { + $check = ' checked="checked" '; + } + } + } + } elsif ($context ne 'lti') { if ($role eq 'st') { if (ref($settings) ne 'HASH') { $check = ' checked="checked" '; @@ -8490,6 +10935,7 @@ sub modifiable_userdata_row { } $output .= ''; } if ($colsleft > 1) { @@ -9591,7 +12042,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); @@ -9916,7 +12367,7 @@ sub modify_quotas { $context = $action; } if ($context eq 'requestcourses') { - @usertools = ('official','unofficial','community','textbook','placement'); + @usertools = ('official','unofficial','community','textbook','placement','lti'); @options =('norequest','approval','validate','autolimit'); %validations = &Apache::lonnet::auto_courserequest_checks($dom); %titles = &courserequest_titles(); @@ -9965,7 +12416,7 @@ sub modify_quotas { my @approvalnotify = &Apache::loncommon::get_env_multiple('form.'.$context.'notifyapproval'); @approvalnotify = sort(@approvalnotify); $confhash{'notify'}{'approval'} = join(',',@approvalnotify); - my @crstypes = ('official','unofficial','community','textbook','placement'); + my @crstypes = ('official','unofficial','community','textbook','placement','lti'); my @hasuniquecode = &Apache::loncommon::get_env_multiple('form.uniquecode'); foreach my $type (@hasuniquecode) { if (grep(/^\Q$type\E$/,@crstypes)) { @@ -10750,6 +13201,13 @@ sub modify_ltitools { } } } + if (ref($confhash{$newid}{'fields'}) eq 'HASH') { + if ($confhash{$newid}{'fields'}{'user'}) { + if ($env{'form.ltitools_userincdom_add'}) { + $confhash{$newid}{'incdom'} = 1; + } + } + } my @courseconfig = &Apache::loncommon::get_env_multiple('form.ltitools_courseconfig'); foreach my $item (@courseconfig) { $confhash{$newid}{'crsconf'}{$item} = 1; @@ -10880,7 +13338,7 @@ sub modify_ltitools { if ($env{'form.ltitools_'.$extra.'_'.$i}) { $confhash{$itemid}{$extra} = 1; if ($env{'form.ltitools_'.$extra.'valid_'.$i} ne '') { - my $lifetime = $env{'form.ltitools_'.$extra.'valid_add'}; + my $lifetime = $env{'form.ltitools_'.$extra.'valid_'.$i}; $lifetime =~ s/^\s+|\s+$//g; if ($lifetime =~ /^\d+\.?\d*$/) { $confhash{$itemid}{$extra.'valid'} = $lifetime; @@ -10939,6 +13397,16 @@ sub modify_ltitools { } } } + if (ref($confhash{$itemid}{'fields'}) eq 'HASH') { + if ($confhash{$itemid}{'fields'}{'user'}) { + if ($env{'form.ltitools_userincdom_'.$i}) { + $confhash{$itemid}{'incdom'} = 1; + } + if ($domconfig{$action}{$itemid}{'incdom'} ne $confhash{$itemid}{'incdom'}) { + $changes{$itemid} = 1; + } + } + } $allpos[$newpos] = $itemid; } if ($imgdeletions{$itemid}) { @@ -11097,7 +13565,7 @@ sub modify_ltitools { } } if (!$numconfig) { - $resulttext .= &mt('None'); + $resulttext .= ' '.&mt('None'); } $resulttext .= ''; foreach my $item ('passback','roster') { @@ -11149,6 +13617,13 @@ sub modify_ltitools { } if ($fieldlist) { $fieldlist =~ s/,$//; + if ($confhash{$itemid}{'fields'}{'user'}) { + if ($confhash{$itemid}{'incdom'}) { + $fieldlist .= ' ('.&mt('username:domain').')'; + } else { + $fieldlist .= ' ('.&mt('username').')'; + } + } $resulttext .= '
  • '.&mt('Data sent').':'.$fieldlist.'
  • '; } } @@ -11269,6 +13744,536 @@ sub get_ltitools_id { return ($id,$error); } +sub modify_proctoring { + my ($r,$dom,$action,$lastactref,%domconfig) = @_; + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my (@allpos,%changes,%confhash,%encconfhash,$errors,$resulttext,%imgdeletions); + my $confname = $dom.'-domainconfig'; + my $servadm = $r->dir_config('lonAdmEMail'); + my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm); + my %providernames = &proctoring_providernames(); + my $maxnum = scalar(keys(%providernames)); + + my (%requserfields,%optuserfields,%defaults,%extended,%crsconf,@courseroles,@ltiroles); + my ($requref,$opturef,$defref,$extref,$crsref,$rolesref,$ltiref) = &proctoring_data(); + if (ref($requref) eq 'HASH') { + %requserfields = %{$requref}; + } + if (ref($opturef) eq 'HASH') { + %optuserfields = %{$opturef}; + } + if (ref($defref) eq 'HASH') { + %defaults = %{$defref}; + } + if (ref($extref) eq 'HASH') { + %extended = %{$extref}; + } + if (ref($crsref) eq 'HASH') { + %crsconf = %{$crsref}; + } + if (ref($rolesref) eq 'ARRAY') { + @courseroles = @{$rolesref}; + } + if (ref($ltiref) eq 'ARRAY') { + @ltiroles = @{$ltiref}; + } + + if (ref($domconfig{$action}) eq 'HASH') { + my @todeleteimages = &Apache::loncommon::get_env_multiple('form.proctoring_image_del'); + if (@todeleteimages) { + map { $imgdeletions{$_} = 1; } @todeleteimages; + } + } + my %customadds; + my @newcustom = &Apache::loncommon::get_env_multiple('form.proctoring_customadd'); + if (@newcustom) { + map { $customadds{$_} = 1; } @newcustom; + } + foreach my $provider (sort(keys(%providernames))) { + $confhash{$provider} = {}; + my $pos = $env{'form.proctoring_pos_'.$provider}; + $pos =~ s/\D+//g; + $allpos[$pos] = $provider; + my (%current,%currentenc); + my $showroles = 0; + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$provider}) eq 'HASH') { + %current = %{$domconfig{$action}{$provider}}; + foreach my $item ('key','secret') { + $currentenc{$item} = $current{$item}; + delete($current{$item}); + } + } + } + if ($env{'form.proctoring_available_'.$provider}) { + $confhash{$provider}{'available'} = 1; + unless ($current{'available'}) { + $changes{$provider} = 1; + } + } else { + %{$confhash{$provider}} = %current; + %{$encconfhash{$provider}} = %currentenc; + $confhash{$provider}{'available'} = 0; + if ($current{'available'}) { + $changes{$provider} = 1; + } + } + if ($confhash{$provider}{'available'}) { + foreach my $field ('lifetime','version','sigmethod','url','key','secret') { + my $possval = $env{'form.proctoring_'.$provider.'_'.$field}; + if ($field eq 'lifetime') { + if ($possval =~ /^\d+$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'version') { + if ($possval =~ /^\d+\.\d+$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'sigmethod') { + if ($possval =~ /^\QHMAC-SHA\E(1|256)$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'url') { + $confhash{$provider}{$field} = $possval; + } elsif (($field eq 'key') || ($field eq 'secret')) { + $encconfhash{$provider}{$field} = $possval; + unless ($currentenc{$field} eq $possval) { + $changes{$provider} = 1; + } + } + unless (($field eq 'key') || ($field eq 'secret')) { + unless ($current{$field} eq $confhash{$provider}{$field}) { + $changes{$provider} = 1; + } + } + } + if ($imgdeletions{$provider}) { + $changes{$provider} = 1; + } elsif ($env{'form.proctoring_image_'.$provider.'.filename'} ne '') { + my ($imageurl,$error) = + &process_proctoring_image($r,$dom,$confname,'proctoring_image_'.$provider,$provider, + $configuserok,$switchserver,$author_ok); + if ($imageurl) { + $confhash{$provider}{'image'} = $imageurl; + $changes{$provider} = 1; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } elsif (exists($current{'image'})) { + $confhash{$provider}{'image'} = $current{'image'}; + } + if (ref($requserfields{$provider}) eq 'ARRAY') { + if (@{$requserfields{$provider}} > 0) { + if (grep(/^user$/,@{$requserfields{$provider}})) { + if ($env{'form.proctoring_userincdom_'.$provider}) { + $confhash{$provider}{'incdom'} = 1; + } + unless ($current{'incdom'} eq $confhash{$provider}{'incdom'}) { + $changes{$provider} = 1; + } + } + if (grep(/^roles$/,@{$requserfields{$provider}})) { + $showroles = 1; + } + } + } + $confhash{$provider}{'fields'} = []; + if (ref($optuserfields{$provider}) eq 'ARRAY') { + if (@{$optuserfields{$provider}} > 0) { + my @optfields = &Apache::loncommon::get_env_multiple('form.proctoring_optional_'.$provider); + foreach my $field (@{$optuserfields{$provider}}) { + if (grep(/^\Q$field\E$/,@optfields)) { + push(@{$confhash{$provider}{'fields'}},$field); + } + } + } + if (ref($current{'fields'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'fields'}}); + my @old = sort(@{$current{'fields'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (@{$confhash{$provider}{'fields'}}) { + $changes{$provider} = 1; + } + } + if (ref($defaults{$provider}) eq 'ARRAY') { + if (@{$defaults{$provider}} > 0) { + my %options; + if (ref($extended{$provider}) eq 'HASH') { + %options = %{$extended{$provider}}; + } + my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_defaults_'.$provider); + foreach my $field (@{$defaults{$provider}}) { + if ((exists($options{$field})) && (ref($options{$field}) eq 'ARRAY')) { + my $poss = $env{'form.proctoring_defaults_'.$field.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@{$options{$field}})) { + push(@{$confhash{$provider}{'defaults'}},$poss); + } + } elsif ((exists($options{$field})) && (ref($options{$field}) eq 'HASH')) { + foreach my $inner (keys(%{$options{$field}})) { + if (ref($options{$field}{$inner}) eq 'ARRAY') { + my $poss = $env{'form.proctoring_'.$inner.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@{$options{$field}{$inner}})) { + $confhash{$provider}{'defaults'}{$inner} = $poss; + } + } else { + $confhash{$provider}{'defaults'}{$inner} = $env{'form.proctoring_'.$inner.'_'.$provider}; + } + } + } else { + if (grep(/^\Q$field\E$/,@checked)) { + push(@{$confhash{$provider}{'defaults'}},$field); + } + } + } + if (ref($confhash{$provider}{'defaults'}) eq 'ARRAY') { + if (ref($current{'defaults'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'defaults'}}); + my @old = sort(@{$current{'defaults'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (ref($current{'defaults'}) eq 'ARRAY') { + if (@{$current{'defaults'}}) { + $changes{$provider} = 1; + } + } + } elsif (ref($confhash{$provider}{'defaults'}) eq 'HASH') { + if (ref($current{'defaults'}) eq 'HASH') { + unless ($changes{$provider}) { + foreach my $key (keys(%{$confhash{$provider}{'defaults'}})) { + unless ($confhash{$provider}{'defaults'}{$key} eq $current{'defaults'}{$key}) { + $changes{$provider} = 1; + last; + } + } + } + unless ($changes{$provider}) { + foreach my $key (keys(%{$current{'defaults'}})) { + unless ($current{'defaults'}{$key} eq $confhash{$provider}{'defaults'}{$key}) { + $changes{$provider} = 1; + last; + } + } + } + } elsif (keys(%{$confhash{$provider}{'defaults'}})) { + $changes{$provider} = 1; + } + } + } + } + if (ref($crsconf{$provider}) eq 'ARRAY') { + if (@{$crsconf{$provider}} > 0) { + $confhash{$provider}{'crsconf'} = []; + my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_crsconf_'.$provider); + foreach my $crsfield (@{$crsconf{$provider}}) { + if (grep(/^\Q$crsfield\E$/,@checked)) { + push(@{$confhash{$provider}{'crsconf'}},$crsfield); + } + } + if (ref($current{'crsconf'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'crsconf'}}); + my @old = sort(@{$current{'crsconf'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (@{$confhash{$provider}{'crsconf'}}) { + $changes{$provider} = 1; + } + } + } + if ($showroles) { + $confhash{$provider}{'roles'} = {}; + foreach my $role (@courseroles) { + my $poss = $env{'form.proctoring_roles_'.$role.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@ltiroles)) { + $confhash{$provider}{'roles'}{$role} = $poss; + } + } + unless ($changes{$provider}) { + if (ref($current{'roles'}) eq 'HASH') { + foreach my $role (keys(%{$current{'roles'}})) { + unless ($current{'roles'}{$role} eq $confhash{$provider}{'roles'}{$role}) { + $changes{$provider} = 1; + last + } + } + unless ($changes{$provider}) { + foreach my $role (keys(%{$confhash{$provider}{'roles'}})) { + unless ($confhash{$provider}{'roles'}{$role} eq $current{'roles'}{$role}) { + $changes{$provider} = 1; + last; + } + } + } + } elsif (keys(%{$confhash{$provider}{'roles'}})) { + $changes{$provider} = 1; + } + } + } + if (ref($current{'custom'}) eq 'HASH') { + my @customdels = &Apache::loncommon::get_env_multiple('form.proctoring_customdel_'.$provider); + foreach my $key (keys(%{$current{'custom'}})) { + if (grep(/^\Q$key\E$/,@customdels)) { + $changes{$provider} = 1; + } else { + $confhash{$provider}{'custom'}{$key} = $env{'form.proctoring_customval_'.$key.'_'.$provider}; + if ($confhash{$provider}{'custom'}{$key} ne $current{'custom'}{$key}) { + $changes{$provider} = 1; + } + } + } + } + if ($customadds{$provider}) { + my $name = $env{'form.proctoring_custom_name_'.$provider}; + $name =~ s/(`)/'/g; + $name =~ s/^\s+//; + $name =~ s/\s+$//; + my $value = $env{'form.proctoring_custom_value_'.$provider}; + $value =~ s/(`)/'/g; + $value =~ s/^\s+//; + $value =~ s/\s+$//; + if ($name ne '') { + $confhash{$provider}{'custom'}{$name} = $value; + $changes{$provider} = 1; + } + } + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $provider (@allpos) { + if ($provider ne '') { + $confhash{$provider}{'order'} = $idx; + unless ($changes{$provider}) { + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$provider}) eq 'HASH') { + if ($domconfig{$action}{$provider}{'order'} ne $idx) { + $changes{$provider} = 1; + } + } + } + } + $idx ++; + } + } + } + my %proc_hash = ( + $action => { %confhash } + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%proc_hash, + $dom); + if ($putresult eq 'ok') { + my %proc_enchash = ( + $action => { %encconfhash } + ); + &Apache::lonnet::put_dom('encconfig',\%proc_enchash,$dom); + if (keys(%changes) > 0) { + my $cachetime = 24*60*60; + my %procall = %confhash; + foreach my $provider (keys(%procall)) { + if (ref($encconfhash{$provider}) eq 'HASH') { + foreach my $key ('key','secret') { + $procall{$provider}{$key} = $encconfhash{$provider}{$key}; + } + } + } + &Apache::lonnet::do_cache_new('proctoring',$dom,\%procall,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'proctoring'} = 1; + } + $resulttext = &mt('Configuration for Provider(s) with changes:').''; + } else { + $resulttext = &mt('No changes made.'); + } + } else { + $errors .= '
  • '.&mt('Failed to save changes').'
  • '; + } + if ($errors) { + $resulttext .= &mt('The following errors occurred: ').''; + } + return $resulttext; +} + +sub process_proctoring_image { + my ($r,$dom,$confname,$caller,$provider,$configuserok,$switchserver,$author_ok) = @_; + my $filename = $env{'form.'.$caller.'.filename'}; + my ($error,$url); + my ($width,$height) = (21,21); + if ($configuserok eq 'ok') { + if ($switchserver) { + $error = &mt('Upload of Remote Proctoring Provider icon is not permitted to this server: [_1]', + $switchserver); + } elsif ($author_ok eq 'ok') { + my ($result,$imageurl,$madethumb) = + &publishlogo($r,'upload',$caller,$dom,$confname, + "proctoring/$provider/icon",$width,$height); + if ($result eq 'ok') { + if ($madethumb) { + my ($path,$imagefile) = ($imageurl =~ m{^(.+)/([^/]+)$}); + my $imagethumb = "$path/tn-".$imagefile; + $url = $imagethumb; + } else { + $url = $imageurl; + } + } else { + $error = &mt("Upload of [_1] failed because an error occurred publishing the file in RES space. Error was: [_2].",$filename,$result); + } + } else { + $error = &mt("Upload of [_1] failed because an author role could not be assigned to a Domain Configuration user ([_2]) in domain: [_3]. Error was: [_4].",$filename,$confname,$dom,$author_ok); + } + } else { + $error = &mt("Upload of [_1] failed because a Domain Configuration user ([_2]) could not be created in domain: [_3]. Error was: [_4].",$filename,$confname,$dom,$configuserok); + } + return ($url,$error); +} + sub modify_lti { my ($r,$dom,$action,$lastactref,%domconfig) = @_; my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); @@ -11285,11 +14290,14 @@ sub modify_lti { textbook => 'Textbook', placement => 'Placement Test', ); + my %fieldtitles = &Apache::loncommon::personal_data_fieldtitles(); my %lt = <i_names(); map { $posslti{$_} = 1; } @ltiroles; map { $posslticrs{$_} = 1; } @lticourseroles; map { $posscrstype{$_} = 1; } @coursetypes; - + + my %menutitles = <imenu_titles(); + my (@items,%deletions,%itemids); if ($env{'form.lti_add'}) { my $consumer = $env{'form.lti_consumer_add'}; @@ -11331,7 +14339,7 @@ sub modify_lti { if ($position ne '') { $allpos[$position] = $itemid; } - foreach my $item ('consumer','key','secret','lifetime') { + foreach my $item ('consumer','key','secret','lifetime','requser') { my $formitem = 'form.lti_'.$item.'_'.$idx; $env{$formitem} =~ s/(`)/'/g; if ($item eq 'lifetime') { @@ -11353,127 +14361,186 @@ sub modify_lti { if ($env{'form.lti_version_'.$idx} eq 'LTI-1p0') { $confhash{$itemid}{'version'} = $env{'form.lti_version_'.$idx}; } - if ($env{'form.lti_mapuser_'.$idx} eq 'sourcedid') { - $confhash{$itemid}{'mapuser'} = 'lis_person_sourcedid'; - } elsif ($env{'form.lti_mapuser_'.$idx} eq 'email') { - $confhash{$itemid}{'mapuser'} = 'lis_person_contact_email_primary'; - } elsif ($env{'form.lti_mapuser_'.$idx} eq 'other') { - my $mapuser = $env{'form.lti_customuser_'.$idx}; - $mapuser =~ s/(`)/'/g; - $mapuser =~ s/^\s+|\s+$//g; - $confhash{$itemid}{'mapuser'} = $mapuser; - } - foreach my $ltirole (@lticourseroles) { - my $possrole = $env{'form.lti_maprole_'.$ltirole.'_'.$idx}; - if (grep(/^\Q$possrole\E$/,@courseroles)) { - $confhash{$itemid}{'maproles'}{$ltirole} = $possrole; - } - } - my @possmakeuser = &Apache::loncommon::get_env_multiple('form.lti_makeuser_'.$idx); - my @makeuser; - foreach my $ltirole (sort(@possmakeuser)) { - if ($posslti{$ltirole}) { - push(@makeuser,$ltirole); - } - } - $confhash{$itemid}{'makeuser'} = \@makeuser; - if (($env{'form.lti_mapcrs_'.$idx} eq 'course_offering_sourcedid') || - ($env{'form.lti_mapcrs_'.$idx} eq 'context_id')) { - $confhash{$itemid}{'mapcrs'} = $env{'form.lti_mapcrs_'.$idx}; - } elsif ($env{'form.lti_mapcrs_'.$idx} eq 'other') { - my $mapcrs = $env{'form.lti_mapcrsfield_'.$idx}; - $mapcrs =~ s/(`)/'/g; - $mapcrs =~ s/^\s+|\s+$//g; - $confhash{$itemid}{'mapcrs'} = $mapcrs; - } - my @posstypes = &Apache::loncommon::get_env_multiple('form.lti_mapcrstype_'.$idx); - my @crstypes; - foreach my $type (sort(@posstypes)) { - if ($posscrstype{$type}) { - push(@crstypes,$type); + if ($confhash{$itemid}{'requser'}) { + if ($env{'form.lti_mapuser_'.$idx} eq 'sourcedid') { + $confhash{$itemid}{'mapuser'} = 'lis_person_sourcedid'; + } elsif ($env{'form.lti_mapuser_'.$idx} eq 'email') { + $confhash{$itemid}{'mapuser'} = 'lis_person_contact_email_primary'; + } elsif ($env{'form.lti_mapuser_'.$idx} eq 'other') { + my $mapuser = $env{'form.lti_customuser_'.$idx}; + $mapuser =~ s/(`)/'/g; + $mapuser =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'mapuser'} = $mapuser; + } + foreach my $ltirole (@lticourseroles) { + my $possrole = $env{'form.lti_maprole_'.$ltirole.'_'.$idx}; + if (grep(/^\Q$possrole\E$/,@courseroles)) { + $confhash{$itemid}{'maproles'}{$ltirole} = $possrole; + } + } + my @possmakeuser = &Apache::loncommon::get_env_multiple('form.lti_makeuser_'.$idx); + my @makeuser; + foreach my $ltirole (sort(@possmakeuser)) { + if ($posslti{$ltirole}) { + push(@makeuser,$ltirole); + } + } + $confhash{$itemid}{'makeuser'} = \@makeuser; + if (@makeuser) { + my $lcauth = $env{'form.lti_lcauth_'.$idx}; + if ($lcauth =~ /^(internal|krb4|krb5|localauth)$/) { + $confhash{$itemid}{'lcauth'} = $lcauth; + if ($lcauth ne 'internal') { + my $lcauthparm = $env{'form.lti_lcauthparm_'.$idx}; + $lcauthparm =~ s/^(\s+|\s+)$//g; + $lcauthparm =~ s/`//g; + if ($lcauthparm ne '') { + $confhash{$itemid}{'lcauthparm'} = $lcauthparm; + } + } + } else { + $confhash{$itemid}{'lcauth'} = 'lti'; + } } - } - $confhash{$itemid}{'mapcrstype'} = \@crstypes; - if ($env{'form.lti_makecrs_'.$idx}) { - $confhash{$itemid}{'makecrs'} = 1; - } - my @possenroll = &Apache::loncommon::get_env_multiple('form.lti_selfenroll_'.$idx); - my @selfenroll; - foreach my $type (sort(@possenroll)) { - if ($posslticrs{$type}) { - push(@selfenroll,$type); + my @possinstdata = &Apache::loncommon::get_env_multiple('form.lti_instdata_'.$idx); + if (@possinstdata) { + foreach my $field (@possinstdata) { + if (exists($fieldtitles{$field})) { + push(@{$confhash{$itemid}{'instdata'}}); + } + } } - } - $confhash{$itemid}{'selfenroll'} = \@selfenroll; - if ($env{'form.lti_crssec_'.$idx}) { - if ($env{'form.lti_crssecsrc_'.$idx} eq 'course_section_sourcedid') { - $confhash{$itemid}{'section'} = $env{'form.lti_crssecsrc_'.$idx}; - } elsif ($env{'form.lti_crssecsrc_'.$idx} eq 'other') { - my $section = $env{'form.lti_customsection_'.$idx}; - $section =~ s/(`)/'/g; - $section =~ s/^\s+|\s+$//g; - if ($section ne '') { - $confhash{$itemid}{'section'} = $section; + if (($env{'form.lti_mapcrs_'.$idx} eq 'course_offering_sourcedid') || + ($env{'form.lti_mapcrs_'.$idx} eq 'context_id')) { + $confhash{$itemid}{'mapcrs'} = $env{'form.lti_mapcrs_'.$idx}; + } elsif ($env{'form.lti_mapcrs_'.$idx} eq 'other') { + my $mapcrs = $env{'form.lti_mapcrsfield_'.$idx}; + $mapcrs =~ s/(`)/'/g; + $mapcrs =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'mapcrs'} = $mapcrs; + } + my @posstypes = &Apache::loncommon::get_env_multiple('form.lti_mapcrstype_'.$idx); + my @crstypes; + foreach my $type (sort(@posstypes)) { + if ($posscrstype{$type}) { + push(@crstypes,$type); } } - } - foreach my $field ('passback','roster') { - if ($env{'form.lti_'.$field.'_'.$idx}) { - $confhash{$itemid}{$field} = 1; + $confhash{$itemid}{'mapcrstype'} = \@crstypes; + if ($env{'form.lti_makecrs_'.$idx}) { + $confhash{$itemid}{'makecrs'} = 1; } - } - unless (($idx eq 'add') || ($changes{$itemid})) { - foreach my $field ('mapuser','mapcrs','section','passback','roster') { - if ($domconfig{$action}{$itemid}{$field} ne $confhash{$itemid}{$field}) { - $changes{$itemid} = 1; + my @possenroll = &Apache::loncommon::get_env_multiple('form.lti_selfenroll_'.$idx); + my @selfenroll; + foreach my $type (sort(@possenroll)) { + if ($posslticrs{$type}) { + push(@selfenroll,$type); } } - foreach my $field ('makeuser','mapcrstype','selfenroll') { - unless ($changes{$itemid}) { - if (ref($domconfig{$action}{$itemid}{$field}) eq 'ARRAY') { - if (ref($confhash{$itemid}{$field}) eq 'ARRAY') { - my @diffs = &Apache::loncommon::compare_arrays($domconfig{$action}{$itemid}{$field}, - $confhash{$itemid}{$field}); - if (@diffs) { - $changes{$itemid} = 1; - } - } elsif (@{$domconfig{$action}{$itemid}{$field}} > 0) { - $changes{$itemid} = 1; + $confhash{$itemid}{'selfenroll'} = \@selfenroll; + if ($env{'form.lti_crssec_'.$idx}) { + if ($env{'form.lti_crssecsrc_'.$idx} eq 'course_section_sourcedid') { + $confhash{$itemid}{'section'} = $env{'form.lti_crssecsrc_'.$idx}; + } elsif ($env{'form.lti_crssecsrc_'.$idx} eq 'other') { + my $section = $env{'form.lti_customsection_'.$idx}; + $section =~ s/(`)/'/g; + $section =~ s/^\s+|\s+$//g; + if ($section ne '') { + $confhash{$itemid}{'section'} = $section; + } + } + } + if ($env{'form.lti_callback_'.$idx}) { + if ($env{'form.lti_callbackparam_'.$idx}) { + my $callback = $env{'form.lti_callbackparam_'.$idx}; + $callback =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'callback'} = $callback; + } + } + foreach my $field ('passback','roster','topmenu','inlinemenu') { + if ($env{'form.lti_'.$field.'_'.$idx}) { + $confhash{$itemid}{$field} = 1; + } + } + if ($env{'form.lti_passback_'.$idx}) { + if ($env{'form.lti_passbackformat_'.$idx} eq '1.0') { + $confhash{$itemid}{'passbackformat'} = '1.0'; + } else { + $confhash{$itemid}{'passbackformat'} = '1.1'; + } + } + if ($env{'form.lti_topmenu_'.$idx} || $env{'form.lti_inlinemenu_'.$idx}) { + $confhash{$itemid}{lcmenu} = []; + my @possmenu = &Apache::loncommon::get_env_multiple('form.lti_menuitem_'.$idx); + foreach my $field (@possmenu) { + if (exists($menutitles{$field})) { + if ($field eq 'grades') { + next unless ($env{'form.lti_inlinemenu_'.$idx}); } - } elsif (ref($confhash{$itemid}{$field}) eq 'ARRAY') { - if (@{$confhash{$itemid}{$field}} > 0) { + push(@{$confhash{$itemid}{lcmenu}},$field); + } + } + } + unless (($idx eq 'add') || ($changes{$itemid})) { + foreach my $field ('mapuser','mapcrs','makecrs','section','passback','roster','lcauth','lcauthparm','topmenu','inlinemenu','callback') { + if ($domconfig{$action}{$itemid}{$field} ne $confhash{$itemid}{$field}) { + $changes{$itemid} = 1; + } + } + unless ($changes{$itemid}) { + if ($domconfig{$action}{$itemid}{'passback'} eq $confhash{$itemid}{'passback'}) { + if ($domconfig{$action}{$itemid}{'passbackformat'} ne $confhash{$itemid}{'passbackformat'}) { $changes{$itemid} = 1; } - } + } } - } - unless ($changes{$itemid}) { - if (ref($domconfig{$action}{$itemid}{'maproles'}) eq 'HASH') { - if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { - foreach my $ltirole (keys(%{$domconfig{$action}{$itemid}{'maproles'}})) { - if ($domconfig{$action}{$itemid}{'maproles'}{$ltirole} ne - $confhash{$itemid}{'maproles'}{$ltirole}) { + foreach my $field ('makeuser','mapcrstype','selfenroll','instdata','lcmenu') { + unless ($changes{$itemid}) { + if (ref($domconfig{$action}{$itemid}{$field}) eq 'ARRAY') { + if (ref($confhash{$itemid}{$field}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($domconfig{$action}{$itemid}{$field}, + $confhash{$itemid}{$field}); + if (@diffs) { + $changes{$itemid} = 1; + } + } elsif (@{$domconfig{$action}{$itemid}{$field}} > 0) { + $changes{$itemid} = 1; + } + } elsif (ref($confhash{$itemid}{$field}) eq 'ARRAY') { + if (@{$confhash{$itemid}{$field}} > 0) { $changes{$itemid} = 1; - last; } } - unless ($changes{$itemid}) { - foreach my $ltirole (keys(%{$confhash{$itemid}{'maproles'}})) { - if ($confhash{$itemid}{'maproles'}{$ltirole} ne - $domconfig{$action}{$itemid}{'maproles'}{$ltirole}) { + } + } + unless ($changes{$itemid}) { + if (ref($domconfig{$action}{$itemid}{'maproles'}) eq 'HASH') { + if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { + foreach my $ltirole (keys(%{$domconfig{$action}{$itemid}{'maproles'}})) { + if ($domconfig{$action}{$itemid}{'maproles'}{$ltirole} ne + $confhash{$itemid}{'maproles'}{$ltirole}) { $changes{$itemid} = 1; last; } } - } - } elsif (keys(%{$domconfig{$action}{$itemid}{'maproles'}}) > 0) { - $changes{$itemid} = 1; - } - } elsif (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { - unless ($changes{$itemid}) { - if (keys(%{$confhash{$itemid}{'maproles'}}) > 0) { + unless ($changes{$itemid}) { + foreach my $ltirole (keys(%{$confhash{$itemid}{'maproles'}})) { + if ($confhash{$itemid}{'maproles'}{$ltirole} ne + $domconfig{$action}{$itemid}{'maproles'}{$ltirole}) { + $changes{$itemid} = 1; + last; + } + } + } + } elsif (keys(%{$domconfig{$action}{$itemid}{'maproles'}}) > 0) { $changes{$itemid} = 1; } + } elsif (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { + unless ($changes{$itemid}) { + if (keys(%{$confhash{$itemid}{'maproles'}}) > 0) { + $changes{$itemid} = 1; + } + } } } } @@ -11546,84 +14613,130 @@ sub modify_lti { my $num = length($encconfig{$itemid}{'secret'}); $resulttext .= ('*'x$num).''; } - if ($confhash{$itemid}{'mapuser'}) { - my $shownmapuser; - if ($confhash{$itemid}{'mapuser'} eq 'lis_person_sourcedid') { - $shownmapuser = $lt{'sourcedid'}.' (lis_person_sourcedid)'; - } elsif ($confhash{$itemid}{'mapuser'} eq 'lis_person_contact_email_primary') { - $shownmapuser = $lt{'email'}.' (lis_person_contact_email_primary)'; - } else { - $shownmapuser = &mt('Other').' ('.$confhash{$itemid}{'mapuser'}.')'; - } - $resulttext .= '
  • '.&mt('LON-CAPA username').': '.$shownmapuser.'
  • '; - } - if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { - my $rolemaps; - foreach my $role (@ltiroles) { - if ($confhash{$itemid}{'maproles'}{$role}) { - $rolemaps .= (' 'x2).$role.'='. - &Apache::lonnet::plaintext($confhash{$itemid}{'maproles'}{$role}, - 'Course').','; + if ($confhash{$itemid}{'requser'}) { + if ($confhash{$itemid}{'mapuser'}) { + my $shownmapuser; + if ($confhash{$itemid}{'mapuser'} eq 'lis_person_sourcedid') { + $shownmapuser = $lt{'sourcedid'}.' (lis_person_sourcedid)'; + } elsif ($confhash{$itemid}{'mapuser'} eq 'lis_person_contact_email_primary') { + $shownmapuser = $lt{'email'}.' (lis_person_contact_email_primary)'; + } else { + $shownmapuser = &mt('Other').' ('.$confhash{$itemid}{'mapuser'}.')'; } + $resulttext .= '
  • '.&mt('LON-CAPA username').': '.$shownmapuser.'
  • '; } - if ($rolemaps) { - $rolemaps =~ s/,$//; - $resulttext .= '
  • '.&mt('Role mapping:').$rolemaps.'
  • '; + if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { + my $rolemaps; + foreach my $role (@ltiroles) { + if ($confhash{$itemid}{'maproles'}{$role}) { + $rolemaps .= (' 'x2).$role.'='. + &Apache::lonnet::plaintext($confhash{$itemid}{'maproles'}{$role}, + 'Course').','; + } + } + if ($rolemaps) { + $rolemaps =~ s/,$//; + $resulttext .= '
  • '.&mt('Role mapping:').$rolemaps.'
  • '; + } + } + if (ref($confhash{$itemid}{'makeuser'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'makeuser'}} > 0) { + $resulttext .= '
  • '.&mt('Following roles may create user accounts: [_1]', + join(', ',@{$confhash{$itemid}{'makeuser'}})).'
    '; + if ($confhash{$itemid}{'lcauth'} eq 'lti') { + $resulttext .= &mt('New users will only be able to authenticate via LTI').'
  • '; + } else { + $resulttext .= &mt('New users will be assigned LON-CAPA authentication: [_1]', + $confhash{$itemid}{'lcauth'}); + if ($confhash{$itemid}{'lcauth'} eq 'internal') { + $resulttext .= '; '.&mt('a randomly generated password will be created'); + } elsif ($confhash{$itemid}{'lcauth'} eq 'localauth') { + if ($confhash{$itemid}{'lcauthparm'} ne '') { + $resulttext .= ' '.&mt('with argument: [_1]',$confhash{$itemid}{'lcauthparm'}); + } + } else { + $resulttext .= '; '.&mt('Kerberos domain: [_1]',$confhash{$itemid}{'lcauthparm'}); + } + } + $resulttext .= ''; + } else { + $resulttext .= '
  • '.&mt('User account creation not permitted.').'
  • '; + } } - } - if (ref($confhash{$itemid}{'makeuser'}) eq 'ARRAY') { - if (@{$confhash{$itemid}{'makeuser'}} > 0) { - $resulttext .= '
  • '.&mt('Following roles may create user accounts: [_1]', - join(', ',@{$confhash{$itemid}{'makeuser'}})).'
  • '; - } else { - $resulttext .= '
  • '.&mt('User account creation not permitted.').'
  • '; + if (ref($confhash{$itemid}{'instdata'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'instdata'}} > 0) { + $resulttext .= '
  • '.&mt('Institutional data will be used when creating a new user for: [_1]', + join(', ',map { $fieldtitles{$_}; } @{$confhash{$itemid}{'instdata'}})).'
  • '; + } else { + $resulttext .= '
  • '.&mt('No institutional data used when creating a new user.').'
  • '; + } } - } - if ($confhash{$itemid}{'mapcrs'}) { - $resulttext .= '
  • '.&mt('Unique course identifier').': '.$confhash{$itemid}{'mapcrs'}.'
  • '; - } - if (ref($confhash{$itemid}{'mapcrstype'}) eq 'ARRAY') { - if (@{$confhash{$itemid}{'mapcrstype'}} > 0) { - $resulttext .= '
  • '.&mt('Mapping for the following LON-CAPA course types: [_1]', - join(', ',map { $coursetypetitles{$_}; } @coursetypes)). - '
  • '; - } else { - $resulttext .= '
  • '.&mt('No mapping to LON-CAPA courses').'
  • '; + if ($confhash{$itemid}{'mapcrs'}) { + $resulttext .= '
  • '.&mt('Unique course identifier').': '.$confhash{$itemid}{'mapcrs'}.'
  • '; } - } - if ($confhash{$itemid}{'makecrs'}) { - $resulttext .= '
  • '.&mt('Instructor may create course (if absent).').'
  • '; - } else { - $resulttext .= '
  • '.&mt('Instructor may not create course (if absent).').'
  • '; - } - if (ref($confhash{$itemid}{'selfenroll'}) eq 'ARRAY') { - if (@{$confhash{$itemid}{'selfenroll'}} > 0) { - $resulttext .= '
  • '.&mt('Self-enrollment for following roles: [_1]', - join(', ',@{$confhash{$itemid}{'selfenroll'}})). - '
  • '; + if (ref($confhash{$itemid}{'mapcrstype'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'mapcrstype'}} > 0) { + $resulttext .= '
  • '.&mt('Mapping for the following LON-CAPA course types: [_1]', + join(', ',map { $coursetypetitles{$_}; } @coursetypes)). + '
  • '; + } else { + $resulttext .= '
  • '.&mt('No mapping to LON-CAPA courses').'
  • '; + } + } + if ($confhash{$itemid}{'makecrs'}) { + $resulttext .= '
  • '.&mt('Instructor may create course (if absent).').'
  • '; } else { - $resulttext .= '
  • '.&mt('Self-enrollment not permitted').'
  • '; + $resulttext .= '
  • '.&mt('Instructor may not create course (if absent).').'
  • '; } - } - if ($confhash{$itemid}{'section'}) { - if ($confhash{$itemid}{'section'} eq 'course_section_sourcedid') { - $resulttext .= '
  • '.&mt('User section from standard field:'). - ' (course_section_sourcedid)'.'
  • '; + if (ref($confhash{$itemid}{'selfenroll'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'selfenroll'}} > 0) { + $resulttext .= '
  • '.&mt('Self-enrollment for following roles: [_1]', + join(', ',@{$confhash{$itemid}{'selfenroll'}})). + '
  • '; + } else { + $resulttext .= '
  • '.&mt('Self-enrollment not permitted').'
  • '; + } + } + if ($confhash{$itemid}{'section'}) { + if ($confhash{$itemid}{'section'} eq 'course_section_sourcedid') { + $resulttext .= '
  • '.&mt('User section from standard field:'). + ' (course_section_sourcedid)'.'
  • '; + } else { + $resulttext .= '
  • '.&mt('User section from:').' '. + $confhash{$itemid}{'section'}.'
  • '; + } } else { - $resulttext .= '
  • '.&mt('User section from:').' '. - $confhash{$itemid}{'section'}.'
  • '; + $resulttext .= '
  • '.&mt('No section assignment').'
  • '; } - } else { - $resulttext .= '
  • '.&mt('No section assignment').'
  • '; - } - foreach my $item ('passback','roster') { - $resulttext .= '
  • '.$lt{$item}.' '; - if ($confhash{$itemid}{$item}) { - $resulttext .= &mt('Yes'); + if ($confhash{$itemid}{'callback'}) { + $resulttext .= '
  • '.&mt('Callback setting').': '.$confhash{$itemid}{'callback'}.'
  • '; } else { - $resulttext .= &mt('No'); + $resulttext .= '
  • '.&mt('No callback to logout LON-CAPA session when user logs out of Comsumer'); + } + foreach my $item ('passback','roster','topmenu','inlinemenu') { + $resulttext .= '
  • '.$lt{$item}.': '; + if ($confhash{$itemid}{$item}) { + $resulttext .= &mt('Yes'); + if ($item eq 'passback') { + if ($confhash{$itemid}{'passbackformat'} eq '1.0') { + $resulttext .= ' ('.&mt('Outcomes Extension (1.0)').')'; + } elsif ($confhash{$itemid}{'passbackformat'} eq '1.1') { + $resulttext .= ' ('.&mt('Outcomes Service (1.1)').')'; + } + } + } else { + $resulttext .= &mt('No'); + } + $resulttext .= '
  • '; + } + if (ref($confhash{$itemid}{'lcmenu'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'lcmenu'}} > 0) { + $resulttext .= '
  • '.&mt('Menu items:').' '. + join(', ', map { $menutitles{$_}; } (@{$confhash{$itemid}{'lcmenu'}})).'
  • '; + } else { + $resulttext .= '
  • '.&mt('No menu items displayed in header or online menu').'
  • '; + } } - $resulttext .= ''; } $resulttext .= ''; } @@ -12270,8 +15383,9 @@ sub modify_contacts { my (%others,%to,%bcc,%includestr,%includeloc); my @contacts = ('supportemail','adminemail'); my @mailings = ('errormail','packagesmail','helpdeskmail','otherdomsmail', - 'lonstatusmail','requestsmail','updatesmail','idconflictsmail'); - my @toggles = ('reporterrors','reportupdates'); + 'lonstatusmail','requestsmail','updatesmail','idconflictsmail','hostipmail'); + my @toggles = ('reporterrors','reportupdates','reportstatus'); + my @lonstatus = ('threshold','sysmail','weights','excluded'); my ($fields,$fieldtitles,$fieldoptions,$possoptions) = &helpform_fields(); foreach my $type (@mailings) { @{$newsetting{$type}} = @@ -12304,6 +15418,41 @@ sub modify_contacts { $contacts_hash{'contacts'}{$item} = $env{'form.'.$item}; } } + my ($lonstatus_defs,$lonstatus_names) = &Apache::loncommon::lon_status_items(); + foreach my $item (@lonstatus) { + if ($item eq 'excluded') { + my (%serverhomes,@excluded); + map { $serverhomes{$_} = 1; } values(%Apache::lonnet::serverhomeIDs); + my @possexcluded = &Apache::loncommon::get_env_multiple('form.errorexcluded'); + if (@possexcluded) { + foreach my $id (sort(@possexcluded)) { + if ($serverhomes{$id}) { + push(@excluded,$id); + } + } + } + if (@excluded) { + $contacts_hash{'contacts'}{'lonstatus'}{$item} = \@excluded; + } + } elsif ($item eq 'weights') { + foreach my $type ('E','W','N','U') { + $env{'form.error'.$item.'_'.$type} =~ s/^\s+|\s+$//g; + if ($env{'form.error'.$item.'_'.$type} =~ /^\d+$/) { + unless ($env{'form.error'.$item.'_'.$type} == $lonstatus_defs->{$type}) { + $contacts_hash{'contacts'}{'lonstatus'}{$item}{$type} = + $env{'form.error'.$item.'_'.$type}; + } + } + } + } elsif (($item eq 'threshold') || ($item eq 'sysmail')) { + $env{'form.error'.$item} =~ s/^\s+|\s+$//g; + if ($env{'form.error'.$item} =~ /^\d+$/) { + unless ($env{'form.error'.$item} == $lonstatus_defs->{$item}) { + $contacts_hash{'contacts'}{'lonstatus'}{$item} = $env{'form.error'.$item}; + } + } + } + } if ((ref($fields) eq 'ARRAY') && (ref($possoptions) eq 'HASH')) { foreach my $field (@{$fields}) { if (ref($possoptions->{$field}) eq 'ARRAY') { @@ -12442,6 +15591,76 @@ sub modify_contacts { } } } + if (ref($currsetting{'lonstatus'}) eq 'HASH') { + foreach my $key ('excluded','weights','threshold','sysmail') { + if ($key eq 'excluded') { + if ((ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') && + (ref($contacts_hash{contacts}{lonstatus}{excluded}) eq 'ARRAY')) { + if ((ref($currsetting{'lonstatus'}{$key}) eq 'ARRAY') && + (@{$currsetting{'lonstatus'}{$key}})) { + my @diffs = + &Apache::loncommon::compare_arrays($contacts_hash{contacts}{lonstatus}{excluded}, + $currsetting{'lonstatus'}{$key}); + if (@diffs) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif (@{$contacts_hash{contacts}{lonstatus}{excluded}}) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif ((ref($currsetting{'lonstatus'}{$key}) eq 'ARRAY') && + (@{$currsetting{'lonstatus'}{$key}})) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif ($key eq 'weights') { + if ((ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') && + (ref($contacts_hash{contacts}{lonstatus}{$key}) eq 'HASH')) { + if (ref($currsetting{'lonstatus'}{$key}) eq 'HASH') { + foreach my $type ('E','W','N','U') { + unless ($contacts_hash{contacts}{lonstatus}{$key}{$type} eq + $currsetting{'lonstatus'}{$key}{$type}) { + push(@{$changes{'lonstatus'}},$key); + last; + } + } + } else { + foreach my $type ('E','W','N','U') { + if ($contacts_hash{contacts}{lonstatus}{$key}{$type} ne '') { + push(@{$changes{'lonstatus'}},$key); + last; + } + } + } + } elsif (ref($currsetting{'lonstatus'}{$key}) eq 'HASH') { + foreach my $type ('E','W','N','U') { + if ($currsetting{'lonstatus'}{$key}{$type} ne '') { + push(@{$changes{'lonstatus'}},$key); + last; + } + } + } + } elsif (($key eq 'threshold') || ($key eq 'sysmail')) { + if (ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') { + if ($currsetting{'lonstatus'}{$key} =~ /^\d+$/) { + if ($currsetting{'lonstatus'}{$key} != $contacts_hash{contacts}{lonstatus}{$key}) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif ($contacts_hash{contacts}{lonstatus}{$key} =~ /^\d+$/) { + push(@{$changes{'lonstatus'}},$key); + } + } elsif ($currsetting{'lonstatus'}{$key} =~ /^\d+$/) { + push(@{$changes{'lonstatus'}},$key); + } + } + } + } else { + if (ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') { + foreach my $key ('excluded','weights','threshold','sysmail') { + if (exists($contacts_hash{contacts}{lonstatus}{$key})) { + push(@{$changes{'lonstatus'}},$key); + } + } + } + } } else { my %default; $default{'supportemail'} = $Apache::lonnet::perlvar{'lonSupportEMail'}; @@ -12453,6 +15672,7 @@ sub modify_contacts { $default{'lonstatusmail'} = 'adminemail'; $default{'requestsmail'} = 'adminemail'; $default{'updatesmail'} = 'adminemail'; + $default{'hostipmail'} = 'adminemail'; foreach my $item (@contacts) { if ($to{$item} ne $default{$item}) { $changes{$item} = 1; @@ -12486,6 +15706,13 @@ sub modify_contacts { } } } + if (ref($contacts_hash{contacts}{lonstatus}) eq 'HASH') { + foreach my $key ('excluded','weights','threshold','sysmail') { + if (exists($contacts_hash{contacts}{lonstatus}{$key})) { + push(@{$changes{'lonstatus'}},$key); + } + } + } } foreach my $item (@toggles) { if (($env{'form.'.$item} == 1) && ($currsetting{$item} == 0)) { @@ -12611,22 +15838,79 @@ sub modify_contacts { } } my @offon = ('off','on'); + my $corelink = &core_link_msu(); if ($changes{'reporterrors'}) { $resulttext .= '
  • '. &mt('E-mail error reports to [_1] set to "'. $offon[$env{'form.reporterrors'}].'".', - &Apache::loncommon::modal_link('http://loncapa.org/core.html', - &mt('LON-CAPA core group - MSU'),600,500)). + $corelink). '
  • '; } if ($changes{'reportupdates'}) { $resulttext .= '
  • '. &mt('E-mail record of completed LON-CAPA updates to [_1] set to "'. $offon[$env{'form.reportupdates'}].'".', - &Apache::loncommon::modal_link('http://loncapa.org/core.html', - &mt('LON-CAPA core group - MSU'),600,500)). + $corelink). '
  • '; } + if ($changes{'reportstatus'}) { + $resulttext .= '
  • '. + &mt('E-mail status if errors above threshold to [_1] set to "'. + $offon[$env{'form.reportstatus'}].'".', + $corelink). + '
  • '; + } + if (ref($changes{'lonstatus'}) eq 'ARRAY') { + $resulttext .= '
  • '. + &mt('Nightly status check e-mail settings').':
      '; + my (%defval,%use_def,%shown); + $defval{'threshold'} = $lonstatus_defs->{'threshold'}; + $defval{'sysmail'} = $lonstatus_defs->{'sysmail'}; + $defval{'weights'} = + join(', ',map { $lonstatus_names->{$_}.'='.$lonstatus_defs->{$_}; } ('E','W','N','U')); + $defval{'excluded'} = &mt('None'); + if (ref($contacts_hash{'contacts'}{'lonstatus'}) eq 'HASH') { + foreach my $item ('threshold','sysmail','weights','excluded') { + if (exists($contacts_hash{'contacts'}{'lonstatus'}{$item})) { + if (($item eq 'threshold') || ($item eq 'sysmail')) { + $shown{$item} = $contacts_hash{'contacts'}{'lonstatus'}{$item}; + } elsif ($item eq 'weights') { + if (ref($contacts_hash{'contacts'}{'lonstatus'}{$item}) eq 'HASH') { + foreach my $type ('E','W','N','U') { + $shown{$item} .= $lonstatus_names->{$type}.'='; + if (exists($contacts_hash{'contacts'}{'lonstatus'}{$item}{$type})) { + $shown{$item} .= $contacts_hash{'contacts'}{'lonstatus'}{$item}{$type}; + } else { + $shown{$item} .= $lonstatus_defs->{$type}; + } + $shown{$item} .= ', '; + } + $shown{$item} =~ s/, $//; + } else { + $shown{$item} = $defval{$item}; + } + } elsif ($item eq 'excluded') { + if (ref($contacts_hash{'contacts'}{'lonstatus'}{$item}) eq 'ARRAY') { + $shown{$item} = join(', ',@{$contacts_hash{'contacts'}{'lonstatus'}{$item}}); + } else { + $shown{$item} = $defval{$item}; + } + } + } else { + $shown{$item} = $defval{$item}; + } + } + } else { + foreach my $item ('threshold','weights','excluded','sysmail') { + $shown{$item} = $defval{$item}; + } + } + foreach my $item ('threshold','weights','excluded','sysmail') { + $resulttext .= '
    • '.&mt($titles->{'error'.$item}.' -- [_1]', + $shown{$item}).'
    • '; + } + $resulttext .= '
  • '; + } if ((ref($changes{'helpform'}) eq 'ARRAY') && (ref($fields) eq 'ARRAY')) { my (@optional,@required,@unused,$maxsizechg); foreach my $field (@{$changes{'helpform'}}) { @@ -12678,6 +15962,778 @@ sub modify_contacts { return $resulttext; } +sub modify_privacy { + my ($dom,%domconfig) = @_; + my ($resulttext,%current,%changes); + if (ref($domconfig{'privacy'}) eq 'HASH') { + %current = %{$domconfig{'privacy'}}; + } + my @fields = ('lastname','firstname','middlename','generation','permanentemail','id'); + my @items = ('domain','author','course','community'); + my %names = &Apache::lonlocal::texthash ( + domain => 'Assigned domain role(s)', + author => 'Assigned co-author role(s)', + course => 'Assigned course role(s)', + community => 'Assigned community role', + ); + my %roles = &Apache::lonlocal::texthash ( + domain => 'Domain role', + author => 'Co-author role', + course => 'Course role', + community => 'Community role', + ); + my %titles = &Apache::lonlocal::texthash ( + approval => 'Approval for role in different domain', + othdom => 'User information available in other domain', + priv => 'Information viewable by privileged user in same domain', + unpriv => 'Information viewable by unprivileged user in same domain', + instdom => 'Other domain shares institution/provider', + extdom => 'Other domain has different institution/provider', + none => 'Not allowed', + user => 'User authorizes', + domain => 'Domain Coordinator authorizes', + auto => 'Unrestricted', + ); + my %fieldnames = &Apache::lonlocal::texthash ( + id => 'Student/Employee ID', + permanentemail => 'E-mail address', + lastname => 'Last Name', + firstname => 'First Name', + middlename => 'Middle Name', + generation => 'Generation', + ); + my ($othertitle,$usertypes,$types) = + &Apache::loncommon::sorted_inst_types($dom); + my (%by_ip,%by_location,@intdoms,@instdoms); + &build_location_hashes(\@intdoms,\%by_ip,\%by_location,\@instdoms); + + my %privacyhash = ( + 'approval' => { + instdom => {}, + extdom => {}, + }, + 'othdom' => {}, + 'priv' => {}, + 'unpriv' => {}, + ); + foreach my $item (@items) { + if (@instdoms > 1) { + if ($env{'form.privacy_approval_instdom'.$item} =~ /^(none|user|domain|auto)$/) { + $privacyhash{'approval'}{'instdom'}{$item} = $env{'form.privacy_approval_instdom_'.$item}; + } + if (ref($current{'approval'}) eq 'HASH') { + if (ref($current{'approval'}{'instdom'}) eq 'HASH') { + unless ($privacyhash{'approval'}{'instdom'}{$item} eq $current{'approval'}{'instdom'}{$item}) { + $changes{'approval'} = 1; + } + } + } elsif ($privacyhash{'approval'}{'instdom'}{$item} ne 'auto') { + $changes{'approval'} = 1; + } + } + if (keys(%by_location) > 0) { + if ($env{'form.privacy_approval_extdom_'.$item} =~ /^(none|user|domain|auto)$/) { + $privacyhash{'approval'}{'extdom'}{$item} = $env{'form.privacy_approval_extdom_'.$item}; + } + if (ref($current{'approval'}) eq 'HASH') { + if (ref($current{'approval'}{'extdom'}) eq 'HASH') { + unless ($privacyhash{'approval'}{'extdom'}{$item} eq $current{'approval'}{'extdom'}{$item}) { + $changes{'approval'} = 1; + } + } + } elsif ($privacyhash{'approval'}{'extdom'}{$item} ne 'auto') { + $changes{'approval'} = 1; + } + } + foreach my $status ('priv','unpriv') { + my @possibles = sort(&Apache::loncommon::get_env_multiple('form.privacy_'.$status.'_'.$item)); + my @newvalues; + foreach my $field (@possibles) { + if (grep(/^\Q$field\E$/,@fields)) { + $privacyhash{$status}{$item}{$field} = 1; + push(@newvalues,$field); + } + } + @newvalues = sort(@newvalues); + if (ref($current{$status}) eq 'HASH') { + if (ref($current{$status}{$item}) eq 'HASH') { + my @currvalues = sort(keys(%{$current{$status}{$item}})); + my @diffs = &Apache::loncommon::compare_arrays(\@currvalues,\@newvalues); + if (@diffs > 0) { + $changes{$status} = 1; + } + } + } else { + my @stdfields; + foreach my $field (@fields) { + if ($field eq 'id') { + next if ($status eq 'unpriv'); + next if (($status eq 'priv') && ($item eq 'community')); + } + push(@stdfields,$field); + } + my @diffs = &Apache::loncommon::compare_arrays(\@stdfields,\@newvalues); + if (@diffs > 0) { + $changes{$status} = 1; + } + } + } + } + if ((@instdoms > 1) || (keys(%by_location) > 0)) { + my @statuses; + if (ref($types) eq 'ARRAY') { + @statuses = @{$types}; + } + foreach my $type (@statuses,'default') { + my @possfields = &Apache::loncommon::get_env_multiple('form.privacy_othdom_'.$type); + my @newvalues; + foreach my $field (sort(@possfields)) { + if (grep(/^\Q$field\E$/,@fields)) { + $privacyhash{'othdom'}{$type}{$field} = 1; + push(@newvalues,$field); + } + } + @newvalues = sort(@newvalues); + if (ref($current{'othdom'}) eq 'HASH') { + if (ref($current{'othdom'}{$type}) eq 'HASH') { + my @currvalues = sort(keys(%{$current{'othdom'}{$type}})); + my @diffs = &Apache::loncommon::compare_arrays(\@currvalues,\@newvalues); + if (@diffs > 0) { + $changes{'othdom'} = 1; + } + } + } else { + my @stdfields = ('lastname','firstname','middlename','generation','permanentemail'); + my @diffs = &Apache::loncommon::compare_arrays(\@stdfields,\@newvalues); + if (@diffs > 0) { + $changes{'othdom'} = 1; + } + } + } + } + my %confighash = ( + privacy => \%privacyhash, + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%confighash,$dom); + if ($putresult eq 'ok') { + if (keys(%changes) > 0) { + $resulttext = &mt('Changes made: ').''; } + &Apache::lonnet::do_cache_new('cats',$dom,$cathash,3600); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'cats'} = 1; + } } $resulttext .= ''; if ($changes{'unauth'} || $changes{'auth'}) { @@ -15100,7 +19239,6 @@ sub modify_coursedefaults { $defaultshash{'coursedefaults'}{$setting}{$type} = $newdef; } if ($currdef ne $newdef) { - my $staticdef; if ($item eq 'anonsurvey_threshold') { unless (($currdef eq '') && ($newdef == $staticdefaults{$item})) { $changes{$item} = 1; @@ -15116,11 +19254,12 @@ sub modify_coursedefaults { my $texengine; if ($env{'form.texengine'} =~ /^(MathJax|mimetex|tth)$/) { $texengine = $env{'form.texengine'}; - if ($defaultshash{'coursedefaults'}{'texengine'} eq '') { - unless ($texengine eq 'MathJax') { + my $currdef = $domconfig{'coursedefaults'}{'texengine'}; + if ($currdef eq '') { + unless ($texengine eq $Apache::lonnet::deftex) { $changes{'texengine'} = 1; } - } elsif ($defaultshash{'coursedefaults'}{'texengine'} ne $texengine) { + } elsif ($currdef ne $texengine) { $changes{'texengine'} = 1; } } @@ -15668,6 +19807,267 @@ sub modify_selfenrollment { return $resulttext; } +sub modify_wafproxy { + my ($dom,$action,$lastactref,%domconfig) = @_; + my %servers = &Apache::lonnet::internet_dom_servers($dom); + my (%othercontrol,%canset,%values,%curralias,%currvalue,@warnings,%wafproxy, + %changes,%expirecache); + foreach my $server (sort(keys(%servers))) { + my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server}); + if ($serverhome eq $server) { + my $serverdom = &Apache::lonnet::host_domain($server); + if ($serverdom eq $dom) { + $canset{$server} = 1; + } + } + } + if (ref($domconfig{'wafproxy'}) eq 'HASH') { + %{$values{$dom}} = (); + if (ref($domconfig{'wafproxy'}{'alias'}) eq 'HASH') { + %curralias = %{$domconfig{'wafproxy'}{'alias'}}; + } + foreach my $item ('remoteip','ipheader','trusted','vpnint','vpnext') { + $currvalue{$item} = $domconfig{'wafproxy'}{$item}; + } + } + my $output; + if (keys(%canset)) { + %{$wafproxy{'alias'}} = (); + foreach my $key (sort(keys(%canset))) { + if ($env{'form.wafproxy_'.$dom}) { + $wafproxy{'alias'}{$key} = $env{'form.wafproxy_alias_'.$key}; + $wafproxy{'alias'}{$key} =~ s/^\s+|\s+$//g; + if ($wafproxy{'alias'}{$key} ne $curralias{$key}) { + $changes{'alias'} = 1; + } + } else { + $wafproxy{'alias'}{$key} = ''; + if ($curralias{$key}) { + $changes{'alias'} = 1; + } + } + if ($wafproxy{'alias'}{$key} eq '') { + if ($curralias{$key}) { + $expirecache{$key} = 1; + } + delete($wafproxy{'alias'}{$key}); + } + } + unless (keys(%{$wafproxy{'alias'}})) { + delete($wafproxy{'alias'}); + } + # Localization for values in %warn occus in &mt() calls separately. + my %warn = ( + trusted => 'trusted IP range(s)', + vpnint => 'internal IP range(s) for VPN sessions(s)', + vpnext => 'IP range(s) for backend WAF connections', + ); + foreach my $item ('remoteip','ipheader','trusted','vpnint','vpnext') { + 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; + } + } else { + my (@ok,$count); + if (($item eq 'vpnint') || ($item eq 'vpnext')) { + unless ($env{'form.wafproxy_vpnaccess'}) { + $possible = ''; + } + } elsif ($item eq 'trusted') { + unless ($wafproxy{'remoteip'} eq 'h') { + $possible = ''; + } + } + unless ($possible eq '') { + $possible =~ s/[\r\n]+/\s/g; + $possible =~ s/\s*-\s*/-/g; + $possible =~ s/\s+/,/g; + } + $count = 0; + if ($possible ne '') { + foreach my $poss (split(/\,/,$possible)) { + $count ++; + if (&validate_ip_pattern($poss)) { + push(@ok,$poss); + } + } + if (@ok) { + $wafproxy{$item} = join(',',@ok); + } + my $diff = $count - scalar(@ok); + if ($diff) { + push(@warnings,'
  • '. + &mt('[quant,_1,IP] invalid and excluded from saved value for [_2]', + $diff,$warn{$item}). + '
  • '); + } + } + } + if ($wafproxy{$item} ne $currvalue{$item}) { + $changes{$item} = 1; + } + } elsif ($currvalue{$item}) { + $changes{$item} = 1; + } + } + } else { + if (keys(%curralias)) { + $changes{'alias'} = 1; + } + if (keys(%currvalue)) { + foreach my $key (keys(%currvalue)) { + $changes{$key} = 1; + } + } + } + if (keys(%changes)) { + my %defaultshash = ( + wafproxy => \%wafproxy, + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash, + $dom); + if ($putresult eq 'ok') { + my $cachetime = 24*60*60; + my (%domdefaults,$updatedomdefs); + foreach my $item ('ipheader','trusted','vpnint','vpnext') { + if ($changes{$item}) { + unless ($updatedomdefs) { + %domdefaults = &Apache::lonnet::get_domain_defaults($dom); + $updatedomdefs = 1; + } + if ($wafproxy{$item}) { + $domdefaults{'waf_'.$item} = $wafproxy{$item}; + } elsif (exists($domdefaults{'waf_'.$item})) { + delete($domdefaults{'waf_'.$item}); + } + } + } + if ($updatedomdefs) { + &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; + } + } + if ((exists($wafproxy{'alias'})) || (keys(%expirecache))) { + my %updates = %expirecache; + foreach my $key (keys(%expirecache)) { + &Apache::lonnet::devalidate_cache_new('proxyalias',$key); + } + if (ref($wafproxy{'alias'}) eq 'HASH') { + my $cachetime = 24*60*60; + foreach my $key (keys(%{$wafproxy{'alias'}})) { + $updates{$key} = 1; + &Apache::lonnet::do_cache_new('proxyalias',$key,$wafproxy{'alias'}{$key}, + $cachetime); + } + } + if (ref($lastactref) eq 'HASH') { + $lastactref->{'proxyalias'} = \%updates; + } + } + $output = &mt('Changes were made to Web Application Firewall/Reverse Proxy').'
    '. ''; + my $prefix = 'canmodify'; if ($role eq 'emailusername') { unless ($checks{$fields[$i]} =~ /^(required|optional)$/) { $checks{$fields[$i]} = 'omit'; @@ -8500,13 +10946,18 @@ sub modifiable_userdata_row { $checked='checked="checked" '; } $output .= ''.(' ' x2); } $output .= ''.$fieldtitles{$fields[$i]}.''; } else { + if ($context eq 'lti') { + $prefix = 'lti'; + } elsif ($context eq 'privacy') { + $prefix = 'privacy'; + } $output .= ''; } @@ -8608,7 +11059,7 @@ sub insttypes_row { } $output .= ' '; } else { - if (($rem == 0) && (@{$types} > 0)) { + if ($rem == 0) { $output .= '