Diff for /loncom/interface/loncoursedata.pm between versions 1.1 and 1.47

version 1.1, 2002/07/09 15:43:49 version 1.47, 2003/02/14 21:02:05
Line 35  loncoursedata Line 35  loncoursedata
   
 =head1 SYNOPSIS  =head1 SYNOPSIS
   
 Set of functions that download and process student information.  Set of functions that download and process student and course information.
   
 =head1 PACKAGES USED  =head1 PACKAGES USED
   
  Apache::Constants qw(:common :http)   Apache::Constants qw(:common :http)
  Apache::lonnet()   Apache::lonnet()
    Apache::lonhtmlcommon
  HTML::TokeParser   HTML::TokeParser
  GDBM_File   GDBM_File
   
Line 51  package Apache::loncoursedata; Line 52  package Apache::loncoursedata;
 use strict;  use strict;
 use Apache::Constants qw(:common :http);  use Apache::Constants qw(:common :http);
 use Apache::lonnet();  use Apache::lonnet();
   use Apache::lonhtmlcommon;
 use HTML::TokeParser;  use HTML::TokeParser;
 use GDBM_File;  use GDBM_File;
   
Line 58  use GDBM_File; Line 60  use GDBM_File;
   
 =head1 DOWNLOAD INFORMATION  =head1 DOWNLOAD INFORMATION
   
 This section contains all the files that get data from other servers   This section contains all the functions that get data from other servers 
 and/or itself.  There is one function that has a call to get remote  and/or itself.
 information but isn't included here which is ProcessTopLevelMap.  The  
 usage was small enough to be ignored, but that portion may be moved  
 here in the future.  
   
 =cut  =cut
   
Line 70  here in the future. Line 69  here in the future.
   
 =pod  =pod
   
 =item &DownloadNamePIDSection()  =item &DownloadClasslist()
   
 Collects lastname, generation, middlename, firstname, PID, and section for each  Collects lastname, generation, middlename, firstname, PID, and section for each
 student from their environment database.  The list of students is built from  student from their environment database.  The section data is also download, though
 collecting a classlist for the course that is to be displayed.  it is in a rough format, and is processed later.  The list of students is built from
   collecting a classlist for the course that is to be displayed.  Once the classlist
   has been downloaded, its date stamp is recorded.  Unless the datestamp for the
   class database is reset or is modified, this data will not be downloaded again.  
   Also, there was talk about putting the fullname and section
   and perhaps other pieces of data into the classlist file.  This would
   reduce the number of different file accesses and reduce the amount of 
   processing on this side.
   
 =over 4  =over 4
   
 Input: $courseID, $c  Input: $courseID, $lastDownloadTime, $c
   
 $courseID:  The id of the course  $courseID:  The id of the course
   
   $lastDownloadTime:  This is the date stamp for when this information was
   last gathered.  If it is set to Not downloaded, it will gather the data
   again, though it currently does not remove the old data.
   
 $c: The connection class that can determine if the browser has aborted.  It  $c: The connection class that can determine if the browser has aborted.  It
 is used to short circuit this function so that it doesn't continue to   is used to short circuit this function so that it does not continue to 
 get information when there is no need.  get information when there is no need.
   
 Output: \%classlist  Output: \%classlist
