Diff for /loncom/interface/statistics/lonstudentsubmissions.pm between versions 1.15 and 1.67

version 1.15, 2004/09/01 21:13:04 version 1.67, 2012/03/20 17:52:23
Line 27 Line 27
 package Apache::lonstudentsubmissions;  package Apache::lonstudentsubmissions;
   
 use strict;  use strict;
 use Apache::lonnet();  use Apache::lonnet;
 use Apache::loncommon();  use Apache::loncommon();
 use Apache::lonhtmlcommon();  use Apache::lonhtmlcommon();
   use Apache::lonquickgrades();
 use Apache::loncoursedata();  use Apache::loncoursedata();
 use Apache::lonstatistics;  use Apache::lonstatistics;
 use Apache::lonlocal;  use Apache::lonlocal;
 use Apache::lonstathelpers;  use Apache::lonstathelpers;
   use Apache::lonmsgdisplay();
 use HTML::Entities();  use HTML::Entities();
 use Time::Local();  use Time::Local();
 use Spreadsheet::WriteExcel();  use Spreadsheet::WriteExcel();
   use lib '/home/httpd/lib/perl/';
   use LONCAPA;
     
   
 my @SubmitButtons = ({ name => 'PrevProblem',  my @SubmitButtons = ({ name => 'SelectAnother',
                        text => 'Previous Problem' },  
                      { name => 'NextProblem',  
                        text => 'Next Problem' },  
                      { name => 'break'},  
                      { name => 'SelectAnother',  
                        text => 'Choose a different Problem' },                         text => 'Choose a different Problem' },
                      { name => 'Generate',                       { name => 'Generate',
                        text => 'Generate Spreadsheet'},                         text => 'Generate Report'},
                      );                       );
   
 sub BuildStudentSubmissionsPage {  sub BuildStudentSubmissionsPage {
Line 63  sub BuildStudentSubmissionsPage { Line 63  sub BuildStudentSubmissionsPage {
     #      #
     &Apache::lonstatistics::PrepareClasslist();      &Apache::lonstatistics::PrepareClasslist();
     #      #
       $r->print( &Apache::lonhtmlcommon::breadcrumbs('Student Submission Reports'));
       &Apache::lonquickgrades::startGradeScreen($r,'statistics');
     $r->print(&CreateInterface());      $r->print(&CreateInterface());
     #      #
     my @Students = @Apache::lonstatistics::Students;      my @Students = @Apache::lonstatistics::Students;
     #      #
     if (@Students < 1) {      if (@Students < 1) {
         $r->print('<h2>There are no students in the sections selected</h2>');          $r->print('<div class="LC_warning">'
                    .&mt('There are no students in the sections selected.')
                    .'</div>');
     }      }
     #      #
     my @CacheButtonHTML =       my @CacheButtonHTML = 
         &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status');          &Apache::lonstathelpers::manage_caches($r,'Statistics','stats_status',
                                      '<div class="LC_info">'.&mt('Loading student data...').'</div>');
     $r->rflush();      $r->rflush();
     #      #
     if (exists($ENV{'form.problemchoice'}) &&       my %anoncounter =
         ! exists($ENV{'form.SelectAnother'})) {           &Apache::lonnet::dump('nohist_anonsurveys',
                           $env{'course.'.$env{'request.course.id'}.'.domain'},
                           $env{'course.'.$env{'request.course.id'}.'.num'});
       if (exists($env{'form.problemchoice'}) && 
           ! exists($env{'form.SelectAnother'})) {
         foreach my $button (@SubmitButtons) {          foreach my $button (@SubmitButtons) {
             if ($button->{'name'} eq 'break') {              if ($button->{'name'} eq 'break') {
                 $r->print("<br />\n");                  $r->print("<br />\n");
Line 90  sub BuildStudentSubmissionsPage { Line 99  sub BuildStudentSubmissionsPage {
             $r->print($html.('&nbsp;'x5));              $r->print($html.('&nbsp;'x5));
         }          }
         #          #
         $r->print('<hr />');          $r->print('<hr />'.$/);
         $r->rflush();          $r->rflush();
         #          #
         # Determine which problem we are to analyze          # Determine which problems we are to analyze
         my $current_problem = &Apache::lonstathelpers::get_target_from_id          my @Symbs = 
             ($ENV{'form.problemchoice'});              &Apache::lonstathelpers::get_selected_symbs('problemchoice');
         #  
         my ($prev,$curr,$next) =           # If there are multi-part problems with anonymous survey and named
             &Apache::lonstathelpers::get_prev_curr_next($current_problem,          # parts check if named was picked for display.
                                                         '.',          #
                                                         'response',          my %mixed_named; 
                                                         );          foreach my $envkey (%env) {
         if (exists($ENV{'form.PrevProblem'}) && defined($prev)) {              if ($envkey =~ /^form\.mixed_(\d+:\d+)$/) {
             $current_problem = $prev;                  my $item = $1; 
         } elsif (exists($ENV{'form.NextProblem'}) && defined($next)) {                  if ($env{$envkey} =~ /^symb_(.+)$/) {
             $current_problem = $next;                      my $symb = &unescape($1);
         } else {                      if (ref($mixed_named{$symb}) eq 'ARRAY') {
             $current_problem = $curr;                          push(@{$mixed_named{$symb}},$item);
                       } else {
                           @{$mixed_named{$symb}} = ($item);
                       }
                   }
               }
         }          }
         #          #
         # Store the current problem choice and send it out in the form          # Get resource objects
         $ENV{'form.problemchoice'} =           my $navmap = Apache::lonnavmaps::navmap->new();
             &Apache::lonstathelpers::make_target_id($current_problem);          if (!defined($navmap)) {
         $r->print('<input type="hidden" name="problemchoice" value="'.              foreach my $selected (@Symbs) {
                   $ENV{'form.problemchoice'}.'" />');                  $r->print('<input type="hidden" name="problemchoice" value="'.
                             &escape($selected).'" />'.$/);
                   if (ref($mixed_named{$selected}) eq 'ARRAY') {
                       foreach my $item (@{$mixed_named{$selected}}) {
                           $r->print('<input type="hidden" name="mixed_'.$item.'" value="'.&escape($selected).'" />'.$/);
                       }
                   }
               }
               $r->print('<div class="LC_error">'.&mt("Internal error").'</div>');
               return;
           }
           my %already_seen;
           my (@Problems,@anonProbs,@namedProbs,$show_named);
           foreach my $symb (@Symbs) {
               my $resource = $navmap->getBySymb($symb);
               my ($hasanon,$hasnamed);
               if (ref($resource)) {
                   foreach my $partid (@{$resource->parts}) {
                       if (($anoncounter{$symb."\0".$partid}) || ($resource->is_anonsurvey($partid))) {
                           unless (exists($mixed_named{$symb})) {
                               $hasanon = 1;
                           }
                       } else {
                           $hasnamed = 1;
                       }
                   }
                   if ($hasanon) {
                       push(@anonProbs,$resource);
                   } elsif ($hasnamed) {
                       push(@namedProbs,$resource);
                   }
               }
           }
           if (@namedProbs > 0) {
               @Problems = @namedProbs;
               $show_named = 1;
           } elsif (@anonProbs > 0) {
               @Problems = @anonProbs;
           } 
           foreach my $selected (@Symbs) {
               $r->print('<input type="hidden" name="problemchoice" value="'.
                         &escape($selected).'" />'.$/);
               if (ref($mixed_named{$selected}) eq 'ARRAY') {
                   foreach my $item (@{$mixed_named{$selected}}) {
                       $r->print('<input type="hidden" name="mixed_'.$item.'" value="'.&escape($selected).'" />'.$/);
                   }
               }
           }
           # If these are to be anonymized, do a random shuffle of @Students. 
           unless ($show_named) {
               &array_shuffle(\@Students);
           }
         #          #
         if (! defined($current_problem->{'resource'})) {  
             $r->print('resource is undefined');          my $threshold = $env{'course.'.$env{'request.course.id'}.'.internal.anonsurvey_threshold'};
           if ($threshold eq '') {
               my %domconfig =
                   &Apache::lonnet::get_dom('configuration',['coursedefaults'],
                                           $env{'course.'.$env{'request.course.id'}.'.domain'});
               if (ref($domconfig{'coursedefaults'}) eq 'HASH') {
                   $threshold = $domconfig{'coursedefaults'}{'anonsurvey_threshold'};
                   if ($threshold eq '') {
                       $threshold = 10;
                   }
               } else {
                   $threshold = 10;
               }
           }
           $r->print('<h4>'.
                     &Apache::lonstatistics::section_and_enrollment_description().
                     '</h4>');
           if (! scalar(@Problems) || ! defined($Problems[0])) {
               $r->print(&mt('resource is undefined'));
           } elsif (!$show_named && @Students < $threshold) {
               $r->print(&mt('The number of students matching the selection criteria is too few for display of submission data for anonymous surveys.').'<br />'.&mt('There must be at least [quant,_1,student].',$threshold).' '.&mt('Contact a Domain Coordinator if you need the threshold to be changed for this course.'));
         } else {          } else {
             my $resource = $current_problem->{'resource'};              if (scalar(@Problems) == 1) {
             $r->print('<h1>'.$resource->{'title'}.'</h1>');                  my $resource = $Problems[0];
             $r->print('<h3>'.$resource->{'src'}.'</h3>');                  $r->print('<h1>'.$resource->title.'</h1>');
             if ($ENV{'form.renderprob'} eq 'true') {                  $r->print('<h3>'.$resource->src.'</h3>');
                 $r->print(&Apache::lonstathelpers::render_resource($resource));                  if ($env{'form.renderprob'} eq 'true') {
             }                      $r->print(&Apache::lonstathelpers::render_resource($resource));
             $r->rflush();                      $r->rflush();
             my %Data = &Apache::lonstathelpers::get_problem_data                  }
                 ($resource->{'src'});              }
             my $ProblemData = $Data{$current_problem->{'part'}.              if ($env{'form.output'} eq 'excel') {
                                     '.'.                  &prepare_excel_output($r,\@Problems,\@Students,\%anoncounter,$show_named);
                                     $current_problem->{'respid'}};              } elsif ($env{'form.output'} eq 'csv') {
             &prepare_html_output($r,$current_problem,                  &prepare_csv_output($r,\@Problems,\@Students,\%anoncounter,$show_named);
                                   $ProblemData,\@Students);              } else {
 #            &prepare_excel_output($r,$current_problem,                  &prepare_html_output($r,\@Problems,\@Students,\%anoncounter,$show_named);
 #                                  $ProblemData,\@Students);              }
         }          }
         $r->print('<hr />');          $r->print('<hr />');
     } else {      } else {
         $r->print('<input type="submit" name="Generate" value="'.          $r->print('<input type="submit" name="Generate" value="'.
                   &mt('Generate Spreadsheet').'" />');                    &mt('Prepare Report').'" />');
         $r->print('&nbsp;'x5);          $r->print('&nbsp;'x5);
         $r->print('<h3>'.&mt('Please select a problem to analyze').'</h3>');          $r->print('<p>'.
         $r->print(&Apache::lonstathelpers::ProblemSelector('.'));                    &mt('Computing correct answers greatly increases the amount of time required to prepare a report.').
                     '</p>');
           $r->print('<p>'.
                     &mt('Please select problems and use the [_1]Prepare Report[_2] button to continue.','<b>','</b>').
                     '</p>');
           $r->print(&Apache::lonstathelpers::MultipleProblemSelector
                     (undef,'problemchoice','Statistics',\%anoncounter));
     }      }
 }  }
   
   sub array_shuffle {
       my $array = shift;
       return unless (ref($array) eq 'ARRAY');
       my $i = scalar(@$array);
       my $j;
       foreach my $item (@$array) {
           --$i;
           $j = int(rand($i+1));
           next if($i == $j);
           @$array [$i,$j] = @$array[$j,$i];
       }
       return @$array;
   }
   
   ##
   ## get_extra_response_headers
   ##
   sub get_extra_response_headers {
       my ($show_named) = @_;
       my @extra_resp_headers;
       if ($env{'form.correctans'} eq 'true') {
           push(@extra_resp_headers,'Correct');
       }
       if ($show_named) { 
           if ($env{'form.prob_status'} eq 'true') {
               push(@extra_resp_headers,'Award Detail'); 
               push(@extra_resp_headers,'Time');
               push(@extra_resp_headers,'Attempt');
               push(@extra_resp_headers,'Awarded');
           }
       }
       return @extra_resp_headers;
   }
   
   ##
   ## get_headers:
   ##     return the proper headers for the given response 
   sub get_headers {
       my ($prob,$partid,$respid,$resptype,$analysis,$output,$purpose,
           @basic_headers) = @_;
       my @headers;
       if ($resptype eq 'essay' && $purpose eq 'display' &&
           ($output eq 'html')) {# || scalar(@{$prob->parts})!=1)) {
           @headers = ();
       } elsif ($resptype =~ /^(option|match|rank)$/) {
           my $prefix = '_';
           if ($purpose eq 'display') {
               $prefix = '';
           }
           my @foils = 
               map { 
                   $prefix.$_; 
               } sort(keys(%{$analysis->{$partid.'.'.$respid}->{'_Foils'}}));
           if (scalar(@basic_headers) && $basic_headers[0] eq 'Correct') {
               @foils = map { ($_ , $_.' Correct') } @foils;
               shift(@basic_headers);  # Get rid of 'Correct'
           }
           @headers = (@foils,@basic_headers);
       } elsif (lc($resptype) eq 'task') {
           @headers = ('Grader','Status',@basic_headers,'Submission');
       } else {
           @headers = ('Submission',@basic_headers);
       }
       return @headers;
   }
   
 #########################################################  #########################################################
 #########################################################  #########################################################
 ##  ##
 ##    prepare_html_output  ##    HTML Output Routines
 ##  ##
 #########################################################  #########################################################
 #########################################################  #########################################################
 sub prepare_html_output {  sub prepare_html_output {
     my ($r,$problem,$ProblemData,$Students) = @_;      my ($r,$problems,$students,$anoncounter,$show_named) = @_;
     my $c = $r->connection();      my $c = $r->connection();
     my ($resource,$respid,$partid) = ($problem->{'resource'},  
                                       $problem->{'respid'},  
                                       $problem->{'part'});  
     $r->print('<h2>'.&mt('Student Responses').'</h2>');  
     #  
     if ($ENV{'form.correctans'} eq 'true') {  
         &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students,  
                                                    'Statistics',  
                                                    'stats_status');  
     }  
     #      #
     $r->rflush();      # Set a flag for the case when there is just one problem
     my $response_type;      my $single_response = 0;
     for (my $i=0;      if (scalar(@$problems) == 1 &&
          $i<scalar(@{$resource->{'partdata'}->{$partid}->{'ResponseIds'}});          $problems->[0]->countResponses == 1) {
          $i++) {          $single_response = 1;
         if($resource->{'partdata'}->{$partid}->{'ResponseIds'}->[$i] eq $respid){      }
             $response_type =       #
                 $resource->{'partdata'}->{$partid}->{'ResponseTypes'}->[$i];      # Compute the number of columns per response
             last;      my @extra_resp_headers = &get_extra_response_headers($show_named);
       #
       # Create the table header
       my @student_columns = &get_student_columns($show_named);
       my %headers;
       my $student_column_count = scalar(@student_columns);
       $headers{'problem'} = qq{<th colspan="$student_column_count">\&nbsp;</th>};
       foreach my $field (@student_columns) {
           $headers{'student'}.= '<th>'.&mt(ucfirst($field)).'</th>';
       }
       #
       # we put the headers into the %headers hash
       my $total_col = $student_column_count;
       my $nonempty_part_headers = 0;
       #
       my %problem_analysis;
       foreach my $prob (@$problems) {
           my %analysis = &Apache::lonstathelpers::get_problem_data($prob->src);
           $problem_analysis{$prob->src}=\%analysis;
           my $symb = $prob->symb();
           #
           my $prob_span = 0;
           my $single_part = 0;
           if (scalar(@{$prob->parts}) == 1) {
               $single_part = 1;
           }
           my $shown_parts = 0;
           foreach my $partid (@{$prob->parts}) {
               if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
                   next if ($show_named);
               } else {
                   next unless ($show_named);
               }
               $shown_parts ++;
               my $part_span = 0;
               my $responses = [$prob->responseIds($partid)];
               my $resptypes = [$prob->responseType($partid)];
               for (my $i=0;$i<scalar(@$responses);$i++) {
                   my $respid = $responses->[$i];
                   my @headers = &get_headers($prob,$partid,$respid,
                                              $resptypes->[$i],
                                              $problem_analysis{$prob->src},
                                              'html','display',
                                              @extra_resp_headers);
                   if (scalar(@headers)>0) {
                       $total_col += scalar(@headers);
                       $part_span += scalar(@headers);
                       $headers{'response'} .=
                           '<th colspan="'.scalar(@headers).'">'.
                           &mt('Response [_1]',$responses->[$i]).'</th>';
                       $headers{'student'}.= '<th><span class="LC_nobreak">'.
                                             join('</span></th><th><span class="LC_nobreak">',
                                                         @headers).
                                                             '</span></th>';
                   }
               }
               if ($part_span == 0) {
                   next;
               }
               if (! $single_part) {
                   my $tmpname = $partid;
                   if ($partid =~/^\d+$/) {
                       $tmpname = $prob->part_display($partid);
                   }
                   if ($tmpname !~ /^part/) {
                       $tmpname = 'Part '.$tmpname;
                   }
                   $headers{'part'} .= qq{<th colspan="$part_span">$tmpname</th>};
                   $nonempty_part_headers = 1;
               } else {
                   $headers{'part'} .= qq{<th colspan="$part_span">&nbsp;</th>};
               }
               $prob_span += $part_span;
           }
           next if (!$shown_parts);
           my $title = $prob->compTitle;
           if ($prob_span > 0) {
               $headers{'problem'}.= qq{<th colspan="$prob_span">$title</th>};
           } elsif ($single_response) {
               $prob_span = scalar(@student_columns);
               $headers{'problem'} = qq{<th colspan="$prob_span">$title</th>};
         }          }
     }      }
     if (! defined($response_type)) {      if (exists($headers{'part'})) {
         $r->print('<h2>'.&mt('Unable to determine response type').'</h2>');          $headers{'part'} = qq{<th colspan="$student_column_count">\&nbsp;</th>}.
     } else {              $headers{'part'};
         my $count = 0;      }
         my $header;      if (exists($headers{'response'})) {
         if ($response_type eq 'essay') {          $headers{'response'}=
             $header = &html_essay_header();              qq{<th colspan="$student_column_count">\&nbsp;</th>}.
         } elsif ($response_type eq 'radiobutton') {              $headers{'response'};
             $header = &html_radiobutton_header();      }
         } elsif ($response_type eq 'option') {      my $full_header = $/.'<table>'.$/;
             $header = &html_option_header();      $full_header .= '<tr align="left">'.$headers{'problem'}.'</tr>'.$/;
       if ($nonempty_part_headers) {
           $full_header .= '<tr align="left">'.$headers{'part'}.'</tr>'.$/;
       }
       $full_header .= '<tr align="left">'.$headers{'response'}.'</tr>'.$/;
       $full_header .= '<tr align="left">'.$headers{'student'}.'</tr>'.$/;
       #
       # Main loop
       my $count;
       $r->print($/.$full_header.$/);
       my $row_class = 'odd';   # css 
       foreach my $student (@$students) {
           my $student_row_data;
           if ($count++ >= 30) {
               $r->print('</table>'.$/.$full_header.$/);
               $count = 0;
           }
           last if ($c->aborted());
           if ($show_named) {
               foreach my $field (@student_columns) {
                   $student_row_data .= '<td valign="top">';
                   # handle comments like in lonstudentassessment.pm
                   if($field eq 'comments') {
                       $student_row_data .= 
                           '<a href="/adm/'.$student->{'domain'}.'/'.
                           $student->{'username'}.'/'.'aboutme#coursecomment">'.&mt('Comments').'</a>';
                   } else {
                       $student_row_data .= $student->{$field};
                   }
                   $student_row_data .= '</td>';
               }
         } else {          } else {
             $header = &html_generic_header();              $student_row_data = '<td valign="top" colspan="'.$student_column_count.'">'.&mt('Anonymized').'</td>';
         }          }
         $header = '<tr>'.$header.'</tr>';  
         #          #
         $r->print($/.'<table>'.$/.$header.$/);          # Figure out what it is we need to output for this student
         foreach my $student (@$Students) {          my @essays;
             if ($count >= 50) {          my %prob_data;
                 $r->print('</table>'.$/.'<table>'.$/.$header.$/);          my $maxrow;
                 $count = 0;          foreach my $prob (@$problems) {
             }              my $symb = $prob->symb;
             last if ($c->aborted());              $prob_data{$symb}={};
             my $results = &Apache::loncoursedata::get_response_data_by_student              foreach my $partid (@{$prob->parts}) {
                 ($student,$resource->{'symb'},$respid);                  if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
             next if (! defined($results) || ref($results) ne 'ARRAY');                      next if ($show_named);
             for (my $i=0;$i<scalar(@$results);$i++) {  
                 my $response = $results->[$i];  
                 if ($ENV{'form.last_sub_only'} eq 'true' &&   
                     $i < (scalar(@$results)-1)) {  
                     next;  
                 }  
                 my $data;  
                 $data->{'sname'} = $student->{'username'}.'@'.  
                     $student->{'domain'};  
                 $data->{'time'} = &Apache::lonlocal::locallocaltime  
                     ($response->[&Apache::loncoursedata::RDs_timestamp()]);  
                 $data->{'attempt'} =   
                     $response->[&Apache::loncoursedata::RDs_tries()];  
                 $data->{'submission'} =   
                     $response->[&Apache::loncoursedata::RDs_submission()];  
                 $data->{'correct'} = $student->{'answer'};  
                 my $row;  
                 if ($response_type eq 'essay') {  
                     $row = &html_essay($data);  
                 } elsif ($response_type eq 'radiobutton') {  
                     $row = &html_radiobutton($data);  
                 } elsif ($response_type eq 'option') {  
                     $row = &html_option($data);  
                 } else {                  } else {
                     $row = &html_generic($data);                      next unless ($show_named);
                   }
                   my @responses = $prob->responseIds($partid);
                   my @response_type = $prob->responseType($partid);
                   for (my $i=0;$i<=$#responses;$i++) {
                       my $respid  = $responses[$i];
                       my $results = 
                           &Apache::loncoursedata::get_response_data_by_student
                           ($student,$prob->symb(),$respid);
                       my $resptype = $response_type[$i];
                       my @headers = &get_headers($prob,$partid,$respid,
                                                  $resptype,
                                                  $problem_analysis{$prob->src},
                                                  'html','normal',
                                                  @extra_resp_headers);
                       my $width = scalar(@headers);
                       next if ($width < 1);
                       my $resp_data;
                       $resp_data->{'fake'} = qq{<td colspan="$width">&nbsp;</td>};
                       if (! defined($results)) {
                           $results = [];
                       }
                       # 
                       if (scalar(@$results) > $maxrow && $resptype ne 'essay') {
                           $maxrow = scalar(@$results);
                       }
                       for (my $j=scalar(@$results)-1;$j>=0;$j--) {
                           if ($env{'form.all_sub'} ne 'true') {
                               next if ($j ne scalar(@$results)-1);
                           }
                           my $response = &hashify_response($results->[$j],
                                                            $prob,
                                                            $student,
                                                            $partid,
                                                            $respid);
                           if ($resptype eq 'essay') {
                               push(@essays,
                                    &html_essay_results(\@headers,
                                                        $prob,$partid,$respid,
                                                        $response,
                                                        $single_response).
                                    '</td>');
    } elsif (lc($resptype) eq 'task') {
       my $results = 
    &html_task_results(\@headers,
      $prob,$partid,$respid,
      $response,$resptype);
       if ($results) {
    push(@{$resp_data->{'real'}},$results);
       }
                           } else {
                               push(@{$resp_data->{'real'}},
                                    &html_non_essay_results(\@headers,
                                                            $prob,$partid,$respid,
                                                            $response,$resptype));
                           }
                       }
                       $prob_data{$prob->symb}->{$partid}->{$respid}=$resp_data;
                   } # end of $i loop
               } # end of partid loop
           } # end of prob loop
           #
           # if there is no data, skip this student.
           next if (! $maxrow && ! scalar(@essays));
           #
           # Go through the problem data and output a row.
           if ($row_class eq 'even') {
               $row_class = 'odd'; 
           } else {
               $row_class = 'even'; 
           }
           my $printed_something;
           for (my $rows_output = 0;$rows_output<$maxrow;$rows_output++) {
               my $html;
               my $no_data = 1;
               foreach my $prob (@$problems) {
                   foreach my $partid (@{$prob->parts}) {
                       my @responses     = $prob->responseIds($partid);
                       my @response_type = $prob->responseType($partid);
                       for (my $i=0;$i<=$#responses;$i++) {
                           my $respid   = $responses[$i];
                           my $resp_data = 
                               $prob_data{$prob->symb}->{$partid}->{$respid};
                           next if ($response_type[$i] eq 'essay');
                           if (defined($resp_data->{'real'}->[$rows_output])) {
                               $html .= $resp_data->{'real'}->[$rows_output];
                               $no_data = 0;
                           } else {
                               $html .= $resp_data->{'fake'};
                           }
                       }
                 }                  }
                 $r->print($row.$/);              }
                 $count++;              if (! $no_data) {
                   $r->print(qq{<tr class="$row_class">$student_row_data$html</tr>}.$/);
                   $printed_something=1;
             }              }
         }          }
         $r->print('</table>'.$/);          if (@essays) {
     }              my $tr = qq{<tr class="$row_class">};
               my $td = qq{<td  valign="top" class="essay" colspan="$total_col">};
               if (! $printed_something) {
                   $r->print($tr.$student_row_data.'</tr>'.$/);
               }
               $r->print($tr.$td.
                         join('</td></tr>'.$/.$tr.$td,@essays).'</td></tr>'.$/);
               undef(@essays);
           }
       } # end of student loop
       $r->print('</table>'.$/);
     return;      return;
 }  }
   
   sub hashify_response {
       my ($response,$prob,$student,$partid,$respid) =@_;
       my $resp_hash = {};
       if ($env{'form.correctans'} eq 'true') {
           $resp_hash->{'Correct'} = 
               &Apache::lonstathelpers::get_student_answer
               ($prob,$student->{'username'},$student->{'domain'},
                $partid,$respid);
       }
       $resp_hash->{'Submission'} = 
           $response->[&Apache::loncoursedata::RDs_submission()];
       $resp_hash->{'Time'} = 
           $response->[&Apache::loncoursedata::RDs_timestamp()];
       $resp_hash->{'Attempt'} =
           $response->[&Apache::loncoursedata::RDs_tries()];
       $resp_hash->{'Awarded'} = 
           $response->[&Apache::loncoursedata::RDs_awarded()];
       if ($prob->is_task()) {
    $resp_hash->{'Grader'} = 
       $response->[&Apache::loncoursedata::RDs_response_eval_2()];
    if ($resp_hash->{'Attempt'} eq '0') {
       $resp_hash->{'Attempt'} = '';
    }
    $resp_hash->{'Award Detail'} = 
       $response->[&Apache::loncoursedata::RDs_part_award()];
    $resp_hash->{'Status'} = 
       $response->[&Apache::loncoursedata::RDs_response_eval()];
       } else {
    $resp_hash->{'Award Detail'} = 
       $response->[&Apache::loncoursedata::RDs_awarddetail()];
       }
   
       return $resp_hash;
   }
   
 #####################################################  #####################################################
 ##  ##
 ##     HTML helper routines  ##     HTML helper routines
 ##  ##
 #####################################################  #####################################################
 my @FullHeaders = (  sub html_essay_results {
                    {name=>'sname',      my ($headers,$prob,$partid,$respid,$response,$single_response)=@_;
                     display=>'Student'},      if (! ref($headers) || ref($headers) ne 'ARRAY') {
                    {name => 'id',          return '';
                     display => 'Id'},      }
                    {name => 'time',      # Start of telling them what problem, part, and response
                     display =>'Time'},      my $Str;
                    {name => 'attempt',      if (! $single_response) {
                     display =>'Attempt'},          my $id = $prob->compTitle;
                    {name => 'grading',          if (defined($partid) && $partid ne '0') {
                     display =>'Grading'},              $id .= ' '.$prob->part_display($partid);
                    );  
   
 sub html_essay_header {  
     my $header;  
     if ($ENV{'form.subdata'} eq 'true') {  
         foreach (@FullHeaders) {  
             $header .= '<th>'.&mt($_->{'display'}).'</th>';  
         }          }
     } else {          if (defined($respid)) {
         $header = '<th>'.&mt('Username and Submission').'</th>';              $id .= ' '.$respid;
           }
           $Str .= '<span class="LC_nobreak">'.$id.'</span>'.('&nbsp;'x4);
     }      }
     return $header;  
 }  
   
 sub html_essay {  
     my ($data)=@_;  
     #      #
     $data->{'submission'} =~ s|\\r\\n|$/|g;      shift(@$headers); # Get rid of the Submission header
     $data->{'submission'} =       my $correct = '';
         &HTML::Entities::encode($data->{'submission'},'<>&"');      if ($headers->[0] eq 'Correct') {
     $data->{'submission'} =~ s|$/\s*$/|$/</p><p>$/|g;          $correct = &html_format_essay_sub($response->{'Correct'});
     $data->{'submission'} =~ s|\\||g;          shift(@$headers);
     $data->{'submission'} = '<p>'.$data->{'submission'}.'</p>';      }
     #      $Str .= '<span class="LC_nobreak">'.
     my $Str  = '<tr>';          join('',
     if ($ENV{'form.subdata'} eq 'true') {               map {
         $Str .=                    ('&nbsp;'x4).&mt($_.': [_1]',$response->{$_});
             '<td><b>'.$data->{'sname'}.'</b></td>'.               } @$headers).'</span>';
             '<td>'.$data->{'time'}.'</td>'.      if (@$headers || ! $single_response) {
             '<td>'.$data->{'attempt'}.'</td>';          $Str .= '<br />';
         $Str .= '</tr>';  
         $Str .= '<tr><td colspan="'.scalar(@FullHeaders).'">'.  
             $data->{'submission'}.'</td>';  
     } else {  
         $Str .= '<td><b>'.$data->{'sname'}.'</b>'.  
             $data->{'submission'}.'</td>';  
     }      }
     if ($ENV{'form.correctans'} eq 'true') {      $Str .= &html_format_essay_sub($response->{'Submission'});
         $Str .= '</tr>';  
         if (defined($data->{'correct'}) && $data->{'correct'} !~ /^\s*$/) {  
             $Str .= '<tr><td colspan="'.scalar(@FullHeaders).'">'.  
                 '<b>'.&mt('Correct Answer:').'</b>'.$data->{'correct'}.'</td>';  
         }  
     }  
     $Str .= '</tr>';  
     #      #
       if (defined($correct) && $correct !~ /^\s*$/) {
           $Str .= '<hr /><b>'.&mt('Correct').'</b>'.$correct
       }
     return $Str;      return $Str;
 }  }
   
 sub html_radiobutton_header {  sub html_format_essay_sub {
     my $header;      my ($submission) = @_;
     if ($ENV{'form.subdata'} eq 'true') {      return '' if (! defined($submission) || $submission eq '');
         foreach (@FullHeaders) {      $submission = &HTML::Entities::decode($submission);
             $header .= '<th>'.&mt($_->{'display'}).'</th>';      $submission =~ s/\\\"/\"/g;
         }      $submission =~ s/\\\'/\'/g;
     } else {      $submission =~ s|\\r\\n|$/|g;
         $header =       $submission = &HTML::Entities::encode($submission,'<>&"');
             '<th>'.&mt('Username').'</th>';      $submission =~ s|$/\s*$/|$/</p><p>$/|g;
     }      $submission =~ s|\\||g;
     $header .='<th>'.&mt('Submission').'</th>';      $submission = '<p>'.$submission.'</p>';
     if ($ENV{'form.correctans'} eq 'true') {      return $submission;
         $header .= '<th>'.&mt('Correct').'</th>';  
     }  
     return $header;  
 }  
   
 sub html_radiobutton {  
     my ($data)=@_;  
     #  
     $data->{'submission'} =~ s/=([^=])$//;  
     #  
     my $Str  = '<tr>';  
     if ($ENV{'form.subdata'} eq 'true') {  
         $Str .=   
             '<td>'.'<b>'.$data->{'sname'}.'</b></td>'.  
             '<td>'.$data->{'time'}.'</td>'.  
             '<td>'.$data->{'attempt'}.'</td>';  
         $Str .= '<td>'.$data->{'submission'}.'</td>';  
         if ($ENV{'form.correctans'} eq 'true') {  
             $Str .= '<td>'.$data->{'correct'}.'</td>';  
         }  
     } else {  
         $Str .= '<td><b>'.$data->{'sname'}.'</b></td>';  
         $Str .= '<td>'.$data->{'submission'}.'</td>';  
         if ($ENV{'form.correctans'} eq 'true') {  
             $Str .= '<td>'.$data->{'correct'}.'</td>';  
         }  
     }  
     $Str .= '</tr>';  
     #  
     return $Str;  
 }  }
   
 sub html_generic_header {  sub html_task_results {
     my $header;      my ($headers,$prob,$partid,$respid,$response,$resptype) = @_;
     if ($ENV{'form.subdata'} eq 'true') {      if (! ref($headers) || ref($headers) ne 'ARRAY' || ! scalar(@$headers)) {
         foreach (@FullHeaders) {          return '';
             $header .= '<th>'.&mt($_->{'display'}).'</th>';  
         }  
     } else {  
         $header =   
             '<th>'.&mt('Username').'</th>';  
     }   
     $header .= '<th>'.&mt('Submission').'</th>';  
     if ($ENV{'form.correctans'} eq 'true') {  
         $header .= '<th>'.&mt('Correct').'</th>';  
     }  
     return $header;  
 }  
   
 sub html_generic {  
     my ($data)=@_;  
     $data->{'submission'} = &Apache::lonnet::unescape($data->{'submission'});  
     my $Str  = '<tr>';  
     if ($ENV{'form.subdata'} eq 'true') {  
         $Str .=   
             '<td><b>'.$data->{'sname'}.'</b></td>'.  
             '<td>'.$data->{'time'}.'</td>'.  
             '<td>'.$data->{'attempt'}.'</td>';  
     } else {  
         $Str .= '<td><b>'.$data->{'sname'}.'</b></td>';  
     }      }
     $Str .= '<td>'.$data->{'submission'}.'</td>';  
     if ($ENV{'form.correctans'} eq 'true') {      my @values;
         $Str .= '<td>'.$data->{'correct'}.'</td>';      @values = map { $response->{$_}; } @$headers;
     }  
     $Str .= '</tr>';      my $td = '<td valign="top">';
     return $Str;      my $str = $td.join('</td>'.$td,@values).'</td>';
       return $str;
 }  }
   
 sub html_option_header {  sub html_non_essay_results {
     my $header;      my ($headers,$prob,$partid,$respid,$response,$resptype) = @_;
     if ($ENV{'form.subdata'} eq 'true') {      if (! ref($headers) || ref($headers) ne 'ARRAY' || ! scalar(@$headers)) {
         foreach (@FullHeaders) {          return '';
             $header .= '<th>'.&mt($_->{'display'}).'</th>';      }
       # 
       my $submission = &HTML::Entities::decode(&unescape($response->{'Submission'})); 
       return '' if (! defined($submission) || $submission eq '');
       $submission =~ s/\\\"/\"/g;
       $submission =~ s/\\\'/\'/g;
       if ($resptype eq 'radiobutton') {
           $submission = &HTML::Entities::encode($submission,'<>&"');
           $submission =~ s/=([^=])$//;
           $submission = '<span class="LC_nobreak">'.$submission.'</span>';
       }
       $response->{'Submission'} = $submission;
       #
       my @values;
       if ($resptype =~ /^(option|match|rank)$/) {
           my %submission = 
               map { 
                   my ($foil,$value) = split('=',&unescape($_));
                   ($foil,$value);
               } split('&',$response->{'Submission'});
           my %correct;
           if (exists($response->{'Correct'})) {
               %correct = 
                   map { 
                       my ($foil,$value)=split('=',&unescape($_));
                       ($foil,$value);
                   } split('&',$response->{'Correct'});
           }
           #
           foreach my $original_header (@$headers) {
               if ($original_header =~ /^_/) {
                   # '_' denotes a foil column
                   my ($header) = ($original_header =~ m/^_(.*)$/);
                   my $option = '';
                   if ( my ($foil) = ($header =~ /(.*) Correct$/)) {
                       if (exists($correct{$foil})) {
                           $option = $correct{$foil};
                       }
                   } elsif (exists($submission{$header})) {
                       $option = $submission{$header};
                   }
                   push(@values,&HTML::Entities::encode($option));
               } elsif ($original_header eq 'Time') {
                   push(@values,&Apache::lonlocal::locallocaltime($response->{$original_header}));
               } else {
                   # A normal column
                   push(@values,$response->{$original_header});
               }
         }          }
     } else {      } else {
         $header =           @values = map { $response->{$_}; } @$headers;
             '<th>'.&mt('Username').'</th>';  
     }   
     $header .= '<th>'.&mt('Submission').'</th>';  
     if ($ENV{'form.correctans'} eq 'true') {  
         $header .= '<th>'.&mt('Correct').'</th>';  
     }  
     return $header;  
 }  
   
 sub html_option {  
     my ($data)=@_;  
     $data->{'submission'} = '<ul class="studentans">'.  
         '<li>'.join('</li><li>',  
                     map {   
                         &Apache::lonnet::unescape($_) ;  
                     } sort split('&',$data->{'submission'})).  
                         '</li><ul>';  
     $data->{'correct'} = '<ul class="correctans">'.  
         '<li>'.join('</li><li>',  
                     map {   
                         &Apache::lonnet::unescape($_) ;  
                     } sort split('&',$data->{'correct'})).'</li></ul>';  
     my $Str  = '<tr>';  
     if ($ENV{'form.subdata'} eq 'true') {  
         $Str .=   
             '<td><b>'.$data->{'sname'}.'</b></td>'.  
             '<td>'.$data->{'time'}.'</td>'.  
             '<td>'.$data->{'attempt'}.'</td>';  
     } else {  
         $Str .= '<td><b>'.$data->{'sname'}.'</b></td>';  
     }  
     $Str .= '<td>'.$data->{'submission'}.'</td>';  
     if ($ENV{'form.correctans'} eq 'true') {  
         $Str .= '<td>'.$data->{'correct'}.'</td>';  
     }      }
     $Str .= '</tr>';      my $td = '<td valign="top">';
     return $Str;      my $str = $td.join('</td>'.$td,@values).'</td>';
       return $str;
 }  }
   
   
 =pod  #########################################################
   #########################################################
     my @Columns;  ##
     push(@Columns,'username');  ##    Excel Output Routines
     push(@Columns,'domain');  ##
     push(@Columns,'attempt');  #########################################################
     push(@Columns,'time');  #########################################################
     push(@Columns,'submission');  sub prepare_excel_output {
     if ($ENV{'form.correctans'} eq 'true') { push(@Columns,'correct'); }      my ($r,$Problems,$Students,$anoncounter,$show_named) = @_;
     push(@Columns,'grading');      my $c = $r->connection();
     push(@Columns,'awarded');  
     my $awarded_col = $#Columns;  
     push(@Columns,'weight');  
     my $weight_col  = $#Columns;  
     push(@Columns,'score');  
     #  
     # Create excel worksheet  
     my $filename = '/prtspool/'.  
         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.  
         time.'_'.rand(1000000000).'.xls';  
     my $workbook  = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);  
     if (! defined($workbook)) {  
         $r->log_error("Error creating excel spreadsheet $filename: $!");  
         $r->print('<p>'.&mt("Unable to create new Excel file.  ".  
                             "This error has been logged.  ".  
                             "Please alert your LON-CAPA administrator").  
                   '</p>');  
         return undef;  
     }  
     #      #
     $workbook->set_tempdir('/home/httpd/perl/tmp');  
     #      #
     my $format = &Apache::loncommon::define_excel_formats($workbook);      # Determine the number of columns in the spreadsheet
     my $worksheet  = $workbook->addworksheet('Student Submission Data');      my $columncount = 3; # username, domain, id
       my @extra_resp_headers = &get_extra_response_headers($show_named);
       my $lastprob;
       my %problem_analysis;
       foreach my $prob (@$Problems) {
           my $symb = $prob->symb();
           my %analysis = &Apache::lonstathelpers::get_problem_data($prob->src);
           $problem_analysis{$prob->src}=\%analysis;
           foreach my $partid (@{$prob->parts}) {
               if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
                   next if ($show_named);
               } else {
                   next unless ($show_named);
               }
   
               my $responses = [$prob->responseIds($partid)];
               my $resptypes = [$prob->responseType($partid)];
               for (my $i=0;$i<scalar(@$responses);$i++) {
                   my @headers = &get_headers($prob,$partid,$responses->[$i],
                                              $resptypes->[$i],
                                              $problem_analysis{$prob->src},
                                              'excel','display',
                                              @extra_resp_headers);
                   $columncount += scalar(@headers);
               }
           }
           last if ($columncount > 255);
           $lastprob = $prob;
       }
       if ($columncount > 255) {
           $r->print('<h1>'.&mt('Unable to complete request').'</h1>'.$/.
                     '<p>'.&mt('LON-CAPA is unable to produce your Excel spreadsheet because your selections will result in more than 255 columns.  Excel allows only 255 columns in a spreadsheet.').'</p>'.$/.
                     '<p>'.&mt('Consider selecting fewer problems to generate reports on, or reducing the number of items per problem.  Or use HTML or CSV output.').'</p>'.$/);
           if (ref($lastprob)) {
               $r->print('<p>'.&mt('The last problem that will fit in the current spreadsheet is [_1].',$lastprob->compTitle).'</p>');
           }
           $r->rflush();
           return;
       }
     #      #
     # Make sure we get new weight data instead of data on a 10 minute delay      # Print out a message telling them what we are doing
     &Apache::lonnet::clear_EXT_cache_status();      if (scalar(@$Problems) > 1) {
           $r->print('<h2>'.
                     &mt('Preparing Excel spreadsheet of student responses to [_1] problems',
                         scalar(@$Problems)).
                     '</h2>');
       } else {
           $r->print('<h2>'.
                     &mt('Preparing Excel spreadsheet of student responses').
                     '</h2>');
       }
       $r->rflush();
     #      #
     # Put on the standard headers and whatnot      # Create the excel spreadsheet
     my $rows_output=0;      my ($workbook,$filename,$format) = 
     $worksheet->write($rows_output++,0,$resource->{'title'},$format->{'h1'});          &Apache::loncommon::create_workbook($r);
     $worksheet->write($rows_output++,0,$resource->{'src'},$format->{'h3'});      return if (! defined($workbook));
   
       # Worksheet name
       # The length of a worksheet name in Excel 95 is limited to 31 characters.
       # Let's make sure to not exceed the maximum length in the translation files.
       # Otherwise, no Excel file would be created.
       my $worksheetname = &mt('Student Submission Data');
       if (length($worksheetname) > 31) {
           # nice way to cut off too long name
           $worksheetname = substr($worksheetname,0,28,).'...';
       }
       my $worksheet  = $workbook->addworksheet($worksheetname);
       #
       # Add headers to the worksheet
       my $rows_output = 0;
       $worksheet->write($rows_output++,0,
                       $env{'course.'.$env{'request.course.id'}.'.description'},
                         $format->{'h1'});
     $rows_output++;      $rows_output++;
     $worksheet->write_row($rows_output++,0,\@Columns,$format->{'bold'});      my $cols_output = 0;
       my $title_row  = $rows_output++;
       my $partid_row = $rows_output++;
       my $respid_row = $rows_output++;
       my $header_row = $rows_output++;
       $worksheet->write($title_row ,0,&mt('Problem Title'),$format->{'bold'});
       $worksheet->write($partid_row,0,&mt('Part ID'),$format->{'bold'});
       $worksheet->write($respid_row,0,&mt('Response ID'),$format->{'bold'});
       # Student headers
       my @StudentColumns = &get_student_columns($show_named);
       foreach my $field (@StudentColumns) {
           $worksheet->write($header_row,$cols_output++,&mt(ucfirst($field)),
                             $format->{'bold'});
       }
       # Problem headers
       my %start_col;
       foreach my $prob (@$Problems) {
           my $title = $prob->compTitle;
           my $symb = $prob->symb();
           $worksheet->write($title_row,$cols_output,
                             $title,$format->{'h3'});
           foreach my $partid (@{$prob->parts}) {
               if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
                   next if ($show_named);
               } else {
                   next unless ($show_named);
               }
               $worksheet->write($partid_row,$cols_output,
                                 $prob->part_display($partid));
               my $responses = [$prob->responseIds($partid)];
               my $resptypes = [$prob->responseType($partid)];
               for (my $i=0;$i<scalar(@$responses);$i++) {
                   $start_col{$prob->symb}->{$partid}->{$responses->[$i]}=
                       $cols_output;
                   $worksheet->write($respid_row,$cols_output,
                                     $resptypes->[$i].', '.$responses->[$i]);
                   my @headers = &get_headers($prob,$partid,$responses->[$i],
                                              $resptypes->[$i],
                                              $problem_analysis{$prob->src},
                                              'excel','display',
                                              @extra_resp_headers);
                   foreach my $text (@headers) {
                       if ($text eq 'Time') {
                           $worksheet->set_column($cols_output,$cols_output,undef,
                                                  $format->{'date'});
                       } 
                       $worksheet->write($header_row,$cols_output++,$text);
                   }
               }
           }
       }
     #      #
     # Populate the worksheet with the student data      # Populate the worksheet with the student data
       my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,scalar(@$Students));
       my $max_row = $rows_output;
     foreach my $student (@$Students) {      foreach my $student (@$Students) {
         last if ($c->aborted());          last if ($c->aborted());
         my $results = &Apache::loncoursedata::get_response_data_by_student          $cols_output = 0;
             ($student,$resource->{'symb'},$respid);          my $student_row = $max_row;
         my %row;          foreach my $prob (@$Problems) {
         $row{'username'} = $student->{'username'};              my $symb = $prob->symb();
         $row{'domain'}   = $student->{'domain'};              foreach my $partid (@{$prob->parts}) {
         $row{'correct'}  = $student->{'answer'};                  if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
         $row{'weight'} = &Apache::lonnet::EXT                      next if ($show_named);
             ('resource.'.$partid.'.weight',$resource->{'symb'},                  } else {
              undef,undef,undef);                      next unless ($show_named);
         if (! defined($results) || ref($results) ne 'ARRAY') {                  }
             $row{'score'} = '='.                  my @Response = $prob->responseIds($partid);
                 &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell                  my @ResponseType = $prob->responseType($partid);
                     ($rows_output,$awarded_col)                  for (my $i=0;$i<=$#Response;$i++) {
                 .'*'.                      my $respid   = $Response[$i];
                 &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell                      my $resptype = $ResponseType[$i];
                     ($rows_output,$weight_col);                      my $results = 
             my $cols_output = 0;                          &Apache::loncoursedata::get_response_data_by_student
             foreach my $col (@Columns) {                          ($student,$prob->symb(),$respid);
                 if (! exists($row{$col})) {                      my @headers = &get_headers($prob,$partid,$respid,
                     $cols_output++;                                                 $resptype,
                     next;                                                 $problem_analysis{$prob->src},
                                                  'excel','normal',
                                                  @extra_resp_headers);
   
                       if (! defined($results)) {
                           $results = [];
                       }
                       #
                       $rows_output = $student_row;
                       #
                       my $response_start_col = $start_col{$prob->symb}->{$partid}->{$respid};
                       for (my $j=scalar(@$results)-1;$j>=0;$j--) {
                           $cols_output = $response_start_col;
                           if ($env{'form.all_sub'} ne 'true') {
                               next if ($j ne scalar(@$results)-1);
                           }
                           my $response = &hashify_response($results->[$j],
                                                            $prob,
                                                            $student,
                                                            $partid,
                                                            $respid);
                           my @response_data = 
                               &compile_response_data(\@headers,$response,
                                                      $prob,$partid,$respid,
                                                      $resptype,
                                                      \&excel_format_item);
                           $worksheet->write_row($rows_output++,$cols_output,
                                                 \@response_data);
                           $cols_output+=scalar(@response_data);
                           if ($rows_output > $max_row) {
                               $max_row = $rows_output;
                           }
                       }
                 }                  }
                 $worksheet->write($rows_output,$cols_output++,$row{$col});  
             }              }
             $rows_output++;          }
         } else {          # Prepend current student's user information to all rows  
             for (my $i=0;$i<scalar(@$results);$i++) {          for (my $row = $student_row;$row<$max_row;$row++) {
                 my $response = $results->[$i];              my $cols = 0;
                 if ($ENV{'form.last_sub_only'} eq 'true' &&               foreach my $field (@StudentColumns) {
                     $i < (scalar(@$results)-1)) {                  if ($show_named) {
                     next;                      my $value = $student->{$field};
                 }                      if ($field eq 'comments') {
                 delete($row{'time'});                          $value = &Apache::lonmsgdisplay::retrieve_instructor_comments
                 delete($row{'attempt'});                                   ($student->{'username'},$student->{'domain'});
                 delete($row{'submission'});                      }
                 delete($row{'awarded'});                      $worksheet->write($row,$cols++,$value);
                 delete($row{'grading'});                  } else {
                 delete($row{'score'});                      $worksheet->write($row,$cols++,
                 my %row_format;                                        &mt('Anonymized'));
                 #  
                 # Time is handled differently  
                 $row{'time'} = &Apache::lonstathelpers::calc_serial  
                     ($response->[&Apache::loncoursedata::RDs_timestamp()]);  
                 $row_format{'time'}=$format->{'date'};  
                 #  
                 $row{'attempt'}  = $response->[  
                      &Apache::loncoursedata::RDs_tries()];  
                 $row{'submission'} = $response->[  
                      &Apache::loncoursedata::RDs_submission()];  
                 if ($row{'submission'} =~ m/^=/) {  
                     # This will be interpreted as a formula.  That is bad!  
                     $row{'submission'} = " ".$row{'submission'};  
                 }  
                 $row{'grading'} = $response->[  
                      &Apache::loncoursedata::RDs_awarddetail()];  
                 $row{'awarded'} = $response->[  
                      &Apache::loncoursedata::RDs_awarded()];  
                 $row{'score'} = '='.  
                     &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell  
                         ($rows_output,$awarded_col)  
                     .'*'.  
                     &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell  
                         ($rows_output,$weight_col);  
                 my $cols_output = 0;  
                 foreach my $col (@Columns) {  
                     $worksheet->write($rows_output,$cols_output++,$row{$col},  
                                       $row_format{$col});  
                 }                  }
                 $rows_output++;  
             }              }
         } # End of else clause on if (! defined($results) ....          }
           &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
     }      }
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
     #      #
     # Close the excel file      # Close the excel file
     $workbook->close();      $workbook->close();
