[LON-CAPA-cvs] cvs: loncom /interface courseprefs.pm lonconfigsettings.pm londocs.pm lonextresedit.pm lonexttool.pm

raeburn raeburn at source.lon-capa.org
Fri Jun 13 22:50:26 EDT 2025


raeburn		Sat Jun 14 02:50:26 2025 EDT

  Modified files:              
    /loncom/interface	courseprefs.pm lonconfigsettings.pm londocs.pm 
                     	lonextresedit.pm lonexttool.pm 
  Log:
  - Destination URL which differs from launch URL can be specified for an LTI
    Provider which does not support specifying this in another way (e.g., via 
    custom parameter in launch payload, or as string appended to launch URL).
  
  
-------------- next part --------------
Index: loncom/interface/courseprefs.pm
diff -u loncom/interface/courseprefs.pm:1.135 loncom/interface/courseprefs.pm:1.136
--- loncom/interface/courseprefs.pm:1.135	Tue May 13 04:07:07 2025
+++ loncom/interface/courseprefs.pm	Sat Jun 14 02:50:25 2025
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set configuration settings for a course
 #
-# $Id: courseprefs.pm,v 1.135 2025/05/13 04:07:07 raeburn Exp $
+# $Id: courseprefs.pm,v 1.136 2025/06/14 02:50:25 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -2026,13 +2026,23 @@
             } else {
                 $ltitools{$newid}{'display'}{'target'} = 'iframe';
             }