Line 93  Output: \%classlist Line 103  Output: \%classlist
 -A list of student name:domain (as keys) (known below as $name)  -A list of student name:domain (as keys) (known below as $name)
   
 -A hash pointer for each student containing lastname, generation, firstname,  -A hash pointer for each student containing lastname, generation, firstname,
 middlename, and PID : Key is $name.'studentInformation'  middlename, and PID : Key is $name.studentInformation
   
 -A hash pointer to each students section data : Key is $name.section  -A hash pointer to each students section data : Key is $name.section
   
   -If there was an error in dump, it will be returned in the hash.  See
   the error codes for dump in lonnet.  Also, an error key will be 
   generated if an abort occurs.
   
 =back  =back
   
 =cut  =cut
   
 sub DownloadStudentNamePIDSection {  sub DownloadClasslist {
     my ($courseID, $c)=@_;      my ($courseID, $lastDownloadTime, $c)=@_;
     my ($courseDomain,$courseNumber)=split(/\_/,$courseID);      my ($courseDomain,$courseNumber)=split(/\_/,$courseID);
       my %classlist;
   
     my %classlist=&Apache::lonnet::dump('classlist',$courseDomain,      my $modifiedTime = &Apache::lonnet::GetFileTimestamp($courseDomain, $courseNumber,
                                         $courseNumber);                                                           'classlist.db', 
     my ($checkForError)=keys (%classlist);                                                           $Apache::lonnet::perlvar{'lonUsersDir'});
     if($checkForError =~ /^(con_lost|error|no_such_host)/i) {  
       # Always download the information if lastDownloadTime is set to
       # Not downloaded, otherwise it is only downloaded if the file
       # has been updated and has a more recent date stamp
       if($lastDownloadTime ne 'Not downloaded' &&
          $lastDownloadTime >= $modifiedTime && $modifiedTime >= 0) {
           # Data is not gathered so return UpToDate as true.  This
           # will be interpreted in ProcessClasslist
           $classlist{'lastDownloadTime'}=time;
           $classlist{'UpToDate'} = 'true';
         return \%classlist;          return \%classlist;
     }      }
   
       %classlist=&Apache::lonnet::dump('classlist',$courseDomain, $courseNumber);
       foreach(keys (%classlist)) {
           if(/^(con_lost|error|no_such_host)/i) {
       return;
           }
       }
   
     foreach my $name (keys(%classlist)) {      foreach my $name (keys(%classlist)) {
         if($c->aborted()) {          if(defined($c) && ($c->aborted())) {
             $classlist{'error'}='aborted';              $classlist{'error'}='aborted';
             return \%classlist;              return \%classlist;
         }          }
Line 135  sub DownloadStudentNamePIDSection { Line 166  sub DownloadStudentNamePIDSection {
   
         #Section          #Section
         my %section=&Apache::lonnet::dump('roles',$studentDomain,$studentName);          my %section=&Apache::lonnet::dump('roles',$studentDomain,$studentName);
         $classlist{$name.':section'}=\%section;          $classlist{$name.':sections'}=\%section;
     }      }
   
       $classlist{'UpToDate'} = 'false';
       $classlist{'lastDownloadTime'}=time;
   
     return \%classlist;      return \%classlist;
 }  }
   
 =pod  =pod
   
 =item &DownloadStudentCourseInformation()  =item &DownloadCourseInformation()
   
 Dump of all the course information for a single student.  There is no  Dump of all the course information for a single student.  The data can be
 pruning of data, it is all stored in a hash and returned.  pruned by making use of dumps regular expression arguement.  This function
   also takes a regular expression which it passes straight through to dump.  
   The data is no escaped, because it is done elsewhere.  It also
   checks the timestamp of the students course database file and only downloads
   if it has been modified since the last download.
   
 =over 4  =over 4
   
 Input: $name, $courseID  Input: $namedata, $courseID, $lastDownloadTime, $WhatIWant
   
 $name: student name:domain  $namedata: student name:domain
   
 $courseID:  The id of the course  $courseID:  The id of the course
   
   $lastDownloadTime:  This is the date stamp for when this information was
   last gathered.  If it is set to Not downloaded, it will gather the data
   again, though it currently does not remove the old data.
   
   $WhatIWant:  Regular expression used to get selected data with dump
   
 Output: \%courseData  Output: \%courseData
   
 \%courseData:  A hash pointer to the raw data from the student's course  \%courseData:  A hash pointer to the raw data from the students course
 database.  database.
   
 =back  =back
   
 =cut  =cut
   
 sub DownloadStudentCourseInformation {  sub DownloadCourseInformation {
     my ($name,$courseID)=@_;      my ($namedata,$courseID,$lastDownloadTime,$WhatIWant)=@_;
     my ($studentName,$studentDomain) = split(/\:/,$name);      my %courseData;
       my ($name,$domain) = split(/\:/,$namedata);
     # Download student course data  
     my %courseData=&Apache::lonnet::dump($courseID,$studentDomain,      my $modifiedTime = &Apache::lonnet::GetFileTimestamp($domain, $name,
  $studentName);                                        $courseID.'.db', 
     return \%courseData;                                        $Apache::lonnet::perlvar{'lonUsersDir'});
   
       if($lastDownloadTime ne 'Not downloaded' && 
          $lastDownloadTime >= $modifiedTime && $modifiedTime >= 0) {
           # Data is not gathered so return UpToDate as true.  This
           # will be interpreted in ProcessClasslist
           $courseData{$namedata.':lastDownloadTime'}=time;
           $courseData{$namedata.':UpToDate'} = 'true';
           return \%courseData;
       }
   
       # Download course data
       if(!defined($WhatIWant)) {
           # set the regular expression to everything by setting it to period
           $WhatIWant = '.';
       }
       %courseData=&Apache::lonnet::dump($courseID, $domain, $name, $WhatIWant);
       $courseData{'UpToDate'} = 'false';
       $courseData{'lastDownloadTime'}=time;
   
       my %newData;
       foreach (keys(%courseData)) {
           # need to have the keys to be prepended with the name:domain of the
           # student to reduce data collision later.
           $newData{$namedata.':'.$_} = $courseData{$_};
       }
   
       return \%newData;
 }  }
   
 # ----- END DOWNLOAD INFORMATION ---------------------------------------  # ----- END DOWNLOAD INFORMATION ---------------------------------------
Line 182  sub DownloadStudentCourseInformation { Line 253  sub DownloadStudentCourseInformation {
 =head1 PROCESSING FUNCTIONS  =head1 PROCESSING FUNCTIONS
   
 These functions process all the data for all the students.  Also, they  These functions process all the data for all the students.  Also, they
 are the only functions that access the cache database for writing.  Thus  are the functions that access the cache database for writing the majority of
 they are the only functions that cache data.  The downloading and caching  the time.  The downloading and caching were separated to reduce problems 
 were separated to reduce problems with stopping downloading then can't  with stopping downloading then can not tie hash to database later.
 tie hash to database later.  
   
 =cut  =cut
   
 # ----- PROCESSING FUNCTIONS ---------------------------------------  # ----- PROCESSING FUNCTIONS ---------------------------------------
   
   
   
   =pod
   
   =item &get_sequence_assessment_data()
   
   AT THIS TIME THE USE OF THIS FUNCTION IS *NOT* RECOMMENDED
   
   Use lonnavmaps to build a data structure describing the order and 
   assessment contents of each sequence in the current course.
   
   The returned structure is a hash reference. 
   
   { title  => 'title',
     symb   => 'symb',
     source => '/s/o/u/r/c/e',
     type  => (container|assessment),
     contents     => [ {},{},{},{} ], # only for container
     parts        => [11,13,15],      # only for assessment
     response_ids => [12,14,16]       # only for assessment
   }
   
   $hash->{'contents'} is a reference to an array of hashes of the same structure.
   
   =cut
   
   sub get_sequence_assessment_data {
       return undef;
       my $fn=$ENV{'request.course.fn'};
       &Apache::lonnet::logthis('filename = '.$fn);
       ##
       ## use navmaps
       my $navmap = Apache::lonnavmaps::navmap->new($fn.".db",$fn."_parms.db",
                                                    1,0);
       if (!defined($navmap)) {
           return 'Can not open Coursemap';
       }
       my $iterator = $navmap->getIterator(undef, undef, undef, 1);
       ##
       ## Prime the pump 
       ## 
       ## We are going to loop until we run out of sequences/pages to explore for
       ## resources.  This means we have to start out with something to look
       ## at.
       my $curRes = $iterator->next(); # BEGIN_MAP
       $curRes = $iterator->next(); # The sequence itself
       #
       my $title = $curRes->title();
       my $symb  = $curRes->symb();
       my $src   = $curRes->src();
       #
       my @Nested_Sequences = ();   # Stack of sequences, keeps track of depth
       my $top = { title    => $title,
                   symb     => $symb,
                   type     => 'container',
                   num_assess => 0,
                   contents   => [], };
       push (@Nested_Sequences, $top);
       #
       # We need to keep track of which sequences contain homework problems
       # 
       while (scalar(@Nested_Sequences)) {
           $curRes = $iterator->next();
           my $currentmap = $Nested_Sequences[-1]; # Last one on the stack
           if ($curRes == $iterator->BEGIN_MAP()) {
               # get the map itself, instead of BEGIN_MAP
               $curRes = $iterator->next(); 
               $title = $curRes->title();
               $symb  = $curRes->symb();
               $src   = $curRes->src();
               my $newmap = { title    => $title,
                              src      => $src,
                              symb     => $symb,
                              type     => 'container',
                              num_assess => 0,
                              contents   => [],
                          };
               push (@{$currentmap->{'contents'}},$newmap); # this is permanent
               push (@Nested_Sequences, $newmap); # this is a stack
               next;
           }
           if ($curRes == $iterator->END_MAP()) {
               pop(@Nested_Sequences);
               next;
           }
           next if (! ref($curRes));
           next if (! $curRes->is_problem() && !$curRes->randomout);
           # Okay, from here on out we only deal with assessments
           $title = $curRes->title();
           $symb  = $curRes->symb();
           $src   = $curRes->src();
           my $parts = $curRes->parts();
           my $assessment = { title => $title,
                              src   => $src,
                              symb  => $symb,
                              type  => 'assessment',
                          };
           push(@{$currentmap->{'contents'}},$assessment);
           $currentmap->{'num_assess'}++;
       }
       return $top;
   }
   
 =pod  =pod
   
 =item &ProcessTopResourceMap()  =item &ProcessTopResourceMap()
Line 227  sub ProcessTopResourceMap { Line 400  sub ProcessTopResourceMap {
             if($c->aborted()) {              if($c->aborted()) {
                 return;                  return;
             }              }
     if(tie(%hash,'GDBM_File',"$fn.db",&GDBM_READER,0640)) {      if(tie(%hash,'GDBM_File',"$fn.db",&GDBM_READER(),0640)) {
  last;   last;
     }      }
     $tieTries++;      $tieTries++;
Line 240  sub ProcessTopResourceMap { Line 413  sub ProcessTopResourceMap {
         return 'Can not open Coursemap.';          return 'Can not open Coursemap.';
     }      }
   
       my $oldkeys;
       delete $cache->{'OptionResponses'};
       if(defined($cache->{'ResourceKeys'})) {
           $oldkeys = $cache->{'ResourceKeys'};
           foreach (split(':::', $cache->{'ResourceKeys'})) {
               delete $cache->{$_};
           }
           delete $cache->{'ResourceKeys'};
       }
   
     # Initialize state machine.  Set information pointing to top level map.      # Initialize state machine.  Set information pointing to top level map.
     my (@sequences, @currentResource, @finishResource);      my (@sequences, @currentResource, @finishResource);
     my ($currentSequence, $currentResourceID, $lastResourceID);      my ($currentSequence, $currentResourceID, $lastResourceID);
   
     $currentResourceID=$hash{'ids_/res/'.$ENV{'request.course.uri'}};      $currentResourceID=$hash{'ids_'.
         &Apache::lonnet::clutter($ENV{'request.course.uri'})};
     push(@currentResource, $currentResourceID);      push(@currentResource, $currentResourceID);
     $lastResourceID=-1;      $lastResourceID=-1;
     $currentSequence=-1;      $currentSequence=-1;
     my $topLevelSequenceNumber = $currentSequence;      my $topLevelSequenceNumber = $currentSequence;
   
       my %sequenceRecord;
       my %allkeys;
     while(1) {      while(1) {
         if($c->aborted()) {          if($c->aborted()) {
             last;              last;
         }          }
  # HANDLE NEW SEQUENCE!   # HANDLE NEW SEQUENCE!
  #if page || sequence   #if page || sequence
  if(defined($hash{'map_pc_'.$hash{'src_'.$currentResourceID}})) {   if(defined($hash{'map_pc_'.$hash{'src_'.$currentResourceID}}) &&
              !defined($sequenceRecord{$currentResourceID})) {
               $sequenceRecord{$currentResourceID}++;
     push(@sequences, $currentSequence);      push(@sequences, $currentSequence);
     push(@currentResource, $currentResourceID);      push(@currentResource, $currentResourceID);
     push(@finishResource, $lastResourceID);      push(@finishResource, $lastResourceID);
Line 272  sub ProcessTopResourceMap { Line 460  sub ProcessTopResourceMap {
             } else {              } else {
                 $cache->{'orderedSequences'}.=':'.$currentSequence;                  $cache->{'orderedSequences'}.=':'.$currentSequence;
             }              }
               $allkeys{'orderedSequences'}++;
   
     $lastResourceID=$hash{'map_finish_'.      $lastResourceID=$hash{'map_finish_'.
   $hash{'src_'.$currentResourceID}};    $hash{'src_'.$currentResourceID}};
Line 286  sub ProcessTopResourceMap { Line 475  sub ProcessTopResourceMap {
     last;      last;
  }   }
     }      }
               next;
  }   }
   
  # Handle gradable resources: exams, problems, etc   # Handle gradable resources: exams, problems, etc
Line 294  sub ProcessTopResourceMap { Line 484  sub ProcessTopResourceMap {
         my $partB=$2;          my $partB=$2;
  if($hash{'src_'.$currentResourceID}=~   if($hash{'src_'.$currentResourceID}=~
    /\.(problem|exam|quiz|assess|survey|form)$/ &&     /\.(problem|exam|quiz|assess|survey|form)$/ &&
    $partA eq $currentSequence) {     $partA eq $currentSequence && 
              !defined($sequenceRecord{$currentSequence.':'.
                                       $currentResourceID})) {
               $sequenceRecord{$currentSequence.':'.$currentResourceID}++;
     my $Problem = &Apache::lonnet::symbclean(      my $Problem = &Apache::lonnet::symbclean(
   &Apache::lonnet::declutter($hash{'map_id_'.$partA}).    &Apache::lonnet::declutter($hash{'map_id_'.$partA}).
   '___'.$partB.'___'.    '___'.$partB.'___'.
Line 302  sub ProcessTopResourceMap { Line 495  sub ProcessTopResourceMap {
  $currentResourceID}));   $currentResourceID}));
   
     $cache->{$currentResourceID.':problem'}=$Problem;      $cache->{$currentResourceID.':problem'}=$Problem;
               $allkeys{$currentResourceID.':problem'}++;
     if(!defined($cache->{$currentSequence.':problems'})) {      if(!defined($cache->{$currentSequence.':problems'})) {
  $cache->{$currentSequence.':problems'}=$currentResourceID;   $cache->{$currentSequence.':problems'}=$currentResourceID;
     } else {      } else {
  $cache->{$currentSequence.':problems'}.=   $cache->{$currentSequence.':problems'}.=
     ':'.$currentResourceID;      ':'.$currentResourceID;
     }      }
               $allkeys{$currentSequence.':problems'}++;
   
             # Get Parts for problem  
     my $meta=$hash{'src_'.$currentResourceID};      my $meta=$hash{'src_'.$currentResourceID};
     foreach (split(/\,/,&Apache::lonnet::metadata($meta,'keys'))) {  #            $cache->{$currentResourceID.':title'}=
  if($_=~/^stores\_(\d+)\_tries$/) {  #                &Apache::lonnet::metdata($meta,'title');
     my $Part=&Apache::lonnet::metadata($meta,$_.'.part');              $cache->{$currentResourceID.':title'}=
                     if(!defined($cache->{$currentSequence.':'.                  $hash{'title_'.$currentResourceID};
                                           $currentResourceID.':parts'})) {              $allkeys{$currentResourceID.':title'}++;
                         $cache->{$currentSequence.':'.$currentResourceID.              $cache->{$currentResourceID.':source'}=
                                    ':parts'}=$Part;                  $hash{'src_'.$currentResourceID};
                     } else {              $allkeys{$currentResourceID.':source'}++;
                         $cache->{$currentSequence.':'.$currentResourceID.  
                                    ':parts'}.=':'.$Part;              # Get Parts for problem
               my %beenHere;
               foreach (split(/\,/,&Apache::lonnet::metadata($meta,'packages'))) {
                   if(/^\w+response_\d+.*/) {
                       my (undef, $partId, $responseId) = split(/_/,$_);
                       if($beenHere{'p:'.$partId} ==  0) {
                           $beenHere{'p:'.$partId}++;
                           if(!defined($cache->{$currentSequence.':'.
                                               $currentResourceID.':parts'})) {
                               $cache->{$currentSequence.':'.$currentResourceID.
                                        ':parts'}=$partId;
                           } else {
                               $cache->{$currentSequence.':'.$currentResourceID.
                                        ':parts'}.=':'.$partId;
                           }
                           $allkeys{$currentSequence.':'.$currentResourceID.
                                     ':parts'}++;
                     }                      }
  }                      if($beenHere{'r:'.$partId.':'.$responseId} == 0) {
     }                          $beenHere{'r:'.$partId.':'.$responseId}++;
  }                          if(!defined($cache->{$currentSequence.':'.
                                                $currentResourceID.':'.$partId.
                                                ':responseIDs'})) {
                               $cache->{$currentSequence.':'.$currentResourceID.
                                        ':'.$partId.':responseIDs'}=$responseId;
                           } else {
                               $cache->{$currentSequence.':'.$currentResourceID.
                                        ':'.$partId.':responseIDs'}.=':'.
                                                                     $responseId;
                           }
                           $allkeys{$currentSequence.':'.$currentResourceID.':'.
                                        $partId.':responseIDs'}++;
                       }
                       if(/^optionresponse/ && 
                          $beenHere{'o:'.$partId.':'.$currentResourceID} == 0) {
                           $beenHere{'o:'.$partId.$currentResourceID}++;
                           if(defined($cache->{'OptionResponses'})) {
                               $cache->{'OptionResponses'}.= ':::'.
                                   $currentSequence.':'.$currentResourceID.':'.
                                   $partId.':'.$responseId;
                           } else {
                               $cache->{'OptionResponses'}= $currentSequence.':'.
                                   $currentResourceID.':'.
                                   $partId.':'.$responseId;
                           }
                           $allkeys{'OptionResponses'}++;
                       }
                   }
               }
           }
   
  # if resource == finish resource, then it is the end of a sequence/page   # if resource == finish resource, then it is the end of a sequence/page
  if($currentResourceID eq $lastResourceID) {   if($currentResourceID eq $lastResourceID) {
Line 336  sub ProcessTopResourceMap { Line 575  sub ProcessTopResourceMap {
  # Capture sequence information here   # Capture sequence information here
  $cache->{$currentSequence.':title'}=   $cache->{$currentSequence.':title'}=
     $hash{'title_'.$currentResourceID};      $hash{'title_'.$currentResourceID};
                   $allkeys{$currentSequence.':title'}++;
                   $cache->{$currentSequence.':source'}=
                       $hash{'src_'.$currentResourceID};
                   $allkeys{$currentSequence.':source'}++;
   
                 my $totalProblems=0;                  my $totalProblems=0;
                 foreach my $currentProblem (split(/\:/,                  foreach my $currentProblem (split(/\:/,
Line 349  sub ProcessTopResourceMap { Line 592  sub ProcessTopResourceMap {
                 }                  }
  my @titleLength=split(//,$cache->{$currentSequence.   my @titleLength=split(//,$cache->{$currentSequence.
                                                     ':title'});                                                      ':title'});
                 # $extra is 3 for problems correct and 3 for space                  # $extra is 5 for problems correct and 3 for space
                 # between problems correct and problem output                  # between problems correct and problem output
                 my $extra = 6;                  my $extra = 8;
  if(($totalProblems + $extra) > (scalar @titleLength)) {   if(($totalProblems + $extra) > (scalar @titleLength)) {
     $cache->{$currentSequence.':columnWidth'}=      $cache->{$currentSequence.':columnWidth'}=
                         $totalProblems + $extra;                          $totalProblems + $extra;
Line 359  sub ProcessTopResourceMap { Line 602  sub ProcessTopResourceMap {
     $cache->{$currentSequence.':columnWidth'}=      $cache->{$currentSequence.':columnWidth'}=
                         (scalar @titleLength);                          (scalar @titleLength);
  }   }
                   $allkeys{$currentSequence.':columnWidth'}++;
     } else {      } else {
                 # Remove sequence from list, if it contains no problems to                  # Remove sequence from list, if it contains no problems to
                 # display.                  # display.
Line 371  sub ProcessTopResourceMap { Line 615  sub ProcessTopResourceMap {
     if($currentSequence eq $topLevelSequenceNumber) {      if($currentSequence eq $topLevelSequenceNumber) {
  last;   last;
     }      }
  }          }
   
  # MOVE!!!   # MOVE!!!
  # move to next resource   # move to next resource
  unless(defined($hash{'to_'.$currentResourceID})) {   unless(defined($hash{'to_'.$currentResourceID})) {
     # big problem, need to handle.  Next is probably wrong      # big problem, need to handle.  Next is probably wrong
     last;              my $errorMessage = 'Big problem in ';
               $errorMessage .= 'loncoursedata::ProcessTopLevelMap.';
               $errorMessage .= "  bighash to_$currentResourceID not defined!";
               &Apache::lonnet::logthis($errorMessage);
       if (!defined($currentResourceID)) {last;}
  }   }
  my @nextResources=();   my @nextResources=();
  foreach (split(/\,/,$hash{'to_'.$currentResourceID})) {   foreach (split(/\,/,$hash{'to_'.$currentResourceID})) {
     push(@nextResources, $hash{'goesto_'.$_});              if(!defined($sequenceRecord{$currentSequence.':'.
                                           $hash{'goesto_'.$_}})) {
                   push(@nextResources, $hash{'goesto_'.$_});
               }
  }   }
  push(@currentResource, @nextResources);   push(@currentResource, @nextResources);
  # Set the next resource to be processed   # Set the next resource to be processed
  $currentResourceID=pop(@currentResource);   $currentResourceID=pop(@currentResource);
     }      }
   
       my @theKeys = keys(%allkeys);
       my $newkeys = join(':::', @theKeys);
       $cache->{'ResourceKeys'} = join(':::', $newkeys);
       if($newkeys ne $oldkeys) {
           $cache->{'ResourceUpdated'} = 'true';
       } else {
           $cache->{'ResourceUpdated'} = 'false';
       }
   
     unless (untie(%hash)) {      unless (untie(%hash)) {
         &Apache::lonnet::logthis("<font color=blue>WARNING: ".          &Apache::lonnet::logthis("<font color=blue>WARNING: ".
                                  "Could not untie coursemap $fn (browse)".                                   "Could not untie coursemap $fn (browse)".
Line 399  sub ProcessTopResourceMap { Line 659  sub ProcessTopResourceMap {
   
 =pod  =pod
   
 =item &ProcessSection()  =item &ProcessClasslist()
   
 Determine the section number for a student for the class.  A student can have   
 multiple sections for the same class.  The correct one is chosen.  
   
 =over 4  
   
 Input: $sectionData, $courseid, $ActiveFlag  
   
 $sectionData:  A pointer to a hash containing all section data for this   
 student for the class  
   
 $courseid:  The course ID.  
   
 $ActiveFlag:  The student's active status (Active/Expired)  
   
 Output: $oldsection, $cursection, or -1  
   
 $oldsection and $cursection and sections number that will be displayed in the   
 chart.  
   
 -1 is returned if an error occurs.  
   
 =back  
   
 =cut  
   
 sub ProcessSection {  Taking the class list dumped from &DownloadClasslist(), all the 
     my ($sectionData,$courseid,$ActiveFlag)=@_;  students and their non-class information is processed using the 
     $courseid=~s/\_/\//g;  &ProcessStudentInformation() function.  A date stamp is also recorded for
     $courseid=~s/^(\w)/\/$1/;  when the data was processed.
   
     my $cursection='-1';  
     my $oldsection='-1';  
     my $status='Expired';  
     my $section='';  
     foreach my $key (keys (%$sectionData)) {  
  my $value = $sectionData->{$key};  
         if ($key=~/^$courseid(?:\/)*(\w+)*\_st$/) {  
     $section=$1;  
     if($key eq $courseid.'_st') {  
  $section='';  
     }  
     my ($dummy,$end,$start)=split(/\_/,$value);  
     my $now=time;  
     my $notactive=0;  
     if ($start) {  
  if($now<$start) {  
     $notactive=1;  
  }  
     }  
     if($end) {  
  if ($now>$end) {  
     $notactive=1;  
  }  
     }  
     if($notactive == 0) {  
  $status='Active';  
  $cursection=$section;  
  last;  
     }  
     if($notactive == 1) {  
  $oldsection=$section;  
     }  
  }  
     }  
     if($status eq $ActiveFlag) {  
  if($cursection eq '-1') {  
     return $oldsection;  
  }  
  return $cursection;  
     }  
     if($ActiveFlag eq 'Any') {  
  if($cursection eq '-1') {  
     return $oldsection;  
  }  
  return $cursection;  
     }  
     return '-1';  
 }  
   
 =pod  
   
 =item &ProcessNamePIDSection()  
   
 Takes data downloaded for a student and breaks it up into managable pieces and   Takes data downloaded for a student and breaks it up into managable pieces and 
 stored in cache data.  The username, domain, class related date, PID,   stored in cache data.  The username, domain, class related date, PID, 
Line 490  full name, and section are all processed Line 672  full name, and section are all processed
   
 =over 4  =over 4
   
 Input: $cache, $studentInformation, $section, $date, $name, $courseID  
   
 $cache:  A hash pointer to store the data  
   
 $studentInformation:  Student information is what was requested in   
 &DownloadPrerequistedData().  See that function for what data is requested.  
   
 $section: A hash pointer to class section related information.  
   
 $date:  A composite of the start and end date for this class for this  
 student.  Format:  end:start  
   
 $name:  the username:domain information  
   
 $courseID: The course ID  
   
 Output: None  
   
 *NOTE:  There is no return value, but if an error occurs a key is added to   
 the cache data with the value being the error message.  The key is   
 username:domain:error.  It will only exist if an error occurs.  
   
 =back  
   
 =cut  
   
 sub ProcessStudentNamePIDSection {  
     my ($cache,$studentInformation,$section,$date,$name,$courseID,$status)=@_;  
     my ($studentName,$studentDomain) = split(/\:/,$name);  
   
     $cache->{$name.':username'}=$studentName;  
     $cache->{$name.':domain'}=$studentDomain;  
     $cache->{$name.':date'}=$date;  
   
     my ($checkForError)=keys(%$studentInformation);  
     if($checkForError =~ /^(con_lost|error|no_such_host)/i) {  
  $cache->{$name.':error'}=  
     'Could not download student environment data.';  
  $cache->{$name.':fullname'}='';  
  $cache->{$name.':id'}='';  
     } else {  
  $cache->{$name.':fullname'}=&ProcessFullName(  
                                           $studentInformation->{'lastname'},  
           $studentInformation->{'generation'},  
           $studentInformation->{'firstname'},  
                                           $studentInformation->{'middlename'});  
  $cache->{$name.':id'}=$studentInformation->{'id'};  
     }  
   
     # Get student's section number  
     my $sec=&ProcessSection($section, $courseID, $status);  
     if($sec != -1) {  
  $cache->{$name.':section'}=$sec;  
     } else {  
  $cache->{$name.':section'}='';  
     }  
   
     return;  
 }  
   
 =pod  
   
 =item &ProcessClassList()  
   
 Taking the class list dumped from &DownloadPrerequisiteData(), all the   
 students and their non-class information is processed using the   
 &ProcessStudentInformation() function.  A date stamp is also recorded for  
 when the data was processed.  
   
 =over 4  
   
 Input: $cache, $classlist, $courseID, $ChartDB, $c  Input: $cache, $classlist, $courseID, $ChartDB, $c
   
 $cache: A hash pointer to store the data  $cache: A hash pointer to store the data
   
 $classlist:  The hash of data collected about a student from   $classlist:  The hash of data collected about a student from 
 &DownloadPrerequisteData().  The hash contains a list of students, a pointer   &DownloadClasslist().  The hash contains a list of students, a pointer 
 to a hash of student information for each student, and each student's section   to a hash of student information for each student, and each students section 
 number.  number.
   
 $courseID:  The course ID  $courseID:  The course ID
Line 580  browser Line 691  browser
 Output: @names  Output: @names
   
 @names:  An array of students whose information has been processed, and are to   @names:  An array of students whose information has been processed, and are to 
 be considered in an arbitrary order.  be considered in an arbitrary order.  The entries in @names are of the form
   username:domain.
   
   The values in $cache are as follows:
   
    *NOTE: for the following $name implies username:domain
    $name.':error'                  only defined if an error occured.  Value
                                    contains the error message
    $name.':lastDownloadTime'       unconverted time of the last update of a
                                    student\'s course data
    $name.'updateTime'              coverted time of the last update of a 
                                    student\'s course data
    $name.':username'               username of a student
    $name.':domain'                 domain of a student
    $name.':fullname'               full name of a student
    $name.':id'                     PID of a student
    $name.':Status'                 active/expired status of a student
    $name.':section'                section of a student
   
 =back  =back
   
 =cut  =cut
   
 sub ProcessClassList {  sub ProcessClasslist {
     my ($cache,$classlist,$courseID,$status,$c)=@_;      my ($cache,$classlist,$courseID,$c)=@_;
     my @names=();      my @names=();
   
       $cache->{'ClasslistTimeStamp'}=$classlist->{'lastDownloadTime'};
       if($classlist->{'UpToDate'} eq 'true') {
           return split(/:::/,$cache->{'NamesOfStudents'});;
       }
   
     foreach my $name (keys(%$classlist)) {      foreach my $name (keys(%$classlist)) {
         if($name =~ /\:section/ || $name =~ /\:studentInformation/ ||          if($name =~ /\:section/ || $name =~ /\:studentInformation/ ||
            $name eq '') {             $name eq '' || $name eq 'UpToDate' || $name eq 'lastDownloadTime') {
             next;              next;
         }          }
         if($c->aborted()) {          if($c->aborted()) {
             last;              return ();
           }
           my $studentInformation = $classlist->{$name.':studentInformation'};
           my $date = $classlist->{$name};
           my ($studentName,$studentDomain) = split(/\:/,$name);
   
           $cache->{$name.':username'}=$studentName;
           $cache->{$name.':domain'}=$studentDomain;
           # Initialize timestamp for student
           if(!defined($cache->{$name.':lastDownloadTime'})) {
               $cache->{$name.':lastDownloadTime'}='Not downloaded';
               $cache->{$name.':updateTime'}=' Not updated';
         }          }
   
           my $error = 0;
           foreach(keys(%$studentInformation)) {
               if(/^(con_lost|error|no_such_host)/i) {
                   $cache->{$name.':error'}=
                       'Could not download student environment data.';
                   $cache->{$name.':fullname'}='';
                   $cache->{$name.':id'}='';
                   $error = 1;
               }
           }
           next if($error);
         push(@names,$name);          push(@names,$name);
         &ProcessStudentNamePIDSection($cache,          $cache->{$name.':fullname'}=&ProcessFullName(
                                      $classlist->{$name.':studentInformation'},                                            $studentInformation->{'lastname'},
                                      $classlist->{$name.':section'},                                            $studentInformation->{'generation'},
                                      $classlist->{$name},                                            $studentInformation->{'firstname'},
                                      $name,$courseID,$status);                                            $studentInformation->{'middlename'});
           $cache->{$name.':id'}=$studentInformation->{'id'};
   
           my ($end, $start)=split(':',$date);
           $courseID=~s/\_/\//g;
           $courseID=~s/^(\w)/\/$1/;
   
           my $sec='';
           my $sectionData = $classlist->{$name.':sections'};
           foreach my $key (keys (%$sectionData)) {
               my $value = $sectionData->{$key};
               if ($key=~/^$courseID(?:\/)*(\w+)*\_st$/) {
                   my $tempsection=$1;
                   if($key eq $courseID.'_st') {
                       $tempsection='';
                   }
                   my (undef,$roleend,$rolestart)=split(/\_/,$value);
                   if($roleend eq $end && $rolestart eq $start) {
                       $sec = $tempsection;
                       last;
                   }
               }
           }
   
           my $status='Expired';
           if(((!$end) || time < $end) && ((!$start) || (time > $start))) {
               $status='Active';
           }
           $cache->{$name.':Status'}=$status;
           $cache->{$name.':section'}=$sec;
   
           if($sec eq '' || !defined($sec) || $sec eq ' ') {
               $sec = 'none';
           }
           if(defined($cache->{'sectionList'})) {
               if($cache->{'sectionList'} !~ /(^$sec:|^$sec$|:$sec$|:$sec:)/) {
                   $cache->{'sectionList'} .= ':'.$sec;
               }
           } else {
               $cache->{'sectionList'} = $sec;
           }
     }      }
   
     # Time of download      $cache->{'ClasslistTimestamp'}=time;
     $cache->{'time'}=localtime();      $cache->{'NamesOfStudents'}=join(':::',@names);
   
     return @names;      return @names;
 }  }
Line 617  sub ProcessClassList { Line 813  sub ProcessClassList {
 =item &ProcessStudentData()  =item &ProcessStudentData()
   
 Takes the course data downloaded for a student in   Takes the course data downloaded for a student in 
 &DownloadStudentCourseInformation() and breaks it up into key value pairs  &DownloadCourseInformation() and breaks it up into key value pairs
 to be stored in the cached data.  The keys are comprised of the   to be stored in the cached data.  The keys are comprised of the 
 $username:$domain:$keyFromCourseDatabase.  The student username:domain is  $username:$domain:$keyFromCourseDatabase.  The student username:domain is
 stored away signifying that the student's information has been downloaded and   stored away signifying that the students information has been downloaded and 
 can be reused from cached data.  can be reused from cached data.
   
 =over 4  =over 4
Line 639  Output: None Line 835  Output: None
 *NOTE:  There is no output, but an error message is stored away in the cache   *NOTE:  There is no output, but an error message is stored away in the cache 
 data.  This is checked in &FormatStudentData().  The key username:domain:error   data.  This is checked in &FormatStudentData().  The key username:domain:error 
 will only exist if an error occured.  The error is an error from   will only exist if an error occured.  The error is an error from 
 &DownloadStudentCourseInformation().  &DownloadCourseInformation().
   
 =back  =back
   
Line 648  will only exist if an error occured.  Th Line 844  will only exist if an error occured.  Th
 sub ProcessStudentData {  sub ProcessStudentData {
     my ($cache,$courseData,$name)=@_;      my ($cache,$courseData,$name)=@_;
   
     my ($checkForError) = keys(%$courseData);      if(!&CheckDateStampError($courseData, $cache, $name)) {
     if($checkForError =~ /^(con_lost|error|no_such_host)/i) {          return;
         $cache->{$name.':error'}='Could not download course data.';      }
     } else {  
         foreach my $key (keys (%$courseData)) {      # This little delete thing, should not be here.  Move some other
             $cache->{$name.':'.$key}=$courseData->{$key};      # time though.
         }      if(defined($cache->{$name.':keys'})) {
         if(defined($cache->{'NamesOfStudents'})) {   foreach (split(':::', $cache->{$name.':keys'})) {
             $cache->{'NamesOfStudents'}.=':::'.$name;      delete $cache->{$name.':'.$_};
         } else {   }
             $cache->{'NamesOfStudents'}=$name;          delete $cache->{$name.':keys'};
       }
   
       my %courseKeys;
       # user name:domain was prepended earlier in DownloadCourseInformation
       foreach (keys %$courseData) {
    my $currentKey = $_;
    $currentKey =~ s/^$name//;
    $courseKeys{$currentKey}++;
           $cache->{$_}=$courseData->{$_};
       }
   
       $cache->{$name.':keys'} = join(':::', keys(%courseKeys));
   
       return;
   }
   
   =pod
   
   =item &ExtractStudentData()
   
   HISTORY: This function originally existed in every statistics module,
   and performed different tasks, the had some overlap.  Due to the need
   for the data from the different modules, they were combined into
   a single function.
   
   This function now extracts all the necessary course data for a student
   from what was downloaded from their homeserver.  There is some extra
   time overhead compared to the ProcessStudentInformation function, but
   it would have had to occurred at some point anyways.  This is now
   typically called while downloading the data it will process.  It is
   the brother function to ProcessStudentInformation.
   
   =over 4
   
   Input: $input, $output, $data, $name
   
   $input: A hash that contains the input data to be processed
   
   $output: A hash to contain the processed data
   
   $data: A hash containing the information on what is to be
   processed and how (basically).
   
   $name:  username:domain
   
   The input is slightly different here, but is quite simple.
   It is currently used where the $input, $output, and $data
   can and are often the same hashes, but they do not need
   to be.
   
   Output: None
   
   *NOTE:  There is no output, but an error message is stored away in the cache 
   data.  This is checked in &FormatStudentData().  The key username:domain:error 
   will only exist if an error occured.  The error is an error from 
   &DownloadCourseInformation().
   
   =back
   
   =cut
   
   sub ExtractStudentData {
       my ($input, $output, $data, $name)=@_;
   
       if(!&CheckDateStampError($input, $data, $name)) {
           return;
       }
   
       # This little delete thing, should not be here.  Move some other
       # time though.
       my %allkeys;
       if(defined($output->{$name.':keys'})) {
    foreach (split(':::', $output->{$name.':keys'})) {
       delete $output->{$name.':'.$_};
    }
           delete $output->{$name.':keys'};
       }
   
       my ($username,$domain)=split(':',$name);
   
       my $Version;
       my $problemsCorrect = 0;
       my $totalProblems   = 0;
       my $problemsSolved  = 0;
       my $numberOfParts   = 0;
       my $totalAwarded    = 0;
       foreach my $sequence (split(':', $data->{'orderedSequences'})) {
           foreach my $problemID (split(':', $data->{$sequence.':problems'})) {
               my $problem = $data->{$problemID.':problem'};
               my $LatestVersion = $input->{$name.':version:'.$problem};
   
               # Output dashes for all the parts of this problem if there
               # is no version information about the current problem.
               $output->{$name.':'.$problemID.':NoVersion'} = 'false';
               $allkeys{$name.':'.$problemID.':NoVersion'}++;
               if(!$LatestVersion) {
                   foreach my $part (split(/\:/,$data->{$sequence.':'.
                                                         $problemID.
                                                         ':parts'})) {
                       $output->{$name.':'.$problemID.':'.$part.':tries'} = 0;
                       $output->{$name.':'.$problemID.':'.$part.':awarded'} = 0;
                       $output->{$name.':'.$problemID.':'.$part.':code'} = ' ';
       $allkeys{$name.':'.$problemID.':'.$part.':tries'}++;
       $allkeys{$name.':'.$problemID.':'.$part.':awarded'}++;
       $allkeys{$name.':'.$problemID.':'.$part.':code'}++;
                       $totalProblems++;
                   }
                   $output->{$name.':'.$problemID.':NoVersion'} = 'true';
                   next;
               }
   
               my %partData=undef;
               # Initialize part data, display skips correctly
               # Skip refers to when a student made no submissions on that
               # part/problem.
               foreach my $part (split(/\:/,$data->{$sequence.':'.
                                                    $problemID.
                                                    ':parts'})) {
                   $partData{$part.':tries'}=0;
                   $partData{$part.':code'}=' ';
                   $partData{$part.':awarded'}=0;
                   $partData{$part.':timestamp'}=0;
                   foreach my $response (split(':', $data->{$sequence.':'.
                                                            $problemID.':'.
                                                            $part.':responseIDs'})) {
                       $partData{$part.':'.$response.':submission'}='';
                   }
               }
   
               # Looping through all the versions of each part, starting with the
               # oldest version.  Basically, it gets the most recent 
               # set of grade data for each part.
               my @submissions = ();
       for(my $Version=1; $Version<=$LatestVersion; $Version++) {
                   foreach my $part (split(/\:/,$data->{$sequence.':'.
                                                        $problemID.
                                                        ':parts'})) {
   
                       if(!defined($input->{"$name:$Version:$problem".
                                            ":resource.$part.solved"})) {
                           # No grade for this submission, so skip
                           next;
                       }
   
                       my $tries=0;
                       my $code=' ';
                       my $awarded=0;
   
                       $tries = $input->{$name.':'.$Version.':'.$problem.
                                         ':resource.'.$part.'.tries'};
                       $awarded = $input->{$name.':'.$Version.':'.$problem.
                                           ':resource.'.$part.'.awarded'};
   
                       $partData{$part.':awarded'}=($awarded) ? $awarded : 0;
                       $partData{$part.':tries'}=($tries) ? $tries : 0;
   
                       $partData{$part.':timestamp'}=$input->{$name.':'.$Version.':'.
                                                              $problem.
                                                              ':timestamp'};
                       if(!$input->{$name.':'.$Version.':'.$problem.':resource.'.$part.
                                    '.previous'}) {
                           foreach my $response (split(':',
                                                      $data->{$sequence.':'.
                                                              $problemID.':'.
                                                              $part.':responseIDs'})) {
                               @submissions=($input->{$name.':'.$Version.':'.
                                                      $problem.
                                                      ':resource.'.$part.'.'.
                                                      $response.'.submission'},
                                             @submissions);
                           }
                       }
   
                       my $val = $input->{$name.':'.$Version.':'.$problem.
                                          ':resource.'.$part.'.solved'};
                       if    ($val eq 'correct_by_student')   {$code = '*';} 
                       elsif ($val eq 'correct_by_override')  {$code = '+';}
                       elsif ($val eq 'incorrect_attempted')  {$code = '.';} 
                       elsif ($val eq 'incorrect_by_override'){$code = '-';}
                       elsif ($val eq 'excused')              {$code = 'x';}
                       elsif ($val eq 'ungraded_attempted')   {$code = '#';}
                       else                                   {$code = ' ';}
                       $partData{$part.':code'}=$code;
                   }
               }
   
               foreach my $part (split(/\:/,$data->{$sequence.':'.$problemID.
                                                    ':parts'})) {
                   $output->{$name.':'.$problemID.':'.$part.':wrong'} = 
                       $partData{$part.':tries'};
    $allkeys{$name.':'.$problemID.':'.$part.':wrong'}++;
   
                   if($partData{$part.':code'} eq '*') {
                       $output->{$name.':'.$problemID.':'.$part.':wrong'}--;
                       $problemsCorrect++;
                   } elsif($partData{$part.':code'} eq '+') {
                       $output->{$name.':'.$problemID.':'.$part.':wrong'}--;
                       $problemsCorrect++;
                   }
   
                   $output->{$name.':'.$problemID.':'.$part.':tries'} = 
                       $partData{$part.':tries'};
                   $output->{$name.':'.$problemID.':'.$part.':code'} =
                       $partData{$part.':code'};
                   $output->{$name.':'.$problemID.':'.$part.':awarded'} =
                       $partData{$part.':awarded'};
    $allkeys{$name.':'.$problemID.':'.$part.':tries'}++;
    $allkeys{$name.':'.$problemID.':'.$part.':code'}++;
    $allkeys{$name.':'.$problemID.':'.$part.':awarded'}++;
   
                   $totalAwarded += $partData{$part.':awarded'};
                   $output->{$name.':'.$problemID.':'.$part.':timestamp'} =
                       $partData{$part.':timestamp'};
    $allkeys{$name.':'.$problemID.':'.$part.':timestamp'}++;
   
                   foreach my $response (split(':', $data->{$sequence.':'.
                                                            $problemID.':'.
                                                            $part.':responseIDs'})) {
                       $output->{$name.':'.$problemID.':'.$part.':'.$response.
                                 ':submission'}=join(':::',@submissions);
       $allkeys{$name.':'.$problemID.':'.$part.':'.$response.
        ':submission'}++;
                   }
   
                   if($partData{$part.':code'} ne 'x') {
                       $totalProblems++;
                   }
               }
         }          }
   
           $output->{$name.':'.$sequence.':problemsCorrect'} = $problemsCorrect;
    $allkeys{$name.':'.$sequence.':problemsCorrect'}++;
           $problemsSolved += $problemsCorrect;
    $problemsCorrect=0;
     }      }
   
       $output->{$name.':problemsSolved'} = $problemsSolved;
       $output->{$name.':totalProblems'} = $totalProblems;
       $output->{$name.':totalAwarded'} = $totalAwarded;
       $allkeys{$name.':problemsSolved'}++;
       $allkeys{$name.':totalProblems'}++;
       $allkeys{$name.':totalAwarded'}++;
   
       $output->{$name.':keys'} = join(':::', keys(%allkeys));
   
     return;      return;
 }  }
   
   sub LoadDiscussion {
       my ($courseID)=@_;
       my %Discuss=();
       my %contrib=&Apache::lonnet::dump(
                   $courseID,
                   $ENV{'course.'.$courseID.'.domain'},
                   $ENV{'course.'.$courseID.'.num'});
     
       #my %contrib=&DownloadCourseInformation($name, $courseID, 0);
   
       foreach my $temp(keys %contrib) {
    if ($temp=~/^version/) {
       my $ver=$contrib{$temp};
       my ($dummy,$prb)=split(':',$temp);
       for (my $idx=1; $idx<=$ver; $idx++ ) {
    my $name=$contrib{"$idx:$prb:sendername"};
    $Discuss{"$name:$prb"}=$idx;
       }
    }
       }       
   
       return \%Discuss;
   }
   
 # ----- END PROCESSING FUNCTIONS ---------------------------------------  # ----- END PROCESSING FUNCTIONS ---------------------------------------
   
 =pod  =pod
Line 672  sub ProcessStudentData { Line 1134  sub ProcessStudentData {
 =head1 HELPER FUNCTIONS  =head1 HELPER FUNCTIONS
   
 These are just a couple of functions do various odd and end   These are just a couple of functions do various odd and end 
 jobs.  jobs.  There was also a couple of bulk functions added.  These are
   &DownloadStudentCourseData(), &DownloadStudentCourseDataSeparate(), and
   &CheckForResidualDownload().  These functions now act as the interface
   for downloading student course data.  The statistical modules should
   no longer make the calls to dump and download and process etc.  They
   make calls to these bulk functions to get their data.
   
 =cut  =cut
   
 # ----- HELPER FUNCTIONS -----------------------------------------------  # ----- HELPER FUNCTIONS -----------------------------------------------
   
   sub CheckDateStampError {
       my ($courseData, $cache, $name)=@_;
       if($courseData->{$name.':UpToDate'} eq 'true') {
           $cache->{$name.':lastDownloadTime'} = 
               $courseData->{$name.':lastDownloadTime'};
           if($courseData->{$name.':lastDownloadTime'} eq 'Not downloaded') {
               $cache->{$name.':updateTime'} = ' Not updated';
           } else {
               $cache->{$name.':updateTime'}=
                   localtime($courseData->{$name.':lastDownloadTime'});
           }
           return 0;
       }
   
       $cache->{$name.':lastDownloadTime'}=$courseData->{$name.':lastDownloadTime'};
       if($courseData->{$name.':lastDownloadTime'} eq 'Not downloaded') {
           $cache->{$name.':updateTime'} = ' Not updated';
       } else {
           $cache->{$name.':updateTime'}=
               localtime($courseData->{$name.':lastDownloadTime'});
       }
   
       if(defined($courseData->{$name.':error'})) {
           $cache->{$name.':error'}=$courseData->{$name.':error'};
           return 0;
       }
   
       return 1;
   }
   
 =pod  =pod
   
 =item &ProcessFullName()  =item &ProcessFullName()
Line 692  sub ProcessFullName { Line 1189  sub ProcessFullName {
     my ($lastname, $generation, $firstname, $middlename)=@_;      my ($lastname, $generation, $firstname, $middlename)=@_;
     my $Str = '';      my $Str = '';
   
       # Strip whitespace preceeding & following name components.
       $lastname   =~ s/(\s+$|^\s+)//g;
       $generation =~ s/(\s+$|^\s+)//g;
       $firstname  =~ s/(\s+$|^\s+)//g;
       $middlename =~ s/(\s+$|^\s+)//g;
   
     if($lastname ne '') {      if($lastname ne '') {
  $Str .= $lastname.' ';   $Str .= $lastname;
  if($generation ne '') {   $Str .= ' '.$generation if ($generation ne '');
     $Str .= $generation;   $Str .= ',';
  } else {          $Str .= ' '.$firstname  if ($firstname ne '');
     chop($Str);          $Str .= ' '.$middlename if ($middlename ne '');
  }  
  $Str .= ', ';  
  if($firstname ne '') {  
     $Str .= $firstname.' ';  
  }  
  if($middlename ne '') {  
     $Str .= $middlename;  
  } else {  
     chop($Str);  
     if($firstname eq '') {  
  chop($Str);  
     }  
  }  
     } else {      } else {
  if($firstname ne '') {          $Str .= $firstname      if ($firstname ne '');
     $Str .= $firstname.' ';          $Str .= ' '.$middlename if ($middlename ne '');
  }          $Str .= ' '.$generation if ($generation ne '');
  if($middlename ne '') {  
     $Str .= $middlename.' ';  
  }  
  if($generation ne '') {  
     $Str .= $generation;  
  } else {  
     chop($Str);  
  }  
     }      }
   
     return $Str;      return $Str;
Line 746  $ChartDB: The name of the cache database Line 1228  $ChartDB: The name of the cache database
   
 Output: -1, 0, 1  Output: -1, 0, 1
   
 -1: Couldn't tie database  -1: Could not tie database
  0: Use cached data   0: Use cached data
  1: New cache database created, use that.   1: New cache database created, use that.
   
Line 773  sub TestCacheData { Line 1255  sub TestCacheData {
     while($tieTries < $totalDelay) {      while($tieTries < $totalDelay) {
         my $result=0;          my $result=0;
         if($isCached) {          if($isCached) {
             $result=tie(%testData,'GDBM_File',$ChartDB,&GDBM_READER,0640);              $result=tie(%testData,'GDBM_File',$ChartDB,&GDBM_READER(),0640);
         } else {          } else {
             $result=tie(%testData,'GDBM_File',$ChartDB,&GDBM_NEWDB,0640);              $result=tie(%testData,'GDBM_File',$ChartDB,&GDBM_NEWDB(),0640);
         }          }
         if($result) {          if($result) {
             last;              last;
Line 792  sub TestCacheData { Line 1274  sub TestCacheData {
     return $isCached;      return $isCached;
 }  }
   
   sub DownloadStudentCourseData {
       my ($students,$checkDate,$cacheDB,$extract,$status,$courseID,$r,$c)=@_;
   
       my $title = 'LON-CAPA Statistics';
       my $heading = 'Download and Process Course Data';
       my $studentCount = scalar(@$students);
   
       my $WhatIWant;
       $WhatIWant = '(^version:|';
       $WhatIWant .= '^\d+:.+?:(resource\.\d+\.';
       $WhatIWant .= '(solved|tries|previous|awarded|(\d+\.submission))\s*$';#'
       $WhatIWant .= '|timestamp)';
       $WhatIWant .= ')';
   #    $WhatIWant = '.';
   
       if($status eq 'true') {
           &Apache::lonhtmlcommon::Create_PrgWin($r, $title, $heading);
       }
   
       my $displayString;
       my $count=0;
       foreach (@$students) {
           my %cache;
   
           if($c->aborted()) { return 'Aborted'; }
   
           if($status eq 'true') {
               $count++;
               my $displayString = $count.'/'.$studentCount.': '.$_;
               &Apache::lonhtmlcommon::Update_PrgWin($displayString, $r);
           }
   
           my $downloadTime='Not downloaded';
           my $needUpdate = 'false';
           if($checkDate eq 'true'  && 
              tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
               $downloadTime = $cache{$_.':lastDownloadTime'};
               $needUpdate = $cache{'ResourceUpdated'};
               untie(%cache);
           }
   
           if($c->aborted()) { return 'Aborted'; }
   
           if($needUpdate eq 'true') {
               $downloadTime = 'Not downloaded';
    }
    my $courseData = 
       &DownloadCourseInformation($_, $courseID, $downloadTime, 
          $WhatIWant);
    if(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT(),0640)) {
       foreach my $key (keys(%$courseData)) {
    if($key =~ /^(con_lost|error|no_such_host)/i) {
       $courseData->{$_.':error'} = 'No course data for '.$_;
       last;
    }
       }
       if($extract eq 'true') {
    &ExtractStudentData($courseData, \%cache, \%cache, $_);
       } else {
    &ProcessStudentData(\%cache, $courseData, $_);
       }
       untie(%cache);
    } else {
       next;
    }
       }
       if($status eq 'true') { &Apache::lonhtmlcommon::Close_PrgWin($r); }
   
       return 'OK';
   }
   
   sub DownloadStudentCourseDataSeparate {
       my ($students,$checkDate,$cacheDB,$extract,$status,$courseID,$r,$c)=@_;
       my $residualFile = $Apache::lonnet::tmpdir.$courseID.'DownloadFile.db';
       my $title = 'LON-CAPA Statistics';
       my $heading = 'Download Course Data';
   
       my $WhatIWant;
       $WhatIWant = '(^version:|';
       $WhatIWant .= '^\d+:.+?:(resource\.\d+\.';
       $WhatIWant .= '(solved|tries|previous|awarded|(\d+\.submission))\s*$';#'
       $WhatIWant .= '|timestamp)';
       $WhatIWant .= ')';
   
       &CheckForResidualDownload($cacheDB, 'true', 'true', $courseID, $r, $c);
   
       my $studentCount = scalar(@$students);
       if($status eq 'true') {
           &Apache::lonhtmlcommon::Create_PrgWin($r, $title, $heading);
       }
       my $count=0;
       my $displayString='';
       foreach (@$students) {
           if($c->aborted()) {
               return 'Aborted';
           }
   
           if($status eq 'true') {
               $count++;
               $displayString = $count.'/'.$studentCount.': '.$_;
               &Apache::lonhtmlcommon::Update_PrgWin($displayString, $r);
           }
   
           my %cache;
           my $downloadTime='Not downloaded';
           my $needUpdate = 'false';
           if($checkDate eq 'true'  && 
              tie(%cache,'GDBM_File',$cacheDB,&GDBM_READER(),0640)) {
               $downloadTime = $cache{$_.':lastDownloadTime'};
               $needUpdate = $cache{'ResourceUpdated'};
               untie(%cache);
           }
   
           if($c->aborted()) {
               return 'Aborted';
           }
   
           if($needUpdate eq 'true') {
               $downloadTime = 'Not downloaded';
    }
   
           my $error = 0;
           my $courseData = 
               &DownloadCourseInformation($_, $courseID, $downloadTime,
                                          $WhatIWant);
           my %downloadData;
           unless(tie(%downloadData,'GDBM_File',$residualFile,
                      &GDBM_WRCREAT(),0640)) {
               return 'Failed to tie temporary download hash.';
           }
           foreach my $key (keys(%$courseData)) {
               $downloadData{$key} = $courseData->{$key};
               if($key =~ /^(con_lost|error|no_such_host)/i) {
                   $error = 1;
                   last;
               }
           }
           if($error) {
               foreach my $deleteKey (keys(%$courseData)) {
                   delete $downloadData{$deleteKey};
               }
               $downloadData{$_.':error'} = 'No course data for '.$_;
           }
           untie(%downloadData);
       }
       if($status eq 'true') { &Apache::lonhtmlcommon::Close_PrgWin($r); }
   
       return &CheckForResidualDownload($cacheDB, 'true', 'true', 
                                        $courseID, $r, $c);
   }
   
   sub CheckForResidualDownload {
       my ($cacheDB,$extract,$status,$courseID,$r,$c)=@_;
   
       my $residualFile = $Apache::lonnet::tmpdir.$courseID.'DownloadFile.db';
       if(!-e $residualFile) {
           return 'OK';
       }
   
       my %downloadData;
       my %cache;
       unless(tie(%downloadData,'GDBM_File',$residualFile,&GDBM_READER(),0640)) {
           return 'Can not tie database for check for residual download: tempDB';
       }
       unless(tie(%cache,'GDBM_File',$cacheDB,&GDBM_WRCREAT(),0640)) {
           untie(%downloadData);
           return 'Can not tie database for check for residual download: cacheDB';
       }
   
       my @students=();
       my %checkStudent;
       my $key;
       while(($key, undef) = each %downloadData) {
           my @temp = split(':', $key);
           my $student = $temp[0].':'.$temp[1];
           if(!defined($checkStudent{$student})) {
               $checkStudent{$student}++;
               push(@students, $student);
           }
       }
   
       my $heading = 'Process Course Data';
       my $title = 'LON-CAPA Statistics';
       my $studentCount = scalar(@students);
       if($status eq 'true') {
           &Apache::lonhtmlcommon::Create_PrgWin($r, $title, $heading);
       }
   
       my $count=1;
       foreach my $name (@students) {
           last if($c->aborted());
   
           if($status eq 'true') {
               my $displayString = $count.'/'.$studentCount.': '.$name;
               &Apache::lonhtmlcommon::Update_PrgWin($displayString, $r);
           }
   
           if($extract eq 'true') {
               &ExtractStudentData(\%downloadData, \%cache, \%cache, $name);
           } else {
               &ProcessStudentData(\%cache, \%downloadData, $name);
           }
           $count++;
       }
   
       if($status eq 'true') { &Apache::lonhtmlcommon::Close_PrgWin($r); }
   
       untie(%cache);
       untie(%downloadData);
   
       if(!$c->aborted()) {
           my @files = ($residualFile);
           unlink(@files);
       }
   
       return 'OK';
   }
   
   
   ################################################
   ################################################
   
   =pod
   
   =item &make_into_hash($values);
   
   Returns a reference to a hash as described by $values.  $values is
   assumed to be the result of 
       join(':',map {&Apache::lonnet::escape($_)} %orighash;
   
   This is a helper function for get_current_state.
   
   =cut
   
   ################################################
   ################################################
   sub make_into_hash {
       my $values = shift;
       my %tmp = map { &Apache::lonnet::unescape($_); }
                                              split(':',$values);
       return \%tmp;
   }
   
   
   ################################################
   ################################################
   
   =pod
   
   =item &get_current_state($sname,$sdom,$symb,$courseid);
   
   Retrieve the current status of a students performance.  $sname and
   $sdom are the only required parameters.  If $symb is undef the results
   of an &Apache::lonnet::currentdump() will be returned.  
   If $courseid is undef it will be retrieved from the environment.
   
   The return structure is based on &Apache::lonnet::currentdump.  If
   $symb is unspecified, all the students data is returned in a hash of
   the form:
   ( 
     symb1 => { param1 => value1, param2 => value2 ... },
     symb2 => { param1 => value1, param2 => value2 ... },
   )
   
   If $symb is specified, a hash of 
   (
     param1 => value1, 
     param2 => value2,
   )
   is returned.
   
   If no data is found for $symb, or if the student has not performance data,
   an empty list is returned.
   
   =cut
   
   ################################################
   ################################################
   sub get_current_state {
       my ($sname,$sdom,$symb,$courseid,$forcedownload)=@_;
       return () if (! defined($sname) || ! defined($sdom));
       #
       $courseid = $ENV{'request.course.id'} if (! defined($courseid));
       #
       my $cachefilename = $Apache::lonnet::tmpdir.$ENV{'user.name'}.'_'.
                                                   $ENV{'user.domain'}.'_'.
                                                   $courseid.'_student_data.db';
       my %cache;
       #
       my %student_data; # return values go here
       #
       my $updatetime = 0;
       my $key = &Apache::lonnet::escape($sname).':'.
                 &Apache::lonnet::escape($sdom).':';
       # Open the cache file
       if (tie(%cache,'GDBM_File',$cachefilename,&GDBM_READER(),0640)) {
           if (exists($cache{$key.'time'})) {
               $updatetime = $cache{$key.'time'};
   #            &Apache::lonnet::logthis('got updatetime of '.$updatetime);
           }
           untie(%cache);
       }
       # timestamp/devalidation 
       my $modifiedtime = 1;
       # Take whatever steps are neccessary at this point to give $modifiedtime a
       # new value
       #
       if (($updatetime < $modifiedtime) || 
           (defined($forcedownload) && $forcedownload)) {
   #        &Apache::lonnet::logthis("loading data");
           # Get all the students current data
           my $time_of_retrieval = time;
           my @tmp = &Apache::lonnet::currentdump($courseid,$sdom,$sname);
           if ((scalar(@tmp) > 0) && ($tmp[0] =~ /^error:/)) {
               &Apache::lonnet::logthis('error getting data for '.
                                        $sname.':'.$sdom.' in course '.$courseid.
                                        ':'.$tmp[0]);
               return ();
           }
           %student_data = @tmp;
           #
           # Store away the data
           #
           # The cache structure is colon deliminated.  
           # $uname:$udom:time  => timestamp
           # $uname:$udom:$symb => $parm1:$val1:$parm2:$val2 ...
           #
           # BEWARE: The colons are NOT escaped so can search with escaped 
           #         keys instead of unescaping every key.
           #
           if (tie(%cache,'GDBM_File',$cachefilename,&GDBM_WRCREAT(),0640)) {
   #            &Apache::lonnet::logthis("writing data");
               while (my ($current_symb,$param_hash) = each(%student_data)) {
                   my @Parameters = %{$param_hash};
                   my $value = join(':',map { &Apache::lonnet::escape($_); } 
                                    @Parameters);
                   # Store away the values
                   $cache{$key.&Apache::lonnet::escape($symb)}=$value;
               }
               $cache{$key.'time'}=$time_of_retrieval;
               untie(%cache);
           }
       } else {
   #        &Apache::lonnet::logthis('retrieving cached data ');
           if (tie(%cache,'GDBM_File',$cachefilename,&GDBM_READER(),0640)) {
               if (defined($symb)) {
                   my  $searchkey = $key.&Apache::lonnet::escape($symb);
                   if (exists($cache{$searchkey})) {
                       $student_data{$symb} = &make_into_hash($cache{$searchkey});
                   }
               } else {
                   my $searchkey = '^'.$key.'(.*)$';#'
                   while (my ($testkey,$params)=each(%cache)) {
                       if ($testkey =~ /$searchkey/) { # \Q \E?  May be necc.
                           $student_data{&Apache::lonnet::unescape($1)} = 
                               &make_into_hash($params);
                       }
                   }
               }
               untie(%cache);
           }
       }
       if (! defined($symb)) {
   #        &Apache::lonnet::logthis("returning all data");
           return %student_data;
       } elsif (exists($student_data{$symb})) {
   #        &Apache::lonnet::logthis("returning data for symb=".$symb);
           return %{$student_data{$symb}};
       } else {
           return ();
       }
   }
   
   ################################################
   ################################################
   
   =pod
   
   =item &get_classlist();
   
   Retrieve the classist of a given class or of the current class.  Student
   information is returned from the classlist.db file and, if needed,
   from the students environment.
   
   Optional arguments are $cid, $cdom, and $cnum (course id, course domain,
   and course number, respectively).  Any omitted arguments will be taken 
   from the current environment ($ENV{'request.course.id'},
   $ENV{'course.'.$cid.'.domain'}, and $ENV{'course.'.$cid.'.num'}).
   
   Returns a reference to a hash which contains:
    keys    '$sname:$sdom'
    values  [$end,$start,$id,$section,$fullname]
   
   =cut
   
   ################################################
   ################################################
   
   sub get_classlist {
       my ($cid,$cdom,$cnum) = @_;
       $cid = $cid || $ENV{'request.course.id'};
       $cdom = $cdom || $ENV{'course.'.$cid.'.domain'};
       $cnum = $cnum || $ENV{'course.'.$cid.'.num'};
      my $now = time;
       #
       my %classlist=&Apache::lonnet::dump('classlist',$cdom,$cnum);
       while (my ($student,$info) = each(%classlist)) {
           return undef if ($student =~ /^(con_lost|error|no_such_host)/i);
           my ($sname,$sdom) = split(/:/,$student);
           my @Values = split(/:/,$info);
           my ($end,$start,$id,$section,$fullname);
           if (@Values > 2) {
               ($end,$start,$id,$section,$fullname) = @Values;
           } else { # We have to get the data ourselves
               ($end,$start) = @Values;
               $section = &Apache::lonnet::getsection($sdom,$sname,$cid);
               my %info=&Apache::lonnet::get('environment',
                                             ['firstname','middlename',
                                              'lastname','generation','id'],
                                             $sdom, $sname);
               my ($tmp) = keys(%info);
               if ($tmp =~/^(con_lost|error|no_such_host)/i) {
                   $fullname = 'not available';
                   $id = 'not available';
                   &Apache::lonnet::logthis('unable to retrieve environment '.
                                            'for '.$sname.':'.$sdom);
               } else {
                   $fullname = &ProcessFullName(@info{qw/lastname generation 
                                                          firstname middlename/});
                   $id = $info{'id'};
               }
               # Update the classlist with this students information
               if ($fullname ne 'not available') {
                   my $enrolldata = join(':',$end,$start,$id,$section,$fullname);
                   my $reply=&Apache::lonnet::cput('classlist',
                                                   {$student => $enrolldata},
                                                   $cdom,$cnum);
                   if ($reply !~ /^(ok|delayed)/) {
                       &Apache::lonnet::logthis('Unable to update classlist for '.
                                                'student '.$sname.':'.$sdom.
                                                ' error:'.$reply);
                   }
               }
           }
           my $status='Expired';
           if(((!$end) || $now < $end) && ((!$start) || ($now > $start))) {
               $status='Active';
           }
           $classlist{$student} = 
               [$sdom,$sname,$end,$start,$id,$section,$fullname,$status];
       }
       if (wantarray()) {
           return (\%classlist,['domain','username','end','start','id',
                                'section','fullname','status']);
       } else {
           return \%classlist;
       }
   }
   
 # ----- END HELPER FUNCTIONS --------------------------------------------  # ----- END HELPER FUNCTIONS --------------------------------------------
   
 1;  1;
 __END__  __END__
   
   

Removed from v.1.1  
changed lines
  Added in v.1.47


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>