Annotation of loncom/lonmap.pm, revision 1.16

1.1       foxr        1: # The LearningOnline Network
                      2: #
                      3: #  Read maps into a 'big hash'.
                      4: #
1.16    ! raeburn     5: # $Id: lonmap.pm,v 1.15 2014/12/11 01:50:27 raeburn Exp $
1.1       foxr        6: #
                      7: # Copyright Michigan State University Board of Trustees
                      8: #
                      9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                     10: #
                     11: # LON-CAPA is free software; you can redistribute it and/or modify
                     12: # it under the terms of the GNU General Public License as published by
                     13: # the Free Software Foundation; either version 2 of the License, or
                     14: # (at your option) any later version.
                     15: #
                     16: # LON-CAPA is distributed in the hope that it will be useful,
                     17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     19: # GNU General Public License for more details.
                     20: #
                     21: # You should have received a copy of the GNU General Public License
                     22: # along with LON-CAPA; if not, write to the Free Software
                     23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     24: #
                     25: # /home/httpd/html/adm/gpl.txt
                     26: #
                     27: # http://www.lon-capa.org/
                     28: #
                     29: ###
                     30: 
1.3       foxr       31: package Apache::lonmap;
1.1       foxr       32: use strict;
                     33: 
                     34: #------------- Required external modules.
                     35: 
                     36: use Error qw(:try);
                     37: 
                     38: use HTML::TokeParser;
                     39: 
                     40: 
1.2       foxr       41: use LONCAPA;
1.1       foxr       42: use Apache::lonnet;
1.6       foxr       43: use Apache::lonlocal;
                     44: 
1.3       foxr       45: use Data::Dumper;
                     46: 
1.1       foxr       47: 
                     48: #------------- File scoped variables:
                     49: 
1.3       foxr       50: my $map_number = 0;		# keep track of maps within the course.
1.1       foxr       51: my $course_id;     		# Will be the id of the course being read in.
                     52: 
                     53: #
                     54: # The variables below are auxiliary hashes.  They will be tacked onto the
                     55: # big hash though currently not used by clients.. you never know about later.
                     56: #
                     57: 
                     58: my %randompick;
                     59: my %randompickseed;
                     60: my %randomorder;
                     61: my %encurl;
                     62: my %hiddenurl;
1.2       foxr       63: my %parmhash;
1.1       foxr       64: my @cond;			# Array of conditions.
1.2       foxr       65: my $retfrid;
1.1       foxr       66: #
                     67: #  Other stuff we make global (sigh) so that it does not need
                     68: #  to be passed around all the time:
                     69: #
                     70: 
                     71: my $username;			# User for whom the map is being read.
                     72: my $userdomain;  		# Domain the user lives in.
1.6       foxr       73: my $short_name;			# Course shortname.
1.1       foxr       74: my %mapalias_cache;		# Keeps track of map aliases -> resources detects duplicates.
1.3       foxr       75: my %cenv;			# Course environment.
1.1       foxr       76: 
                     77: #------------- Executable code: 
                     78: 
                     79: 
                     80: #----------------------------------------------------------------
                     81: #
                     82: #  General utilities:
                     83: 
                     84: 
                     85: #
                     86: #  I _think_ this does common sub-expression simplification and 
                     87: #  optimization for condition strings...based on common pattern matching.
                     88: # Parameters:
                     89: #    expression - the condition expression string.
                     90: # Returns:
                     91: #    The optimized expression if an optimization could be found.
                     92: #
                     93: # NOTE:
                     94: #   Added repetetive optimization..it's possible that an
                     95: #   optimization results in an expression that can be recognized further in
                     96: #   a subsequent optimization pass:
                     97: #
                     98: 
                     99: sub simplify {
                    100:     my $expression=shift;
1.4       foxr      101: 
1.1       foxr      102: # (0&1) = 1
                    103: 	$expression=~s/\(0\&([_\.\d]+)\)/$1/g;
                    104: # (8)=8
                    105: 	$expression=~s/\(([_\.\d]+)\)/$1/g;
                    106: # 8&8=8
                    107: 	$expression=~s/([^_\.\d])([_\.\d]+)\&\2([^_\.\d])/$1$2$3/g;
                    108: # 8|8=8
                    109: 	$expression=~s/([^_\.\d])([_\.\d]+)(?:\|\2)+([^_\.\d])/$1$2$3/g;
                    110: # (5&3)&4=5&3&4
                    111: 	$expression=~s/\(([_\.\d]+)((?:\&[_\.\d]+)+)\)\&([_\.\d]+[^_\.\d])/$1$2\&$3/g;
                    112: # (((5&3)|(4&6)))=((5&3)|(4&6))
                    113: 	$expression=~
                    114: 	    s/\((\(\([_\.\d]+(?:\&[_\.\d]+)*\)(?:\|\([_\.\d]+(?:\&[_\.\d]+)*\))+\))\)/$1/g;
                    115: # ((5&3)|(4&6))|(1&2)=(5&3)|(4&6)|(1&2)
                    116: 	$expression=~
                    117: 	    s/\((\([_\.\d]+(?:\&[_\.\d]+)*\))((?:\|\([_\.\d]+(?:\&[_\.\d]+)*\))+)\)\|(\([_\.\d]+(?:\&[_\.\d]+)*\))/\($1$2\|$3\)/g;
1.4       foxr      118: 
                    119: 
1.1       foxr      120:     return $expression;
                    121: }
                    122:     
                    123: 
                    124: #
                    125: #  Merge the conditions into the big hash
                    126: #  these will result in hash entries of the form:
                    127: #   'condition.n'  where 'n' is the array index of the condition in the
                    128: #   @cond array above.
                    129: #
                    130: #  Parameters:
                    131: #    $hash - big hashthat's being built up.
                    132: #
                    133: sub merge_conditions {
                    134:     my $hash = shift;
                    135: 
1.2       foxr      136:     for (my $i = 0; $i < scalar(@cond); $i++) {
1.1       foxr      137: 	$hash->{'condition' . '.' . $i} = $cond[$i];
                    138:     }
                    139: }
                    140: 
                    141: # Merge the contents of a 'child hash' into a parent hash hanging it off another key.
                    142: # This is _not_ done by hanging a reference to the child hash as the parent may be
                    143: # bound to a GDBM file e.g. and shared by more than one process ..and references are
                    144: # pretty clearly not going to work across process boundaries.
                    145: #
                    146: # Parameters:
                    147: #   $parent  - The hash to which the child will be merged (reference)
                    148: #   $key     - The key in the parent hash on which the child elements will be hung.
                    149: #              given a key named $childkey the final parent hash entry will be
                    150: #              $parent . '.' $childkey
                    151: #  $child    - The hash whose contents we merge into the parent (reference)
                    152: #
                    153: sub merge_hash {
                    154:     my ($parent, $key, $child) = @_;
                    155: 
1.3       foxr      156:     if ($key ne '') {
                    157: 	$key .= '.';		# If we are prefixing, prefix then .
                    158:     }
                    159: 
1.15      raeburn   160:     foreach my $childkey (keys(%$child)) {
1.3       foxr      161: 	$parent->{$key . $childkey} = $child->{$childkey};
1.1       foxr      162:     }
                    163: }
                    164: 
                    165: #----------------------------------------------------------------------------------
                    166: #
                    167: #   Code to keep track of map aliases and to determine if there are doubly 
                    168: #   defined aliases.
                    169: #
                    170: 
                    171: #
                    172: #  Maintains the mapalias hash.  This is a hash of arrays.  Each array
                    173: #  is indexed by the alias and contains the set of resource ids pointed to by that
                    174: #  alias.  In an ideal world, there will only be one element in each array.
                    175: #  The point of this, however is to determine which aliases might be doubley defined
                    176: #  due to map nesting e.g.
                    177: #
                    178: #  Parameters:
                    179: #    $value   - Alias name.
                    180: #    $resid   - Resource id pointed to by the alias.
                    181: #
                    182: #    
                    183: sub count_mapalias {
                    184:     my ($value,$resid) = @_;
                    185:     push(@{ $mapalias_cache{$value} }, $resid);
                    186: }
                    187: #
                    188: #  Looks at each key in the mapalias hash and, for each case where an
                    189: #  alias points to more than one value adds an error text to the
                    190: #  result string.'
                    191: #
                    192: #  Parameters:
1.2       foxr      193: #     hash - Reference to the hash we are trying t build up.
1.1       foxr      194: #  Implicit inputs
                    195: #     %mapalias - a hash that is indexed by map aliases and contains for each key
                    196: #                 an array of the resource id's the alias 'points to'.
                    197: # Returns:
                    198: #    A hopefully empty string of messages that descsribe the aliases that have more
                    199: #    than one value.  This string is formatted like an html list.
                    200: #
                    201: #
                    202: sub get_mapalias_errors {
1.2       foxr      203:     my $hash = shift;
1.1       foxr      204:     my $error_text;
                    205:     foreach my $mapalias (sort(keys(%mapalias_cache))) {
                    206: 	next if (scalar(@{ $mapalias_cache{$mapalias} } ) == 1);
                    207: 	my $count;
                    208: 	my $which =
                    209: 	    join('</li><li>', 
                    210: 		 map {
                    211: 		     my $id = $_;
1.2       foxr      212: 		     if (exists($hash->{'src_'.$id})) {
1.1       foxr      213: 			 $count++;
                    214: 		     }
                    215: 		     my ($mapid) = split(/\./,$id);
1.13      bisitz    216: 		     &mt('Resource [_1][_2]in Map [_3]',
                    217: 			 '"'.$hash->{'title_'.$id}.'"',
                    218:                          '<br />',
                    219: 			 '"'.$hash->{'title_'.$hash->{'ids_'.$hash->{'map_id_'.$mapid}}}.'"');
1.1       foxr      220: 		 } (@{ $mapalias_cache{$mapalias} }));
                    221: 	next if ($count < 2);
                    222: 	$error_text .= '<div class="LC_error">'.
                    223: 	    &mt('Error: Found the mapalias "[_1]" defined multiple times.',
                    224: 		$mapalias).
                    225: 		'</div><ul><li>'.$which.'</li></ul>';
                    226:     }
                    227:     &clear_mapalias_count();
                    228:     return $error_text;
                    229: }
                    230: #
                    231: #   Clears the map aliase hash.
                    232: #
                    233: sub clear_mapalias_count {
                    234:     undef(%mapalias_cache);
                    235: }
                    236: 
                    237: #----------------------------------------------------------------
                    238: #
                    239: #  Code dealing with resource versions.
                    240: #
                    241: 
                    242: #
