--- rat/lonuserstate.pm 2018/11/13 03:59:17 1.157 +++ rat/lonuserstate.pm 2021/08/21 03:42:02 1.167 @@ -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.157 2018/11/13 03:59:17 raeburn Exp $ +# $Id: lonuserstate.pm,v 1.167 2021/08/21 03:42:02 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -62,7 +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 %deeplinkonly; # this URL (or complete folder) is deep-link only +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 @@ -293,15 +293,7 @@ sub loadmap { # Handle randomization and random selection if ($randomize) { - my $advanced; - if ($env{'request.course.id'}) { - $advanced = (&Apache::lonnet::allowed('adv') eq 'F'); - } else { - $env{'request.course.id'} = $courseid; - $advanced = (&Apache::lonnet::allowed('adv') eq 'F'); - $env{'request.course.id'} = ''; - } - unless ($advanced) { + unless (&is_advanced($courseid)) { # Order of resources is not randomized if user has and advanced role in the course. my $seed; @@ -384,6 +376,18 @@ sub loadmap { } } +sub is_advanced { + my ($courseid) = @_; + my $advanced; + if ($env{'request.course.id'}) { + $advanced = (&Apache::lonnet::allowed('adv') eq 'F'); + } else { + $env{'request.course.id'} = $courseid; + $advanced = (&Apache::lonnet::allowed('adv') eq 'F'); + $env{'request.course.id'} = ''; + } + return $advanced; +} # -------------------------------------------------------------------- Resource # @@ -470,7 +474,11 @@ sub parse_resource { # is not a page. If the resource is a page then it must be # assembled (at fetch time?). - unless ($ispage) { + if ($ispage) { + if ($token->[2]->{'external'} eq 'true') { # external + $turi=~s{^http\://}{/ext/}; + } + } else { $turi=~/\.(\w+)$/; my $embstyle=&Apache::loncommon::fileembstyle($1); if ($token->[2]->{'external'} eq 'true') { # external @@ -562,7 +570,9 @@ sub parse_resource { if (($turi=~/\.sequence$/) || ($turi=~/\.page$/)) { $hash{'is_map_'.$rid}=1; - &loadmap($turi,$rid,$courseid); + if ((!$hiddenurl{$rid}) || (&is_advanced($courseid))) { + &loadmap($turi,$rid,$courseid); + } } return $token->[2]->{'id'}; } @@ -895,7 +905,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\&/) { @@ -918,13 +928,29 @@ sub traceroute { && ($hash{'src_'.$rid}!~/\.sequence$/)) { $retfrid=$rid; } - my @deeplink=&Apache::lonnet::EXT('resource.0.deeplink',$symb); - unless ((@deeplink == 0) || ($deeplink[0] eq 'full')) { - $deeplinkonly{$rid}=join(':',@deeplink); - if ($deeplink[1] eq 'map') { - my $parent = (split(/\,/,$hash{'map_hierarchy_'.$mapid}))[-1]; - $deeplinkonly{"$parent.$mapid"}=$deeplinkonly{$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})) { @@ -948,7 +974,8 @@ sub traceroute { $hash{'map_start_'.$hash{'src_'.$rid}}, $beenhere, $encflag || $encurl{$rid}, - $hdnflag || $hiddenurl{$rid}); + $hdnflag || $hiddenurl{$rid}, + $cid); } } @@ -975,7 +1002,7 @@ sub traceroute { } # Recurse to resoruces that have to's to us. $newsofar=&traceroute($further,$hash{'goesto_'.$id},$beenhere, - $encflag,$hdnflag); + $encflag,$hdnflag,$cid); } } } @@ -1172,17 +1199,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)) { @@ -1201,7 +1245,7 @@ sub mapcrumbs { # ---------------------------------------------------- Read map and all submaps sub readmap { - my $short=shift; + my ($short,$critmsg_check) = @_; $short=~s/^\///; # TODO: Hidden dependency on current user: @@ -1241,7 +1285,7 @@ sub readmap { undef %randomizationcode; undef %hiddenurl; undef %encurl; - undef %deeplinkonly; + undef %deeplinkout; undef %rescount; undef %mapcount; $retfrid=''; @@ -1390,7 +1434,7 @@ sub readmap { undef %randomizationcode; undef %hiddenurl; undef %encurl; - undef %deeplinkonly; + undef %deeplinkout; undef %rescount; undef %mapcount; $errtext=''; @@ -1440,12 +1484,16 @@ sub readmap { # Depends on user must parameterize this as well..or separate as this is: # more part of determining what someone sees on entering a course? +# When lonuserstate::readmap() is called from lonroles.pm, i.e., +# after selecting a role in a course, critical_redirect will be called, +# unless the course has a blocking event in effect, which suppresses +# critical message checking (users without evb priv). +# - my @what=&Apache::lonnet::dump('critical',$env{'user.domain'}, - $env{'user.name'}); - if ($what[0]) { - if (($what[0] ne 'con_lost') && ($what[0]!~/^error\:/)) { - $retfurl='/adm/email?critical=display'; + if ($critmsg_check) { + my ($redirect,$url) = &Apache::loncommon::critical_redirect(); + if ($redirect) { + $retfurl = $url; } } return ($retfurl,$errtext); @@ -1495,7 +1543,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> @@ -1508,10 +1555,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 @@ -1529,10 +1575,6 @@ sub build_tmp_hashes { # $hash{'src_'.$id}=&Apache::lonenc::encrypted($hash{'src_'.$id}); $hash{'encrypted_'.$id}=1; } -# ------------------------------------------------------------ Deep-linked URLs - foreach my $id (keys(%deeplinkonly)) { - $hash{'deeplinkonly_'.$id}=$deeplinkonly{$id}; - } # ----------------------------------------------- Close hashes to finally store # --------------------------------- Routine must pass this point, no early outs $hash{'first_rid'}=$retfrid; @@ -1555,6 +1597,89 @@ 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'); + } 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; } @@ -1616,6 +1741,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. 500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.