--- loncom/interface/courseprefs.pm	2022/04/05 12:22:41	1.107
+++ loncom/interface/courseprefs.pm	2022/10/18 23:28:00	1.116
@@ -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.116 2022/10/18 23:28:00 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -116,7 +116,7 @@ This module is used for configuration of
 
 =item item_table_row_end()
 
-=item yes_no_radio()
+=item yesno_radio()
 
 =item select_from_options()
 
@@ -293,7 +293,7 @@ sub handler {
                 excc => 'Exclude from community catalog',
                 clon => 'Users allowed to clone community',
                 rept => 'Replacement titles for standard community roles',
-                time => 'Timezone where the community is located',
+                time => 'Time Zone where the community is located',
                 date => 'Locale used for community calendar',
                 coco => 'Community Content',
                 copo => 'Community Policy',
@@ -322,7 +322,7 @@ sub handler {
                 excc => 'Exclude from course catalog',
                 clon => 'Users allowed to clone course',
                 rept => 'Replacement titles for standard course roles',
-                time => 'Timezone in which the course takes place',
+                time => 'Time Zone in which the course takes place',
                 date => 'Locale used for course calendar',
                 coco => 'Course Content',
                 copo => 'Course Policy',
@@ -492,7 +492,8 @@ sub handler {
                      help => 'Course_Prefs_Display',
                      ordered => ['default_xml_style','pageseparators',
                                  'disable_receipt_display','texengine',
-                                 'tthoptions','uselcmath','usejsme','inline_chem'],
+                                 'tthoptions','uselcmath','usejsme',
+                                 'inline_chem','extresource'],
                       itemtext => {
                           default_xml_style       => 'Default XML style file',
                           pageseparators          => 'Visibly Separate Items on Pages',
@@ -502,6 +503,7 @@ sub handler {
                           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',
+                          extresource             => 'Display of external resources',   
                                   },
                   },
         'grading' =>
@@ -601,10 +603,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 +818,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 +987,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) {
@@ -1460,10 +1466,54 @@ sub process_changes {
                                     $newvalues{$entry} = '';
                                 }
                             }
+                        } elsif ($entry eq 'extresource') {
+                            if ($env{'form.'.$entry} =~ /^iframe|tab|window$/) {
+                                $newvalues{$entry} = $env{'form.'.$entry};
+                                if ($env{'form.'.$entry} ne 'iframe') {
+                                    if ($env{'form.extwintabreuse'}) {
+                                        $newvalues{$entry} .= ':1';
+                                    } else {
+                                        $newvalues{$entry} .= ':0';
+                                    }
+                                    if ($env{'form.'.$entry} eq 'window') {
+                                        foreach my $dim ('width','height') {
+                                            $env{'form.extreswin'.$dim} =~ s/^\s+|\s+$//g;
+                                            if ($env{'form.extreswin'.$dim} =~ /^\d+$/) {
+                                                $newvalues{$entry} .= ':'.$env{'form.extreswin'.$dim};
+                                            } else {
+                                                $newvalues{$entry} .= ':';
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                            unless (($newvalues{$entry} eq 'iframe') && ($values->{$entry} eq '')) {
+                                if ($newvalues{$entry} ne $values->{$entry}) {
+                                    $changes->{$entry} = $newvalues{$entry};
+                                }
+                            }
+                        } elsif ($entry eq 'timezone') {
+                            if ($env{'form.'.$entry}) {
+                                $newvalues{$entry} = $env{'form.'.$entry};
+                                if ($newvalues{$entry} ne $values->{$entry}) {
+                                    $changes->{$entry} = $newvalues{$entry};
+                                }
+                                if ($env{'form.tzover'}) {
+                                    $newvalues{'tzover'} = $env{'form.tzover'};
+                                    if ($newvalues{'tzover'} ne $values->{'tzover'}) {
+                                        $changes->{'tzover'} = $newvalues{'tzover'};
+                                    }
+                                } elsif ($values->{'tzover'}) {
+                                    $changes->{'tzover'} = '';
+                                }
+                            } elsif ($values->{$entry}) {
+                                $changes->{$entry} = '';
+                            }
                         } else {
                             $newvalues{$entry} = $env{'form.'.$entry};
                         }
-                        unless (($entry eq 'co-owners') || ($entry eq 'discussion_post_fonts')) {
+                        unless (($entry eq 'co-owners') || ($entry eq 'discussion_post_fonts') || 
+                                ($entry eq 'extresource') || ($entry eq 'timezone')) {
                             if ($newvalues{$entry} ne $values->{$entry}) {
                                 $changes->{$entry} = $newvalues{$entry};
                             }
@@ -1477,7 +1527,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 +1594,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 +1639,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 +1739,9 @@ sub process_linkprot {
         foreach my $entry (keys(%haschanges)) {
             $changes->{$entry} = $linkprot{$entry};
         }
+        if (ref($lastactref) eq 'HASH') {
+            $lastactref->{'courselti'} = 1;
+        }
     }
     return $errors;
 }
@@ -1875,12 +1944,52 @@ sub store_changes {
                                     if ($msg ne '') {
                                         $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt($displayname)).'<ul class="LC_success">'.$msg.'</ul></li>';
                                     }
+                                } elsif ($key eq 'timezone') {
+                                    next unless ((exists($changes->{$item}{$key})) || (exists($changes->{$item}{'tzover'})));
+                                    my ($displayname,$text);
+                                    $text = $prefs->{$item}->{'itemtext'}{$key};
+                                    my $displayval;
+                                    if (exists($changes->{$item}{$key})) {
+                                        $displayname = &mt($text);
+                                        $storehash{$key} = $changes->{$item}{$key};
+                                        if ($changes->{$item}{$key} ne '') {
+                                            $displayval = '<b>'.$changes->{$item}{$key}.'</b>';
+                                        } else {
+                                            push(@delkeys,$key);
+                                            if (exists($values->{'tzover'})) {
+                                                push(@delkeys,'tzover');
+                                            }
+                                            $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('Deleted setting for [_1]',
+                                                                                                      '<i>'.$displayname.'</i>')).'</li>';
+                                        }
+                                    }
+                                    unless (grep(/^\Q$key\E$/,@delkeys)) {
+                                        if (exists($changes->{$item}{'tzover'})) {
+                                            $storehash{'tzover'} = $changes->{$item}{'tzover'};
+                                            my $tzovertext;
+                                            if ($changes->{$item}{'tzover'} ne '') {
+                                                $tzovertext = &mt('Course Time Zone overrides individual user preference');
+                                            } else {
+                                                push(@delkeys,'tzover');
+                                                $tzovertext = &mt('Course Time Zone does not override individual user preference');
+                                            }
+                                            if ($displayval eq '') {
+                                                $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success($tzovertext).'</li>';
+                                            } else {
+                                                $displayval .= '<br />'.('&nbsp;'x5).$tzovertext;
+                                            }
+                                        }
+                                        if ($displayval ne '') {
+                                            $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]',
+                                                              '<i>'.$displayname.'</i>',$displayval)).'</li>';
+                                        }
+                                    }
                                 } else {
                                     next if (!exists($changes->{$item}{$key}));
                                     my ($displayname,$text);
                                     $text = $prefs->{$item}->{'itemtext'}{$key};
                                     my $displayval;
-                                    unless (($key eq 'co-owners') || ($key eq 'discussion_post_fonts')) {
+                                    unless (($key eq 'co-owners') || ($key eq 'discussion_post_fonts') || ($key eq 'extresource')) {
                                         $displayval = $changes->{$item}{$key};
                                     }
                                     if ($item eq 'feedback') {
@@ -2005,6 +2114,37 @@ sub store_changes {
                                         } elsif ($changes->{$item}{$key} eq '0') {
                                             $displayval = &mt('No');
                                         }
+                                    } elsif ($key eq 'extresource') {
+                                        if ($changes->{$item}{$key} eq 'iframe') {
+                                            $displayval = &mt('In iframe');
+                                        } else {
+                                            my ($selected,$reuse,$width,$height) = split(/:/,$changes->{$item}{$key});
+                                            if ($selected eq 'tab') {
+                                                if ($reuse) {
+                                                    $displayval = &mt('[_1]In tab[_2],[_3] and tab re-used for different external resources in course',
+                                                                      "'<b>","</b>'",'<br />');
+                                                } else {
+                                                    $displayval = &mt('[_1]In tab[_2],[_3] with new tab for each external resource in course',
+                                                                      "'<b>","</b>'",'<br />');
+                                                }
+                                            } elsif ($selected eq 'window') {
+                                                 if ($reuse) {
+                                                     $displayval = &mt('[_1]In pop-up window[_2],[_3] and window re-used for different external resources in course',
+                                                                       "'<b>","</b>'",'<br />');
+                                                 } else {
+                                                     $displayval = &mt('[_1]In pop-up window[_2],[_3] with new window for each external resource in course',
+                                                                       "'<b>","</b>'",'<br />');
+                                                 }
+                                                 if (($width ne '') || ($height ne '')) {
+                                                     if ($width ne '') {
+                                                         $displayval .= '<br />'.&mt('Window width: [_1]px',$width);
+                                                     }
+                                                     if ($height ne '') {
+                                                         $displayval .= '<br />'.&mt('Window height: [_1]px',$height);
+                                                     }
+                                                 }
+                                            }
+                                        }
                                     }
                                     if ($key eq 'co-owners') {
                                         if (ref($changes->{$item}{$key}) eq 'HASH') {
@@ -2076,9 +2216,11 @@ sub store_changes {
                                         $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('Numbered menu collections:')).'<br />'.
                                                    $displayval.'</li>';
                                     } else {
+                                        unless (($key eq 'extresource') && ($changes->{$item}{$key} ne 'iframe')) {
+                                            $displayval = "'<b>$displayval</b>'";
+                                        }
                                         $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]',
-                                                   '<i>'.$displayname.'</i>',
-                                                   "'<b>$displayval</b>'"));
+                                                   '<i>'.$displayname.'</i>',$displayval));
                                         if ($key eq 'url') {
                                             my $bkuptime=time;
                                             $output .= ('&nbsp;'x2).&mt('(Previous URL backed up)').': '.
@@ -2206,6 +2348,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 +2401,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 +2417,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}.', ';
                         }
