--- loncom/interface/domainprefs.pm 2018/01/01 01:29:38 1.324 +++ loncom/interface/domainprefs.pm 2021/11/22 22:19:58 1.390 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.324 2018/01/01 01:29:38 raeburn Exp $ +# $Id: domainprefs.pm,v 1.390 2021/11/22 22:19:58 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,undef,1); if (ref($domconfig{'ltitools'}) eq 'HASH') { if (ref($encconfig{'ltitools'}) eq 'HASH') { foreach my $id (keys(%{$domconfig{'ltitools'}})) { @@ -246,12 +248,25 @@ sub handler { } } } - my @prefs_order = ('rolecolors','login','defaults','quotas','autoenroll', - 'autoupdate','autocreate','directorysrch','contacts', - 'usercreation','selfcreation','usermodification','scantron', - 'requestcourses','requestauthor','coursecategories', - 'serverstatuses','helpsettings','coursedefaults', - 'ltitools','selfenrollment','usersessions','ssl','trust','lti'); + if (ref($domconfig{'proctoring'}) eq 'HASH') { + if (ref($encconfig{'proctoring'}) eq 'HASH') { + foreach my $provider (keys(%{$domconfig{'proctoring'}})) { + if ((ref($domconfig{'proctoring'}{$provider}) eq 'HASH') && + (ref($encconfig{'proctoring'}{$provider}) eq 'HASH')) { + foreach my $item ('key','secret') { + $domconfig{'proctoring'}{$provider}{$item} = $encconfig{'proctoring'}{$provider}{$item}; + } + } + } + } + } + my @prefs_order = ('rolecolors','login','defaults','wafproxy','passwords','quotas', + 'autoenroll','autoupdate','autocreate','directorysrch', + 'contacts','privacy','usercreation','selfcreation', + 'usermodification','scantron','requestcourses','requestauthor', + 'coursecategories','serverstatuses','helpsettings','coursedefaults', + 'ltitools','proctoring','selfenrollment','usersessions','ssl', + 'trust','lti'); my %existing; if (ref($domconfig{'loadbalancing'}) eq 'HASH') { %existing = %{$domconfig{'loadbalancing'}}; @@ -282,7 +297,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, }, @@ -291,13 +309,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 +393,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 +435,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 +526,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 +570,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', @@ -577,7 +635,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, }; @@ -625,6 +686,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 +811,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 +837,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 +855,18 @@ sub print_config_box { $output = &Apache::lonuserutils::custom_roledefs_js($context,$crstype,$formname,\%full, \@templateroles); + } elsif ($action eq 'ltitools') { + $output .= <itools_javascript($settings); + } elsif ($action eq 'lti') { + $output .= <i_javascript($settings); + } elsif ($action eq 'proctoring') { + $output .= &proctoring_javascript($settings); + } elsif ($action eq 'wafproxy') { + $output .= &wafproxy_javascript($dom); + } elsif ($action eq 'autoupdate') { + $output .= &autoupdate_javascript(); + } elsif ($action eq 'login') { + $output .= &saml_javascript(); } $output .= ' @@ -797,20 +883,24 @@ sub print_config_box { if ($numheaders > 1) { my $colspan = ''; my $rightcolspan = ''; + my $leftnobr = ''; if (($action eq 'rolecolors') || ($action eq 'defaults') || ($action eq 'directorysrch') || - (($action eq 'login') && ($numheaders < 4))) { + (($action eq 'login') && ($numheaders < 5))) { $colspan = ' colspan="2"'; } if ($action eq 'usersessions') { $rightcolspan = ' colspan="3"'; } + if ($action eq 'passwords') { + $leftnobr = ' LC_nobreak'; + } $output .= ' + + + + + '; } else { $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); } @@ -907,8 +1031,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,12 +908,16 @@ 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) { + if ($numheaders == 5) { $colspan = ' colspan="2"'; $output .= &print_login('service',$dom,$confname,$phase,$settings,\$rowtotal); } else { @@ -849,12 +943,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 +997,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'}).'
+
@@ -932,7 +1058,7 @@ sub print_config_box { '.&mt($item->{'header'}->[3]->{'col2'}).''. $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); } elsif ($action eq 'login') { - if ($numheaders == 4) { + if ($numheaders == 5) { $output .= &print_login('page',$dom,$confname,$phase,$settings,\$rowtotal).' @@ -956,7 +1082,7 @@ sub print_config_box { '; - if ($numheaders == 4) { + if ($numheaders == 5) { $output .= ' @@ -968,7 +1094,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 ++; @@ -1088,10 +1234,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 .= ' @@ -1104,9 +1249,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); my $choice = $choices{'disallowlogin'}; @@ -1300,18 +1448,10 @@ sub print_login { $datatable .= &display_color_options($dom,$confname,$phase,'login',$itemcount,\%choices,\%is_custom,\%defaults,\%designs,\@images,\@bgs,\@links,\%alt_text,$rowtotal,\@logintext); $datatable .= '
'.&mt($item->{'header'}->[4]->{'col1'}).''.&mt($item->{'header'}->[4]->{'col2'}).'
'.&mt($item->{'header'}->[3]->{'col1'}).''.&mt($item->{'header'}->[3]->{'col2'}).'
'; } elsif ($caller eq 'help') { - my ($defaulturl,$defaulttype,%url,%type,%lt,%langchoices); - my $switchserver = &check_switchserver($dom,$confname); + my ($defaulturl,$defaulttype,%url,%type,%langchoices); my $itemcount = 1; $defaulturl = '/adm/loginproblems.html'; $defaulttype = 'default'; - %lt = &Apache::lonlocal::texthash ( - del => 'Delete?', - rep => 'Replace:', - upl => 'Upload:', - default => 'Default', - custom => 'Custom', - ); %langchoices = &Apache::lonlocal::texthash(&get_languages_hash()); my @currlangs; if (ref($settings) eq 'HASH') { @@ -1408,14 +1548,6 @@ sub print_login { } } } - my %lt = &Apache::lonlocal::texthash( - del => 'Delete?', - rep => 'Replace:', - upl => 'Upload:', - curr => 'View contents', - none => 'None', - ); - my $switchserver = &check_switchserver($dom,$confname); foreach my $lonhost (sort(keys(%domservers))) { my $exempt = &check_exempt_addresses($currexempt{$lonhost}); $datatable .= ''.$domservers{$lonhost}.''; @@ -1436,7 +1568,89 @@ sub print_login { } else { $datatable .= ''; } - $datatable .= ''; + $datatable .= ''; + } + $datatable .= ''; + } elsif ($caller eq 'saml') { + my %domservers = &Apache::lonnet::get_servers($dom); + $datatable .= ''. + ''. + ''. + ''."\n"; + my (%saml,%samltext,%samlimg,%samlalt,%samlurl,%samltitle,%samlnotsso,%styleon,%styleoff); + foreach my $lonhost (keys(%domservers)) { + $samlurl{$lonhost} = '/adm/sso'; + $styleon{$lonhost} = 'display:none'; + $styleoff{$lonhost} = ''; + } + if (ref($settings->{'saml'}) eq 'HASH') { + foreach my $lonhost (keys(%{$settings->{'saml'}})) { + if (ref($settings->{'saml'}{$lonhost}) eq 'HASH') { + $saml{$lonhost} = 1; + $samltext{$lonhost} = $settings->{'saml'}{$lonhost}{'text'}; + $samlimg{$lonhost} = $settings->{'saml'}{$lonhost}{'img'}; + $samlalt{$lonhost} = $settings->{'saml'}{$lonhost}{'alt'}; + $samlurl{$lonhost} = $settings->{'saml'}{$lonhost}{'url'}; + $samltitle{$lonhost} = $settings->{'saml'}{$lonhost}{'title'}; + $samlnotsso{$lonhost} = $settings->{'saml'}{$lonhost}{'notsso'}; + $styleon{$lonhost} = ''; + $styleoff{$lonhost} = 'display:none'; + } else { + $styleon{$lonhost} = 'display:none'; + $styleoff{$lonhost} = ''; + } + } + } + my $itemcount = 1; + foreach my $lonhost (sort(keys(%domservers))) { + my $samlon = ' '; + my $samloff = ' checked="checked" '; + if ($saml{$lonhost}) { + $samlon = $samloff; + $samloff = ' '; + } + my $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''. + ''. + ''; + $itemcount ++; } $datatable .= '
'.$choices{'hostid'}.''.$choices{'samllanding'}.''.$choices{'samloptions'}.'
'.$domservers{$lonhost}.''.(' 'x2). + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + '
'.&mt('SSO').''. + ''.&mt('Non-SSO').'
'.&mt('Text').''.&mt('Image').''.&mt('Alt Text').''.&mt('URL').''.&mt('Tool Tip').''.&mt('Text').'
'; + if ($samlimg{$lonhost}) { + $datatable .= '
'. + ' '.$lt{'rep'}.''; + } else { + $datatable .= $lt{'upl'}; + } + $datatable .='
'; + if ($switchserver) { + $datatable .= &mt('Upload to library server: [_1]',$switchserver); + } else { + $datatable .= ''; + } + $datatable .= '
 
'; } @@ -1475,10 +1689,24 @@ 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_rolecolors { my ($phase,$role,$dom,$confname,$settings,$rowtotal) = @_; my %choices = &color_font_choices(); @@ -1596,7 +1824,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 +1833,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 +1848,7 @@ sub display_color_options { ' '. - ' '; + ' '; } my $switchserver = &check_switchserver($dom,$confname); foreach my $img (@{$images}) { @@ -1738,7 +1966,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 +1994,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 +2080,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 +2119,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 +2583,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 +2710,7 @@ sub print_textbookcourses { $datatable .= ''; } $datatable .= ' '."\n". - ''.&mt('Add').''."\n". + ''.&mt('Add').''."\n". ''. ''.&mt('Subject:').' '."\n". (' 'x2). @@ -2501,13 +2727,13 @@ sub print_textbookcourses { } else { $datatable .= ''; } + $datatable .= ''."\n"; } - $datatable .= ''."\n". - ''.&mt('LON-CAPA course:').' '. + $datatable .= ''.&mt('LON-CAPA course:').' '. &Apache::loncommon::select_dom_form($env{'request.role.domain'},$type.'_addbook_cdom'). ''. &Apache::loncommon::selectcourse_link - ('display',$type.'_addbook_cnum',$type.'_addbook_cdom',undef,undef,undef,'Course'); + ('display',$type.'_addbook_cnum',$type.'_addbook_cdom',undef,undef,undef,'Course'). ''."\n". ''."\n"; $itemcount ++; @@ -2733,6 +2959,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(); @@ -2740,14 +3177,16 @@ sub lti_javascript { return $togglejs; } my (%ordered,$total,%jstext); - $total = 0; + $total = scalar(keys(%{$settings})); foreach my $item (keys(%{$settings})) { if (ref($settings->{$item}) eq 'HASH') { my $num = $settings->{$item}{'order'}; + if ($num eq '') { + $num = $total - 1; + } $ordered{$num} = $item; } } - $total = scalar(keys(%{$settings})); my @jsarray = (); foreach my $item (sort {$a <=> $b } (keys(%ordered))) { push(@jsarray,$ordered{$item}); @@ -2806,32 +3245,76 @@ ENDSCRIPT } sub lti_toggle_js { + my %lcauthparmtext = &Apache::lonlocal::texthash ( + localauth => 'Local auth argument', + krb => 'Kerberos domain', + ); return <<"ENDSCRIPT"; + +ENDSCRIPT +} + +sub autoupdate_javascript { + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + +sub saml_javascript { + return <<"ENDSCRIPT"; + + +ENDSCRIPT + +} + sub print_scantronformat { my ($r,$dom,$confname,$settings,$rowtotal) = @_; my $itemcount = 1; @@ -7600,8 +10055,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)) { @@ -7610,7 +10065,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}, @@ -7621,13 +10076,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).''; } @@ -7747,6 +10202,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; @@ -7789,7 +10367,7 @@ sub print_coursecategories { ''.$lt{$type}.' '; } - $datatable .= ''; + $datatable .= '
'; $itemcount ++; } $$rowtotal += $itemcount; @@ -7875,7 +10453,7 @@ sub print_coursecategories { $can_catcomm_dom.' value="dom" />'.$level{'dom'}.' '. '
'. - ''. + ''. ''.$title{'togglecatsplace'}.''. ''.&mt('Add subcategory:').''; + $text .= ''.&mt('Add subcategory:').''; } } } @@ -8426,7 +11066,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') { @@ -8447,6 +11087,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'); @@ -8489,30 +11133,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" '; } } } @@ -8531,8 +11184,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" '; @@ -8542,6 +11213,7 @@ sub modifiable_userdata_row { } $output .= ''; } if ($colsleft > 1) { @@ -8760,12 +11437,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') { @@ -8773,6 +11452,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); @@ -9019,6 +11712,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'; @@ -9059,6 +11835,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; } @@ -9138,6 +11939,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; @@ -9643,7 +12476,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); @@ -9968,7 +12801,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(); @@ -10017,7 +12850,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)) { @@ -10939,7 +13772,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; @@ -11106,7 +13939,7 @@ sub modify_ltitools { my %ltienchash = ( $action => { %encconfig } ); - &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom); + &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom,undef,1); if (keys(%changes) > 0) { my $cachetime = 24*60*60; my %ltiall = %confhash; @@ -11166,7 +13999,7 @@ sub modify_ltitools { } } if (!$numconfig) { - $resulttext .= &mt('None'); + $resulttext .= ' '.&mt('None'); } $resulttext .= ''; foreach my $item ('passback','roster') { @@ -11345,6 +14178,536 @@ sub get_ltitools_id { return ($id,$error); } +sub modify_proctoring { + my ($r,$dom,$action,$lastactref,%domconfig) = @_; + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my (@allpos,%changes,%confhash,%encconfhash,$errors,$resulttext,%imgdeletions); + my $confname = $dom.'-domainconfig'; + my $servadm = $r->dir_config('lonAdmEMail'); + my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm); + my %providernames = &proctoring_providernames(); + my $maxnum = scalar(keys(%providernames)); + + my (%requserfields,%optuserfields,%defaults,%extended,%crsconf,@courseroles,@ltiroles); + my ($requref,$opturef,$defref,$extref,$crsref,$rolesref,$ltiref) = &proctoring_data(); + if (ref($requref) eq 'HASH') { + %requserfields = %{$requref}; + } + if (ref($opturef) eq 'HASH') { + %optuserfields = %{$opturef}; + } + if (ref($defref) eq 'HASH') { + %defaults = %{$defref}; + } + if (ref($extref) eq 'HASH') { + %extended = %{$extref}; + } + if (ref($crsref) eq 'HASH') { + %crsconf = %{$crsref}; + } + if (ref($rolesref) eq 'ARRAY') { + @courseroles = @{$rolesref}; + } + if (ref($ltiref) eq 'ARRAY') { + @ltiroles = @{$ltiref}; + } + + if (ref($domconfig{$action}) eq 'HASH') { + my @todeleteimages = &Apache::loncommon::get_env_multiple('form.proctoring_image_del'); + if (@todeleteimages) { + map { $imgdeletions{$_} = 1; } @todeleteimages; + } + } + my %customadds; + my @newcustom = &Apache::loncommon::get_env_multiple('form.proctoring_customadd'); + if (@newcustom) { + map { $customadds{$_} = 1; } @newcustom; + } + foreach my $provider (sort(keys(%providernames))) { + $confhash{$provider} = {}; + my $pos = $env{'form.proctoring_pos_'.$provider}; + $pos =~ s/\D+//g; + $allpos[$pos] = $provider; + my (%current,%currentenc); + my $showroles = 0; + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$provider}) eq 'HASH') { + %current = %{$domconfig{$action}{$provider}}; + foreach my $item ('key','secret') { + $currentenc{$item} = $current{$item}; + delete($current{$item}); + } + } + } + if ($env{'form.proctoring_available_'.$provider}) { + $confhash{$provider}{'available'} = 1; + unless ($current{'available'}) { + $changes{$provider} = 1; + } + } else { + %{$confhash{$provider}} = %current; + %{$encconfhash{$provider}} = %currentenc; + $confhash{$provider}{'available'} = 0; + if ($current{'available'}) { + $changes{$provider} = 1; + } + } + if ($confhash{$provider}{'available'}) { + foreach my $field ('lifetime','version','sigmethod','url','key','secret') { + my $possval = $env{'form.proctoring_'.$provider.'_'.$field}; + if ($field eq 'lifetime') { + if ($possval =~ /^\d+$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'version') { + if ($possval =~ /^\d+\.\d+$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'sigmethod') { + if ($possval =~ /^\QHMAC-SHA\E(1|256)$/) { + $confhash{$provider}{$field} = $possval; + } + } elsif ($field eq 'url') { + $confhash{$provider}{$field} = $possval; + } elsif (($field eq 'key') || ($field eq 'secret')) { + $encconfhash{$provider}{$field} = $possval; + unless ($currentenc{$field} eq $possval) { + $changes{$provider} = 1; + } + } + unless (($field eq 'key') || ($field eq 'secret')) { + unless ($current{$field} eq $confhash{$provider}{$field}) { + $changes{$provider} = 1; + } + } + } + if ($imgdeletions{$provider}) { + $changes{$provider} = 1; + } elsif ($env{'form.proctoring_image_'.$provider.'.filename'} ne '') { + my ($imageurl,$error) = + &process_proctoring_image($r,$dom,$confname,'proctoring_image_'.$provider,$provider, + $configuserok,$switchserver,$author_ok); + if ($imageurl) { + $confhash{$provider}{'image'} = $imageurl; + $changes{$provider} = 1; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } elsif (exists($current{'image'})) { + $confhash{$provider}{'image'} = $current{'image'}; + } + if (ref($requserfields{$provider}) eq 'ARRAY') { + if (@{$requserfields{$provider}} > 0) { + if (grep(/^user$/,@{$requserfields{$provider}})) { + if ($env{'form.proctoring_userincdom_'.$provider}) { + $confhash{$provider}{'incdom'} = 1; + } + unless ($current{'incdom'} eq $confhash{$provider}{'incdom'}) { + $changes{$provider} = 1; + } + } + if (grep(/^roles$/,@{$requserfields{$provider}})) { + $showroles = 1; + } + } + } + $confhash{$provider}{'fields'} = []; + if (ref($optuserfields{$provider}) eq 'ARRAY') { + if (@{$optuserfields{$provider}} > 0) { + my @optfields = &Apache::loncommon::get_env_multiple('form.proctoring_optional_'.$provider); + foreach my $field (@{$optuserfields{$provider}}) { + if (grep(/^\Q$field\E$/,@optfields)) { + push(@{$confhash{$provider}{'fields'}},$field); + } + } + } + if (ref($current{'fields'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'fields'}}); + my @old = sort(@{$current{'fields'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (@{$confhash{$provider}{'fields'}}) { + $changes{$provider} = 1; + } + } + if (ref($defaults{$provider}) eq 'ARRAY') { + if (@{$defaults{$provider}} > 0) { + my %options; + if (ref($extended{$provider}) eq 'HASH') { + %options = %{$extended{$provider}}; + } + my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_defaults_'.$provider); + foreach my $field (@{$defaults{$provider}}) { + if ((exists($options{$field})) && (ref($options{$field}) eq 'ARRAY')) { + my $poss = $env{'form.proctoring_defaults_'.$field.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@{$options{$field}})) { + push(@{$confhash{$provider}{'defaults'}},$poss); + } + } elsif ((exists($options{$field})) && (ref($options{$field}) eq 'HASH')) { + foreach my $inner (keys(%{$options{$field}})) { + if (ref($options{$field}{$inner}) eq 'ARRAY') { + my $poss = $env{'form.proctoring_'.$inner.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@{$options{$field}{$inner}})) { + $confhash{$provider}{'defaults'}{$inner} = $poss; + } + } else { + $confhash{$provider}{'defaults'}{$inner} = $env{'form.proctoring_'.$inner.'_'.$provider}; + } + } + } else { + if (grep(/^\Q$field\E$/,@checked)) { + push(@{$confhash{$provider}{'defaults'}},$field); + } + } + } + if (ref($confhash{$provider}{'defaults'}) eq 'ARRAY') { + if (ref($current{'defaults'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'defaults'}}); + my @old = sort(@{$current{'defaults'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (ref($current{'defaults'}) eq 'ARRAY') { + if (@{$current{'defaults'}}) { + $changes{$provider} = 1; + } + } + } elsif (ref($confhash{$provider}{'defaults'}) eq 'HASH') { + if (ref($current{'defaults'}) eq 'HASH') { + unless ($changes{$provider}) { + foreach my $key (keys(%{$confhash{$provider}{'defaults'}})) { + unless ($confhash{$provider}{'defaults'}{$key} eq $current{'defaults'}{$key}) { + $changes{$provider} = 1; + last; + } + } + } + unless ($changes{$provider}) { + foreach my $key (keys(%{$current{'defaults'}})) { + unless ($current{'defaults'}{$key} eq $confhash{$provider}{'defaults'}{$key}) { + $changes{$provider} = 1; + last; + } + } + } + } elsif (keys(%{$confhash{$provider}{'defaults'}})) { + $changes{$provider} = 1; + } + } + } + } + if (ref($crsconf{$provider}) eq 'ARRAY') { + if (@{$crsconf{$provider}} > 0) { + $confhash{$provider}{'crsconf'} = []; + my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_crsconf_'.$provider); + foreach my $crsfield (@{$crsconf{$provider}}) { + if (grep(/^\Q$crsfield\E$/,@checked)) { + push(@{$confhash{$provider}{'crsconf'}},$crsfield); + } + } + if (ref($current{'crsconf'}) eq 'ARRAY') { + unless ($changes{$provider}) { + my @new = sort(@{$confhash{$provider}{'crsconf'}}); + my @old = sort(@{$current{'crsconf'}}); + my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old); + if (@diffs) { + $changes{$provider} = 1; + } + } + } elsif (@{$confhash{$provider}{'crsconf'}}) { + $changes{$provider} = 1; + } + } + } + if ($showroles) { + $confhash{$provider}{'roles'} = {}; + foreach my $role (@courseroles) { + my $poss = $env{'form.proctoring_roles_'.$role.'_'.$provider}; + if (grep(/^\Q$poss\E$/,@ltiroles)) { + $confhash{$provider}{'roles'}{$role} = $poss; + } + } + unless ($changes{$provider}) { + if (ref($current{'roles'}) eq 'HASH') { + foreach my $role (keys(%{$current{'roles'}})) { + unless ($current{'roles'}{$role} eq $confhash{$provider}{'roles'}{$role}) { + $changes{$provider} = 1; + last + } + } + unless ($changes{$provider}) { + foreach my $role (keys(%{$confhash{$provider}{'roles'}})) { + unless ($confhash{$provider}{'roles'}{$role} eq $current{'roles'}{$role}) { + $changes{$provider} = 1; + last; + } + } + } + } elsif (keys(%{$confhash{$provider}{'roles'}})) { + $changes{$provider} = 1; + } + } + } + if (ref($current{'custom'}) eq 'HASH') { + my @customdels = &Apache::loncommon::get_env_multiple('form.proctoring_customdel_'.$provider); + foreach my $key (keys(%{$current{'custom'}})) { + if (grep(/^\Q$key\E$/,@customdels)) { + $changes{$provider} = 1; + } else { + $confhash{$provider}{'custom'}{$key} = $env{'form.proctoring_customval_'.$key.'_'.$provider}; + if ($confhash{$provider}{'custom'}{$key} ne $current{'custom'}{$key}) { + $changes{$provider} = 1; + } + } + } + } + if ($customadds{$provider}) { + my $name = $env{'form.proctoring_custom_name_'.$provider}; + $name =~ s/(`)/'/g; + $name =~ s/^\s+//; + $name =~ s/\s+$//; + my $value = $env{'form.proctoring_custom_value_'.$provider}; + $value =~ s/(`)/'/g; + $value =~ s/^\s+//; + $value =~ s/\s+$//; + if ($name ne '') { + $confhash{$provider}{'custom'}{$name} = $value; + $changes{$provider} = 1; + } + } + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $provider (@allpos) { + if ($provider ne '') { + $confhash{$provider}{'order'} = $idx; + unless ($changes{$provider}) { + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$provider}) eq 'HASH') { + if ($domconfig{$action}{$provider}{'order'} ne $idx) { + $changes{$provider} = 1; + } + } + } + } + $idx ++; + } + } + } + my %proc_hash = ( + $action => { %confhash } + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%proc_hash, + $dom); + if ($putresult eq 'ok') { + my %proc_enchash = ( + $action => { %encconfhash } + ); + &Apache::lonnet::put_dom('encconfig',\%proc_enchash,$dom,undef,1); + if (keys(%changes) > 0) { + my $cachetime = 24*60*60; + my %procall = %confhash; + foreach my $provider (keys(%procall)) { + if (ref($encconfhash{$provider}) eq 'HASH') { + foreach my $key ('key','secret') { + $procall{$provider}{$key} = $encconfhash{$provider}{$key}; + } + } + } + &Apache::lonnet::do_cache_new('proctoring',$dom,\%procall,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'proctoring'} = 1; + } + $resulttext = &mt('Configuration for Provider(s) with changes:').''; + } 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); @@ -11361,11 +14724,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'}; @@ -11386,15 +14752,15 @@ sub modify_lti { map { $deletions{$_} = 1; } @todelete; } my $maxnum = $env{'form.lti_maxnum'}; - for (my $i=0; $i<=$maxnum; $i++) { + for (my $i=0; $i<$maxnum; $i++) { my $itemid = $env{'form.lti_id_'.$i}; $itemid =~ s/\D+//g; if (ref($domconfig{$action}{$itemid}) eq 'HASH') { if ($deletions{$itemid}) { $changes{$itemid} = $domconfig{$action}{$itemid}{'consumer'}; } else { - push(@items,$i); - $itemids{$i} = $itemid; + push(@items,$i); + $itemids{$i} = $itemid; } } } @@ -11402,12 +14768,12 @@ sub modify_lti { foreach my $idx (@items) { my $itemid = $itemids{$idx}; next unless ($itemid); - my $position = $env{'form.lti_pos_'.$idx}; + my $position = $env{'form.lti_pos_'.$itemid}; $position =~ s/\D+//g; 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') { @@ -11429,127 +14795,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; + } + } } } } @@ -11580,7 +15005,7 @@ sub modify_lti { my %ltienchash = ( $action => { %encconfig } ); - &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom); + &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom,undef,1); if (keys(%changes) > 0) { my $cachetime = 24*60*60; my %ltiall = %confhash; @@ -11606,7 +15031,7 @@ sub modify_lti { if (ref($confhash{$itemid}) ne 'HASH') { $resulttext .= '
  • '.&mt('Deleted: [_1]',$changes{$itemid}).'
  • '; } else { - $resulttext .= '
  • '.$confhash{$itemid}{'consumer'}.'
  • '; if ($changes{'unauth'} || $changes{'auth'}) { @@ -15176,7 +19701,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; @@ -15192,11 +19716,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; } } @@ -15744,6 +20269,333 @@ 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; + } + $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(%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').'
    '. ''; + my $prefix = 'canmodify'; if ($role eq 'emailusername') { unless ($checks{$fields[$i]} =~ /^(required|optional)$/) { $checks{$fields[$i]} = 'omit'; @@ -8552,13 +11224,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 .= ''; } @@ -8660,7 +11337,7 @@ sub insttypes_row { } $output .= ' '; } else { - if (($rem == 0) && (@{$types} > 0)) { + if ($rem == 0) { $output .= '