--- loncom/lonmap.pm 2011/09/13 10:27:52 1.2
+++ loncom/lonmap.pm 2013/07/22 18:07:01 1.13
@@ -2,7 +2,7 @@
#
# Read maps into a 'big hash'.
#
-# $Id: lonmap.pm,v 1.2 2011/09/13 10:27:52 foxr Exp $
+# $Id: lonmap.pm,v 1.13 2013/07/22 18:07:01 bisitz Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -28,7 +28,7 @@
#
###
-package lonmap;
+package Apache::lonmap;
use strict;
#------------- Required external modules.
@@ -40,10 +40,14 @@ use HTML::TokeParser;
use LONCAPA;
use Apache::lonnet;
+use Apache::lonlocal;
+
+use Data::Dumper;
+
#------------- File scoped variables:
-my $map_number = 1; # keep track of maps within the course.
+my $map_number = 0; # keep track of maps within the course.
my $course_id; # Will be the id of the course being read in.
#
@@ -66,7 +70,9 @@ my $retfrid;
my $username; # User for whom the map is being read.
my $userdomain; # Domain the user lives in.
+my $short_name; # Course shortname.
my %mapalias_cache; # Keeps track of map aliases -> resources detects duplicates.
+my %cenv; # Course environment.
#------------- Executable code:
@@ -92,10 +98,7 @@ my %mapalias_cache; # Keeps track of ma
sub simplify {
my $expression=shift;
- my $prior = ''; # This is safe as a null expression is pretty optimal.
-
- while ($prior ne $expression) {
- $prior = $expression; # Stop when the substitutions below do nothing.
+
# (0&1) = 1
$expression=~s/\(0\&([_\.\d]+)\)/$1/g;
# (8)=8
@@ -112,7 +115,8 @@ sub simplify {
# ((5&3)|(4&6))|(1&2)=(5&3)|(4&6)|(1&2)
$expression=~
s/\((\([_\.\d]+(?:\&[_\.\d]+)*\))((?:\|\([_\.\d]+(?:\&[_\.\d]+)*\))+)\)\|(\([_\.\d]+(?:\&[_\.\d]+)*\))/\($1$2\|$3\)/g;
- }
+
+
return $expression;
}
@@ -149,8 +153,12 @@ sub merge_conditions {
sub merge_hash {
my ($parent, $key, $child) = @_;
+ if ($key ne '') {
+ $key .= '.'; # If we are prefixing, prefix then .
+ }
+
foreach my $childkey (keys (%$child)) {
- $parent->{$key . '.' . $childkey} = $child->{$childkey};
+ $parent->{$key . $childkey} = $child->{$childkey};
}
}
@@ -205,9 +213,10 @@ sub get_mapalias_errors {
$count++;
}
my ($mapid) = split(/\./,$id);
- &mt('Resource "[_1]"
in Map "[_2]"',
- $hash->{'title_'.$id},
- $hash->{'title_'.$hash->{'ids_'.$hash->{'map_id_'.$mapid}}});
+ &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);
$error_text .= '
'.
@@ -287,7 +296,6 @@ sub process_versions {
#
sub versionerror {
my ($uri, $used, $unused) = @_;
- my ($uri,$usedversion,$unusedversion)=@_;
return '
'.
&mt('Version discrepancy: resource [_1] included in both version [_2] and version [_3]. Using version [_2].',
$uri,$used,$unused).'
';
@@ -308,7 +316,7 @@ sub versionerror {
# Returns:
# URI with the version cut out.
#
-sub vesiontrack {
+sub versiontrack {
my ($uri, $hash) = @_;
@@ -366,11 +374,41 @@ sub append_version {
return $uri;
}
+#------------------------------------------------------------------------------
+#
+# Misc. utilities that don't fit into the other classifications.
+
+# Determine if the specified user has an 'advanced' role in a course.
+# Parameters:
+# cenv - reference to a course environment.
+# username - Name of the user we care about.
+# domain - Domain in which the user is defined.
+# Returns:
+# 0 - User does not have an advanced role in the course.
+# 1 - User does have an advanced role in the course.
+#
+sub has_advanced_role {
+ my ($username, $domain) = @_;
+
+ my %adv_roles = &Apache::lonnet::get_course_adv_roles($short_name);
+ my $merged_username = $username . ':' . $domain;
+ foreach my $user (values %adv_roles) {
+ if ($merged_username eq $user) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
#--------------------------------------------------------------------------------
# Post processing subs:
sub hiddenurls {
my $hash = shift;
+ my $uname = $hash->{'context.username'};
+ my $udom = $hash->{'context.userdom'};
+ my $courseid = $hash->{'context.courseid'};
+
my $randomoutentry='';
foreach my $rid (keys %randompick) {
my $rndpick=$randompick{$rid};
@@ -393,15 +431,15 @@ sub hiddenurls {
# -------------------------------- randomly eliminate the ones that should stay
my (undef,$id)=split(/\./,$rid);
if ($randompickseed{$rid}) { $id=$randompickseed{$rid}; }
- my $rndseed=&Apache::lonnet::rndseed($id); # use id instead of symb
+ my $rndseed=&Apache::lonnet::rndseed($id, $courseid, $udom, $uname, \%cenv); # use id instead of symb
&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]]=''; }
- #&Apache::lonnet::logthis("$id,$rndseed,".join(':',@whichids));
+
# -------------------------------------------------------- delete the leftovers
for (my $k=0; $k<=$#currentrids; $k++) {
if ($currentrids[$k]) {
- $hash->{'randomout_'.$currentrids[$k]}=1;
+ $hash->{'randomout_'.$currentrids[$k]}='1';
my ($mapid,$resid)=split(/\./,$currentrids[$k]);
$randomoutentry.='&'.
&Apache::lonnet::encode_symb($hash->{'map_id_'.$mapid},
@@ -413,7 +451,7 @@ sub hiddenurls {
}
# ------------------------------ take care of explicitly hidden urls or folders
foreach my $rid (keys %hiddenurl) {
- $hash->{'randomout_'.$rid}=1;
+ $hash->{'randomout_'.$rid}='1';
my ($mapid,$resid)=split(/\./,$rid);
$randomoutentry.='&'.
&Apache::lonnet::encode_symb($hash->{'map_id_'.$mapid},$resid,
@@ -437,7 +475,7 @@ sub hiddenurls {
#
sub accinit {
- my ($uri, $short, $fn, $hash)=@_;
+ my ($uri, $short, $hash)=@_;
my %acchash=();
my %captured=();
my $condcounter=0;
@@ -580,7 +618,16 @@ sub traceroute {
my $hidden=&Apache::lonnet::EXT('resource.0.hiddenresource',$symb);
if ($hdnflag || lc($hidden) eq 'yes') {
- $hiddenurl{$rid}=1;
+ my $ignorehidden;
+ if (defined($hash->{'is_map_'.$rid})) {
+ if (($hash->{'context.nohideurl'}) && ($hash->{'context.nohideurl'} eq $hash->{'src_'.$rid})) {
+ $ignorehidden = 1; # Hidden parameter explicitly deleted
+ # if printing/grading bubblesheet exam
+ }
+ }
+ unless ($ignorehidden) {
+ $hiddenurl{$rid}=1;
+ }
}
if (!$hdnflag && lc($hidden) eq 'no') {
delete($hiddenurl{$rid});
@@ -615,7 +662,7 @@ sub traceroute {
$hash->{'map_start_'.$hash->{'src_'.$rid}},
$beenhere,
$encflag || $encurl{$rid},
- $hdnflag || $hiddenurl{$rid});
+ $hdnflag || $hiddenurl{$rid}, $hash);
}
}
@@ -635,13 +682,13 @@ sub traceroute {
$further=simplify('('.'_'.$rid.')&('.
$hash->{'condid_'.$hash->{'undercond_'.$id}}.')');
} else {
- my $errtext.=&mt('
Undefined condition ID: [_1]',$hash->{'undercond_'.$id});
+ my $errtext.='
'.&mt('Undefined condition ID: [_1]',$hash->{'undercond_'.$id});
throw Error::Simple($errtext);
}
}
# Recurse to resoruces that have to's to us.
$newsofar=&traceroute($further,$hash->{'goesto_'.$id},$beenhere,
- $encflag,$hdnflag);
+ $encflag,$hdnflag, $hash);
}
}
}
@@ -745,7 +792,6 @@ sub parse_param {
$hiddenurl{$referid}=1;
}
}
-
}
@@ -763,6 +809,7 @@ sub parse_param {
# $lpc - Map nesting level (?)
# $ispage - True if this resource is encapsulated in a .page (assembled resourcde).
# $uri - URI of the enclosing resource.
+# $code - CODE for which resource is being parsed (CODEd assignments).
# $hash - Reference to the hash we are building.
#
# Returns:
@@ -785,7 +832,7 @@ sub parse_param {
#
sub parse_resource {
- my ($token,$lpc,$ispage,$uri, $hash) = @_;
+ my ($token,$lpc,$ispage,$uri,$code,$hash) = @_;
# I refuse to countenance code like this that has
# such a dirty side effect (and forcing this sub to be called within a loop).
@@ -931,8 +978,16 @@ sub parse_resource {
if (($turi=~/\.sequence$/) ||
($turi=~/\.page$/)) {
- $hash->{'is_map_'.$rid}=1;
- &read_map($turi,$rid, $hash);
+ $hash->{'is_map_'.$rid}='1'; # String in lonuserstate.
+ if ($hiddenurl{$rid}) {
+ if (($hash->{'context.nohideurl'}) &&
+ ($hash->{'context.nohideurl'} eq $hash->{'src_'.$rid})) {
+ delete($hiddenurl{$rid}); # Hidden parameter explicitly deleted
+ # if printing/grading bubblesheet exam
+ }
+ }
+
+ &read_map($turi,$rid,$code,$hash);
}
return $token->[2]->{'id'};
}
@@ -969,7 +1024,7 @@ sub make_link {
my $linkid=$lpc.'.'.$linkpc;
my $goesto=$lpc.'.'.$to;
my $comesfrom=$lpc.'.'.$from;
- my $undercond=0;
+ my $undercond='0';
# If there is a condition, qualify it with the level counter.
@@ -1140,7 +1195,8 @@ sub parse_mapalias_param {
# $parent_rid - map number qualified id of the parent of the map being read.
# For the top level course map this is 0.0. For the first nested
# map 1.n where n is the id of the resource within the
-# top level map and so on.
+# top level map and so on.
+# $code - CODE for which map is being read (CODEd assignments).
# $hash - Reference to a hash that will become the big hash for the course
# This hash is modified as per the map description.
# Side-effects:
@@ -1150,7 +1206,8 @@ sub parse_mapalias_param {
#
#
sub read_map {
- my ($uri, $parent_rid, $hash) = @_;
+ my ($uri, $parent_rid, $code, $hash) = @_;
+
# Check for duplication: A map may only be included once.
@@ -1167,8 +1224,8 @@ sub read_map {
# map_pc_uri is the map number of the map with that URI.
# map_id_$lmap_no is the URI for this map level.
#
- $hash->{'map_pc_' . $uri} = $lmap_no;
- $hash->{'map_id_' . $lmap_no} = $uri;
+ $hash->{'map_pc_' . $uri} = "$lmap_no"; # string form in lonuserstate.
+ $hash->{'map_id_' . $lmap_no} = "$uri";
# Create the path up to the top of the course.
# this is in 'map_hierarchy_mapno' that's a comma separated path down to us
@@ -1191,9 +1248,12 @@ sub read_map {
# sorts of files that make sense for this sub
my $filename = &Apache::lonnet::filelocation('', &append_version($uri, $hash));
+
+
my $ispage = ($filename =~/\.page$/);
unless ($ispage || ($filename =~ /\.sequence$/)) {
- throw Error::Simple(&mt("
Invalid map: [_1]", $filename));
+ &Apache::lonnet::logthis("invalid: $filename : $uri");
+ throw Error::Simple('
'.&mt('Invalid map: [_1]',''.$filename.''));
}
$filename =~ /\.(\w+)$/;
@@ -1202,10 +1262,10 @@ sub read_map {
# Repcopy the file and get its contents...report errors if we can't
- my $contents = &Apache::lonet::getfile($filename);
+ my $contents = &Apache::lonnet::getfile($filename);
if($contents eq -1) {
- throw Error::Simple(&mt('
Map not loaded: The file [_1] does not exist.',
- $filename));
+ throw Error::Simple('
'.&mt('Map not loaded: The file [_1] does not exist.',
+ ''.$filename.''));
}
# Now that we succesfully retrieved the file we can make our parsing passes over it:
# parsing is done in passes:
@@ -1247,7 +1307,7 @@ sub read_map {
# Resource
if ($token->[1] eq 'resource') {
- my $resource_id = &parse_resource($token,$lmap_no,$ispage,$uri, $hash);
+ my $resource_id = &parse_resource($token,$lmap_no,$ispage,$uri,$code,$hash);
if (defined $resource_id) {
push(@map_ids, $resource_id);
}
@@ -1271,7 +1331,7 @@ sub read_map {
#
if ($randomize) {
- if (!$env{'request.role.adv'}) {
+ if (!&has_advanced_role($username, $userdomain) || $code) {
my $seed;
# In the advanced role, the map's random seed
@@ -1293,7 +1353,11 @@ sub read_map {
}
- my $rndseed=&Apache::lonnet::rndseed($seed, $username, $userdomain);
+ my $rndseed=&Apache::lonnet::rndseed($seed, '',
+ $userdomain, $username,
+ \%cenv);
+
+
&Apache::lonnet::setup_random_from_rndseed($rndseed);
# Take the set of map ids we have decoded and permute them to a
@@ -1301,10 +1365,8 @@ sub read_map {
# 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 = $lmap_no.'.'.$from;
$hash->{'map_start_'.$uri} = $from_rid;
@@ -1315,7 +1377,7 @@ sub read_map {
# if randomorder was set. This means that for an instructor to choose
while (my $to = shift(@map_ids)) {
- &make_link(++$linkpc,$lmap_no,$to,$from);
+ &make_link(++$linkpc,$lmap_no,$to,$from, 0, $hash);
my $to_rid = $lmap_no.'.'.$to;
$hash->{'type_'.$to_rid}='normal';
$from = $to;
@@ -1326,6 +1388,7 @@ sub read_map {
$hash->{'type_'.$from_rid}='finish';
}
+
# The last parsing pass parses the tags that associate a name
# with resource ids.
@@ -1352,6 +1415,8 @@ sub read_map {
# $cdom - Domain in which the course is evaluated.
# $uname - Name of the user for whom the course is being read
# $udom - Name of the domain of the user for whom the course is being read.
+# $code - CODE for which course is being read (CODEd assignments)
+# $nohideurl - URL for an exam folder for which hidden state is to be ignored.
# $target_hash- Reference to the target hash into which all of this is read.
# Note tht some of the hash entries we need to build require knowledge of the
# course URI.. these are expected to be filled in by the caller.
@@ -1360,9 +1425,11 @@ sub read_map {
#
#
sub loadmap {
- my ($cnum, $cdom, $uname, $udom, $filepath, $target_hash) = @_;
+ my ($cnum, $cdom, $uname, $udom, $code, $nohideurl, $target_hash) = @_;
- # Clear the auxillary hashes and the cond array.
+
+
+ # Clear the auxiliary hashes and the cond array.
%randompick = ();
@@ -1370,35 +1437,42 @@ sub loadmap {
%encurl = ();
%hiddenurl = ();
%parmhash = ();
- @cond = ();
+ @cond = ('true:normal'); # Initial value for cond 0.
$retfrid = '';
-
+ $username = '';
+ $userdomain = '';
+ %mapalias_cache = ();
+ %cenv = ();
+ $map_number = 0;
#
$username = $uname;
$userdomain = $udom;
- my $short_name = $cdom . $cnum;
+ $short_name = $cdom .'/' . $cnum;
+ my $retfurl;
try {
# Get the information we need about the course.
- # Return without filling in anything if we can't get any info:
-
- my %cenv = &Apache::lonnet::coursedescription($short_name,
- {'freshen_cache' => 1,
- 'user' => $uname});
- unless ($cenv{'url'}) {
- &Apache::lonnet::logthis("lonmap::loadmap failed: $cnum/$cdom - did not get url");
- return;
- }
- $course_id = $cdom . '.' . $cnum; # Long course id.
-
- # Load the version information into the hash
-
-
+ # Return without filling in anything if we can't get any info:
+
+ %cenv = &Apache::lonnet::coursedescription($short_name,
+ {'freshen_cache' => 1,
+ 'user' => $uname});
+
+ unless ($cenv{'url'}) {
+ &Apache::lonnet::logthis("lonmap::loadmap failed: $cnum/$cdom - did not get url");
+ return;
+ }
+
+ $course_id = $cdom . '_' . $cnum; # Long course id.
+
+ # Load the version information into the hash
+
+
&process_versions(\%cenv, $target_hash);
@@ -1409,16 +1483,29 @@ sub loadmap {
$target_hash->{'src_0.0'} = &versiontrack($map_uri, $target_hash);
$target_hash->{'title_0.0'} = &Apache::lonnet::metadata($course_uri, 'title');
+ if(!defined $target_hash->{'title_0.0'}) {
+ $target_hash->{'title_0.0'} = '';
+ }
$target_hash->{'ids_'.$map_uri} = '0.0';
- $target_hash->{'is_map_0.0'} = 1;
- &read_map($course_uri, '0.0', &hash);
+ $target_hash->{'is_map_0.0'} = '1';
+
+ # In some places we need a username a domain and the courseid...store that
+ # in the target hash in the context.xxxx keys:
+
+ $target_hash->{'context.username'} = $username;
+ $target_hash->{'context.userdom'} = $userdomain;
+ $target_hash->{'context.courseid'} = $course_id;
+
+ # When grading or printing a bubblesheet exam ignore
+ # "hidden" parameter set in the map containing the exam folder.
+ $target_hash->{'context.nohideurl'} = $nohideurl;
- #
+ &read_map($course_uri, '0.0', $code, $target_hash);
if (defined($target_hash->{'map_start_'.$map_uri})) {
- &traceroute('0',$target_hash->{'map_start_'.$course_uri},'&', $target_hash);
- &accinit($course_uri, $short_name, $filepath, $target_hash);
+ &traceroute('0',$target_hash->{'map_start_'.$course_uri},'&', 0, 0, $target_hash);
+ &accinit($course_uri, $short_name, $target_hash);
&hiddenurls($target_hash);
}
my $errors = &get_mapalias_errors($target_hash);
@@ -1438,12 +1525,32 @@ sub loadmap {
$target_hash->{$type.&putinversion($url, $target_hash, $short_name)}=$value;
}
}
+ # Mark necrypted URLS.
+
+ foreach my $id (keys(%encurl)) {
+ $target_hash->{'encrypted_'.$id}=1;
+ }
+
+ # Store first keys.
+
+ $target_hash->{'first_rid'}=$retfrid;
+ my ($mapid,$resid)=split(/\./,$retfrid);
+ $target_hash->{'first_mapurl'}=$target_hash->{'map_id_'.$mapid};
+ my $symb=&Apache::lonnet::encode_symb($target_hash->{'map_id_'.$mapid},
+ $resid,
+ $target_hash->{'src_'.$retfrid});
+ $retfurl=&add_get_param($target_hash->{'src_'.$retfrid},{ 'symb' => $symb });
+ if ($target_hash->{'encrypted_'.$retfrid}) {
+ $retfurl=&Apache::lonenc::encrypted($retfurl,
+ (&Apache::lonnet::allowed('adv') ne 'F'));
+ }
+ $target_hash->{'first_url'}=$retfurl;
# Merge in the child hashes in case the caller wants that information as well.
&merge_hash($target_hash, 'randompick', \%randompick);
- &merge_hash($target_hash, 'randompickseed', \%randompick);
+ &merge_hash($target_hash, 'randompickseed', \%randompickseed);
&merge_hash($target_hash, 'randomorder', \%randomorder);
&merge_hash($target_hash, 'encurl', \%encurl);
&merge_hash($target_hash, 'hiddenurl', \%hiddenurl);
@@ -1462,7 +1569,7 @@ sub loadmap {
#
# Module initialization code:
-#
+# TODO: Fix the pod docs below.
1;
__END__
@@ -1473,28 +1580,39 @@ Apache::lonmap - Construct a hash that r
=head1 SYNOPSIS
-&Apache::lonmap::loadmap($filepath, \%target_hash);
+&Apache::lonmap::loadmap($cnum, $cdom, $uname, $udom, $code, $nohideurl, \%target_hash);
=head1 INTRODUCTION
This module reads a course filename into a hash reference. It's up to the caller
-to to things like decide the has should be tied to some external file and handle the locking
-if this file should be shared amongst several Apache children.
+to do things like decide that the hash should be tied to some external file and handle the
+the locking if this file should be shared amongst several Apache children.
=head1 SUBROUTINES
=over
-=item loadmap($filepath, $targethash)
+=item loadmap($cnum, $cdom, $uname, $udom, $code, $nohideurl, $targethash)
-Reads the map file into a target hash.
+Reads the top-level map file into a target hash. This is done by first parsing the
+map file into local hashes and then unrolling those hashes into the big hash.
=over
-=item $filepath - The path to the map file to read.
+=item $cnum - number of course being read.
+
+=item $cdom - domain in which the course is evaluated.
+
+=item $uname - name of the user for whom the course is being read.
+
+=item $udom - name of the domain of the user for whom the course is being read.
+
+=item $code - CODE for which course is being read (CODEd assignments).
+
+=item $nohideurl - URL for an exam folder for which hidden state is to be ignored.
-=item $targethash - A reference to hash into which the course is read.
+=item $targethash - A reference to hash into which the course is read
=back