1.2       foxr      243: #  Put a version into a src element of a hash or url:
                    244: #
                    245: #  Parameters:
                    246: #     uri - URI into which the version must be added.
                    247: #    hash - Reference to the hash being built up.
                    248: #    short- Short coursename.
                    249: #
                    250: 
                    251: sub putinversion {
                    252:     my ($uri, $hash, $short) = @_;
                    253:     my $key=$short.'_'.&Apache::lonnet::clutter($uri);
                    254:     if ($hash->{'version_'.$uri}) {
                    255: 	my $version=$hash->{'version_'.$uri};
                    256: 	if ($version eq 'mostrecent') { return $uri; }
                    257: 	if ($version eq &Apache::lonnet::getversion(
                    258: 			&Apache::lonnet::filelocation('',$uri))) 
                    259: 	             { return $uri; }
                    260: 	$uri=~s/\.(\w+)$/\.$version\.$1/;
                    261:     }
                    262:     &Apache::lonnet::do_cache_new('courseresversion',$key,&Apache::lonnet::declutter($uri),600);
                    263:     return $uri;
                    264: }
                    265: 
                    266: 
                    267: #
1.1       foxr      268: #  Create hash entries for each version of the course.
                    269: # Parameters:
                    270: #   $cenv    - Reference to a course environment from lonnet::coursedescription.
                    271: #   $hash    - Reference to a hash that will be populated.
                    272: 
                    273: #
                    274: sub process_versions {
                    275:     my ($cenv, $hash) = @_;
                    276: 
                    277:     
                    278:     my %versions = &Apache::lonnet::dump('resourceversions',
                    279: 					 $cenv->{'domain'},
                    280: 					 $cenv->{'num'});
                    281: 
1.15      raeburn   282:     foreach my $ver (keys(%versions)) {
1.1       foxr      283: 	if ($ver =~/^error\:/) { # lonc/lond transaction failed.
                    284: 	    throw Error::Simple('lonc/lond returned error: ' . $ver);
                    285: 	}
                    286: 	$hash->{'version_'.$ver} = $versions{$ver};
                    287:     }
                    288: }
                    289: 
                    290: #
                    291: #  Generate text for a version discrepancy error:
                    292: # Parameters:
                    293: #  $uri   - URI of the resource.
                    294: #  $used  - Version used.
                    295: #  $unused - Veresion of duplicate.
                    296: #
                    297: sub versionerror {
                    298:     my ($uri, $used, $unused) = @_;
                    299:     return '<br />'.
                    300: 	&mt('Version discrepancy: resource [_1] included in both version [_2] and version [_3]. Using version [_2].',
                    301: 	    $uri,$used,$unused).'<br />';
                    302: 
                    303: }
                    304: 
                    305: #  Removes the version number from a URI and returns the resulting
                    306: #  URI (e.g. mumbly.version.stuff => mumbly.stuff).
                    307: #
                    308: #   If the URI has not been seen with a version before the
                    309: #   hash{'version_'.resultingURI} is set to the  version number.
                    310: #   If the hash has already been seen, but differs then
                    311: #   an error is raised.
                    312: #
                    313: # Parameters:
                    314: #   $uri  -  potentially with a version.
                    315: #   $hash -  reference to a hash to fill in. 
                    316: # Returns:
                    317: #   URI with the version cut out.
                    318: #
1.3       foxr      319: sub versiontrack {
1.1       foxr      320:     my ($uri, $hash) = @_;
                    321: 
                    322: 
                    323:     if ($uri=~/\.(\d+)\.\w+$/) { # URI like *.n.text it's version 'n'
                    324: 	my $version=$1;
                    325: 	$uri=~s/\.\d+\.(\w+)$/\.$1/; # elide the version.
                    326:         unless ($hash->{'version_'.$uri}) {
                    327: 	    $hash->{'version_'.$uri}=$version;
                    328: 	} elsif ($version!=$hash->{'version_'.$uri}) {
1.2       foxr      329: 	    throw Error::Simple(&versionerror($uri, $hash->{'version_'.$uri}, $version));
1.1       foxr      330:         }
                    331:     }
                    332:     return $uri;
                    333: }
                    334: #
                    335: #  Appends the version of a resource to its uri and also caches the 
                    336: #  URI (contents?) on the local server
                    337: #
                    338: #  Parameters:
                    339: #     $uri   - URI of the course (without version informatino.
                    340: #     $hash  - What we have of  the big hash.
                    341: #
                    342: # Side-Effects:
                    343: #   The URI is cached by memcached.
                    344: #
                    345: # Returns:
                    346: #    The version appended URI.
                    347: #
                    348: sub append_version {
                    349:     my ($uri, $hash) = @_;
                    350: 
                    351:     # Create the key for the cache entry.
                    352: 
                    353:     my $key = $course_id . '_' . &Apache::lonnet::clutter($uri);
                    354: 
                    355:     # If there is a version it will already be  in the hash:
                    356: 
                    357:     if ($hash->{'version_' . $uri}) {
                    358: 	my $version = $hash->{'version_' . $uri};
                    359: 	if ($version eq 'mostrecent') {
                    360: 	    return $uri;     # Most recent version does not require decoration (or caching?).
                    361: 	}
                    362: 	if ($version eq 
                    363: 	    &Apache::lonnet::getversion(&Apache::lonnet::filelocation('', $uri))) {
                    364: 	    return $uri;	# version matches the most recent file version?
                    365: 	}
                    366: 	$uri =~ s/\.(\w+)$/\.$version\.$1/; # insert the versino prior to the last .word.
                    367:     }
                    368:  
                    369:    # cache the version:
                    370: 
                    371:    &Apache::lonnet::do_cache_new('courseresversion', $key, 
                    372: 				 &Apache::lonnet::declutter($uri), 600);
                    373: 
                    374:     return $uri;
                    375: 
                    376: }
1.6       foxr      377: #------------------------------------------------------------------------------
                    378: #
                    379: #  Misc. utilities that don't fit into the other classifications.
                    380: 
                    381: # Determine if the specified user has an 'advanced' role in a course.
                    382: # Parameters:
                    383: #   cenv       - reference to a course environment.
                    384: #   username   - Name of the user we care about.
                    385: #   domain     - Domain in which the user is defined.
                    386: # Returns:
                    387: #    0  - User does not have an advanced role in the course.
                    388: #    1  - User does have an advanced role in the course.
                    389: #
                    390: sub has_advanced_role {
                    391:     my ($username, $domain) = @_;
                    392: 
                    393:     my %adv_roles = &Apache::lonnet::get_course_adv_roles($short_name);
                    394:     my $merged_username = $username . ':' . $domain;
                    395:     foreach my $user (values %adv_roles) {
                    396: 	if ($merged_username eq $user) {
                    397: 	    return 1;
                    398: 	}
                    399:     }
                    400:     return 0;
                    401: }
                    402: 
