--- rat/lonuserstate.pm 2011/08/04 10:57:26 1.139 +++ rat/lonuserstate.pm 2011/08/09 09:15:50 1.140 @@ -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.139 2011/08/04 10:57:26 foxr Exp $ +# $Id: lonuserstate.pm,v 1.140 2011/08/09 09:15:50 foxr Exp $ # # Copyright Michigan State University Board of Trustees # @@ -131,6 +131,17 @@ sub processversionfile { # --------------------------------------------------------- Loads from disk + +# +# Loads a map file. +# Note that this may implicitly recurse via parse_resource if one of the resources +# is itself composed. +# +# 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) +# +# sub loadmap { my ($uri,$parent_rid)=@_; @@ -280,7 +291,13 @@ sub loadmap { my $rndseed=&Apache::lonnet::rndseed($seed); &Apache::lonnet::setup_random_from_rndseed($rndseed); - @map_ids=&math::Random::random_permutation(@map_ids); # randomorder. + + # 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); } @@ -289,11 +306,9 @@ sub loadmap { $hash{'map_start_'.$uri} = $from_rid; $hash{'type_'.$from_rid}='start'; - # Create links to reflect this random ordering. - # BUG? If there are conditions, this invalidates them? Then again - # with randompick there's no gaurentee the resources required for the - # conditinos to work will be selected into the map. - # so randompick is inconsistent with a map that has conditions? + # Create links to reflect the random re-ordering done above. + # In the code to process the map XML, we did not process links or conditions + # if randomorder was set. This means that for an instructor to choose while (my $to = shift(@map_ids)) { &make_link(++$linkpc,$lpc,$to,$from); @@ -310,8 +325,8 @@ sub loadmap { $parser = HTML::TokeParser->new(\$instr); $parser->attr_encoded(1); - # last parse out the mapalias params so as to ignore anything - # refering to non-existant resources + # last parse out the mapalias params. Thes provide mnemonic + # tags to resources that can be used in conditions while (my $token = $parser->get_token) { next if ($token->[0] ne 'S'); @@ -721,12 +736,50 @@ sub parse_param { } } } - +# +# Parse mapalias parameters. +# these are tags of the form: +# +# A map alias is a textual name for a resource: +# - The to attribute identifies the resource (this gets level qualified below) +# - The value attributes provides the alias string. +# - name must be of the regexp form: /^parameter_(0_)*mapalias$/ +# - e.g. the string 'parameter_' followed by 0 or more "0_" strings +# terminating with the string 'mapalias'. +# Examples: +# 'parameter_mapalias', 'parameter_0_mapalias', parameter_0_0_mapalias' +# Invalid to ids are silently ignored. +# +# Parameters: +# token - The token array fromthe HMTML::TokeParser +# lpc - The current map level counter. +# sub parse_mapalias_param { my ($token,$lpc) = @_; + + # Fully qualify the to value and ignore the alias if there is no + # corresponding resource. + my $referid=$lpc.'.'.$token->[2]->{'to'}; return if (!exists($hash{'src_'.$referid})); + # If this is a valid mapalias parameter, + # Append the target id to the count_mapalias element for that + # alias so that we can detect doubly defined aliases + # e.g.: + # + # + # + # The example above is trivial but the case that's important has to do with + # constructing a map that includes a nested map where the nested map may have + # aliases that conflict with aliases established in the enclosing map. + # + # ...and create/update the hash mapalias entry to actually store the alias. + # + if ($token->[2]->{'name'}=~/^parameter_(0_)*mapalias$/) { &count_mapalias($token->[2]->{'value'},$referid); $hash{'mapalias_'.$token->[2]->{'value'}}=$referid; @@ -735,6 +788,10 @@ sub parse_mapalias_param { # --------------------------------------------------------- Simplify expression + +# +# Someone should really comment this to describe what it does to what and why. +# sub simplify { my $expression=shift; # (0&1) = 1 @@ -758,9 +815,38 @@ sub simplify { # -------------------------------------------------------- Build condition hash +# +# Traces a route recursively through the map after it has been loaded +# (I believe this really visits each resourcde that is reachable fromt he +# start top node. +# +# - Marks hidden resources as hidden. +# - Marks which resource URL's must be encrypted. +# - Figures out (if necessary) the first resource in the map. +# - Further builds the chunks of the big hash that define how +# conditions work +# +# Note that the tracing strategy won't visit resources that are not linked to +# anything or islands in the map (groups of resources that form a path but are not +# linked in to the path that can be traced from the start resource...but that's ok +# because by definition, those resources are not reachable by users of the course. +# +# Parameters: +# sofar - _URI of the prior entry or 0 if this is the top. +# rid - URI of the resource to visit. +# beenhere - list of resources (each resource enclosed by &'s) that have +# already been visited. +# encflag - If true the resource that resulted in a recursive call to us +# has an encoded URL (which means contained resources should too). +# hdnflag - If true,the resource that resulted in a recursive call to us +# was hidden (which means contained resources should be hidden too). +# Returns +# new value indicating how far the map has been traversed (the sofar). +# sub traceroute { my ($sofar,$rid,$beenhere,$encflag,$hdnflag)=@_; my $newsofar=$sofar=simplify($sofar); + unless ($beenhere=~/\&\Q$rid\E\&/) { $beenhere.=$rid.'&'; my ($mapid,$resid)=split(/\./,$rid); @@ -776,10 +862,12 @@ sub traceroute { my $encrypt=&Apache::lonnet::EXT('resource.0.encrypturl',$symb); if ($encflag || lc($encrypt) eq 'yes') { $encurl{$rid}=1; } + if (($retfrid eq '') && ($hash{'src_'.$rid}) && ($hash{'src_'.$rid}!~/\.sequence$/)) { $retfrid=$rid; } + if (defined($hash{'conditions_'.$rid})) { $hash{'conditions_'.$rid}=simplify( '('.$hash{'conditions_'.$rid}.')|('.$sofar.')'); @@ -789,8 +877,11 @@ sub traceroute { # if the expression is just the 0th condition keep it # otherwise leave a pointer to this condition expression + $newsofar = ($sofar eq '0') ? $sofar : '_'.$rid; + # Recurse if the resource is a map: + if (defined($hash{'is_map_'.$rid})) { if (defined($hash{'map_start_'.$hash{'src_'.$rid}})) { $sofar=$newsofar= @@ -801,9 +892,18 @@ sub traceroute { $hdnflag || $hiddenurl{$rid}); } } + + # Processes links to this resource: + # - verify the existence of any conditionals on the link to here. + # - Recurse to any resources linked to us. + # if (defined($hash{'to_'.$rid})) { foreach my $id (split(/\,/,$hash{'to_'.$rid})) { my $further=$sofar; + # + # If there's a condition associated with this link be sure + # it's been defined else that's an error: + # if ($hash{'undercond_'.$id}) { if (defined($hash{'condid_'.$hash{'undercond_'.$id}})) { $further=simplify('('.'_'.$rid.')&('. @@ -812,6 +912,7 @@ sub traceroute { $errtext.=&mt('
Undefined condition ID: [_1]',$hash{'undercond_'.$id}); } } + # Recurse to resoruces that have to's to us. $newsofar=&traceroute($further,$hash{'goesto_'.$id},$beenhere, $encflag,$hdnflag); } @@ -822,16 +923,33 @@ sub traceroute { # ------------------------------ Cascading conditions, quick access, parameters +# +# Seems a rather strangely named sub given what the comment above says it does. + + sub accinit { my ($uri,$short,$fn)=@_; my %acchash=(); my %captured=(); my $condcounter=0; $acchash{'acc.cond.'.$short.'.0'}=0; + + # This loop is only interested in conditions and + # parameters in the big hash: + foreach my $key (keys(%hash)) { + + # conditions: + if ($key=~/^conditions/) { my $expr=$hash{$key}; + # try to find and factor out common sub-expressions + # Any subexpression that is found is simplified, removed from + # the original condition expression and the simplified sub-expression + # substituted back in to the epxression..I'm not actually convinced this + # factors anything out...but instead maybe simplifies common factors(?) + foreach my $sub ($expr=~m/(\(\([_\.\d]+(?:\&[_\.\d]+)+\)(?:\|\([_\.\d]+(?:\&[_\.\d]+)+\))+\))/g) { my $orig=$sub; @@ -845,11 +963,16 @@ sub accinit { $expr=~s/\Q$orig\E/$sub/; } $hash{$key}=$expr; + + # If not yet seen, record in acchash and that we've seen it. + unless (defined($captured{$expr})) { $condcounter++; $captured{$expr}=$condcounter; $acchash{'acc.cond.'.$short.'.'.$condcounter}=$expr; } + # Parameters: + } elsif ($key=~/^param_(\d+)\.(\d+)/) { my $prefix=&Apache::lonnet::encode_symb($hash{'map_id_'.$1},$2, $hash{'src_'.$1.'.'.$2}); @@ -863,6 +986,8 @@ sub accinit { } } } + # This loop only processes id entries in the big hash. + foreach my $key (keys(%hash)) { if ($key=~/^ids/) { foreach my $resid (split(/\,/,$hash{$key})) { @@ -1226,6 +1351,9 @@ sub build_tmp_hashes { $pc=0; &clear_mapalias_count(); &processversionfile(%cenv); + + # URI Of the map file. + my $furi=&Apache::lonnet::clutter($uri); # # the map staring points. @@ -1234,7 +1362,17 @@ sub build_tmp_hashes { $hash{'title_0.0'}=&Apache::lonnet::metadata($uri,'title'); $hash{'ids_'.$furi}='0.0'; $hash{'is_map_0.0'}=1; + + # Load the map.. note that loadmap may implicitly recurse if the map contains + # sub-maps. + + &loadmap($uri,'0.0'); + + # 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? + # + if (defined($hash{'map_start_'.$uri})) { &Apache::lonnet::appenv({"request.course.id" => $short, "request.course.fn" => $fn,