[LON-CAPA-cvs] cvs: rat /client parameter.html loncom loncapa_apache.conf loncom/interface courseprefs.pm loncommon.pm lonconfigsettings.pm lonnavmaps.pm lonparmset.pm loncom/lti ltiauth.pm

raeburn raeburn at source.lon-capa.org
Wed Aug 4 15:59:12 EDT 2021


raeburn		Wed Aug  4 19:59:12 2021 EDT

  Modified files:              
    /loncom/interface	courseprefs.pm lonconfigsettings.pm loncommon.pm 
                     	lonnavmaps.pm lonparmset.pm 
    /loncom	loncapa_apache.conf 
    /loncom/lti	ltiauth.pm 
    /rat/client	parameter.html 
  Log:
  - Bug 6907 Content in a course can be set to be deep-link only.
    - deeplink parameter has 5 components:
      state, others, listing, scope, protect and menus.
    - deeplink protection for launch from non-LON-CAPA LTI-enabled systems
      uses /adm/launch/tiny/$dom/$uniqueID, and key and secret used by launcher
      can be set in a course (by CC), or for domain (by DC).
  
  
-------------- next part --------------
Index: loncom/interface/courseprefs.pm
diff -u loncom/interface/courseprefs.pm:1.92 loncom/interface/courseprefs.pm:1.93
--- loncom/interface/courseprefs.pm:1.92	Mon Jun  7 02:19:51 2021
+++ loncom/interface/courseprefs.pm	Wed Aug  4 19:59:10 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set configuration settings for a course
 #
-# $Id: courseprefs.pm,v 1.92 2021/06/07 02:19:51 raeburn Exp $
+# $Id: courseprefs.pm,v 1.93 2021/08/04 19:59:10 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -365,9 +365,15 @@
     }
 
     my %values=&Apache::lonnet::dump('environment',$cdom,$cnum);
+    my %courselti=&Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1);
+    if ($courselti{'lock'}) {
+        delete($courselti{'lock'});
+    }
+    $values{'linkprotection'} = \%courselti;
     my @prefs_order = ('courseinfo','localization','feedback','discussion',
                        'classlists','appearance','grading','printouts',
-                       'menuitems','spreadsheet','bridgetasks','lti','other');
+                       'menuitems','linkprotection','spreadsheet','bridgetasks',
+                       'lti','other');
 
     my %prefs = (
         'courseinfo' =>
@@ -557,6 +563,14 @@
                          menucollections => 'Menu collections',
                                  },
                    },
+        'linkprotection' =>
+                   {
+                     text => 'Link protection',
+                     help => 'Course_Prefs_Linkprotection',
+                     header => [{col1 => 'Item',
+                                 col2 => 'Settings',
+                                }],
+                   },
         'other' =>
                   { text => 'Other settings',
                     help => 'Course_Prefs_Other',
@@ -764,6 +778,8 @@
         $output .= &print_lti($cdom,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit);
     } elsif ($action eq 'menuitems') {
         $output .= &print_menuitems('bottom',$cdom,$settings,$itemtext,\$rowtotal,$crstype,$noedit);
+    } elsif ($action eq 'linkprotection') {
+        $output .= &print_linkprotection($cdom,$settings,\$rowtotal,$crstype,$noedit);
     } elsif ($action eq 'other') {
         $output .= &print_other($cdom,$settings,$allitems,\$rowtotal,$crstype,$noedit);
     }
@@ -776,8 +792,8 @@
 }
 
 sub process_changes {
-    my ($cdom,$action,$values,$item,$changes,$allitems,$disallowed,$crstype) = @_;
-    my %newvalues;
+    my ($cdom,$cnum,$action,$values,$item,$changes,$allitems,$disallowed,$crstype) = @_;
+    my (%newvalues,%courselti,$errors);
     if (ref($item) eq 'HASH') {
         if (ref($changes) eq 'HASH') {
             my @ordered;
@@ -794,6 +810,21 @@
                         }
                     }
                 }
+            } elsif ($action eq 'linkprotection') {
+                if (ref($values->{'linkprotection'}) eq 'HASH') {
+                    foreach my $id (keys(%{$values->{'linkprotection'}})) {
+                        if ($id =~ /^\d+$/) {
+                            push(@ordered,$id);
+                            unless (ref($values->{'linkprotection'}->{$id}) eq 'HASH') {
+                                $courselti{$id} = '';
+                            }
+                        }
+                    }
+                }
+                @ordered = sort { $a <=> $b } @ordered;
+                if (($env{'form.linkprot_add'}) && ($env{'form.linkprot_maxnum'} =~ /^\d+$/)) {
+                    push(@ordered,$env{'form.linkprot_maxnum'});
+                }
             } elsif (ref($item->{'ordered'}) eq 'ARRAY') {
                 if ($action eq 'courseinfo') {
                     my ($can_toggle_cat,$can_categorize) =
@@ -931,6 +962,78 @@
                     } elsif ($values->{'menucollections'}) {
                         $changes->{'menucollections'} = '';
                     }
+                } elsif ($action eq 'linkprotection') {
+                    my %menutitles = &ltimenu_titles();
+                    my (@items,%deletions,%itemids,%haschanges);
+                    if ($env{'form.linkprot_add'}) {
+                        my $name = $env{'form.linkprot_name_add'};
+                        $name =~ s/(`)/'/g;
+                        my ($newid,$error) = &get_courselti_id($cdom,$cnum,$name);
+                        if ($newid) {
+                            $itemids{'add'} = $newid;
+                            push(@items,'add');
+                            $haschanges{$newid} = 1;
+                        } else {
+                            $errors .= '<span class="LC_error">'.
+                                       &mt('Failed to acquire unique ID for link protection').
+                                       '</span>';
+                        }
+                    }
+                    if (ref($values->{'linkprotection'}) eq 'HASH') {
+                        my @todelete = &Apache::loncommon::get_env_multiple('form.linkprot_del');
+                        my $maxnum = $env{'form.linkprot_maxnum'};
+                        for (my $i=0; $i<=$maxnum; $i++) {
+                            my $itemid = $env{'form.linkprot_id_'.$i};
+                            $itemid =~ s/\D+//g;
+                            if ($itemid) {
+                                if (ref($values->{'linkprotection'}->{$itemid}) eq 'HASH') {
+                                    push(@items,$i);
+                                    $itemids{$i} = $itemid;
+                                    if ((@todelete > 0) && (grep(/^$i$/, at todelete))) {
+                                        $deletions{$itemid} = $values->{'linkprotection'}->{$itemid}->{'name'};
+                                    }
+                                }
+                            }
+                        }
+
+                    }
+                    foreach my $idx (@items) {
+                        my $itemid = $itemids{$idx};
+                        next unless ($itemid);
+                        if (exists($deletions{$itemid})) {
+                            $courselti{$itemid} = $deletions{$itemid};
+                            $haschanges{$itemid} = 1;
+                            next;
+                        }
+                        my %current;
+                        if (ref($values->{'linkprotection'}) eq 'HASH') {
+                            if (ref($values->{'linkprotection'}->{$itemid}) eq 'HASH') {
+                                foreach my $key (keys(%{$values->{'linkprotection'}->{$itemid}})) {
+                                    $current{$key} = $values->{'linkprotection'}->{$itemid}->{$key};
+                                }
+                            }
+                        }
+                        foreach my $inner ('name','key','secret','lifetime','version') {
+                            my $formitem = 'form.linkprot_'.$inner.'_'.$idx;
+                            $env{$formitem} =~ s/(`)/'/g;
+                            if ($inner eq 'lifetime') {
+                                $env{$formitem} =~ s/[^\d.]//g;
+                            }
+                            unless ($idx eq 'add') {
+                                if ($current{$inner} ne $env{$formitem}) {
+                                    $haschanges{$itemid} = 1;
+                                }
+                            }
+                            if ($env{$formitem} ne '') {
+                                $courselti{$itemid}{$inner} = $env{$formitem};
+                            }
+                        }
+                    }
+                    if (keys(%haschanges)) {
+                        foreach my $entry (keys(%haschanges)) {
+                            $changes->{$entry} = $courselti{$entry};
+                        }
+                    }
                 } else {
                     foreach my $entry (@ordered) {
                         if ($entry eq 'cloners') {
@@ -971,7 +1074,7 @@
                                     my $clonedom = $env{'form.cloners_newdom'};
                                     if (&check_clone($clonedom,$disallowed) eq 'ok') {
                                         my $newdom = '*:'.$env{'form.cloners_newdom'};
-                                        if (@clonedoms) { 
+                                        if (@clonedoms) {
                                             if (!grep(/^\Q$newdom\E$/, at clonedoms)) {
                                                 $newvalues{$entry} .= ','.$newdom;
                                             }
@@ -1417,7 +1520,51 @@
             }
         }
     }
-    return;
+    return $errors;
+}
+
+sub get_courselti_id {
+    my ($cdom,$cnum,$name) = @_;
+    # get lock on lti db in course
+    my $lockhash = {
+                      lock => $env{'user.name'}.
+                              ':'.$env{'user.domain'},
+                   };
+    my $tries = 0;
+    my $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum);
+    my ($id,$error);
+    while (($gotlock ne 'ok') && ($tries<10)) {
+        $tries ++;
+        sleep (0.1);
+        $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum);
+    }
+    if ($gotlock eq 'ok') {
+        my %currids  = &Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1);
+        if ($currids{'lock'}) {
+            delete($currids{'lock'});
+            if (keys(%currids)) {
+                my @curr = sort { $a <=> $b } keys(%currids);
+                if ($curr[-1] =~ /^\d+$/) {
+                    $id = 1 + $curr[-1];
+                } else {
+                    $id = 1;
+                }
+            } else {
+                $id = 1;
+            }
+            if ($id) {
+                unless (&Apache::lonnet::newput('lti',{ $id => $name },$cdom,$cnum) eq 'ok') {
+                    $error = 'nostore';
+                }
+            } else {
+                $error = 'nonumber';
+            }
+        }
+        my $dellockoutcome = &Apache::lonnet::del('lti',['lock'],$cdom,$cnum);
+    } else {
+        $error = 'nolock';
+    }
+    return ($id,$error);
 }
 
 sub get_sec_str {
@@ -1462,8 +1609,12 @@
 sub store_changes {
     my ($cdom,$cnum,$prefs_order,$actions,$prefs,$values,$changes,$crstype) = @_;
     my ($chome,$output);
-    my (%storehash, at delkeys, at need_env_update, at oldcloner);
+    my (%storehash, at delkeys, at need_env_update, at oldcloner,%oldlinkprot);
     if ((ref($values) eq 'HASH') && (ref($changes) eq 'HASH')) {
+        if (ref($values->{'linkprotection'}) eq 'HASH') {
+            %oldlinkprot = %{$values->{'linkprotection'}};
+        }
+        delete($values->{'linkprotection'});
         %storehash = %{$values};
     } else {
         if ($crstype eq 'Community') {
@@ -1473,6 +1624,20 @@
         }
         return $output;
     }
+    my ($numchanges,$skipstore);
+    if (ref($changes) eq 'HASH') {
+        $numchanges = scalar(keys(%{$changes}));
+        if (($numchanges == 1) && (exists($changes->{'linkprotection'}))) {
+            $skipstore = 1;
+        } elsif (!$numchanges) {
+            if ($crstype eq 'Community') {
+                $output = &mt('No changes made to community settings.');
+            } else {
+                $output = &mt('No changes made to course settings.');
+            }
+            return $output;
+        }
+    }
     my %yesno = (
                  hidefromcat           => '1',
                  problem_stream_switch => '1',
@@ -1485,7 +1650,7 @@
         if (grep(/^\Q$item\E$/,@{$actions})) {
             $output .= '<h3>'.&mt($prefs->{$item}{'text'}).'</h3>';
             if (ref($changes->{$item}) eq 'HASH') {
-                if (keys(%{$changes->{$item}}) > 0) {
+                if ((keys(%{$changes->{$item}}) > 0) || ($item eq 'linkprotection')) {
                     $output .= &mt('Changes made:').'<ul style="list-style:none;">';
                     if ($item eq 'other') {
                         foreach my $key (sort(keys(%{$changes->{$item}}))) {
@@ -1498,6 +1663,41 @@
                                            "'$storehash{$key}'")).'</li>';
                             }
                         }
+                    } elsif ($item eq 'linkprotection') {
+                        if (&Apache::lonnet::put('lti',$changes->{'linkprotection'},$cdom,$cnum,1) eq 'ok') {
+                            my $hashid=$cdom.'_'.$cnum;
+                            &Apache::lonnet::devalidate_cache_new('courselti',$hashid);
+                            foreach my $itemid (sort { $a <=> $b } %{$changes->{'linkprotection'}}) {
+                                if (ref($changes->{'linkprotection'}->{$itemid}) eq 'HASH') {
+                                    my %values = %{$changes->{'linkprotection'}->{$itemid}};
+                                    my %desc = &linkprot_names();
+                                    my $display;
+                                    foreach my $title ('name','lifetime','version','key','secret') {
+                                        if ($title eq 'secret') {
+                                            my $length = length($values{$title});
+                                            $display .= $desc{$title}.': '.('*' x $length);
+                                        } elsif ($title eq 'version') {
+                                            if ($values{$title} eq 'LTI-1p0') {
+                                                $display .= $desc{$title}.': 1.1, ';
+                                            }
+                                        } else {
+                                            $display .= $desc{$title}.': '.$values{$title}.', ';
+                                        }
+                                    }
+                                    $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]','<i>'.$itemid.'</i>',
+                                               "'$display'")).'</li>';
+                                } elsif (ref($oldlinkprot{$itemid}) eq 'HASH') {
+                                    my $oldname = $oldlinkprot{$itemid}{'name'};
+                                    $output .= '<li>'.&Apache::lonhtmlcommon::confirm_success(&mt('Deleted setting for [_1]','<i>'."$itemid ($oldname)".'</i>')).'</li>';
+                                }
+                            }
+                        } else {
+                            $output .= '<li>'.
+                                       '<span class="LC_error">'.
+                                       &mt('An error occurred when saving changes to link protection settings, which remain unchanged.').
+                                       '</span>'.
+                                       '</li>';
+                        }
                     } else {
                         if (ref($prefs->{$item}->{'ordered'}) eq 'ARRAY') {
                             my @settings = @{$prefs->{$item}->{'ordered'}};
@@ -1785,6 +1985,9 @@
             }
         }
     }