Line 567  sub html_option { Line 979  sub html_option {
               '";</script>');                '";</script>');
     $r->rflush();      $r->rflush();
     return;      return;
       
 }  }
   
 =cut  sub compile_response_data {
       my ($headers,$response,$prob,$partid,$respid,$resptype,$format) = @_;
       if (! ref($headers) || ref($headers) ne 'ARRAY' || ! scalar(@$headers)) {
           return ();
       }
       if (ref($format) ne 'CODE') {
           $format = sub { return $_[0]; };
       }
       #
       my $submission = 
           &HTML::Entities::decode
           (&unescape($response->{'Submission'}));
       if (!$prob->is_task()) {
    return () if (! defined($submission) || $submission eq '');
       }
       $submission =~ s/\\\"/\"/g;
       $submission =~ s/\\\'/\'/g;
       if ($resptype eq 'radiobutton') {
           $submission =~ s/=([^=])$//;
       }
       $response->{'Submission'} = $submission;
       #
       my @values;
       if ($resptype =~ /^(option|match|rank)$/) {
           my %submission = 
               map { 
                   my ($foil,$value) = split('=',&unescape($_));
                   ($foil,$value);
               } split('&',$response->{'Submission'});
           my %correct;
           if (exists($response->{'Correct'})) {
               %correct = 
                   map { 
                       my ($foil,$value)=split('=',&unescape($_));
                       ($foil,$value);
                   } split('&',$response->{'Correct'});
           }
           #
           foreach my $original_header (@$headers) {
               if ($original_header =~ /^_/) {
                   # '_' denotes a foil column
                   my ($header) = ($original_header =~ m/^_(.*)$/);
                   my $option = '';
                   if ( my ($foil) = ($header =~ /(.*) Correct$/)) {
                       if (exists($correct{$foil})) {
                           $option = $correct{$foil};
                       }
                   } elsif (exists($submission{$header})) {
                       $option = $submission{$header};
                   }
                   push(@values,&{$format}($option,$header));
               } else {
                   # A normal column
                   push(@values,&{$format}($response->{$original_header},
                                           $original_header));
               }
           }
       } else {
           @values = map { &{$format}($response->{$_},$_); } @$headers;
       }
       return @values;
   }
   
   sub excel_format_item {
       my ($item,$type) = @_;
       if ($type eq 'Time') {
           $item = &Apache::lonstathelpers::calc_serial($item);
       } else {
           if ($item =~ m/^=/) {
               $item = ' '.$item;
           }
           $item =~ s/\\r//g;
           $item =~ s/\\n/\n/g;
           $item =~ s/(\s*$|^\s*)//g;
           $item =~ s/\\\'/\'/g;
       }
       return $item;
   }
   
 #########################################################  #########################################################
 #########################################################  #########################################################
 ##  ##
 ##      Excel output of student answers and correct answers  ##      CSV output of student answers
 ##  ##
 #########################################################  #########################################################
 #########################################################  #########################################################
 sub prepare_excel_output {  sub prepare_csv_output {
     my ($r,$problem,$ProblemData,$Students) = @_;      my ($r,$problems,$students,$anoncounter,$show_named) = @_;
     my $c = $r->connection();      my $c = $r->connection();
     my ($resource,$respid,$partid) = ($problem->{'resource'},      #
                                       $problem->{'respid'},  
                                       $problem->{'part'});  
     $r->print('<h2>'.      $r->print('<h2>'.
               &mt('Preparing Excel spreadsheet of student responses').                &mt('Generating CSV report of student responses').'</h2>');
               '</h2>'.  
               '<p>'.  
               &mt('See the status bar above for student answer computation progress').  
               '</p>');  
     #  
     if ($ENV{'form.correctans'} eq 'true') {  
         &Apache::lonstathelpers::GetStudentAnswers($r,$problem,$Students,  
                                                    'Statistics',  
                                                    'stats_status');  
     }  
     #      #
     $r->print('<script>'.      # Progress window
               'window.document.Statistics.stats_status.value="'.      my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,scalar(@$students));
               'Done computing student answers.  Compiling spreadsheet.'.      
               '";</script>');  
     $r->rflush();      $r->rflush();
     my @Columns;  
     push(@Columns,'username');  
     push(@Columns,'domain');  
     push(@Columns,'attempt');  
     push(@Columns,'time');  
     push(@Columns,'submission');  
     if ($ENV{'form.correctans'} eq 'true') { push(@Columns,'correct'); }  
     push(@Columns,'grading');  
     push(@Columns,'awarded');  
     my $awarded_col = $#Columns;  
     push(@Columns,'weight');  
     my $weight_col  = $#Columns;  
     push(@Columns,'score');  
     #      #
     # Create excel worksheet      # Open a file
       my $outputfile;
     my $filename = '/prtspool/'.      my $filename = '/prtspool/'.
         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.          $env{'user.name'}.'_'.$env{'user.domain'}.'_'.
         time.'_'.rand(1000000000).'.xls';              time.'_'.rand(1000000000).'.csv';
     my $workbook  = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);      unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) {
     if (! defined($workbook)) {          $r->log_error("Couldn't open $filename for output $!");
         $r->log_error("Error creating excel spreadsheet $filename: $!");          $r->print(
         $r->print('<p>'.&mt("Unable to create new Excel file.  ".              '<p class="LC_error">'
                             "This error has been logged.  ".             .&mt('Problems occurred in writing the CSV file.')
                             "Please alert your LON-CAPA administrator").             .' '.&mt('This error has been logged.')
                   '</p>');             .' '.&mt('Please alert your LON-CAPA administrator.')
         return undef;             .'</p>'
           );
           $outputfile = undef;
       }
       #
       # Compute the number of columns per response
       my @extra_resp_headers = &get_extra_response_headers($show_named);
       #
       # Create the table header
       my @student_columns = &get_student_columns($show_named);
       my $student_column_count = scalar(@student_columns);
       #
       my %headers;
       push(@{$headers{'student'}},@student_columns);
       # Pad for the student data
       foreach my $row ('problem','part','response') {
           $headers{$row}=[map {''} @student_columns];
       }
       #
       # we put the headers into the %headers hash
       my %problem_analysis;
       my %start_col;
       my $max_column = $student_column_count;
       foreach my $prob (@$problems) {
           my $symb = $prob->symb();
           my %analysis = &Apache::lonstathelpers::get_problem_data($prob->src);
           $problem_analysis{$prob->src}=\%analysis;
           $headers{'problem'}->[$max_column] = $prob->compTitle;
           foreach my $partid (@{$prob->parts}) {
               if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
                   next if ($show_named);
               } else {
                   next unless ($show_named);
               }
               $headers{'part'}->[$max_column] = $prob->part_display($partid);
               my $responses = [$prob->responseIds($partid)];
               my $resptypes = [$prob->responseType($partid)];
               for (my $i=0;$i<scalar(@$responses);$i++) {
                   my @headers = &get_headers($prob,$partid,$responses->[$i],
                                              $resptypes->[$i],
                                              $problem_analysis{$prob->src},
                                              'csv','display',
                                              @extra_resp_headers);
                   $start_col{$prob->symb}->{$partid}->{$responses->[$i]}=
                       $max_column;
                   $headers{'response'}->[$max_column]=
                       &mt('Response [_1]',$responses->[$i]);
                   for (my $j=0;$j<=$#headers;$j++) {
                       $headers{'student'}->[$max_column+$j]=$headers[$j];
                   }
                   $max_column += scalar(@headers);
               }
           }
       }
       foreach my $row ('problem','part','response','student') {
           print $outputfile '"'.
               join('","',
                    map { 
                        &Apache::loncommon::csv_translate($_); 
                    } @{$headers{$row}}).'"'.$/;
     }      }
     #      #
     $workbook->set_tempdir('/home/httpd/perl/tmp');      # Main loop
     #      foreach my $student (@$students) {
     my $format = &Apache::loncommon::define_excel_formats($workbook);  
     my $worksheet  = $workbook->addworksheet('Student Submission Data');  
     #  
     # Make sure we get new weight data instead of data on a 10 minute delay  
     &Apache::lonnet::clear_EXT_cache_status();  
     #  
     # Put on the standard headers and whatnot  
     my $rows_output=0;  
     $worksheet->write($rows_output++,0,$resource->{'title'},$format->{'h1'});  
     $worksheet->write($rows_output++,0,$resource->{'src'},$format->{'h3'});  
     $rows_output++;  
     $worksheet->write_row($rows_output++,0,\@Columns,$format->{'bold'});  
     #  
     # Populate the worksheet with the student data  
     foreach my $student (@$Students) {  
         last if ($c->aborted());          last if ($c->aborted());
         my $results = &Apache::loncoursedata::get_response_data_by_student          my @rows;
             ($student,$resource->{'symb'},$respid);          foreach my $prob (@$problems) {
         my %row;              my $symb = $prob->symb;
         $row{'username'} = $student->{'username'};              foreach my $partid (@{$prob->parts}) {
         $row{'domain'}   = $student->{'domain'};                  if (($prob->is_anonsurvey($partid)) || ($anoncounter->{$symb."\0".$partid})) {
         $row{'correct'}  = $student->{'answer'};                      next if ($show_named); 
         $row{'weight'} = &Apache::lonnet::EXT                  } else {
             ('resource.'.$partid.'.weight',$resource->{'symb'},                      next unless ($show_named);
              undef,undef,undef);                  }
         if (! defined($results) || ref($results) ne 'ARRAY') {                  my @responses = $prob->responseIds($partid);
             $row{'score'} = '='.                  my @response_type = $prob->responseType($partid);
                 &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell                  for (my $i=0;$i<=$#responses;$i++) {
                     ($rows_output,$awarded_col)                      my $respid   = $responses[$i];
                 .'*'.                      my $resptype = $response_type[$i];
                 &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell                      my @headers = &get_headers($prob,$partid,$respid,$resptype,
                     ($rows_output,$weight_col);                                                 $problem_analysis{$prob->src},
             my $cols_output = 0;                                                 'csv','normal',
             foreach my $col (@Columns) {                                                 @extra_resp_headers);
                 if (! exists($row{$col})) {                      my $results = 
                     $cols_output++;                          &Apache::loncoursedata::get_response_data_by_student
                     next;                          ($student,$prob->symb(),$respid);
                       if (! defined($results)) {
                           $results = [];
                       }
                       for (my $j=0; $j<scalar(@$results);$j++) {
                           if ($env{'form.all_sub'} ne 'true') {
                               next if ($j != 0);
                           }
                           my $idx = scalar(@$results) - $j - 1;
                           my $response = &hashify_response($results->[$idx],
                                                            $prob,$student,
                                                            $partid,$respid);
                           my @data = &compile_response_data(\@headers,$response,
                                                             $prob,$partid,
                                                             $respid,$resptype,
                                                             \&csv_format_item);
                           my $resp_start_idx =
                               $start_col{$prob->symb}->{$partid}->{$respid};
                           for (my $k=0;$k<=$#data;$k++) {
                               $rows[$j]->[$resp_start_idx + $k] = $data[$k];
                           }
                       }
                 }                  }
                 $worksheet->write($rows_output,$cols_output++,$row{$col});  
             }              }
             $rows_output++;          }
         } else {          foreach my $row (@rows) {
             for (my $i=0;$i<scalar(@$results);$i++) {              my $student_row_data = '';
                 my $response = $results->[$i];              if ($show_named) {
                 if ($ENV{'form.last_sub_only'} eq 'true' &&                   foreach my $field (@student_columns) {
                     $i < (scalar(@$results)-1)) {                      my $value = $student->{$field};
                     next;                      if ($field eq 'comments') {
                 }                          $value = &Apache::lonmsgdisplay::retrieve_instructor_comments
                 delete($row{'time'});                                   ($student->{'username'},$student->{'domain'});
                 delete($row{'attempt'});                      }
                 delete($row{'submission'});                      $student_row_data .= '"'.&Apache::loncommon::csv_translate($value).'",';
                 delete($row{'awarded'});  
                 delete($row{'grading'});  
                 delete($row{'score'});  
                 my %row_format;  
                 #  
                 # Time is handled differently  
                 $row{'time'} = &Apache::lonstathelpers::calc_serial  
                     ($response->[&Apache::loncoursedata::RDs_timestamp()]);  
                 $row_format{'time'}=$format->{'date'};  
                 #  
                 $row{'attempt'}  = $response->[  
                      &Apache::loncoursedata::RDs_tries()];  
                 $row{'submission'} = $response->[  
                      &Apache::loncoursedata::RDs_submission()];  
                 if ($row{'submission'} =~ m/^=/) {  
                     # This will be interpreted as a formula.  That is bad!  
                     $row{'submission'} = " ".$row{'submission'};  
                 }  
                 $row{'grading'} = $response->[  
                      &Apache::loncoursedata::RDs_awarddetail()];  
                 $row{'awarded'} = $response->[  
                      &Apache::loncoursedata::RDs_awarded()];  
                 $row{'score'} = '='.  
                     &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell  
                         ($rows_output,$awarded_col)  
                     .'*'.  
                     &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell  
                         ($rows_output,$weight_col);  
                 my $cols_output = 0;  
                 foreach my $col (@Columns) {  
                     $worksheet->write($rows_output,$cols_output++,$row{$col},  
                                       $row_format{$col});  
                 }                  }
                 $rows_output++;                  $student_row_data =~ s/,$//;
               } else {
                   $student_row_data = '"'.&mt('Anonymized').'"';
             }              }
         } # End of else clause on if (! defined($results) ....              print $outputfile $student_row_data;
               for (my $i=$student_column_count;$i<$max_column;$i++) {
                   my $value = &Apache::loncommon::csv_translate($row->[$i]);
                   $value ||='';
                   print $outputfile ',"'.$value.'"';
               }
               print $outputfile $/;
           }
           undef(@rows);
           &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
     }      }
       close($outputfile);
     #      #
     # Close the excel file      # Close the progress window
     $workbook->close();      &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
     #      #
     # Write a link to allow them to download it      # Tell the user where to get their CSV file
     $r->print('<p><a href="'.$filename.'">'.      $r->print('<br />'.
               &mt('Your Excel spreadsheet.').                '<a href="'.$filename.'">'.&mt('Your CSV file.').'</a>'."\n");
               '</a></p>'."\n");  
     $r->print('<script>'.  
               'window.document.Statistics.stats_status.value="'.  
               'Done compiling spreadsheet.  See link below to download.'.  
               '";</script>');  
     $r->rflush();      $r->rflush();
     return;      return;
 }  }
   
   sub csv_format_item {
       my ($item,$type) = @_;
       if ($type eq 'Time') {
           $item = localtime($item);
       }
       $item =&Apache::loncommon::csv_translate($item); 
       return $item;
   }
   
 #########################################################  #########################################################
 #########################################################  #########################################################
 ##  ##