@@ -2460,7 +2609,7 @@ sub get_course {
 sub get_jscript {
     my ($cid,$cdom,$phase,$crstype,$settings,$noedit) = @_;
     my ($can_toggle_cat,$can_categorize) = &can_modify_catsettings($cdom,$crstype);
-    my ($jscript,$categorize_js,$loncaparev_js,$instcode_js);
+    my ($jscript,$categorize_js,$loncaparev_js,$instcode_js,$extresource_js,$localization_js);
     my $stubrowse_js = &Apache::loncommon::studentbrowser_javascript();
     my $browse_js = &Apache::loncommon::browser_and_searcher_javascript('parmset');
     my $cloners_js = &cloners_javascript($phase);
@@ -2667,11 +2816,53 @@ function toggleAddmenucoll() {
 }
 ENDSCRIPT
     }
+    $extresource_js = <<"ENDSCRIPT";
+function toggleExtRes() {
+    if (document.getElementById('LC_extresource')) {
+        var extressel = document.getElementById('LC_extresource').value;
+        if (document.getElementById('LC_extresreusediv')) {
+            var extresreuse = document.getElementById('LC_extresreusediv');
+            if (document.getElementById('LC_extressize')) {
+                var extressize = document.getElementById('LC_extressize');
+                var setvis;
+                if ((extressel == 'tab') || (extressel == 'window')) {
+                    extresreuse.style.display = 'inline-block';
+                    setvis = 1;
+                    if (extressel == 'window') {
+                        extressize.style.display = 'inline-block';
+                    } else {
+                        extressize.style.display = 'none';
+                    }
+                }
+                if (!setvis) {
+                    extresreuse.style.display = 'none';
+                    extressize.style.display = 'none';
+                }
+            }
+        }
+    }
+}
+ENDSCRIPT
+    $localization_js = <<"ENDSCRIPT";
+function toggleTimeZone() {
+    if (document.getElementById('LC_set_timezone')) {
+        var timezonesel = document.getElementById('LC_set_timezone').value;
+        if (document.getElementById('LC_tzoverdiv')) {
+            var tzoverdiv = document.getElementById('LC_tzoverdiv');
+            if (timezonesel == '') {
+                tzoverdiv.style.display = 'none';
+            } else {
+                tzoverdiv.style.display = 'block';
+            }
+        }
+    }
+}
+ENDSCRIPT
     $jscript = '<script type="text/javascript" language="Javascript">'."\n".
                '// <![CDATA['."\n".  
                $browse_js."\n".$categorize_js."\n".$loncaparev_js."\n".
-               $cloners_js."\n".$instcode_js.
-               $syllabus_js."\n".$menuitems_js."\n".
+               $cloners_js."\n".$instcode_js."\n".$localization_js."\n".
+               $syllabus_js."\n".$menuitems_js."\n".$extresource_js."\n".
                &linkprot_javascript()."\n".'//]]>'."\n".
                '</script>'."\n".$stubrowse_js."\n";
     return $jscript;
@@ -2792,7 +2983,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];
@@ -4325,9 +4516,23 @@ sub print_localization {
         if ($item eq 'timezone') {
             my $includeempty = 1;
             my $timezone = &Apache::lonlocal::gettimezone();
+            my $onchange;
+            unless ($noedit) {
+               $onchange = ' onchange="javascript:toggleTimeZone();"';
+            }
+            my $id = ' id="LC_set_timezone"';
             $datatable .= 
-                &Apache::loncommon::select_timezone($item,$timezone,undef,
-                                                    $includeempty,$disabled);
+                &Apache::loncommon::select_timezone($item,$timezone,$onchange,
+                                                    $includeempty,$id,$disabled);
+            my $tzsty = 'none';
+            if ($timezone ne '') {
+                $tzsty = 'block';
+            }
+            $datatable .= '<div id="LC_tzoverdiv" style="display:'.$tzsty.';">'.
+                          '<span class="LC_nobreak">'.
+                          &mt('Override individual user preference?').
+                          &yesno_radio('tzover',$settings,undef,1,'',$noedit).
+                          '</span></div>';
         } elsif ($item eq 'datelocale') {
             my $includeempty = 1;
             my $locale_obj = &Apache::lonlocal::getdatelocale();
@@ -5046,6 +5251,16 @@ sub print_appearance {
                    text => '<b>'.&mt($itemtext->{'inline_chem'}).'</b>',
                    input => 'radio',
                  },
+         'extresource' => {
+                   text => '<b>'.&mt($itemtext->{'extresource'}).'</b>',
+                   input => 'selectbox',
+                   options => {
+                                iframe => 'In iframe',
+                                tab    => 'In new tab',
+                                window => 'In pop-up window',
+                              },
+                   order  => ['iframe','tab','window'],
+                 },
     );
     return &make_item_rows($cdom,\%items,$ordered,$settings,$rowtotal,$crstype,'appearance',$noedit);
 }