+    if ($skipstore) {
+        return $output;
+    }
     if (&Apache::lonnet::put('environment',\%storehash,$cdom,$cnum) eq 'ok') {
         if (ref($changes) eq 'HASH') {
             if (ref($changes->{'courseinfo'}) eq 'HASH') {
@@ -5039,6 +5242,97 @@
     return $output;
 }
 
+sub print_linkprotection {
+    my ($cdom,$settings,$rowtotal,$crstype,$noedit) = @_;
+    unless (ref($settings) eq 'HASH') {
+        return;
+    }
+
+    my %linkprotection;
+    my $count = 0;
+    my $next = 1;
+    my ($datatable,$disabled,$css_class);
+    if ($noedit) {
+        $disabled = ' disabled="disabled"';
+    }
+    my %lt = &linkprot_names();
+    my $itemcount = 0;
+
+    if (ref($settings->{'linkprotection'}) eq 'HASH') {
+        if (keys(%{$settings->{'linkprotection'}})) {
+            my @current = sort { $a <=> $b } keys(%{$settings->{'linkprotection'}});
+            $next += $current[-1];
+            for (my $i=0; $i<@current; $i++) {
+                my $num = $current[$i];
+                my %values;
+                if (ref($settings->{'linkprotection'}->{$num}) eq 'HASH') {
+                    %values = %{$settings->{'linkprotection'}->{$num}};
+                } else {
+                    next;
+                }
+                my $selected;
+                if (($values{'version'} eq 'LTI-1p0') || ($values{'version'} eq '')) {
+                    $selected = ' selected="selected"';
+                }
+                $css_class = $itemcount%2?' class="LC_odd_row"':'';
+                $datatable .=
+                    '<tr '.$css_class.'><td><span class="LC_nobreak">'.
+                    '<label><input type="checkbox" name="linkprot_del" value="'.$i.'"'.$disabled.' />'.
+                    &mt('Delete?').'</label></span></td>'.
+                    '<td><span class="LC_nobreak">'.$lt{'name'}.
+                    ':<input type="text" size="15" name="linkprot_name_'.$i.'" value="'.$values{'name'}.'"'.$disabled.' /></span> '.
+                    (' 'x2).
+                    '<span class="LC_nobreak">'.$lt{'version'}.':<select name="linkprot_version_'.$i.'">'.
+                    '<option value="LTI-1p0" '.$selected.'>1.1</option></select></span> '."\n".
+                    (' 'x2).
+                    '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" name="linkprot_lifetime_'.$i.'"'.
+                    'value="'.$values{'lifetime'}.'" size="3"'.$disabled.' /></span>'.
+                    '<br /><br />'.
+                    '<span class="LC_nobreak">'.$lt{'key'}.
+                    ':<input type="text" size="25" name="linkprot_key_'.$i.'" value="'.$values{'key'}.'"'.$disabled.' /></span> '.
+                    (' 'x2).
+                    '<span class="LC_nobreak">'.$lt{'secret'}.':'.
+                    '<input type="password" size="20" name="linkprot_secret_'.$i.'" value="'.$values{'secret'}.'"'.$disabled.' />'.
+                    '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.linkprot_secret_'.$i.'.type='."'text'".' } else { this.form.linkprot_secret_'.$i.'.type='."'password'".' }" />'.&mt('Visible input').'</label>'.
+                    '<input type="hidden" name="linkprot_id_'.$i.'" value="'.$num.'" /></span>'.
+                    '</td></tr>';
+                $itemcount ++;
+            }
+        }
+    }
+    $css_class = $itemcount%2?' class="LC_odd_row"':'';
+    $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'."\n".
+                  '<input type="hidden" name="linkprot_maxnum" value="'.$next.'" />'."\n".
+                  '<input type="checkbox" name="linkprot_add" value="1" />'.&mt('Add').'</span></td>'."\n".
+                  '<td>'.
+                  '<span class="LC_nobreak">'.$lt{'name'}.
+                  ':<input type="text" size="15" name="linkprot_name_add" value="" /></span> '."\n".
+                  (' 'x2).
+                  '<span class="LC_nobreak">'.$lt{'version'}.':<select name="linkprot_version_add">'.
+                  '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '."\n".
+                  (' 'x2).
+                  '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" size="3" name="linkprot_lifetime_add" value="300" /></span> '."\n".
+                  '<br /><br />'.
+                  '<span class="LC_nobreak">'.$lt{'key'}.':<input type="text" size="25" name="linkprot_key_add" value="" /></span> '."\n".
+                  (' 'x2).
+                  '<span class="LC_nobreak">'.$lt{'secret'}.':<input type="password" size="20" name="linkprot_secret_add" value="" />'.
+                  '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.linkprot_secret_add.type='."'text'".' } else { this.form.linkprot_secret_add.type='."'password'".' }" />'.&mt('Visible input').'</label></span> '."\n".
+                  '</td></tr>';
+    $$rowtotal ++;
+    return $datatable;;
+}
+
+sub linkprot_names {
+    my %lt = &Apache::lonlocal::texthash(
+                                          'version'   => 'LTI Version',
+                                          'key'       => 'Key',
+                                          'lifetime'  => 'Nonce lifetime (s)',
+                                          'name'      => 'Launcher Application Name',
+                                          'secret'    => 'Secret',
+                                        );
+    return %lt;
+}
+
 sub print_other {
     my ($cdom,$settings,$allitems,$rowtotal,$crstype,$noedit) = @_;
     unless ((ref($settings) eq 'HASH') && (ref($allitems) eq 'ARRAY')) {
@@ -5061,7 +5355,7 @@
                             input => 'textbox',
                             size  => '30',
                            };
-    my $output = &make_item_rows($cdom,\%items,\@ordered,$settings,$rowtotal,$crstype,'other',$noedit);
+    return &make_item_rows($cdom,\%items,\@ordered,$settings,$rowtotal,$crstype,'other',$noedit);
 }
 
 sub get_other_items {
Index: loncom/interface/lonconfigsettings.pm
diff -u loncom/interface/lonconfigsettings.pm:1.49 loncom/interface/lonconfigsettings.pm:1.50
--- loncom/interface/lonconfigsettings.pm:1.49	Fri Jul 30 13:48:20 2021
+++ loncom/interface/lonconfigsettings.pm	Wed Aug  4 19:59:10 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: lonconfigsettings.pm,v 1.49 2021/07/30 13:48:20 raeburn Exp $
+# $Id: lonconfigsettings.pm,v 1.50 2021/08/04 19:59:10 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -355,7 +355,7 @@
        {href=>"javascript:changePage(document.$phase,'$phase')",
         text=>"Updated"});
     &print_header($r,$phase,$context,undef,$container);
-    my ($crstype,%lastact);
+    my ($crstype,%lastact,$errors);
     if ($context eq 'course') {
         $crstype = &Apache::loncommon::course_type();
     }
@@ -369,9 +369,10 @@
                                           $confname,$item,$roles,$values,\%lastact));
                 } else {
                     $changes{$item} = {};
-                    &Apache::courseprefs::process_changes($dom,$item,$values,
-                                                          $prefs->{$item},$changes{$item},
-                                                          $allitems,\%disallowed,$crstype);
+                    $errors =
+                        &Apache::courseprefs::process_changes($dom,$item,$values,
+                                                              $prefs->{$item},$changes{$item},
+                                                              $allitems,\%disallowed,$crstype);
                     if (keys(%{$changes{$item}}) > 0) {
                         $numchanged ++;
                     }
@@ -403,6 +404,9 @@
             }
             $r->print('</p>');
         }