Line 746  sub prepare_excel_output { Line 1257  sub prepare_excel_output {
 #########################################################  #########################################################
 sub CreateInterface {  sub CreateInterface {
     ##      ##
       ## Output Selection
       my $output_selector = $/.'<select name="output">'.$/;
       foreach ('HTML','Excel','CSV') {
           $output_selector .= '    <option value="'.lc($_).'"';
           if ($env{'form.output'} eq lc($_)) {
               $output_selector .= ' selected="selected"';
           }
           $output_selector .='>'.&mt($_).'</option>'.$/;
       } 
       $output_selector .= '</select>'.$/;
       ##
     ## Environment variable initialization      ## Environment variable initialization
     my $Str = '';      my $Str = '';
     $Str .= &Apache::lonhtmlcommon::breadcrumbs      $Str .= '<br />';
         (undef,'Student Submission Reports');      $Str .= &Apache::loncommon::start_data_table();
     $Str .= '<p>';      $Str .= &Apache::loncommon::start_data_table_header_row();
     $Str .= '<table cellspacing="5">'."\n";      $Str .= '<th>'.&mt('Sections').'</th>';
     $Str .= '<tr>';      $Str .= '<th>'.&mt('Groups').'</th>';
     $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';      $Str .= '<th>'.&mt('Student Data').&Apache::loncommon::help_open_topic("Chart_Student_Data").'</th>';
     $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';      $Str .= '<th>'.&mt('Access Status').'</th>';
     $Str .= '<td>&nbsp;</td>';      $Str .= '<th>'.&mt('Options').'</th>';
     $Str .= '</tr>'."\n";      $Str .= '<th>'.&mt('Output Format').'</th>';
       $Str .= &Apache::loncommon::end_data_table_header_row();
     #      #
     $Str .= '<tr><td align="center">'."\n";      $Str .= &Apache::loncommon::start_data_table_row();
       $Str .= '<td align="center">'."\n";
     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);      $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
     $Str .= '</td>';      $Str .= '</td>';
     #      #
       $Str .= '<td align="center">'."\n";
       $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',5);
       $Str .= '</td>';
       #
       $Str .= '<td align="center">'."\n";
       $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple', 5,undef);
       $Str .= '</td>';
       #
     $Str .= '<td align="center">';      $Str .= '<td align="center">';
     $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);      $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
     $Str .= '</td>';      $Str .= '</td>';
     #      #
     # Render problem checkbox      # Render problem checkbox
     my $prob_checkbox = '<input type="checkbox" name="renderprob" ';      my $prob_checkbox = '<input type="checkbox" name="renderprob" ';
     if (exists($ENV{'form.renderprob'}) && $ENV{'form.renderprob'} eq 'true') {      if (exists($env{'form.renderprob'}) && $env{'form.renderprob'} eq 'true') {
         $prob_checkbox .= 'checked ';          $prob_checkbox .= 'checked="checked" ';
     }      }
     $prob_checkbox .= 'value="true" />';      $prob_checkbox .= 'value="true" />';
     #      #
     # Compute correct answers checkbox      # Compute correct answers checkbox
     my $ans_checkbox = '<input type="checkbox" name="correctans" ';      my $ans_checkbox = '<input type="checkbox" name="correctans" ';
     if (exists($ENV{'form.correctans'}) && $ENV{'form.correctans'} eq 'true') {      if (exists($env{'form.correctans'}) && $env{'form.correctans'} eq 'true') {
         $ans_checkbox .= 'checked ';          $ans_checkbox .= 'checked="checked" ';
     }      }
     $ans_checkbox .= 'value="true" />';      $ans_checkbox .= 'value="true" />';
     #      #
     # Only show last submission checkbox      # Show all submissions checkbox
     my $last_sub_checkbox = '<input type="checkbox" name="last_sub_only" ';      my $all_sub_checkbox = '<input type="checkbox" name="all_sub" ';
     if (exists($ENV{'form.last_sub_only'}) &&       if (exists($env{'form.all_sub'}) && 
         $ENV{'form.last_sub_only'} eq 'true') {          $env{'form.all_sub'} eq 'true') {
         $last_sub_checkbox .= 'checked ';          $all_sub_checkbox .= 'checked="checked" ';
     }      }
     $last_sub_checkbox.= 'value="true" />';      $all_sub_checkbox.= 'value="true" />';
     #      #
     # extra submission data checkbox      # problem status checkbox
     my $subdata_checkbox = '<input type="checkbox" name="subdata" ';      my $prob_status_checkbox = '<input type="checkbox" name="prob_status" ';
     if (exists($ENV{'form.subdata'}) &&       if (exists($env{'form.prob_status'}) && 
         $ENV{'form.subdata'} eq 'true') {          $env{'form.prob_status'} eq 'true') {
         $subdata_checkbox .= 'checked ';          $prob_status_checkbox .= 'checked="checked" ';
     }      }
     $subdata_checkbox.= 'value="true" />';      $prob_status_checkbox .= 'value="true" />';
     #      #
     $Str .= '<td align="right" halign="top">'.'<label><b>'.      $Str .=
         &mt('show problem [_1]',$prob_checkbox).'</b></label><br />'.          '<td valign="top">'
         '<label><b>'.         .'<label>'
         &mt('compute correct answers [_1]',$ans_checkbox).'</b></label><br />'.         .$prob_checkbox.&mt('Show problem')
         '<label><b>'.         .'</label><br />'
         &mt('final answer only [_1]',$last_sub_checkbox).'</b></label><br />'.         .'<label>'
         '<label><b>'.&mt('show extra submission data [_1]',$subdata_checkbox).         .' '.$ans_checkbox.&mt('Show correct answers')
         '</b></label><br />'.         .'</label><br />'
         '</td>';         .'<label>'
     #         .$all_sub_checkbox.&mt('Show all submissions')
     $Str .= '</tr>'."\n";         .'</label><br />'
     $Str .= '</table>'."\n";         .'<label>'
     #         .$prob_status_checkbox.&mt('Show problem grading')
     $Str .= '<nobr>'.&mt('Status: [_1]',         .'</label>'
                          '<input type="text" '.         .'</td>';
                          'name="stats_status" size="60" value="" />').      #
             '</nobr>'.'</p>';          $Str .= '<td align="center" valign="top">'.$output_selector.'</td>';
     ##      #
       $Str .= &Apache::loncommon::end_data_table_row();
       $Str .= &Apache::loncommon::end_data_table();
       #
     return $Str;      return $Str;
 }  }
   
   sub get_student_columns {
       my ($show_named) = @_; 
       my @student_columns;
       if ($show_named) {
           @student_columns = @Apache::lonstatistics::SelectedStudentData;
           if (grep(/^all$/,@student_columns)) {
               @student_columns = qw(fullname username domain id section status groups comments);
           }
       } else {
           @student_columns = ('username');
       }
       return @student_columns;
   } 
   
 1;  1;
   
 __END__  __END__

Removed from v.1.15  
changed lines
  Added in v.1.67


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.