[LON-CAPA-cvs] cvs: doc /loncapafiles loncapafiles.lpml loncom/homework grades.pm loncom/html/res/adm/pages passback.png loncom/interface loncourserespicker.pm
raeburn
raeburn at source.lon-capa.org
Tue Dec 3 18:34:12 EST 2024
raeburn Tue Dec 3 23:34:12 2024 EDT
Added files:
/loncom/html/res/adm/pages passback.png
Modified files:
/loncom/homework grades.pm
/loncom/interface loncourserespicker.pm
/doc/loncapafiles loncapafiles.lpml
Log:
- Bug 6907. "Content in a course can be set to be deep-link only".
Course personnel with mgr priv can pass scores back to launcher CMS for
students who accessed via deep-link with LTI-mediated link protection.
-------------- next part --------------
Index: loncom/homework/grades.pm
diff -u loncom/homework/grades.pm:1.795 loncom/homework/grades.pm:1.796
--- loncom/homework/grades.pm:1.795 Mon Jul 1 22:29:01 2024
+++ loncom/homework/grades.pm Tue Dec 3 23:34:10 2024
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.795 2024/07/01 22:29:01 raeburn Exp $
+# $Id: grades.pm,v 1.796 2024/12/03 23:34:10 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -47,10 +47,12 @@
use Apache::lonquickgrades;
use Apache::bridgetask();
use Apache::lontexconvert();
+use Apache::loncourserespicker;
use String::Similarity;
use HTML::Parser();
use File::MMagic;
use LONCAPA;
+use LONCAPA::ltiutils();
use POSIX qw(floor);
@@ -636,7 +638,7 @@
#--- Dumps the class list with usernames,list of sections,
#--- section, ids and fullnames for each user.
sub getclasslist {
- my ($getsec,$filterbyaccstatus,$getgroup,$symb,$submitonly,$filterbysubmstatus) = @_;
+ my ($getsec,$filterbyaccstatus,$getgroup,$symb,$submitonly,$filterbysubmstatus,$filterbypbid,$possibles) = @_;
my @getsec;
my @getgroup;
my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
@@ -664,12 +666,16 @@
#
my %sections;
my %fullnames;
+ my %passback;
my ($cdom,$cnum,$partlist);
if (($filterbysubmstatus) && ($submitonly ne 'all') && ($symb ne '')) {
$cdom = $env{"course.$env{'request.course.id'}.domain"};
$cnum = $env{"course.$env{'request.course.id'}.num"};
my $res_error;
($partlist) = &response_type($symb,\$res_error);
+ } elsif ($filterbypbid) {
+ $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ $cnum = $env{"course.$env{'request.course.id'}.num"};
}
foreach my $student (keys(%$classlist)) {
my $end =
@@ -756,6 +762,27 @@
}
}
}
+ if ($filterbypbid) {
+ if (ref($possibles) eq 'HASH') {
+ unless (exists($possibles->{$student})) {
+ delete($classlist->{$student});
+ next;
+ }
+ }
+ my $udom =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_SDOM()];
+ my $uname =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_SNAME()];
+ if (($udom ne '') && ($uname ne '')) {
+ my %pbinfo = &Apache::lonnet::get('nohist_'.$cdom.'_'.$cnum.'_linkprot_pb',[$filterbypbid],$udom,$uname);
+ if (ref($pbinfo{$filterbypbid}) eq 'ARRAY') {
+ $passback{$student} = $pbinfo{$filterbypbid}
+ } else {
+ delete($classlist->{$student});
+ next;
+ }
+ }
+ }
$section = ($section ne '' ? $section : 'none');
if (&canview($section)) {
if (!@getsec || grep(/^\Q$section\E$/, at getsec)) {
@@ -771,7 +798,7 @@
}
}
my @sections = sort(keys(%sections));
- return ($classlist,\@sections,\%fullnames);
+ return ($classlist,\@sections,\%fullnames,\%passback);
}
sub canmodify {
@@ -1034,6 +1061,611 @@
return $string;
}
+sub initialpassback {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my %passback = &Apache::lonnet::dump('nohist_linkprot_passback',$cdom,$cnum);
+ my $readonly;
+ unless ($perm{'mgr'}) {
+ $readonly = 1;
+ }
+ my $formname = 'initialpassback';
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ my $output;
+ if (!defined($navmap)) {
+ if ($crstype eq 'Community') {
+ $output = &mt('Unable to retrieve information about community contents');
+ } else {
+ $output = &mt('Unable to retrieve information about course contents');
+ }
+ return '<p>'.$output.'</p>';
+ }
+ return &Apache::loncourserespicker::create_picker($navmap,'passback',$formname,$crstype,undef,
+ undef,undef,undef,undef,undef,undef,
+ \%passback,$readonly);
+}
+
+sub passback_filters {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my ($launcher,$appname,$setter,$linkuri,$linkprotector,$scope,$chosen);
+ if ($env{'form.passback'} ne '') {
+ $chosen = &unescape($env{'form.passback'});
+ ($linkuri,$linkprotector,$scope) = split("\0",$chosen);
+ ($launcher,$appname,$setter) = &get_passback_launcher($cdom,$cnum,$chosen);
+ }
+ my $result;
+ if ($launcher ne '') {
+ $result = &launcher_info_box($launcher,$appname,$setter,$linkuri,$scope).
+ '<p><br />'.&mt('Set criteria to use to list students for possible passback of scores, then push Next [_1]',
+ '→').
+ '</p>';
+ }
+ $result .= '<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
+ '<input type="hidden" name="passback" value="'.&escape($chosen).'" />'."\n".
+ '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
+ my ($submittext,$newcommand);
+ if ($launcher ne '') {
+ $submittext = &mt('Next').' →';
+ $newcommand = 'passbacknames';
+ $result .= &selectfield(0)."\n";
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'initialpassback';
+ if ($env{'form.passback'}) {
+ $result .= '<span class="LC_warning">'.&mt('Invalid launcher').'</span>'."\n";
+ } else {
+ $result .= '<span class="LC_warning">'.&mt('No launcher selected').'</span>'."\n";
+ }
+ }
+ $result .= '<input type="hidden" name="command" value="'.$newcommand.'" />'."\n".
+ '<div>'."\n".
+ '<input type="submit" value="'.$submittext.'" />'."\n".
+ '</div>'."\n".
+ '</form>'."\n";
+ return $result;
+}
+
+sub names_for_passback {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my ($launcher,$appname,$setter,$linkuri,$linkprotector,$scope,$chosen);
+ if ($env{'form.passback'} ne '') {
+ $chosen = &unescape($env{'form.passback'});
+ ($linkuri,$linkprotector,$scope) = split("\0",$chosen);
+ ($launcher,$appname,$setter) = &get_passback_launcher($cdom,$cnum,$chosen);
+ }
+ my ($result,$ctr,$newcommand,$submittext);
+ if ($launcher ne '') {
+ $result = &launcher_info_box($launcher,$appname,$setter,$linkuri,$scope);
+ }
+ $ctr = 0;
+ my @statuses = &Apache::loncommon::get_env_multiple('form.Status');
+ my $stu_status = join(':', at statuses);
+ $result .= '<form action="/adm/grades" method="post" name="passbackusers">'."\n".
+ '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
+ if ($launcher ne '') {
+ $result .= '<input type="hidden" name="passback" value="'.&escape($chosen).'" />'."\n".
+ '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
+ my ($sections,$groups,$group_display,$disabled) = §ions_and_groups();
+ my $section_display = join(' ',@{$sections});
+ my $status_display;
+ if ((grep(/^Any$/, at statuses)) ||
+ (@statuses == 3)) {
+ $status_display = &mt('Any');
+ } else {
+ $status_display = join(' '.&mt('or').' ',map { &mt($_); } @statuses);
+ }
+ $result .= '<p>'.&mt('Student(s) with stored passback credentials for [_1], and also satisfy:',
+ '<span class="LC_cusr_emph">'.$linkuri.'</span>').
+ '<ul>'.
+ '<li>'.&mt('Section(s)').": $section_display</li>\n".
+ '<li>'.&mt('Group(s)').": $group_display</li>\n".
+ '<li>'.&mt('Status').": $status_display</li>\n".
+ '</ul>';
+ my ($classlist,undef,$fullname) = &getclasslist($sections,'1',$groups,'','','',$chosen);
+ if (keys(%$fullname)) {
+ $newcommand = 'passbackscores';
+ $result .= &build_section_inputs().
+ &checkselect_js('passbackusers').
+ '<p><br />'.
+ &mt("To send scores, check box(es) next to the student's name(s), then push 'Send Scores'.").
+ '</p>'.
+ &check_script('passbackusers', 'stuinfo')."\n".
+ '<input type="button" '."\n".
+ 'onclick="javascript:checkSelect(this.form.stuinfo);" '."\n".
+ 'value="'.&mt('Send Scores').'" /> <br />'."\n".
+ &check_buttons()."\n".
+ &Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row();
+ my $loop = 0;
+ while ($loop < 2) {
+ $result .= '<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
+ '<th>'.&nameUserString('header').' '.&mt('Section/Group').'</th>';
+ $loop++;
+ }
+ $result .= &Apache::loncommon::end_data_table_header_row()."\n";
+ foreach my $student (sort
+ {
+ if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
+ return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
+ }
+ return $a cmp $b;
+ }
+ (keys(%$fullname))) {
+ $ctr++;
+ my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
+ my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
+ my $udom = $classlist->{$student}->[&Apache::loncoursedata::CL_SDOM()];
+ my $uname = $classlist->{$student}->[&Apache::loncoursedata::CL_SNAME()];
+ if ( $perm{'vgr'} eq 'F' ) {
+ if ($ctr%2 ==1) {
+ $result.= &Apache::loncommon::start_data_table_row();
+ }
+ $result .= '<td align="right">'.$ctr.' </td>'.
+ '<td align="center"><label><input type="checkbox" name="stuinfo" value="'.
+ $student.':'.$$fullname{$student}.':::SECTION'.$section.
+ ') " /> </label></td>'."\n".'<td>'.
+ &nameUserString(undef,$$fullname{$student},$uname,$udom).
+ ' '.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
+
+ if ($ctr%2 ==0) {
+ $result .= &Apache::loncommon::end_data_table_row()."\n";
+ }
+ }
+ }
+ if ($ctr%2 ==1) {
+ $result .= &Apache::loncommon::end_data_table_row();
+ }
+ $result .= &Apache::loncommon::end_data_table()."\n";
+ if ($ctr) {
+ $result .= '<input type="button" '.
+ 'onclick="javascript:checkSelect(this.form.stuinfo);" '.
+ 'value="'.&mt('Send Scores').'" />'."\n";
+ }
+ } else {
+ $submittext = '← '.&mt('Previous');
+ $newcommand = 'passback';
+ $result .= '<span class="LC_warning">'.&mt('No students match the selection criteria').'</p>';
+ }
+ } else {
+ $newcommand = 'initialpassback';
+ $submittext = &mt('Start over');
+ if ($env{'form.passback'}) {
+ $result .= '<span class="LC_warning">'.&mt('Invalid launcher').'</span>'."\n";
+ } else {
+ $result .= '<span class="LC_warning">'.&mt('No launcher selected').'</span>'."\n";
+ }
+ }
+ $result .= '<input type="hidden" name="command" value="'.$newcommand.'" />'."\n";
+ if (!$ctr) {
+ $result .= '<div>'."\n".
+ '<input type="submit" value="'.$submittext.'" />'."\n".
+ '</div>'."\n";
+ }
+ $result .= '</form>'."\n";
+ return $result;
+}
+
+sub do_passback {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my ($launcher,$appname,$setter,$linkuri,$linkprotector,$scope,$chosen);
+ if ($env{'form.passback'} ne '') {
+ $chosen = &unescape($env{'form.passback'});
+ ($linkuri,$linkprotector,$scope) = split("\0",$chosen);
+ ($launcher,$appname,$setter) = &get_passback_launcher($cdom,$cnum,$chosen);
+ }
+ if ($launcher ne '') {
+ $request->print(&launcher_info_box($launcher,$appname,$setter,$linkuri,$scope));
+ }
+ my $error;
+ if ($perm{'mgr'}) {
+ if ($launcher ne '') {
+ my @poss_students = &Apache::loncommon::get_env_multiple('form.stuinfo');
+ if (@poss_students) {
+ my %possibles;
+ foreach my $item (@poss_students) {
+ my ($stuname,$studom) = split(/:/,$item,3);
+ $possibles{$stuname.':'.$studom} = 1;
+ }
+ my ($sections,$groups,$group_display,$disabled) = §ions_and_groups();
+ my ($classlist,undef,$fullname,$pbinfo) =
+ &getclasslist($sections,'1',$groups,'','','',$chosen,\%possibles);
+ if ((ref($classlist) eq 'HASH') && (ref($pbinfo) eq 'HASH')) {
+ my %passback = %{$pbinfo};
+ my (%tosend,%remotenotok,%scorenotok,%zeroposs,%nopbinfo);
+ foreach my $possible (keys(%possibles)) {
+ if ((exists($classlist->{$possible})) &&
+ (exists($passback{$possible})) && (ref($passback{$possible}) eq 'ARRAY')) {
+ $tosend{$possible} = 1;
+ }
+ }
+ if (keys(%tosend)) {
+ my ($lti_in_use,$crsdef);
+ my ($ltinum,$ltitype) = ($linkprotector =~ /^(\d+)(c|d)$/);
+ if ($ltitype eq 'c') {
+ my %crslti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
+ $lti_in_use = $crslti{$ltinum};
+ $crsdef = 1;
+ } else {
+ my %domlti = &Apache::lonnet::get_domain_lti($cdom,'linkprot');
+ $lti_in_use = $domlti{$ltinum};
+ }
+ if (ref($lti_in_use) eq 'HASH') {
+ my $msgformat = $lti_in_use->{'passbackformat'};
+ my $keynum = $lti_in_use->{'cipher'};
+ my $scoretype = 'decimal';
+ if ($lti_in_use->{'scoreformat'} =~ /^(decimal|ratio|percentage)$/) {
+ $scoretype = $1;
+ }
+ my $pbsymb = &Apache::loncommon::symb_from_tinyurl($linkuri,$cnum,$cdom);
+ my $pbmap;
+ if ($pbsymb =~ /\.(page|sequence)$/) {
+ $pbmap = &Apache::lonnet::deversion((&Apache::lonnet::decode_symb($pbsymb))[2]);
+ } else {
+ $pbmap = &Apache::lonnet::deversion((&Apache::lonnet::decode_symb($pbsymb))[0]);
+ }
+ $pbmap = &Apache::lonnet::clutter($pbmap);
+ my $pbscope;
+ if ($scope eq 'res') {
+ $pbscope = 'resource';
+ } elsif ($scope eq 'map') {
+ $pbscope = 'nonrec';
+ } elsif ($scope eq 'rec') {
+ $pbscope = 'map';
+ }
+ my $sigmethod = 'HMAC-SHA1';
+ my $type = 'linkprot';
+ my $clientip = &Apache::lonnet::get_requestor_ip();
+ my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
+ my $ip = &Apache::lonnet::get_host_ip($lonhost);
+ my $numstudents = scalar(keys(%tosend));
+ my %prog_state = &Apache::lonhtmlcommon::Create_PrgWin($request,$numstudents);
+ my $outcome = &Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row();
+ my $loop = 0;
+ while ($loop < 2) {
+ $outcome .= '<th>'.&mt('No.').'</th>'.
+ '<th>'.&nameUserString('header').' '.&mt('Section/Group').'</th>'.
+ '<th>'.&mt('Score').'</th>';
+ $loop++;
+ }
+ $outcome .= &Apache::loncommon::end_data_table_header_row()."\n";
+ my $ctr=0;
+ foreach my $student (sort
+ {
+ if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
+ return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
+ }
+ return $a cmp $b;
+ } (keys(%$fullname))) {
+ next unless ($tosend{$student});
+ my ($uname,$udom) = split(/:/,$student);
+ &Apache::lonhtmlcommon::Increment_PrgWin($request,\%prog_state,'last student');
+ my ($uname,$udom) = split(/:/,$student);
+ my $uhome = &Apache::lonnet::homeserver($uname,$udom),
+ my $id = $passback{$student}[0],
+ my $url = $passback{$student}[1],
+ my ($total,$possible,$usec);
+ if (ref($classlist->{$student}) eq 'ARRAY') {
+ $usec = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION];
+ }
+ if ($pbscope eq 'resource') {
+ $total = 0;
+ $possible = 0;
+ my $navmap = Apache::lonnavmaps::navmap->new($uname,$udom);
+ if (ref($navmap)) {
+ my $res = $navmap->getBySymb($pbsymb);
+ if (ref($res)) {
+ my $partlist = $res->parts();
+ if (ref($partlist) eq 'ARRAY') {
+ my %record = &Apache::lonnet::restore($pbsymb,$env{'request.course.id'},$udom,$uname);
+ foreach my $part (@{$partlist}) {
+ next if ($record{"resource.$part.solved"} =~/^excused/);
+ my $weight = &Apache::lonnet::EXT("resource.$part.weight",$pbsymb,$udom,$uname,$usec);
+ $possible += $weight;
+ if (($record{'version'}) && (exists($record{"resource.$part.awarded"}))) {
+ my $awarded = $record{"resource.$part.awarded"};
+ if ($awarded) {
+ $total += $weight * $awarded;
+ }
+ }
+ }
+ }
+ }
+ }
+ } elsif (($pbscope eq 'map') || ($pbscope eq 'nonrec')) {
+ ($total,$possible) =
+ &Apache::lonhomework::get_lti_score($uname,$udom,$pbmap,$pbscope);
+ }
+ if (($id ne '') && ($url ne '') && ($possible)) {
+ my ($sent,$score,$code,$result) =
+ &LONCAPA::ltiutils::send_grade($cdom,$cnum,$crsdef,$type,$ltinum,$keynum,$id,
+ $url,$scoretype,$sigmethod,$msgformat,$total,$possible);
+ my $no_passback;
+ if ($sent) {
+ if ($code == 200) {
+ delete($tosend{$student});
+ my $namespace = $cdom.'_'.$cnum.'_lp_passback';
+ my $store = {
+ 'score' => $score,
+ 'ip' => $ip,
+ 'host' => $lonhost,
+ 'protector' => $linkprotector,
+ 'deeplink' => $linkuri,
+ 'scope' => $scope,
+ 'url' => $url,
+ 'id' => $id,
+ 'clientip' => $clientip,
+ 'whodoneit' => $env{'user.name'}.':'.$env{'user.domain'},
+ };
+ my $value='';
+ foreach my $key (keys(%{$store})) {
+ $value.=&escape($key).'='.&Apache::lonnet::freeze_escape($store->{$key}).'&';
+ }
+ $value=~s/\&$//;
+ &Apache::lonnet::courselog(&escape($linkuri).':'.$uname.':'.$udom.':EXPORT:'.$value);
+ &Apache::lonnet::cstore({'score' => $score},$chosen,$namespace,$udom,$uname,'',$ip,1);
+ $ctr++;
+ if ($ctr%2 ==1) {
+ $outcome .= &Apache::loncommon::start_data_table_row();
+ }
+ my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
+ my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
+ $outcome .= '<td align="right">'.$ctr.' </td>'.
+ '<td>'.&nameUserString(undef,$$fullname{$student},$uname,$udom).
+ ' '.$section.($group ne '' ?'/'.$group:'').'</td>'.
+ '<td>'.$score.'</td>'."\n";
+ if ($ctr%2 ==0) {
+ $outcome .= &Apache::loncommon::end_data_table_row()."\n";
+ }
+ } else {
+ $remotenotok{$student} = 1;
+ $no_passback = "Passback response for ".$linkprotector." was $code ($result)";
+ &Apache::lonnet::logthis($no_passback." for $uname:$udom in ${cdom}_${cnum}");
+ }
+ } else {
+ $scorenotok{$student} = 1;
+ $no_passback = "Passback of grades not sent for ".$linkprotector;
+ &Apache::lonnet::logthis($no_passback." for $uname:$udom in ${cdom}_${cnum}");
+ }
+ if ($no_passback) {
+ &Apache::lonnet::log($udom,$uname,$uhome,$no_passback." score: $score; total: $total; possible: $possible");
+ my $ltigrade = {
+ 'ltinum' => $ltinum,
+ 'lti' => $lti_in_use,
+ 'crsdef' => $crsdef,
+ 'cid' => $cdom.'_'.$cnum,
+ 'uname' => $uname,
+ 'udom' => $udom,
+ 'uhome' => $uhome,
+ 'pbid' => $id,
+ 'pburl' => $url,
+ 'pbtype' => $type,
+ 'pbscope' => $pbscope,
+ 'pbmap' => $pbmap,
+ 'pbsymb' => $pbsymb,
+ 'format' => $scoretype,
+ 'scope' => $scope,
+ 'clientip' => $clientip,
+ 'linkprot' => $linkprotector,
+ 'total' => $total,
+ 'possible' => $possible,
+ 'score' => $score,
+ };
+ &Apache::lonnet::put('linkprot_passback_pending',$ltigrade,$cdom,$cnum);
+ }
+ } else {
+ if (($id ne '') && ($url ne '')) {
+ $zeroposs{$student} = 1;
+ } else {
+ $nopbinfo{$student} = 1;
+ }
+ }
+ }
+ &Apache::lonhtmlcommon::Close_PrgWin($request,\%prog_state);
+ if ($ctr%2 ==1) {
+ $outcome .= &Apache::loncommon::end_data_table_row();
+ }
+ $outcome .= &Apache::loncommon::end_data_table();
+ if ($ctr) {
+ $request->print('<p><br />'.&mt('Scores sent to launcher CMS').'</p>'.
+ '<p>'.$outcome.'</p>');
+ } else {
+ $request->print('<p>'.&mt('No scores sent to launcher CMS').'</p>');
+ }
+ if (keys(%tosend)) {
+ $request->print('<p>'.&mt('No scores sent for following'));
+ my ($zeros,$nopbcreds,$noconfirm,$noscore);
+ foreach my $student (sort
+ {
+ if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
+ return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
+ }
+ return $a cmp $b;
+ } (keys(%$fullname))) {
+ next unless ($tosend{$student});
+ my ($uname,$udom) = split(/:/,$student);
+ my $line = '<li>'.&nameUserString(undef,$$fullname{$student},$uname,$udom).'</li>'."\n";
+ if ($zeroposs{$student}) {
+ $zeros .= $line;
+ } elsif ($nopbinfo{$student}) {
+ $nopbcreds .= $line;
+ } elsif ($remotenotok{$student}) {
+ $noconfirm .= $line;
+ } elsif ($scorenotok{$student}) {
+ $noscore .= $line;
+ }
+ }
+ if ($zeros) {
+ $request->print('<br />'.&mt('Total points possible was 0').':'.
+ '<ul>'.$zeros.'</ul><br />');
+ }
+ if ($nopbcreds) {
+ $request->print('<br />'.&mt('Missing unique identifier and/or passback location').':'.
+ '<ul>'.$nopbcreds.'</ul><br />');
+ }
+ if ($noconfirm) {
+ $request->print('<br />'.&mt('Score receipt not confirmed by receiving CMS').':'.
+ '<ul>'.$noconfirm.'</ul><br />');
+ }
+ if ($noscore) {
+ $request->print('<br />'.&mt('Score computation or transmission failed').':'.
+ '<ul>'.$noscore.'</ul><br />');
+ }
+ $request->print('</p>');
+ }
+ } else {
+ $error = &mt('Settings for deep-link launch target unavailable, so no scores were sent');
+ }
+ } else {
+ $error = &mt('No available students for whom scores can be sent.');
+ }
+ } else {
+ $error = &mt('Classlist could not be retrieved so no scores were sent.');
+ }
+ } else {
+ $error = &mt('No students selected to receive scores so none were sent.');
+ }
+ } else {
+ if ($env{'form.passback'}) {
+ $error = &mt('Deep-link launch target was invalid so no scores were sent.');
+ } else {
+ $error = &mt('Deep-link launch target was missing so no scores were sent.');
+ }
+ }
+ } else {
+ $error = &mt('You do not have permission to manage grades, so no scores were sent');
+ }
+ if ($error) {
+ $request->print('<p class="LC_info">'.$error.'</p>');
+ }
+ return;
+}
+
+sub get_passback_launcher {
+ my ($cdom,$cnum,$chosen) = @_;
+ my ($linkuri,$linkprotector,$scope) = split("\0",$chosen);
+ my ($ltinum,$ltitype) = ($linkprotector =~ /^(\d+)(c|d)$/);
+ my ($appname,$setter);
+ if ($ltitype eq 'c') {
+ my %lti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
+ if (ref($lti{$ltinum}) eq 'HASH') {
+ $appname = $lti{$ltinum}{'name'};
+ if ($appname) {
+ $setter = ' (defined in course)';
+ }
+ }
+ } elsif ($ltitype eq 'd') {
+ my %lti = &Apache::lonnet::get_domain_lti($cdom,'linkprot');
+ if (ref($lti{$ltinum}) eq 'HASH') {
+ $appname = $lti{$ltinum}{'name'};
+ if ($appname) {
+ $setter = ' (defined in domain)';
+ }
+ }
+ }
+ if ($linkuri =~ m{^\Q/tiny/$cdom/\E(\w+)$}) {
+ my $key = $1;
+ my $tinyurl;
+ my ($result,$cached)=&Apache::lonnet::is_cached_new('tiny',$cdom."\0".$key);
+ if (defined($cached)) {
+ $tinyurl = $result;
+ } else {
+ my $configuname = &Apache::lonnet::get_domainconfiguser($cdom);
+ my %currtiny = &Apache::lonnet::get('tiny',[$key],$cdom,$configuname);
+ if ($currtiny{$key} ne '') {
+ $tinyurl = $currtiny{$key};
+ &Apache::lonnet::do_cache_new('tiny',$cdom."\0".$key,$currtiny{$key},600);
+ }
+ }
+ if ($tinyurl) {
+ my ($crsnum,$launchsymb) = split(/\&/,$tinyurl);
+ if ($crsnum eq $cnum) {
+ my %passback = &Apache::lonnet::get('nohist_linkprot_passback',[$launchsymb],$cdom,$cnum);
+ if (ref($passback{$launchsymb}) eq 'HASH') {
+ if (exists($passback{$launchsymb}{$chosen})) {
+ return ($launchsymb,$appname,$setter)
+ }
+ }
+ }
+ }
+ }
+ return ();
+}
+
+sub sections_and_groups {
+ my (@sections, at groups,$group_display);
+ @groups = &Apache::loncommon::get_env_multiple('form.group');
+ if (grep(/^all$/, at groups)) {
+ @groups = ('all');
+ $group_display = 'all';
+ } elsif (grep(/^none$/, at groups)) {
+ @groups = ('none');
+ $group_display = 'none';
+ } elsif (@groups > 0) {
+ $group_display = join(', ', at groups);
+ }
+ if ($env{'request.course.sec'} ne '') {
+ @sections = ($env{'request.course.sec'});
+ } else {
+ @sections = &Apache::loncommon::get_env_multiple('form.section');
+ }
+ my $disabled = ' disabled="disabled"';
+ if ($perm{'mgr'}) {
+ if (grep(/^all$/, at sections)) {
+ undef($disabled);
+ } else {
+ foreach my $sec (@sections) {
+ if (&canmodify($sec)) {
+ undef($disabled);
+ last;
+ }
+ }
+ }
+ }
+ if (grep(/^all$/, at sections)) {
+ @sections = ('all');
+ }
+ return(\@sections,\@groups,$group_display,$disabled);
+}
+
+sub launcher_info_box {
+ my ($launcher,$appname,$setter,$linkuri,$scope) = @_;
+ my $shownscope;
+ if ($scope eq 'res') {
+ $shownscope = &mt('Resource');
+ } elsif ($scope eq 'map') {
+ $shownscope = &mt('Folder');
+ } elsif ($scope eq 'rec') {
+ $shownscope = &mt('Folder + sub-folders');
+ }
+ return '<p>'.
+ &Apache::lonhtmlcommon::start_pick_box().
+ &Apache::lonhtmlcommon::row_title(&mt('Launch Item Title')).
+ &Apache::lonnet::gettitle($launcher);
+ &Apache::lonhtmlcommon::row_closure().
+ &Apache::lonhtmlcommon::row_title(&mt('Deep-link')).
+ $linkuri.
+ &Apache::lonhtmlcommon::row_closure().
+ &Apache::lonhtmlcommon::row_title(&mt('Launcher')).
+ $appname.' '.$setter.
+ &Apache::lonhtmlcommon::row_closure().
+ &Apache::lonhtmlcommon::row_title(&mt('Score Type')).
+ $shownscope.
+ &Apache::lonhtmlcommon::row_closure(1).
+ &Apache::lonhtmlcommon::end_pick_box().'</p>'."\n";
+}
+
#--- This is called by a number of programs.
#--- Called from the Grading Menu - View/Grade an individual student
#--- Also called directly when one clicks on the subm button
@@ -1065,34 +1697,8 @@
}
}
- my %js_lt = &Apache::lonlocal::texthash (
- 'multiple' => 'Please select a student or group of students before clicking on the Next button.',
- 'single' => 'Please select the student before clicking on the Next button.',
- );
- &js_escape(\%js_lt);
+ $request->print(&checkselect_js());
$request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
- function checkSelect(checkBox) {
- var ctr=0;
- var sense="";
- if (checkBox.length > 1) {
- for (var i=0; i<checkBox.length; i++) {
- if (checkBox[i].checked) {
- ctr++;
- }
- }
- sense = '$js_lt{'multiple'}';
- } else {
- if (checkBox.checked) {
- ctr = 1;
- }
- sense = '$js_lt{'single'}';
- }
- if (ctr == 0) {
- alert(sense);
- return false;
- }
- document.gradesub.submit();
- }
function reLoadList(formname) {
if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
@@ -1380,7 +1986,55 @@
return '';
}
-#---- Called from the listStudents routine
+#---- Called from the listStudents and the names_for_passback routines.
+
+sub checkselect_js {
+ my ($formname) = @_;
+ if ($formname eq '') {
+ $formname = 'gradesub';
+ }
+ my %js_lt;
+ if ($formname eq 'passbackusers') {
+ %js_lt = &Apache::lonlocal::texthash (
+ 'multiple' => 'Please select a student or group of students before pushing the Save Scores button.',
+ 'single' => 'Please select the student before pushing the Save Scores button.',
+ );
+ } else {
+ %js_lt = &Apache::lonlocal::texthash (
+ 'multiple' => 'Please select a student or group of students before clicking on the Next button.',
+ 'single' => 'Please select the student before clicking on the Next button.',
+ );
+ }
+ &js_escape(\%js_lt);
+ return &Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT);
+
+ function checkSelect(checkBox) {
+ var ctr=0;
+ var sense="";
+ var len = checkBox.length;
+ if (len == undefined) len = 1;
+ if (len > 1) {
+ for (var i=0; i<len; i++) {
+ if (checkBox[i].checked) {
+ ctr++;
+ }
+ }
+ sense = '$js_lt{'multiple'}';
+ } else {
+ if (checkBox.checked) {
+ ctr = 1;
+ }
+ sense = '$js_lt{'single'}';
+ }
+ if (ctr == 0) {
+ alert(sense);
+ return false;
+ }
+ document.$formname.submit();
+ }
+LISTJAVASCRIPT
+
+}
sub check_script {
my ($form,$type) = @_;
@@ -10560,7 +11214,8 @@
sub href_symb_cmd {
my ($symb,$cmd)=@_;
- return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&command='.$cmd;
+ return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&command='.
+ &HTML::Entities::encode($cmd,'<>&"');
}
sub grading_menu {
@@ -10669,7 +11324,20 @@
]
});
-
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my %passback = &Apache::lonnet::dump('nohist_linkprot_passback',$cdom,$cnum);
+ if (keys(%passback)) {
+ $fields{'command'} = 'initialpassback';
+ my $url6 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
+ push (@{$menu[1]{items}},
+ { linktext => 'Passback of Scores',
+ url => $url6,
+ permission => $permissions{'either'},
+ icon => 'passback.png',
+ linktitle => 'Passback scores to launcher CMS for resources accessed via LTI-mediated deep-linking',
+ });
+ }
# Create the menu
my $Str;
$Str .= '<form method="post" action="" name="gradingMenu">';
@@ -11889,6 +12557,44 @@
undef,undef,undef,undef,undef,undef,undef,1);
$request->print('<div style="padding:0;clear:both;margin:0;border:0"></div>');
&submit_download_link($request,$symb);
+ } elsif ($command eq 'initialpassback') {
+ &startpage($request,$symb,[{href=>'', text=>'Choose Launcher'}],undef,1);
+ $request->print(&initialpassback($request,$symb));
+ } elsif ($command eq 'passback') {
+ &startpage($request,$symb,
+ [{href=>&href_symb_cmd($symb,'initialpassback'), text=>'Choose Launcher'},
+ {href=>'', text=>'Types of User'}],undef,1);
+ $request->print(&passback_filters($request,$symb));
+ } elsif ($command eq 'passbacknames') {
+ my $chosen;
+ if ($env{'form.passback'} ne '') {
+ if ($env{'form.passback'} eq &unescape($env{'form.passback'})) {
+ $env{'form.passback'} = &escape($env{'form.passback'} );
+ }
+ $chosen = &HTML::Entities::encode($env{'form.passback'},'<>"&');
+ }
+ &startpage($request,$symb,
+ [{href=>&href_symb_cmd($symb,'initialpassback'), text=>'Choose Launcher'},
+ {href=>&href_symb_cmd($symb,'passback').'&passback='.$chosen, text=>'Types of User'},
+ {href=>'', text=>'Select Users'}],undef,1);
+ $request->print(&names_for_passback($request,$symb));
+ } elsif ($command eq 'passbackscores') {
+ my ($chosen,$stu_status);
+ if ($env{'form.passback'} ne '') {
+ if ($env{'form.passback'} eq &unescape($env{'form.passback'})) {
+ $env{'form.passback'} = &escape($env{'form.passback'} );
+ }
+ $chosen = &HTML::Entities::encode($env{'form.passback'},'<>"&');
+ }
+ if ($env{'form.Status'}) {
+ $stu_status = &HTML::Entities::encode($env{'form.Status'});
+ }
+ &startpage($request,$symb,
+ [{href=>&href_symb_cmd($symb,'initialpassback'), text=>'Choose Launcher'},
+ {href=>&href_symb_cmd($symb,'passback').'&passback='.$chosen, text=>'Types of User'},
+ {href=>&href_symb_cmd($symb,'passbacknames').'&Status='.$stu_status.'&passback='.$chosen, text=>'Select Users'},
+ {href=>'', text=>'Execute Passback'}],undef,1);
+ $request->print(&do_passback($request,$symb));
} elsif ($command) {
&startpage($request,$symb,[{href=>'', text=>'Access denied'}]);
$request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>');
Index: loncom/interface/loncourserespicker.pm
diff -u loncom/interface/loncourserespicker.pm:1.18 loncom/interface/loncourserespicker.pm:1.19
--- loncom/interface/loncourserespicker.pm:1.18 Sun Nov 24 04:17:50 2024
+++ loncom/interface/loncourserespicker.pm Tue Dec 3 23:34:11 2024
@@ -1,6 +1,6 @@
# The LearningOnline Network
#
-# $Id: loncourserespicker.pm,v 1.18 2024/11/24 04:17:50 raeburn Exp $
+# $Id: loncourserespicker.pm,v 1.19 2024/12/03 23:34:11 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -33,51 +33,73 @@
=head1 SYNOPSIS
-loncourserespicker provides an interface for selecting which folders and/or
-resources are to be either:
+loncourserespicker provides either (1) an interface for selecting which
+folders and/or resources are to be selected for a specific action, one of:
-(a) exported to an IMS Content Package
-(b) subject to access blocking for the duration of an exam/quiz.
-(c) dumped to an Authoring Space
+(a) export to an IMS Content Package
+(b) be subject to access blocking for the duration of an exam/quiz.
+(c) dump to an Authoring Space
(d) receive shortened URLs to be used when deep-linking into a course
+or (2) an interface for selecting a single folder or resource for which
+existing passback credentials can be used to send scores to another Course
+Management System (CMS).
+
=head1 DESCRIPTION
This module provides routines to generate a hierarchical display of folders
-and resources in a course which can be selected for specific actions.
-
-The choice of items is copied back to the main window from which the pop-up
-window used to display the Course Contents was opened.
+and resources in a course which can be selected for specific actions. In
+all except one use case all items in the course are shown. The case where
+only a filtered list is shown is passback of scores, and filtering limits
+folders and resources to those items for which passback credentials exist,
+(and their parent folders).
+
+When the display is shown in a pop-up window, The choice of items will be
+copied back to the main window from which the pop-up window used to display
+the Course Contents was opened.
=head1 OVERVIEW
-The main subroutine: &create_picker() will display the hierarchy of folders,
-sub-folders, and resources in the Main Content area. Items can be selected
-using checkboxes, and/or a "Check All" button. Selection of a folder
-causes the contents of the folder to also be selected automatically. The
-propagation of check status is recursive into sub-folders. Likewise, if an
-item deep in a nested set of folders and sub-folders is unchecked, the
-uncheck will propagate up through the hierarchy causing any folders at
-a higher level to become unchecked.
+In the cases where multiple items may be selected the main subroutine:
+&create_picker() will display the hierarchy of folders, sub-folders, and
+resources in the Main Content area. Items can be selected using checkboxes,
+and/or a "Check All" button. Selection of a folder causes the contents of
+the folder to also be selected automatically. The propagation of check
+status is recursive into sub-folders. Likewise, if an item deep in a nested
+set of folders and sub-folders is unchecked, the uncheck will propagate up
+through the hierarchy causing any folders at a higher level to become
+unchecked.
+
+In the case where only a single item may be selected the main subroutine:
+&create_picker() will display the hierarchy of folders and sub-folders for
+only those items for which passback credentials exist,
There is a submit button, which will be named differently according to the
context in which resource/folder selection is being made.
-The four contexts currently supported are: IMS export, selection of
+The five contexts currently supported are: IMS export, selection of
content to be subject to access restructions for the duration of an
-exam, selection of items for dumping to an Authoring Space, and
-display or creation of shortened URLs for deep-linking,
+exam, selection of items for dumping to an Authoring Space, display or
+creation of shortened URLs for deep-linking, and selection of a single
+item for apssback of grades to another CMS.
=head1 INTERNAL SUBROUTINES
=item &create_picker()
-Created HTML mark up to display contents of course with checkboxes to
+In the cases where multiple items may be selected ...
+
+Creates HTML markup to display contents of course with checkboxes to
select items. Checking a folder causes recursive checking of items
within the folder. Unchecking a resource causing unchecking of folders
containing the item back up to the top level.
-Inputs: 11.
+In the case where only a single item may be selected ...
+
+Creates HTML markup to display filtered contents of course with radio
+buttons to select an item.
+
+Inputs: 13.
- $navmap -- Reference to LON-CAPA navmap object
(encapsulates information about resources in the course).
@@ -113,13 +135,23 @@
- $tiny -- Reference to hash: keys are symbs of course items for which
shortened URLs have already been created.
+ - $passback -- Reference to hash: keys are symbs of course items for
+ which passback credentials exist. For each symb the
+ hash value is itself a hash of deeplink launch items
+ for that symb with inner hash key set to:
+ $linkuri\0$linkprotector\0$scope, and corresponding
+ value of 1.
+
- $readonly -- if true, no "check all" or "uncheck all" buttons will
be displayed, and checkboxes will be disabled, if this
- is for an exam block or for shortened URL creation.
+ is for an exam block or for shortened URL creation,
+ and radio buttons will be disabled, if this is for
+ passback of scores to another CMS,
Output: $output is the HTML mark-up for display/selection of content
- items in the pop-up window.
+ items, either in a pop-up window, or in the main window,
+ depending on context.
=item &respicker_javascript()
@@ -154,7 +186,8 @@
Inputs: 2.
- $crstype -- Container type: Course or Community
- - $context -- Context: imsexport, examblock, dumpdocs, or shorturls
+ - $context -- Context: imsexport, examblock, dumpdocs, shorturls
+ or passback.
=item &clean()
@@ -219,13 +252,15 @@
sub create_picker {
my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block,$preamble,
- $numhome,$uploadedfiles,$tiny,$readonly) = @_;
+ $numhome,$uploadedfiles,$tiny,$passback,$readonly) = @_;
return unless (ref($navmap));
my ($it,$output,$numdisc,%discussiontime,%currmaps,%currresources,%files,
- %shorturls,$chkname);
+ %shorturls,%shownmaps,%shownsymbs,%recursed,%retrieved,%pb,$chkname);
$chkname = 'archive';
if ($context eq 'shorturls') {
$chkname = 'addtiny';
+ } elsif ($context eq 'passback') {
+ $chkname = 'passback';
}
$it = $navmap->getIterator(undef,undef,undef,1,undef,undef);
if (ref($blockedmaps) eq 'HASH') {
@@ -237,6 +272,51 @@
%files = %{$uploadedfiles};
} elsif (ref($tiny) eq 'HASH') {
%shorturls = %{$tiny};
+ } elsif ($context eq 'passback') {
+ if (ref($passback) eq 'HASH') {
+ %pb = %{$passback};
+ foreach my $symb (keys(%pb)) {
+ my ($map,$id,$url) = &Apache::lonnet::decode_symb($symb);
+ my @recurseup;
+ if ($url =~ /\.(page|sequence)$/) {
+ @recurseup = $navmap->recurseup_maps($url);
+ $shownmaps{&Apache::lonnet::clutter($url)} = 1;
+ if (ref($pb{$symb}) eq 'HASH') {
+ foreach my $entry (keys(%{$pb{$symb}})) {
+ my $scope = (split("\0",$entry))[-1];
+ if (($scope eq 'map') || ($scope eq 'rec')) {
+ my @contents;
+ if ($scope eq 'map') {
+ unless ($retrieved{$url} || $recursed{$url}) {
+ @contents = $navmap->retrieveResources($url,sub { $_[0]->is_gradable() },0);
+ $retrieved{$url} = 1;
+ }
+ } elsif ($scope eq 'rec') {
+ unless ($recursed{$url}) {
+ @contents = $navmap->retrieveResources($url,sub { $_[0]->is_gradable() },1,0,1);
+ my @subfolders = $navmap->retrieveResources($url,sub { $_[0]->is_map() },1,0,1);
+ if (@subfolders) {
+ map { $shownmaps{$_->src()} = 1; } @subfolders;
+ }
+ $recursed{$url} = 1;
+ }
+ }
+ if (@contents) {
+ map { $shownsymbs{$_->symb()} = 1; } @contents;
+ }
+ }
+ }
+ }
+ } else {
+ @recurseup = $navmap->recurseup_maps($map);
+ $shownmaps{&Apache::lonnet::clutter($map)} = 1;
+ $shownsymbs{$symb} = 1;
+ }
+ if (@recurseup) {
+ map { $shownmaps{&Apache::lonnet::clutter($_)} = 1; } @recurseup;
+ }
+ }
+ }
}
my @checked_maps;
my $curRes;
@@ -256,7 +336,7 @@
my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
my $crsprefix = &propath($cdom,$cnum).'/userfiles/';
- my ($info,$display,$onsubmit,$togglebuttons,$disabled);
+ my ($info,$display,$onsubmit,$togglebuttons,$disabled,$action);
if ($context eq 'examblock') {
my $maps_elem = 'docs_maps_'.$block;
my $res_elem = 'docs_resources_'.$block;
@@ -279,16 +359,25 @@
} elsif ($context eq 'imsexport') {
$info = &mt('Choose which items you wish to export from your '.$crstype.'.');
$startcount = 5;
+ } elsif ($context eq 'passback') {
+ $action = '/adm/grades';
+ $info = '<p>'.
+ &mt('Select link-protected launch item for which scores should be sent to launcher CMS, then push Next [_1].',
+ '→').
+ '</p><br />';
+ if ($readonly) {
+ $disabled = ' disabled="disabled"';
+ }
}
if ($disabled) {
$togglebuttons = '<br />';
- } else {
+ } elsif ($context ne 'passback') {
$togglebuttons = '<input type="button" value="'.&mt('check all').'" '.
'onclick="javascript:checkAll(document.'.$formname.'.'.$chkname.')" />'.
' <input type="button" value="'.&mt('uncheck all').'"'.
' onclick="javascript:uncheckAll(document.'.$formname.'.'.$chkname.')" />';
}
- $display = '<form name="'.$formname.'" action="" method="post"'.$onsubmit.'>'."\n";
+ $display = '<form name="'.$formname.'" action="'.$action.'" method="post"'.$onsubmit.'>'."\n";
if ($context eq 'imsexport') {
$display .= $info.
'<div class="LC_columnSection">'."\n".
@@ -309,7 +398,7 @@
'</fieldset>';
}
$display .= '</div>';
- } elsif (($context eq 'examblock') || ($context eq 'shorturls')) {
+ } elsif (($context eq 'examblock') || ($context eq 'shorturls') || ($context eq 'passback')) {
$display .= $info.$togglebuttons;
} elsif ($context eq 'dumpdocs') {
$display .= $preamble.
@@ -336,6 +425,12 @@
} elsif ($context eq 'shorturls') {
$display .= '<th colspan="2">'.&mt('Tiny URL').'</th>'.
'<th>'.&mt("Title in $crstype").'</th>';
+ } elsif ($context eq 'passback') {
+ $display .= '<th>'.&mt("Title in $crstype").'</th>'.
+ '<th>'.&mt('Tiny URL Deep-link').'</th>'.
+ '<th>'.&mt('Launcher').'</th>'.
+ '<th style="padding-left: 6px; padding-right: 6px">'.&mt('Score Type').'</th>'.
+ '<th style="padding-left: 6px; padding-right: 6px">'.&mt('Select').'</th>';
}
$display .= &Apache::loncommon::end_data_table_header_row();
while ($curRes = $it->next()) {
@@ -361,18 +456,26 @@
}
}
$count ++;
- my ($currelem,$mapurl,$is_map);
+ my ($currelem,$mapurl,$is_map,$showitem);
if ($context eq 'imsexport') {
$currelem = $count+$boards+$startcount;
} else {
$currelem = $count+$startcount;
}
- $display .= &Apache::loncommon::start_data_table_row()."\n";
if (($curRes->is_sequence()) || ($curRes->is_page())) {
$lastcontainer = $currelem;
$mapurl = (&Apache::lonnet::decode_symb($symb))[2];
$is_map = 1;
}
+ if ($context eq 'passback') {
+ if (($curRes->is_sequence()) || ($curRes->is_page())) {
+ next unless ($shownmaps{$curRes->src});
+ } else {
+ next unless ($shownsymbs{$symb});
+ }
+ } else {
+ $display .= &Apache::loncommon::start_data_table_row()."\n";
+ }
if ($context eq 'shorturls') {
if ($shorturls{$symb}) {
$display .= '<td> </td><td align="right"><b>'."/tiny/$cdom/$shorturls{$symb}".'</b></td>'."\n";
@@ -381,7 +484,7 @@
'value="'.$count.'"'.$disabled.' />'.&mt('Add').'</label></td>'.
'<td> </td>'."\n";
}
- } else {
+ } elsif ($context ne 'passback') {
$display .= '<td><input type="checkbox" name="'.$chkname.'" value="'.$count.'" ';
if ($is_map) {
$display .= 'onclick="javascript:checkFolder(document.'.$formname.','."'$currelem'".')" ';
@@ -391,7 +494,7 @@
}
} else {
if ($curRes->is_problem()) {
- $numprobs ++;
+ $numprobs ++;
}
$display .= 'onclick="javascript:checkResource(document.'.$formname.','."'$currelem'".')" ';
if ($currresources{$symb}) {
@@ -406,7 +509,7 @@
$display .= '<td valign="top">';
}
for (my $i=0; $i<$depth; $i++) {
- $display .= "$whitespace\n";
+ $showitem .= "$whitespace\n";
}
my $icon = 'src="'.$location.'/unknown.gif" alt=""';
if ($curRes->is_sequence()) {
@@ -420,7 +523,7 @@
} elsif ($curRes->src ne '') {
$icon = 'src="'.&Apache::loncommon::icon($curRes->src).'" alt=""';
}
- $display .= '<img '.$icon.' /> '."\n";
+ $showitem .= '<img '.$icon.' /> '."\n";
$children{$parent{$depth}} .= $currelem.':';
if ($context eq 'examblock') {
if ($parent{$depth} > 1) {
@@ -431,8 +534,65 @@
}
}
}
- $display .= ' '.$curRes->title().$whitespace.'</td>'."\n";
-
+ $showitem .= ' '.$curRes->title().$whitespace;
+ if ($context eq 'passback') {
+ if ((exists($pb{$symb})) && (ref($pb{$symb}) eq 'HASH')) {
+ my $numlinks = scalar(keys(%{$pb{$symb}}));
+ my $count = 0;
+ foreach my $launcher (sort(keys(%{$pb{$symb}}))) {
+ if ($count == 0) {
+ $display .= &Apache::loncommon::start_data_table_row()."\n";
+ if ($numlinks > 1) {
+ $display .= '<td rowspan="'.$numlinks.'">'.$showitem.'</td>';
+ } else {
+ $display .= '<td style="vertical-align: baseline">'.$showitem.'</td>';
+ }
+ } else {
+ $display .= &Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::start_data_table_row()."\n";
+ }
+ my ($linkuri,$linkprotector,$scope) = split("\0",$launcher);
+ my ($ltinum,$ltitype) = ($linkprotector =~ /^(\d+)(c|d)$/);
+ my ($appname,$setter);
+ if ($ltitype eq 'c') {
+ my %lti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
+ if (ref($lti{$ltinum}) eq 'HASH') {
+ $appname = $lti{$ltinum}{'name'};
+ if ($appname) {
+ $setter = ' (defined in course)';
+ }
+ }
+ } elsif ($ltitype eq 'd') {
+ my %lti = &Apache::lonnet::get_domain_lti($cdom,'linkprot');
+ if (ref($lti{$ltinum}) eq 'HASH') {
+ $appname = $lti{$ltinum}{'name'};
+ if ($appname) {
+ $setter = ' (defined in domain)';
+ }
+ }
+ }
+ my $shownscope;
+ if ($scope eq 'res') {
+ $shownscope = &mt('Resource');
+ } elsif ($scope eq 'map') {
+ $shownscope = &mt('Folder');
+ } elsif ($scope eq 'rec') {
+ $shownscope = &mt('Folder + sub-folders');
+ }
+ $display .= '<td style="vertical-align: baseline"><span style="font-weight: bold;">'.$linkuri.'</span></td>'."\n".
+ '<td style="vertical-align: baseline; padding-left: 6px; padding-right: 6px">'.$appname.$setter.'</td>'."\n".
+ '<td style="vertical-align: baseline"><span style="font-style: italic;">'.$shownscope.'</span></td>'."\n".
+ '<td align="right" style="vertical-align: baseline"><input type="radio" name="'.$chkname.'" '.
+ 'value="'.&escape($launcher).'"'.$disabled.' /></td>'."\n";
+ $count ++;
+ }
+ } else {
+ $display .= &Apache::loncommon::start_data_table_row()."\n".
+ '<td colspan="5">'.$showitem.'</td>';
+ }
+ } else {
+ $display .= $showitem.'</td>'."\n";
+ }
if ($context eq 'imsexport') {
# Existing discussion posts?
if ($discussiontime{$ressymb} > 0) {
@@ -519,8 +679,20 @@
'<input type="submit" name="shorturls" value="'.
&mt('Create Tiny URL(s)').'" /></p>';
}
+ } elsif ($context eq 'passback') {
+ unless ($readonly) {
+ $display .=
+ '<p>'.
+ '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($env{'form.symb'}).'" />'."\n".
+ '<input type="hidden" name="command" value="passback" />'.
+ '<input type="submit" name="picklauncher" value="'.
+ &mt('Next').' →" /></p>';
+ }
}
$display .= '</form>';
+ if ($context eq 'passback') {
+ return $display;
+ }
my $scripttag =
&respicker_javascript($startcount,$numcount,$context,$formname,\%children,
\%hierarchy,\@checked_maps,$numhome,$chkname);
Index: doc/loncapafiles/loncapafiles.lpml
diff -u doc/loncapafiles/loncapafiles.lpml:1.1073 doc/loncapafiles/loncapafiles.lpml:1.1074
--- doc/loncapafiles/loncapafiles.lpml:1.1073 Fri Nov 15 16:25:30 2024
+++ doc/loncapafiles/loncapafiles.lpml Tue Dec 3 23:34:12 2024
@@ -2,7 +2,7 @@
"http://lpml.sourceforge.net/DTD/lpml.dtd">
<!-- loncapafiles.lpml -->
-<!-- $Id: loncapafiles.lpml,v 1.1073 2024/11/15 16:25:30 raeburn Exp $ -->
+<!-- $Id: loncapafiles.lpml,v 1.1074 2024/12/03 23:34:12 raeburn Exp $ -->
<!--
@@ -8625,6 +8625,7 @@
navigation.png;
network-workgroup.png;
page.png;
+passback.png;
preferences-desktop-font.png;
preferences-desktop-locale.png;
preferences-desktop-remote-desktop.png;
More information about the LON-CAPA-cvs
mailing list