--- loncom/interface/courseprefs.pm	2022/04/05 12:22:41	1.107
+++ loncom/interface/courseprefs.pm	2022/07/01 01:05:04	1.112
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set configuration settings for a course
 #
-# $Id: courseprefs.pm,v 1.107 2022/04/05 12:22:41 raeburn Exp $
+# $Id: courseprefs.pm,v 1.112 2022/07/01 01:05:04 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -601,10 +601,14 @@ sub handler {
     );
     if (($phase eq 'process') && ($parm_permission->{'process'})) {
         my @allitems = &get_allitems(%prefs);
-        &Apache::lonconfigsettings::make_changes($r,$cdom,$phase,$context,
-                                                 \@prefs_order,\%prefs,\%values,
-                                                  $cnum,undef,\@allitems,
-                                                  'coursepref',$parm_permission);
+        my $result = &Apache::lonconfigsettings::make_changes($r,$cdom,$phase,$context,
+                                                              \@prefs_order,\%prefs,\%values,
+                                                              $cnum,undef,\@allitems,
+                                                              'coursepref',$parm_permission);
+        if ((ref($result) eq 'HASH') && (keys(%{$result}))) {
+            $r->rflush();
+            &devalidate_remote_courseprefs($cdom,$cnum,$result);
+        }
     } elsif (($phase eq 'display') && ($parm_permission->{'display'})) {
         my $noedit;
         if (ref($parm_permission) eq 'HASH') {
@@ -812,7 +816,7 @@ sub print_config_box {
 }
 
 sub process_changes {
-    my ($cdom,$cnum,$action,$values,$item,$changes,$allitems,$disallowed,$crstype) = @_;
+    my ($cdom,$cnum,$action,$values,$item,$changes,$allitems,$disallowed,$crstype,$lastactref) = @_;
     my (%newvalues,$errors);
     if (ref($item) eq 'HASH') {
         if (ref($changes) eq 'HASH') {
@@ -981,7 +985,7 @@ sub process_changes {
                     }
                 } elsif ($action eq 'linkprot') {
                     if (ref($values) eq 'HASH') {
-                        $errors = &process_linkprot($cdom,$cnum,$values->{$action},$changes,'course');
+                        $errors = &process_linkprot($cdom,$cnum,$values->{$action},$changes,'course',$lastactref);
                     }
                 } else {
                     foreach my $entry (@ordered) {
@@ -1477,7 +1481,7 @@ sub process_changes {
 }
 
 sub process_linkprot {
-    my ($cdom,$cnum,$values,$changes,$context) = @_;
+    my ($cdom,$cnum,$values,$changes,$context,$lastactref) = @_;
     my ($home,$dest,$ltiauth,$privkey,$privnum,$cipher,$errors,%linkprot);
     if (ref($values) eq 'HASH') {
         foreach my $id (keys(%{$values})) {
@@ -1544,7 +1548,7 @@ sub process_linkprot {
     if (ref($values) 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++) {
+        for (my $i=0; $i<$maxnum; $i++) {
             my $itemid = $env{'form.linkprot_id_'.$i};
             $itemid =~ s/\D+//g;
             if ($itemid) {
@@ -1589,6 +1593,22 @@ sub process_linkprot {
                 $linkprot{$itemid}{$inner} = $env{$formitem};
             }
         }
+        my $urlitem = 'form.linkprot_returnurl_'.$idx;
+        my $urlparamname = 'form.linkprot_urlparam_'.$idx;
+        if ($env{$urlitem} == 1) {
+            $env{$urlparamname} =~ s/(`)/'/g;
+        } elsif (exists($env{$urlparamname})) {
+            $env{$urlparamname} = '';
+        }
+        unless ($idx eq 'add') {
+            if ((!$current{'returnurl'} && ($env{$urlparamname} ne '')) ||
+                ($current{'returnurl'} && ($env{$urlparamname} eq ''))) {
+                $haschanges{$itemid} = 1;
+            }
+        }
+        if ($env{$urlparamname} ne '') {
+            $linkprot{$itemid}{'returnurl'} = $env{$urlparamname};
+        }
         if ($ltiauth) {
             my $reqitem = 'form.linkprot_requser_'.$idx;
             $env{$reqitem} =~ s/(`)/'/g;
@@ -1673,6 +1693,9 @@ sub process_linkprot {
         foreach my $entry (keys(%haschanges)) {
             $changes->{$entry} = $linkprot{$entry};
         }
+        if (ref($lastactref) eq 'HASH') {
+            $lastactref->{'courselti'} = 1;
+        }
     }
     return $errors;
 }
@@ -2206,6 +2229,9 @@ sub store_linkprot {
                     if (exists($oldlinkprot->{$id}{'usable'})) {
                         $changes->{$id}->{'usable'} = 1;
                     }
+                    if (exists($oldlinkprot->{$id}{'cipher'})) {
+                        $changes->{$id}->{'cipher'} = $oldlinkprot->{$id}{'cipher'};
+                    }
                 }
             }
         }
@@ -2256,7 +2282,7 @@ sub store_linkprot {
                     my %values = %{$changes->{$id}};
                     my %desc = &linkprot_names();
                     my $display;
-                    foreach my $title ('name','lifetime','version','key','secret') {
+                    foreach my $title ('name','lifetime','version','key','secret','returnurl') {
                         if (($title eq 'key') || ($title eq 'secret')) {
                             if (ref($ltienc{$id}) eq 'HASH') {
                                 if (exists($ltienc{$id}{$title})) {
@@ -2272,6 +2298,10 @@ sub store_linkprot {
                             if ($values{$title} eq 'LTI-1p0') {
                                 $display .= $desc{$title}.': 1.1, ';
                             }
+                        } elsif ($title eq 'returnurl') {
+                            if ($values{$title}) {
+                                $display .= &mt('Return URL parameter').': '.$values{$title}.', '; 
+                            }
                         } else {
                             $display .= $desc{$title}.': '.$values{$title}.', ';
                         }
@@ -2792,7 +2822,7 @@ function toggleLinkProt(form,num,item) {
     return;
 }
 
-function toggleLinkProtReqUser(form,item,extra,valon,styleon,num) {
+function toggleLinkProtExtra(form,item,extra,valon,styleon,num) {
     if (document.getElementById('linkprot_'+extra+'_'+num)) {
         var extraid = document.getElementById('linkprot_'+extra+'_'+num);
         var itemname = form.elements['linkprot_'+item+'_'+num];
@@ -5699,10 +5729,10 @@ sub print_linkprotection {
                     '<tr '.$css_class.'><td><span class="LC_nobreak">'.
                     '<label><input type="checkbox" name="linkprot_del" value="'.$i.'"'.$disabled.' />'.
                     &mt('Delete?').'</label></span></td><td>';
-                my ($usersty,$onclickrequser,%checkedrequser);
+                my ($usersty,$onclickrequser,%checkedrequser,$onclickreturnurl,%checkedreturnurl);
                 if ($ltiauth) {
                     $usersty = 'display:none';
-                    $onclickrequser = ' onclick="toggleLinkProtReqUser(this.form,'."'requser','optional','1','block','$i'".');"';
+                    $onclickrequser = ' onclick="toggleLinkProtExtra(this.form,'."'requser','optional','1','block','$i'".');"';
                     %checkedrequser = (
                         no => ' checked="checked"',
                         yes  => '',
@@ -5716,6 +5746,15 @@ sub print_linkprotection {
                         $usersty = 'display:inline-block';
                     }
                 }
+                $onclickreturnurl = ' onclick="toggleLinkProtExtra(this.form,'."'returnurl','divurlparam','1','inline-block','$i'".');"';
+                %checkedreturnurl = (
+                    no => ' checked="checked"',
+                    yes  => '',
+                );
+                if ($values{'returnurl'} ne '') {
+                    $checkedreturnurl{'yes'} = $checkedreturnurl{'no'};
+                    $checkedreturnurl{'no'} = '';
+                }
                 $datatable .=
                     '<span class="LC_nobreak">'.$desc{'name'}.
                     ':<input type="text" size="15" name="linkprot_name_'.$i.'" value="'.$values{'name'}.'" autocomplete="off"'.$disabled.' /></span> '.
@@ -5724,15 +5763,7 @@ sub print_linkprotection {
                     '<option value="LTI-1p0" '.$selected.'>1.1</option></select></span> '."\n".
                     ('&nbsp;'x2).
                     '<span class="LC_nobreak">'.$desc{'lifetime'}.':<input type="text" name="linkprot_lifetime_'.$i.'"'.
-                    ' value="'.$values{'lifetime'}.'" size="3"'.$disabled.' /></span>';
-                if ($ltiauth) {
-                    $datatable .= ('&nbsp;'x2).'<span class="LC_nobreak">'.$desc{'requser'}.'?'.
-                                  '<label><input type="radio" name="linkprot_requser_'.$i.'" value="0"'.
-                                  $onclickrequser.$checkedrequser{'no'}.$disabled.' />'.&mt('No').'</label>&nbsp;'.
-                                  '<label><input type="radio" name="linkprot_requser_'.$i.'" value="1"'.
-                                  $onclickrequser.$checkedrequser{'yes'}.$disabled.' />'.&mt('Yes').'</label></span>';
-                }
-                $datatable .= '<br /><br />';
+                    ' value="'.$values{'lifetime'}.'" size="3"'.$disabled.' /></span><br /><br />';
                 if ($values{'key'} ne '') {
                     $datatable .= '<span class="LC_nobreak">'.$desc{'key'};
                     if ($noedit) {
@@ -5764,6 +5795,7 @@ sub print_linkprotection {
                     } else {
                         $datatable .= '<span class="LC_nobreak">'.&mt('Secret required').' - '.$switchmessage.'</span>'."\n";
                     }
+                    $datatable .= '<input type="hidden" name="linkprot_id_'.$i.'" value="'.$num.'" />';
                 } else {
                     if ($values{'usable'} ne '') {
                         $datatable .= '<div id="linkprot_divcurrsecret_'.$i.'" style="display:inline-block" /><span class="LC_nobreak">'.
@@ -5785,8 +5817,22 @@ sub print_linkprotection {
                             '<input type="hidden" name="linkprot_id_'.$i.'" value="'.$num.'" /></span>';
                     }
                 }
+                $datatable .= '<br /><br />'.
+                              '<span class="LC_nobreak">'.$desc{'returnurl'}.'?'.
+                              '<label><input type="radio" name="linkprot_returnurl_'.$i.'" value="0"'.
+                              $onclickreturnurl.$checkedreturnurl{'no'}.$disabled.' />'.&mt('No').'</label>&nbsp;'.
+                              '<label><input type="radio" name="linkprot_returnurl_'.$i.'" value="1"'.
+                              $onclickreturnurl.$checkedreturnurl{'yes'}.$disabled.' />'.&mt('Yes').'</label></span>'.
+                              '&nbsp;&nbsp;</span><div id="linkprot_divurlparam_'.$i.'" style="display:none" />'.
+                              '<span class="LC_nobreak">'.&mt('Parameter name').':'.
+                              '<input type="text" size="15" name="linkprot_urlparam_'.$i.'" value="'.$values{'returnurl'}.'" autocomplete="off"'.$disabled.' />'.
+                              '</span></div> ';
                 if ($ltiauth) {
-                    $datatable .= 
+                    $datatable .= ('&nbsp;'x2).'<span class="LC_nobreak">'.$desc{'requser'}.'?'.
+                                  '<label><input type="radio" name="linkprot_requser_'.$i.'" value="0"'.
+                                  $onclickrequser.$checkedrequser{'no'}.$disabled.' />'.&mt('No').'</label>&nbsp;'.
+                                  '<label><input type="radio" name="linkprot_requser_'.$i.'" value="1"'.
+                                  $onclickrequser.$checkedrequser{'yes'}.$disabled.' />'.&mt('Yes').'</label></span>'.
                         '</fieldset>'.
                         '<fieldset id="linkprot_optional_'.$i.'" style="'.$usersty.'"><legend>'.$lt{'opti'}.'</legend>'.
                         &linkprot_options($i,$itemcount,$disabled,\%values,\%desc).
@@ -5802,31 +5848,29 @@ sub print_linkprotection {
                   '<input type="hidden" name="linkprot_maxnum" value="'.$next.'" />'."\n".
                   '<input type="checkbox" name="linkprot_add" value="1"'.$disabled.' />'.&mt('Add').'</span></td>'."\n".
                   '<td width="100%">';
-    my ($usersty,$onclickrequser,%checkedrequser);
+    my ($usersty,$onclickrequser,%checkedrequser,$onclickreturnurl,%checkedreturnurl);
     if ($ltiauth) {
         $usersty = 'display:none';
-        $onclickrequser = ' onclick="toggleLinkProtReqUser(this.form,'."'requser','optional','1','block','add'".');"';
+        $onclickrequser = ' onclick="toggleLinkProtExtra(this.form,'."'requser','optional','1','block','add'".');"';
         %checkedrequser = (
             no => ' checked="checked"',
             yes  => '',
         );
         $datatable .= '<fieldset><legend>'.$lt{'requ'}.'</legend>';
     }
+    $onclickreturnurl = ' onclick="toggleLinkProtExtra(this.form,'."'returnurl','divurlparam','1','inline-block','add'".');"';
+    %checkedreturnurl = (
+        no => ' checked="checked"',
+        yes => '',
+    );
     $datatable .= '<span class="LC_nobreak">'.$desc{'name'}.
                   ':<input type="text" size="15" name="linkprot_name_add" value="" autocomplete="off"'.$disabled.' /></span> '."\n".
                   ('&nbsp;'x2).
                   '<span class="LC_nobreak">'.$desc{'version'}.':<select name="linkprot_version_add"'.$disabled.'>'.
                   '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '."\n".
                   ('&nbsp;'x2).
-                  '<span class="LC_nobreak">'.$desc{'lifetime'}.':<input type="text" size="3" name="linkprot_lifetime_add" value="300"'.$disabled.' /></span> '."\n";
-    if ($ltiauth) {
-        $datatable .= ('&nbsp;'x2).'<span class="LC_nobreak">'.$desc{'requser'}.'?'.
-                      '<label><input type="radio" name="linkprot_requser_add" value="0"'.
-                      $onclickrequser.$checkedrequser{'no'}.$disabled.' />'.&mt('No').'</label>&nbsp;'.
-                      '<label><input type="radio" name="linkprot_requser_add" value="1"'.
-                      $onclickrequser.$checkedrequser{'yes'}.$disabled.' />'.&mt('Yes').'</label></span>';
-    }
-    $datatable .= '<br /><br />';
+                  '<span class="LC_nobreak">'.$desc{'lifetime'}.':<input type="text" size="3" name="linkprot_lifetime_add" value="300"'.$disabled.' /></span> '."\n".
+                  '<br /><br />';
     if ($switchserver) {
         $datatable .= '<span class="LC_nobreak">'.&mt('Key and Secret are required').' - '.$switchmessage.'</span>'."\n";
     } else {
@@ -5835,6 +5879,23 @@ sub print_linkprotection {
                       '<span class="LC_nobreak">'.$desc{'secret'}.':<input type="password" size="20" name="linkprot_secret_add" value="" autocomplete="off"'.$disabled.' />'.
                       '<label><input type="checkbox" name="linkprot_visible_add" id="linkprot_visible_add" onclick="if (this.checked) { this.form.linkprot_secret_add.type='."'text'".' } else { this.form.linkprot_secret_add.type='."'password'".' }"'.$disabled.' />'.&mt('Visible input').'</label></span> '."\n";
     }
+    $datatable .= '<br /><br />'.
+                  '<span class="LC_nobreak">'.$desc{'returnurl'}.'?'.
+                  '<label><input type="radio" name="linkprot_returnurl_add" value="0"'.
+                  $onclickreturnurl.$checkedreturnurl{'no'}.$disabled.' />'.&mt('No').'</label>&nbsp;'.
+                  '<label><input type="radio" name="linkprot_returnurl_add" value="1"'.
+                  $onclickreturnurl.$checkedreturnurl{'yes'}.$disabled.' />'.&mt('Yes').'</label></span>'.
+                  '&nbsp;&nbsp;</span><div id="linkprot_divurlparam_add" style="display:none" />'.
+                  '<span class="LC_nobreak">'.&mt('Parameter name').':'.
+                  '<input type="text" size="15" name="linkprot_urlparam_add" value="" autocomplete="off"'.$disabled.' />'.
+                  '</span></div> ';
+    if ($ltiauth) {
+        $datatable .= ('&nbsp;'x2).'<span class="LC_nobreak">'.$desc{'requser'}.'?'.
+                      '<label><input type="radio" name="linkprot_requser_add" value="0"'.
+                      $onclickrequser.$checkedrequser{'no'}.$disabled.' />'.&mt('No').'</label>&nbsp;'.
+                      '<label><input type="radio" name="linkprot_requser_add" value="1"'.
+                      $onclickrequser.$checkedrequser{'yes'}.$disabled.' />'.&mt('Yes').'</label></span>';
+    }
     if ($ltiauth) {
         $datatable .= '</fieldset>'.
                       '<fieldset id="linkprot_optional_add" style="'.$usersty.'"><legend>'.$lt{'opti'}.'</legend>'.
@@ -5853,6 +5914,7 @@ sub linkprot_names {
                                           'lifetime'  => 'Nonce lifetime (s)',
                                           'name'      => 'Launcher Application',
                                           'secret'    => 'Secret',
+                                          'returnurl' => 'Launcher return URL',
                                           'requser'   => 'Use identity',
                                           'email'     => 'Email address',
                                           'sourcedid' => 'User ID',
@@ -5908,7 +5970,7 @@ sub linkprot_options {
             $checked{'auth'} = ' checked="checked"';
         }
     }
-    my $onclickuser = ' onclick="toggleLinkProtReqUser(this.form,'."'mapuser','userfield','other','inline-block','$num'".');"';
+    my $onclickuser = ' onclick="toggleLinkProtExtra(this.form,'."'mapuser','userfield','other','inline-block','$num'".');"';
     my $output = '<div class="LC_floatleft"><span class="LC_nobreak">'.
                  &mt('Source of LON-CAPA username in LTI request').':&nbsp;';
     foreach my $option ('sourcedid','email','other') {
@@ -6551,6 +6613,30 @@ sub change_clone {
             }
         }
     }
+    return;
+}
+
+sub devalidate_remote_courseprefs {
+    my ($cdom,$cnum,$cachekeys) = @_;
+    return unless (ref($cachekeys) eq 'HASH');
+    my %servers = &Apache::lonnet::internet_dom_servers($cdom);
+    my %thismachine;
+    map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids();
+    my @posscached = ('courselti');
+    if (keys(%servers)) {
+        foreach my $server (keys(%servers)) {
+            next if ($thismachine{$server});
+            my @cached;
+            foreach my $name (@posscached) {
+                if ($cachekeys->{$name}) {
+                    push(@cached,&escape($name).':'.&escape($cdom.'_'.$cnum));
+                }
+            }
+            if (@cached) {
+                &Apache::lonnet::remote_devalidate_cache($server,\@cached);
+            }
+        }
+    }
     return;
 }