+        if ($errors) {
+            $r->print('<p>'.$errors.'</p>');
+        }
     }
     $r->print('<p>');
     my $footer_text = 'Back to configuration display';
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1362 loncom/interface/loncommon.pm:1.1363
--- loncom/interface/loncommon.pm:1.1362	Mon Jul 19 15:48:26 2021
+++ loncom/interface/loncommon.pm	Wed Aug  4 19:59:10 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1362 2021/07/19 15:48:26 raeburn Exp $
+# $Id: loncommon.pm,v 1.1363 2021/08/04 19:59:10 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -9170,7 +9170,7 @@
                 }
             }
             if ($deeplink ne '') {
-                my ($listed,$scope,$access,$display) = split(/,/,$deeplink);
+                my ($state,$others,$listed,$scope,$protect,$display) = split(/,/,$deeplink);
                 if ($display =~ /^\d+$/) {
                     $deeplinkmenu = 1;
                     $menucoll = $display;
Index: loncom/interface/lonnavmaps.pm
diff -u loncom/interface/lonnavmaps.pm:1.553 loncom/interface/lonnavmaps.pm:1.554
--- loncom/interface/lonnavmaps.pm:1.553	Mon Jul 19 15:48:26 2021
+++ loncom/interface/lonnavmaps.pm	Wed Aug  4 19:59:10 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Navigate Maps Handler
 #
-# $Id: lonnavmaps.pm,v 1.553 2021/07/19 15:48:26 raeburn Exp $
+# $Id: lonnavmaps.pm,v 1.554 2021/08/04 19:59:10 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1858,7 +1858,8 @@
                     }
                 } else {
                     my $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink");
-                    if ($deeplink =~ /^(absent|grades),/) {
+                    my ($state,$others,$listed) = split(/,/,$deeplink);
+                    if (($listed eq 'absent') || ($listed eq 'grades')) {
                         if ($userCanSeeHidden) {
                             $args->{'mapUnlisted'} = 1;
                         } else {
@@ -3662,7 +3663,7 @@
 before anything else. deeplinklisted if true (default false), will
 check "listed" status of a resource with a deeplink, and unless "absent"
 will exclude deeplink checking when retrieving the browsePriv from
-lonnet::allowed(). 
+lonnet::allowed().
 
 Thus, by default, only top-level resources will be shown. Change the
 condition to a 1 without changing the hash, and all resources will be
@@ -5192,7 +5193,7 @@
     my ($self,$caller,$action) = @_;
     my $deeplink = $self->parmval("deeplink");
     if ($deeplink) {
-        my ($listed,$scope,$access) = split(/,/,$deeplink);
+        my ($state,$others,$listed,$scope) = split(/,/,$deeplink);
         if ($action eq 'getlisted') {
             return $listed;
         }
@@ -5222,7 +5223,7 @@
                 }
             }
         }
-        unless (($caller eq 'sequence') || ($access eq 'any')) {
+        unless (($caller eq 'sequence') || ($state eq 'both')) {
             return $listed;
         }
     }
@@ -6435,7 +6436,7 @@
 						    $self->{SYMB},undef,
                                                     undef,$noblockcheck,
                                                     undef,$nodeeplinkcheck,
-                                                    $nodeeplinkout);  
+                                                    $nodeeplinkout);
 }
 
 =pod
Index: loncom/interface/lonparmset.pm
diff -u loncom/interface/lonparmset.pm:1.600 loncom/interface/lonparmset.pm:1.601
--- loncom/interface/lonparmset.pm:1.600	Sat Jun 12 22:06:32 2021
+++ loncom/interface/lonparmset.pm	Wed Aug  4 19:59:10 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set parameters for assessments
 #
-# $Id: lonparmset.pm,v 1.600 2021/06/12 22:06:32 raeburn Exp $
+# $Id: lonparmset.pm,v 1.601 2021/08/04 19:59:10 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1245,9 +1245,10 @@
     var ipallowRegExp = /^setipallow_/;
     var ipdenyRegExp = /^setipdeny_/; 
     var deeplinkRegExp = /^deeplink_/;
-    var dlListScopeRegExp = /^deeplink_(listing|scope)_/; 
-    var dlLinkUrlsRegExp = /^deeplink_urls_/;
-    var dlLtiRegExp = /^deeplink_lti_/;
+    var dlListScopeRegExp = /^deeplink_(state|others|listing|scope)_/; 
+    var dlLinkProtectRegExp = /^deeplink_protect_/;
+    var dlLtidRegExp = /^deeplink_ltid_/;
+    var dlLticRegExp = /^deeplink_ltic_/;
     var dlKeyRegExp = /^deeplink_key_/;
     var dlMenusRegExp = /^deeplink_menus_/;
     var dlCollsRegExp = /^deeplink_colls_/;
@@ -1314,9 +1315,9 @@
                             }
                             document.parmform.elements['set_'+identifier].value += possdeeplink;
                         }
