--- rat/lonuserstate.pm 2021/08/06 01:27:04 1.165 +++ rat/lonuserstate.pm 2022/10/05 16:11:25 1.170 @@ -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.165 2021/08/06 01:27:04 raeburn Exp $ +# $Id: lonuserstate.pm,v 1.170 2022/10/05 16:11:25 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -199,6 +199,10 @@ sub loadmap { $errtext.= '
' .&mt('Map not loaded: The file [_1] does not exist.', "$fn"); + $hash{'map_type_'.$lpc}='none'; + if (&is_advanced($courseid)) { + $errtext .= &error_detail($parent_rid,$courseid,$ispage,$uri); + } return; } @@ -389,6 +393,237 @@ sub is_advanced { return $advanced; } +sub error_detail { + my ($parent_rid,$courseid,$ispage,$uri) = @_; + my $errinfo; + if ($courseid) { + my $courseurl = &Apache::lonnet::courseid_to_courseurl($courseid); + if ($parent_rid =~ /^(\d+)\.(\d+)$/) { + my ($parent_pc,$parent_id) = ($1,$2); + my ($parent_type,$published,$uploaded,$canedit,$role,$switchserver,$audom,$auname, + $editfile,$filerole,$fileswitch,$audomfile,$aunamefile); + 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') { + if ($hash{'map_id_1'} eq "/uploaded$courseurl/default.sequence") { + $uploaded = 1; + if (&Apache::lonnet::allowed('mdc',$courseid)) { + $canedit = 1; + } + $errinfo = &mt('Map is referenced in the top level ([_1]Main Content[_2]) folder.', + '',''); + } elsif ($hash{'map_id_1'} =~ m{^/res/($match_domain)/($match_username)/.+\.(sequence|page)$}) { + ($audom,$auname) = ($1,$2); + ($canedit,$role,$switchserver) = &canedit_published($audom,$auname); + $published = 1; + $errinfo = &mt('Map is referenced in the top level published sequence file.'); + } + } else { + if ($hash{'map_id_'.$parent_pc} =~ m{^\Q/uploaded$courseurl/default_\E\d+\.(sequence|page)$}) { + $uploaded = 1; + if (&Apache::lonnet::allowed('mdc',$courseid)) { + $canedit = 1; + } + } elsif ($hash{'map_id_'.$parent_pc} =~ m{^/res/($match_domain)/($match_username)/.+\.(sequence|page)$}) { + ($audom,$auname) = ($1,$2); + ($canedit,$role,$switchserver) = &canedit_published($audom,$auname); + $published = 1; + } + if (exists($hash{'ids_'.$hash{'map_id_'.$parent_pc}})) { + $parent_type = $hash{'map_type_'.$parent_pc}; + if ($published) { + $errinfo = &mt("Map is referenced in the published $parent_type file: [_1].", + ''.$hash{'map_id_'.$parent_pc}.''); + } else { + my $title = $hash{'title_'.$hash{'ids_'.$hash{'map_id_'.$parent_pc}}}; + if ($title ne '') { + my $mapdesc; + if ($parent_type eq 'sequence') { + $mapdesc = 'folder'; + } else { + $mapdesc = 'composite page'; + } + $errinfo = &mt("Map is referenced in the $mapdesc named: [_1].", + ''.$title.''); + } + my @containers = split(/,/,$hash{'map_hierarchy_'.$parent_pc}); + shift(@containers); + my $folderpath; + foreach my $id (@containers) { + my $name; + if ($id == 1) { + $name = &mt('Main Content'); + } elsif ($hash{'title_'.$hash{'ids_'.$hash{'map_id_'.$id}}} ne '') { + $name = $hash{'title_'.$hash{'ids_'.$hash{'map_id_'.$id}}}; + } + if ($name ne '') { + $folderpath .= $name.' » '; + } + } + if ($title eq '') { + $folderpath =~ s/\Q » \E$//; + } else { + $folderpath .= $title; + } + if ($folderpath) { + $errinfo .= '
'.&mt('Hierarchy is: [_1]', + ''.$folderpath.''); + } + } + } + } + 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) { + $errinfo = '
'.$errinfo.'
'; + } + if ($editfile) { + if ($errinfo ne '') { + $errinfo .= '
'; + } + if ($canedit) { + $errinfo .= &mt('One way to rectify this problem is to create and publish the missing file'); + } 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; + if ($filerole eq 'au') { + $rolecode = 'au./'.$audomfile.'/'; + } else { + $rolecode = $filerole.'./'.$audomfile.'/'.$aunamefile; + } + $errinfo .= '.
'.&mt('You will need to [_1]switch server[_2].', + '',''); + } else { + &js_escape(\$fileurl); + $errinfo .= ': '.&mt('Create the missing file').''; + } + } + if ($canedit) { + if ($errinfo ne '') { + $errinfo .= '
'; + } + if ($published) { + my $rolename = &Apache::lonnet::plaintext($role); + my $rolecode; + if ($role eq 'au') { + $rolecode = 'au./'.$audom.'/'; + } else { + $rolecode = $role.'./'.$audom.'/'.$auname; + } + if ($editfile) { + $errinfo .= &mt('Another way is to edit the parent map to remove the reference to the missing file'); + } 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 .= '.
'; + 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); + $errinfo .= ': '.&mt('Edit the map').''; + } + } elsif ($uploaded && $courseid) { + my ($dest,$linktext); + my $crstype = &Apache::loncommon::course_type($courseid); + if ($parent_pc eq '1') { + $dest = '/adm/coursedocs?folderpath='.&escape('default&Main%20Content:::::'); + $linktext = &mt('Edit Folder'); + } elsif ($hash{'ids_'.$hash{'map_id_'.$parent_pc}} =~ /^(\d+)\.(\d+)$/) { + my ($editmap,$editidx) = ($1,$2); + my $symb = &Apache::lonnet::encode_symb($hash{'map_id_'.$editmap}, + $editidx,$hash{'map_id_'.$parent_pc}); + $dest = '/adm/coursedocs?command=directnav&symb='.&escape($symb); + if ($parent_type eq 'sequence') { + $linktext = &mt('Edit Folder'); + } else { + $linktext = &mt('Edit Composite Page'); + } + } else { + $dest = '/adm/coursedocs?folderpath='.&escape('default&Main%20Content:::::'); + $linktext = &mt("Edit $crstype"); + } + if ($editfile) { + $errinfo .= &mt("Another way is to use the $crstype Editor to delete the reference to the missing file"); + } else { + $errinfo .= &mt("To rectify this problem use the $crstype Editor to delete the reference to the missing file"); + } + $errinfo .= ': '.$linktext.''; + } + $errinfo .= '
'; + } + } + } + return $errinfo; +} + +sub canedit_published { + my ($audom,$auname) = @_; + my ($canedit,$role,$switchserver); + my $now = time; + if (($auname eq $env{'user.name'}) && ($audom eq $env{'user.domain'})) { + if (exists($env{"user.role.au./$audom/"})) { + my ($start,$end) = split(/\./,$env{"user.role.au./$audom/"}); + unless (($end && $end < $now) || ($start && $start > $now)) { + $canedit = 1; + $role = 'au'; + } + } + } + unless ($canedit) { + foreach my $possrole ('ca','aa') { + if (exists($env{"user.role.$possrole./$audom/$auname"})) { + my ($end,$start) = split(/\./,$env{"user.role.$possrole./$audom/$auname"}); + unless (($end && $end < time) || ($start && $start > time)) { + $canedit = 1; + $role = $possrole; + last; + } + } + } + } + if ($canedit) { + my $auhome = &Apache::lonnet::homeserver($auname,$audom); + my @ids=&Apache::lonnet::current_machine_ids(); + if (($auhome ne 'no_host') && (!grep(/^\Q$auhome\E$/,@ids))) { + $switchserver = $auhome; + } + } + return ($canedit,$role,$switchserver); +} + # -------------------------------------------------------------------- Resource # # Parses a resource tag to produce the value to push into the @@ -950,7 +1185,7 @@ sub traceroute { @deeplink = &Apache::lonnet::EXT('resource.0.deeplink',$symb,'','','','',$cid,\@recurseup); } unless (@deeplink < 2) { - $hash{'deeplinkonly_'.$rid}=join(':',@deeplink); + $hash{'deeplinkonly_'.$rid}=join(':',map { &escape($_); } @deeplink); } if (defined($hash{'conditions_'.$rid})) { @@ -1613,50 +1848,73 @@ sub build_tmp_hashes { } my $deeplink; if ($hash{'deeplinkonly_'.$loginrid} ne '') { - $deeplink = $hash{'deeplinkonly_'.$loginrid}; + 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 ($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; + 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); } - 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)); + 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; } - $deeplinkout{$rid} = 1; } } } + &deeplinkouts(); } - &deeplinkouts(); } } &mapcrumbs();