--- rat/lonuserstate.pm 2014/12/15 01:10:19 1.149 +++ rat/lonuserstate.pm 2024/07/03 02:16:44 1.149.2.6 @@ -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 2014/12/15 01:10:19 raeburn Exp $ +# $Id: lonuserstate.pm,v 1.149.2.6 2024/07/03 02:16:44 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -42,7 +42,7 @@ use Safe::Hole; use Opcode; use Apache::lonenc; use Fcntl qw(:flock); -use LONCAPA; +use LONCAPA qw(:DEFAULT :match); use File::Basename; @@ -62,6 +62,8 @@ 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 %rescount; # count of unhidden items in each map +my %mapcount; # count of unhidden maps in each map # ----------------------------------- Remove version from URL and store in hash @@ -196,6 +198,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; } @@ -240,6 +246,8 @@ sub loadmap { my @map_ids; my $codechecked; + $rescount{$lpc} = 0; + $mapcount{$lpc} = 0; while (my $token = $parser->get_token) { next if ($token->[0] ne 'S'); @@ -249,6 +257,13 @@ sub loadmap { my $resource_id = &parse_resource($token,$lpc,$ispage,$uri,$courseid); if (defined $resource_id) { push(@map_ids, $resource_id); + if ($hash{'src_'.$lpc.'.'.$resource_id}) { + $rescount{$lpc} ++; + if (($hash{'src_'.$lpc.'.'.$resource_id}=~/\.sequence$/) || + ($hash{'src_'.$lpc.'.'.$resource_id}=~/\.page$/)) { + $mapcount{$lpc} ++; + } + } unless ($codechecked) { my $startsymb = &Apache::lonnet::encode_symb($hash{'map_id_'.$lpc},$resource_id, @@ -278,19 +293,10 @@ sub loadmap { } undef($codechecked); - # 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; @@ -338,7 +344,6 @@ sub loadmap { @map_ids=&Math::Random::random_permutation(@map_ids); } - my $from = shift(@map_ids); my $from_rid = $lpc.'.'.$from; $hash{'map_start_'.$uri} = $from_rid; @@ -363,7 +368,7 @@ sub loadmap { $parser = HTML::TokeParser->new(\$instr); $parser->attr_encoded(1); - # last parse out the mapalias params. Thes provide mnemonic + # last parse out the mapalias params. These provide mnemonic # tags to resources that can be used in conditions while (my $token = $parser->get_token) { @@ -374,6 +379,232 @@ 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; +} + +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); + $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 ($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 .= '.
'. + &mt('You will need to [_1]switch server[_2].', + '',''); + } 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 # @@ -460,7 +691,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 @@ -550,7 +785,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'}; } @@ -706,7 +943,7 @@ sub parse_condition { # Typical attributes: # to=n - Number of the resource the parameter applies to. # type=xx - Type of parameter value (e.g. string_yesno or int_pos). -# name=xxx - Name ofr parameter (e.g. parameter_randompick or parameter_randomorder). +# name=xxx - Name of parameter (e.g. parameter_randompick or parameter_randomorder). # value=xxx - value of the parameter. sub parse_param { @@ -1114,6 +1351,14 @@ sub hiddenurls { if ($currentrids[$k]) { $hash{'randomout_'.$currentrids[$k]}=1; my ($mapid,$resid)=split(/\./,$currentrids[$k]); + if ($rescount{$mapid}) { + $rescount{$mapid} --; + } + if ($hash{'is_map_'.$currentrids[$k]}) { + if ($mapcount{$mapid}) { + $mapcount{$mapid} --; + } + } $randomoutentry.='&'. &Apache::lonnet::encode_symb($hash{'map_id_'.$mapid}, $resid, @@ -1126,6 +1371,14 @@ sub hiddenurls { foreach my $rid (keys(%hiddenurl)) { $hash{'randomout_'.$rid}=1; my ($mapid,$resid)=split(/\./,$rid); + if ($rescount{$mapid}) { + $rescount{$mapid} --; + } + if ($hash{'is_map_'.$rid}) { + if ($mapcount{$mapid}) { + $mapcount{$mapid} --; + } + } $randomoutentry.='&'. &Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},$resid, $hash{'src_'.$rid}).'&'; @@ -1136,10 +1389,36 @@ sub hiddenurls { } } +# -------------------------------------- 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 { + foreach my $key (keys(%rescount)) { + if ($hash{'map_hierarchy_'.$key}) { + my $skipnext = 0; + foreach my $id (split(/,/,$hash{'map_hierarchy_'.$key}),$key) { + unless ($skipnext) { + $hash{'map_breadcrumbs_'.$key} .= "$id,"; + } + unless (($id == 0) || ($id == 1)) { + if ((!$rescount{$id}) || ($rescount{$id} == 1 && $mapcount{$id} == 1)) { + $skipnext = 1; + } else { + $skipnext = 0; + } + } + } + $hash{'map_breadcrumbs_'.$key} =~ s/,$//; + } + } +} + # ---------------------------------------------------- Read map and all submaps sub readmap { - my $short=shift; + my ($short,$critmsg_check) = @_; $short=~s/^\///; # TODO: Hidden dependency on current user: @@ -1156,7 +1435,7 @@ sub readmap { } @cond=('true:normal'); - unless (open(LOCKFILE,">$fn.db.lock")) { + unless (open(LOCKFILE,">","$fn.db.lock")) { # # Most likely a permissions problem on the lockfile or its directory. # @@ -1174,8 +1453,13 @@ sub readmap { &unlink_tmpfiles($fn); } undef %randompick; + undef %randompickseed; + undef %randomorder; + undef %randomizationcode; undef %hiddenurl; undef %encurl; + undef %rescount; + undef %mapcount; $retfrid=''; $errtext=''; my ($untiedhash,$untiedparmhash,$tiedhash,$tiedparmhash); # More state flags. @@ -1317,8 +1601,13 @@ sub readmap { $lock=1; } undef %randompick; + undef %randompickseed; + undef %randomorder; + undef %randomizationcode; undef %hiddenurl; undef %encurl; + undef %rescount; + undef %mapcount; $errtext=''; $retfrid=''; # @@ -1366,14 +1655,18 @@ 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); } @@ -1437,6 +1730,7 @@ sub build_tmp_hashes { &traceroute('0',$hash{'map_start_'.$uri},'&'); &accinit($uri,$short,$fn); &hiddenurls(); + &mapcrumbs(); } $errtext .= &get_mapalias_errors(); # ------------------------------------------------------- Put versions into src @@ -1468,7 +1762,7 @@ sub build_tmp_hashes { # ---------------------------------------------------- Store away initial state { my $cfh; - if (open($cfh,">$fn.state")) { + if (open($cfh,">","$fn.state")) { print $cfh join("\n",@cond); $gotstate = 1; } else { @@ -1505,7 +1799,7 @@ sub evalstate { if (-e $fn) { my @conditions=(); { - open(my $fh,"<$fn"); + open(my $fh,"<",$fn); @conditions=<$fh>; close($fh); }