--- rat/lonuserstate.pm 2024/07/03 02:16:44 1.149.2.6 +++ rat/lonuserstate.pm 2022/10/04 20:39:57 1.169 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Construct and maintain state and binary representation of course for user # -# $Id: lonuserstate.pm,v 1.149.2.6 2024/07/03 02:16:44 raeburn Exp $ +# $Id: lonuserstate.pm,v 1.169 2022/10/04 20:39:57 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -62,6 +62,7 @@ my %randomorder; # maps to order content my %randomizationcode; # code used to grade folder for bubblesheet exam my %encurl; # URLs in this folder are supposed to be encrypted my %hiddenurl; # this URL (or complete folder) is supposed to be hidden +my %deeplinkout; # this URL (or complete folder) unavailable in deep-link session my %rescount; # count of unhidden items in each map my %mapcount; # count of unhidden maps in each map @@ -259,9 +260,9 @@ sub loadmap { push(@map_ids, $resource_id); if ($hash{'src_'.$lpc.'.'.$resource_id}) { $rescount{$lpc} ++; - if (($hash{'src_'.$lpc.'.'.$resource_id}=~/\.sequence$/) || + if (($hash{'src_'.$lpc.'.'.$resource_id}=~/\.sequence$/) || ($hash{'src_'.$lpc.'.'.$resource_id}=~/\.page$/)) { - $mapcount{$lpc} ++; + $mapcount{$lpc} ++; } } unless ($codechecked) { @@ -404,6 +405,12 @@ sub error_detail { if (($parent_pc eq '0') && ($hash{'map_id_1'} =~ m{^/res/($match_domain)/($match_username)/.+\.(sequence|page)$})) { ($audomfile,$aunamefile) = ($1,$2); ($editfile,$filerole,$fileswitch) = &canedit_published($audomfile,$aunamefile); + if ($fileswitch) { + unless ((&Apache::lonnet::will_trust('othcoau',$env{'user.domain'},$audomfile)) && + (&Apache::lonnet::will_trust('coaurem',$audomfile,$env{'user.domain'}))) { + undef($editfile); + } + } $errinfo = &mt('Top level published sequence file is missing.'); } else { if ($parent_pc eq '1') { @@ -477,6 +484,12 @@ sub error_detail { if ($uri =~ m{^/res/($match_domain)/($match_username)/.+\.(sequence|page)$}) { ($audomfile,$aunamefile) = ($1,$2); ($editfile,$filerole,$fileswitch) = &canedit_published($audomfile,$aunamefile); + if ($fileswitch) { + unless ((&Apache::lonnet::will_trust('othcoau',$env{'user.domain'},$audomfile)) && + (&Apache::lonnet::will_trust('coaurem',$audomfile,$env{'user.domain'}))) { + undef($editfile); + } + } } } if ($errinfo) { @@ -491,8 +504,6 @@ sub error_detail { } else { $errinfo .= &mt('To rectify this problem, create and publish the missing file'); } - my $fileurl = $uri; - $fileurl =~s{^/res/}{/priv/}; if ($fileswitch) { my $rolename = &Apache::lonnet::plaintext($filerole); my $rolecode; @@ -502,10 +513,10 @@ sub error_detail { $rolecode = $filerole.'./'.$audomfile.'/'.$aunamefile; } $errinfo .= '.
'.&mt('You will need to [_1]switch server[_2].', - '',''); + '',''); } else { - &js_escape(\$fileurl); + my $fileurl = $uri; + $fileurl =~s{^/res/}{/priv/}; $errinfo .= ': '.&mt('Create the missing file').''; } } @@ -526,15 +537,18 @@ sub error_detail { } else { $errinfo .= &mt('To rectify this problem edit the parent map to remove the reference to the missing file'); } - my $mapurl = $hash{'map_id_'.$parent_pc}; - $mapurl =~s{^/res/}{/priv/}; if ($switchserver) { - $errinfo .= '.
'. - &mt('You will need to [_1]switch server[_2].', - '',''); + $errinfo .= '.
'; + if ((&Apache::lonnet::will_trust('othcoau',$env{'user.domain'},$audom)) && + (&Apache::lonnet::will_trust('coaurem',$audom,$env{'user.domain'}))) { + $errinfo .= &mt('You will need to [_1]switch server[_2].', + '',''); + } else { + $errinfo .= &mt('Session switch required but prohibited.'); + } } else { - &js_escape(\$mapurl); + my $mapurl = $hash{'map_id_'.$parent_pc}; + $mapurl =~s{^/res/}{/priv/}; $errinfo .= ': '.&mt('Edit the map').''; } } elsif ($uploaded && $courseid) { @@ -710,6 +724,8 @@ sub parse_resource { } elsif ($turi!~/\.(sequence|page)$/) { $turi='/adm/coursedocs/showdoc'.$turi; } + } elsif ($turi=~ m{^/adm/$match_domain/$match_courseid/\d+/ext\.tool$}) { + $turi='/adm/wrapper'.$turi; } elsif ($turi=~/\S/) { # normal non-empty internal resource my $mapdir=$uri; $mapdir=~s/[^\/]+$//; @@ -1120,7 +1136,7 @@ sub simplify { # new value indicating how far the map has been traversed (the sofar). # sub traceroute { - my ($sofar,$rid,$beenhere,$encflag,$hdnflag)=@_; + my ($sofar,$rid,$beenhere,$encflag,$hdnflag,$cid)=@_; my $newsofar=$sofar=simplify($sofar); unless ($beenhere=~/\&\Q$rid\E\&/) { @@ -1144,6 +1160,30 @@ sub traceroute { $retfrid=$rid; } + my (@deeplink,@recurseup); + if ($hash{'is_map_'.$rid}) { + my ($cdom,$cnum) = split(/_/,$cid); + my $mapsrc = $hash{'src_'.$rid}; + my $map_pc = $hash{'map_pc_'.$mapsrc}; + my @pcs = split(/,/,$hash{'map_hierarchy_'.$map_pc}); + shift(@pcs); + @recurseup = map { &Apache::lonnet::declutter($hash{'map_id_'.$_}) } reverse(@pcs); + my $mapname = &Apache::lonnet::declutter(&Apache::lonnet::deversion($mapsrc)); + my $deeplinkval = &get_mapparam($env{'user.name'},$env{'user.domain'},$cnum,$cdom, + $rid,$mapname,'0.deeplink',\@recurseup); + if ($deeplinkval ne '') { + @deeplink = ($deeplinkval,'map'); + } + } else { + my @pcs = split(/,/,$hash{'map_hierarchy_'.$mapid}); + shift(@pcs); + @recurseup = map { &Apache::lonnet::declutter($hash{'map_id_'.$_}) } reverse(@pcs); + @deeplink = &Apache::lonnet::EXT('resource.0.deeplink',$symb,'','','','',$cid,\@recurseup); + } + unless (@deeplink < 2) { + $hash{'deeplinkonly_'.$rid}=join(':',map { &escape($_); } @deeplink); + } + if (defined($hash{'conditions_'.$rid})) { $hash{'conditions_'.$rid}=simplify( '('.$hash{'conditions_'.$rid}.')|('.$sofar.')'); @@ -1165,7 +1205,8 @@ sub traceroute { $hash{'map_start_'.$hash{'src_'.$rid}}, $beenhere, $encflag || $encurl{$rid}, - $hdnflag || $hiddenurl{$rid}); + $hdnflag || $hiddenurl{$rid}, + $cid); } } @@ -1192,7 +1233,7 @@ sub traceroute { } # Recurse to resoruces that have to's to us. $newsofar=&traceroute($further,$hash{'goesto_'.$id},$beenhere, - $encflag,$hdnflag); + $encflag,$hdnflag,$cid); } } } @@ -1389,17 +1430,34 @@ sub hiddenurls { } } +sub deeplinkouts { + my $deeplinkoutentry; + foreach my $rid (keys(%deeplinkout)) { + $hash{'deeplinkout_'.$rid}=1; + my ($mapid,$resid)=split(/\./,$rid); + $deeplinkoutentry.='&'. + &Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},$resid, + $hash{'src_'.$rid}).'&'; + } +# --------------------------------------- append deeplinkout entry to environment + if ($deeplinkoutentry) { + &Apache::lonnet::appenv({'acc.deeplinkout' => $deeplinkoutentry}); + } +} + # -------------------------------------- populate big hash with map breadcrumbs # Create map_breadcrumbs_$pc from map_hierarchy_$pc by omitting intermediate # maps not shown in Course Contents table. sub mapcrumbs { + my ($cid) = @_; foreach my $key (keys(%rescount)) { if ($hash{'map_hierarchy_'.$key}) { my $skipnext = 0; foreach my $id (split(/,/,$hash{'map_hierarchy_'.$key}),$key) { - unless ($skipnext) { + my $rid = $hash{'ids_'.$hash{'map_id_'.$id}}; + unless (($skipnext) || (!&is_advanced($cid) && $hash{'deeplinkout_'.$rid})) { $hash{'map_breadcrumbs_'.$key} .= "$id,"; } unless (($id == 0) || ($id == 1)) { @@ -1458,6 +1516,7 @@ sub readmap { undef %randomizationcode; undef %hiddenurl; undef %encurl; + undef %deeplinkout; undef %rescount; undef %mapcount; $retfrid=''; @@ -1606,6 +1665,7 @@ sub readmap { undef %randomizationcode; undef %hiddenurl; undef %encurl; + undef %deeplinkout; undef %rescount; undef %mapcount; $errtext=''; @@ -1666,7 +1726,7 @@ sub readmap { if ($redirect) { $retfurl = $url; } - } + } return ($retfurl,$errtext); } @@ -1714,7 +1774,6 @@ sub build_tmp_hashes { # Load the map.. note that loadmap may implicitly recurse if the map contains # sub-maps. - &loadmap($uri,'0.0',$short); # The code below only executes if there is a starting point for the map> @@ -1727,10 +1786,9 @@ sub build_tmp_hashes { "request.course.uri" => $uri, "request.course.tied" => time}); $env{'request.course.id'}=$short; - &traceroute('0',$hash{'map_start_'.$uri},'&'); + &traceroute('0',$hash{'map_start_'.$uri},'&','','',$short); &accinit($uri,$short,$fn); &hiddenurls(); - &mapcrumbs(); } $errtext .= &get_mapalias_errors(); # ------------------------------------------------------- Put versions into src @@ -1770,6 +1828,92 @@ sub build_tmp_hashes { "Could not write statemap $fn for $uri."); } } + + # Was initial access via a deep-link? + my ($cdom,$cnum) = split(/_/,$short); + if (($cdom ne '') && ($env{'request.deeplink.login'} ne '')) { + my $deeplink_symb = &Apache::loncommon::deeplink_login_symb($cnum,$cdom); + if ($deeplink_symb) { + my ($loginrid,$deeplink_login_pc,$login_hierarchy); + my ($map,$resid,$url) = &Apache::lonnet::decode_symb($deeplink_symb); + $loginrid = $hash{'map_pc_'.&Apache::lonnet::clutter($map)}.'.'.$resid; + if ($deeplink_symb =~ /\.(page|sequence)$/) { + $deeplink_login_pc = $hash{'map_pc_'.&Apache::lonnet::clutter($url)}; + } else { + $deeplink_login_pc = $hash{'map_pc_'.&Apache::lonnet::clutter($map)}; + } + my $deeplink; + if ($hash{'deeplinkonly_'.$loginrid} ne '') { + my @deeplinkinfo = map { &unescape($_); } split(/:/,$hash{'deeplinkonly_'.$loginrid}); + unless (@deeplinkinfo < 2) { + $deeplink = $deeplinkinfo[0]; + } + } + if ($deeplink) { + my $disallow; + my ($state,$others,$listed,$scope,$protect) = split(/,/,$deeplink); + if (($protect ne 'none') && ($protect ne '')) { + my ($acctype,$item) = split(/:/,$protect); + if ($acctype =~ /lti(c|d)$/) { + unless ($env{'request.linkprot'} eq $item.$1.':'.$env{'request.deeplink.login'}) { + $disallow = 1; + } + } elsif ($acctype eq 'key') { + unless ($env{'request.linkkey'} eq $item) { + $disallow = 1; + } + } + } + if ($disallow) { + &Apache::lonnet::delenv('request.deeplink.login'); + if ($env{'request.deeplink.target'} ne '') { + &Apache::lonnet::delenv('request.deeplink.target'); + } + } else { + if ($others eq 'hide') { + my @recfolders; + if ($scope eq 'rec') { + foreach my $key (keys(%hash)) { + if ($key=~/^map_hierarchy_(\d+)$/) { + my $mpc = $1; + my @ids = split(/,/,$hash{$key}); + if (grep(/^$deeplink_login_pc$/,@ids)) { + my $idx; + foreach my $mapid (@ids) { + if ($idx) { + push(@recfolders,$mapid); + } elsif ($mapid == $deeplink_login_pc) { + push(@recfolders,$mapid); + $idx = $mapid; + } + } + push(@recfolders,$mpc); + } + } + } + } + foreach my $key (keys(%hash)) { + if ($key=~/^src_(.+)$/) { + my $rid = $1; + next if ($rid eq '0.0'); + next if ($rid eq $loginrid); + if ($scope ne 'res') { + my $mapid = (split(/\./,$rid))[0]; + next if ($mapid eq $deeplink_login_pc); + if ($scope eq 'rec') { + next if (grep(/^$mapid$/,@recfolders)); + } + } + $deeplinkout{$rid} = 1; + } + } + } + } + &deeplinkouts(); + } + } + } + &mapcrumbs(); return $gotstate; } @@ -1831,6 +1975,181 @@ sub evalstate { return $state; } +sub get_mapparam { + my ($uname,$udom,$cnum,$cdom,$rid,$mapname,$what,$recurseupref) = @_; + unless ($mapname) { return; } + +# ------------------------------------------------- Get coursedata (if present) + my $courseopt=&Apache::lonnet::get_courseresdata($cnum,$cdom); + if (!ref($courseopt)) { + undef($courseopt); + } + +# --------------------------------------------------- Get userdata (if present) + my $useropt=&Apache::lonnet::get_userresdata($uname,$udom); + if (!ref($useropt)) { + undef($useropt); + } + + my @recurseup; + if (ref($recurseupref) eq 'ARRAY') { + @recurseup = @{$recurseupref}; + } + + # Get the section if there is one. + + my $cid = $cdom.'_'.$cnum; + my $csec=$env{'request.course.sec'}; + my $cgroup=''; + my @cgrps=split(/:/,$env{'request.course.groups'}); + if (@cgrps > 0) { + @cgrps = sort(@cgrps); + $cgroup = $cgrps[0]; + } + + my $rwhat=$what; + $what=~s/^parameter\_//; + $what=~s/\_/\./; + + # Build the hash keys for the lookup: + + my $mapparm=$mapname.'___(all).'.$what; + my $recurseparm=$mapname.'___(rec).'.$what; + my $usercourseprefix=$cid; + + my $grplevelm = "$usercourseprefix.[$cgroup].$mapparm"; + my $seclevelm = "$usercourseprefix.[$csec].$mapparm"; + my $courselevelm = "$usercourseprefix.$mapparm"; + + my $grpleveli = "$usercourseprefix.[$cgroup].$recurseparm"; + my $secleveli = "$usercourseprefix.[$csec].$recurseparm"; + my $courseleveli = "$usercourseprefix.$recurseparm"; + + # Check per user + + if ($uname and defined($useropt)) { + if (defined($$useropt{$courselevelm})) { + return $$useropt{$courselevelm}; + } + if (defined($$useropt{$courseleveli})) { + return $$useropt{$courseleveli}; + } + foreach my $item (@recurseup) { + my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what; + if (defined($$useropt{$norecursechk})) { + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + return $$useropt{$norecursechk}; + } else { + last; + } + } + my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what; + if (defined($$useropt{$recursechk})) { + return $$useropt{$recursechk}; + } + } + } + + # Check course -- group + + if ($cgroup ne '' and defined ($courseopt)) { + if (defined($$courseopt{$grplevelm})) { + return $$courseopt{$grplevelm}; + } + if (defined($$courseopt{$grpleveli})) { + return $$courseopt{$grpleveli}; + } + foreach my $item (@recurseup) { + my $norecursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(all).'.$what; + if (defined($$courseopt{$norecursechk})) { + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + return $$courseopt{$norecursechk}; + } else { + last; + } + } + my $recursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(rec).'.$what; + if (defined($$courseopt{$recursechk})) { + return $$courseopt{$recursechk}; + } + } + } + + # Check course -- section + + if ($csec ne '' and defined($courseopt)) { + if (defined($$courseopt{$seclevelm})) { + return $$courseopt{$seclevelm}; + } + if (defined($$courseopt{$secleveli})) { + return $$courseopt{$secleveli}; + } + foreach my $item (@recurseup) { + my $norecursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(all).'.$what; + if (defined($$courseopt{$norecursechk})) { + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + return $$courseopt{$norecursechk}; + } else { + last; + } + } + my $recursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(rec).'.$what; + if (defined($$courseopt{$recursechk})) { + return $$courseopt{$recursechk}; + } + } + } + + # Check the map parameters themselves: + + if ($hash{'param_'.$rid}) { + my @items = split(/\&/,$hash{'param_'.$rid}); + my $thisparm; + foreach my $item (@items) { + my ($esctype,$escname,$escvalue) = ($item =~ /^([^:]+):([^=]+)=(.*)$/); + my $name = &unescape($escname); + my $value = &unescape($escvalue); + if ($name eq $what) { + $thisparm = $value; + last; + } + } + if (defined($thisparm)) { + return $thisparm; + } + } + + # Additional course parameters: + + if (defined($courseopt)) { + if (defined($$courseopt{$courselevelm})) { + return $$courseopt{$courselevelm}; + } + + if (defined($$courseopt{$courseleveli})) { + return $$courseopt{$courseleveli}; + } + + if (@recurseup) { + foreach my $item (@recurseup) { + my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what; + if (defined($$courseopt{$norecursechk})) { + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + return $$courseopt{$norecursechk}; + } else { + last; + } + } + my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what; + if (defined($$courseopt{$recursechk})) { + return $$courseopt{$recursechk}; + } + } + } + } + return undef; +} + # This block seems to have code to manage/detect doubly defined # aliases in maps.