--- loncom/interface/lonnavmaps.pm 2003/07/17 18:40:49 1.216 +++ loncom/interface/lonnavmaps.pm 2003/08/07 17:26:44 1.221 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.216 2003/07/17 18:40:49 bowersj2 Exp $ +# $Id: lonnavmaps.pm,v 1.221 2003/08/07 17:26:44 bowersj2 Exp $ # # Copyright Michigan State University Board of Trustees # @@ -129,9 +129,7 @@ sub real_handler { $r->send_http_header; # Create the nav map - my $navmap = Apache::lonnavmaps::navmap->new( - $ENV{"request.course.fn"}.".db", - $ENV{"request.course.fn"}."_parms.db", 1, 1); + my $navmap = Apache::lonnavmaps::navmap->new(); if (!defined($navmap)) { @@ -161,11 +159,6 @@ sub real_handler { $r->rflush(); - # Now that we've displayed some stuff to the user, init the navmap - $navmap->init(); - - $r->rflush(); - # Check that it's defined if (!($navmap->courseMapDefined())) { $r->print('Coursemap undefined.' . @@ -552,7 +545,8 @@ sub timeToHumanString { =head1 NAME -Apache::lonnavmap - Subroutines to handle and render the navigation maps +Apache::lonnavmap - Subroutines to handle and render the navigation + maps =head1 SYNOPSIS @@ -562,12 +556,12 @@ other modules. =head1 OVERVIEW -When a user enters a course, LON-CAPA examines the course structure -and caches it in what is often referred to as the "big hash". You -can see it if you are logged into LON-CAPA, in a course, by going -to /adm/test. (You may need to tweak the /home/httpd/lonTabs/htpasswd -file to view it.) The content of the hash will be under the heading -"Big Hash". +X When a user enters a course, LON-CAPA examines the +course structure and caches it in what is often referred to as the +"big hash" X. You can see it if you are logged into +LON-CAPA, in a course, by going to /adm/test. (You may need to +tweak the /home/httpd/lonTabs/htpasswd file to view it.) The +content of the hash will be under the heading "Big Hash". Big Hash contains, among other things, how resources are related to each other (next/previous), what resources are maps, which @@ -716,9 +710,7 @@ to override vertical and horizontal alig =head2 Parameters -Most of these parameters are only useful if you are *not* using the -folder interface (i.e., the default first column), which is probably -the common case. If you are using this interface, then you should be +Minimally, you should be able to get away with just using 'cols' (to specify the columns shown), 'url' (necessary for the folders to link to the current screen correctly), and possibly 'queryString' if your app calls for it. In @@ -1173,12 +1165,9 @@ sub render { if (!$ENV{'form.folderManip'} && !defined($args->{'iterator'})) { # Step 1: Check to see if we have a navmap if (!defined($navmap)) { - $navmap = Apache::lonnavmaps::navmap->new( - $ENV{"request.course.fn"}.".db", - $ENV{"request.course.fn"}."_parms.db", 1, 1); + $navmap = Apache::lonnavmaps::navmap->new(); $mustCloseNavMap = 1; } - $navmap->init(); # Step two: Locate what kind of here marker is necessary # Determine where the "here" marker is and where the screen jumps to. @@ -1239,13 +1228,9 @@ sub render { # Step 1: Check to see if we have a navmap if (!defined($navmap)) { - $navmap = Apache::lonnavmaps::navmap->new($r, - $ENV{"request.course.fn"}.".db", - $ENV{"request.course.fn"}."_parms.db", 1, 1); + $navmap = Apache::lonnavmaps::navmap->new(); $mustCloseNavMap = 1; } - # Paranoia: Make sure it's ready - $navmap->init(); # See if we're being passed a specific map if ($args->{'iterator_map'}) { @@ -1605,29 +1590,59 @@ package Apache::lonnavmaps::navmap; =head1 Object: Apache::lonnavmaps::navmap -You must obtain resource objects through the navmap object. +=head2 Overview + +The navmap object's job is to provide access to the resources +in the course as Apache::lonnavmaps::resource objects, and to +query and manage the relationship between those resource objects. + +Generally, you'll use the navmap object in one of three basic ways. +In order of increasing complexity and power: + +=over 4 + +=item * C<$navmap-EgetByX>, where X is B, B, B or B. This provides + various ways to obtain resource objects, based on various identifiers. + Use this when you want to request information about one object or + a handful of resources you already know the identities of, from some + other source. For more about Ids, Symbs, and MapPcs, see the + Resource documentation. Note that Url should be a B, + not your first choice; it only works when there is only one + instance of the resource in the course, which only applies to + maps, and even that may change in the future. + +=item * CretrieveResources(args)>. This + retrieves resources matching some criterion and returns them + in a flat array, with no structure information. Use this when + you are manipulating a series of resources, based on what map + the are in, but do not care about branching, or exactly how + the maps and resources are related. This is the most common case. + +=item * C<$it = $navmap-EgetIterator(args)>. This allows you traverse + the course's navmap in various ways without writing the traversal + code yourself. See iterator documentation below. Use this when + you need to know absolutely everything about the course, including + branches and the precise relationship between maps and resources. + +=back + +=head2 Creation And Destruction -=head2 Creation +To create a navmap object, use the following function: =over 4 -=item * B(navHashFile, parmHashFile, genCourseAndUserOptions, - genMailDiscussStatus, getUserData): +=item * Bnew>(): -Binds a new navmap object to the compiled nav map hash and parm hash -given as filenames. genCourseAndUserOptions is a flag saying whether -the course options and user options hash should be generated. This is -for when you are using the parameters of the resources that require -them; see documentation in resource object -documentation. genMailDiscussStatus causes the nav map to retreive -information about the email and discussion status of -resources. Returns the navmap object if this is successful, or -B if not. You must check for undef; errors will occur when you -try to use the other methods otherwise. getUserData, if true, will -retreive the user's performance data for various problems. +Creates a new navmap object. Returns the navmap object if this is +successful, or B if not. =back +When you are done with the $navmap object, you I call +$navmap->untieHashes(), or you'll prevent the current user from using that +course until the web server is restarted. (!) + =head2 Methods =over 4 @@ -1647,12 +1662,6 @@ sub new { my $class = ref($proto) || $proto; my $self = {}; - $self->{NAV_HASH_FILE} = shift; - $self->{PARM_HASH_FILE} = shift; - $self->{GENERATE_COURSE_USER_OPT} = shift; - $self->{GENERATE_EMAIL_DISCUSS_STATUS} = shift; - $self->{GET_USER_DATA} = shift; - # Resource cache stores navmap resources as we reference them. We generate # them on-demand so we don't pay for creating resources unless we use them. $self->{RESOURCE_CACHE} = {}; @@ -1665,12 +1674,13 @@ sub new { my %navmaphash; my %parmhash; - if (!(tie(%navmaphash, 'GDBM_File', $self->{NAV_HASH_FILE}, + my $courseFn = $ENV{"request.course.fn"}; + if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db", &GDBM_READER(), 0640))) { return undef; } - if (!(tie(%parmhash, 'GDBM_File', $self->{PARM_HASH_FILE}, + if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db", &GDBM_READER(), 0640))) { untie %{$self->{PARM_HASH}}; @@ -1679,128 +1689,134 @@ sub new { $self->{NAV_HASH} = \%navmaphash; $self->{PARM_HASH} = \%parmhash; - $self->{INITED} = 0; + $self->{PARM_CACHE} = {}; bless($self); return $self; } -sub init { +sub generate_course_user_opt { my $self = shift; - if ($self->{INITED}) { return; } + if ($self->{COURSE_USER_OPT_GENERATED}) { return; } - # If the course opt hash and the user opt hash should be generated, - # generate them - if ($self->{GENERATE_COURSE_USER_OPT}) { - my $uname=$ENV{'user.name'}; - my $udom=$ENV{'user.domain'}; - my $uhome=$ENV{'user.home'}; - my $cid=$ENV{'request.course.id'}; - my $chome=$ENV{'course.'.$cid.'.home'}; - my ($cdom,$cnum)=split(/\_/,$cid); - - my $userprefix=$uname.'_'.$udom.'_'; - - my %courserdatas; my %useropt; my %courseopt; my %userrdatas; - unless ($uhome eq 'no_host') { + my $uname=$ENV{'user.name'}; + my $udom=$ENV{'user.domain'}; + my $uhome=$ENV{'user.home'}; + my $cid=$ENV{'request.course.id'}; + my $chome=$ENV{'course.'.$cid.'.home'}; + my ($cdom,$cnum)=split(/\_/,$cid); + + my $userprefix=$uname.'_'.$udom.'_'; + + my %courserdatas; my %useropt; my %courseopt; my %userrdatas; + unless ($uhome eq 'no_host') { # ------------------------------------------------- Get coursedata (if present) - unless ((time-$courserdatas{$cid.'.last_cache'})<240) { - my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum. - ':resourcedata',$chome); - # Check for network failure - if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) { - $self->{NETWORK_FAILURE} = 1; - } elsif ($reply!~/^error\:/) { - $courserdatas{$cid}=$reply; - $courserdatas{$cid.'.last_cache'}=time; - } - } - foreach (split(/\&/,$courserdatas{$cid})) { - my ($name,$value)=split(/\=/,$_); - $courseopt{$userprefix.&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); - } + unless ((time-$courserdatas{$cid.'.last_cache'})<240) { + my $reply=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum. + ':resourcedata',$chome); + # Check for network failure + if ( $reply =~ /no.such.host/i || $reply =~ /con_lost/i) { + $self->{NETWORK_FAILURE} = 1; + } elsif ($reply!~/^error\:/) { + $courserdatas{$cid}=$reply; + $courserdatas{$cid.'.last_cache'}=time; + } + } + foreach (split(/\&/,$courserdatas{$cid})) { + my ($name,$value)=split(/\=/,$_); + $courseopt{$userprefix.&Apache::lonnet::unescape($name)}= + &Apache::lonnet::unescape($value); + } # --------------------------------------------------- Get userdata (if present) - unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) { - my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome); - if ($reply!~/^error\:/) { - $userrdatas{$uname.'___'.$udom}=$reply; - $userrdatas{$uname.'___'.$udom.'.last_cache'}=time; - } - # check to see if network failed - elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i ) - { - $self->{NETWORK_FAILURE} = 1; - } - } - foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) { - my ($name,$value)=split(/\=/,$_); - $useropt{$userprefix.&Apache::lonnet::unescape($name)}= - &Apache::lonnet::unescape($value); - } - $self->{COURSE_OPT} = \%courseopt; - $self->{USER_OPT} = \%useropt; - } - } - - if ($self->{GENERATE_EMAIL_DISCUSS_STATUS}) { - my $cid=$ENV{'request.course.id'}; - my ($cdom,$cnum)=split(/\_/,$cid); - - my %emailstatus = &Apache::lonnet::dump('email_status'); - my $logoutTime = $emailstatus{'logout'}; - my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}}; - $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ? - $courseLeaveTime : $logoutTime); - my %discussiontime = &Apache::lonnet::dump('discussiontimes', - $cdom, $cnum); - my %feedback=(); - my %error=(); - my $keys = &Apache::lonnet::reply('keys:'. - $ENV{'user.domain'}.':'. - $ENV{'user.name'}.':nohist_email', - $ENV{'user.home'}); - - foreach my $msgid (split(/\&/, $keys)) { - $msgid=&Apache::lonnet::unescape($msgid); - my $plain=&Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid)); - if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) { - my ($what,$url)=($1,$2); - my %status= - &Apache::lonnet::get('email_status',[$msgid]); - if ($status{$msgid}=~/^error\:/) { - $status{$msgid}=''; - } - - if (($status{$msgid} eq 'new') || - (!$status{$msgid})) { - if ($what eq 'Error') { - $error{$url}.=','.$msgid; - } else { - $feedback{$url}.=','.$msgid; - } - } - } - } - - $self->{FEEDBACK} = \%feedback; - $self->{ERROR_MSG} = \%error; # what is this? JB - $self->{DISCUSSION_TIME} = \%discussiontime; - $self->{EMAIL_STATUS} = \%emailstatus; - + unless ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) { + my $reply=&Apache::lonnet::reply('dump:'.$udom.':'.$uname.':resourcedata',$uhome); + if ($reply!~/^error\:/) { + $userrdatas{$uname.'___'.$udom}=$reply; + $userrdatas{$uname.'___'.$udom.'.last_cache'}=time; + } + # check to see if network failed + elsif ( $reply=~/no.such.host/i || $reply=~/con.*lost/i ) + { + $self->{NETWORK_FAILURE} = 1; + } + } + foreach (split(/\&/,$userrdatas{$uname.'___'.$udom})) { + my ($name,$value)=split(/\=/,$_); + $useropt{$userprefix.&Apache::lonnet::unescape($name)}= + &Apache::lonnet::unescape($value); + } + $self->{COURSE_OPT} = \%courseopt; + $self->{USER_OPT} = \%useropt; } - if ($self->{GET_USER_DATA}) { - # Retreive performance data on problems - my %student_data = Apache::lonnet::currentdump($ENV{'request.course.id'}, - $ENV{'user.domain'}, - $ENV{'user.name'}); - $self->{STUDENT_DATA} = \%student_data; + $self->{COURSE_USER_OPT_GENERATED} = 1; + + return; +} + +sub generate_email_discuss_status { + my $self = shift; + if ($self->{EMAIL_DISCUSS_GENERATED}) { return; } + + my $cid=$ENV{'request.course.id'}; + my ($cdom,$cnum)=split(/\_/,$cid); + + my %emailstatus = &Apache::lonnet::dump('email_status'); + my $logoutTime = $emailstatus{'logout'}; + my $courseLeaveTime = $emailstatus{'logout_'.$ENV{'request.course.id'}}; + $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ? + $courseLeaveTime : $logoutTime); + my %discussiontime = &Apache::lonnet::dump('discussiontimes', + $cdom, $cnum); + my %feedback=(); + my %error=(); + my $keys = &Apache::lonnet::reply('keys:'. + $ENV{'user.domain'}.':'. + $ENV{'user.name'}.':nohist_email', + $ENV{'user.home'}); + + foreach my $msgid (split(/\&/, $keys)) { + $msgid=&Apache::lonnet::unescape($msgid); + my $plain=&Apache::lonnet::unescape(&Apache::lonnet::unescape($msgid)); + if ($plain=~/(Error|Feedback) \[([^\]]+)\]/) { + my ($what,$url)=($1,$2); + my %status= + &Apache::lonnet::get('email_status',[$msgid]); + if ($status{$msgid}=~/^error\:/) { + $status{$msgid}=''; + } + + if (($status{$msgid} eq 'new') || + (!$status{$msgid})) { + if ($what eq 'Error') { + $error{$url}.=','.$msgid; + } else { + $feedback{$url}.=','.$msgid; + } + } + } } + + $self->{FEEDBACK} = \%feedback; + $self->{ERROR_MSG} = \%error; # what is this? JB + $self->{DISCUSSION_TIME} = \%discussiontime; + $self->{EMAIL_STATUS} = \%emailstatus; + + $self->{EMAIL_DISCUSS_GENERATED} = 1; +} - $self->{PARM_CACHE} = {}; - $self->{INITED} = 1; +sub get_user_data { + my $self = shift; + if ($self->{RETRIEVED_USER_DATA}) { return; } + + # Retrieve performance data on problems + my %student_data = Apache::lonnet::currentdump($ENV{'request.course.id'}, + $ENV{'user.domain'}, + $ENV{'user.name'}); + $self->{STUDENT_DATA} = \%student_data; + + $self->{RETRIEVED_USER_DATA} = 1; } # Internal function: Takes a key to look up in the nav hash and implements internal @@ -1810,6 +1826,15 @@ sub navhash { return $self->{NAV_HASH}->{$key}; } +=pod + +=item * B(): Returns true if the course map is defined, + false otherwise. Undefined course maps indicate an error somewhere in + LON-CAPA, and you will not be able to proceed with using the navmap. + See the B