1.1       foxr      403: #--------------------------------------------------------------------------------
                    404: # Post processing subs:
                    405: sub hiddenurls {
                    406:     my $hash = shift;
                    407: 
1.3       foxr      408:     my $uname    = $hash->{'context.username'};
                    409:     my $udom     = $hash->{'context.userdom'};
                    410:     my $courseid = $hash->{'context.courseid'};
                    411: 
1.1       foxr      412:     my $randomoutentry='';
1.15      raeburn   413:     foreach my $rid (keys(%randompick)) {
1.1       foxr      414:         my $rndpick=$randompick{$rid};
                    415:         my $mpc=$hash->{'map_pc_'.$hash->{'src_'.$rid}};
                    416: # ------------------------------------------- put existing resources into array
                    417:         my @currentrids=();
                    418:         foreach my $key (sort(keys(%$hash))) {
                    419: 	    if ($key=~/^src_($mpc\.\d+)/) {
                    420: 		if ($hash->{'src_'.$1}) { push @currentrids, $1; }
                    421:             }
                    422:         }
                    423: 	# rids are number.number and we want to numercially sort on 
                    424:         # the second number
                    425: 	@currentrids=sort {
                    426: 	    my (undef,$aid)=split(/\./,$a);
                    427: 	    my (undef,$bid)=split(/\./,$b);
                    428: 	    $aid <=> $bid;
                    429: 	} @currentrids;
                    430:         next if ($#currentrids<$rndpick);
                    431: # -------------------------------- randomly eliminate the ones that should stay
                    432: 	my (undef,$id)=split(/\./,$rid);
                    433:         if ($randompickseed{$rid}) { $id=$randompickseed{$rid}; }
1.3       foxr      434: 	my $rndseed=&Apache::lonnet::rndseed($id, $courseid, $udom, $uname, \%cenv); # use id instead of symb
1.1       foxr      435: 	&Apache::lonnet::setup_random_from_rndseed($rndseed);
                    436: 	my @whichids=&Math::Random::random_permuted_index($#currentrids+1);
                    437:         for (my $i=1;$i<=$rndpick;$i++) { $currentrids[$whichids[$i]]=''; }
1.3       foxr      438: 
1.1       foxr      439: # -------------------------------------------------------- delete the leftovers
                    440:         for (my $k=0; $k<=$#currentrids; $k++) {
                    441:             if ($currentrids[$k]) {
1.3       foxr      442: 		$hash->{'randomout_'.$currentrids[$k]}='1';
1.1       foxr      443:                 my ($mapid,$resid)=split(/\./,$currentrids[$k]);
                    444:                 $randomoutentry.='&'.
                    445: 		    &Apache::lonnet::encode_symb($hash->{'map_id_'.$mapid},
                    446: 						 $resid,
                    447: 						 $hash->{'src_'.$currentrids[$k]}
                    448: 						 ).'&';
                    449:             }
                    450:         }
                    451:     }
                    452: # ------------------------------ take care of explicitly hidden urls or folders
1.15      raeburn   453:     foreach my $rid (keys(%hiddenurl)) {
1.3       foxr      454: 	$hash->{'randomout_'.$rid}='1';
1.1       foxr      455: 	my ($mapid,$resid)=split(/\./,$rid);
                    456: 	$randomoutentry.='&'.
                    457: 	    &Apache::lonnet::encode_symb($hash->{'map_id_'.$mapid},$resid,
                    458: 					 $hash->{'src_'.$rid}).'&';
                    459:     }
                    460: # --------------------------------------- add randomout to the hash.
                    461:     if ($randomoutentry) {
                    462: 	$hash->{'acc.randomout'} = $randomoutentry;
                    463: 
                    464:     }
                    465: }
                    466: 
                    467: #
                    468: # It's not so clear to me what this sub does.
                    469: #
                    470: #  Parameters
                    471: #     uri   - URI from the course description hash.
                    472: #     short - Course short name.
                    473: #     fn    - Course filename.
                    474: #     hash  - Reference to the big hash as filled in so far
                    475: #       
                    476: 
                    477: sub accinit {
1.3       foxr      478:     my ($uri, $short, $hash)=@_;
1.1       foxr      479:     my %acchash=();
                    480:     my %captured=();
                    481:     my $condcounter=0;
                    482:     $acchash{'acc.cond.'.$short.'.0'}=0;
                    483: 
                    484:     # This loop is only interested in conditions and 
                    485:     # parameters in the big hash:
                    486: 
                    487:     foreach my $key (keys(%$hash)) {
                    488: 
                    489: 	# conditions:
                    490: 
                    491: 	if ($key=~/^conditions/) {
                    492: 	    my $expr=$hash->{$key};
                    493: 
                    494: 	    # try to find and factor out common sub-expressions
                    495: 	    # Any subexpression that is found is simplified, removed from
                    496: 	    # the original condition expression and the simplified sub-expression
                    497: 	    # substituted back in to the epxression..I'm not actually convinced this
                    498: 	    # factors anything out...but instead maybe simplifies common factors(?)
                    499: 
                    500: 	    foreach my $sub ($expr=~m/(\(\([_\.\d]+(?:\&[_\.\d]+)+\)(?:\|\([_\.\d]+(?:\&[_\.\d]+)+\))+\))/g) {
                    501: 		my $orig=$sub;
                    502: 
                    503: 		my ($factor) = ($sub=~/\(\(([_\.\d]+\&(:?[_\.\d]+\&)*)(?:[_\.\d]+\&*)+\)(?:\|\(\1(?:[_\.\d]+\&*)+\))+\)/);
                    504: 		next if (!defined($factor));
                    505: 
                    506: 		$sub=~s/\Q$factor\E//g;
                    507: 		$sub=~s/^\(/\($factor\(/;
                    508: 		$sub.=')';
                    509: 		$sub=simplify($sub);
                    510: 		$expr=~s/\Q$orig\E/$sub/;
                    511: 	    }
                    512: 	    $hash->{$key}=$expr;
                    513: 
                    514:            # If not yet seen, record in acchash and that we've seen it.
                    515: 
                    516: 	    unless (defined($captured{$expr})) {
                    517: 		$condcounter++;
                    518: 		$captured{$expr}=$condcounter;
                    519: 		$acchash{'acc.cond.'.$short.'.'.$condcounter}=$expr;
                    520: 	    } 
                    521:         # Parameters:
                    522: 
                    523: 	} elsif ($key=~/^param_(\d+)\.(\d+)/) {
                    524: 	    my $prefix=&Apache::lonnet::encode_symb($hash->{'map_id_'.$1},$2,
                    525: 						    $hash->{'src_'.$1.'.'.$2});
                    526: 	    foreach my $param (split(/\&/,$hash->{$key})) {
                    527: 		my ($typename,$value)=split(/\=/,$param);
                    528: 		my ($type,$name)=split(/\:/,$typename);
                    529: 		$parmhash{$prefix.'.'.&unescape($name)}=
                    530: 		    &unescape($value);
                    531: 		$parmhash{$prefix.'.'.&unescape($name).'.type'}=
                    532: 		    &unescape($type);
                    533: 	    }
                    534: 	}
                    535:     }
                    536:     # This loop only processes id entries in the big hash.
                    537: 
                    538:     foreach my $key (keys(%$hash)) {
                    539: 	if ($key=~/^ids/) {
                    540: 	    foreach my $resid (split(/\,/,$hash->{$key})) {
                    541: 		my $uri=$hash->{'src_'.$resid};
                    542: 		my ($uripath,$urifile) =
                    543: 		    &Apache::lonnet::split_uri_for_cond($uri);
                    544: 		if ($uripath) {
                    545: 		    my $uricond='0';
                    546: 		    if (defined($hash->{'conditions_'.$resid})) {
                    547: 			$uricond=$captured{$hash->{'conditions_'.$resid}};
                    548: 		    }
                    549: 		    if (defined($acchash{'acc.res.'.$short.'.'.$uripath})) {
                    550: 			if ($acchash{'acc.res.'.$short.'.'.$uripath}=~
                    551: 			    /(\&\Q$urifile\E\:[^\&]*)/) {
                    552: 			    my $replace=$1;
                    553: 			    my $regexp=$replace;
                    554: 			    #$regexp=~s/\|/\\\|/g;
                    555: 			    $acchash{'acc.res.'.$short.'.'.$uripath} =~
                    556: 				s/\Q$regexp\E/$replace\|$uricond/;
                    557: 			} else {
                    558: 			    $acchash{'acc.res.'.$short.'.'.$uripath}.=
                    559: 				$urifile.':'.$uricond.'&';
                    560: 			}
                    561: 		    } else {
                    562: 			$acchash{'acc.res.'.$short.'.'.$uripath}=
                    563: 			    '&'.$urifile.':'.$uricond.'&';
                    564: 		    }
                    565: 		} 
                    566: 	    }
                    567:         }
                    568:     }
                    569:     $acchash{'acc.res.'.$short.'.'}='&:0&';
                    570:     my $courseuri=$uri;
                    571:     $courseuri=~s/^\/res\///;
                    572:     my $regexp = 1;
                    573:  
                    574:     &merge_hash($hash, '', \%acchash); # there's already an acc prefix in the hash keys.
                    575: 
                    576: 
                    577: }
                    578: 
                    579: 
                    580: #
                    581: #  Traces a route recursively through the map after it has been loaded
                    582: #  (I believe this really visits each resource that is reachable fromt he
                    583: #  start top node.
                    584: #
                    585: #  - Marks hidden resources as hidden.
                    586: #  - Marks which resource URL's must be encrypted.
                    587: #  - Figures out (if necessary) the first resource in the map.
                    588: #  - Further builds the chunks of the big hash that define how 
                    589: #    conditions work
                    590: #
                    591: #  Note that the tracing strategy won't visit resources that are not linked to
                    592: #  anything or islands in the map (groups of resources that form a path but are not
                    593: #  linked in to the path that can be traced from the start resource...but that's ok
                    594: #  because by definition, those resources are not reachable by users of the course.
                    595: #
                    596: # Parameters:
                    597: #   sofar    - _URI of the prior entry or 0 if this is the top.
                    598: #   rid      - URI of the resource to visit.
                    599: #   beenhere - list of resources (each resource enclosed by &'s) that have
                    600: #              already been visited.
                    601: #   encflag  - If true the resource that resulted in a recursive call to us
                    602: #              has an encoded URL (which means contained resources should too). 
                    603: #   hdnflag  - If true,the resource that resulted in a recursive call to us
                    604: #              was hidden (which means contained resources should be hidden too).
                    605: #   hash     - Reference to the hash we are traversing.
                    606: # Returns
                    607: #    new value indicating how far the map has been traversed (the sofar).
                    608: #
                    609: sub traceroute {
1.2       foxr      610:     my ($sofar, $rid, $beenhere, $encflag, $hdnflag, $hash)=@_;
1.1       foxr      611:     my $newsofar=$sofar=simplify($sofar);
                    612: 
                    613:     unless ($beenhere=~/\&\Q$rid\E\&/) {
                    614: 	$beenhere.=$rid.'&';  
                    615: 	my ($mapid,$resid)=split(/\./,$rid);
                    616: 	my $symb=&Apache::lonnet::encode_symb($hash->{'map_id_'.$mapid},$resid,
                    617: 					      $hash->{'src_'.$rid});
                    618: 	my $hidden=&Apache::lonnet::EXT('resource.0.hiddenresource',$symb);
1.14      raeburn   619:         my $ignorehidden;
1.1       foxr      620: 
                    621: 	if ($hdnflag || lc($hidden) eq 'yes') {
1.11      raeburn   622:             if (defined($hash->{'is_map_'.$rid})) {
                    623:                 if (($hash->{'context.nohideurl'}) && ($hash->{'context.nohideurl'} eq $hash->{'src_'.$rid})) {
                    624:                     $ignorehidden = 1; # Hidden parameter explicitly deleted 
                    625:                                        # if printing/grading bubblesheet exam
                    626:                 }
                    627:             }
                    628:             unless ($ignorehidden) {
                    629: 	        $hiddenurl{$rid}=1;
                    630:             }
1.1       foxr      631: 	}
                    632: 	if (!$hdnflag && lc($hidden) eq 'no') {
                    633: 	    delete($hiddenurl{$rid});
                    634: 	}
                    635: 
                    636: 	my $encrypt=&Apache::lonnet::EXT('resource.0.encrypturl',$symb);
                    637: 	if ($encflag || lc($encrypt) eq 'yes') { $encurl{$rid}=1; }
                    638: 
                    639: 	if (($retfrid eq '') && ($hash->{'src_'.$rid})
                    640: 	    && ($hash->{'src_'.$rid}!~/\.sequence$/)) {
                    641: 	    $retfrid=$rid;
                    642: 	}
                    643: 
                    644: 	if (defined($hash->{'conditions_'.$rid})) {
                    645: 	    $hash->{'conditions_'.$rid}=simplify(
                    646:            '('.$hash->{'conditions_'.$rid}.')|('.$sofar.')');
                    647: 	} else {
                    648: 	    $hash->{'conditions_'.$rid}=$sofar;
                    649: 	}
                    650: 
                    651: 	# if the expression is just the 0th condition keep it
                    652: 	# otherwise leave a pointer to this condition expression
                    653: 
                    654: 	$newsofar = ($sofar eq '0') ? $sofar : '_'.$rid;
                    655: 
                    656: 	# Recurse if the resource is a map:
                    657: 
                    658: 	if (defined($hash->{'is_map_'.$rid})) {
                    659: 	    if (defined($hash->{'map_start_'.$hash->{'src_'.$rid}})) {
1.14      raeburn   660:                 my $maphidden;
                    661:                 unless ($ignorehidden) {
                    662:                     $maphidden = $hdnflag || $hiddenurl{$rid};
                    663:                 }
1.1       foxr      664: 		$sofar=$newsofar=
                    665: 		    &traceroute($sofar,
                    666: 				$hash->{'map_start_'.$hash->{'src_'.$rid}},
                    667: 				$beenhere,
                    668: 				$encflag || $encurl{$rid},
1.14      raeburn   669: 				$maphidden, $hash);
1.1       foxr      670: 	    }
                    671: 	}
                    672: 
                    673: 	# Processes  links to this resource:
                    674: 	#  - verify the existence of any conditionals on the link to here.
                    675: 	#  - Recurse to any resources linked to us.
                    676: 	#
                    677: 	if (defined($hash->{'to_'.$rid})) {
                    678: 	    foreach my $id (split(/\,/,$hash->{'to_'.$rid})) {
                    679: 		my $further=$sofar;
                    680: 		#
                    681: 		# If there's a condition associated with this link be sure
                    682: 		# it's been defined else that's an error:
                    683: 		#
                    684:                 if ($hash->{'undercond_'.$id}) {
                    685: 		    if (defined($hash->{'condid_'.$hash->{'undercond_'.$id}})) {
                    686: 			$further=simplify('('.'_'.$rid.')&('.
                    687: 					  $hash->{'condid_'.$hash->{'undercond_'.$id}}.')');
                    688: 		    } else {
1.12      bisitz    689: 			my $errtext.='<br />'.&mt('Undefined condition ID: [_1]',$hash->{'undercond_'.$id});
1.1       foxr      690: 			throw Error::Simple($errtext);
                    691: 		    }
                    692:                 }
                    693: 		#  Recurse to resoruces that have to's to us.
                    694:                 $newsofar=&traceroute($further,$hash->{'goesto_'.$id},$beenhere,
1.3       foxr      695: 				      $encflag,$hdnflag, $hash);
1.1       foxr      696: 	    }
                    697: 	}
                    698:     }
                    699:     return $newsofar;
                    700: }
                    701: 
                    702: 
                    703: #---------------------------------------------------------------------------------
                    704: #
                    705: #  Map parsing code:
                    706: #
                    707: 
                    708: # 
                    709: #  Parse the <param> tag.  for most parameters, the only action is to define/extend
                    710: #  a has entry for {'param_{refid}'} where refid is the resource the parameter is
                    711: #  attached to and the value built up is an & separated list of parameters of the form:
                    712: #  type:part.name=value
                    713: #
                    714: #   In addition there is special case code for:
                    715: #   - randompick
                    716: #   - randompickseed
                    717: #   - randomorder
                    718: #
                    719: #   - encrypturl
                    720: #   - hiddenresource
                    721: #
                    722: # Parameters:
                    723: #    token - The token array from HTML::TokeParse  we mostly care about element [2]
                    724: #            which is a hash of attribute => values supplied in the tag
                    725: #            (remember this sub is only processing start tag tokens).
                    726: #    mno   - Map number.  This is used to qualify resource ids within a map
                    727: #            to make them unique course wide (a process known as uniquifaction).
                    728: #    hash  - Reference to the hash we are building.
                    729: #
                    730: sub parse_param {
                    731:     my ($token, $mno, $hash)  = @_;
                    732: 
                    733:     # Qualify the reference and name by the map number and part number.
                    734:     # if no explicit part number is supplied, 0 is the implicit part num.
                    735: 
                    736:     my $referid=$mno.'.'.$token->[2]->{'to'}; # Resource param applies to.
                    737:     my $name=$token->[2]->{'name'};	      # Name of parameter
                    738:     my $part;
                    739: 
                    740: 
                    741:     if ($name=~/^parameter_(.*)_/) { 
                    742: 	$part=$1;
                    743:     } else {
                    744: 	$part=0;
                    745:     }
                    746: 
                    747:     # Peel the parameter_ off the parameter name.
                    748: 
                    749:     $name=~s/^.*_([^_]*)$/$1/;
                    750: 
                    751:     # The value is:
                    752:     #   type.part.name.value
                    753: 
                    754:     my $newparam=
                    755: 	&escape($token->[2]->{'type'}).':'.
                    756: 	&escape($part.'.'.$name).'='.
                    757: 	&escape($token->[2]->{'value'});
                    758: 
                    759:     # The hash key is param_resourceid.
                    760:     # Multiple parameters for a single resource are & separated in the hash.
                    761: 
                    762: 
                    763:     if (defined($hash->{'param_'.$referid})) {
                    764: 	$hash->{'param_'.$referid}.='&'.$newparam;
                    765:     } else {
                    766: 	$hash->{'param_'.$referid}=''.$newparam;
                    767:     }
                    768:     #
                    769:     #  These parameters have to do with randomly selecting
                    770:     # resources, therefore a separate hash is also created to 
                    771:     # make it easy to locate them when actually computing the resource set later on
                    772:     # See the code conditionalized by ($randomize) in read_map().
                    773: 
                    774:     if ($token->[2]->{'name'}=~/^parameter_(0_)*randompick$/) { # Random selection turned on
                    775: 	$randompick{$referid}=$token->[2]->{'value'};
                    776:     }
                    777:     if ($token->[2]->{'name'}=~/^parameter_(0_)*randompickseed$/) { # Randomseed provided.
                    778: 	$randompickseed{$referid}=$token->[2]->{'value'};
                    779:     }
                    780:     if ($token->[2]->{'name'}=~/^parameter_(0_)*randomorder$/) { # Random order turned on.
                    781: 	$randomorder{$referid}=$token->[2]->{'value'};
                    782:     }
                    783: 
                    784:     # These parameters have to do with how the URLs of resources are presented to
                    785:     # course members(?).  encrypturl presents encypted url's while
                    786:     # hiddenresource hides the URL.
                    787:     #
                    788: 
                    789:     if ($token->[2]->{'name'}=~/^parameter_(0_)*encrypturl$/) {
                    790: 	if ($token->[2]->{'value'}=~/^yes$/i) {
                    791: 	    $encurl{$referid}=1;
                    792: 	}
                    793:     }
                    794:     if ($token->[2]->{'name'}=~/^parameter_(0_)*hiddenresource$/) {
                    795: 	if ($token->[2]->{'value'}=~/^yes$/i) {
                    796: 	    $hiddenurl{$referid}=1;
                    797: 	}
                    798:     }
                    799: }
                    800: 
                    801: 
                    802: #
                    803: #  Parses a resource tag to produce the value to push into the
                    804: #  map_ids array.
                    805: # 
                    806: #
                    807: #  Information about the actual type of resource is provided by the file extension
                    808: #  of the uri (e.g. .problem, .sequence etc. etc.).
                    809: #
                    810: #  Parameters:
                    811: #    $token   - A token from HTML::TokeParser
                    812: #               This is an array that describes the most recently parsed HTML item.
                    813: #    $lpc     - Map nesting level (?)
                    814: #    $ispage  - True if this resource is encapsulated in a .page (assembled resourcde).
                    815: #    $uri     - URI of the enclosing resource.
1.7       raeburn   816: #    $code    - CODE for which resource is being parsed (CODEd assignments).
1.1       foxr      817: #    $hash    - Reference to the hash we are building.
                    818: #
                    819: # Returns:
                    820: #   Value of the id attribute of the tag.
                    821: #
                    822: # Note:
                    823: #   The token is an array that contains the following elements:
                    824: #   [0]   => 'S' indicating this is a start token
                    825: #   [1]   => 'resource'  indicating this tag is a <resource> tag.
                    826: #   [2]   => Hash of attribute =>value pairs.
                    827: #   [3]   => @(keys [2]).
                    828: #   [4]   => unused.
                    829: #
                    830: #   The attributes of the resourcde tag include:
                    831: #
                    832: #   id     - The resource id.
                    833: #   src    - The URI of the resource.
                    834: #   type   - The resource type (e.g. start and finish).
                    835: #   title  - The resource title.
                    836: #
                    837: 
                    838: sub parse_resource {
1.7       raeburn   839:     my ($token,$lpc,$ispage,$uri,$code,$hash) = @_;
1.1       foxr      840:     
                    841:     # I refuse to countenance code like this that has 
                    842:     # such a dirty side effect (and forcing this sub to be called within a loop).
                    843:     #
                    844:     #  if ($token->[2]->{'type'} eq 'zombie') { next; }
                    845:     #
                    846:     #  The original code both returns _and_ skips to the next pass of the >caller's<
                    847:     #  loop, that's just dirty.
                    848:     #
                    849: 
                    850:     # Zombie resources don't produce anything useful.
                    851: 
                    852:     if ($token->[2]->{'type'} eq 'zombie') {
                    853: 	return undef;
                    854:     }
                    855: 
                    856:     my $rid=$lpc.'.'.$token->[2]->{'id'}; # Resource id in hash is levelcounter.id-in-xml.
                    857: 
                    858:     # Save the hash element type and title:
                    859: 	    
                    860:     $hash->{'kind_'.$rid}='res';
                    861:     $hash->{'title_'.$rid}=$token->[2]->{'title'};
                    862: 
                    863:     # Get the version free URI for the resource.
                    864:     # If a 'version' attribute was supplied, and this resource's version 
                    865:     # information has not yet been stored, store it.
                    866:     #
                    867: 
                    868: 
                    869:     my $turi=&versiontrack($token->[2]->{'src'});
                    870:     if ($token->[2]->{'version'}) {
                    871: 	unless ($hash->{'version_'.$turi}) {
                    872: 
                    873: 	    #Where does the value of $1 below come from?
                    874: 	    #$1 for the regexps in versiontrack should have gone out of scope.
                    875: 	    #
                    876: 	    # I think this may be dead code since versiontrack ought to set
                    877: 	    # this hash element(?).
                    878: 	    #
                    879: 	    $hash->{'version_'.$turi}=$1;
                    880: 	}
                    881:     }
                    882:     # Pull out the title and do entity substitution on &colon
                    883:     # Q: Why no other entity substitutions?
                    884: 
                    885:     my $title=$token->[2]->{'title'};
                    886:     $title=~s/\&colon\;/\:/gs;
                    887: 
                    888: 
                    889: 
                    890:     # I think the point of all this code is to construct a final
                    891:     # URI that apache and its rewrite rules can use to
                    892:     # fetch the resource.   Thi s sonly necessary if the resource
                    893:     # is not a page.  If the resource is a page then it must be
                    894:     # assembled (at fetch time?).
                    895: 
                    896:     unless ($ispage) {
                    897: 	$turi=~/\.(\w+)$/;
                    898: 	my $embstyle=&Apache::loncommon::fileembstyle($1);
                    899: 	if ($token->[2]->{'external'} eq 'true') { # external
                    900: 	    $turi=~s/^https?\:\/\//\/adm\/wrapper\/ext\//;
                    901: 	} elsif ($turi=~/^\/*uploaded\//) { # uploaded
                    902: 	    if (($embstyle eq 'img') 
                    903: 		|| ($embstyle eq 'emb')
                    904: 		|| ($embstyle eq 'wrp')) {
                    905: 		$turi='/adm/wrapper'.$turi;
                    906: 	    } elsif ($embstyle eq 'ssi') {
                    907: 		#do nothing with these
                    908: 	    } elsif ($turi!~/\.(sequence|page)$/) {
                    909: 		$turi='/adm/coursedocs/showdoc'.$turi;
                    910: 	    }
                    911: 	} elsif ($turi=~/\S/) { # normal non-empty internal resource
                    912: 	    my $mapdir=$uri;
                    913: 	    $mapdir=~s/[^\/]+$//;
                    914: 	    $turi=&Apache::lonnet::hreflocation($mapdir,$turi);
                    915: 	    if (($embstyle eq 'img') 
                    916: 		|| ($embstyle eq 'emb')
                    917: 		|| ($embstyle eq 'wrp')) {
                    918: 		$turi='/adm/wrapper'.$turi;
                    919: 	    }
                    920: 	}
                    921:     }
                    922:     # Store reverse lookup, remove query string resource 'ids'_uri => resource id.
                    923:     # If the URI appears more than one time in the sequence, it's resourcde
                    924:     # id's are constructed as a comma spearated list.
                    925: 
                    926:     my $idsuri=$turi;
                    927:     $idsuri=~s/\?.+$//;
                    928:     if (defined($hash->{'ids_'.$idsuri})) {
                    929: 	$hash->{'ids_'.$idsuri}.=','.$rid;
                    930:     } else {
                    931: 	$hash->{'ids_'.$idsuri}=''.$rid;
                    932:     }
                    933:     
                    934: 
                    935: 
                    936:     if ($turi=~/\/(syllabus|aboutme|navmaps|smppg|bulletinboard|viewclasslist)$/) {
                    937: 	$turi.='?register=1';
                    938:     }
                    939:     
                    940: 
                    941:     # resource id lookup:  'src'_resourc-di  => URI decorated with a query
                    942:     # parameter as above if necessary due to the resource type.
                    943:     
                    944:     $hash->{'src_'.$rid}=$turi;
                    945: 
                    946:     # Mark the external-ness of the resource:
                    947:     
                    948:     if ($token->[2]->{'external'} eq 'true') {
                    949: 	$hash->{'ext_'.$rid}='true:';
                    950:     } else {
                    951: 	$hash->{'ext_'.$rid}='false:';
                    952:     }
                    953: 
                    954:     # If the resource is a start/finish resource set those
                    955:     # entries in the has so that navigation knows where everything starts.
                    956:     #   If there is a malformed sequence that has no start or no finish
                    957:     # resource, should this be detected and errors thrown?  How would such a 
                    958:     # resource come into being other than being manually constructed by a person
                    959:     # and then uploaded?  Could that happen if an author decided a sequence was almost
                    960:     # right edited it by hand and then reuploaded it to 'fix it' but accidently cut the
                    961:     #  start or finish resources?
                    962:     #
                    963:     #  All resourcess also get a type_id => (start | finish | normal)    hash entr.
                    964:     #
                    965:     if ($token->[2]->{'type'}) {
                    966: 	$hash->{'type_'.$rid}=$token->[2]->{'type'};
                    967: 	if ($token->[2]->{'type'} eq 'start') {
                    968: 	    $hash->{'map_start_'.$uri}="$rid";
                    969: 	}
                    970: 	if ($token->[2]->{'type'} eq 'finish') {
                    971: 	    $hash->{'map_finish_'.$uri}="$rid";
                    972: 	}
                    973:     }  else {
                    974: 	$hash->{'type_'.$rid}='normal';
                    975:     }
                    976: 
                    977:     # Sequences end pages are constructed entities.  They require that the 
                    978:     # map that defines _them_ be loaded as well into the hash...with this resourcde
                    979:     # as the base of the nesting.
                    980:     # Resources like that are also marked with is_map_id => 1 entries.
                    981:     #
                    982:     
                    983:     if (($turi=~/\.sequence$/) ||
                    984: 	($turi=~/\.page$/)) {
1.3       foxr      985: 	$hash->{'is_map_'.$rid}='1'; # String in lonuserstate.
1.11      raeburn   986:         if ($hiddenurl{$rid}) {
                    987:             if (($hash->{'context.nohideurl'}) &&
                    988:                 ($hash->{'context.nohideurl'} eq $hash->{'src_'.$rid})) {
                    989:                 delete($hiddenurl{$rid}); # Hidden parameter explicitly deleted
                    990:                                           # if printing/grading bubblesheet exam  
                    991:             }
                    992:         }
                    993: 
1.7       raeburn   994: 	&read_map($turi,$rid,$code,$hash);
1.1       foxr      995:     } 
                    996:     return $token->[2]->{'id'};
                    997: }
                    998: 
                    999: #  Links define how you are allowed to move from one resource to another.
                   1000: #  They are the transition edges in the directed graph that a map is.
                   1001: #  This sub takes informatino from a <link> tag and constructs the
                   1002: #  navigation bits and pieces of a map.  There is no requirement that the
                   1003: #  resources that are linke are already defined, however clearly the map is 
                   1004: #  badly broken if they are not _eventually_ defined.
                   1005: #
                   1006: #  Note that links can be unconditional or conditional.
                   1007: #
                   1008: #  Parameters:
                   1009: #     linkpc   - The link counter for this level of map nesting (this is 
                   1010: #                reset to zero by read_map prior to starting to process
                   1011: #                links for map).
                   1012: #     lpc      - The map level ocounter (how deeply nested this map is in
                   1013: #                the hierarchy of maps that are recursively read in.
                   1014: #     to       - resource id (within the XML) of the target of the edge.
                   1015: #     from     - resource id (within the XML) of the source of the edge.
                   1016: #     condition- id of condition associated with the edge (also within the XML).
                   1017: #     hash     - reference to the hash we are building.
                   1018: 
                   1019: #
                   1020: 
                   1021: sub make_link {
                   1022:     my ($linkpc,$lpc,$to,$from,$condition, $hash) = @_;
                   1023:     
                   1024:     #  Compute fully qualified ids for the link, the 
                   1025:     # and from/to by prepending lpc.
                   1026:     #
                   1027: 
                   1028:     my $linkid=$lpc.'.'.$linkpc;
                   1029:     my $goesto=$lpc.'.'.$to;
                   1030:     my $comesfrom=$lpc.'.'.$from;
1.3       foxr     1031:     my $undercond='0';
1.1       foxr     1032: 
                   1033: 
                   1034:     # If there is a condition, qualify it with the level counter.
                   1035: 
                   1036:     if ($condition) {
                   1037: 	$undercond=$lpc.'.'.$condition;
                   1038:     }
                   1039: 
                   1040:     # Links are represnted by:
                   1041:     #  goesto_.fuullyqualifedlinkid => fully qualified to
                   1042:     #  comesfrom.fullyqualifiedlinkid => fully qualified from
                   1043:     #  undercond_.fullyqualifiedlinkid => fully qualified condition id.
                   1044: 
                   1045:     $hash->{'goesto_'.$linkid}=$goesto;
                   1046:     $hash->{'comesfrom_'.$linkid}=$comesfrom;
                   1047:     $hash->{'undercond_'.$linkid}=$undercond;
                   1048: 
                   1049:     # In addition:
                   1050:     #   to_.fully qualified from => comma separated list of 
                   1051:     #   link ids with that from.
                   1052:     # Similarly:
                   1053:     #   from_.fully qualified to => comma separated list of link ids`
                   1054:     #                               with that to.
                   1055:     #  That allows us given a resource id to know all edges that go to it
                   1056:     #  and leave from it.
                   1057:     #
                   1058: 
                   1059:     if (defined($hash->{'to_'.$comesfrom})) {
                   1060: 	$hash->{'to_'.$comesfrom}.=','.$linkid;
                   1061:     } else {
                   1062: 	$hash->{'to_'.$comesfrom}=''.$linkid;
                   1063:     }
                   1064:     if (defined($hash->{'from_'.$goesto})) {
                   1065: 	$hash->{'from_'.$goesto}.=','.$linkid;
                   1066:     } else {
                   1067: 	$hash->{'from_'.$goesto}=''.$linkid;
                   1068:     }
                   1069: }
                   1070: 
                   1071: # ------------------------------------------------------------------- Condition
                   1072: #
                   1073: #  Processes <condition> tags, storing sufficient information about them
                   1074: #  in the hash so that they can be evaluated and used to conditionalize
                   1075: #  what is presented to the student.
                   1076: #
                   1077: #  these can have the following attributes 
                   1078: #
                   1079: #    id    = A unique identifier of the condition within the map.
                   1080: #
                   1081: #    value = Is a perl script-let that, when evaluated in safe space
                   1082: #            determines whether or not the condition is true.
                   1083: #            Normally this takes the form of a test on an  Apache::lonnet::EXT call
                   1084: #            to find the value of variable associated with a resource in the
                   1085: #            map identified by a mapalias.
                   1086: #            Here's a fragment of XML code that illustrates this:
                   1087: #
                   1088: #           <param to="5" value="mainproblem" name="parameter_0_mapalias" type="string" />
                   1089: #           <resource src="" id="1" type="start" title="Start" />
                   1090: #           <resource src="/res/msu/albertel/b_and_c/p1.problem" id="5"  title="p1.problem" />
                   1091: #           <condition value="&EXT('user.resource.resource.0.tries','mainproblem')
                   1092: #           <2 " id="61" type="stop" />
                   1093: #           <link to="5" index="1" from="1" condition="61" />    
                   1094: #
                   1095: #           In this fragment:
                   1096: #             - The param tag establishes an alias to resource id 5 of 'mainproblem'.
                   1097: #             - The resource that is the start of the map is identified.
                   1098: #             - The resource tag identifies the resource associated with this tag
                   1099: #               and gives it the id 5.
                   1100: #             - The condition is true if the tries variable associated with mainproblem
                   1101: #               is less than 2 (that is the user has had more than 2 tries).
                   1102: #               The condition type is a stop condition which inhibits(?) the associated
                   1103: #               link if the condition  is false. 
                   1104: #             - The link to resource 5 from resource 1 is affected by this condition.    
                   1105: #            
                   1106: #    type  = Type of the condition. The type determines how the condition affects the
                   1107: #            link associated with it and is one of
                   1108: #            -  'force'
                   1109: #            -  'stop'
                   1110: #              anything else including not supplied..which treated as:
                   1111: #            - 'normal'.
                   1112: #            Presumably maps get created by the resource assembly tool and therefore
                   1113: #            illegal type values won't squirm their way into the XML.
                   1114: #   hash   - Reference to the hash we are trying to build up.
                   1115: #
                   1116: # Side effects:
                   1117: #   -  The kind_level-qualified-condition-id hash element is set to 'cond'.
                   1118: #   -  The condition text is pushed into the cond array and its element number is
                   1119: #      set in the condid_level-qualified-condition-id element of the hash.
                   1120: #   - The condition type is colon appneded to the cond array element for this condition.
                   1121: sub parse_condition {
                   1122:     my ($token, $lpc, $hash) = @_;
                   1123:     my $rid=$lpc.'.'.$token->[2]->{'id'};
                   1124:     
                   1125:     $hash->{'kind_'.$rid}='cond';
                   1126: 
                   1127:     my $condition = $token->[2]->{'value'};
                   1128:     $condition =~ s/[\n\r]+/ /gs;
                   1129:     push(@cond, $condition);
                   1130:     $hash->{'condid_'.$rid}=$#cond;
                   1131:     if ($token->[2]->{'type'}) {
                   1132: 	$cond[$#cond].=':'.$token->[2]->{'type'};
                   1133:     }  else {
                   1134: 	$cond[$#cond].=':normal';
                   1135:     }
                   1136: }
                   1137: 
                   1138: #
                   1139: #  Parse mapalias parameters.
                   1140: #  these are tags of the form:
                   1141: #  <param to="nn" 
                   1142: #         value="some-alias-for-resourceid-nn" 
                   1143: #         name="parameter_0_mapalias" 
                   1144: #         type="string" />
                   1145: #  A map alias is a textual name for a resource:
                   1146: #    - The to  attribute identifies the resource (this gets level qualified below)
                   1147: #    - The value attributes provides the alias string.
                   1148: #    - name must be of the regexp form: /^parameter_(0_)*mapalias$/
                   1149: #    - e.g. the string 'parameter_' followed by 0 or more "0_" strings
                   1150: #      terminating with the string 'mapalias'.
                   1151: #      Examples:
                   1152: #         'parameter_mapalias', 'parameter_0_mapalias', parameter_0_0_mapalias'
                   1153: #  Invalid to ids are silently ignored.
                   1154: #
                   1155: #  Parameters:
                   1156: #     token - The token array fromthe HMTML::TokeParser
                   1157: #     lpc   - The current map level counter.
                   1158: #     hash  - Reference to the hash that we are building.
                   1159: #
                   1160: sub parse_mapalias_param {
                   1161:     my ($token, $lpc, $hash) = @_;
                   1162: 
                   1163:     # Fully qualify the to value and ignore the alias if there is no
                   1164:     # corresponding resource.
                   1165: 
                   1166:     my $referid=$lpc.'.'.$token->[2]->{'to'};
                   1167:     return if (!exists($hash->{'src_'.$referid}));
                   1168: 
                   1169:     # If this is a valid mapalias parameter, 
                   1170:     # Append the target id to the count_mapalias element for that
                   1171:     # alias so that we can detect doubly defined aliases
                   1172:     # e.g.:
                   1173:     #  <param to="1" value="george" name="parameter_0_mapalias" type="string" />
                   1174:     #  <param to="2" value="george" name="parameter_0_mapalias" type="string" />
                   1175:     #
                   1176:     #  The example above is trivial but the case that's important has to do with
                   1177:     #  constructing a map that includes a nested map where the nested map may have
                   1178:     #  aliases that conflict with aliases established in the enclosing map.
                   1179:     #
                   1180:     # ...and create/update the hash mapalias entry to actually store the alias.
                   1181:     #
                   1182: 
                   1183:     if ($token->[2]->{'name'}=~/^parameter_(0_)*mapalias$/) {
                   1184: 	&count_mapalias($token->[2]->{'value'},$referid);
                   1185: 	$hash->{'mapalias_'.$token->[2]->{'value'}}=$referid;
                   1186:     }
                   1187: }
                   1188: 
                   1189: 
                   1190: #---------------------------------------------------------------------------------
                   1191: #
                   1192: #  Code to process the map file.
                   1193: 
                   1194: #  read a map file and add it to the hash.  Since a course map can contain resources
                   1195: #  that are themselves maps, read_map might be recursively called.
                   1196: #
                   1197: # Parameters:
                   1198: #   $uri         - URI of the course itself (not the map file).
                   1199: #   $parent_rid  - map number qualified id of the parent of the map being read.
                   1200: #                  For the top level course map this is 0.0.  For the first nested
                   1201: #                  map 1.n  where n is the id of the resource within the
1.7       raeburn  1202: #                  top level map and so on.
                   1203: #   $code        - CODE for which map is being read (CODEd assignments).
1.1       foxr     1204: #   $hash        - Reference to a hash that will become the big hash for the course
                   1205: #                  This hash is modified as per the map description.
                   1206: # Side-effects:
                   1207: #   $map_number - Will be  incremented.   This keeps track of the number of the map
                   1208: #                 we are currently working on (see parent_rid above, the number to the
                   1209: #                 left of the . in $parent_rid is the map number).
                   1210: #
                   1211: #  
                   1212: sub read_map {
1.7       raeburn  1213:     my ($uri, $parent_rid, $code, $hash) = @_;
1.1       foxr     1214: 
1.3       foxr     1215: 
1.1       foxr     1216:     # Check for duplication: A map may only be included once.
                   1217: 
                   1218:     if($hash->{'map_pc_' . $uri}) {
1.2       foxr     1219: 	throw Error::Simple('Duplicate map: ', $uri);
1.1       foxr     1220:     }
                   1221:     # count the map number and save it locally so that we don't lose it
                   1222:     # when we recurse.
                   1223: 
                   1224:     $map_number++;
                   1225:     my $lmap_no = $map_number;
                   1226: 
                   1227:     # save the map_pc and map_id elements of the hash for this map:
                   1228:     #  map_pc_uri is the map number of the map with that URI.
                   1229:     #  map_id_$lmap_no is the URI for this map level.
                   1230:     #
1.3       foxr     1231:     $hash->{'map_pc_' . $uri}     = "$lmap_no"; # string form in lonuserstate.
                   1232:     $hash->{'map_id_' . $lmap_no} = "$uri";
1.1       foxr     1233: 
                   1234:     # Create the path up to the top of the course.
                   1235:     # this is in 'map_hierarchy_mapno'  that's a comma separated path down to us
                   1236:     # in the hierarchy:
                   1237: 
                   1238:     if ($parent_rid =~/^(\d+).\d+$/) { 
                   1239: 	my $parent_no = $1;	       # Parent's map number.
                   1240: 	if (defined($hash->{'map_hierarchy_' . $parent_no})) {
                   1241: 	    $hash->{'map_hierarchy_' . $lmap_no} =
1.2       foxr     1242: 		$hash->{'map_hierarchy_' . $parent_no} . ',' . $parent_no;
1.1       foxr     1243: 	} else {
                   1244: 	    # Only 1 level deep ..nothing to append to:
                   1245: 
                   1246: 	    $hash->{'map_hierarchy_' . $lmap_no} = $parent_no;
                   1247: 	}
                   1248:     }
                   1249: 
                   1250:     # figure out the name of the map file we need to read.
                   1251:     # ensure that it is a .page or a .sequence as those are the only 
                   1252:     # sorts of files that make sense for this sub 
                   1253: 
                   1254:     my $filename = &Apache::lonnet::filelocation('', &append_version($uri, $hash));
1.3       foxr     1255: 
                   1256: 
1.1       foxr     1257:     my $ispage = ($filename =~/\.page$/);
1.2       foxr     1258:     unless ($ispage || ($filename =~ /\.sequence$/)) {
1.6       foxr     1259: 	&Apache::lonnet::logthis("invalid: $filename : $uri");
1.12      bisitz   1260: 	throw Error::Simple('<br />'.&mt('Invalid map: [_1]','<span class="LC_filename">'.$filename.'</span>'));
1.1       foxr     1261:     }
                   1262: 
                   1263:     $filename =~ /\.(\w+)$/;
                   1264: 
1.2       foxr     1265:     $hash->{'map_type_'.$lmap_no}=$1;
1.1       foxr     1266: 
                   1267:     # Repcopy the file and get its contents...report errors if we can't
                   1268:    
1.3       foxr     1269:     my $contents = &Apache::lonnet::getfile($filename);
1.1       foxr     1270:     if($contents eq -1) {
1.16    ! raeburn  1271:         $hash->{'map_type_'.$lmap_no} = 'none';
1.12      bisitz   1272:         throw Error::Simple('<br />'.&mt('Map not loaded: The file [_1] does not exist.',
                   1273: 				'<span class="LC_filename">'.$filename.'</span>'));
1.1       foxr     1274:     }
                   1275:     # Now that we succesfully retrieved the file we can make our parsing passes over it:
                   1276:     # parsing is done in passes:
                   1277:     # 1. Parameters are parsed.
                   1278:     # 2. Resource, links and conditions are parsed.
                   1279:     #
                   1280:     # post processing takes care of the case where the sequence is random ordered
                   1281:     # or randomselected.
                   1282: 
                   1283:     # Parse the parameters,  This pass only cares about start tags for <param>
                   1284:     # tags.. this is because there is no body to a <param> tag.
                   1285:     #
                   1286: 
1.2       foxr     1287:     my $parser  = HTML::TokeParser->new(\$contents);
1.1       foxr     1288:     $parser->attr_encoded(1);	# Don't interpret entities in attributes (leave &xyz; alone).
                   1289: 
                   1290:     while (my $token = $parser->get_token()) {
                   1291: 	if (($token->[0] eq 'S') && ($token->[1] eq 'param')) { 
                   1292: 	    &parse_param($token, $map_number, $hash);
                   1293: 	}
                   1294:     }
                   1295: 
                   1296:     # ready for pass 2: Resource links and conditions.
                   1297:     # Note that if the map is random-ordered link tags are computed by randomizing
                   1298:     # resource order.  Furthermore, since conditions are set on links rather than
                   1299:     # resources, they are also not processed if random order is turned on.
                   1300:     #
                   1301: 
1.2       foxr     1302:     $parser = HTML::TokeParser->new(\$contents); # no way to reset the existing parser
1.1       foxr     1303:     $parser->attr_encoded(1);
                   1304: 
                   1305:     my $linkpc=0;
                   1306:     my $randomize = ($randomorder{$parent_rid} =~ /^yes$/i);
                   1307: 
                   1308:     my @map_ids;
                   1309:     while (my $token = $parser->get_token) {
                   1310: 	next if ($token->[0] ne 'S');
                   1311: 
                   1312: 	# Resource
                   1313: 
                   1314: 	if ($token->[1] eq 'resource') {
1.7       raeburn  1315: 	    my $resource_id = &parse_resource($token,$lmap_no,$ispage,$uri,$code,$hash);
1.1       foxr     1316: 	    if (defined $resource_id) {
                   1317: 		push(@map_ids, $resource_id); 
                   1318: 	    }
                   1319: 
                   1320:        # Link
                   1321: 
                   1322: 	} elsif ($token->[1] eq 'link' && !$randomize) {
1.2       foxr     1323: 	    &make_link(++$linkpc,$lmap_no,$token->[2]->{'to'},
1.1       foxr     1324: 		       $token->[2]->{'from'},
                   1325: 		       $token->[2]->{'condition'}, $hash); # note ..condition may be undefined.
                   1326: 
                   1327: 	# condition
                   1328: 
                   1329: 	} elsif ($token->[1] eq 'condition' && !$randomize) {
1.2       foxr     1330: 	    &parse_condition($token,$lmap_no, $hash);
1.1       foxr     1331: 	}
                   1332:     }
                   1333: 
                   1334:     #  This section handles random ordering by permuting the 
                   1335:     # IDs of the map according to the user's random seed.
                   1336:     # 
                   1337: 
                   1338:     if ($randomize) {
1.7       raeburn  1339: 	if (!&has_advanced_role($username, $userdomain) || $code) {
1.1       foxr     1340: 	    my $seed;
                   1341: 
                   1342: 	    # In the advanced role, the map's random seed
                   1343: 	    # parameter is used as the basis for computing the
                   1344: 	    # seed ... if it has been specified:
                   1345: 
                   1346: 	    if (defined($randompickseed{$parent_rid})) {
                   1347: 		$seed = $randompickseed{$parent_rid};
                   1348: 	    } else {
                   1349: 
                   1350: 		# Otherwise the parent's fully encoded symb is used.
                   1351: 
                   1352: 		my ($mapid,$resid)=split(/\./,$parent_rid);
                   1353: 		my $symb=
                   1354: 		    &Apache::lonnet::encode_symb($hash->{'map_id_'.$mapid},
                   1355: 						 $resid,$hash->{'src_'.$parent_rid});
                   1356: 		
                   1357: 		$seed = $symb;
                   1358: 	    }
                   1359: 
                   1360: 
1.6       foxr     1361: 	    my $rndseed=&Apache::lonnet::rndseed($seed, '', 
                   1362: 						 $userdomain, $username,
                   1363: 						 \%cenv);
                   1364: 						 
                   1365: 
1.1       foxr     1366: 	    &Apache::lonnet::setup_random_from_rndseed($rndseed);
                   1367: 
                   1368: 	    # Take the set of map ids we have decoded and permute them to a
                   1369: 	    # random order based on the seed set above. All of this is
                   1370: 	    # processing the randomorder parameter if it is set, not
                   1371: 	    # randompick.
                   1372: 
1.6       foxr     1373: 	    @map_ids=&Math::Random::random_permutation(@map_ids); 
1.1       foxr     1374: 	}
                   1375: 	my $from = shift(@map_ids);
1.2       foxr     1376: 	my $from_rid = $lmap_no.'.'.$from;
1.1       foxr     1377: 	$hash->{'map_start_'.$uri} = $from_rid;
                   1378: 	$hash->{'type_'.$from_rid}='start';
                   1379: 
                   1380: 	# Create links to reflect the random re-ordering done above.
                   1381: 	# In the code to process the map XML, we did not process links or conditions
                   1382: 	# if randomorder was set.  This means that for an instructor to choose
                   1383: 
                   1384: 	while (my $to = shift(@map_ids)) {
1.3       foxr     1385: 	    &make_link(++$linkpc,$lmap_no,$to,$from, 0, $hash);
1.2       foxr     1386: 	    my $to_rid =  $lmap_no.'.'.$to;
1.1       foxr     1387: 	    $hash->{'type_'.$to_rid}='normal';
                   1388: 	    $from = $to;
                   1389: 	    $from_rid = $to_rid;
                   1390: 	}
                   1391: 
                   1392: 	$hash->{'map_finish_'.$uri}= $from_rid;
                   1393: 	$hash->{'type_'.$from_rid}='finish';
                   1394:     }
                   1395: 
1.6       foxr     1396: 
1.1       foxr     1397:     #  The last parsing pass parses the <mapalias> tags that associate a name
                   1398:     #  with resource ids.
                   1399: 
                   1400:     $parser = HTML::TokeParser->new(\$contents);
                   1401:     $parser->attr_encoded(1);
                   1402: 
                   1403:     while (my $token = $parser->get_token) {
                   1404: 	next if ($token->[0] ne 'S');
                   1405: 	if ($token->[1] eq 'param') {
1.2       foxr     1406: 	    &parse_mapalias_param($token,$lmap_no, $hash);  
1.1       foxr     1407: 	} 
                   1408:     }
                   1409: 
                   1410: }
                   1411: 
                   1412: 
                   1413: #
                   1414: #  Load a map from file into a target hash.  This is done by first parsing the 
                   1415: #  map file into local hashes and then unrolling those hashes into the big hash.
                   1416: # 
                   1417: # Parameters:
                   1418: #
                   1419: #    $cnum       - number of course being read.
                   1420: #    $cdom       - Domain in which the course is evaluated.
                   1421: #    $uname      - Name of the user for whom the course is being read
                   1422: #    $udom       - Name of the domain of the user for whom the course is being read.
1.7       raeburn  1423: #    $code       - CODE for which course is being read (CODEd assignments)
1.11      raeburn  1424: #    $nohideurl  - URL for an exam folder for which hidden state is to be ignored.
1.1       foxr     1425: #    $target_hash- Reference to the target hash into which all of this is read.
                   1426: #                  Note tht some of the hash entries we need to build require knowledge of the
                   1427: #                  course URI.. these are expected to be filled in by the caller.
                   1428: #
                   1429: # Errors are logged to lonnet and are managed via the Perl structured exception package.
                   1430: #
                   1431: #  
                   1432: sub loadmap {
1.11      raeburn  1433:     my ($cnum, $cdom, $uname, $udom, $code, $nohideurl, $target_hash) = @_;
1.3       foxr     1434: 
                   1435: 
1.1       foxr     1436: 
1.8       raeburn  1437:     # Clear the auxiliary hashes and the cond array.
1.1       foxr     1438: 
                   1439: 
                   1440:     %randompick     = ();
                   1441:     %randompickseed = ();
                   1442:     %encurl         = ();
                   1443:     %hiddenurl      = ();
1.2       foxr     1444:     %parmhash       = ();
1.4       foxr     1445:     @cond           = ('true:normal'); # Initial value for cond 0.
1.2       foxr     1446:     $retfrid        = '';
1.3       foxr     1447:     $username       = '';
                   1448:     $userdomain     = '';
                   1449:     %mapalias_cache = ();
                   1450:     %cenv           = ();
1.10      raeburn  1451:     $map_number     =  0;
1.11      raeburn  1452:     
1.1       foxr     1453:     # 
                   1454: 
                   1455:     $username   = $uname;
                   1456:     $userdomain = $udom;
                   1457: 
1.6       foxr     1458:     $short_name = $cdom .'/' . $cnum;
1.3       foxr     1459:     my $retfurl;
1.1       foxr     1460: 
                   1461:     try {
                   1462: 
                   1463: 	
                   1464: 	# Get the information we need about the course.
1.6       foxr     1465:  	# Return without filling in anything if we can't get any info:
                   1466:  	
                   1467:  	%cenv = &Apache::lonnet::coursedescription($short_name,
                   1468:  						     {'freshen_cache' => 1,
                   1469:  						      'user'          => $uname}); 
                   1470:  
1.10      raeburn  1471:  	unless ($cenv{'url'}) {
1.6       foxr     1472:  	    &Apache::lonnet::logthis("lonmap::loadmap failed: $cnum/$cdom - did not get url");
                   1473:  	    return; 
                   1474:  	}
                   1475:  
                   1476:  	$course_id = $cdom . '_' . $cnum; # Long course id.
                   1477:  
                   1478:  	# Load the version information into the hash
                   1479:  
                   1480:  	
1.1       foxr     1481: 	&process_versions(\%cenv, $target_hash);
                   1482: 	
                   1483: 	
                   1484: 	# Figure out the map filename's URI, and set up some starting points for the map.
                   1485: 	
1.2       foxr     1486: 	my $course_uri = $cenv{'url'};
                   1487: 	my $map_uri    = &Apache::lonnet::clutter($course_uri);
1.1       foxr     1488: 	
                   1489: 	$target_hash->{'src_0.0'}            = &versiontrack($map_uri, $target_hash); 
                   1490: 	$target_hash->{'title_0.0'}          = &Apache::lonnet::metadata($course_uri, 'title');
1.3       foxr     1491: 	if(!defined $target_hash->{'title_0.0'}) {
                   1492: 	    $target_hash->{'title_0.0'} = '';
                   1493: 	}
1.2       foxr     1494: 	$target_hash->{'ids_'.$map_uri} = '0.0';
1.3       foxr     1495: 	$target_hash->{'is_map_0.0'}         = '1';
                   1496: 
                   1497: 	# In some places we need a username a domain and the courseid...store that
                   1498: 	# in the target hash in the context.xxxx keys:
                   1499: 
                   1500: 	$target_hash->{'context.username'} = $username;
                   1501: 	$target_hash->{'context.userdom'}  = $userdomain;
                   1502: 	$target_hash->{'context.courseid'} = $course_id;
1.11      raeburn  1503:  
                   1504:         # When grading or printing a bubblesheet exam ignore
                   1505:         # "hidden" parameter set in the map containing the exam folder.
                   1506:         $target_hash->{'context.nohideurl'} = $nohideurl;
1.6       foxr     1507: 
1.7       raeburn  1508:         &read_map($course_uri, '0.0', $code, $target_hash);
1.1       foxr     1509: 
1.2       foxr     1510: 	if (defined($target_hash->{'map_start_'.$map_uri})) {
1.1       foxr     1511: 
1.3       foxr     1512: 	    &traceroute('0',$target_hash->{'map_start_'.$course_uri},'&', 0, 0, $target_hash);
                   1513: 	    &accinit($course_uri, $short_name,  $target_hash);
1.2       foxr     1514: 	    &hiddenurls($target_hash);
                   1515: 	}
                   1516: 	my $errors = &get_mapalias_errors($target_hash);
                   1517: 	if ($errors ne "") {
                   1518: 	    throw Error::Simple("Map alias errors: ", $errors);
                   1519: 	}
                   1520: 
                   1521: 	# Put the versions in to src:
                   1522: 
                   1523: 	foreach my $key (keys(%$target_hash)) {
                   1524: 	    if ($key =~ /^src_/) {
                   1525: 		$target_hash->{$key} = 
                   1526: 		    &putinversion($target_hash->{$key}, $target_hash, $short_name);
                   1527: 	    } elsif ($key =~ /^(map_(?:start|finish|pc)_)(.*)/) {
                   1528: 		my ($type, $url) = ($1,$2);
                   1529: 		my $value = $target_hash->{$key};
                   1530: 		$target_hash->{$type.&putinversion($url, $target_hash, $short_name)}=$value;
                   1531: 	    }
1.1       foxr     1532: 	}
1.3       foxr     1533: 	#  Mark necrypted URLS.
                   1534: 
                   1535: 	foreach my $id (keys(%encurl)) {
                   1536: 	    $target_hash->{'encrypted_'.$id}=1;
                   1537: 	}
                   1538: 
                   1539: 	# Store first keys.
                   1540: 
                   1541: 	$target_hash->{'first_rid'}=$retfrid;
                   1542: 	my ($mapid,$resid)=split(/\./,$retfrid);
                   1543: 	$target_hash->{'first_mapurl'}=$target_hash->{'map_id_'.$mapid};
                   1544: 	my $symb=&Apache::lonnet::encode_symb($target_hash->{'map_id_'.$mapid},
                   1545: 					      $resid,
                   1546: 					      $target_hash->{'src_'.$retfrid});
                   1547: 	$retfurl=&add_get_param($target_hash->{'src_'.$retfrid},{ 'symb' => $symb });
                   1548: 	if ($target_hash->{'encrypted_'.$retfrid}) {
                   1549: 	    $retfurl=&Apache::lonenc::encrypted($retfurl,
                   1550: 						(&Apache::lonnet::allowed('adv') ne 'F'));
                   1551: 	}
                   1552: 	$target_hash->{'first_url'}=$retfurl;	
1.1       foxr     1553: 
                   1554: 	# Merge in the child hashes in case the caller wants that information as well.
                   1555: 
                   1556: 
1.2       foxr     1557: 	&merge_hash($target_hash, 'randompick', \%randompick);
1.10      raeburn  1558: 	&merge_hash($target_hash, 'randompickseed', \%randompickseed);
1.2       foxr     1559: 	&merge_hash($target_hash, 'randomorder', \%randomorder);
                   1560: 	&merge_hash($target_hash, 'encurl', \%encurl);
                   1561: 	&merge_hash($target_hash, 'hiddenurl', \%hiddenurl);
                   1562: 	&merge_hash($target_hash, 'param', \%parmhash);
                   1563: 	&merge_conditions($target_hash);
1.1       foxr     1564:     }
                   1565:     otherwise {
                   1566: 	my $e = shift;
                   1567: 	&Apache::lonnet::logthis("lonmap::loadmap failed: " . $e->stringify());
                   1568:     }
                   1569: 
                   1570: }
                   1571: 
                   1572: 
                   1573: 1;
                   1574: 
                   1575: #
                   1576: #  Module initialization code:
1.3       foxr     1577: #  TODO:  Fix the pod docs below.
1.1       foxr     1578: 
                   1579: 1;
                   1580: __END__
                   1581: 
                   1582: =head1 NAME
                   1583: 
                   1584: Apache::lonmap - Construct a hash that represents a course (Big Hash).
                   1585: 
                   1586: =head1 SYNOPSIS
                   1587: 
1.11      raeburn  1588: &Apache::lonmap::loadmap($cnum, $cdom, $uname, $udom, $code, $nohideurl, \%target_hash);
1.1       foxr     1589: 
                   1590: =head1 INTRODUCTION
                   1591: 
                   1592: This module reads a course filename into a hash reference.  It's up to the caller
1.11      raeburn  1593: to do things like decide that the hash should be tied to some external file and handle the
                   1594: the locking if this file should be shared amongst several Apache children.
1.1       foxr     1595: 
                   1596: =head1 SUBROUTINES
                   1597: 
                   1598: =over
                   1599: 
1.11      raeburn  1600: =item loadmap($cnum, $cdom, $uname, $udom, $code, $nohideurl, $targethash)
1.1       foxr     1601: 
                   1602: 
1.11      raeburn  1603: Reads the top-level map file into a target hash. This is done by first parsing the
                   1604: map file into local hashes and then unrolling those hashes into the big hash.
1.1       foxr     1605: 
                   1606: =over
                   1607: 
1.11      raeburn  1608: =item $cnum - number of course being read.
                   1609: 
                   1610: =item $cdom - domain in which the course is evaluated.
                   1611: 
                   1612: =item $uname - name of the user for whom the course is being read.
                   1613: 
                   1614: =item $udom  - name of the domain of the user for whom the course is being read.
                   1615: 
                   1616: =item $code  - CODE for which course is being read (CODEd assignments).
                   1617: 
                   1618: =item $nohideurl - URL for an exam folder for which hidden state is to be ignored.
1.1       foxr     1619: 
1.11      raeburn  1620: =item $targethash - A reference to hash into which the course is read
1.1       foxr     1621: 
                   1622: =back
                   1623: 
                   1624: =item process_versions($cenv, $hash)
                   1625: 
                   1626: Makes hash entries for each version of a course described by a course environment
                   1627: returned from Apache::lonnet::coursedescription.
                   1628: 
                   1629: =over
                   1630: 
                   1631: =item $cenv - Reference to the environment hash returned by Apache::lonnet::coursedescription
                   1632: 
                   1633: =item $hash - Hash to be filled in with 'version_xxx' entries as per the big hash.
                   1634: 
                   1635: =back
                   1636: 
                   1637: =back
                   1638:  
                   1639: 
                   1640: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
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.