--- loncom/interface/courseprefs.pm 2020/10/29 23:24:13 1.91
+++ loncom/interface/courseprefs.pm 2022/01/16 16:52:42 1.97
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Handler to set configuration settings for a course
#
-# $Id: courseprefs.pm,v 1.91 2020/10/29 23:24:13 raeburn Exp $
+# $Id: courseprefs.pm,v 1.97 2022/01/16 16:52:42 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -365,9 +365,15 @@ sub handler {
}
my %values=&Apache::lonnet::dump('environment',$cdom,$cnum);
+ my %courselti=&Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1);
+ if ($courselti{'lock'}) {
+ delete($courselti{'lock'});
+ }
+ $values{'linkprotection'} = \%courselti;
my @prefs_order = ('courseinfo','localization','feedback','discussion',
'classlists','appearance','grading','printouts',
- 'menuitems','spreadsheet','bridgetasks','lti','other');
+ 'menuitems','linkprotection','spreadsheet','bridgetasks',
+ 'lti','other');
my %prefs = (
'courseinfo' =>
@@ -467,7 +473,7 @@ sub handler {
help => 'Course_Prefs_Display',
ordered => ['default_xml_style','pageseparators',
'disable_receipt_display','texengine',
- 'tthoptions','uselcmath','usejsme'],
+ 'tthoptions','uselcmath','usejsme','inline_chem'],
itemtext => {
default_xml_style => 'Default XML style file',
pageseparators => 'Visibly Separate Items on Pages',
@@ -476,6 +482,7 @@ sub handler {
tthoptions => 'Default set of options to pass to tth/m when converting TeX',
uselcmath => 'Student formula entry uses inline preview, not DragMath pop-up',
usejsme => 'Molecule editor uses JSME (HTML5) in place of JME (Java)',
+ inline_chem => 'Chemical reaction response uses inline preview, not pop-up',
},
},
'grading' =>
@@ -557,6 +564,14 @@ sub handler {
menucollections => 'Menu collections',
},
},
+ 'linkprotection' =>
+ {
+ text => 'Link protection',
+ help => 'Course_Prefs_Linkprotection',
+ header => [{col1 => 'Item',
+ col2 => 'Settings',
+ }],
+ },
'other' =>
{ text => 'Other settings',
help => 'Course_Prefs_Other',
@@ -764,6 +779,8 @@ sub print_config_box {
$output .= &print_lti($cdom,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit);
} elsif ($action eq 'menuitems') {
$output .= &print_menuitems('bottom',$cdom,$settings,$itemtext,\$rowtotal,$crstype,$noedit);
+ } elsif ($action eq 'linkprotection') {
+ $output .= &print_linkprotection($cdom,$settings,\$rowtotal,$crstype,$noedit);
} elsif ($action eq 'other') {
$output .= &print_other($cdom,$settings,$allitems,\$rowtotal,$crstype,$noedit);
}
@@ -776,8 +793,8 @@ sub print_config_box {
}
sub process_changes {
- my ($cdom,$action,$values,$item,$changes,$allitems,$disallowed,$crstype) = @_;
- my %newvalues;
+ my ($cdom,$cnum,$action,$values,$item,$changes,$allitems,$disallowed,$crstype) = @_;
+ my (%newvalues,%courselti,$errors);
if (ref($item) eq 'HASH') {
if (ref($changes) eq 'HASH') {
my @ordered;
@@ -794,6 +811,21 @@ sub process_changes {
}
}
}
+ } elsif ($action eq 'linkprotection') {
+ if (ref($values->{'linkprotection'}) eq 'HASH') {
+ foreach my $id (keys(%{$values->{'linkprotection'}})) {
+ if ($id =~ /^\d+$/) {
+ push(@ordered,$id);
+ unless (ref($values->{'linkprotection'}->{$id}) eq 'HASH') {
+ $courselti{$id} = '';
+ }
+ }
+ }
+ }
+ @ordered = sort { $a <=> $b } @ordered;
+ if (($env{'form.linkprot_add'}) && ($env{'form.linkprot_maxnum'} =~ /^\d+$/)) {
+ push(@ordered,$env{'form.linkprot_maxnum'});
+ }
} elsif (ref($item->{'ordered'}) eq 'ARRAY') {
if ($action eq 'courseinfo') {
my ($can_toggle_cat,$can_categorize) =
@@ -884,10 +916,10 @@ sub process_changes {
my $currdef = $values->{'menudefault'};
my $possdef = $env{'form.menudefault'};
if (($possdef =~ /^\d+$/) && (grep(/^$possdef$/,@colls))) {
- if ($values->{'menudefault'} ne $possdef) {
+ if ($currdef ne $possdef) {
$changes->{'menudefault'} = $possdef;
}
- } elsif ($values->{'menudefault'}) {
+ } elsif ($currdef) {
$changes->{'menudefault'} = '';
}
my $menucoll;
@@ -931,6 +963,78 @@ sub process_changes {
} elsif ($values->{'menucollections'}) {
$changes->{'menucollections'} = '';
}
+ } elsif ($action eq 'linkprotection') {
+ my %menutitles = <imenu_titles();
+ my (@items,%deletions,%itemids,%haschanges);
+ if ($env{'form.linkprot_add'}) {
+ my $name = $env{'form.linkprot_name_add'};
+ $name =~ s/(`)/'/g;
+ my ($newid,$error) = &get_courselti_id($cdom,$cnum,$name);
+ if ($newid) {
+ $itemids{'add'} = $newid;
+ push(@items,'add');
+ $haschanges{$newid} = 1;
+ } else {
+ $errors .= ''.
+ &mt('Failed to acquire unique ID for link protection').
+ '';
+ }
+ }
+ if (ref($values->{'linkprotection'}) eq 'HASH') {
+ my @todelete = &Apache::loncommon::get_env_multiple('form.linkprot_del');
+ my $maxnum = $env{'form.linkprot_maxnum'};
+ for (my $i=0; $i<=$maxnum; $i++) {
+ my $itemid = $env{'form.linkprot_id_'.$i};
+ $itemid =~ s/\D+//g;
+ if ($itemid) {
+ if (ref($values->{'linkprotection'}->{$itemid}) eq 'HASH') {
+ push(@items,$i);
+ $itemids{$i} = $itemid;
+ if ((@todelete > 0) && (grep(/^$i$/,@todelete))) {
+ $deletions{$itemid} = $values->{'linkprotection'}->{$itemid}->{'name'};
+ }
+ }
+ }
+ }
+ }
+
+ foreach my $idx (@items) {
+ my $itemid = $itemids{$idx};
+ next unless ($itemid);
+ if (exists($deletions{$itemid})) {
+ $courselti{$itemid} = $deletions{$itemid};
+ $haschanges{$itemid} = 1;
+ next;
+ }
+ my %current;
+ if (ref($values->{'linkprotection'}) eq 'HASH') {
+ if (ref($values->{'linkprotection'}->{$itemid}) eq 'HASH') {
+ foreach my $key (keys(%{$values->{'linkprotection'}->{$itemid}})) {
+ $current{$key} = $values->{'linkprotection'}->{$itemid}->{$key};
+ }
+ }
+ }
+ foreach my $inner ('name','key','secret','lifetime','version') {
+ my $formitem = 'form.linkprot_'.$inner.'_'.$idx;
+ $env{$formitem} =~ s/(`)/'/g;
+ if ($inner eq 'lifetime') {
+ $env{$formitem} =~ s/[^\d.]//g;
+ }
+ unless ($idx eq 'add') {
+ if ($current{$inner} ne $env{$formitem}) {
+ $haschanges{$itemid} = 1;
+ }
+ }
+ if ($env{$formitem} ne '') {
+ $courselti{$itemid}{$inner} = $env{$formitem};
+ }
+ }
+ }
+ if (keys(%haschanges)) {
+ foreach my $entry (keys(%haschanges)) {
+ $changes->{$entry} = $courselti{$entry};
+ }
+ }
} else {
foreach my $entry (@ordered) {
if ($entry eq 'cloners') {
@@ -971,7 +1075,7 @@ sub process_changes {
my $clonedom = $env{'form.cloners_newdom'};
if (&check_clone($clonedom,$disallowed) eq 'ok') {
my $newdom = '*:'.$env{'form.cloners_newdom'};
- if (@clonedoms) {
+ if (@clonedoms) {
if (!grep(/^\Q$newdom\E$/,@clonedoms)) {
$newvalues{$entry} .= ','.$newdom;
}
@@ -1060,7 +1164,9 @@ sub process_changes {
$autocoowner = $domconf{'autoenroll'}{'co-owners'};
}
}
- unless ($autocoowner) {
+ if ($autocoowner) {
+ $newvalues{'co-owners'} = $values->{'internal.co-owners'};
+ } else {
my @keepcoowners = &Apache::loncommon::get_env_multiple('form.coowners');
my @pendingcoowners = &Apache::loncommon::get_env_multiple('form.pendingcoowners');
my @invitecoowners = &Apache::loncommon::get_env_multiple('form.invitecoowners');
@@ -1105,6 +1211,8 @@ sub process_changes {
if ($pendingcoowners ne '') {
@newpending = @pendingcoown;
}
+ } else {
+ @newcoown = @currcoown;
}
$newvalues{'pendingco-owners'} = join(',',sort(@newpending));
$newvalues{'co-owners'} = join(',',sort(@newcoown));
@@ -1417,7 +1525,51 @@ sub process_changes {
}
}
}
- return;
+ return $errors;
+}
+
+sub get_courselti_id {
+ my ($cdom,$cnum,$name) = @_;
+ # get lock on lti db in course
+ my $lockhash = {
+ lock => $env{'user.name'}.
+ ':'.$env{'user.domain'},
+ };
+ my $tries = 0;
+ my $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum);
+ my ($id,$error);
+ while (($gotlock ne 'ok') && ($tries<10)) {
+ $tries ++;
+ sleep (0.1);
+ $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum);
+ }
+ if ($gotlock eq 'ok') {
+ my %currids = &Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1);
+ if ($currids{'lock'}) {
+ delete($currids{'lock'});
+ if (keys(%currids)) {
+ my @curr = sort { $a <=> $b } keys(%currids);
+ if ($curr[-1] =~ /^\d+$/) {
+ $id = 1 + $curr[-1];
+ } else {
+ $id = 1;
+ }
+ } else {
+ $id = 1;
+ }
+ if ($id) {
+ unless (&Apache::lonnet::newput('lti',{ $id => $name },$cdom,$cnum) eq 'ok') {
+ $error = 'nostore';
+ }
+ } else {
+ $error = 'nonumber';
+ }
+ }
+ my $dellockoutcome = &Apache::lonnet::del('lti',['lock'],$cdom,$cnum);
+ } else {
+ $error = 'nolock';
+ }
+ return ($id,$error);
}
sub get_sec_str {
@@ -1462,8 +1614,12 @@ sub check_clone {
sub store_changes {
my ($cdom,$cnum,$prefs_order,$actions,$prefs,$values,$changes,$crstype) = @_;
my ($chome,$output);
- my (%storehash,@delkeys,@need_env_update,@oldcloner);
+ my (%storehash,@delkeys,@need_env_update,@oldcloner,%oldlinkprot);
if ((ref($values) eq 'HASH') && (ref($changes) eq 'HASH')) {
+ if (ref($values->{'linkprotection'}) eq 'HASH') {
+ %oldlinkprot = %{$values->{'linkprotection'}};
+ }
+ delete($values->{'linkprotection'});
%storehash = %{$values};
} else {
if ($crstype eq 'Community') {
@@ -1473,6 +1629,20 @@ sub store_changes {
}
return $output;
}
+ my ($numchanges,$skipstore);
+ if (ref($changes) eq 'HASH') {
+ $numchanges = scalar(keys(%{$changes}));
+ if (($numchanges == 1) && (exists($changes->{'linkprotection'}))) {
+ $skipstore = 1;
+ } elsif (!$numchanges) {
+ if ($crstype eq 'Community') {
+ $output = &mt('No changes made to community settings.');
+ } else {
+ $output = &mt('No changes made to course settings.');
+ }
+ return $output;
+ }
+ }
my %yesno = (
hidefromcat => '1',
problem_stream_switch => '1',
@@ -1485,7 +1655,7 @@ sub store_changes {
if (grep(/^\Q$item\E$/,@{$actions})) {
$output .= '
'.&mt($prefs->{$item}{'text'}).'
';
if (ref($changes->{$item}) eq 'HASH') {
- if (keys(%{$changes->{$item}}) > 0) {
+ if ((keys(%{$changes->{$item}}) > 0) || ($item eq 'linkprotection')) {
$output .= &mt('Changes made:').'';
if ($item eq 'other') {
foreach my $key (sort(keys(%{$changes->{$item}}))) {
@@ -1498,6 +1668,41 @@ sub store_changes {
"'$storehash{$key}'")).'';
}
}
+ } elsif ($item eq 'linkprotection') {
+ if (&Apache::lonnet::put('lti',$changes->{'linkprotection'},$cdom,$cnum,1) eq 'ok') {
+ my $hashid=$cdom.'_'.$cnum;
+ &Apache::lonnet::devalidate_cache_new('courselti',$hashid);
+ foreach my $itemid (sort { $a <=> $b } %{$changes->{'linkprotection'}}) {
+ if (ref($changes->{'linkprotection'}->{$itemid}) eq 'HASH') {
+ my %values = %{$changes->{'linkprotection'}->{$itemid}};
+ my %desc = &linkprot_names();
+ my $display;
+ foreach my $title ('name','lifetime','version','key','secret') {
+ if ($title eq 'secret') {
+ my $length = length($values{$title});
+ $display .= $desc{$title}.': '.('*' x $length);
+ } elsif ($title eq 'version') {
+ if ($values{$title} eq 'LTI-1p0') {
+ $display .= $desc{$title}.': 1.1, ';
+ }
+ } else {
+ $display .= $desc{$title}.': '.$values{$title}.', ';
+ }
+ }
+ $output .= '- '.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]',''.$itemid.'',
+ "'$display'")).'
';
+ } elsif (ref($oldlinkprot{$itemid}) eq 'HASH') {
+ my $oldname = $oldlinkprot{$itemid}{'name'};
+ $output .= '- '.&Apache::lonhtmlcommon::confirm_success(&mt('Deleted setting for [_1]',''."$itemid ($oldname)".'')).'
';
+ }
+ }
+ } else {
+ $output .= '- '.
+ ''.
+ &mt('An error occurred when saving changes to link protection settings, which remain unchanged.').
+ ''.
+ '
';
+ }
} else {
if (ref($prefs->{$item}->{'ordered'}) eq 'ARRAY') {
my @settings = @{$prefs->{$item}->{'ordered'}};
@@ -1652,7 +1857,8 @@ sub store_changes {
$displayval = &Apache::lonlocal::locallocaltime($displayval);
} elsif ($key eq 'categories') {
$displayval = $env{'form.categories_display'};
- } elsif (($key eq 'canuse_pdfforms') || ($key eq 'usejsme') || ($key eq 'uselcmath')) {
+ } elsif (($key eq 'canuse_pdfforms') || ($key eq 'usejsme') ||
+ ($key eq 'uselcmath') || ($key eq 'inline_chem')) {
if ($changes->{$item}{$key} eq '1') {
$displayval = &mt('Yes');
} elsif ($changes->{$item}{$key} eq '0') {
@@ -1785,6 +1991,9 @@ sub store_changes {
}
}
}
+ if ($skipstore) {
+ return $output;
+ }
if (&Apache::lonnet::put('environment',\%storehash,$cdom,$cnum) eq 'ok') {
if (ref($changes) eq 'HASH') {
if (ref($changes->{'courseinfo'}) eq 'HASH') {
@@ -3601,7 +3810,7 @@ sub coowner_invitations {
@pendingcoown = split(',',$pendingcoowners);
}
if (ref($currcoownref) eq 'ARRAY') {
- @currcoown == @{$currcoownref};
+ @currcoown = @{$currcoownref};
}
my $disabled;
if ($noedit) {
@@ -3665,7 +3874,7 @@ sub manage_coownership {
@pendingcoown = split(',',$pendingcoowners);
}
if (ref($currcoownref) eq 'ARRAY') {
- @currcoown == @{$currcoownref};
+ @currcoown = @{$currcoownref};
}
my $disabled;
if ($noedit) {
@@ -3873,8 +4082,8 @@ sub print_feedback {
}
if ($position eq 'top') {
my $includeempty = 0;
- $datatable .= ''.
- &user_table($cdom,$item,\@sections,
+ $datatable .= ' | '.
+ &user_table($cdom,$item,\@sections,
$settings->{$item},\%lt,$noedit);
} else {
$datatable .= &Apache::lonhtmlcommon::textbox($item.'.text',
@@ -4097,8 +4306,8 @@ sub print_discussion {
''.&role_checkboxes($cdom,$cnum,$item,$settings,undef,undef,$noedit).
' ';
} elsif ($item eq 'plc.users.denied') {
- $datatable .= ' | '.
- &user_table($cdom,$item,undef,
+ $datatable .= ' | '.
+ &user_table($cdom,$item,undef,
$settings->{$item},\%lt,$noedit);
} elsif ($item eq 'pch.roles.denied') {
$datatable .= ' | '.
@@ -4468,6 +4677,10 @@ sub print_appearance {
text => ''.&mt($itemtext->{'usejsme'}).'',
input => 'radio',
},
+ 'inline_chem' => {
+ text => ''.&mt($itemtext->{'inline_chem'}).'',
+ input => 'radio',
+ },
);
return &make_item_rows($cdom,\%items,$ordered,$settings,$rowtotal,$crstype,'appearance',$noedit);
}
@@ -4861,7 +5074,7 @@ sub print_menuitems {
my %checked;
my $on = ' checked="checked"';
foreach my $key (keys(%{$menu{$num}})) {
- if (($key eq 'top') || ($key eq 'inline') || ($key eq 'main')) {
+ if (($key eq 'top') || ($key eq 'inline') || ($key eq 'foot') || ($key eq 'main')) {
if ($menu{$num}{$key} eq 'y') {
$checked{$key} = $on;
}
@@ -4878,7 +5091,13 @@ sub print_menuitems {
foreach my $category (@order) {
if ((ref($categories{$category}) eq 'ARRAY') && (@{$categories{$category}} > 0)) {
$datatable .= ' |