-            foreach my $item ('passback','roster','returnurl') {
+            foreach my $item ('desturl','returnurl','passback','roster') {
                 if ($env{'form.ltitools_'.$item.'_add'}) {
                     $ltitools{$newid}{$item} = 1;
-                    if ($item eq 'returnurl') {
+                    if (($item eq 'returnurl') || ($item eq 'desturl')) {
                         if ($env{'form.ltitools_crs'.$item.'_add'}) {
                             $ltitools{$newid}{'crsconf'}{$item} = 1;
                         }
+                        if ($item eq 'desturl') {
+                            $env{'form.ltitools_defdest_add'} =~ s{^\s+|\s+$}{}g;
+                            $env{'form.ltitools_defdelay_add'} =~ s{^\s+|\s+$}{}g;
+                            if ($env{'form.ltitools_defdest_add'} ne '') {
+                                $ltitools{$newid}{'defdest'} = $env{'form.ltitools_defdest_add'};
+                                if ($env{'form.ltitools_defdelay_add'} =~ /^(\d+\.?\d*)$/) {
+                                    $ltitools{$newid}{'defdelay'} = $1;
+                                }
+                            }
+                        }
                     } elsif ($env{'form.ltitools_'.$item.'valid_add'} ne '') {
                         my $lifetime = $env{'form.ltitools_'.$item.'valid_add'};
                         $lifetime =~ s/^\s+|\s+$//g;
@@ -2217,10 +2227,10 @@
                     } else {
                         $haschanges{$itemid} = 1;
                     }
-                    foreach my $extra ('passback','roster','returnurl') {
+                    foreach my $extra ('desturl','returnurl','passback','roster') {
                         if ($env{'form.ltitools_'.$extra.'_'.$i}) {
                             $ltitools{$itemid}{$extra} = 1;
-                            if ($extra eq 'returnurl') {
+                            if (($extra eq 'returnurl') || ($extra eq 'desturl')) {
                                 if ($env{'form.ltitools_crs'.$extra.'_'.$i}) {
                                     $ltitools{$itemid}{'crsconf'}{$extra} = 1;
                                     if (ref($values->{$itemid}{'crsconf'}) eq 'HASH') {
@@ -2231,6 +2241,26 @@
                                         $haschanges{$itemid} = 1;
                                     }
                                 }
+                                if ($extra eq 'desturl') {
+                                    $env{'form.ltitools_defdest_'.$i} =~ s{^\s+|\s+$}{}g;
+                                    $env{'form.ltitools_defdelay_'.$i} =~ s{^\s+|\s+$}{}g;
+                                    if ($env{'form.ltitools_defdest_'.$i} =~ m{^(https?\://|/)}) {
+                                        $ltitools{$itemid}{'defdest'} = $env{'form.ltitools_defdest_'.$i};
+                                        if ($values->{$itemid}{'defdest'} ne $ltitools{$itemid}{'defdest'}) {
+                                            $haschanges{$itemid} = 1;
+                                        }
+                                    } elsif ($values->{$itemid}{'defdest'} ne '') {
+                                        $haschanges{$itemid} = 1;
+                                    }
+                                    if ($env{'form.ltitools_defdelay_'.$i} =~ /^(\d+\.?\d*)$/) {
+                                        $ltitools{$itemid}{'defdelay'} = $1;
+                                        if ($values->{$itemid}{'defdelay'} ne $ltitools{$itemid}{'defdelay'}) {
+                                            $haschanges{$itemid} = 1;
+                                        }
+                                    } elsif ($values->{$itemid} =~ /^(\d+\.?\d*)$/) {
+                                        $haschanges{$itemid} = 1;
+                                    }
+                                }
                             } elsif ($env{'form.ltitools_'.$extra.'valid_'.$i} ne '') {
                                 my $lifetime = $env{'form.ltitools_'.$extra.'valid_'.$i};
                                 $lifetime =~ s/^\s+|\s+$//g;
@@ -3356,11 +3386,14 @@
                         $output .= ' '.&mt('None');
                     }
                     $output .= '</li>';
-                    foreach my $item ('passback','roster','returnurl') {
-                        $output .= '<li>'.$lt{$item}.' ';
+                    foreach my $item ('desturl','returnurl','passback','roster') {
+                        $output .= '<li>'.$lt{$item};
                         if ($changes->{$itemid}{$item}) {
-                            $output .= &mt('Yes');
-                            if ($item eq 'returnurl') {
+                            if ($item eq 'desturl') {
+                                $output .= ':';
+                            }
+                            $output .= ' '.&mt('Yes');
+                            if (($item eq 'returnurl') || ($item eq 'desturl')) {
                                 if (ref($changes->{$itemid}{'crsconf'}) eq 'HASH') {
                                     $output .= ' ['.&mt('Configurable in course').': ';
                                     if ($changes->{$itemid}{'crsconf'}{$item}) {
@@ -3368,7 +3401,22 @@
                                     } else {
                                         $output .= &mt('No');
                                     }
-                                    $output .= ']';
+                                    $output .= ' ]';
+                                }
+                                if ($item eq 'desturl') {
+                                    $output .=  '</li><li>'.&mt('Default destination').': ';
+                                    if ($changes->{$itemid}{'defdest'} ne '') {
+                                        $output .= $changes->{$itemid}{'defdest'}.
+                                                   '</li><li>'.&mt('Default delay between login and redirect').': ';
+                                        if ($changes->{$itemid}{'defdelay'} ne '') {
+                                            $output .= $changes->{$itemid}{'defdelay'}.' s';
+                                        } else {
+                                            $output .= &mt('None set');
+                                        }
+                                        $output .= '</li>';
+                                    } else {
+                                        $output .= &mt('None').'</li>';
+                                    }
                                 }
                             } elsif ($changes->{$itemid}{$item.'valid'}) {
                                 if ($item eq 'passback') {
@@ -3379,10 +3427,12 @@
                                                        $changes->{$itemid}{$item.'valid'});
                                 }
                             }
+                            unless ($item eq 'desturl') {
+                                $output .= '</li>';
+                            }
                         } else {
-                            $output .= &mt('No');
+                            $output .= ' '.&mt('No').'</li>';
                         }
-                        $output .= '</li>';
                     }
                     if (ref($changes->{$itemid}{'display'}) eq 'HASH') {
                         my $displaylist;
@@ -6664,22 +6714,27 @@
                     %courseconfig = %{$settings->{$item}->{'crsconf'}};
                 }
             }
-            foreach my $extra ('passback','roster','returnurl') {
+            foreach my $extra ('desturl','returnurl','passback','roster') {
                 my $validsty = 'none';
                 my $currvalid;
                 my $checkedon = '';
                 my $checkedoff = ' checked="checked"';
                 my $crscheckedon = '';
                 my $crscheckedoff = ' checked="checked"';
+                my ($defdest,$defdelay);
                 if ($settings->{$item}->{$extra}) {
                     $checkedon = $checkedoff;
                     $checkedoff = '';
                     $validsty = 'inline-block';
-                    if ($extra eq 'returnurl') {
+                    if (($extra eq 'returnurl') || ($extra eq 'desturl')) {
                         if ($courseconfig{$extra}) {
                             $crscheckedon = ' checked="checked"';
                             $crscheckedoff = '';
                         }
+                        if ($extra eq 'desturl') {
+                            $defdest = $settings->{$item}->{'defdest'};
+                            $defdelay = $settings->{$item}->{'defdelay'};  
+                        }
                     } elsif ($settings->{$item}->{$extra.'valid'} =~ /^\d+\.?\d*$/) {
                         $currvalid = $settings->{$item}->{$extra.'valid'};
                     }
@@ -6690,7 +6745,7 @@
                               &mt('No').'</label>'.(' 'x2).
                               '<label><input type="radio" name="ltitools_'.$extra.'_'.$i.'" value="1"'.$checkedon.$onclick.' />'.
                               &mt('Yes').'</label></span></div>';
-                if ($extra eq 'returnurl') {
+                if (($extra eq 'returnurl') || ($extra eq 'desturl')) {
                     $datatable .= '<div class="LC_floatleft" style="display:'.$validsty.';" id="ltitools_course'.$extra.'_'.$i.'">'.
                                   '<span class="LC_nobreak"> -- '.&mt('configurable in course').': '.
                                   '<label><input type="radio" name="ltitools_crs'.$extra.'_'.$i.'" value="0"'.$crscheckedoff.' />'.
@@ -6704,6 +6759,15 @@
                                       '<input type="text" name="ltitools_'.$extra.'valid_'.$i.'" value="'.$currvalid.'" />');
                 }
                 $datatable .= '</span></div><div style="padding:0;clear:both;margin:0;border:0"></div>';
+		if ($extra eq 'desturl') {
+                    $datatable .= '<div style="display:'.$validsty.';" id="ltitools_default'.$extra.'_'.$i.'">'.
+                                  '<span class="LC_nobreak"><label>'.&mt('Default destination URL').':'.
+                                  '<input type="text" size="60" name="ltitools_defdest_'.$i.'" value="'.$defdest.'" />'.
+                                  '</label><span><br /><span class="LC_nobreak"><label>'.
+                                  &mt('Default delay between login and redirect').':'.
+                                  '<input type="text" size="3" name="ltitools_defdelay_'.$i.'" value="'.$defdelay.'" />'.
+                                  '(s)</label></span></div><br />';
+                }
             }
             $datatable .= '<span class="LC_nobreak">'.$lt{'icon'}.': ';
             if ($imgsrc) {
@@ -6898,14 +6962,14 @@
                      'passback' => '7',
                      'roster'   => '300',
                    );
-    foreach my $extra ('passback','roster','returnurl') {
+    foreach my $extra ('desturl','returnurl','passback','roster') {
         my $onclick = ' onclick="toggleLTITools(this.form,'."'$extra','add'".');"';
         $datatable .= '<div class="LC_floatleft"><span class="LC_nobreak">'.$lt{$extra}.' '.
                       '<label><input type="radio" name="ltitools_'.$extra.'_add" value="0" checked="checked"'.$onclick.' />'.
                       &mt('No').'</label></span>'.(' 'x2).'<span class="LC_nobreak">'.
                       '<label><input type="radio" name="ltitools_'.$extra.'_add" value="1"'.$onclick.' />'.
                       &mt('Yes').'</label></span></div>';
-        if ($extra eq 'returnurl') {
+        if (($extra eq 'returnurl') || ($extra eq 'desturl')) {
             $datatable .= '<div class="LC_floatleft" style="display:none;" id="ltitools_course'.$extra.'_add">'.
                           '<span class="LC_nobreak"> -- '.&mt('configurable in course').': '.
                           '<label><input type="radio" name="ltitools_crs'.$extra.'_add" value="0" checked="checked" />'.
@@ -6919,6 +6983,15 @@
                               '<input type="text" name="ltitools_'.$extra.'valid_add" value="'.$defaulttimes{$extra}.'" />');
         }
         $datatable .= '</span></div><div style="padding:0;clear:both;margin:0;border:0"></div>';
+        if ($extra eq 'desturl') {
+            $datatable .= '<div style="display:none;" id="ltitools_default'.$extra.'_add">'.
+                          '<span class="LC_nobreak"><label>'.&mt('Default destination URL').':'.
+                          '<input type="text" size="50" name="ltitools_defdest_add" value="" />'.
+                          '</label></span><br /><span class="LC_nobreak"><label>'.
+                          &mt('Default delay between login and redirect').':'.
+                          '<input type="text" size="3" name="ltitools_defdelay_add" value="1" />'.
+                          '(s)</label></span></div><br />';
+        }
     }
     $datatable .= '<span class="LC_nobreak">'.$lt{'icon'}.': '.
                   '('.&mt('if larger than 21x21 pixels, image will be scaled').') ';
@@ -7015,13 +7088,15 @@
                                           'explanation'    => 'Default Explanation',
                                           'passback'       => 'Tool can return grades:',
                                           'roster'         => 'Tool can retrieve roster:',
-                                          'returnurl'      => 'Return URL sent on launch:',
+                                          'returnurl'      => 'Return LONCAPA URL sent on launch:',
                                           'crstarget'      => 'Display target',
                                           'crslabel'       => 'Course label',
                                           'crstitle'       => 'Course title',
                                           'crslinktext'    => 'Link Text',
                                           'crsexplanation' => 'Explanation',
                                           'crsappend'      => 'Provider URL',
+                                          'desturl'        => 'Destination URL different from Provider URL',
+                                          'delay'          => 'Second(s) delay before redirect to destination URL',
                                         );
     return %lt;
 }
Index: loncom/interface/lonconfigsettings.pm
diff -u loncom/interface/lonconfigsettings.pm:1.74 loncom/interface/lonconfigsettings.pm:1.75
--- loncom/interface/lonconfigsettings.pm:1.74	Sat Mar 15 01:03:33 2025
+++ loncom/interface/lonconfigsettings.pm	Sat Jun 14 02:50:25 2025
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: lonconfigsettings.pm,v 1.74 2025/03/15 01:03:33 raeburn Exp $
+# $Id: lonconfigsettings.pm,v 1.75 2025/06/14 02:50:25 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -269,6 +269,7 @@
             if (grep(/^ltitools$/, at actions)) {
                 $onload .= "toggleLTITools(document.display,'passback','add');".
                            "toggleLTITools(document.display,'roster','add');".
+                           "toggleLTITools(document.display,'desturl','add');".
                            "toggleLTITools(document.display,'returnurl','add');".
                            "toggleLTITools(document.display,'user','add');";
                 if (ref($values) eq 'HASH') {
@@ -277,6 +278,7 @@
                         for (my $i=0; $i<$numltitools; $i++) {
                             $onload .= "toggleLTITools(document.display,'passback','$i');".
                                        "toggleLTITools(document.display,'roster','$i');".
+                                       "toggleLTITools(document.display,'desturl','$i');".
                                        "toggleLTITools(document.display,'returnurl','$i');".
                                        "toggleLTITools(document.display,'user','$i');";
                         }
@@ -1120,10 +1122,14 @@
 function toggleLTITools(form,setting,item) {
     var radioname = '';
     var divid = '';
-    if ((setting == 'passback') || (setting == 'roster') || (setting == 'returnurl')) {
+    var defdivid = '';
+    if ((setting == 'passback') || (setting == 'roster') || (setting == 'returnurl') || (setting == 'desturl')) {
         radioname = 'ltitools_'+setting+'_'+item;
-        if (setting == 'returnurl') {
+        if ((setting == 'returnurl') || (setting == 'desturl')) {
             divid = 'ltitools_course'+setting+'_'+item;
+            if (setting == 'desturl') {
+                defdivid = 'ltitools_default'+setting+'_'+item;
+            }
         } else {
             divid = 'ltitools_'+setting+'time_'+item;
         }
@@ -1135,6 +1141,9 @@
                     if (form.elements[radioname][i].value == '1') {
                         if (document.getElementById(divid)) {
                             document.getElementById(divid).style.display = 'inline-block';
+                            if ((setting == 'desturl') && (document.getElementById(defdivid))) {
+                                document.getElementById(defdivid).style.display = 'inline-block';
+                            }
                         }
                         setvis = 1;
                     }
@@ -1146,6 +1155,9 @@
             if (document.getElementById(divid)) {
                 document.getElementById(divid).style.display = 'none';
             }
+            if ((setting == 'desturl') && (document.getElementById(defdivid))) {
+                document.getElementById(defdivid).style.display = 'none';
+            }
         }
     }
     if (setting == 'user') {
Index: loncom/interface/londocs.pm
diff -u loncom/interface/londocs.pm:1.727 loncom/interface/londocs.pm:1.728
--- loncom/interface/londocs.pm:1.727	Sat Mar 15 01:03:33 2025
+++ loncom/interface/londocs.pm	Sat Jun 14 02:50:25 2025
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Documents
 #
-# $Id: londocs.pm,v 1.727 2025/03/15 01:03:33 raeburn Exp $
+# $Id: londocs.pm,v 1.728 2025/06/14 02:50:25 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1704,7 +1704,7 @@
                 my $marker = $2;
                 my $info = $3;
                 my ($toolid,$toolprefix,$tooltype,%toolhash,%toolsettings);
-                my @extras = ('linktext','explanation','crslabel','crstitle','crsappend','returnurl','backtourl');
+                my @extras = ('linktext','explanation','crslabel','crstitle','crsappend','returnurl','backtourl','desturl','delay');
                 my @toolinfo = split(/:/,$info);
                 if ($residx) {
                     %toolsettings=&Apache::lonnet::dump('exttool_'.$marker,$coursedom,$coursenum);
@@ -1722,10 +1722,16 @@
                 ($toolhash{'target'},$toolhash{'width'},$toolhash{'height'},
                  $toolhash{'linktext'},$toolhash{'explanation'},$toolhash{'crslabel'},
                  $toolhash{'crstitle'},$toolhash{'crsappend'},$toolhash{'gradable'},
-                 $toolhash{'returnurl'},$toolhash{'backtourl'}) = @toolinfo;
+                 $toolhash{'returnurl'},$toolhash{'backtourl'},$toolhash{'desturl'},
+                 $toolhash{'delay'}) = @toolinfo;
                 foreach my $item (@extras) {
                     $toolhash{$item} = &unescape($toolhash{$item});
                 }
+                foreach my $item ('crsappend','gradable','returnurl',
+                                  'backtourl','desturl','delay') {
+                    $toolhash{$item} =~ s/^\s+//;
+                    $toolhash{$item} =~ s/\s+$//;
+                }
                 if ($folder =~ /^supplemental/) {
                     delete($toolhash{'gradable'});
                 } else {
@@ -1786,6 +1792,7 @@
                                     }
                                 }
                             }
+                            my $nocrsdest = 1;
                             if (ref($tools{'crsconf'}) eq 'HASH') {
                                 foreach my $item ('label','title','linktext','explanation') {
                                     my $crsitem;
@@ -1817,6 +1824,17 @@
                                     delete($toolhash{'returnurl'});
                                     delete($toolhash{'backtourl'});
                                 }
+                                if ($tools{'crsconf'}{'desturl'}) {
+                                    if ($toolhash{'desturl'} =~ m{^(https?\://|/)}) {
+                                        undef($nocrsdest);
+                                    } else {
+                                        delete($toolhash{'desturl'});
+                                        delete($toolhash{'delay'});
+                                    }
+                                } else {
+                                    delete($toolhash{'desturl'});
+                                    delete($toolhash{'delay'});
+                                }
                             }
                             if ($toolhash{'passback'}) {
                                 my $gradesecret = UUID::Tiny::create_uuid_as_string(UUID_V4);
@@ -1828,6 +1846,17 @@
                                 $toolhash{'rostersecret'} = $rostersecret;
                                 $toolhash{'rostersecretdate'} = time;
                             }
+                            if ($nocrsdest) {
+                                foreach my $item ('desturl','delay') {
+                                    if (exists($toolhash{$item})) {
+                                        delete($toolhash{$item});
+                                    }
+                                }
+                            } elsif (exists($toolhash{'delay'})) {
+                                unless ($toolhash{'delay'} =~ /^(\d+\.?\d*)$/) {
+                                    delete($toolhash{'delay'});
+                                }
+                            }
                             my $changegradable;
                             if (($residx) && ($folder =~ /^default/)) {
                                 if ($toolsettings{'gradable'}) {
@@ -1852,6 +1881,12 @@
                                 if (($toolsettings{'returnurl'} ne '') && (!exists($toolhash{'returnurl'}))) {
                                     push(@deleted,'returnurl');
                                 }
+                                if (($toolsettings{'desturl'} ne '') && (!exists($toolhash{'desturl'}))) {
+                                    push(@deleted,'desturl');
+                                }
+                                if (($toolsettings{'delay'} ne '') && (!exists($toolhash{'delay'}))) {
+                                    push(@deleted,'delay');
+                                }
                             }
                             my $putres = &Apache::lonnet::put('exttool_'.$marker,\%toolhash,$coursedom,$coursenum);
                             if ($putres eq 'ok') {
Index: loncom/interface/lonextresedit.pm
diff -u loncom/interface/lonextresedit.pm:1.36 loncom/interface/lonextresedit.pm:1.37
--- loncom/interface/lonextresedit.pm:1.36	Sat Mar 15 01:03:33 2025
+++ loncom/interface/lonextresedit.pm	Sat Jun 14 02:50:25 2025
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Documents
 #
-# $Id: lonextresedit.pm,v 1.36 2025/03/15 01:03:33 raeburn Exp $
+# $Id: lonextresedit.pm,v 1.37 2025/06/14 02:50:25 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -310,12 +310,32 @@
     my (%newhash,$changed,$newgradable, at deleted,$errormsg);
     ($newhash{'target'},$newhash{'width'},$newhash{'height'},$newhash{'linktext'},$newhash{'explanation'},
      $newhash{'crslabel'},$newhash{'crstitle'},$newhash{'crsappend'},$newhash{'gradable'},
-     $newhash{'returnurl'},$newhash{'backtourl'}) = split(/:/,$args);
-    foreach my $item ('linktext','explanation','crslabel','crstitle','crsappend','backtourl') {
+     $newhash{'returnurl'},$newhash{'backtourl'},$newhash{'desturl'},$newhash{'delay'}) = split(/:/,$args);
+    foreach my $item ('linktext','explanation','crslabel','crstitle','crsappend','backtourl','desturl','delay') {
         $newhash{$item} = &unescape($newhash{$item});
     }
     my %toolhash=&Apache::lonnet::dump('exttool_'.$marker,$cdom,$cnum);
-    foreach my $item ('target','width','height','linktext','explanation','crslabel','crstitle','crsappend','gradable','returnurl','backtourl') {
+    my ($tooltype,$tool,$ltihash);
+    if ($toolhash{'id'} =~/^c(\d+)$/) {
+        $tool = $1;
+        $tooltype = 'crs';
+    } elsif ($toolhash{'id'} =~/^\d+$/) {
+        $tooltype = 'dom';
+        $tool = $toolhash{'id'};
+    }
+    if (($tool ne '') && ($tooltype ne '')) {
+        my %tools;
+        my %tooltypes = &Apache::loncommon::usable_exttools();
+        if ($tooltypes{$tooltype}) {
+            if ($tooltype eq 'dom') {
+                %tools = &Apache::lonnet::get_domain_lti($cdom,'consumer');
+            } elsif ($tooltypes{'crs'}) {
+                %tools = &Apache::lonnet::get_course_lti($cnum,$cdom,'consumer');
+            }
+            $ltihash = $tools{$tool};
+        }
+    }
+    foreach my $item ('target','width','height','linktext','explanation','crslabel','crstitle','crsappend','gradable','returnurl','backtourl','desturl','delay') {
         $newhash{$item} =~ s/^\s+//;
         $newhash{$item} =~ s/\s+$//;
         if (($item eq 'width') || ($item eq 'height') || ($item eq 'linktext') || ($item eq 'explanation')) {
@@ -334,6 +354,22 @@
             unless ($newhash{'returnurl'} eq 'custom') {
                 $newhash{$item} = '';
             }
+        } elsif ($item eq 'desturl') {
+            my $nocrsdest = 1;
+            if ((ref($ltihash) eq 'HASH') && (ref($ltihash->{'crsconf'}) eq 'HASH') &&
+                ($ltihash->{'crsconf'}->{'desturl'})) {
+                undef($nocrsdest);
+            }
+            if ($nocrsdest) {
+                $newhash{$item} = '';
+            }
+        } elsif ($item eq 'delay') {
+            if ($newhash{'desturl'} eq '') {
+                $newhash{$item} = '';
+	    }
+            unless ($newhash{$item} =~ /^(\d+\.?\d*)$/) {
+                $newhash{$item} = '';
+            }
         }
         if ($toolhash{$item} ne $newhash{$item}) {
             if (($item eq 'gradable') && (!$supplementalflag)) {
@@ -400,9 +436,9 @@
         $tabid = 'ee';
     }
     my ($formname,$formid,$toggle,$fieldsetid,$urlid,$subdivid,$dispdivstyle,$dimendivstyle,
-        $windivstyle,$linktextstyle,$explanationstyle,$labelstyle,$titlestyle,
-        $appendstyle,$gradablestyle,$returnurlstyle,$subdivstyle,$legend,$urlelem,
-        $toolelem,%toolattr);
+        $windivstyle,$linktextstyle,$explanationstyle,$labelstyle,$titlestyle,$providerstyle,
+        $appendstyle,$gradablestyle,$returnurlstyle,$subdivstyle,$desturlstyle,
+	$desturlinfostyle,$desturlfixedstyle,$legend,$urlelem,$toolelem,%toolattr);
     $formname = 'new'.$type;
     $toggle = $type;
     $fieldsetid = 'external'.$type.'form';
@@ -411,7 +447,9 @@
                                        'crstitlediv','crslabeldiv','crsappenddiv',
                                        'gradablediv','returnurldiv','crstitle','crslabel',
                                        'crsappend','windiv','linktextdiv','explanationdiv',
-                                       'linktext','explanation','providerurl','customreturnurl');
+                                       'linktext','explanation','provider','providerpath','customreturnurl',
+                                       'desturl','delay','providerdiv','desturldiv','desturlinfodiv',
+                                       'desturlfixeddiv','fixeddest','fixeddelay');
     $dispdivstyle = 'display:none';
     $dimendivstyle = 'display:none';
     $windivstyle = 'display:none';
@@ -419,9 +457,13 @@
     $explanationstyle = 'display:none';
     $labelstyle = 'display:none';
     $titlestyle = 'display:none';
+    $providerstyle = 'display:none';
     $appendstyle = 'display:none';
     $gradablestyle = 'display:none';
     $returnurlstyle = 'display:none';
+    $desturlstyle = 'display:none';
+    $desturlinfostyle = 'display:none';
+    $desturlfixedstyle = 'display:none';
     $subdivstyle = 'display:block';
     if ($supplementalflag) {
         $formname = 'newsupp'.$type;
@@ -432,8 +474,9 @@
     }
     my ($link,$legend,$active,$srcclass,$extsrc,$preview,$title,$save,$crstitle,$crslabel,
         $crsappend,$fieldsetstyle,$action,$hiddenelem,$form,$width,$height,$tooltarget,
-        $linktext,$explanation,$providerurl,$returnurl,$chkgrd,$chknogrd,%chkstate,
-        $chknoreturn,$chkreturndef,$chkreturncust,$customreturn,$backtourl);
+        $linktext,$explanation,$path,$returnurl,$chkgrd,$chknogrd,%chkstate,
+        $chknoreturn,$chkreturndef,$chkreturncust,$customreturn,$backtourl,$desturl,$delay,
+	$chknodest,$chkdest);
     $fieldsetstyle = 'display: none;';
     $action = '/adm/coursedocs';
     my $protocol = ($ENV{'SERVER_PORT'} == 443?'https':'http');
@@ -507,6 +550,7 @@
         $urlelem = '<input type="text" size="'.$size.'" name="exturl" id="'.$urlid.'" value="'.$orig_url.'"'.$disabled.' />';
     } else {
         my $class = 'LC_nobreak';
+        my ($nocrsdest,$fixeddest,$fixeddelay,$showprovider,$provider,$scheme);
         if ($residx) {
             $class = 'LC_docs_ext_edit LC_nobreak'; 
             if ($orig_url =~ m{^/adm/$cdom/$cnum/(\d+)/ext\.tool$}) {
@@ -516,18 +560,13 @@
                 if ($toolhash{'id'} =~/^c(\d+)$/) {
                     $tool = $1;
                     $tooltype = 'crs';
-                    if (ref($ltitools) eq 'HASH') {
-                        if (ref($ltitools->{'crs'}) eq 'HASH') {
-                            $ltihash = $ltitools->{'crs'}->{$tool};
-                        }
-                    }
                 } elsif ($toolhash{'id'} =~/^\d+$/) {
                     $tooltype = 'dom';
                     $tool = $toolhash{'id'};
-                    if (ref($ltitools) eq 'HASH') {
-                        if (ref($ltitools->{'dom'}) eq 'HASH') {
-                            $ltihash = $ltitools->{'dom'}->{$tool};
-                        }
+                }
+                if (($tool ne '') && ($tooltype ne '') && (ref($ltitools) eq 'HASH')) {
+                    if (ref($ltitools->{$tooltype}) eq 'HASH') {
+                        $ltihash = $ltitools->{$tooltype}->{$tool};
                     }
                 }
                 if (($tool ne '') && (ref($ltihash) eq 'HASH')) {
@@ -537,10 +576,10 @@
                     if ($icon) {
                         $image = '<img src="'.$icon.'" alt="'.$tooltitle.'" />';
                     }
-                    if ($ltihash->{'url'} =~ m{://}) {
-                        (my $prot,my $host,$providerurl) = ($ltihash->{'url'} =~ m{^([^/]+)://([^/]+)(|/.+)$});
+                    if ($ltihash->{'url'} =~ m{^https?\://}) {
+                        ($scheme,$provider,$path) = ($ltihash->{'url'} =~ m{^(https?\://)([^/]+)(|/.+)$});
                     } else {
-                        $providerurl = $ltihash->{'url'};
+                        $path = $ltihash->{'url'};
                     }
                     $tooltarget = $toolhash{'target'};
                     if ($tooltarget eq 'window') {
@@ -574,6 +613,7 @@
                         if ($ltihash->{'crsconf'}->{'append'}) {
                             $crsappend = $toolhash{'crsappend'};
                             $appendstyle = 'display:inline';
+                            $showprovider = 1;
                         }
                         if ($ltihash->{'crsconf'}->{'target'}) {
                             $dispdivstyle = 'display:block';
@@ -599,9 +639,37 @@
                                 $customreturn = 'hidden';
                             }
                         }
+                        if ($ltihash->{'crsconf'}->{'desturl'}) {
+                            $desturl = $toolhash{'desturl'};
+			    $desturlstyle = 'display:inline';
+                            $delay = $toolhash{'delay'}; 
+                            $showprovider = 1;
+			    if ($desturl ne '') {
+                                $desturlinfostyle = 'display:inline';
+                                $chkdest = ' checked="checked"';
+				$chknodest = ''; 
+			    } else {
+				$desturlinfostyle = 'display:none';
+			        $chkdest = ''; 
+                                $chknodest = ' checked="checked"';
+			    } 
+			} else {
+                            $nocrsdest = 1;
+                        }
                     }
                     $toolelem = '<span class="LC_nobreak">'.$image.' '.$tooltitle.'</span><br />';
                     $gradablestyle = 'display:inline';
+                    if (($ltihash->{'desturl'}) && ($nocrsdest) && ($ltihash->{'defdest'} =~ m{^(https?\://|/)})) {
+                        $fixeddest = $ltihash->{'defdest'};
+                        if ($ltihash->{'defdelay'} =~ /^\d+\.?\d*$/) {
+                            $fixeddelay = $ltihash->{'defdelay'};
+                        }
+                        $desturlfixedstyle = 'display:inline-block';
+                        $showprovider = 1;
+                    }
+                    if (($showprovider) && ($provider ne '')) {
+                        $providerstyle = 'display:inline-block';
+                    }
                 }
             }
         } else {
@@ -688,9 +756,42 @@
             $chknoreturn = '';
             $chkreturndef = ' checked="checked"';
             $chkreturncust = '';
+	    $chknodest = ' checked="checked"';
+	    $chkdest = '';
         }
         my $onclickreturl = ' onclick="updateReturnUrl('."this.form,'$toolattr{'customreturnurl'}','$toolattr{'returnurldiv'}','exttoolreturnurl'".');"';
-        $toolelem .= '<div id="'.$toolattr{'dispdiv'}.'" style="'.$dispdivstyle.'">'.
+        my $onclickdest = ' onclick="updateDestUrl('."this.form,'$toolattr{'desturlinfodiv'}','$toolattr{'desturldiv'}','exttooldest'".');"';
+        $toolelem.= '<div id="'.$toolattr{'providerdiv'}.'" style="'.$providerstyle.'">'.
+                    '<span class="'.$class.'">'.&mt('Provider').': <span id="'.$toolattr{'provider'}.'">'.$scheme.$provider.'</span><br />'.
+                    '<span class="'.$class.'">'.&mt('Provider path').': <span id="'.$toolattr{'providerpath'}.'">'.$path.'</span>'."\n".
+                    '</div>'."\n".
+                    '<div style="padding:0;clear:both;margin:0;border:0"></div>'.
+                    '<div id="'.$toolattr{'crsappenddiv'}.'" style="'.$appendstyle.'">'.
+                    '<span class="'.$class.'">'.&mt('Append to LTI login URL').':</span><br />'.
+                    '<input type="text" id="'.$toolattr{'crsappend'}.'" size="30" name="exttoolappend" value="'.$crsappend.'"'.$disabled.' /></span>'.
+                    '</div>'."\n".
+                    '<div style="padding:0;clear:both;margin:0;border:0"></div>'.
+                    '<div id="'.$toolattr{'desturldiv'}.'" style="'.$desturlstyle.'">'.
+                    '<span class="'.$class.'">'.&mt('Post-login destination different from path').': '.
+                    '<label><input type="radio" name="exttooldest" value="0"'.$chknodest.$disabled.$onclickdest.' />'.
+                    &mt('No').'</label>'.(' 'x2).
+                    '<label><input type="radio" name="exttooldest" value="1"'.$chkdest.$disabled.$onclickdest.' />'.
+                    &mt('Yes').'</label>'.(' 'x2).
+                    '</span><br />'."\n".
+                    '<div id="'.$toolattr{'desturlinfodiv'}.'" style="'.$desturlinfostyle.'">'.
+                    '<span class="'.$class.'">'.
+                    '<input type="text" size="'.$size.'" name="exttooldesturl" value="'.$desturl.'"'.$disabled.' /></label></span><br />'.
+                    '<span class="'.$class.'"><label>'.&mt('Delay between login and redirect').
+                    ':<input type="text" size="3" name="exttooldelay" value="'.$delay.'"'.$disabled.' /> s'.
+                    '</label></span></div></div>'."\n".
+                    '<div id="'.$toolattr{'desturlfixeddiv'}.'" style="'.$desturlfixedstyle.'">'.
+                    '<span class="'.$class.'">'.&mt('Destination after provider login').': '.
+                    '<br /><span id="'.$toolattr{'fixeddest'}.'">'.$fixeddest.'</span></span>'.
+                    '<br /><span class="'.$class.'">'.
+                    &mt('Post-login delay before redirect to destination').': <span id="'.$toolattr{'fixeddelay'}.'">'.$fixeddelay.'</span> s</span>'."\n".
+                    '<br /></div>'.
+                    '<div style="padding:0;clear:both;margin:0;border:0"></div>'.
+                    '<div id="'.$toolattr{'dispdiv'}.'" style="'.$dispdivstyle.'">'.
                     '<span class="'.$class.'">'.&mt('Display target:').' '.
                     '<label><input type="radio" name="exttooltarget" value="iframe" '.$chkstate{'iframe'}.'onclick="updateTooldim(this.form,'.
                     "'$toolattr{dimendiv}','$toolattr{windiv}','$toolattr{dimenwidth}','$toolattr{dimenheight}',
@@ -704,17 +805,17 @@
                     '<div id="'.$toolattr{'dimendiv'}.'" style="'.$dimendivstyle.'"><span class="'.$class.'">'.
                     &mt('Width').': <input type="text" size="4" id="'.$toolattr{'dimenwidth'}.'" name="exttoolwidth" value="'.$width.'"'.$disabled.' />'.(' 'x2).
                     &mt('Height').': <input type="text" size="4" id="'.$toolattr{'dimenheight'}.'" name="exttoolheight" value="'.$height.'"'.$disabled.' /></span>'."\n".
-                    '</div></div>';
-        $toolelem .= '<div id="'.$toolattr{'windiv'}.'" style="'.$windivstyle.'">'.
-                     '<div id="'.$toolattr{'linktextdiv'}.'" class="LC_left_float" style="'.$linktextstyle.'">'.
-                     '<span class="'.$class.'">'.&mt('Link Text').'</span><br /><input type="text" size="25" id="'.$toolattr{'linktext'}.
-                     '" name="exttoollinktext" value="'.$linktext.'"'.$disabled.' />'.
-                     '</div><div id="'.$toolattr{'explanationdiv'}.'" class="LC_left_float" style="'.$explanationstyle.'">'.
-                     '<span class="'.$class.'">'.&mt('Explanation').'</span><br />'.
-                     '<textarea rows="'.$rows.'" cols="'.$cols.'" id="'.$toolattr{'explanation'}.'" name="exttoolexplanation" '.$disabled.'>'.
-                     $explanation.'</textarea></div><div style="padding:0;clear:both;margin:0;border:0"></div>'.
-                     '</div>';
-        $toolelem .= '<div id="'.$toolattr{'crslabeldiv'}.'" style="'.$labelstyle.'">'.
+                    '</div></div>'.
+                    '<div id="'.$toolattr{'windiv'}.'" style="'.$windivstyle.'">'.
+                    '<div id="'.$toolattr{'linktextdiv'}.'" class="LC_left_float" style="'.$linktextstyle.'">'.
+                    '<span class="'.$class.'">'.&mt('Link Text').'</span><br /><input type="text" size="25" id="'.$toolattr{'linktext'}.
+                    '" name="exttoollinktext" value="'.$linktext.'"'.$disabled.' />'.
+                    '</div><div id="'.$toolattr{'explanationdiv'}.'" class="LC_left_float" style="'.$explanationstyle.'">'.
+                    '<span class="'.$class.'">'.&mt('Explanation').'</span><br />'.
+                    '<textarea rows="'.$rows.'" cols="'.$cols.'" id="'.$toolattr{'explanation'}.'" name="exttoolexplanation" '.$disabled.'>'.
+                    $explanation.'</textarea></div><div style="padding:0;clear:both;margin:0;border:0"></div>'.
+                    '</div>'.
+                    '<div id="'.$toolattr{'crslabeldiv'}.'" style="'.$labelstyle.'">'.
                     '<span class="'.$class.'">'.&mt('Course label:').' '.
                     '<input type="text" id="'.$toolattr{'crslabel'}.'" name="exttoollabel" value="'.$crslabel.'"'.$disabled.' /></span><br />'.
                     '</div>'."\n".
@@ -722,11 +823,6 @@
                     '<span class="'.$class.'">'.&mt('Course title:').' '.
                     '<input type="text" id="'.$toolattr{'crstitle'}.'" name="exttooltitle" value="'.$crstitle.'"'.$disabled.' /></span><br />'.
                     '</div>'."\n".
-                    '<div id="'.$toolattr{'crsappenddiv'}.'" style="'.$appendstyle.'">'.
-                    '<span class="'.$class.'">'.&mt('Append to URL[_1]',
-                    '<span id="'.$toolattr{'providerurl'}.'"> ('.$providerurl.')<br /></span>').
-                    '<input type="text" id="'.$toolattr{'crsappend'}.'" size="30" name="exttoolappend" value="'.$crsappend.'"'.$disabled.' /></span><br />'.
-                    '</div>'."\n".
                     '<div id="'.$toolattr{'gradablediv'}.'" style="'.$gradablestyle.'">'.
                     '<span class="'.$class.'">'.&mt('Gradable').' '.
                     '<label><input type="radio" name="exttoolgradable" value="1"'.$chkgrd.$disabled.
@@ -871,7 +967,8 @@
     my ($toolsjs,$exttoolnums,$exttooloptions);
     if (ref($toolsref) eq 'HASH') {
         $toolsjs = "        var ltitools = new Array();\n".
-                   "        var ltitoolsUrl = new Array();\n".
+	           "        var ltitoolsProvider = new Array();\n".
+                   "        var ltitoolsPath = new Array();\n".
                    "        var ltitoolsTarget = new Array();\n".
                    "        var ltitoolsWidth = new Array();\n".
                    "        var ltitoolsHeight = new Array();\n".
@@ -883,7 +980,10 @@
                    "        var ltitoolsLabel = new Array();\n".
                    "        var ltitoolsTitle = new Array();\n".
                    "        var ltitoolsAppend = new Array();\n".
-                   "        var ltitoolsReturnUrl = new Array();\n";
+                   "        var ltitoolsReturnUrl = new Array();\n".
+                   "        var ltitoolsDestUrl = new Array();\n".
+                   "        var ltitoolsDefDest = new Array();\n".
+                   "        var ltitoolsDefDelay = new Array();\n";
         $exttoolnums = "        var ltitoolsnum = new Array();\n".
                        "        var tooloptval = new Array();\n".
                        "        var toolopttxt = new Array();\n";
@@ -892,7 +992,8 @@
             if (ref($toolsref->{$type}) eq 'HASH') {
                 my $num = scalar(keys(%{$toolsref->{$type}}));
                 $toolsjs .= "        ltitools[$idx] = new Array($num);\n".
-                            "        ltitoolsUrl[$idx] = new Array($num);\n".
+                            "        ltitoolsProvider[$idx] = new Array($num);\n".
+                            "        ltitoolsPath[$idx] = new Array($num);\n".
                             "        ltitoolsTarget[$idx] = new Array($num);\n".
                             "        ltitoolsWidth[$idx] = new Array($num);\n".
                             "        ltitoolsHeight[$idx] = new Array($num);\n".
@@ -904,7 +1005,10 @@
                             "        ltitoolsLabel[$idx] = new Array($num);\n".
                             "        ltitoolsTitle[$idx] = new Array($num);\n".
                             "        ltitoolsAppend[$idx] = new Array($num);\n".
-                            "        ltitoolsReturnUrl[$idx] = new Array($num);\n";
+                            "        ltitoolsReturnUrl[$idx] = new Array($num);\n".
+                            "        ltitoolsDestUrl[$idx] = new Array($num);\n".
+                            "        ltitoolsDefDest[$idx] = new Array($num);\n".
+                            "        ltitoolsDefDelay[$idx] = new Array($num);\n";
                 my $i=0;
                 foreach my $key (sort { $a <=> $b } keys(%{$toolsref->{$type}})) {
                     if (ref($toolsref->{$type}->{$key}) eq 'HASH') {
@@ -914,21 +1018,24 @@
                             my $height = $toolsref->{$type}->{$key}->{'display'}->{'height'};
                             my $linkdef = $toolsref->{$type}->{$key}->{'display'}->{'linktext'};
                             my $explaindef = $toolsref->{$type}->{$key}->{'display'}->{'explanation'};
-                            my $providerurl;
-                            if ($toolsref->{$type}->{$key}->{'url'} =~ m{://}) {
-                                (my $prot,my $host,$providerurl) =
-                                    ($toolsref->{$type}->{$key}->{'url'} =~ m{^([^/]+)://([^/]+)(|/.+)$});
+                            my ($scheme,$provider,$path);
+                            if ($toolsref->{$type}->{$key}->{'url'} =~ m{^https?\://}) {
+                                ($scheme,$provider,$path) =
+                                    ($toolsref->{$type}->{$key}->{'url'} =~ m{^(https?\://)([^/]+)(|/.+)$});
                             } else {
-                                $providerurl = $toolsref->{$type}->{$key}->{'url'};
+                                $path = $toolsref->{$type}->{$key}->{'url'};
                             }
-                            $providerurl = &LONCAPA::map::qtunescape($providerurl);
+                            $scheme = &LONCAPA::map::qtunescape($scheme);
+                            $provider = &LONCAPA::map::qtunescape($provider);
+                            $path = &LONCAPA::map::qtunescape($path);
                             $toolsjs .= "        ltitools[$idx][$i] = '$key';\n".
                                         "        ltitoolsTarget[$idx][$i] = '$target';\n".
                                         "        ltitoolsWidth[$idx][$i] = '$width';\n".
                                         "        ltitoolsHeight[$idx][$i] = '$height';\n".
                                         "        ltitoolsLinkDef[$idx][$i] = '$linkdef';\n".
                                         "        ltitoolsExplainDef[$idx][$i] = '$explaindef';\n".
-                                        "        ltitoolsUrl[$idx][$i] = '$providerurl';\n";
+                                        "        ltitoolsProvider[$idx][$i] = '$scheme$provider';\n".
+                                        "        ltitoolsPath[$idx][$i] = '$path';\n";
                         }
                         if (ref($toolsref->{$type}->{$key}->{'crsconf'}) eq 'HASH') {
                             my $display = $toolsref->{$type}->{$key}->{'crsconf'}->{'target'};
@@ -945,7 +1052,13 @@
                             $toolsjs .= "         ltitoolsAppend[$idx][$i] = '$append';\n";
                             my $returnurl = $toolsref->{$type}->{$key}->{'crsconf'}->{'returnurl'};
                             $toolsjs .= "         ltitoolsReturnUrl[$idx][$i] = '$returnurl';\n";
+                            my $desturl = $toolsref->{$type}->{$key}->{'crsconf'}->{'desturl'};
+                            $toolsjs .= "         ltitoolsDestUrl[$idx][$i] = '$desturl';\n";
                         }
+                        my $defdest = $toolsref->{$type}->{$key}->{'defdest'};
+                        $toolsjs .= "         ltitoolsDefDest[$idx][$i] = '$defdest';\n";
+                        my $defdelay = $toolsref->{$type}->{$key}->{'defdelay'};
+                        $toolsjs .= "         ltitoolsDefDelay[$idx][$i] = '$defdelay';\n";
                     }
                     $i++;
                 }
@@ -1183,6 +1296,32 @@
         } else {
             info += '::';
         }
+        var desturldiv = prefix+'tooldesturldiv';
+        if (residx > 0) {
+            desturldiv += '_'+residx;
+        }
+        if (document.getElementById(desturldiv)) {
+            if (document.getElementById(desturldiv).style.display == 'inline') {
+                if (extform.exttooldest.length) {
+                    for (var i=0; i<extform.exttooldest.length; i++) {
+                        if (extform.exttooldest[i].checked) {
+                            if (extform.exttooldest[i].value == '1') {
+                                var dest = extform.exttooldesturl.value;
+                                dest.trim();
+                                var delay = extform.exttooldelay.value;
+                                info += ':'+escape(dest)+':'+escape(delay);
+                            } else {
+                                info += '::';
+                            }
+                        }
+                    }
+                }
+            } else {
+                info += '::';
+            }
+        } else {
+            info += '::';
+        }
         info=escape(info);
         if (residx > 0) {
             eval("extform.importdetail.value=title+'='+info+'='+residx;extform.submit();");
@@ -1366,7 +1505,13 @@
     appenddiv = prefix+'toolcrsappenddiv';
     gradablediv = prefix+'toolgradablediv';
     returnurldiv = prefix+'toolreturnurldiv';
-    providerurl = prefix+'toolproviderurl';
+    provider = prefix+'toolprovider';
+    providerpath = prefix+'toolproviderpath';
+    providerdiv = prefix+'toolproviderdiv';
+    desturldiv = prefix+'tooldesturldiv';
+    desturlfixeddiv = prefix+'tooldesturlfixeddiv';
+    fixeddest = prefix+'toolfixeddest';
+    fixeddelay = prefix+'toolfixeddelay';
     labelinput = prefix+'toolcrslabel';
     titleinput = prefix+'toolcrstitle';
     appendinput = prefix+'toolcrsappend';
@@ -1379,6 +1524,21 @@
         var toolpick = caller.options[caller.selectedIndex].value;
         $toolsjs
         if (toolpick == '') {
+            if (document.getElementById(providerdiv)) {
+                document.getElementById(providerdiv).style.display = 'none';
+            }
+            if (document.getElementById(desturldiv)) {
+                document.getElementById(desturldiv).style.display = 'none';
+            }
+            if (document.getElementById(desturlfixeddiv)) {
+                document.getElementById(desturlfixeddiv).style.display = 'none';
+            }
+            if (document.getElementById(fixeddest)) {
+                document.getElementById(fixeddest).innerHTML = '';
+            }
+            if (document.getElementById(fixeddelay)) {
+                document.getElementById(fixeddelay).innerHTML = '';
+            }
             if (document.getElementById(dispdiv)) {
                 document.getElementById(dispdiv).style.display = 'none';    
             }
@@ -1427,6 +1587,56 @@
                 if (ltitools[i].length > 0) {
                     for (var j=0; j<ltitools[i].length; j++) {
                         if (ltitools[i][j] == toolpick) {
+                            if (document.getElementById(providerdiv)) {
+                                if ((ltitoolsDestUrl[i][j]) || (ltitoolsAppend[i][j]) || (ltitoolsDefDest[i][j])) {
+                                    document.getElementById(providerdiv).style.display = 'inline';
+                                    if (document.getElementById(provider)) {
+                                        if ((ltitoolsProvider[i][j] != '') && (ltitoolsProvider[i][j] != null)) {
+                                            document.getElementById(provider).innerHTML = ltitoolsProvider[i][j];
+                                        }
+                                    }
+                                    if (document.getElementById(providerpath)) {
+                                        if ((ltitoolsPath[i][j] != '') && (ltitoolsPath[i][j] != null)) {
+                                            document.getElementById(providerpath).innerHTML = ltitoolsPath[i][j];
+                                        }
+                                    }
+                                } else {
+                                    document.getElementById(providerdiv).style.display = 'none';
+                                    if (document.getElementById(provider)) {
+                                        document.getElementById(provider).innerHTML = '';
+                                    }
+                                    if (document.getElementById(providerpath)) {
+                                        document.getElementById(providerpath).innerHTML = '';
+                                    }
+                                }
+                            }
+                            if (document.getElementById(appenddiv)) {
+                                if (ltitoolsAppend[i][j]) {
+                                    document.getElementById(appenddiv).style.display = 'inline';
+                                } else {
+                                    document.getElementById(appenddiv).style.display = 'none';
+                                }
+                            }
+                            if (document.getElementById(desturldiv)) {
+                                if (ltitoolsDestUrl[i][j]) {
+                                    document.getElementById(desturldiv).style.display = 'inline';
+                                } else {
+                                    document.getElementById(desturldiv).style.display = 'none';
+                                }
+                            }
+                            if (document.getElementById(desturlfixeddiv)) {
+                                if ((!ltitoolsDestUrl[i][j]) && (ltitoolsDefDest[i][j])) {
+                                    document.getElementById(desturlfixeddiv).style.display = 'inline';
+                                } else {
+                                    document.getElementById(desturlfixeddiv).style.display = 'none';
+                                }
+                            }
+                            if (document.getElementById(fixeddest)) {
+                                document.getElementById(fixeddest).innerHTML = ltitoolsDefDest[i][j];
+                            }
+                            if (document.getElementById(fixeddelay)) {
+                                document.getElementById(fixeddelay).innerHTML = ltitoolsDefDelay[i][j];
+                            }
                             if (document.getElementById(dispdiv)) {
                                 if (ltitoolsDisplay[i][j]) {
                                     document.getElementById(dispdiv).style.display = 'block';
@@ -1506,21 +1716,6 @@
                                     document.getElementById(titlediv).style.display = 'none';
                                 }
                             }
-                            if (document.getElementById(appenddiv)) {
-                                if (ltitoolsAppend[i][j]) {
-                                    document.getElementById(appenddiv).style.display = 'inline';
-                                    if (document.getElementById(providerurl)) {
-                                        if ((ltitoolsUrl[i][j] != '') && (ltitoolsUrl[i][j] != null)) {
-                                            document.getElementById(providerurl).innerHTML = ' ('+ltitoolsUrl[i][j]+')<br />';
-                                        }
-                                    }
-                                } else {
-                                    document.getElementById(appenddiv).style.display = 'none';
-                                    if (document.getElementById(providerurl)) {
-                                        document.getElementById(providerurl).innerHTML = '';
-                                    }
-                                }
-                            }
                             if (document.getElementById(gradablediv)) {
                                 if (supplementalflag != 1) {
                                     document.getElementById(gradablediv).style.display = 'inline';
@@ -1605,6 +1800,30 @@
     }
 }
 
+function updateDestUrl(form,detailid,divid,radioname) {
+    if ((document.getElementById(detailid)) &&
+       (document.getElementById(divid))) {
+        if (document.getElementById(divid).style.display == 'inline') {
+            var radelem = form.elements[radioname];
+            var detaildisplay = 'none';
+            if (radelem.length) {
+                for (var i=0; i<radelem.length; i++) {
+                    if (radelem[i].checked) {
+                        if (radelem[i].value == '1') {
+                            detaildisplay = 'inline';
+                        } else {
+                            detaildisplay = 'none';
+                        }
+                        break;
+                    }
+                }
+            }
+            document.getElementById(detailid).style.display = detaildisplay;
+        }
+    }
+    return;
+}
+
 ENDJS
 
 }
Index: loncom/interface/lonexttool.pm
diff -u loncom/interface/lonexttool.pm:1.28 loncom/interface/lonexttool.pm:1.29
--- loncom/interface/lonexttool.pm:1.28	Fri Jun  6 20:36:58 2025
+++ loncom/interface/lonexttool.pm	Sat Jun 14 02:50:25 2025
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Launch External Tool Provider (LTI)
 #
-# $Id: lonexttool.pm,v 1.28 2025/06/06 20:36:58 raeburn Exp $
+# $Id: lonexttool.pm,v 1.29 2025/06/14 02:50:25 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -96,7 +96,7 @@
     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
     my $chome = $env{'course.'.$env{'request.course.id'}.'.home'};
-    my ($idx,$crstool,$is_tool,%toolhash,%toolsettings);
+    my ($idx,$crstool,$is_tool,%toolhash,%toolsettings,$desturl,$delay);
 
     if ($r->uri eq "/adm/$cdom/$cnum/$marker/$exttool") {
         %toolsettings=&Apache::lonnet::dump('exttool_'.$marker,$cdom,$cnum);
@@ -136,6 +136,25 @@
                 } else {
                     $toolhash{'returnurl'} = 'none';
                 }
+                if ($toolhash{'desturl'}) {
+                    if ((ref($toolhash{'crsconf'}) eq 'HASH')) {
+                        if ($toolhash{'crsconf'}{'desturl'}) {
+                            if ((exists($toolsettings{'desturl'})) &&
+                                ($toolsettings{'desturl'} =~ m{^(https?\://|/)})) {
+                                $desturl = $toolsettings{'desturl'};
+                                if ((exists($toolsettings{'delay'})) &&
+                                    ($toolsettings{'delay'} =~ /^\d+\.?\d*$/)) {
+                                    $delay = $toolsettings{'delay'};
+                                }
+                            }
+                        } elsif ($toolhash{'defdest'} =~ m{^(https?\://|/)}) {
+                            $desturl = $toolhash{'defdest'};
+                            if ($toolhash{'defdelay'} =~ /^\d+\.?\d*$/) {
+                                $delay = $toolhash{'defdelay'};
+                            }
+                        }
+                    }
+                }
                 $is_tool = 1;
             }
         }
@@ -233,11 +252,31 @@
             if ($toolhash{'crsappend'} ne '') {
                 $url .= $toolhash{'crsappend'};
             }
+            if ($desturl ne '') {
+                my ($scheme,$provider,$path);
+                if ($url =~ m{^https?\://}) {
+                    ($scheme,$provider,$path) = ($url =~ m{^(https?\://)([^/]+)(|/.+)$});
+                    if ($desturl =~ m{^/}) {
+                        if ($path eq $desturl) {
+                            undef($desturl);
+                        } else {
+                            $desturl = $scheme.$provider.$desturl;
+                        }
+                    } elsif ($desturl =~ m{^https?\://}) {
+                        if ($url eq $desturl) {
+                            undef($desturl);
+                        }
+                    }
+                } elsif ($url eq $desturl) {
+                    undef($desturl);
+                }
+            }
             my %info = (
                          method => $toolhash{'sigmethod'},
                        );
             $r->print(&launch_html($cdom,$cnum,$crstool,$url,$idx,
-                                   $toolhash{'cipher'},$submittext,\%lti,\%info));
+                                   $toolhash{'cipher'},$submittext,
+                                   \%lti,\%info,$desturl,$delay));
         } else {
             $r->print('<div class="LC_warning">'.&mt('External Tool Unavailable').'</div>');
         }
@@ -519,33 +558,93 @@
 }
 
 sub launch_html {
-    my ($cdom,$cnum,$crstool,$url,$idx,$keynum,$submittext,$paramsref,$inforef) = @_;
+    my ($cdom,$cnum,$crstool,$url,$idx,$keynum,$submittext,$paramsref,
+        $inforef,$desturl,$delay) = @_;
     my ($status,$hashref) =
         &Apache::lonnet::sign_lti($cdom,$cnum,$crstool,'tools','launch',$url,$idx,$keynum,
                                   $paramsref,$inforef);
     unless ($status eq 'ok') {
         return '<div class="LC_warning">'.&mt('External Tool Unavailable').'</div>';
     }
-    my $action = &HTML::Entities::encode($url,'<>&"');
-    my $form = <<"END";
+    my ($action,$js_html,$header,$title,$bodytag,$form,$divsty,$delay_in_ms);
+    if ($delay ne '') {
+        $delay_in_ms = int(1000 * $delay);
+    }
+    $action = &HTML::Entities::encode($url,'<>&"');
+    $title = &mt('Launcher');
+    $header = <<"END";
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+END
+    if ($desturl) {
+        $divsty = 'none';
+        if ($delay_in_ms eq '') {
+            $delay_in_ms = 1000;
+        }
+        $bodytag = <<"END";
+<body onload="setTimeout(document.LCltiLaunchForm.submit(),$delay_in_ms);">
+END
+    } else {
+        $divsty = 'block';
+        $bodytag = <<"END";
 <body>
-<div id="LCltiLaunch">
+END
+    }
+    $form = <<"END";
+<div id="LCltiLaunch" style="display:$divsty;">
 <form name="LCltiLaunchForm" id="LCltiLaunchFormId" action="$action" method="post" encType="application/x-www-form-urlencoded">
 END
     if (ref($hashref) eq 'HASH') {
         foreach my $item (keys(%{$hashref})) {
             my $type = 'hidden';
             if ($item eq 'basiclti_submit') {
-                $type = 'submit';
+                unless ($desturl) {
+                    $type = 'submit';
+                }
             }
             $form .= '<input type="'.$type.'" name="'.$item.'" value="'.$hashref->{$item}.'" id="id_'.$item.'" />'."\n";
         }
     }
     $form .= "</form></div>\n";
-    $form .= <<"ENDJS";
+    my $footer = <<"END";
+</body>
+</html>
+END
+    if ($desturl) {
+        $js_html = $header.$bodytag.$form.$footer;
+        &js_escape(\$js_html);
+	&js_escape(\$desturl);
+        return <<"END";
+$header
+<head>
+<title>$title</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="google" content="notranslate" />
+</head>
+<body onload="javascript:addcontent();">
+<iframe id="LC_launcher_iframe" style="position: absolute; width:0; height:0; border:0;">
+</iframe>
 <script type="text/javascript">
+// <![CDATA[
+function addcontent() {
+    if (document.getElementById('LC_launcher_iframe')) {
+        document.getElementById('LC_launcher_iframe').src = "data:text/html;charset=utf-8," + escape("$js_html");
+    }
+    setTimeout(() => {
+        document.location.href="$desturl";
+    }, $delay_in_ms);
+}
+// ]]>
+</script>
+$footer
+END
+    } else {
+        return <<"END";
+$header
+$bodytag
+$form
+<script type="text/javascript">
+// <![CDATA[
     document.getElementById("LCltiLaunch").style.display = "none";
     nei = document.createElement('input');
     nei.setAttribute('type','hidden');
@@ -553,10 +652,11 @@
     nei.setAttribute('value','$submittext');
     document.getElementById("LCltiLaunchFormId").appendChild(nei);
     document.LCltiLaunchForm.submit();
- </script>
-ENDJS
-    $form .= "</body></html>\n";
-    return $form;
+// ]]>
+</script>
+$footer
+END
+    }
 }
 
 sub gradabletool_access_check {


More information about the LON-CAPA-cvs mailing list