@@ -5699,10 +5914,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 +5931,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 +5948,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 +5980,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">'.
@@ -5774,19 +5991,33 @@ sub print_linkprotection {
                                       '<label><input type="radio" value="1" name="linkprot_changesecret_'.$i.'" onclick="javascript:toggleLinkProt(this.form,'."'$i','secret'".');"'.$disabled.' />'.&mt('Yes').
                                       '</label>&nbsp;&nbsp;</span><div id="linkprot_divchgsecret_'.$i.'" style="display:none" />'.
                                       '<span class="LC_nobreak">'.&mt('New Secret').':'.
-                                      '<input type="password" size="20" name="linkprot_secret_'.$i.'" value="" autocomplete="off"'.$disabled.' />'.
+                                      '<input type="password" size="20" name="linkprot_secret_'.$i.'" value="" autocomplete="new-password"'.$disabled.' />'.
                                       '<label><input type="checkbox" name="linkprot_visible_'.$i.'" id="linkprot_visible_'.$i.'" onclick="if (this.checked) { this.form.linkprot_secret_'.$i.'.type='."'text'".' } else { this.form.linkprot_secret_'.$i.'.type='."'password'".' }"'.$disabled.' />'.&mt('Visible input').'</label>'.
                                       '<input type="hidden" name="linkprot_id_'.$i.'" value="'.$num.'" /></span></div>';
                     } else {
                         $datatable .=
                             '<span class="LC_nobreak">'.$desc{'secret'}.':'.
-                            '<input type="password" size="20" name="linkprot_secret_'.$i.'" value="" autocomplete="off"'.$disabled.' />'.
+                            '<input type="password" size="20" name="linkprot_secret_'.$i.'" value="" autocomplete="new-password"'.$disabled.' />'.
                             '<label><input type="checkbox" name="linkprot_visible_'.$i.'" id="linkprot_visible_'.$i.'" onclick="if (this.checked) { this.form.linkprot_secret_'.$i.'.type='."'text'".' } else { this.form.linkprot_secret_'.$i.'.type='."'password'".' }"'.$disabled.' />'.&mt('Visible input').'</label>'.
                             '<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,41 +6033,54 @@ 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 {
         $datatable .= '<span class="LC_nobreak">'.$desc{'key'}.':<input type="text" size="25" name="linkprot_key_add" value="" autocomplete="off"'.$disabled.' /></span> '."\n".
                       ('&nbsp;'x2).
-                      '<span class="LC_nobreak">'.$desc{'secret'}.':<input type="password" size="20" name="linkprot_secret_add" value="" autocomplete="off"'.$disabled.' />'.
+                      '<span class="LC_nobreak">'.$desc{'secret'}.':<input type="password" size="20" name="linkprot_secret_add" value="" autocomplete="new-password"'.$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 .= '</fieldset>'.
+        $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>'.
+                      '</fieldset>'.
                       '<fieldset id="linkprot_optional_add" style="'.$usersty.'"><legend>'.$lt{'opti'}.'</legend>'.
                       &linkprot_options('add',$itemcount,$disabled,{},\%desc).
                      '</fieldset>';
@@ -5853,6 +6097,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 +6153,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') {
@@ -5932,6 +6177,45 @@ sub linkprot_options {
     return $output;
 }
 
+sub print_extresource_row {
+    my ($item,$config,$curr,$noedit) = @_;
+    my $onchange;
+    unless ($noedit) {
+        $onchange = ' onchange="javascript:toggleExtRes();"';
+    }
+    my $id = 'LC_'.$item;
+    my ($selected,$reuse,$width,$height) = split(/:/,$curr);
+    my $output = &select_from_options($item,$config->{'order'},
+                                      $config->{'options'},$selected,
+                                      $config->{'nullval'},
+                                      undef,undef,$onchange,$noedit,$id);
+    my ($checked,$reusesty,$sizesty); 
+    if ($reuse) {
+        $checked = ' checked="checked"';
+    }
+    $reusesty = 'none';
+    $sizesty = 'none';
+    if (($selected eq 'window') || ($selected eq 'tab')) {
+        $reusesty = 'inline-block';
+        if ($selected eq 'window') {
+            $sizesty = 'inline-block';
+        }
+    }
+    $output .= '<div id="LC_extresreusediv" style="display:'.$reusesty.';">'.
+               '<span class="LC_nobreak">'.
+               '<label><input type="checkbox" name="extwintabreuse" value="1"'.$checked.'>'.
+               &mt('Re-use tab/window').'</label>'.
+               '</span></div>'.
+               '<fieldset id="LC_extressize" style="display:'.$sizesty.';">'.
+               '<legend>'.&mt('Window size (optional)').'</legend>'.
+               '<span class="LC_nobreak">'.
+               &mt('width').':<input type="text" name="extreswinwidth" value="'.$width.'" size="3" />px'.
+               ('&nbsp;' x 3).
+               &mt('height').':<input type="text" name="extreswinheight" value="'.$height.'" size="3" />px'.
+               '</span></fieldset>';
+    return $output;
+}
+
 sub print_other {
     my ($cdom,$settings,$allitems,$rowtotal,$crstype,$noedit) = @_;
     unless ((ref($settings) eq 'HASH') && (ref($allitems) eq 'ARRAY')) {
@@ -6103,10 +6387,16 @@ sub make_item_rows {
                 (($caller eq 'printouts') && ($item ne 'print_header_format'))) {
                 $colspan = 2;
             }
+            my $rowdesc;
+            if ($caller eq 'appearance') {
+                $rowdesc = '<span class="LC_nobreak">'.$items->{$item}{text}.'</span>';
+            } else {
+                $rowdesc = $items->{$item}{text};
+            }
             if (exists $items->{$item}{advanced} && $items->{$item}{advanced} == 1) {
-                $datatable .= &item_table_row_start($items->{$item}{text},$count,"advanced",$colspan);
+                $datatable .= &item_table_row_start($rowdesc,$count,"advanced",$colspan);
             } else {
-                $datatable .= &item_table_row_start($items->{$item}{text},$count,undef,$colspan);
+                $datatable .= &item_table_row_start($rowdesc,$count,undef,$colspan);
             }
             if ($item eq 'defaultcredits') {
                 my $defaultcredits = $env{'course.'.$env{'request.course.id'}.'.internal.defaultcredits'};
@@ -6125,6 +6415,8 @@ sub make_item_rows {
                 $datatable .= &print_hdrfmt_row($item,$settings,$noedit);
             } elsif ($item eq 'lti.lcmenu') {
                 $datatable .= &lcmenu_checkboxes($cdom,$item,$settings,$crstype,$noedit);
+            } elsif ($item eq 'extresource') {
+                $datatable .= &print_extresource_row($item,$items->{$item},$settings->{$item},$noedit);
             } elsif ($items->{$item}{input} eq 'dates') {
                my $disabled;
                if ($noedit) {
@@ -6551,6 +6843,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;
 }