-                    } else if (dlLinkUrlsRegExp.test(name)) {
+                    } else if (dlLinkProtectRegExp.test(name)) {
                         if (document.parmform.elements[i].checked) {
-                            var identifier =  name.replace(dlLinkUrlsRegExp,'');
+                            var identifier =  name.replace(dlLinkProtectRegExp,'');
                             var posslinkurl = document.parmform.elements[i].value;
                             posslinkurl = posslinkurl.replace(/^\s+|\s+$/g,'');
                             if (document.parmform.elements['set_'+identifier].value) {
@@ -1324,25 +1325,41 @@
                             }
                             document.parmform.elements['set_'+identifier].value += posslinkurl;
                         }
-                    } else if (dlLtiRegExp.test(name)) {
-                        var identifier = name.replace(dlLtiRegExp,'');
-                        if (isRadioSet('deeplink_urls_'+identifier,'lti')) {
-                            var posslti = document.parmform.elements[i].value;
-                            posslti = posslti.replace(/\D+/g,'');
-                            if (posslti.length) {
+                    } else if (dlLtidRegExp.test(name)) {
+                        var identifier = name.replace(dlLtidRegExp,'');
+                        if (isRadioSet('deeplink_protect_'+identifier,'ltid')) {
+                            var possltid = document.parmform.elements[i].value;
+                            possltid = possltid.replace(/\D+/g,'');
+                            if (possltid.length) {
                                 if (document.parmform.elements['set_'+identifier].value) {
-                                    posslti = ':'+posslti;
+                                    possltid = ':'+possltid;
                                 }
-                                document.parmform.elements['set_'+identifier].value += posslti;
+                                document.parmform.elements['set_'+identifier].value += possltid;
                             } else {
                                 document.parmform.elements['set_'+identifier].value = '';
-                                alert("A link type of 'deep with LTI launch' was selected but no LTI launcher was selected.\nPlease select one, or choose a different supported link type.");
+                                alert("A link type of 'domain LTI launch' was selected but no domain LTI launcher was selected.\nPlease select one, or choose a different supported link type.");
                                 return false;  
                             }
                         }
+                    } else if (dlLticRegExp.test(name)) {
+                        var identifier = name.replace(dlLticRegExp,'');
+                        if (isRadioSet('deeplink_protect_'+identifier,'ltic')) {
+                            var possltic = document.parmform.elements[i].value;
+                            possltic = possltic.replace(/\D+/g,'');
+                            if (possltic.length) {
+                                if (document.parmform.elements['set_'+identifier].value) {
+                                    possltic = ':'+possltic;
+                                }
+                                document.parmform.elements['set_'+identifier].value += possltic;
+                            } else {
+                                document.parmform.elements['set_'+identifier].value = '';
+                                alert("A link type of 'course LTI launch' was selected but no course LTI launcher was selected.\nPlease select one, or choose a different supported link type.");
+                                return false;
+                            }
+                        }
                     } else if (dlKeyRegExp.test(name)) {
                         var identifier = name.replace(dlKeyRegExp,'');
-                        if (isRadioSet('deeplink_urls_'+identifier,'key')) {
+                        if (isRadioSet('deeplink_protect_'+identifier,'key')) {
                             var posskey = document.parmform.elements[i].value;
                             posskey = posskey.replace(/^\s+|\s+$/g,'');
                             var origlength = posskey.length;
@@ -1478,32 +1495,44 @@
         if (document.getElementById('deeplink_key_'+item+'_'+key)) {
             keybox = document.getElementById('deeplink_key_'+item+'_'+key);
         }
-        var divoption;
-        if (item == 'urls') {
-            divoption = 'lti'
+        var divoptions = new Array();
+        if (item == 'protect') {
+            divoptions = ['ltic','ltid'];
         } else {
             if (item == 'menus') {
-                divoption = 'colls';
+                divoptions = ['colls'];
             }
         }
-        var seldiv;
-        if (document.getElementById('deeplinkdiv_'+divoption+'_'+item+'_'+key)) {
-            seldiv = document.getElementById('deeplinkdiv_'+divoption+'_'+item+'_'+key);
+        var seldivs = new Array();
+        if ((item == 'protect') || (item == 'menus')) {
+            for (var i=0; i<divoptions.length; i++) {
+                if (document.getElementById('deeplinkdiv_'+divoptions[i]+'_'+item+'_'+key)) {
+                    seldivs[i] = document.getElementById('deeplinkdiv_'+divoptions[i]+'_'+item+'_'+key);
+                } else {
+                    seldivs[i] = '';
+                }
+            }
         }
         for (var i=0; i<radios.length; i++) {
             if (radios[i].checked) {
-                if (radios[i].value == divoption) {
-                    seldiv.style.display = 'inline-block';
-                    if (item == 'urls') {
-                        keybox.type = 'hidden';
-                        keybox.value = '';
-                    }
-                } else {
-                    if (seldiv != '') {
-                        seldiv.style.display = 'none';
-                        form['deeplink_'+divoption+'_'+key].selectedIndex = 0;
+                if ((item == 'protect') || (item == 'menus')) {
+                    for (var j=0; j<seldivs.length; j++) {
+                        if (radios[i].value == divoptions[j]) {
+                            if (seldivs[j] != '') {
+                                seldivs[j].style.display = 'inline-block';
+                            }
+                            if (item == 'protect') {
+                                keybox.type = 'hidden';
+                                keybox.value = '';
+                            }
+                        } else {
+                            if (seldivs[j] != '') {
+                                seldivs[j].style.display = 'none';
+                                form['deeplink_'+divoptions[j]+'_'+key].selectedIndex = 0;
+                            }
+                        }
                     }
-                    if (item == 'urls') {
+                    if (item == 'protect') {
                         if (radios[i].value == 'key') {
                             keybox.type = 'text';
                         } else {
@@ -1704,23 +1733,37 @@
         $effparm_rec = 1;
     }
     if ($parmname eq 'deeplink') {
-        my %posslti;
+        my ($domltistr,$crsltistr);
         my %lti =
             &Apache::lonnet::get_domain_lti($env{'course.'.$env{'request.course.id'}.'.domain'},
                                             'provider');
-        foreach my $item (keys(%lti)) {
-            if (ref($lti{$item}) eq 'HASH') {
-                unless ($lti{$item}{'requser'}) {
-                    $posslti{$item} = $lti{$item}{'consumer'};
+        if (keys(%lti)) {
+            foreach my $item (sort { $a <=> $b }  (keys(%lti))) {
+                if (ref($lti{$item}) eq 'HASH') {
+                    unless ($lti{$item}{'requser'}) {
+                        $domltistr .= $item.':'.&escape(&escape($lti{$item}{'consumer'})).',';
+                    }
                 }
             }
+            $domltistr =~ s/,$//;
+            if ($domltistr) {
+                $extra = 'ltid_'.$domltistr;
+            }
         }
-        if (keys(%posslti)) {
-            $extra = 'lti_';
-            foreach my $lti (sort { $a <=> $b } keys(%posslti)) {
-                $extra .= $lti.':'.&escape(&escape($posslti{$lti})).',';
+        my %courselti = &Apache::lonnet::get_course_lti($cnum,$cdom);
+        if (keys(%courselti)) {
+            foreach my $item (sort { $a <=> $b } keys(%courselti)) {
+                if (($item =~ /^\d+$/) && (ref($courselti{$item}) eq 'HASH')) {
+                    $crsltistr .= $item.':'.&escape(&escape($courselti{$item}{'name'})).',';
+                }
+            }
+            $crsltistr =~ s/,$//;
+            if ($crsltistr) {
+                if ($extra) {
+                    $extra .= '&';
+                }
+                $extra .= 'ltic_'.$crsltistr;
             }
-            $extra =~ s/,$//;
         }
         if ($env{'course.'.$env{'request.course.id'}.'.menucollections'}) {
             my @colls;
@@ -4982,21 +5025,30 @@
 sub string_deeplink_selector {
     my ($thiskey, $showval, $readonly) = @_;
     my (@components,%values, at current,%titles,%options,%optiontext,%defaults,
-        %selectnull,%posslti, at possmenus);
-    @components = ('listing','scope','urls','menus');
+        %selectnull,%domlti,%crslti, at possmenus);
+    @components = ('state','others','listing','scope','protect','menus');
     %titles = &Apache::lonlocal::texthash (
+                  state   => 'Access status',
+                  others  => 'Hide other resources',
                   listing => 'In Contents and/or Gradebook',
                   scope   => 'Access scope for link',
-                  urls    => 'Supported link types',
+                  protect => 'Link protection',
                   menus   => 'Menu Items Displayed',
               );
     %options = (
+                   state   => ['only','off','both'],
+                   others  => ['hide','unhide'],
                    listing => ['full','absent','grades','details','datestatus'],
                    scope   => ['res','map','rec'],
-                   urls    => ['any','only','key','lti'],
+                   protect => ['none','key','ltid','ltic'],
                    menus   => ['std','colls'],
                );
     %optiontext = &Apache::lonlocal::texthash (
+                    only       => 'deep only',
+                    off        => 'deeplink off',
+                    both       => 'regular + deep',
+                    hide       => 'Hidden',
+                    unhide     => 'Unhidden',
                     full       => 'Listed (linked) in both',
                     absent     => 'Not listed',
                     grades     => 'Listed in grades only',
@@ -5005,41 +5057,54 @@
                     res        => 'resource only',
                     map        => 'enclosing map/folder',
                     rec        => 'recursive map/folder',
-                    any        => 'regular + deep',
-                    only       => 'deep only',
-                    key        => 'deep with key',
-                    lti        => 'deep with LTI launch',
+                    none       => 'not in use',
+                    key        => 'key access',
+                    ltic       => 'LTI access (course)',
+                    ltid       => 'LTI access (domain)' ,
                     std        => 'Standard (all menus)',
                     colls      => 'Numbered collection',
                   );
     %selectnull = &Apache::lonlocal::texthash (
-                    lti => 'Select Provider',
+                    ltic => 'Select Launcher',
+                    ltid => 'Select Launcher', 
                     colls => 'Select',
                   );
     if ($showval =~ /,/) {
         %values=();
         @current = split(/,/,$showval);
-        ($values{'listing'}) = ($current[0] =~ /^(full|absent|grades|details|datestatus)$/);
-        ($values{'scope'}) = ($current[1] =~ /^(res|map|rec)$/);
-        ($values{'urls'}) = ($current[2] =~ /^(any|only|key:[a-zA-Z\d_.!\@#\$%^&*()+=-]+|lti:\d+)$/);
-        ($values{'menus'}) = ($current[3] =~ /^(\d+)$/);
+        ($values{'state'}) = ($current[0] =~ /^(only|off|both)$/);
+        ($values{'others'}) = ($current[1] =~ /^(hide|unhide)$/);
+        ($values{'listing'}) = ($current[2] =~ /^(full|absent|grades|details|datestatus)$/);
+        ($values{'scope'}) = ($current[3] =~ /^(res|map|rec)$/);
+        ($values{'protect'}) = ($current[4] =~ /^(key:[a-zA-Z\d_.!\@#\$%^&*()+=-]+|ltic:\d+|ltid:\d+)$/);
+        ($values{'menus'}) = ($current[5] =~ /^(\d+)$/);
     } else {
+        $defaults{'state'} = 'off',
+        $defaults{'others'} = 'unhide',
         $defaults{'listing'} = 'full';
         $defaults{'scope'} = 'res';
-        $defaults{'urls'} = 'any';
+        $defaults{'protect'} = 'none';
         $defaults{'menus'} = '0';
     }
     my $disabled;
     if ($readonly) {
         $disabled=' disabled="disabled"';
     }
-    my %lti = 
+    my %courselti =
+        &Apache::lonnet::get_course_lti($env{'course.'.$env{'request.course.id'}.'.num'},
+                                        $env{'course.'.$env{'request.course.id'}.'.domain'});
+    foreach my $item (keys(%courselti)) {
+        if (ref($courselti{$item}) eq 'HASH') {
+            $crslti{$item} = $courselti{$item}{'name'};
+        }
+    }
+    my %lti =
         &Apache::lonnet::get_domain_lti($env{'course.'.$env{'request.course.id'}.'.domain'},
                                         'provider');
     foreach my $item (keys(%lti)) {
         if (ref($lti{$item}) eq 'HASH') {
             unless ($lti{$item}{'requser'}) {
-                $posslti{$item} = $lti{$item}{'consumer'};
+                $domlti{$item} = $lti{$item}{'consumer'};
             }
         }
     }
@@ -5059,11 +5124,15 @@
     $output .= '</tr><tr>';
     foreach my $item (@components) {
         $output .= '<td>';
-        if (($item eq 'urls') || ($item eq 'menus')) {
+        if (($item eq 'protect') || ($item eq 'menus')) {
             my $selected = $values{$item};
             foreach my $option (@{$options{$item}}) {
-                if (($item eq 'urls') && ($option eq 'lti')) {
-                    next unless (keys(%posslti));
+                if ($item eq 'protect') { 
+                    if ($option eq 'ltid') {
+                        next unless (keys(%domlti));
+                    } elsif ($option eq 'ltic') {
+                        next unless (keys(%crslti));
+                    }
                 } elsif (($item eq 'menus') && ($option eq 'colls')) {
                     next unless (@possmenus);
                 }
@@ -5088,7 +5157,7 @@
                 $output .= '<span class="LC_nobreak"><label>'.
                            '<input type="radio" name="deeplink_'.$item.'_'.$thiskey.'" value="'.$option.'"'.$onclick.$disabled.$checked.' />'."\n".
                            $optiontext{$option}.'</label>';
-                if (($item eq 'urls') && ($option eq 'key')) {
+                if (($item eq 'protect') && ($option eq 'key')) {
                     my $visibility="hidden";
                     my $currkey;
                     if ($checked) {
@@ -5097,12 +5166,12 @@
                     }
                     $output .= ' '.
                         '<input type="'.$visibility.'" name="deeplink_'.$option.'_'.$thiskey.'" id="deeplink_'.$option.'_'.$item.'_'.$thiskey.'" value="'.$currkey.'" size="10"'.$disabled.' />';
-                } elsif (($option eq 'lti') || ($option eq 'colls')) {
+                } elsif (($option eq 'ltic') || ($option eq 'ltid') || ($option eq 'colls')) {
                     my $display="none";
                     my ($current,$blankcheck, at possibles);
                     if ($checked) {
                         $display = 'inline-block';
-                        if ($option eq 'lti') {
+                        if (($option eq 'ltic') || ($option eq 'ltid'))  {
                             $current = (split(/\:/,$selected))[1];
                         } else {
                             $current = $selected;
@@ -5110,8 +5179,10 @@
                     } else {
                         $blankcheck = ' selected="selected"';
                     }
-                    if ($option eq 'lti') {
-                        @possibles = keys(%posslti);
+                    if ($option eq 'ltid') {
+                        @possibles = keys(%domlti);
+                    } elsif ($option eq 'ltic') {
+                        @possibles = keys(%crslti); 
                     } else {
                         @possibles = @possmenus;
                     }
@@ -5128,8 +5199,10 @@
                             $selected = ' selected="selected"';
                         }
                         my $shown = $poss;
-                        if ($option eq 'lti') {
-                            $shown = $posslti{$poss};
+                        if ($option eq 'ltid') {
+                            $shown = $domlti{$poss};
+                        } elsif ($option eq 'ltic') {
+                            $shown = $crslti{$poss};
                         }
                         $output .= '<option value="'.$poss.'"'.$selected.'>'.$shown.'</option>';
                     }
Index: loncom/loncapa_apache.conf
diff -u loncom/loncapa_apache.conf:1.274 loncom/loncapa_apache.conf:1.275
--- loncom/loncapa_apache.conf:1.274	Sat Sep 12 16:26:01 2020
+++ loncom/loncapa_apache.conf	Wed Aug  4 19:59:11 2021
@@ -2,7 +2,7 @@
 ## loncapa_apache.conf -- Apache HTTP LON-CAPA configuration file
 ##
 
-# $Id: loncapa_apache.conf,v 1.274 2020/09/12 16:26:01 raeburn Exp $
+# $Id: loncapa_apache.conf,v 1.275 2021/08/04 19:59:11 raeburn Exp $
 
 #
 # LON-CAPA Section (extensions to httpd.conf daemon configuration)
@@ -770,6 +770,11 @@
 PerlHandler Apache::lonlogin
 </Location>
 
+<LocationMatch "^/+adm/launch/tiny/[\w.-]+/\w+">
+SetHandler perl-script
+PerlHandler Apache::ltiauth
+</LocationMatch>
+
 <LocationMatch "^/+adm/lti($|/)">
 SetHandler perl-script
 PerlHandler Apache::ltiauth
Index: loncom/lti/ltiauth.pm
diff -u loncom/lti/ltiauth.pm:1.19 loncom/lti/ltiauth.pm:1.20
--- loncom/lti/ltiauth.pm:1.19	Thu Jul 18 18:28:46 2019
+++ loncom/lti/ltiauth.pm	Wed Aug  4 19:59:11 2021
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Basic LTI Authentication Module
 #
-# $Id: ltiauth.pm,v 1.19 2019/07/18 18:28:46 raeburn Exp $
+# $Id: ltiauth.pm,v 1.20 2021/08/04 19:59:11 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -42,6 +42,7 @@
 sub handler {
     my $r = shift;
     my $requri = $r->uri;
+    my $hostname = $r->hostname;
 #
 # Check for existing session, and temporarily delete any form items
 # in %env, if session exists
@@ -57,7 +58,7 @@
         }
     }
 #
-# Retrieve data POSTed by LTI Consumer on launch
+# Retrieve data POSTed by LTI launch
 #
     &Apache::lonacc::get_posted_cgi($r);
     my $params = {};
@@ -97,12 +98,131 @@
 # Retrieve "internet domains" for all this institution's LON-CAPA
 # nodes.
 #
-    my ($udom,$uname,$uhome,$cdom,$cnum,$symb,$mapurl, at intdoms);
+    my @intdoms;
     my $lonhost = $r->dir_config('lonHostID');
     my $internet_names = &Apache::lonnet::get_internet_names($lonhost);
     if (ref($internet_names) eq 'ARRAY') {
         @intdoms = @{$internet_names};
     }
+#
+# Determine course's domain in LON-CAPA
+# for basic launch using key and secret managed
+# in LON-CAPA course (i.e., uri begins /adm/launch)
+#
+
+   my ($cdom,$cnum);
+
+# Note: "internet domain" for course's domain must be one of the
+# internet domains for the institution's LON-CAPA servers.
+#
+    if ($requri =~ m{^/adm/launch(|/.*)$}) {
+        my $tail = $1;
+        if ($tail =~ m{^/tiny/($match_domain)/(\w+)$}) {
+            my ($urlcdom,$urlcnum) = &course_from_tinyurl($tail);
+            if (($urlcdom ne '') && ($urlcnum ne '')) {
+                $cdom = $urlcdom;
+                $cnum = $urlcnum;
+                my $primary_id = &Apache::lonnet::domain($cdom,'primary');
+                if ($primary_id ne '') {
+                    my $intdom = &Apache::lonnet::internet_dom($primary_id);
+                    if (($intdom ne '') && (grep(/^\Q$intdom\E$/, at intdoms))) {
+#
+# Retrieve information for LTI link protectors in course
+# where url was /adm/launch/tiny/$cdom/$uniqueid
+#
+                        my (%crslti,%crslti_by_key,$itemid,$ltitype);
+                        %crslti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
+                        if (keys(%crslti)) {
+                            foreach my $id (keys(%crslti)) {
+                                if (ref($crslti{$id}) eq 'HASH') {
+                                    my $key = $crslti{$id}{'key'};
+                                    push(@{$crslti_by_key{$key}},$id);
+                                }
+                            }
+                        }
+#
+# Verify the signed request using the secret for LTI link
+# protectors for which the key in the POSTed data matches
+# keys in the course configuration.
+#
+# Request is invalid if the signed request could not be verified
+# for the key and secret from LON-CAPA course configuration for
+# LTI link protectors or from LON-CAPA configuration for the
+# course's domain if there are LTI Providers which may be used.
+#
+# Determine if nonce in POSTed data has expired.
+# If unexpired, confirm it has not already been used.
+#
+                        if (keys(%crslti)) {
+                            $itemid = &get_lti_itemid($requri,$hostname,$params,\%crslti,\%crslti_by_key);
+                        }
+                        if (($itemid) && (ref($crslti{$itemid}) eq 'HASH')) {
+                            $ltitype = 'c';
+                            unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
+                                                                    $crslti{$itemid}{'lifetime'},$cdom,$r->dir_config('lonLTIDir'))) {
+                                &invalid_request($r,3);
+                                return OK;
+                            }
+                        } else {
+                            my %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
+                            unless (keys(%lti) > 0) {
+                                &invalid_request($r,4);
+                                return OK;
+                            }
+                            my (%domlti_by_key,%domlti);
+                            foreach my $id (keys(%lti)) {
+                                if (ref($lti{$id}) eq 'HASH') {
+                                    my $key = $lti{$id}{'key'};
+                                    if (!$lti{$itemid}{'requser'}) {
+                                        push(@{$domlti_by_key{$key}},$id);
+                                        $domlti{$id} = $lti{$id};
+                                    }
+                                }
+                            }
+                            if (keys(%domlti)) {
+                                $itemid = &get_lti_itemid($requri,$hostname,$params,\%domlti,\%domlti_by_key);
+                            }
+                            if (($itemid) && (ref($domlti{$itemid}) eq 'HASH')) {
+                                $ltitype = 'd';
+                                unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
+                                                                        $domlti{$itemid}{'lifetime'},$cdom,
+                                                                        $r->dir_config('lonLTIDir'))) {
+                                    &invalid_request($r,5);
+                                    return OK;
+                                }
+                            }
+                        }
+                        if ($itemid) {
+                            foreach my $key (%{$params}) {
+                                delete($env{'form.'.$key});
+                            }
+                            my $ltoken = &Apache::lonnet::tmpput({'linkprot' => $itemid.$ltitype.':'.$tail},
+                                                                 $lonhost);
+                            if ($ltoken) {
+                                $r->internal_redirect($tail.'?ltoken='.$ltoken);
+                                $r->set_handlers('PerlHandler'=> undef);
+                            } else {
+                                &invalid_request($r,6);
+                            }
+                        } else {
+                            &invalid_request($r,7);
+                        }
+                    } else {
+                        &invalid_request($r,8);
+                    }
+                } else {
+                    &invalid_request($r,9);
+                }
+            } else {
+                &invalid_request($r,10);
+            }
+        } else {
+            &invalid_request($r,11);
+        }
+        return OK;
+    }
+
+    my ($udom,$uname,$uhome,$symb,$mapurl);
 
 #
 # For user who launched LTI in Consumer, determine user's domain in 
@@ -198,7 +318,7 @@
         if ($tail =~ m{^/uploaded/($match_domain)/($match_courseid)/(?:default|supplemental)(?:|_\d+)\.(?:sequence|page)(|___\d+___.+)$}) {
             ($urlcdom,$urlcnum,my $rest) = ($1,$2,$3);
             if (($cdom ne '') && ($cdom ne $urlcdom)) {
-                &invalid_request($r,3);
+                &invalid_request($r,12);
                 return OK;
             }
             if ($rest eq '') {
@@ -217,30 +337,15 @@
         } elsif ($tail =~ m{^/($match_domain)/($match_courseid)$}) {
             ($urlcdom,$urlcnum) = ($1,$2);
             if (($cdom ne '') && ($cdom ne $urlcdom)) {
-                &invalid_request($r,4);
+                &invalid_request($r,13);
                 return OK;
             }
         } elsif ($tail =~ m{^/tiny/($match_domain)/(\w+)$}) {
-            ($urlcdom,my $key) = ($1,$2);
-            if (($cdom ne '') && ($cdom ne $urlcdom)) {
-                &invalid_request($r,5);
+            ($urlcdom,$urlcnum) = &course_from_tinyurl($tail);
+            if (($urlcdom eq '') || ($urlcnum eq '')) {
+                &invalid_request($r,14);
                 return OK;
             }
-            my $tinyurl;
-            my ($result,$cached)=&Apache::lonnet::is_cached_new('tiny',$urlcdom."\0".$key);
-            if (defined($cached)) {
-                $tinyurl = $result;
-            } else {
-                my $configuname = &Apache::lonnet::get_domainconfiguser($urlcdom);
-                my %currtiny = &Apache::lonnet::get('tiny',[$key],$urlcdom,$configuname);
-                if ($currtiny{$key} ne '') {
-                    $tinyurl = $currtiny{$key};
-                    &Apache::lonnet::do_cache_new('tiny',$urlcdom."\0".$key,$currtiny{$key},600);
-                }
-            }
-            if ($tinyurl ne '') {
-                $urlcnum = (split(/\&/,$tinyurl))[0];
-            }
         }
         if (($cdom eq '') && ($urlcdom ne '')) { 
             my $cprimary_id = &Apache::lonnet::domain($urlcdom,'primary');
@@ -263,14 +368,14 @@
     }
 
 #
-# Retrieve information for LTI Consumers in course domain
+# Retrieve information for LTI Consumers in course's domain
 # and populate hash --  %lti_by_key -- for which keys
 # are those defined in domain configuration for LTI.
 #
  
     my %lti = &Apache::lonnet::get_domain_lti($cdom,'provider');
     unless (keys(%lti) > 0) {
-        &invalid_request($r,6);
+        &invalid_request($r,15);
         return OK;
     }
     my %lti_by_key;
@@ -286,37 +391,11 @@
 #
 # Verify the signed request using the secret for those
 # Consumers for which the key in the POSTed data matches 
-# keys in the domain configuration for LTI.
+# keys in the course configuration or the domain configuration
+# for LTI.
 #
-    my $hostname = $r->hostname;
-    my $protocol = 'http';
-    if ($ENV{'SERVER_PORT'} == 443) {
-        $protocol = 'https';
-    }
 
-    if (exists($params->{'oauth_callback'})) {
-        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
-    } else {
-        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0;
-    }
-
-    my ($itemid,$consumer_key,$secret);
-    $consumer_key = $params->{'oauth_consumer_key'};
-    if (ref($lti_by_key{$consumer_key}) eq 'ARRAY') {
-        foreach my $id (@{$lti_by_key{$consumer_key}}) {
-            if (ref($lti{$id}) eq 'HASH') {
-                $secret = $lti{$id}{'secret'};
-                my $request = Net::OAuth->request('request token')->from_hash($params,
-                                                   request_url => $protocol.'://'.$hostname.$requri,
-                                                   request_method => $env{'request.method'},
-                                                   consumer_secret => $secret,);
-                if ($request->verify()) {
-                    $itemid = $id;
-                    last;
-                }
-            }
-        }
-    }
+    my $itemid = &get_lti_itemid($requri,$hostname,$params,\%lti,\%lti_by_key);
 
 #
 # Request is invalid if the signed request could not be verified
@@ -324,7 +403,7 @@
 # configuration in LON-CAPA for that LTI Consumer.
 #
     unless (($itemid) && (ref($lti{$itemid}) eq 'HASH')) {
-        &invalid_request($r,7);
+        &invalid_request($r,16);
         return OK;
     }
 
@@ -334,7 +413,7 @@
 #
     unless (&LONCAPA::ltiutils::check_nonce($params->{'oauth_nonce'},$params->{'oauth_timestamp'},
                                             $lti{$itemid}{'lifetime'},$cdom,$r->dir_config('lonLTIDir'))) {
-        &invalid_request($r,8);
+        &invalid_request($r,17);
         return OK;
     }
 
@@ -345,19 +424,20 @@
 
     if (!$lti{$itemid}{'requser'}) {
         if ($tail =~ m{^/tiny/($match_domain)/(\w+)$}) {
+            my $ltitype = 'd';
             foreach my $key (%{$params}) {
                 delete($env{'form.'.$key});
             }
-            my $ltoken = &Apache::lonnet::tmpput({'linkprot' => $itemid.':'.$tail},
+            my $ltoken = &Apache::lonnet::tmpput({'linkprot' => $itemid.$ltitype.':'.$tail},
                                                    $lonhost);
             if ($ltoken) {
                 $r->internal_redirect($tail.'?ltoken='.$ltoken);
                 $r->set_handlers('PerlHandler'=> undef);
             } else {
-                &invalid_request($r,9);    
+                &invalid_request($r,18);
             }
         } else {
-            &invalid_request($r,10);
+            &invalid_request($r,19);
         }
         return OK;
     }
@@ -418,7 +498,7 @@
             if ($consumers{$sourcecrs} =~ /^$match_courseid$/) {
                 my $crshome = &Apache::lonnet::homeserver($consumers{$sourcecrs},$cdom);
                 if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
-                    &invalid_request($r,11);
+                    &invalid_request($r,20);
                     return OK;
                 } else {
                     $posscnum = $consumers{$sourcecrs};
@@ -430,7 +510,7 @@
     if ($urlcnum ne '') {
         if ($posscnum ne '') {
             if ($posscnum ne $urlcnum) {
-                &invalid_request($r,12);
+                &invalid_request($r,21);
                 return OK;
             } else {
                 $cnum = $posscnum;
@@ -438,7 +518,7 @@
         } else {
             my $crshome = &Apache::lonnet::homeserver($urlcnum,$cdom);
             if ($crshome =~ /(con_lost|no_host|no_such_host)/) {
-                &invalid_request($r,13);
+                &invalid_request($r,22);
                 return OK;
             } else {
                 $cnum = $urlcnum;
@@ -503,7 +583,7 @@
                                                     $domdesc,\%data,\%alerts,\%rulematch,
                                                     \%inst_results,\%curr_rules,%got_rules);
                 if ($result eq 'notallowed') {
-                    &invalid_request($r,14);
+                    &invalid_request($r,23);
                 } elsif ($result eq 'ok') {
                     if (($ltiroles[0] eq 'Instructor') && ($lcroles[0] eq 'cc') && ($lti{$itemid}{'mapcrs'}) &&
                         ($lti{$itemid}{'makecrs'})) {
@@ -512,16 +592,16 @@
                         }
                     }
                 } else {
-                    &invalid_request($r,15);
+                    &invalid_request($r,24);
                     return OK;
                 }
             } else {
-                &invalid_request($r,16);
+                &invalid_request($r,25);
                 return OK;
             }
         }
     } else {
-        &invalid_request($r,17);
+        &invalid_request($r,26);
         return OK;
     }
 
@@ -543,10 +623,10 @@
                              $symb,$cdom,$cnum,$params,\@ltiroles,$lti{$itemid},\@lcroles,
                              $reqcrs,$sourcecrs);
             } else {
-                &invalid_request($r,18);
+                &invalid_request($r,27);
             }
         } else {
-            &invalid_request($r,19);
+            &invalid_request($r,28);
         }
         return OK;
     }
@@ -632,7 +712,7 @@
             }
         }
         if ($reqrole eq '') {
-            &invalid_request($r,20);
+            &invalid_request($r,29);
             return OK;
         } else {
             unless (%crsenv) {
@@ -642,10 +722,10 @@
             my $default_enrollment_end_date   = $crsenv{'default_enrollment_end_date'};
             my $now = time;
             if ($default_enrollment_end_date && $default_enrollment_end_date <= $now) {
-                &invalid_request($r,21);
+                &invalid_request($r,30);
                 return OK;
             } elsif ($default_enrollment_start_date && $default_enrollment_start_date >$now) {
-                &invalid_request($r,22);
+                &invalid_request($r,31);
                 return OK;
             } else {
                 $selfenrollrole = $reqrole.'./'.$cdom.'/'.$cnum;
@@ -676,6 +756,41 @@
     return OK;
 }
 
+sub get_lti_itemid {
+    my ($requri,$hostname,$params,$lti,$lti_by_key) = @_;
+    return unless ((ref($params) eq 'HASH') && (ref($lti) eq 'HASH')  && (ref($lti_by_key) eq 'HASH'));
+
+    if (exists($params->{'oauth_callback'})) {
+        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
+    } else {
+        $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0;
+    }
+
+    my $protocol = 'http';
+    if ($ENV{'SERVER_PORT'} == 443) {
+        $protocol = 'https';
+    }
+
+    my ($itemid,$consumer_key,$secret);
+    my $consumer_key = $params->{'oauth_consumer_key'};
+    if (ref($lti_by_key->{$consumer_key}) eq 'ARRAY') {
+        foreach my $id (@{$lti_by_key->{$consumer_key}}) {
+            if (ref($lti->{$id}) eq 'HASH') {
+                $secret = $lti->{$id}{'secret'};
+                my $request = Net::OAuth->request('request token')->from_hash($params,
+                                                   request_url => $protocol.'://'.$hostname.$requri,
+                                                   request_method => $env{'request.method'},
+                                                   consumer_secret => $secret,);
+                if ($request->verify()) {
+                    $itemid = $id;
+                    last;
+                }
+            }
+        }
+    }
+    return $itemid;
+}
+
 sub lti_enroll {
     my ($uname,$udom,$selfenrollrole) = @_;
     my $enrollresult;
@@ -917,4 +1032,28 @@
     return;
 }
 
+sub course_from_tinyurl {
+    my ($tail) = @_;
+    my ($urlcdom,$urlcnum);
+    if ($tail =~ m{^/tiny/($match_domain)/(\w+)$}) {
+        my ($urlcdom,$key) = ($1,$2);
+        my $tinyurl;
+        my ($result,$cached)=&Apache::lonnet::is_cached_new('tiny',$urlcdom."\0".$key);
+        if (defined($cached)) {
+            $tinyurl = $result;
+        } else {
+            my $configuname = &Apache::lonnet::get_domainconfiguser($urlcdom);
+            my %currtiny = &Apache::lonnet::get('tiny',[$key],$urlcdom,$configuname);
+            if ($currtiny{$key} ne '') {
+                $tinyurl = $currtiny{$key};
+                &Apache::lonnet::do_cache_new('tiny',$urlcdom."\0".$key,$currtiny{$key},600);
+            }
+        }
+        if ($tinyurl ne '') {
+            $urlcnum = (split(/\&/,$tinyurl))[0];
+        }
+    }
+    return ($urlcdom,$urlcnum);
+}
+
 1;
Index: rat/client/parameter.html
diff -u rat/client/parameter.html:1.85 rat/client/parameter.html:1.86
--- rat/client/parameter.html:1.85	Wed Dec 23 21:20:36 2020
+++ rat/client/parameter.html	Wed Aug  4 19:59:12 2021
@@ -5,7 +5,7 @@
 The LearningOnline Network with CAPA
 Parameter Input Window
 //
-// $Id: parameter.html,v 1.85 2020/12/23 21:20:36 raeburn Exp $
+// $Id: parameter.html,v 1.86 2021/08/04 19:59:12 raeburn Exp $
 //
 // Copyright Michigan State University Board of Trustees
 //
@@ -416,15 +416,17 @@
 
 function validateDeepLink() {
     var sform=choices.document.forms.sch;
-    svalue = sform.deeplinklisted.options[sform.deeplinklisted.selectedIndex].value+',';
+    svalue = sform.deeplinkstate.options[sform.deeplinkstate.selectedIndex].value+',';
+    svalue += sform.deeplinkothers.options[sform.deeplinkothers.selectedIndex].value+','; 
+    svalue += sform.deeplinklisted.options[sform.deeplinklisted.selectedIndex].value+',';
     svalue += sform.deeplinkacc.options[sform.deeplinkacc.selectedIndex].value+',';
     var keyRegExp = /^[a-zA-Z\d_.!@#$%^&*()+=-]+$/;
     var numRegExp = /^\d+$/;
-    if (sform.deeplinktypes.length) {
-        for (var i=0; i<sform.deeplinktypes.length; i++) {
-            if (sform.deeplinktypes[i].checked) {
-                svalue += sform.deeplinktypes[i].value;
-                if (sform.deeplinktypes[i].value == 'key') {
+    if (sform.deeplinkprotect.length) {
+        for (var i=0; i<sform.deeplinkprotect.length; i++) {
+            if (sform.deeplinkprotect[i].checked) {
+                svalue += sform.deeplinkprotect[i].value;
+                if (sform.deeplinkprotect[i].value == 'key') {
                     var posskey = sform.deeplinkkey.value;
                     posskey = posskey.replace(/^\s+|\s+$/g,'');
                     if (keyRegExp.test(posskey)) {
@@ -434,10 +436,18 @@
                               'or choose a different supported link type.');
                         return; 
                     }
-                } else if (sform.deeplinktypes[i].value == 'lti') {
-                    var posslti = sform.linkposslti.options[sform.linkposslti.selectedIndex].value;
-                    if ((numRegExp.test(posslti)) && (posslti > 0)) {
-                        svalue += ':'+posslti;
+                } else if (sform.deeplinkprotect[i].value == 'ltic') {
+                    var possltic = sform.linkpossltic.options[sform.linkpossltic.selectedIndex].value;
+                    if ((numRegExp.test(possltic)) && (possltic > 0)) {
+                        svalue += ':'+possltic;
+                    } else {
+                        alert('Please select an LTI launcher, or choose a different supported link type.');
+                        return;
+                    }
+                } else if (sform.deeplinkprotect[i].value == 'ltid') {
+                    var possltid = sform.linkpossltid.options[sform.linkpossltid.selectedIndex].value;
+                    if ((numRegExp.test(possltid)) && (possltid > 0)) {
+                        svalue += ':'+possltid;
                     } else {
                         alert('Please select an LTI launcher, or choose a different supported link type.');
                         return;
@@ -471,27 +481,29 @@
 
 function toggleDeepLink(caller) {
     var sform=choices.document.forms.sch;
-    if ((caller == 'types') && (sform.deeplinktypes.length)) {
+    if ((caller == 'protect') && (sform.deeplinkprotect.length)) {
         var frame = window.frames["choices"];
-        for (var i=0; i<sform.deeplinktypes.length; i++) {
-            if (sform.deeplinktypes[i].checked) {
-                if (sform.deeplinktypes[i].value == 'key') {
-                    if (frame.document.getElementById('deeplinkkey')) {
+        for (var i=0; i<sform.deeplinkprotect.length; i++) {
+            if (sform.deeplinkprotect[i].checked) {
+                if (frame.document.getElementById('deeplinkkey')) {
+                    if (sform.deeplinkprotect[i].value == 'key') {
                         frame.document.getElementById('deeplinkkey').type='text';
-                    }
-                    if (frame.document.getElementById('deeplinkltidiv')) {
-                        frame.document.getElementById('deeplinkltidiv').style.display='none';
-                    }
-                } else {
-                    if (frame.document.getElementById('deeplinkkey')) {
+                    } else {
                         frame.document.getElementById('deeplinkkey').type='hidden';
                     }
-                    if (frame.document.getElementById('deeplinkltidiv')) {
-                        if (sform.deeplinktypes[i].value == 'lti') {
-                            frame.document.getElementById('deeplinkltidiv').style.display='block';
-                        } else {
-                            frame.document.getElementById('deeplinkltidiv').style.display='none';
-                        }
+                }
+                if (frame.document.getElementById('deeplinklticdiv')) {
+                    if (sform.deeplinkprotect[i].value == 'ltic') {
+                        frame.document.getElementById('deeplinklticdiv').style.display='block';
+                    } else {
+                        frame.document.getElementById('deeplinklticdiv').style.display='none';
+                    }
+                }
+                if (frame.document.getElementById('deeplinkltiddiv')) {
+                    if (sform.deeplinkprotect[i].value == 'ltid') {
+                        frame.document.getElementById('deeplinkltiddiv').style.display='block';
+                    } else {
+                        frame.document.getElementById('deeplinkltiddiv').style.display='none';
                     }
                 }
                 break;
@@ -1167,30 +1179,41 @@
        }
        if (pscat=='deeplink') {
            var deeplinkvals = new Array();
-           var linktypeparts = new Array();
-           var ltikeyRegExp = /^(lti|key):(\w+)$/;
+           var linkprotectparts = new Array();
+           var ltikeyRegExp = /^(ltic|ltid|key):(\w+)$/;
            var dlinkkeysty = 'hidden';
            var dlinkkeyval = '';
-           var dlinkltidivsty = 'none';
+           var dlinklticdivsty = 'none';
+           var dlinkltiddivsty = 'none';
            var dlinkmenusdivsty = 'none';
            if ((svalue != '') && (svalue != null)) {
                deeplinkvals = svalue.split(',');
-               if (ltikeyRegExp.test(deeplinkvals[2])) {
-                   linktypeparts = deeplinkvals[2].split(':');
-                   deeplinkvals[2] = linktypeparts[0];
-                   if (linktypeparts[0] == 'key') {
+               if (ltikeyRegExp.test(deeplinkvals[4])) {
+                   linkprotectparts = deeplinkvals[4].split(':');
+                   deeplinkvals[4] = linkprotectparts[0];
+                   if (linkprotectparts[0] == 'key') {
                        dlinkkeysty = 'text';
-                       dlinkkeyval = linktypeparts[1];
-                   } else if (linktypeparts[0] == 'lti') {
-                       dlinkltidivsty = 'block';
+                       dlinkkeyval = linkprotectparts[1];
+                   } else if (linkprotectparts[0] == 'ltic') {
+                       dlinklticdivsty = 'block';
+                   } else if (linkprotectparts[0] == 'ltid') {
+                       dlinkltiddivsty = 'block';
                    }
                }
-               if (deeplinkvals[3] >= 1) {
+               if (deeplinkvals[5] >= 1) {
                    dlinkmenusdivsty = 'inline-block';
                }
            } else {
-               deeplinkvals = ['full','res','any','0'];
+               deeplinkvals = ['off','unhide','full','res','','0'];
            }
+           var deeplinkstate = new Array();
+           deeplinkstate = ['only','off','both'];
+           var deeplinkstatetxt = new Array();
+           deeplinkstatetxt = ['deep only','deeplink off','regular + deep'];
+           var deeplinkothers = new Array();
+           deeplinkothers = ['hide','unhide'];
+           var deeplinkotherstxt = new Array();
+           deeplinkotherstxt = ['Hidden','Unhidden'];
            var deeplinklisting = new Array();
            deeplinklisting = ['full','absent','grades','details','datestatus'];
            var deeplinklisttxt = new Array();
@@ -1199,94 +1222,150 @@
            deeplinkscopes = ['res','map','rec'];
            var deeplinkscopetxt = new Array();
            deeplinkscopetxt = ['resource only','enclosing map/folder','recursive map/folder'];
-           var deeplinkurls = new Array();
-           deeplinkurls = ['any','only','key','lti'];
+           var deeplinkprotect = new Array();
+           deeplinkprotect = ['none','key','ltic','ltid'];
            tablestart('Deep-linked items');
+           choicewrite('<tr><td>Access status?</td><td>');
+           choicewrite('<select name="deeplinkstate">');
+           for (var i=0; i<deeplinkstate.length; i++) {
+               choicewrite('<option value="'+deeplinkstate[i]+'"');
+               if (deeplinkvals[0] == deeplinkstate[i]) {
+                   choicewrite(' selected="selected"');
+               }
+               choicewrite('>'+deeplinkstatetxt[i]+'</option>');
+           }
+           choicewrite('</select></td></tr>');
+           choicewrite('<tr><td>Hide other resources?</td><td>');
+           choicewrite('<select name="deeplinkothers">');
+           for (var i=0; i<deeplinkothers.length; i++) {
+               choicewrite('<option value="'+deeplinkothers[i]+'"');
+               if (deeplinkvals[1] == deeplinkothers[i]) {
+                   choicewrite(' selected="selected"');
+               }
+               choicewrite('>'+deeplinkotherstxt[i]+'</option>');
+           }
+           choicewrite('</select></td></tr>');
            choicewrite('<tr><td>In Contents + Gradebook?</td><td>');
            choicewrite('<select name="deeplinklisted">');
            for (var i=0; i<deeplinklisting.length; i++) {
                choicewrite('<option value="'+deeplinklisting[i]+'"');
-               if (deeplinkvals[0] == deeplinklisting[i]) {
+               if (deeplinkvals[2] == deeplinklisting[i]) {
                    choicewrite(' selected="selected"');
                }
                choicewrite('>'+deeplinklisttxt[i]+'</option>');
            }
            choicewrite('</select></td></tr>');
-           choicewrite('<tr><td>Access scope via deep-link</td><td>');
+           choicewrite('<tr><td>Access scope for link</td><td>');
            choicewrite('<select name="deeplinkacc">');
            for (var i=0; i<deeplinkscopes.length; i++) {
                choicewrite('<option value="'+deeplinkscopes[i]+'"');
-               if (deeplinkvals[1] == deeplinkscopes[i]) {
+               if (deeplinkvals[3] == deeplinkscopes[i]) {
                    choicewrite(' selected="selected"');
                }
                choicewrite('>'+deeplinkscopetxt[i]+'</option>');
            }
            choicewrite('</select></td></tr>');
-           choicewrite('<tr><td>Supported Link Types</td><td>');
-           choicewrite('<span style="white-space: nowrap;"><label>');
-           choicewrite('<input name="deeplinktypes" value="any"'+
-                  ' type="radio" '+calldeeplink('types'));
-           if (deeplinkvals[2]=='any') { choicewrite(' checked="checked"'); }
-           choicewrite(' /> regular + deep</label></span><br />');
+           choicewrite('<tr><td>Link protection</td><td>');
            choicewrite('<span style="white-space: nowrap;"><label>');
-           choicewrite('<input name="deeplinktypes" value="only"'+
-                  ' type="radio" '+calldeeplink('types'));
-           if (deeplinkvals[2]=='only') { choicewrite(' checked="checked"'); }
-           choicewrite(' /> deep only</label></span><br />');
-           choicewrite('<span style="white-space: nowrap;"><label>');
-           choicewrite('<input name="deeplinktypes" value="key"'+
-                  ' type="radio" '+calldeeplink('types'));
-           if (deeplinkvals[2]=='key') { choicewrite(' checked="checked"'); }
-           choicewrite(' /> deep with key</label>');
+           choicewrite('<input name="deeplinkprotect" value="none"'+
+                  ' type="radio" '+calldeeplink('protect'));
+           if (deeplinkvals[4]=='none') { choicewrite(' checked="checked"'); }
+           choicewrite(' /> not in use</label>');
+           choicewrite('<input name="deeplinkprotect" value="key"'+
+                  ' type="radio" '+calldeeplink('protect'));
+           if (deeplinkvals[4]=='key') { choicewrite(' checked="checked"'); }
+           choicewrite(' /> key access</label>');
            choicewrite('<input type="'+dlinkkeysty+'" name="deeplinkkey" id="deeplinkkey" value="'+dlinkkeyval+'" size="10" />');
            choicewrite('</span><br />');
 
            var possmenus = new Array();
            if ((pextra != '') && (pextra != null)) {
-               var ltiRegExp = /^lti_/;
+               var lticRegExp = /^ltic_/;
+               var ltidRegExp = /^ltid_/;
                var menusRegExp = /^menus_/;
                var extras = pextra.split('&');
                for (var i=0; i<extras.length; i++) {
-                   if (ltiRegExp.test(extras[i])) {
-                       extras[i] = extras[i].replace(ltiRegExp,'');
-                       var posslti = extras[i].split(',');
-                       if (posslti.length >= 1) {
-                           var ltinums = new Array();
-                           var ltititles = new Array();
-                           for (var j=0; j<posslti.length; j++) {
-                               var entries = posslti[j].split(':');
-                               ltinums[j] = entries[0];
-                               ltititles[j] = decodeURIComponent(entries[1]);
+                   if (lticRegExp.test(extras[i])) {
+                       extras[i] = extras[i].replace(lticRegExp,'');
+                       var possltic = extras[i].split(',');
+                       if (possltic.length >= 1) {
+                           var lticnums = new Array();
+                           var ltictitles = new Array();
+                           for (var j=0; j<possltic.length; j++) {
+                               var entries = possltic[j].split(':');
+                               lticnums[j] = entries[0];
+                               ltictitles[j] = decodeURIComponent(entries[1]);
                            }
-                           if (ltinums.length) {
+                           if (lticnums.length) {
                                choicewrite('<span style="white-space: nowrap;"><label>');
-                               choicewrite('<input name="deeplinktypes" value="lti"'+
-                                           ' type="radio" '+calldeeplink('types'));
-                               if (deeplinkvals[2]=='lti') { choicewrite(' checked="checked"'); }
-                               choicewrite(' /> deep with LTI launch</label>');
-                               choicewrite('<div id="deeplinkltidiv" style="display:'+dlinkltidivsty+'">');
-                               choicewrite('<select name="linkposslti">');
+                               choicewrite('<input name="deeplinkprotect" value="ltic"'+
+                                           ' type="radio" '+calldeeplink('protect'));
+                               if (deeplinkvals[4]=='ltic') { choicewrite(' checked="checked"'); }
+                               choicewrite(' /> course LTI launch</label>');
+                               choicewrite('<div id="deeplinklticdiv" style="display:'+dlinklticdivsty+'">');
+                               choicewrite('<select name="linkpossltic">');
                                var sel='';
-                               if (deeplinkvals[2]!='lti') {
+                               if (deeplinkvals[4]!='ltic') {
                                    sel = ' selected="selected"';
                                }
-                               if (ltinums.length > 1) {
+                               if (lticnums.length > 1) {
                                    choicewrite('<option value=""'+sel+'>Select</option>');
                                }
-                               for (var j=0; j<ltinums.length; j++) {
+                               for (var j=0; j<lticnums.length; j++) {
                                    sel = '';
-                                   if (deeplinkvals[2]=='lti') {
-                                       if (linktypeparts.length) {
-                                           if (ltinums[j] == linktypeparts[1]) {
+                                   if (deeplinkvals[4]=='ltic') {
+                                       if (linkprotectparts.length) {
+                                           if (lticnums[j] == linkprotectparts[1]) {
                                                sel = ' selected="selected"';
                                            }
                                        }
                                    }
-                                   choicewrite('<option value="'+ltinums[j]+'"'+sel+'>'+ltititles[j]+'</option>');
+                                   choicewrite('<option value="'+lticnums[j]+'"'+sel+'>'+ltictitles[j]+'</option>');
                                }
                                choicewrite('</select></div></span><br />');
                            }
                        }
+                   } else if (ltidRegExp.test(extras[i])) {
+                       extras[i] = extras[i].replace(ltidRegExp,'');
+                       var possltid = extras[i].split(',');
+                       if (possltid.length >= 1) {
+                           var ltidnums = new Array();
+                           var ltidtitles = new Array();
+                           for (var j=0; j<possltid.length; j++) {
+                               var entries = possltid[j].split(':');
+                               ltidnums[j] = entries[0];
+                               ltidtitles[j] = decodeURIComponent(entries[1]);
+                           }
+                           if (ltidnums.length) {
+                               choicewrite('<span style="white-space: nowrap;"><label>');
+                               choicewrite('<input name="deeplinkprotect" value="ltid"'+
+                                           ' type="radio" '+calldeeplink('protect'));
+                               if (deeplinkvals[4]=='ltid') { choicewrite(' checked="checked"'); }
+                               choicewrite(' />domain LTI launch</label>');
+                               choicewrite('<div id="deeplinkltiddiv" style="display:'+dlinkltiddivsty+'">');
+                               choicewrite('<select name="linkpossltid">');
+                               var sel='';
+                               if (deeplinkvals[4]!='ltid') {
+                                   sel = ' selected="selected"';
+                               }
+                               if (ltidnums.length > 1) {
+                                   choicewrite('<option value=""'+sel+'>Select</option>');
+                               }
+                               for (var j=0; j<ltidnums.length; j++) {
+                                   sel = '';
+                                   if (deeplinkvals[4]=='ltid') {
+                                       if (linkprotectparts.length) {
+                                           if (ltidnums[j] == linkprotectparts[1]) {
+                                               sel = ' selected="selected"';
+                                           }
+                                       }
+                                   }
+                                   choicewrite('<option value="'+ltidnums[j]+'"'+sel+'>'+ltidtitles[j]+'</option>');
+                               }
+                               choicewrite('</select></div></span><br />');
+                           }
+                       }
+
                    } else if (menusRegExp.test(extras[i])) {
                        extras[i] = extras[i].replace(menusRegExp,'');
                        possmenus = extras[i].split(',');
@@ -1297,7 +1376,7 @@
            choicewrite('<span style="white-space: nowrap;"><label>');
            choicewrite('<input name="deeplinkmenus" value="std"'+
                        ' type="radio" '+calldeeplink('menus'));
-           if (deeplinkvals[3] == 0) {
+           if (deeplinkvals[5] == 0) {
                choicewrite(' checked="checked"');
            }
            choicewrite(' /> Standard (all menus)</label></span><br />');
@@ -1305,12 +1384,12 @@
                choicewrite('<span style="white-space: nowrap;"><label>');
                choicewrite('<input name="deeplinkmenus" value="collnum"'+
                            ' type="radio" '+calldeeplink('menus'));
-               if (deeplinkvals[3] > 0) { choicewrite(' checked="checked"'); }
+               if (deeplinkvals[5] > 0) { choicewrite(' checked="checked"'); }
                choicewrite(' /> Numbered collection</label>');
                choicewrite('<div id="deeplinkmenusdiv" style="display:'+dlinkmenusdivsty+'">');
                choicewrite('<select name="linkpossmenu">');
                var sel='';
-               if (deeplinkvals[3] == 0) {
+               if (deeplinkvals[5] == 0) {
                    sel = ' selected="selected"';
                }
                if (possmenus.length > 1) {
@@ -1318,7 +1397,7 @@
                }
                for (var i=0; i<possmenus.length; i++) {
                    sel = '';
-                   if (deeplinkvals[3] == possmenus[i]) {
+                   if (deeplinkvals[5] == possmenus[i]) {
                        sel = ' selected="selected"';
                    }
                    choicewrite('<option value="'+possmenus[i]+'"'+sel+'>'+possmenus[i]+'</option>');


More information about the LON-CAPA-cvs mailing list