--- loncom/interface/domainprefs.pm 2023/11/07 11:50:56 1.431 +++ loncom/interface/domainprefs.pm 2024/02/24 23:41:44 1.434 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.431 2023/11/07 11:50:56 raeburn Exp $ +# $Id: domainprefs.pm,v 1.434 2024/02/24 23:41:44 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -669,6 +669,8 @@ sub handler { col2 => 'Settings'}, {col1 => 'Rules for shared secrets', col2 => 'Settings'}, + {col1 => 'Link Protectors in Courses', + col2 => 'Values'}, {col1 => 'Link Protectors', col2 => 'Settings'}, {col1 => 'Consumers', @@ -1037,6 +1039,19 @@ sub print_config_box { $output .= $item->{'print'}->('shared',$dom,$settings,\$rowtotal); } elsif ($action eq 'passwords') { $output .= $item->{'print'}->('middle',$dom,$confname,$settings,\$rowtotal); + } elsif ($action eq 'lti') { + $output .= $item->{'print'}->('upper',$dom,$settings,\$rowtotal).' + + + + + + + + + + '."\n". + $item->{'print'}->('middle',$dom,$settings,\$rowtotal); } else { $output .= $item->{'print'}->('middle',$dom,$settings,\$rowtotal); } @@ -1069,6 +1084,10 @@ sub print_config_box { '. $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); } else { + my $hdridx = 2; + if ($action eq 'lti') { + $hdridx = 3; + } $output .= '
'.&mt($item->{'header'}->[2]->{'col1'}).''.&mt($item->{'header'}->[2]->{'col2'}).'
'.&mt($item->{'header'}->[8]->{'col2'}).'
@@ -1077,8 +1096,8 @@ sub print_config_box { - - + + '."\n"; if ($action eq 'coursecategories') { $output .= &print_coursecategories('bottom',$dom,$item,$settings,\$rowtotal); @@ -1089,6 +1108,7 @@ sub print_config_box { } else { $output .= $item->{'print'}->('lower',$dom,$settings,\$rowtotal); } + $hdridx ++; $output .= '
'.&mt($item->{'header'}->[2]->{'col1'}).''.&mt($item->{'header'}->[2]->{'col2'}).''.&mt($item->{'header'}->[$hdridx]->{'col1'}).''.&mt($item->{'header'}->[$hdridx]->{'col2'}).'
@@ -1098,8 +1118,8 @@ sub print_config_box { - - '."\n"; + + '."\n"; if ($action eq 'passwords') { $output .= $item->{'print'}->('bottom',$dom,$confname,$settings,\$rowtotal); } else { @@ -6176,7 +6196,7 @@ sub print_lti { my ($position,$dom,$settings,$rowtotal) = @_; my $itemcount = 1; my ($datatable,$css_class); - my (%rules,%encrypt,%privkeys,%linkprot); + my (%rules,%encrypt,%privkeys,%linkprot,%suggestions); if (ref($settings) eq 'HASH') { if ($position eq 'top') { if (exists($settings->{'encrypt'})) { @@ -6199,12 +6219,18 @@ sub print_lti { } } } - } elsif ($position eq 'middle') { + } elsif ($position eq 'upper') { if (exists($settings->{'rules'})) { if (ref($settings->{'rules'}) eq 'HASH') { %rules = %{$settings->{'rules'}}; } } + } elsif ($position eq 'middle') { + if (exists($settings->{'suggested'})) { + if (ref($settings->{'suggested'}) eq 'HASH') { + %suggestions = %{$settings->{'suggested'}}; + } + } } elsif ($position eq 'lower') { if (exists($settings->{'linkprot'})) { if (ref($settings->{'linkprot'}) eq 'HASH') { @@ -6215,7 +6241,7 @@ sub print_lti { } } } else { - foreach my $key ('encrypt','private','rules','linkprot') { + foreach my $key ('encrypt','private','rules','linkprot','suggestions') { if (exists($settings->{$key})) { delete($settings->{$key}); } @@ -6224,11 +6250,14 @@ sub print_lti { } if ($position eq 'top') { $datatable = &secrets_form($dom,'ltisec',\%encrypt,\%privkeys,$rowtotal); - } elsif ($position eq 'middle') { + } elsif ($position eq 'upper') { $datatable = &password_rules('ltisecrets',\$itemcount,\%rules); $$rowtotal += $itemcount; + } elsif ($position eq 'middle') { + $datatable = &linkprot_suggestions(\%suggestions,\$itemcount); + $$rowtotal += $itemcount; } elsif ($position eq 'lower') { - $datatable .= &Apache::courseprefs::print_linkprotection($dom,'',$settings,$rowtotal,'','','domain'); + $datatable .= &Apache::courseprefs::print_linkprotection($dom,'',$settings,$rowtotal,'','','domain'); } else { my ($switchserver,$switchmessage); $switchserver = &check_switchserver($dom); @@ -6815,6 +6844,58 @@ sub ltimenu_titles { ); } +sub linkprot_suggestions { + my ($suggested,$itemcount) = @_; + my $count = 0; + my $next = 1; + my %lt = &Apache::lonlocal::texthash( + 'name' => 'Suggested Launcher', + 'info' => 'Recommendations', + ); + my ($datatable,$css_class,$dest); + if (ref($suggested) eq 'HASH') { + my @current = sort { $a <=> $b } keys(%{$suggested}); + $next += $current[-1]; + for (my $i=0; $i<@current; $i++) { + my $num = $current[$i]; + my %values; + if (ref($suggested->{$num}) eq 'HASH') { + %values = %{$suggested->{$num}}; + } else { + next; + } + $css_class = $$itemcount%2?' class="LC_odd_row"':''; + $datatable .= + ''."\n"; + $$itemcount ++; + } + } + $css_class = $$itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''."\n". + ''."\n"; + return $datatable; +} + sub print_coursedefaults { my ($position,$dom,$settings,$rowtotal) = @_; my ($css_class,$datatable,%checkedon,%checkedoff,%defaultchecked,@toggles); @@ -6822,7 +6903,7 @@ sub print_coursedefaults { my %choices = &Apache::lonlocal::texthash ( canuse_pdfforms => 'Course/Community users can create/upload PDF forms', uploadquota => 'Default quota for files uploaded directly to course/community using Course Editor (MB)', - coursequota => 'Default cumulative quota for all group portfolio spaces in course', + coursequota => 'Default cumulative quota for all group portfolio spaces in course (MB)', anonsurvey_threshold => 'Responder count needed before showing submissions for anonymous surveys', coursecredits => 'Credits can be specified for courses', uselcmath => 'Math preview uses LON-CAPA previewer (javascript) in place of DragMath (Java)', @@ -6835,6 +6916,7 @@ sub print_coursedefaults { ltiauth => 'Student username in LTI launch of deep-linked URL can be accepted without re-authentication', domexttool => 'External Tools defined in the domain may be used in courses/communities (by type)', exttool => 'External Tools can be defined and configured in courses/communities (by type)', + crsauthor => 'Standard LON-CAPA problems can be created within a course/community (by type)', ); my %staticdefaults = ( anonsurvey_threshold => 10, @@ -6844,6 +6926,7 @@ sub print_coursedefaults { mysqltables => 172800, domexttool => 1, exttool => 0, + crsauthor => 1, ); if ($position eq 'top') { %defaultchecked = ( @@ -6964,6 +7047,7 @@ sub print_coursedefaults { my $ltiauth = 0; my %domexttool; my %exttool; + my %crsauthor; my @types = ('official','unofficial','community','textbook','placement'); if (ref($settings) eq 'HASH') { if ($settings->{'ltiauth'}) { @@ -6989,6 +7073,19 @@ sub print_coursedefaults { } } } + if (ref($settings->{'crsauthor'}) eq 'HASH') { + foreach my $type (@types) { + if ($settings->{'crsauthor'}->{$type}) { + $crsauthor{$type} = ' checked="checked"'; + } + } + } else { + foreach my $type (@types) { + if ($staticdefaults{'crsauthor'}) { + $crsauthor{$type} = ' checked="checked"'; + } + } + } $currdefresponder = $settings->{'anonsurvey_threshold'}; if (ref($settings->{'uploadquota'}) eq 'HASH') { foreach my $type (keys(%{$settings->{'uploadquota'}})) { @@ -7048,6 +7145,9 @@ sub print_coursedefaults { if ($staticdefaults{'domexttool'}) { $domexttool{$type} = ' checked="checked"'; } + if ($staticdefaults{'crsauthor'}) { + $crsauthor{$type} = ' checked="checked"'; + } } } if (!$currdefresponder) { @@ -7196,6 +7296,21 @@ sub print_coursedefaults { &mt($type).''."\n"; } $datatable .= '
'.&mt($item->{'header'}->[3]->{'col1'}).''.&mt($item->{'header'}->[3]->{'col2'}).'
'.&mt($item->{'header'}->[$hdridx]->{'col1'}).''.&mt($item->{'header'}->[$hdridx]->{'col2'}).'
'."\n". + ''."\n". + '
'.$lt{'name'}.''."\n". + ''."\n". + '
'. + '
'.$lt{'info'}.''."\n". + ''. + '
'. + '
'."\n". + '
'."\n". + ''."\n". + ''.&mt('Add').''."\n". + '
'.$lt{'name'}.''."\n". + ''."\n". + '
'. + '
'.$lt{'info'}.''."\n". + ''. + '
'. + '
'."\n". + '
'."\n"; + $itemcount ++; + $css_class = $itemcount%2 ? ' class="LC_odd_row"' : ''; + $datatable .= ''. + $choices{'crsauthor'}. + ''. + ''. + ''; + foreach my $type (@types) { + $datatable .= ''."\n"; + } + $datatable .= '
'. + ''. + ''. + &mt($type).'
'."\n"; } $$rowtotal += $itemcount; return $datatable; @@ -8335,8 +8450,10 @@ sub print_wafproxy { my $dom_in_effect; my $aliasrows = ''. ''. - &mt('Hostname').': '. - ''.&Apache::lonnet::hostname($server).' '; + &mt('Hostname').': '. + ''. + &Apache::lonnet::hostname($server). + ' '; if ($othercontrol{$server}) { $dom_in_effect = $othercontrol{$server}; my ($current,$forsaml); @@ -14980,7 +15097,6 @@ sub lti_security_results { off => &mt('Encryption of stored external tool secrets defined in domain disabled'), }, ); - } my @types= ('crs','dom'); if ($context eq 'lti') { @@ -15076,6 +15192,28 @@ sub lti_security_results { } } elsif ($item eq 'linkprot') { next; + } elsif ($item eq 'suggested') { + if ((ref($secchanges->{'suggested'}) eq 'HASH') && + (ref($newsec->{'suggested'}) eq 'HASH')) { + my $suggestions; + foreach my $id (sort { $a <=> $b } keys(%{$secchanges->{'suggested'}})) { + if (ref($newsec->{'suggested'}->{$id}) eq 'HASH') { + my $name = $newsec->{'suggested'}->{$id}->{'name'}; + my $info = $newsec->{'suggested'}->{$id}->{'info'}; + $suggestions .= '
  • '.&mt('Launcher: [_1]',$name).'
    '. + &mt('Recommend: [_1]','
    '.$info.'
    '). + '
  • '; + } else { + $suggestions .= '
  • '.&mt('Recommendations deleted for Launcher: [_1]', + $newsec->{'suggested'}->{$id}).'
  • '; + } + } + if ($suggestions) { + $output .= '
  • '.&mt('Hints in Courses for Link Protector Configuration'). + ''. + '
  • '; + } + } } } if ($needs_update) { @@ -15680,7 +15818,7 @@ sub modify_lti { } } if (ref($currltisec{'linkprot'}) eq 'HASH') { - foreach my $id (%{$currltisec{'linkprot'}}) { + foreach my $id (keys(%{$currltisec{'linkprot'}})) { next if ($id !~ /^\d+$/); unless (exists($linkprotchg{$id})) { if (ref($currltisec{'linkprot'}{$id}) eq 'HASH') { @@ -15702,17 +15840,75 @@ sub modify_lti { if ($proterror) { $errors .= '
  • '.$proterror.'
  • '; } + + my (%delsuggested,%suggids,@suggested);; + if (ref($currltisec{'suggested'}) eq 'HASH') { + my $maxnum = $env{'form.linkprot_suggested_maxnum'}; + my @todelete = &Apache::loncommon::get_env_multiple('form.linkprot_suggested_del'); + for (my $i=0; $i<$maxnum; $i++) { + my $itemid = $env{'form.linkprot_suggested_id_'.$i}; + $itemid =~ s/\D+//g; + if ($itemid) { + if (ref($currltisec{'suggested'}->{$itemid}) eq 'HASH') { + push(@suggested,$i); + $suggids{$i} = $itemid; + if ((@todelete > 0) && (grep(/^$i$/,@todelete))) { + if (ref($currltisec{'suggested'}{$itemid}) eq 'HASH') { + $delsuggested{$itemid} = $currltisec{'suggested'}{$itemid}{'name'}; + } + } else { + if ($env{'form.linkprot_suggested_name_'.$i} eq '') { + $delsuggested{$itemid} = $currltisec{'suggested'}{$itemid}{'name'}; + } else { + $env{'form.linkprot_suggested_name_'.$i} =~ s/(`)/'/g; + $env{'form.linkprot_suggested_info_'.$i} =~ s/(`)/'/g; + $newltisec{'suggested'}{$itemid}{'name'} = $env{'form.linkprot_suggested_name_'.$i}; + $newltisec{'suggested'}{$itemid}{'info'} = $env{'form.linkprot_suggested_info_'.$i}; + if (($currltisec{'suggested'}{$itemid}{'name'} ne $newltisec{'suggested'}{$itemid}{'name'}) || + ($currltisec{'suggested'}{$itemid}{'info'} ne $newltisec{'suggested'}{$itemid}{'info'})) { + $secchanges{'suggested'}{$itemid} = 1; + } + } + } + } + } + } + } + foreach my $key (keys(%delsuggested)) { + $newltisec{'suggested'}{$key} = $delsuggested{$key}; + $secchanges{'suggested'}{$key} = 1; + } + if (($env{'form.linkprot_suggested_add'}) && + ($env{'form.linkprot_suggested_name_add'} ne '')) { + $env{'form.linkprot_suggested_name_add'} =~ s/(`)/'/g; + $env{'form.linkprot_suggested_info_add'} =~ s/(`)/'/g; + my ($newsuggid,$errormsg) = &get_lti_id($dom,$env{'form.linkprot_suggested_name_add'},'suggested'); + if ($newsuggid) { + $newltisec{'suggested'}{$newsuggid}{'name'} = $env{'form.linkprot_suggested_name_add'}; + $newltisec{'suggested'}{$newsuggid}{'info'} = $env{'form.linkprot_suggested_info_add'}; + $secchanges{'suggested'}{$newsuggid} = 1; + } else { + my $error = &mt('Failed to acquire unique ID for new Link Protectors in Courses Suggestion'); + if ($errormsg) { + $error .= ' ('.$errormsg.')'; + } + $errors .= '
  • '.$error.'
  • '; + } + } my (@items,%deletions,%itemids); if ($env{'form.lti_add'}) { my $consumer = $env{'form.lti_consumer_add'}; $consumer =~ s/(`)/'/g; - ($newid,my $error) = &get_lti_id($dom,$consumer); + ($newid,my $errormsg) = &get_lti_id($dom,$consumer,'lti'); if ($newid) { $itemids{'add'} = $newid; push(@items,'add'); $changes{$newid} = 1; } else { my $error = &mt('Failed to acquire unique ID for new LTI configuration'); + if ($errormsg) { + $error .= ' ('.$errormsg.')'; + } $errors .= '
  • '.$error.'
  • '; } } @@ -16306,7 +16502,8 @@ sub modify_lti { } $resulttext .= ''; if (ref($lastactref) eq 'HASH') { - if (($secchanges{'encrypt'}) || ($secchanges{'private'})) { + if (($secchanges{'encrypt'}) || ($secchanges{'private'}) || (exists($secchanges{'suggested'}))) { + &Apache::lonnet::get_domain_defaults($dom,1); $lastactref->{'domdefaults'} = 1; } } @@ -16345,23 +16542,26 @@ sub get_priv_creds { } sub get_lti_id { - my ($domain,$consumer) = @_; - # get lock on lti db + my ($domain,$consumer,$dbname) = @_; + unless (($dbname eq 'lti') || ($dbname eq 'suggested')) { + return ('','invalid db'); + } + # get lock on db my $lockhash = { lock => $env{'user.name'}. ':'.$env{'user.domain'}, }; my $tries = 0; - my $gotlock = &Apache::lonnet::newput_dom('lti',$lockhash,$domain); + my $gotlock = &Apache::lonnet::newput_dom($dbname,$lockhash,$domain); my ($id,$error); while (($gotlock ne 'ok') && ($tries<10)) { $tries ++; sleep (0.1); - $gotlock = &Apache::lonnet::newput_dom('lti',$lockhash,$domain); + $gotlock = &Apache::lonnet::newput_dom($dbname,$lockhash,$domain); } if ($gotlock eq 'ok') { - my %currids = &Apache::lonnet::dump_dom('lti',$domain); + my %currids = &Apache::lonnet::dump_dom($dbname,$domain); if ($currids{'lock'}) { delete($currids{'lock'}); if (keys(%currids)) { @@ -16373,14 +16573,14 @@ sub get_lti_id { $id = 1; } if ($id) { - unless (&Apache::lonnet::newput_dom('lti',{ $id => $consumer },$domain) eq 'ok') { + unless (&Apache::lonnet::newput_dom($dbname,{ $id => $consumer },$domain) eq 'ok') { $error = 'nostore'; } } else { $error = 'nonumber'; } } - my $dellockoutcome = &Apache::lonnet::del_dom('lti',['lock'],$domain); + my $dellockoutcome = &Apache::lonnet::del_dom($dbname,['lock'],$domain); } else { $error = 'nolock'; } @@ -21009,6 +21209,7 @@ sub modify_coursedefaults { postsubmit => 60, mysqltables => 172800, domexttool => 1, + crsauthor => 1, ); my %texoptions = ( MathJax => 'MathJax', @@ -21200,9 +21401,10 @@ sub modify_coursedefaults { $changes{'postsubmit'} = 1; } } - my (%newdomexttool,%newexttool,%olddomexttool,%oldexttool); + my (%newdomexttool,%newexttool,%newcrsauthor,%olddomexttool,%oldexttool,%oldcrsauthor); map { $newdomexttool{$_} = 1; } &Apache::loncommon::get_env_multiple('form.domexttool'); map { $newexttool{$_} = 1; } &Apache::loncommon::get_env_multiple('form.exttool'); + map { $newcrsauthor{$_} = 1; } &Apache::loncommon::get_env_multiple('form.crsauthor'); if (ref($domconfig{'coursedefaults'}{'domexttool'}) eq 'HASH') { %olddomexttool = %{$domconfig{'coursedefaults'}{'domexttool'}}; } else { @@ -21225,6 +21427,17 @@ sub modify_coursedefaults { } } } + if (ref($domconfig{'coursedefaults'}{'crsauthor'}) eq 'HASH') { + %oldcrsauthor = %{$domconfig{'coursedefaults'}{'crsauthor'}}; + } else { + foreach my $type (@types) { + if ($staticdefaults{'crsauthor'}) { + $oldcrsauthor{$type} = 1; + } else { + $oldcrsauthor{$type} = 0; + } + } + } foreach my $type (@types) { unless ($newdomexttool{$type}) { $newdomexttool{$type} = 0; @@ -21232,15 +21445,22 @@ sub modify_coursedefaults { unless ($newexttool{$type}) { $newexttool{$type} = 0; } + unless ($newcrsauthor{$type}) { + $newcrsauthor{$type} = 0; + } if ($newdomexttool{$type} != $olddomexttool{$type}) { $changes{'domexttool'} = 1; } if ($newexttool{$type} != $oldexttool{$type}) { $changes{'exttool'} = 1; } + if ($newcrsauthor{$type} != $oldcrsauthor{$type}) { + $changes{'crsauthor'} = 1; + } } $defaultshash{'coursedefaults'}{'domexttool'} = \%newdomexttool; $defaultshash{'coursedefaults'}{'exttool'} = \%newexttool; + $defaultshash{'coursedefaults'}{'crsauthor'} = \%newcrsauthor; } my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash, $dom); @@ -21251,7 +21471,7 @@ sub modify_coursedefaults { ($changes{'coursecredits'}) || ($changes{'uselcmath'}) || ($changes{'usejsme'}) || ($changes{'canclone'}) || ($changes{'mysqltables'}) || ($changes{'texengine'}) || ($changes{'inline_chem'}) || ($changes{'ltiauth'}) || ($changes{'domexttool'}) || - ($changes{'exttool'}) || ($changes{'coursequota'})) { + ($changes{'exttool'}) || ($changes{'coursequota'}) || ($changes{'crsauthor'})) { foreach my $item ('canuse_pdfforms','uselcmath','usejsme','inline_chem','texengine', 'ltiauth') { if ($changes{$item}) { @@ -21317,6 +21537,13 @@ sub modify_coursedefaults { } } } + if ($changes{'crsauthor'}) { + if (ref($defaultshash{'coursedefaults'}{'crsauthor'}) eq 'HASH') { + foreach my $type (@types) { + $domdefaults{$type.'crsauthor'}=$defaultshash{'coursedefaults'}{'crsauthor'}{$type}; + } + } + } my $cachetime = 24*60*60; &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); if (ref($lastactref) eq 'HASH') { @@ -21467,33 +21694,34 @@ sub modify_coursedefaults { } else { $resulttext .= '
  • '.&mt('LTI launch of deep-linked URL will require re-authentication').'
  • '; } - } elsif ($item eq 'domexttool') { + } elsif (($item eq 'domexttool') || ($item eq 'exttool') || ($item eq 'crsauthor')) { my @noyes = (&mt('no'),&mt('yes')); - if (ref($defaultshash{'coursedefaults'}{'domexttool'}) eq 'HASH') { - $resulttext .= '
  • '.&mt('External Tools defined in the domain may be used as follows:').''. - '
  • '; - } else { - $resulttext .= '
  • '.&mt('External Tools defined in the domain may be used in all course types, by default').'
  • '; - } - } elsif ($item eq 'exttool') { - my @noyes = (&mt('no'),&mt('yes')); - if (ref($defaultshash{'coursedefaults'}{'exttool'}) eq 'HASH') { - $resulttext .= '
  • '.&mt('External Tools can be defined and configured in course containers as follows:').'