--- rat/lonuserstate.pm 2011/08/09 09:15:50 1.140 +++ rat/lonuserstate.pm 2020/03/05 22:39:57 1.149.2.3 @@ -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.140 2011/08/09 09:15:50 foxr Exp $ +# $Id: lonuserstate.pm,v 1.149.2.3 2020/03/05 22:39:57 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -59,8 +59,11 @@ my $retfurl; # first URL my %randompick; # randomly picked resources my %randompickseed; # optional seed for randomly picking resources my %randomorder; # maps to order contents randomly +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 @@ -140,10 +143,10 @@ sub processversionfile { # Parameters: # uri - URI of the map file. # parent_rid - Resource id in the map of the parent resource (0.0 for the top level map) -# +# courseid - Course id for the course for which the map is being loaded # sub loadmap { - my ($uri,$parent_rid)=@_; + my ($uri,$parent_rid,$courseid)=@_; # Is the map already included? @@ -183,7 +186,7 @@ sub loadmap { # We can only nest sequences or pages. Anything else is an illegal nest. unless (($fn=~/\.sequence$/) || $ispage) { - $errtext.=&mt("
Invalid map: [_1]",$fn); + $errtext.='
'.&mt('Invalid map: [_1]',"$fn"); return; } @@ -192,7 +195,9 @@ sub loadmap { my $instr=&Apache::lonnet::getfile($fn); if ($instr eq -1) { - $errtext.=&mt('
Map not loaded: The file [_1] does not exist.',$fn); + $errtext.= '
' + .&mt('Map not loaded: The file [_1] does not exist.', + "$fn"); return; } @@ -236,15 +241,37 @@ sub loadmap { # This is handled in the next chunk of code. my @map_ids; + my $codechecked; + $rescount{$lpc} = 0; + $mapcount{$lpc} = 0; while (my $token = $parser->get_token) { next if ($token->[0] ne 'S'); # Resource if ($token->[1] eq 'resource') { - my $resource_id = &parse_resource($token,$lpc,$ispage,$uri); + my $resource_id = &parse_resource($token,$lpc,$ispage,$uri,$courseid); if (defined $resource_id) { - push(@map_ids, $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, + $hash{'src_'."$lpc.$resource_id"}); + my $code = + &Apache::lonnet::EXT('resource.0.examcode',$startsymb,undef,undef, + undef,undef,$courseid); + if ($code) { + $randomizationcode{$parent_rid} = $code; + } + $codechecked = 1; + } } # Link @@ -260,17 +287,25 @@ sub loadmap { &parse_condition($token,$lpc); } } - + undef($codechecked); # Handle randomization and random selection if ($randomize) { - if (!$env{'request.role.adv'}) { + 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) { + # Order of resources is not randomized if user has and advanced role in the course. my $seed; - # In the advanced role, the map's random seed - # parameter is used as the basis for computing the - # seed ... if it has been specified: + # If the map's random seed parameter has been specified + # it is used as the basis for computing the seed ... if (defined($randompickseed{$parent_rid})) { $seed = $randompickseed{$parent_rid}; @@ -289,18 +324,30 @@ sub loadmap { # TODO: Here for sure we need to pass along the username/domain # so that we can impersonate users in lonprintout e.g. + my $setcode; + if (defined($randomizationcode{$parent_rid})) { + if ($env{'form.CODE'} eq '') { + $env{'form.CODE'} = $randomizationcode{$parent_rid}; + $setcode = 1; + } + } + my $rndseed=&Apache::lonnet::rndseed($seed); &Apache::lonnet::setup_random_from_rndseed($rndseed); + if ($setcode) { + undef($env{'form.CODE'}); + undef($setcode); + } + # Take the set of map ids we have decoded and permute them to a # random order based on the seed set above. All of this is # processing the randomorder parameter if it is set, not # randompick. - @map_ids=&math::Random::random_permutation(@map_ids); + @map_ids=&Math::Random::random_permutation(@map_ids); } - my $from = shift(@map_ids); my $from_rid = $lpc.'.'.$from; $hash{'map_start_'.$uri} = $from_rid; @@ -325,7 +372,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) { @@ -352,6 +399,7 @@ sub loadmap { # $lpc - Map nesting level (?) # $ispage - True if this resource is encapsulated in a .page (assembled resourcde). # $uri - URI of the enclosing resource. +# $courseid - Course id of the course containing the resource being parsed. # Returns: # Value of the id attribute of the tag. # @@ -372,7 +420,7 @@ sub loadmap { sub parse_resource { - my ($token,$lpc,$ispage,$uri) = @_; + my ($token,$lpc,$ispage,$uri,$courseid) = @_; # I refuse to countenance code like this that has # such a dirty side effect (and forcing this sub to be called within a loop). @@ -421,7 +469,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 @@ -511,7 +563,7 @@ sub parse_resource { if (($turi=~/\.sequence$/) || ($turi=~/\.page$/)) { $hash{'is_map_'.$rid}=1; - &loadmap($turi,$rid); + &loadmap($turi,$rid,$courseid); } return $token->[2]->{'id'}; } @@ -667,7 +719,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 { @@ -801,7 +853,7 @@ sub simplify { # 8&8=8 $expression=~s/([^_\.\d])([_\.\d]+)\&\2([^_\.\d])/$1$2$3/g; # 8|8=8 - $expression=~s/([^_\.\d])([_\.\d]+)\|\2([^_\.\d])/$1$2$3/g; + $expression=~s/([^_\.\d])([_\.\d]+)(?:\|\2)+([^_\.\d])/$1$2$3/g; # (5&3)&4=5&3&4 $expression=~s/\(([_\.\d]+)((?:\&[_\.\d]+)+)\)\&([_\.\d]+[^_\.\d])/$1$2\&$3/g; # (((5&3)|(4&6)))=((5&3)|(4&6)) @@ -909,7 +961,9 @@ sub traceroute { $further=simplify('('.'_'.$rid.')&('. $hash{'condid_'.$hash{'undercond_'.$id}}.')'); } else { - $errtext.=&mt('
Undefined condition ID: [_1]',$hash{'undercond_'.$id}); + $errtext.= '
'. + &mt('Undefined condition ID: [_1]', + $hash{'undercond_'.$id}); } } # Recurse to resoruces that have to's to us. @@ -1031,7 +1085,7 @@ sub accinit { sub hiddenurls { my $randomoutentry=''; - foreach my $rid (keys %randompick) { + foreach my $rid (keys(%randompick)) { my $rndpick=$randompick{$rid}; my $mpc=$hash{'map_pc_'.$hash{'src_'.$rid}}; # ------------------------------------------- put existing resources into array @@ -1052,7 +1106,18 @@ sub hiddenurls { # -------------------------------- randomly eliminate the ones that should stay my (undef,$id)=split(/\./,$rid); if ($randompickseed{$rid}) { $id=$randompickseed{$rid}; } + my $setcode; + if (defined($randomizationcode{$rid})) { + if ($env{'form.CODE'} eq '') { + $env{'form.CODE'} = $randomizationcode{$rid}; + $setcode = 1; + } + } my $rndseed=&Apache::lonnet::rndseed($id); # use id instead of symb + if ($setcode) { + undef($env{'form.CODE'}); + undef($setcode); + } &Apache::lonnet::setup_random_from_rndseed($rndseed); my @whichids=&Math::Random::random_permuted_index($#currentrids+1); for (my $i=1;$i<=$rndpick;$i++) { $currentrids[$whichids[$i]]=''; } @@ -1062,6 +1127,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, @@ -1071,9 +1144,17 @@ sub hiddenurls { } } # ------------------------------ take care of explicitly hidden urls or folders - foreach my $rid (keys %hiddenurl) { + 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}).'&'; @@ -1084,6 +1165,32 @@ 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 { @@ -1104,13 +1211,12 @@ 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. # - $errtext.='
'.&mt('Map not loaded - Lock file could not be opened when reading map:').' '.$fn.'.'; $retfurl = ''; - return ($retfurl,$errtext); + return ($retfurl,'
'.&mt('Map not loaded - Lock file could not be opened when reading map:').' '.$fn.'.'); } my $lock=0; my $gotstate=0; @@ -1123,9 +1229,15 @@ 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. # if we got the lock, regenerate course regnerate empty files and tie them. @@ -1220,7 +1332,9 @@ sub readmap { # &Apache::lonnet::appenv({"request.course.id" => $short, "request.course.fn" => $fn, - "request.course.uri" => $uri}); + "request.course.uri" => $uri, + "request.course.tied" => time}); + $untiedhash = untie(%hash); $untiedparmhash = untie(%parmhash); $gotstate = 1; @@ -1263,8 +1377,14 @@ sub readmap { $lock=1; } undef %randompick; + undef %randompickseed; + undef %randomorder; + undef %randomizationcode; undef %hiddenurl; undef %encurl; + undef %rescount; + undef %mapcount; + $errtext=''; $retfrid=''; # # Once more through the routine of tying and loading and so on. @@ -1367,7 +1487,7 @@ sub build_tmp_hashes { # sub-maps. - &loadmap($uri,'0.0'); + &loadmap($uri,'0.0',$short); # The code below only executes if there is a starting point for the map> # Q/BUG??? If there is no start resource for the map should that be an error? @@ -1376,11 +1496,13 @@ sub build_tmp_hashes { if (defined($hash{'map_start_'.$uri})) { &Apache::lonnet::appenv({"request.course.id" => $short, "request.course.fn" => $fn, - "request.course.uri" => $uri}); + "request.course.uri" => $uri, + "request.course.tied" => time}); $env{'request.course.id'}=$short; &traceroute('0',$hash{'map_start_'.$uri},'&'); &accinit($uri,$short,$fn); &hiddenurls(); + &mapcrumbs(); } $errtext .= &get_mapalias_errors(); # ------------------------------------------------------- Put versions into src @@ -1412,7 +1534,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 { @@ -1427,7 +1549,7 @@ sub unlink_tmpfiles { my ($fn) = @_; my $file_dir = dirname($fn); - if ($fn eq LONCAPA::tempdir()) { + if ("$file_dir/" eq LONCAPA::tempdir()) { my @files = qw (.db _symb.db .state _parms.db); foreach my $file (@files) { if (-e $fn.$file) { @@ -1449,7 +1571,7 @@ sub evalstate { if (-e $fn) { my @conditions=(); { - open(my $fh,"<$fn"); + open(my $fh,"<",$fn); @conditions=<$fh>; close($fh); } @@ -1504,8 +1626,8 @@ sub evalstate { $count++; } my ($mapid) = split(/\./,$id); - &mt('Resource "[_1]"
in Map "[_2]"', - $hash{'title_'.$id}, + &mt('Resource [_1][_2]in Map [_3]', + $hash{'title_'.$id},'
', $hash{'title_'.$hash{'ids_'.$hash{'map_id_'.$mapid}}}); } (@{ $mapalias_cache{$mapalias} })); next if ($count < 2); 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.