File:  [LON-CAPA] / rat / lonuserstate.pm
Revision 1.163: download - view: text, annotated - select for diffs
Thu Apr 29 14:54:36 2021 UTC (3 years, 1 month ago) by raeburn
Branches: MAIN
CVS tags: HEAD
- Typo in documentation.

    1: # The LearningOnline Network with CAPA
    2: # Construct and maintain state and binary representation of course for user
    3: #
    4: # $Id: lonuserstate.pm,v 1.163 2021/04/29 14:54:36 raeburn Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: ###
   29: 
   30: package Apache::lonuserstate;
   31: 
   32: # ------------------------------------------------- modules used by this module
   33: use strict;
   34: use HTML::TokeParser;
   35: use Apache::lonnet;
   36: use Apache::lonlocal;
   37: use Apache::loncommon();
   38: use GDBM_File;
   39: use Apache::lonmsg;
   40: use Safe;
   41: use Safe::Hole;
   42: use Opcode;
   43: use Apache::lonenc;
   44: use Fcntl qw(:flock);
   45: use LONCAPA qw(:DEFAULT :match);
   46: use File::Basename;
   47: 
   48:  
   49: 
   50: # ---------------------------------------------------- Globals for this package
   51: 
   52: my $pc;      # Package counter is this what 'Guts' calls the map counter?
   53: my %hash;    # The big tied hash
   54: my %parmhash;# The hash with the parameters
   55: my @cond;    # Array with all of the conditions
   56: my $errtext; # variable with all errors
   57: my $retfrid; # variable with the very first RID in the course
   58: my $retfurl; # first URL
   59: my %randompick; # randomly picked resources
   60: my %randompickseed; # optional seed for randomly picking resources
   61: my %randomorder; # maps to order contents randomly
   62: my %randomizationcode; # code used to grade folder for bubblesheet exam 
   63: my %encurl; # URLs in this folder are supposed to be encrypted
   64: my %hiddenurl; # this URL (or complete folder) is supposed to be hidden
   65: my %deeplinkonly; # this URL (or complete folder) is deep-link only
   66: my %rescount; # count of unhidden items in each map
   67: my %mapcount; # count of unhidden maps in each map
   68: 
   69: # ----------------------------------- Remove version from URL and store in hash
   70: 
   71: sub versionerror {
   72:     my ($uri,$usedversion,$unusedversion)=@_;
   73:     return '<br />'.&mt('Version discrepancy: resource [_1] included in both version [_2] and version [_3]. Using version [_2].',
   74:                     $uri,$usedversion,$unusedversion).'<br />';
   75: }
   76: 
   77: #  Removes the version number from a URI and returns the resulting
   78: #  URI (e.g. mumbly.version.stuff => mumbly.stuff).
   79: #
   80: #   If the URI has not been seen with a versio before the
   81: #   hash{'version_'.resultingURI} is set to the  version number.
   82: #   If the URI has been seen and the version does not match and error
   83: #   is added to the error string.
   84: #
   85: # Parameters:
   86: #   URI potentially with a version.
   87: # Returns:
   88: #   URI with the version cut out.
   89: # See above for side effects.
   90: #
   91: 
   92: sub versiontrack {
   93:     my $uri=shift;
   94:     if ($uri=~/\.(\d+)\.\w+$/) {
   95: 	my $version=$1;
   96: 	$uri=~s/\.\d+\.(\w+)$/\.$1/;
   97:         unless ($hash{'version_'.$uri}) {
   98: 	    $hash{'version_'.$uri}=$version;
   99: 	} elsif ($version!=$hash{'version_'.$uri}) {
  100:             $errtext.=&versionerror($uri,$hash{'version_'.$uri},$version);
  101:         }
  102:     }
  103:     return $uri;
  104: }
  105: 
  106: # -------------------------------------------------------------- Put in version
  107: 
  108: sub putinversion {
  109:     my $uri=shift;
  110:     my $key=$env{'request.course.id'}.'_'.&Apache::lonnet::clutter($uri);
  111:     if ($hash{'version_'.$uri}) {
  112: 	my $version=$hash{'version_'.$uri};
  113: 	if ($version eq 'mostrecent') { return $uri; }
  114: 	if ($version eq &Apache::lonnet::getversion(
  115: 			&Apache::lonnet::filelocation('',$uri))) 
  116: 	             { return $uri; }
  117: 	$uri=~s/\.(\w+)$/\.$version\.$1/;
  118:     }
  119:     &Apache::lonnet::do_cache_new('courseresversion',$key,&Apache::lonnet::declutter($uri),600);
  120:     return $uri;
  121: }
  122: 
  123: # ----------------------------------------- Processing versions file for course
  124: 
  125: sub processversionfile {
  126:     my %cenv=@_;
  127:     my %versions=&Apache::lonnet::dump('resourceversions',
  128: 				       $cenv{'domain'},
  129: 				       $cenv{'num'});
  130:     foreach my $ver (keys(%versions)) {
  131: 	if ($ver=~/^error\:/) { return; }
  132: 	$hash{'version_'.$ver}=$versions{$ver};
  133:     }
  134: }
  135: 
  136: # --------------------------------------------------------- Loads from disk
  137: 
  138: 
  139: #
  140: #  Loads a map file.
  141: #  Note that this may implicitly recurse via parse_resource if one of the resources
  142: #  is itself composed.
  143: #
  144: # Parameters:
  145: #    uri         - URI of the map file.
  146: #    parent_rid  - Resource id in the map of the parent resource (0.0 for the top level map)
  147: #    courseid    - Course id for the course for which the map is being loaded
  148: #
  149: sub loadmap { 
  150:     my ($uri,$parent_rid,$courseid)=@_;
  151: 
  152:     # Is the map already included?
  153: 
  154:     if ($hash{'map_pc_'.$uri}) { 
  155: 	$errtext.='<p class="LC_error">'.
  156: 	    &mt('Multiple use of sequence/page [_1]! The course will not function properly.','<tt>'.$uri.'</tt>').
  157: 	    '</p>';
  158: 	return; 
  159:     }
  160:     # Register the resource in it's map_pc_ [for the URL]
  161:     # map_id.nnn is the nesting level -> to the URI.
  162: 
  163:     $pc++;
  164:     my $lpc=$pc;
  165:     $hash{'map_pc_'.$uri}=$lpc;
  166:     $hash{'map_id_'.$lpc}=$uri;
  167: 
  168:     # If the parent is of the form n.m hang this map underneath it in the
  169:     # map hierarchy.
  170: 
  171:     if ($parent_rid =~ /^(\d+)\.\d+$/) {
  172:         my $parent_pc = $1;
  173:         if (defined($hash{'map_hierarchy_'.$parent_pc})) {
  174:             $hash{'map_hierarchy_'.$lpc}=$hash{'map_hierarchy_'.$parent_pc}.','.
  175:                                          $parent_pc;
  176:         } else {
  177:             $hash{'map_hierarchy_'.$lpc}=$parent_pc;
  178:         }
  179:     }
  180: 
  181: # Determine and check filename of the sequence we need to read:
  182: 
  183:     my $fn=&Apache::lonnet::filelocation('',&putinversion($uri));
  184: 
  185:     my $ispage=($fn=~/\.page$/);
  186: 
  187:     # We can only nest sequences or pages.  Anything else is an illegal nest.
  188: 
  189:     unless (($fn=~/\.sequence$/) || $ispage) { 
  190: 	$errtext.='<br />'.&mt('Invalid map: [_1]',"<tt>$fn</tt>");
  191: 	return; 
  192:     }
  193: 
  194:     # Read the XML that constitutes the file.
  195: 
  196:     my $instr=&Apache::lonnet::getfile($fn);
  197: 
  198:     if ($instr eq -1) {
  199:         $errtext.= '<br />'
  200:                   .&mt('Map not loaded: The file [_1] does not exist.',
  201:                        "<tt>$fn</tt>");
  202: 	return;
  203:     }
  204: 
  205:     # Successfully got file, parse it
  206: 
  207:     # parse for parameter processing.
  208:     # Note that these are <param... / > tags
  209:     # so we only care about 'S' (tag start) nodes.
  210: 
  211: 
  212:     my $parser = HTML::TokeParser->new(\$instr);
  213:     $parser->attr_encoded(1);
  214: 
  215:     # first get all parameters
  216: 
  217: 
  218:     while (my $token = $parser->get_token) {
  219: 	next if ($token->[0] ne 'S');
  220: 	if ($token->[1] eq 'param') {
  221: 	    &parse_param($token,$lpc);
  222: 	} 
  223:     }
  224: 
  225:     # Get set to take another pass through the XML:
  226:     # for resources and links.
  227: 
  228:     $parser = HTML::TokeParser->new(\$instr);
  229:     $parser->attr_encoded(1);
  230: 
  231:     my $linkpc=0;
  232: 
  233:     $fn=~/\.(\w+)$/;
  234: 
  235:     $hash{'map_type_'.$lpc}=$1;
  236: 
  237:     my $randomize = ($randomorder{$parent_rid} =~ /^yes$/i);
  238: 
  239:     # Parse the resources, link and condition tags.
  240:     # Note that if randomorder or random select is chosen the links and
  241:     # conditions are meaningless but are determined by the randomization.
  242:     # This is handled in the next chunk of code.
  243: 
  244:     my @map_ids;
  245:     my $codechecked;
  246:     $rescount{$lpc} = 0;
  247:     $mapcount{$lpc} = 0;
  248:     while (my $token = $parser->get_token) {
  249: 	next if ($token->[0] ne 'S');
  250: 
  251: 	# Resource
  252: 
  253: 	if ($token->[1] eq 'resource') {
  254: 	    my $resource_id = &parse_resource($token,$lpc,$ispage,$uri,$courseid);
  255: 	    if (defined $resource_id) {
  256: 		push(@map_ids, $resource_id);
  257:                 if ($hash{'src_'.$lpc.'.'.$resource_id}) {
  258:                     $rescount{$lpc} ++;
  259:                     if (($hash{'src_'.$lpc.'.'.$resource_id}=~/\.sequence$/) || 
  260:                         ($hash{'src_'.$lpc.'.'.$resource_id}=~/\.page$/)) {
  261:                         $mapcount{$lpc} ++; 
  262:                     }
  263:                 }
  264:                 unless ($codechecked) {
  265:                     my $startsymb =
  266:                        &Apache::lonnet::encode_symb($hash{'map_id_'.$lpc},$resource_id,
  267:                                                     $hash{'src_'."$lpc.$resource_id"});
  268:                     my $code = 
  269:                         &Apache::lonnet::EXT('resource.0.examcode',$startsymb,undef,undef,
  270:                                              undef,undef,$courseid);
  271:                     if ($code) {
  272:                         $randomizationcode{$parent_rid} = $code;
  273:                     }
  274:                     $codechecked = 1; 
  275:                 }
  276: 	    }
  277: 
  278:        # Link
  279: 
  280: 	} elsif ($token->[1] eq 'link' && !$randomize) {
  281: 	    &make_link(++$linkpc,$lpc,$token->[2]->{'to'},
  282: 		       $token->[2]->{'from'},
  283: 		       $token->[2]->{'condition'}); # note ..condition may be undefined.
  284: 
  285: 	# condition
  286: 
  287: 	} elsif ($token->[1] eq 'condition' && !$randomize) {
  288: 	    &parse_condition($token,$lpc);
  289: 	}
  290:     }
  291:     undef($codechecked);
  292: 
  293:     # Handle randomization and random selection
  294: 
  295:     if ($randomize) {
  296:         unless (&is_advanced($courseid)) {
  297:             # Order of resources is not randomized if user has and advanced role in the course.
  298: 	    my $seed;
  299: 
  300:             # If the map's random seed parameter has been specified
  301:             # it is used as the basis for computing the seed ...
  302: 
  303: 	    if (defined($randompickseed{$parent_rid})) {
  304: 		$seed = $randompickseed{$parent_rid};
  305: 	    } else {
  306: 
  307: 		# Otherwise the parent's fully encoded symb is used.
  308: 
  309: 		my ($mapid,$resid)=split(/\./,$parent_rid);
  310: 		my $symb=
  311: 		    &Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},
  312: 						 $resid,$hash{'src_'.$parent_rid});
  313: 		
  314: 		$seed = $symb;
  315: 	    }
  316: 
  317: 	    # TODO: Here for sure we need to pass along the username/domain
  318: 	    # so that we can impersonate users in lonprintout e.g.
  319: 
  320:             my $setcode;
  321:             if (defined($randomizationcode{$parent_rid})) {
  322:                 if ($env{'form.CODE'} eq '') {
  323:                     $env{'form.CODE'} = $randomizationcode{$parent_rid};
  324:                     $setcode = 1;
  325:                 }
  326:             }
  327: 
  328: 	    my $rndseed=&Apache::lonnet::rndseed($seed);
  329: 	    &Apache::lonnet::setup_random_from_rndseed($rndseed);
  330: 
  331:             if ($setcode) {
  332:                 undef($env{'form.CODE'});
  333:                 undef($setcode);
  334:             }
  335: 
  336: 	    # Take the set of map ids we have decoded and permute them to a
  337: 	    # random order based on the seed set above. All of this is
  338: 	    # processing the randomorder parameter if it is set, not
  339: 	    # randompick.
  340: 
  341: 	    @map_ids=&Math::Random::random_permutation(@map_ids);
  342: 	}
  343: 
  344: 	my $from = shift(@map_ids);
  345: 	my $from_rid = $lpc.'.'.$from;
  346: 	$hash{'map_start_'.$uri} = $from_rid;
  347: 	$hash{'type_'.$from_rid}='start';
  348: 
  349: 	# Create links to reflect the random re-ordering done above.
  350: 	# In the code to process the map XML, we did not process links or conditions
  351: 	# if randomorder was set.  This means that for an instructor to choose
  352: 
  353: 	while (my $to = shift(@map_ids)) {
  354: 	    &make_link(++$linkpc,$lpc,$to,$from);
  355: 	    my $to_rid =  $lpc.'.'.$to;
  356: 	    $hash{'type_'.$to_rid}='normal';
  357: 	    $from = $to;
  358: 	    $from_rid = $to_rid;
  359: 	}
  360: 
  361: 	$hash{'map_finish_'.$uri}= $from_rid;
  362: 	$hash{'type_'.$from_rid}='finish';
  363:     }
  364: 
  365:     $parser = HTML::TokeParser->new(\$instr);
  366:     $parser->attr_encoded(1);
  367: 
  368:     # last parse out the mapalias params.  These provide mnemonic
  369:     # tags to resources that can be used in conditions
  370: 
  371:     while (my $token = $parser->get_token) {
  372: 	next if ($token->[0] ne 'S');
  373: 	if ($token->[1] eq 'param') {
  374: 	    &parse_mapalias_param($token,$lpc);
  375: 	} 
  376:     }
  377: }
  378: 
  379: sub is_advanced {
  380:     my ($courseid) = @_;
  381:     my $advanced;
  382:     if ($env{'request.course.id'}) {
  383:         $advanced = (&Apache::lonnet::allowed('adv') eq 'F');
  384:     } else {
  385:         $env{'request.course.id'} = $courseid;
  386:         $advanced = (&Apache::lonnet::allowed('adv') eq 'F');
  387:         $env{'request.course.id'} = '';
  388:     }
  389:     return $advanced;
  390: }
  391: 
  392: # -------------------------------------------------------------------- Resource
  393: #
  394: #  Parses a resource tag to produce the value to push into the
  395: #  map_ids array.
  396: # 
  397: #
  398: #  Information about the actual type of resource is provided by the file extension
  399: #  of the uri (e.g. .problem, .sequence etc. etc.).
  400: #
  401: #  Parameters:
  402: #    $token   - A token from HTML::TokeParser
  403: #               This is an array that describes the most recently parsed HTML item.
  404: #    $lpc     - Map nesting level (?)
  405: #    $ispage  - True if this resource is encapsulated in a .page (assembled resourcde).
  406: #    $uri     - URI of the enclosing resource.
  407: #    $courseid - Course id of the course containing the resource being parsed. 
  408: # Returns:
  409: #   Value of the id attribute of the tag.
  410: #
  411: # Note:
  412: #   The token is an array that contains the following elements:
  413: #   [0]   => 'S' indicating this is a start token
  414: #   [1]   => 'resource'  indicating this tag is a <resource> tag.
  415: #   [2]   => Hash of attribute =>value pairs.
  416: #   [3]   => @(keys [2]).
  417: #   [4]   => unused.
  418: #
  419: #   The attributes of the resourcde tag include:
  420: #
  421: #   id     - The resource id.
  422: #   src    - The URI of the resource.
  423: #   type   - The resource type (e.g. start and finish).
  424: #   title  - The resource title.
  425: 
  426: 
  427: sub parse_resource {
  428:     my ($token,$lpc,$ispage,$uri,$courseid) = @_;
  429:     
  430:     # I refuse to countenance code like this that has 
  431:     # such a dirty side effect (and forcing this sub to be called within a loop).
  432:     #
  433:     #  if ($token->[2]->{'type'} eq 'zombie') { next; }
  434:     #
  435:     #  The original code both returns _and_ skips to the next pass of the >caller's<
  436:     #  loop, that's just dirty.
  437:     #
  438: 
  439:     # Zombie resources don't produce anything useful.
  440: 
  441:     if ($token->[2]->{'type'} eq 'zombie') {
  442: 	return undef;
  443:     }
  444: 
  445:     my $rid=$lpc.'.'.$token->[2]->{'id'}; # Resource id in hash is levelcounter.id-in-xml.
  446: 
  447:     # Save the hash element type and title:
  448: 	    
  449:     $hash{'kind_'.$rid}='res';
  450:     $hash{'title_'.$rid}=$token->[2]->{'title'};
  451: 
  452:     # Get the version free URI for the resource.
  453:     # If a 'version' attribute was supplied, and this resource's version 
  454:     # information has not yet been stored, store it.
  455:     #
  456: 
  457:     my $turi=&versiontrack($token->[2]->{'src'});
  458:     if ($token->[2]->{'version'}) {
  459: 	unless ($hash{'version_'.$turi}) {
  460: 	    $hash{'version_'.$turi}=$1;
  461: 	}
  462:     }
  463:     # Pull out the title and do entity substitution on &colon
  464:     # Q: Why no other entity substitutions?
  465: 
  466:     my $title=$token->[2]->{'title'};
  467:     $title=~s/\&colon\;/\:/gs;
  468: 
  469: 
  470: 
  471:     # I think the point of all this code is to construct a final
  472:     # URI that apache and its rewrite rules can use to
  473:     # fetch the resource.   Thi s sonly necessary if the resource
  474:     # is not a page.  If the resource is a page then it must be
  475:     # assembled (at fetch time?).
  476: 
  477:     if ($ispage) {
  478:         if ($token->[2]->{'external'} eq 'true') { # external
  479:             $turi=~s{^http\://}{/ext/};
  480:         }
  481:     } else {
  482: 	$turi=~/\.(\w+)$/;
  483: 	my $embstyle=&Apache::loncommon::fileembstyle($1);
  484: 	if ($token->[2]->{'external'} eq 'true') { # external
  485: 	    $turi=~s/^https?\:\/\//\/adm\/wrapper\/ext\//;
  486: 	} elsif ($turi=~/^\/*uploaded\//) { # uploaded
  487: 	    if (($embstyle eq 'img') 
  488: 		|| ($embstyle eq 'emb')
  489: 		|| ($embstyle eq 'wrp')) {
  490: 		$turi='/adm/wrapper'.$turi;
  491: 	    } elsif ($embstyle eq 'ssi') {
  492: 		#do nothing with these
  493: 	    } elsif ($turi!~/\.(sequence|page)$/) {
  494: 		$turi='/adm/coursedocs/showdoc'.$turi;
  495: 	    }
  496:         } elsif ($turi=~ m{^/adm/$match_domain/$match_courseid/\d+/ext\.tool$}) {
  497:             $turi='/adm/wrapper'.$turi;
  498: 	} elsif ($turi=~/\S/) { # normal non-empty internal resource
  499: 	    my $mapdir=$uri;
  500: 	    $mapdir=~s/[^\/]+$//;
  501: 	    $turi=&Apache::lonnet::hreflocation($mapdir,$turi);
  502: 	    if (($embstyle eq 'img') 
  503: 		|| ($embstyle eq 'emb')
  504: 		|| ($embstyle eq 'wrp')) {
  505: 		$turi='/adm/wrapper'.$turi;
  506: 	    }
  507: 	}
  508:     }
  509:     # Store reverse lookup, remove query string resource 'ids'_uri => resource id.
  510:     # If the URI appears more than one time in the sequence, it's resourcde
  511:     # id's are constructed as a comma spearated list.
  512: 
  513:     my $idsuri=$turi;
  514:     $idsuri=~s/\?.+$//;
  515:     if (defined($hash{'ids_'.$idsuri})) {
  516: 	$hash{'ids_'.$idsuri}.=','.$rid;
  517:     } else {
  518: 	$hash{'ids_'.$idsuri}=''.$rid;
  519:     }
  520:     
  521: 
  522: 
  523:     if ($turi=~/\/(syllabus|aboutme|navmaps|smppg|bulletinboard|viewclasslist)$/) {
  524: 	$turi.='?register=1';
  525:     }
  526:     
  527: 
  528:     # resource id lookup:  'src'_resourc-di  => URI decorated with a query
  529:     # parameter as above if necessary due to the resource type.
  530:     
  531:     $hash{'src_'.$rid}=$turi;
  532: 
  533:     # Mark the external-ness of the resource:
  534:     
  535:     if ($token->[2]->{'external'} eq 'true') {
  536: 	$hash{'ext_'.$rid}='true:';
  537:     } else {
  538: 	$hash{'ext_'.$rid}='false:';
  539:     }
  540: 
  541:     # If the resource is a start/finish resource set those
  542:     # entries in the has so that navigation knows where everything starts.
  543:     # TODO?  If there is a malformed sequence that has no start or no finish
  544:     # resource, should this be detected and errors thrown?  How would such a 
  545:     # resource come into being other than being manually constructed by a person
  546:     # and then uploaded?  Could that happen if an author decided a sequence was almost
  547:     # right edited it by hand and then reuploaded it to 'fix it' but accidently cut the
  548:     #  start or finish resources?
  549:     #
  550:     #  All resourcess also get a type_id => (start | finish | normal)    hash entr.
  551:     #
  552:     if ($token->[2]->{'type'}) {
  553: 	$hash{'type_'.$rid}=$token->[2]->{'type'};
  554: 	if ($token->[2]->{'type'} eq 'start') {
  555: 	    $hash{'map_start_'.$uri}="$rid";
  556: 	}
  557: 	if ($token->[2]->{'type'} eq 'finish') {
  558: 	    $hash{'map_finish_'.$uri}="$rid";
  559: 	}
  560:     }  else {
  561: 	$hash{'type_'.$rid}='normal';
  562:     }
  563: 
  564:     # Sequences end pages are constructed entities.  They require that the 
  565:     # map that defines _them_ be loaded as well into the hash...with this resourcde
  566:     # as the base of the nesting.
  567:     # Resources like that are also marked with is_map_id => 1 entries.
  568:     #
  569:     
  570:     if (($turi=~/\.sequence$/) ||
  571: 	($turi=~/\.page$/)) {
  572: 	$hash{'is_map_'.$rid}=1;
  573: 	if ((!$hiddenurl{$rid}) || (&is_advanced($courseid))) {
  574: 	    &loadmap($turi,$rid,$courseid);
  575: 	}
  576:     } 
  577:     return $token->[2]->{'id'};
  578: }
  579: 
  580: #-------------------------------------------------------------------- link
  581: #  Links define how you are allowed to move from one resource to another.
  582: #  They are the transition edges in the directed graph that a map is.
  583: #  This sub takes informatino from a <link> tag and constructs the
  584: #  navigation bits and pieces of a map.  There is no requirement that the
  585: #  resources that are linke are already defined, however clearly the map is 
  586: #  badly broken if they are not _eventually_ defined.
  587: #
  588: #  Note that links can be unconditional or conditional.
  589: #
  590: #  Parameters:
  591: #     linkpc   - The link counter for this level of map nesting (this is 
  592: #                reset to zero by loadmap prior to starting to process
  593: #                links for map).
  594: #     lpc      - The map level ocounter (how deeply nested this map is in
  595: #                the hierarchy of maps that are recursively read in.
  596: #     to       - resource id (within the XML) of the target of the edge.
  597: #     from     - resource id (within the XML) of the source of the edge.
  598: #     condition- id of condition associated with the edge (also within the XML).
  599: #
  600: 
  601: sub make_link {
  602:     my ($linkpc,$lpc,$to,$from,$condition) = @_;
  603:     
  604:     #  Compute fully qualified ids for the link, the 
  605:     # and from/to by prepending lpc.
  606:     #
  607: 
  608:     my $linkid=$lpc.'.'.$linkpc;
  609:     my $goesto=$lpc.'.'.$to;
  610:     my $comesfrom=$lpc.'.'.$from;
  611:     my $undercond=0;
  612: 
  613: 
  614:     # If there is a condition, qualify it with the level counter.
  615: 
  616:     if ($condition) {
  617: 	$undercond=$lpc.'.'.$condition;
  618:     }
  619: 
  620:     # Links are represnted by:
  621:     #  goesto_.fuullyqualifedlinkid => fully qualified to
  622:     #  comesfrom.fullyqualifiedlinkid => fully qualified from
  623:     #  undercond_.fullyqualifiedlinkid => fully qualified condition id.
  624: 
  625:     $hash{'goesto_'.$linkid}=$goesto;
  626:     $hash{'comesfrom_'.$linkid}=$comesfrom;
  627:     $hash{'undercond_'.$linkid}=$undercond;
  628: 
  629:     # In addition:
  630:     #   to_.fully qualified from => comma separated list of 
  631:     #   link ids with that from.
  632:     # Similarly:
  633:     #   from_.fully qualified to => comma separated list of link ids`
  634:     #                               with that to.
  635:     #  That allows us given a resource id to know all edges that go to it
  636:     #  and leave from it.
  637:     #
  638: 
  639:     if (defined($hash{'to_'.$comesfrom})) {
  640: 	$hash{'to_'.$comesfrom}.=','.$linkid;
  641:     } else {
  642: 	$hash{'to_'.$comesfrom}=''.$linkid;
  643:     }
  644:     if (defined($hash{'from_'.$goesto})) {
  645: 	$hash{'from_'.$goesto}.=','.$linkid;
  646:     } else {
  647: 	$hash{'from_'.$goesto}=''.$linkid;
  648:     }
  649: }
  650: 
  651: # ------------------------------------------------------------------- Condition
  652: #
  653: #  Processes <condition> tags, storing sufficient information about them
  654: #  in the hash so that they can be evaluated and used to conditionalize
  655: #  what is presented to the student.
  656: #
  657: #  these can have the following attributes 
  658: #
  659: #    id    = A unique identifier of the condition within the map.
  660: #
  661: #    value = Is a perl script-let that, when evaluated in safe space
  662: #            determines whether or not the condition is true.
  663: #            Normally this takes the form of a test on an  Apache::lonnet::EXT call
  664: #            to find the value of variable associated with a resource in the
  665: #            map identified by a mapalias.
  666: #            Here's a fragment of XML code that illustrates this:
  667: #
  668: #           <param to="5" value="mainproblem" name="parameter_0_mapalias" type="string" />
  669: #           <resource src="" id="1" type="start" title="Start" />
  670: #           <resource src="/res/msu/albertel/b_and_c/p1.problem" id="5"  title="p1.problem" />
  671: #           <condition value="&EXT('user.resource.resource.0.tries','mainproblem')
  672: #           <2 " id="61" type="stop" />
  673: #           <link to="5" index="1" from="1" condition="61" />    
  674: #
  675: #           In this fragment:
  676: #             - The param tag establishes an alias to resource id 5 of 'mainproblem'.
  677: #             - The resource that is the start of the map is identified.
  678: #             - The resource tag identifies the resource associated with this tag
  679: #               and gives it the id 5.
  680: #             - The condition is true if the tries variable associated with mainproblem
  681: #               is less than 2 (that is the user has had more than 2 tries).
  682: #               The condition type is a stop condition which inhibits(?) the associated
  683: #               link if the condition  is false. 
  684: #             - The link to resource 5 from resource 1 is affected by this condition.    
  685: #            
  686: #    type  = Type of the condition. The type determines how the condition affects the
  687: #            link associated with it and is one of
  688: #            -  'force'
  689: #            -  'stop'
  690: #              anything else including not supplied..which treated as:
  691: #            - 'normal'.
  692: #            Presumably maps get created by the resource assembly tool and therefore
  693: #            illegal type values won't squirm their way into the XML.
  694: #
  695: # Side effects:
  696: #   -  The kind_level-qualified-condition-id hash element is set to 'cond'.
  697: #   -  The condition text is pushed into the cond array and its element number is
  698: #      set in the condid_level-qualified-condition-id element of the hash.
  699: #   - The condition type is colon appneded to the cond array element for this condition.
  700: sub parse_condition {
  701:     my ($token,$lpc) = @_;
  702:     my $rid=$lpc.'.'.$token->[2]->{'id'};
  703:     
  704:     $hash{'kind_'.$rid}='cond';
  705: 
  706:     my $condition = $token->[2]->{'value'};
  707:     $condition =~ s/[\n\r]+/ /gs;
  708:     push(@cond, $condition);
  709:     $hash{'condid_'.$rid}=$#cond;
  710:     if ($token->[2]->{'type'}) {
  711: 	$cond[$#cond].=':'.$token->[2]->{'type'};
  712:     }  else {
  713: 	$cond[$#cond].=':normal';
  714:     }
  715: }
  716: 
  717: # ------------------------------------------------------------------- Parameter
  718: # Parse a <parameter> tag in the map.
  719: # Parmameters:
  720: #    $token Token array for a start tag from HTML::TokeParser
  721: #           [0] = 'S'
  722: #           [1] = tagname ("param")
  723: #           [2] = Hash of {attribute} = values.
  724: #           [3] = Array of the keys in [2].
  725: #           [4] = unused.
  726: #    $lpc   Current map nesting level.a
  727: #
  728: #  Typical attributes:
  729: #     to=n      - Number of the resource the parameter applies to.
  730: #     type=xx   - Type of parameter value (e.g. string_yesno or int_pos).
  731: #     name=xxx  - Name of parameter (e.g. parameter_randompick or parameter_randomorder).
  732: #     value=xxx - value of the parameter.
  733: 
  734: sub parse_param {
  735:     my ($token,$lpc) = @_;
  736:     my $referid=$lpc.'.'.$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 loadmap().
  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: #  Parse mapalias parameters.
  802: #  these are tags of the form:
  803: #  <param to="nn" 
  804: #         value="some-alias-for-resourceid-nn" 
  805: #         name="parameter_0_mapalias" 
  806: #         type="string" />
  807: #  A map alias is a textual name for a resource:
  808: #    - The to  attribute identifies the resource (this gets level qualified below)
  809: #    - The value attributes provides the alias string.
  810: #    - name must be of the regexp form: /^parameter_(0_)*mapalias$/
  811: #    - e.g. the string 'parameter_' followed by 0 or more "0_" strings
  812: #      terminating with the string 'mapalias'.
  813: #      Examples:
  814: #         'parameter_mapalias', 'parameter_0_mapalias', parameter_0_0_mapalias'
  815: #  Invalid to ids are silently ignored.
  816: #
  817: #  Parameters:
  818: #     token - The token array fromthe HMTML::TokeParser
  819: #     lpc   - The current map level counter.
  820: #
  821: sub parse_mapalias_param {
  822:     my ($token,$lpc) = @_;
  823: 
  824:     # Fully qualify the to value and ignore the alias if there is no
  825:     # corresponding resource.
  826: 
  827:     my $referid=$lpc.'.'.$token->[2]->{'to'};
  828:     return if (!exists($hash{'src_'.$referid}));
  829: 
  830:     # If this is a valid mapalias parameter, 
  831:     # Append the target id to the count_mapalias element for that
  832:     # alias so that we can detect doubly defined aliases
  833:     # e.g.:
  834:     #  <param to="1" value="george" name="parameter_0_mapalias" type="string" />
  835:     #  <param to="2" value="george" name="parameter_0_mapalias" type="string" />
  836:     #
  837:     #  The example above is trivial but the case that's important has to do with
  838:     #  constructing a map that includes a nested map where the nested map may have
  839:     #  aliases that conflict with aliases established in the enclosing map.
  840:     #
  841:     # ...and create/update the hash mapalias entry to actually store the alias.
  842:     #
  843: 
  844:     if ($token->[2]->{'name'}=~/^parameter_(0_)*mapalias$/) {
  845: 	&count_mapalias($token->[2]->{'value'},$referid);
  846: 	$hash{'mapalias_'.$token->[2]->{'value'}}=$referid;
  847:     }
  848: }
  849: 
  850: # --------------------------------------------------------- Simplify expression
  851: 
  852: 
  853: #
  854: #  Someone should really comment this to describe what it does to what and why.
  855: #
  856: sub simplify {
  857:     my $expression=shift;
  858: # (0&1) = 1
  859:     $expression=~s/\(0\&([_\.\d]+)\)/$1/g;
  860: # (8)=8
  861:     $expression=~s/\(([_\.\d]+)\)/$1/g;
  862: # 8&8=8
  863:     $expression=~s/([^_\.\d])([_\.\d]+)\&\2([^_\.\d])/$1$2$3/g;
  864: # 8|8=8
  865:     $expression=~s/([^_\.\d])([_\.\d]+)(?:\|\2)+([^_\.\d])/$1$2$3/g;
  866: # (5&3)&4=5&3&4
  867:     $expression=~s/\(([_\.\d]+)((?:\&[_\.\d]+)+)\)\&([_\.\d]+[^_\.\d])/$1$2\&$3/g;
  868: # (((5&3)|(4&6)))=((5&3)|(4&6))
  869:     $expression=~
  870: 	s/\((\(\([_\.\d]+(?:\&[_\.\d]+)*\)(?:\|\([_\.\d]+(?:\&[_\.\d]+)*\))+\))\)/$1/g;
  871: # ((5&3)|(4&6))|(1&2)=(5&3)|(4&6)|(1&2)
  872:     $expression=~
  873: 	s/\((\([_\.\d]+(?:\&[_\.\d]+)*\))((?:\|\([_\.\d]+(?:\&[_\.\d]+)*\))+)\)\|(\([_\.\d]+(?:\&[_\.\d]+)*\))/\($1$2\|$3\)/g;
  874:     return $expression;
  875: }
  876: 
  877: # -------------------------------------------------------- Build condition hash
  878: 
  879: #
  880: #  Traces a route recursively through the map after it has been loaded
  881: #  (I believe this really visits each resourcde that is reachable fromt he
  882: #  start top node.
  883: #
  884: #  - Marks hidden resources as hidden.
  885: #  - Marks which resource URL's must be encrypted.
  886: #  - Figures out (if necessary) the first resource in the map.
  887: #  - Further builds the chunks of the big hash that define how 
  888: #    conditions work
  889: #
  890: #  Note that the tracing strategy won't visit resources that are not linked to
  891: #  anything or islands in the map (groups of resources that form a path but are not
  892: #  linked in to the path that can be traced from the start resource...but that's ok
  893: #  because by definition, those resources are not reachable by users of the course.
  894: #
  895: # Parameters:
  896: #   sofar    - _URI of the prior entry or 0 if this is the top.
  897: #   rid      - URI of the resource to visit.
  898: #   beenhere - list of resources (each resource enclosed by &'s) that have
  899: #              already been visited.
  900: #   encflag  - If true the resource that resulted in a recursive call to us
  901: #              has an encoded URL (which means contained resources should too). 
  902: #   hdnflag  - If true,the resource that resulted in a recursive call to us
  903: #              was hidden (which means contained resources should be hidden too).
  904: # Returns
  905: #    new value indicating how far the map has been traversed (the sofar).
  906: #
  907: sub traceroute {
  908:     my ($sofar,$rid,$beenhere,$encflag,$hdnflag)=@_;
  909:     my $newsofar=$sofar=simplify($sofar);
  910: 
  911:     unless ($beenhere=~/\&\Q$rid\E\&/) {
  912: 	$beenhere.=$rid.'&';  
  913: 	my ($mapid,$resid)=split(/\./,$rid);
  914: 	my $symb=&Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},$resid,$hash{'src_'.$rid});
  915: 	my $hidden=&Apache::lonnet::EXT('resource.0.hiddenresource',$symb);
  916: 
  917: 	if ($hdnflag || lc($hidden) eq 'yes') {
  918: 	    $hiddenurl{$rid}=1;
  919: 	}
  920: 	if (!$hdnflag && lc($hidden) eq 'no') {
  921: 	    delete($hiddenurl{$rid});
  922: 	}
  923: 
  924: 	my $encrypt=&Apache::lonnet::EXT('resource.0.encrypturl',$symb);
  925: 	if ($encflag || lc($encrypt) eq 'yes') { $encurl{$rid}=1; }
  926: 
  927: 	if (($retfrid eq '') && ($hash{'src_'.$rid})
  928: 	    && ($hash{'src_'.$rid}!~/\.sequence$/)) {
  929: 	    $retfrid=$rid;
  930: 	}
  931:         my @deeplink=&Apache::lonnet::EXT('resource.0.deeplink',$symb);
  932:         unless ((@deeplink == 0) || ($deeplink[0] eq 'full')) {
  933:             $deeplinkonly{$rid}=join(':',@deeplink);
  934:             if ($deeplink[1] eq 'map') {
  935:                 my $parent = (split(/\,/,$hash{'map_hierarchy_'.$mapid}))[-1];
  936:                 $deeplinkonly{"$parent.$mapid"}=$deeplinkonly{$rid};
  937:             }
  938:         }
  939: 
  940: 	if (defined($hash{'conditions_'.$rid})) {
  941: 	    $hash{'conditions_'.$rid}=simplify(
  942:            '('.$hash{'conditions_'.$rid}.')|('.$sofar.')');
  943: 	} else {
  944: 	    $hash{'conditions_'.$rid}=$sofar;
  945: 	}
  946: 
  947: 	# if the expression is just the 0th condition keep it
  948: 	# otherwise leave a pointer to this condition expression
  949: 
  950: 	$newsofar = ($sofar eq '0') ? $sofar : '_'.$rid;
  951: 
  952: 	# Recurse if the resource is a map:
  953: 
  954: 	if (defined($hash{'is_map_'.$rid})) {
  955: 	    if (defined($hash{'map_start_'.$hash{'src_'.$rid}})) {
  956: 		$sofar=$newsofar=
  957: 		    &traceroute($sofar,
  958: 				$hash{'map_start_'.$hash{'src_'.$rid}},
  959: 				$beenhere,
  960: 				$encflag || $encurl{$rid},
  961: 				$hdnflag || $hiddenurl{$rid});
  962: 	    }
  963: 	}
  964: 
  965: 	# Processes  links to this resource:
  966: 	#  - verify the existence of any conditionals on the link to here.
  967: 	#  - Recurse to any resources linked to us.
  968: 	#
  969: 	if (defined($hash{'to_'.$rid})) {
  970: 	    foreach my $id (split(/\,/,$hash{'to_'.$rid})) {
  971: 		my $further=$sofar;
  972: 		#
  973: 		# If there's a condition associated with this link be sure
  974: 		# it's been defined else that's an error:
  975: 		#
  976:                 if ($hash{'undercond_'.$id}) {
  977: 		    if (defined($hash{'condid_'.$hash{'undercond_'.$id}})) {
  978: 			$further=simplify('('.'_'.$rid.')&('.
  979: 					  $hash{'condid_'.$hash{'undercond_'.$id}}.')');
  980: 		    } else {
  981: 			$errtext.= '<br />'.
  982:                                    &mt('Undefined condition ID: [_1]',
  983:                                        $hash{'undercond_'.$id});
  984: 		    }
  985:                 }
  986: 		#  Recurse to resoruces that have to's to us.
  987:                 $newsofar=&traceroute($further,$hash{'goesto_'.$id},$beenhere,
  988: 				      $encflag,$hdnflag);
  989: 	    }
  990: 	}
  991:     }
  992:     return $newsofar;
  993: }
  994: 
  995: # ------------------------------ Cascading conditions, quick access, parameters
  996: 
  997: #
  998: #  Seems a rather strangely named sub given what the comment above says it does.
  999: 
 1000: 
 1001: sub accinit {
 1002:     my ($uri,$short,$fn)=@_;
 1003:     my %acchash=();
 1004:     my %captured=();
 1005:     my $condcounter=0;
 1006:     $acchash{'acc.cond.'.$short.'.0'}=0;
 1007: 
 1008:     # This loop is only interested in conditions and 
 1009:     # parameters in the big hash:
 1010: 
 1011:     foreach my $key (keys(%hash)) {
 1012: 
 1013: 	# conditions:
 1014: 
 1015: 	if ($key=~/^conditions/) {
 1016: 	    my $expr=$hash{$key};
 1017: 
 1018: 	    # try to find and factor out common sub-expressions
 1019: 	    # Any subexpression that is found is simplified, removed from
 1020: 	    # the original condition expression and the simplified sub-expression
 1021: 	    # substituted back in to the epxression..I'm not actually convinced this
 1022: 	    # factors anything out...but instead maybe simplifies common factors(?)
 1023: 
 1024: 	    foreach my $sub ($expr=~m/(\(\([_\.\d]+(?:\&[_\.\d]+)+\)(?:\|\([_\.\d]+(?:\&[_\.\d]+)+\))+\))/g) {
 1025: 		my $orig=$sub;
 1026: 
 1027: 		my ($factor) = ($sub=~/\(\(([_\.\d]+\&(:?[_\.\d]+\&)*)(?:[_\.\d]+\&*)+\)(?:\|\(\1(?:[_\.\d]+\&*)+\))+\)/);
 1028: 		next if (!defined($factor));
 1029: 
 1030: 		$sub=~s/\Q$factor\E//g;
 1031: 		$sub=~s/^\(/\($factor\(/;
 1032: 		$sub.=')';
 1033: 		$sub=simplify($sub);
 1034: 		$expr=~s/\Q$orig\E/$sub/;
 1035: 	    }
 1036: 	    $hash{$key}=$expr;
 1037: 
 1038:            # If not yet seen, record in acchash and that we've seen it.
 1039: 
 1040: 	    unless (defined($captured{$expr})) {
 1041: 		$condcounter++;
 1042: 		$captured{$expr}=$condcounter;
 1043: 		$acchash{'acc.cond.'.$short.'.'.$condcounter}=$expr;
 1044: 	    } 
 1045:         # Parameters:
 1046: 
 1047: 	} elsif ($key=~/^param_(\d+)\.(\d+)/) {
 1048: 	    my $prefix=&Apache::lonnet::encode_symb($hash{'map_id_'.$1},$2,
 1049: 						    $hash{'src_'.$1.'.'.$2});
 1050: 	    foreach my $param (split(/\&/,$hash{$key})) {
 1051: 		my ($typename,$value)=split(/\=/,$param);
 1052: 		my ($type,$name)=split(/\:/,$typename);
 1053: 		$parmhash{$prefix.'.'.&unescape($name)}=
 1054: 		    &unescape($value);
 1055: 		$parmhash{$prefix.'.'.&unescape($name).'.type'}=
 1056: 		    &unescape($type);
 1057: 	    }
 1058: 	}
 1059:     }
 1060:     # This loop only processes id entries in the big hash.
 1061: 
 1062:     foreach my $key (keys(%hash)) {
 1063: 	if ($key=~/^ids/) {
 1064: 	    foreach my $resid (split(/\,/,$hash{$key})) {
 1065: 		my $uri=$hash{'src_'.$resid};
 1066: 		my ($uripath,$urifile) =
 1067: 		    &Apache::lonnet::split_uri_for_cond($uri);
 1068: 		if ($uripath) {
 1069: 		    my $uricond='0';
 1070: 		    if (defined($hash{'conditions_'.$resid})) {
 1071: 			$uricond=$captured{$hash{'conditions_'.$resid}};
 1072: 		    }
 1073: 		    if (defined($acchash{'acc.res.'.$short.'.'.$uripath})) {
 1074: 			if ($acchash{'acc.res.'.$short.'.'.$uripath}=~
 1075: 			    /(\&\Q$urifile\E\:[^\&]*)/) {
 1076: 			    my $replace=$1;
 1077: 			    my $regexp=$replace;
 1078: 			    #$regexp=~s/\|/\\\|/g;
 1079: 			    $acchash{'acc.res.'.$short.'.'.$uripath} =~
 1080: 				s/\Q$regexp\E/$replace\|$uricond/;
 1081: 			} else {
 1082: 			    $acchash{'acc.res.'.$short.'.'.$uripath}.=
 1083: 				$urifile.':'.$uricond.'&';
 1084: 			}
 1085: 		    } else {
 1086: 			$acchash{'acc.res.'.$short.'.'.$uripath}=
 1087: 			    '&'.$urifile.':'.$uricond.'&';
 1088: 		    }
 1089: 		} 
 1090: 	    }
 1091: 	}
 1092:     }
 1093:     $acchash{'acc.res.'.$short.'.'}='&:0&';
 1094:     my $courseuri=$uri;
 1095:     $courseuri=~s/^\/res\///;
 1096:     my $regexp = 1;
 1097:     &Apache::lonnet::delenv('(acc\.|httpref\.)',$regexp);
 1098:     &Apache::lonnet::appenv(\%acchash);
 1099: }
 1100: 
 1101: # ---------------- Selectively delete from randompick maps and hidden url parms
 1102: 
 1103: sub hiddenurls {
 1104:     my $randomoutentry='';
 1105:     foreach my $rid (keys(%randompick)) {
 1106:         my $rndpick=$randompick{$rid};
 1107:         my $mpc=$hash{'map_pc_'.$hash{'src_'.$rid}};
 1108: # ------------------------------------------- put existing resources into array
 1109:         my @currentrids=();
 1110:         foreach my $key (sort(keys(%hash))) {
 1111: 	    if ($key=~/^src_($mpc\.\d+)/) {
 1112: 		if ($hash{'src_'.$1}) { push @currentrids, $1; }
 1113:             }
 1114:         }
 1115: 	# rids are number.number and we want to numercially sort on 
 1116:         # the second number
 1117: 	@currentrids=sort {
 1118: 	    my (undef,$aid)=split(/\./,$a);
 1119: 	    my (undef,$bid)=split(/\./,$b);
 1120: 	    $aid <=> $bid;
 1121: 	} @currentrids;
 1122:         next if ($#currentrids<$rndpick);
 1123: # -------------------------------- randomly eliminate the ones that should stay
 1124: 	my (undef,$id)=split(/\./,$rid);
 1125:         if ($randompickseed{$rid}) { $id=$randompickseed{$rid}; }
 1126:         my $setcode;
 1127:         if (defined($randomizationcode{$rid})) {
 1128:             if ($env{'form.CODE'} eq '') {
 1129:                 $env{'form.CODE'} = $randomizationcode{$rid};
 1130:                 $setcode = 1;
 1131:             }
 1132:         }
 1133: 	my $rndseed=&Apache::lonnet::rndseed($id); # use id instead of symb
 1134:         if ($setcode) {
 1135:             undef($env{'form.CODE'});
 1136:             undef($setcode);
 1137:         }
 1138: 	&Apache::lonnet::setup_random_from_rndseed($rndseed);
 1139: 	my @whichids=&Math::Random::random_permuted_index($#currentrids+1);
 1140:         for (my $i=1;$i<=$rndpick;$i++) { $currentrids[$whichids[$i]]=''; }
 1141: 	#&Apache::lonnet::logthis("$id,$rndseed,".join(':',@whichids));
 1142: # -------------------------------------------------------- delete the leftovers
 1143:         for (my $k=0; $k<=$#currentrids; $k++) {
 1144:             if ($currentrids[$k]) {
 1145: 		$hash{'randomout_'.$currentrids[$k]}=1;
 1146:                 my ($mapid,$resid)=split(/\./,$currentrids[$k]);
 1147:                 if ($rescount{$mapid}) {
 1148:                     $rescount{$mapid} --;
 1149:                 }
 1150:                 if ($hash{'is_map_'.$currentrids[$k]}) {
 1151:                     if ($mapcount{$mapid}) {
 1152:                         $mapcount{$mapid} --;
 1153:                     }
 1154:                 }
 1155:                 $randomoutentry.='&'.
 1156: 		    &Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},
 1157: 						 $resid,
 1158: 						 $hash{'src_'.$currentrids[$k]}
 1159: 						 ).'&';
 1160:             }
 1161:         }
 1162:     }
 1163: # ------------------------------ take care of explicitly hidden urls or folders
 1164:     foreach my $rid (keys(%hiddenurl)) {
 1165: 	$hash{'randomout_'.$rid}=1;
 1166: 	my ($mapid,$resid)=split(/\./,$rid);
 1167:         if ($rescount{$mapid}) {
 1168:             $rescount{$mapid} --;
 1169:         }
 1170:         if ($hash{'is_map_'.$rid}) {
 1171:             if ($mapcount{$mapid}) {
 1172:                 $mapcount{$mapid} --;
 1173:             }
 1174:         }
 1175: 	$randomoutentry.='&'.
 1176: 	    &Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},$resid,
 1177: 					 $hash{'src_'.$rid}).'&';
 1178:     }
 1179: # --------------------------------------- append randomout entry to environment
 1180:     if ($randomoutentry) {
 1181: 	&Apache::lonnet::appenv({'acc.randomout' => $randomoutentry});
 1182:     }
 1183: }
 1184: 
 1185: # -------------------------------------- populate big hash with map breadcrumbs
 1186: 
 1187: # Create map_breadcrumbs_$pc from map_hierarchy_$pc by omitting intermediate
 1188: # maps not shown in Course Contents table.
 1189: 
 1190: sub mapcrumbs {
 1191:     foreach my $key (keys(%rescount)) {
 1192:         if ($hash{'map_hierarchy_'.$key}) {
 1193:             my $skipnext = 0;
 1194:             foreach my $id (split(/,/,$hash{'map_hierarchy_'.$key}),$key) {
 1195:                 unless ($skipnext) {
 1196:                     $hash{'map_breadcrumbs_'.$key} .= "$id,";
 1197:                 }
 1198:                 unless (($id == 0) || ($id == 1)) {
 1199:                     if ((!$rescount{$id}) || ($rescount{$id} == 1 && $mapcount{$id} == 1)) {
 1200:                         $skipnext = 1;
 1201:                     } else {
 1202:                         $skipnext = 0;
 1203:                     }
 1204:                 }
 1205:             }
 1206:             $hash{'map_breadcrumbs_'.$key} =~ s/,$//;
 1207:         }
 1208:     }
 1209: }
 1210: 
 1211: # ---------------------------------------------------- Read map and all submaps
 1212: 
 1213: sub readmap {
 1214:     my ($short,$critmsg_check) = @_;
 1215:     $short=~s/^\///;
 1216: 
 1217:     # TODO:  Hidden dependency on current user:
 1218: 
 1219:     my %cenv=&Apache::lonnet::coursedescription($short,{'freshen_cache'=>1}); 
 1220: 
 1221:     my $fn=$cenv{'fn'};
 1222:     my $uri;
 1223:     $short=~s/\//\_/g;
 1224:     unless ($uri=$cenv{'url'}) { 
 1225: 	&Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1226: 				 "Could not load course $short.</font>"); 
 1227: 	return ('',&mt('No course data available.'));;
 1228:     }
 1229:     @cond=('true:normal');
 1230: 
 1231:     unless (open(LOCKFILE,">","$fn.db.lock")) {
 1232: 	# 
 1233: 	# Most likely a permissions problem on the lockfile or its directory.
 1234: 	#
 1235:         $retfurl = '';
 1236:         return ($retfurl,'<br />'.&mt('Map not loaded - Lock file could not be opened when reading map:').' <tt>'.$fn.'</tt>.');
 1237:     }
 1238:     my $lock=0;
 1239:     my $gotstate=0;
 1240:     
 1241:     # If we can get the lock without delay any files there are idle
 1242:     # and from some prior request.  We'll kill them off and regenerate them:
 1243: 
 1244:     if (flock(LOCKFILE,LOCK_EX|LOCK_NB)) {	
 1245: 	$lock=1;		# Remember that we hold the lock.
 1246:         &unlink_tmpfiles($fn);
 1247:     }
 1248:     undef %randompick;
 1249:     undef %randompickseed;
 1250:     undef %randomorder;
 1251:     undef %randomizationcode;
 1252:     undef %hiddenurl;
 1253:     undef %encurl;
 1254:     undef %deeplinkonly;
 1255:     undef %rescount;
 1256:     undef %mapcount;
 1257:     $retfrid='';
 1258:     $errtext='';
 1259:     my ($untiedhash,$untiedparmhash,$tiedhash,$tiedparmhash); # More state flags.
 1260: 
 1261:     # if we got the lock, regenerate course regnerate empty files and tie them.
 1262: 
 1263:     if ($lock) {
 1264:         if (tie(%hash,'GDBM_File',"$fn.db",&GDBM_WRCREAT(),0640)) {
 1265:             $tiedhash = 1;
 1266:             if (tie(%parmhash,'GDBM_File',$fn.'_parms.db',&GDBM_WRCREAT(),0640)) {
 1267:                 $tiedparmhash = 1;
 1268:                 $gotstate = &build_tmp_hashes($uri,
 1269: 					      $fn,
 1270: 					      $short,
 1271: 					      \%cenv); # TODO: Need to provide requested user@dom
 1272:                 unless ($gotstate) {
 1273:                     &Apache::lonnet::logthis('Failed to write statemap at first attempt '.$fn.' for '.$uri.'.</font>');
 1274:                 }
 1275:                 $untiedparmhash = untie(%parmhash);
 1276:                 unless ($untiedparmhash) {
 1277:                     &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1278:                         'Could not untie coursemap parmhash '.$fn.' for '.$uri.'.</font>');
 1279:                 }
 1280:             }
 1281:             $untiedhash = untie(%hash);
 1282:             unless ($untiedhash) {
 1283:                 &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1284:                     'Could not untie coursemap hash '.$fn.' for '.$uri.'.</font>');
 1285:             }
 1286:         }
 1287: 	flock(LOCKFILE,LOCK_UN); # RF: this is what I don't get unless there are other
 1288: 	                         # unlocked places the remainder happens..seems like if we
 1289:                                  # just kept the lock here the rest of the code would have
 1290:                                  # been much easier? 
 1291:     }
 1292:     unless ($lock && $tiedhash && $tiedparmhash) { 
 1293: 	# if we are here it is likely because we are already trying to 
 1294: 	# initialize the course in another child, busy wait trying to 
 1295: 	# tie the hashes for the next 90 seconds, if we succeed forward 
 1296: 	# them on to navmaps, if we fail, throw up the Could not init 
 1297: 	# course screen
 1298: 	#
 1299: 	# RF: I'm not seeing the case where the ties/unties can fail in a way
 1300: 	#     that can be remedied by this.  Since we owned the lock seems
 1301: 	#     Tie/untie failures are a result of something like a permissions problem instead?
 1302: 	#
 1303: 
 1304: 	#  In any vent, undo what we did manage to do above first:
 1305: 	if ($lock) {
 1306: 	    # Got the lock but not the DB files
 1307: 	    flock(LOCKFILE,LOCK_UN);
 1308:             $lock = 0;
 1309: 	}
 1310:         if ($tiedhash) {
 1311:             unless($untiedhash) {
 1312: 	        untie(%hash);
 1313:             }
 1314:         }
 1315:         if ($tiedparmhash) {
 1316:             unless($untiedparmhash) {
 1317:                 untie(%parmhash);
 1318:             }
 1319:         }
 1320: 	# Log our failure:
 1321: 
 1322: 	&Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1323: 				 "Could not tie coursemap $fn for $uri.</font>");
 1324:         $tiedhash = '';
 1325:         $tiedparmhash = '';
 1326: 	my $i=0;
 1327: 
 1328: 	# Keep on retrying the lock for 90 sec until we succeed.
 1329: 
 1330: 	while($i<90) {
 1331: 	    $i++;
 1332: 	    sleep(1);
 1333: 	    if (flock(LOCKFILE,LOCK_EX|LOCK_NB)) {
 1334: 
 1335: 		# Got the lock, tie the hashes...the assumption in this code is
 1336: 		# that some other worker thread has created the db files quite recently
 1337: 		# so no load is needed:
 1338: 
 1339:                 $lock = 1;
 1340: 		if (tie(%hash,'GDBM_File',"$fn.db",&GDBM_READER(),0640)) {
 1341:                     $tiedhash = 1;
 1342: 		    if (tie(%parmhash,'GDBM_File',$fn.'_parms.db',&GDBM_READER(),0640)) {
 1343:                         $tiedparmhash = 1;
 1344:                         if (-e "$fn.state") {
 1345: 		            $retfurl='/adm/navmaps';
 1346: 
 1347: 			    # BUG BUG: Side effect!
 1348: 			    # Should conditionalize on something so that we can use this
 1349: 			    # to load maps for courses that are not current?
 1350: 			    #
 1351: 		            &Apache::lonnet::appenv({"request.course.id"  => $short,
 1352: 		   			             "request.course.fn"  => $fn,
 1353: 					             "request.course.uri" => $uri,
 1354:                                                      "request.course.tied" => time});
 1355:                             
 1356: 		            $untiedhash = untie(%hash);
 1357: 		            $untiedparmhash = untie(%parmhash);
 1358:                             $gotstate = 1;
 1359: 		            last;
 1360: 		        }
 1361:                         $untiedparmhash = untie(%parmhash);
 1362: 	            }
 1363: 	            $untiedhash = untie(%hash);
 1364:                 }
 1365:             }
 1366: 	}
 1367:         if ($lock) {
 1368:             flock(LOCKFILE,LOCK_UN);
 1369:             $lock = 0;
 1370:             if ($tiedparmhash) {
 1371:                 unless ($untiedparmhash) {
 1372:                     &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1373:                         'Could not untie coursemap parmhash '.$fn.' for '.$uri.'.</font>');
 1374:                 }
 1375:             }
 1376:             if ($tiedparmhash) {
 1377:                 unless ($untiedhash) {
 1378:                     &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1379:                         'Could not untie coursemap hash '.$fn.' for '.$uri.'.</font>');
 1380:                 }
 1381:             }
 1382:         }
 1383:     }
 1384:     # I think this branch of code is all about what happens if we just did the stuff above, 
 1385:     # but found that the  state file did not exist...again if we'd just held the lock
 1386:     # would that have made this logic simpler..as generating all the files would be
 1387:     # an atomic operation with respect to the lock.
 1388:     #
 1389:     unless ($gotstate) {
 1390:         $lock = 0;
 1391:         &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1392:                      'Could not read statemap '.$fn.' for '.$uri.'.</font>');
 1393:         &unlink_tmpfiles($fn);
 1394:         if (flock(LOCKFILE,LOCK_EX|LOCK_NB)) {
 1395:             $lock=1;
 1396:         }
 1397:         undef %randompick;
 1398:         undef %randompickseed;
 1399:         undef %randomorder;
 1400:         undef %randomizationcode;
 1401:         undef %hiddenurl;
 1402:         undef %encurl;
 1403:         undef %deeplinkonly;
 1404:         undef %rescount;
 1405:         undef %mapcount;
 1406:         $errtext='';
 1407:         $retfrid='';
 1408: 	#
 1409: 	# Once more through the routine of tying and loading and so on.
 1410: 	#
 1411:         if ($lock) {
 1412:             if (tie(%hash,'GDBM_File',"$fn.db",&GDBM_WRCREAT(),0640)) {
 1413:                 if (tie(%parmhash,'GDBM_File',$fn.'_parms.db',&GDBM_WRCREAT(),0640)) {
 1414:                     $gotstate = &build_tmp_hashes($uri,$fn,$short,\%cenv); # TODO: User dependent?
 1415:                     unless ($gotstate) {
 1416:                         &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1417:                             'Failed to write statemap at second attempt '.$fn.' for '.$uri.'.</font>');
 1418:                     }
 1419:                     unless (untie(%parmhash)) {
 1420:                         &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1421:                             'Could not untie coursemap parmhash '.$fn.'.db for '.$uri.'.</font>');
 1422:                     }
 1423:                 } else {
 1424:                     &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1425:                         'Could not tie coursemap '.$fn.'__parms.db for '.$uri.'.</font>');
 1426:                 }
 1427:                 unless (untie(%hash)) {
 1428:                     &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1429:                         'Could not untie coursemap hash '.$fn.'.db for '.$uri.'.</font>');
 1430:                 }
 1431:             } else {
 1432:                &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1433:                    'Could not tie coursemap '.$fn.'.db for '.$uri.'.</font>');
 1434:             }
 1435:             flock(LOCKFILE,LOCK_UN);
 1436:             $lock = 0;
 1437:         } else {
 1438: 	    # Failed to get the immediate lock.
 1439: 
 1440:             &Apache::lonnet::logthis('<font color="blue">WARNING: '.
 1441:             'Could not obtain lock to tie coursemap hash '.$fn.'.db for '.$uri.'.</font>');
 1442:         }
 1443:     }
 1444:     close(LOCKFILE);
 1445:     unless (($errtext eq '') || ($env{'request.course.uri'} =~ m{^/uploaded/})) {
 1446:         &Apache::lonmsg::author_res_msg($env{'request.course.uri'},
 1447:                                         $errtext); # TODO: User dependent?
 1448:     }
 1449: # ------------------------------------------------- Check for critical messages
 1450: 
 1451: #  Depends on user must parameterize this as well..or separate as this is:
 1452: #  more part of determining what someone sees on entering a course?
 1453: #  When lonuserstate::readmap() is called from lonroles.pm, i.e.,
 1454: #  after selecting a role in a course, critical_redirect will be called,
 1455: #  unless the course has a blocking event in effect, which suppresses
 1456: #  critical message checking (users without evb priv).
 1457: #
 1458: 
 1459:     if ($critmsg_check) {
 1460:         my ($redirect,$url) = &Apache::loncommon::critical_redirect();
 1461:         if ($redirect) {
 1462:             $retfurl = $url;
 1463:         }
 1464:     }
 1465:     return ($retfurl,$errtext);
 1466: }
 1467: 
 1468: #
 1469: #  This sub is called when the course hash and the param hash have been tied and
 1470: #  their lock file is held.
 1471: #  Parameters:
 1472: #     $uri      -  URI that identifies the course.
 1473: #     $fn       -  The base path/filename of the files that make up the context
 1474: #                  being built.
 1475: #     $short    -  Short course name.
 1476: #     $cenvref  -  Reference to the course environment hash returned by 
 1477: #                  Apache::lonnet::coursedescription
 1478: #
 1479: #  Assumptions:
 1480: #    The globals
 1481: #    %hash, %paramhash are tied to their gdbm files and we hold the lock on them.
 1482: #
 1483: sub build_tmp_hashes {
 1484:     my ($uri,$fn,$short,$cenvref) = @_;
 1485:     
 1486:     unless(ref($cenvref) eq 'HASH') {
 1487:         return;
 1488:     }
 1489:     my %cenv = %{$cenvref};
 1490:     my $gotstate = 0;
 1491:     %hash=();			# empty the global course and  parameter hashes.
 1492:     %parmhash=();
 1493:     $errtext='';		# No error messages yet.
 1494:     $pc=0;
 1495:     &clear_mapalias_count();
 1496:     &processversionfile(%cenv);
 1497: 
 1498:     # URI Of the map file.
 1499: 
 1500:     my $furi=&Apache::lonnet::clutter($uri);
 1501:     #
 1502:     #  the map staring points.
 1503:     #
 1504:     $hash{'src_0.0'}=&versiontrack($furi);
 1505:     $hash{'title_0.0'}=&Apache::lonnet::metadata($uri,'title');
 1506:     $hash{'ids_'.$furi}='0.0';
 1507:     $hash{'is_map_0.0'}=1;
 1508: 
 1509:     # Load the map.. note that loadmap may implicitly recurse if the map contains 
 1510:     # sub-maps.
 1511: 
 1512: 
 1513:     &loadmap($uri,'0.0',$short);
 1514: 
 1515:     #  The code below only executes if there is a starting point for the map>
 1516:     #  Q/BUG??? If there is no start resource for the map should that be an error?
 1517:     #
 1518: 
 1519:     if (defined($hash{'map_start_'.$uri})) {
 1520:         &Apache::lonnet::appenv({"request.course.id"  => $short,
 1521:                                  "request.course.fn"  => $fn,
 1522:                                  "request.course.uri" => $uri,
 1523:                                  "request.course.tied" => time});
 1524:         $env{'request.course.id'}=$short;
 1525:         &traceroute('0',$hash{'map_start_'.$uri},'&');
 1526:         &accinit($uri,$short,$fn);
 1527:         &hiddenurls();
 1528:         &mapcrumbs();
 1529:     }
 1530:     $errtext .= &get_mapalias_errors();
 1531: # ------------------------------------------------------- Put versions into src
 1532:     foreach my $key (keys(%hash)) {
 1533:         if ($key=~/^src_/) {
 1534:             $hash{$key}=&putinversion($hash{$key});
 1535:         } elsif ($key =~ /^(map_(?:start|finish|pc)_)(.*)/) {
 1536:             my ($type, $url) = ($1,$2);
 1537:             my $value = $hash{$key};
 1538:             $hash{$type.&putinversion($url)}=$value;
 1539:         }
 1540:     }
 1541: # ---------------------------------------------------------------- Encrypt URLs
 1542:     foreach my $id (keys(%encurl)) {
 1543: #           $hash{'src_'.$id}=&Apache::lonenc::encrypted($hash{'src_'.$id});
 1544:         $hash{'encrypted_'.$id}=1;
 1545:     }
 1546: # ------------------------------------------------------------ Deep-linked URLs
 1547:     foreach my $id (keys(%deeplinkonly)) {
 1548:         $hash{'deeplinkonly_'.$id}=$deeplinkonly{$id};
 1549:     }
 1550: # ----------------------------------------------- Close hashes to finally store
 1551: # --------------------------------- Routine must pass this point, no early outs
 1552:     $hash{'first_rid'}=$retfrid;
 1553:     my ($mapid,$resid)=split(/\./,$retfrid);
 1554:     $hash{'first_mapurl'}=$hash{'map_id_'.$mapid};
 1555:     my $symb=&Apache::lonnet::encode_symb($hash{'map_id_'.$mapid},$resid,$hash{'src_'.$retfrid});
 1556:     $retfurl=&add_get_param($hash{'src_'.$retfrid},{ 'symb' => $symb });
 1557:     if ($hash{'encrypted_'.$retfrid}) {
 1558:         $retfurl=&Apache::lonenc::encrypted($retfurl,(&Apache::lonnet::allowed('adv') ne 'F'));
 1559:     }
 1560:     $hash{'first_url'}=$retfurl;
 1561: # ---------------------------------------------------- Store away initial state
 1562:     {
 1563:         my $cfh;
 1564:         if (open($cfh,">","$fn.state")) {
 1565:             print $cfh join("\n",@cond);
 1566:             $gotstate = 1;
 1567:         } else {
 1568:             &Apache::lonnet::logthis("<font color=blue>WARNING: ".
 1569:                                      "Could not write statemap $fn for $uri.</font>");
 1570:         }
 1571:     }
 1572:     return $gotstate;
 1573: }
 1574: 
 1575: sub unlink_tmpfiles {
 1576:     my ($fn) = @_;
 1577:     my $file_dir = dirname($fn);
 1578: 
 1579:     if ("$file_dir/" eq LONCAPA::tempdir()) {
 1580:         my @files = qw (.db _symb.db .state _parms.db);
 1581:         foreach my $file (@files) {
 1582:             if (-e $fn.$file) {
 1583:                 unless (unlink($fn.$file)) {
 1584:                     &Apache::lonnet::logthis("<font color=blue>WARNING: ".
 1585:                                  "Could not unlink ".$fn.$file."</font>");
 1586:                 }
 1587:             }
 1588:         }
 1589:     }
 1590:     return;
 1591: }
 1592: 
 1593: # ------------------------------------------------------- Evaluate state string
 1594: 
 1595: sub evalstate {
 1596:     my $fn=$env{'request.course.fn'}.'.state';
 1597:     my $state='';
 1598:     if (-e $fn) {
 1599: 	my @conditions=();
 1600: 	{
 1601: 	    open(my $fh,"<",$fn);
 1602: 	    @conditions=<$fh>;
 1603:             close($fh);
 1604: 	}  
 1605: 	my $safeeval = new Safe;
 1606: 	my $safehole = new Safe::Hole;
 1607: 	$safeeval->permit("entereval");
 1608: 	$safeeval->permit(":base_math");
 1609: 	$safeeval->deny(":base_io");
 1610: 	$safehole->wrap(\&Apache::lonnet::EXT,$safeeval,'&EXT');
 1611: 	foreach my $line (@conditions) {
 1612: 	    chomp($line);
 1613: 	    my ($condition,$weight)=split(/\:/,$line);
 1614: 	    if ($safeeval->reval($condition)) {
 1615: 		if ($weight eq 'force') {
 1616: 		    $state.='3';
 1617: 		} else {
 1618: 		    $state.='2';
 1619: 		}
 1620: 	    } else {
 1621: 		if ($weight eq 'stop') {
 1622: 		    $state.='0';
 1623: 		} else {
 1624: 		    $state.='1';
 1625: 		}
 1626: 	    }
 1627: 	}
 1628:     }
 1629:     &Apache::lonnet::appenv({'user.state.'.$env{'request.course.id'} => $state});
 1630:     return $state;
 1631: }
 1632: 
 1633: #  This block seems to have code to manage/detect doubly defined
 1634: #  aliases in maps.
 1635: 
 1636: {
 1637:     my %mapalias_cache;
 1638:     sub count_mapalias {
 1639: 	my ($value,$resid) = @_;
 1640:  	push(@{ $mapalias_cache{$value} }, $resid);
 1641:     }
 1642: 
 1643:     sub get_mapalias_errors {
 1644: 	my $error_text;
 1645: 	foreach my $mapalias (sort(keys(%mapalias_cache))) {
 1646: 	    next if (scalar(@{ $mapalias_cache{$mapalias} } ) == 1);
 1647: 	    my $count;
 1648: 	    my $which =
 1649: 		join('</li><li>', 
 1650: 		     map {
 1651: 			 my $id = $_;
 1652: 			 if (exists($hash{'src_'.$id})) {
 1653: 			     $count++;
 1654: 			 }
 1655: 			 my ($mapid) = split(/\./,$id);
 1656:                          &mt('Resource [_1][_2]in Map [_3]',
 1657: 			     $hash{'title_'.$id},'<br />',
 1658: 			     $hash{'title_'.$hash{'ids_'.$hash{'map_id_'.$mapid}}});
 1659: 		     } (@{ $mapalias_cache{$mapalias} }));
 1660: 	    next if ($count < 2);
 1661: 	    $error_text .= '<div class="LC_error">'.
 1662: 		&mt('Error: Found the mapalias "[_1]" defined multiple times.',
 1663: 		    $mapalias).
 1664: 		'</div><ul><li>'.$which.'</li></ul>';
 1665: 	}
 1666: 	&clear_mapalias_count();
 1667: 	return $error_text;
 1668:     }
 1669:     sub clear_mapalias_count {
 1670: 	undef(%mapalias_cache);
 1671:     }
 1672: }
 1673: 1;
 1674: __END__
 1675: 
 1676: =head1 NAME
 1677: 
 1678: Apache::lonuserstate - Construct and maintain state and binary representation
 1679: of course for user
 1680: 
 1681: =head1 SYNOPSIS
 1682: 
 1683: Invoked by lonroles.pm.
 1684: 
 1685: &Apache::lonuserstate::readmap($cdom.'/'.$cnum);
 1686: 
 1687: =head1 INTRODUCTION
 1688: 
 1689: This module constructs and maintains state and binary representation
 1690: of course for user.
 1691: 
 1692: This is part of the LearningOnline Network with CAPA project
 1693: described at http://www.lon-capa.org.
 1694: 
 1695: =head1 SUBROUTINES
 1696: 
 1697: =over
 1698: 
 1699: =item loadmap()
 1700: 
 1701: Loads map from disk
 1702: 
 1703: =item simplify()
 1704: 
 1705: Simplify expression
 1706: 
 1707: =item traceroute()
 1708: 
 1709: Build condition hash
 1710: 
 1711: =item accinit()
 1712: 
 1713: Cascading conditions, quick access, parameters
 1714: 
 1715: =item readmap()
 1716: 
 1717: Read map and all submaps
 1718: 
 1719: =item evalstate()
 1720: 
 1721: Evaluate state string
 1722: 
 1723: =back
 1724: 
 1725: =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.