Diff for /loncom/interface/statistics/lonstudentassessment.pm between versions 1.36 and 1.136

version 1.36, 2003/03/06 21:12:54 version 1.136, 2006/04/08 06:59:44
Line 50  Presents assessment data about a student Line 50  Presents assessment data about a student
 package Apache::lonstudentassessment;  package Apache::lonstudentassessment;
   
 use strict;  use strict;
 use Apache::lonstatistics;  use Apache::lonstatistics();
 use Apache::lonhtmlcommon;  use Apache::lonhtmlcommon();
   use Apache::loncommon();
 use Apache::loncoursedata;  use Apache::loncoursedata;
 use Apache::lonnet; # for logging porpoises  use Apache::lonnet; # for logging porpoises
   use Apache::lonlocal;
   use Apache::grades();
   use Apache::lonmsgdisplay();
   use Time::HiRes;
 use Spreadsheet::WriteExcel;  use Spreadsheet::WriteExcel;
   use Spreadsheet::WriteExcel::Utility();
   
 #######################################################  #######################################################
 #######################################################  #######################################################
Line 85  my $Statistics; Line 91  my $Statistics;
   
 =item $show 'all', 'totals', or 'scores' determines how much data is output  =item $show 'all', 'totals', or 'scores' determines how much data is output
   
   =item $single_student_mode evaluates to true if we are showing only one
   student.
   
 =cut  =cut
   
 #######################################################  #######################################################
 #######################################################  #######################################################
 my $show_links;  my $show_links;
 my $output_mode;  my $output_mode;
 my $show;  my $chosen_output;
   my $single_student_mode;
   
 #######################################################  #######################################################
 #######################################################  #######################################################
Line 126  Inputs: Line 136  Inputs:
 #######################################################  #######################################################
 sub BuildStudentAssessmentPage {  sub BuildStudentAssessmentPage {
     my ($r,$c)=@_;      my ($r,$c)=@_;
       #
     undef($Statistics);      undef($Statistics);
       undef($show_links);
       undef($output_mode);
       undef($chosen_output);
       undef($single_student_mode);
       #
       my %Saveable_Parameters = ('Status' => 'scalar',
                                  'chartoutputmode' => 'scalar',
                                  'chartoutputdata' => 'scalar',
                                  'Section' => 'array',
                                  'StudentData' => 'array',
                                  'Maps' => 'array');
       &Apache::loncommon::store_course_settings('chart',\%Saveable_Parameters);
       &Apache::loncommon::restore_course_settings('chart',\%Saveable_Parameters);
       #
       &Apache::lonstatistics::PrepareClasslist();
       #
       $single_student_mode = 0;
       $single_student_mode = 1 if ($env{'form.SelectedStudent'});
       &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
                                               ['selectstudent']);
       if ($env{'form.selectstudent'}) {
           &Apache::lonstatistics::DisplayClasslist($r);
           return;
       }
     #      #
     # Print out the HTML headers for the interface      # Print out the HTML headers for the interface
     #    This also parses the output mode selector      #    This also parses the output mode selector
     #    This step must always be done.      #    This step must *always* be done.
     $r->print(&CreateInterface());      $r->print(&CreateInterface());
     $r->print('<input type="hidden" name="notfirstrun" value="true" />');      $r->print('<input type="hidden" name="notfirstrun" value="true" />');
       $r->print('<input type="hidden" name="sort" value="'.
                 $env{'form.sort'}.'" />');
     $r->rflush();      $r->rflush();
     if (! exists($ENV{'form.notfirstrun'})) {      #
         $r->print(<<ENDMSG);      if (! exists($env{'form.notfirstrun'}) && ! $single_student_mode) {
 <p>  
 <font size="+1">  
 Please make your selections in the boxes above and hit   
 the button marked &quot;Update&nbsp;Display&quot;.  
 </font>  
 </p>  
 ENDMSG  
 #        $r->print(&OutputDescriptions());  
         return;          return;
     }      }
     #      $r->print('<h4>'.
                 &Apache::lonstatistics::section_and_enrollment_description().
                 '</h4>');
     #      #
     my $initialize     = \&html_initialize;      my $initialize     = \&html_initialize;
     my $output_student = \&html_outputstudent;      my $output_student = \&html_outputstudent;
Line 156  ENDMSG Line 187  ENDMSG
         $initialize     = \&excel_initialize;          $initialize     = \&excel_initialize;
         $output_student = \&excel_outputstudent;          $output_student = \&excel_outputstudent;
         $finish         = \&excel_finish;          $finish         = \&excel_finish;
     } elsif ($output_mode eq 'multi-sheet excel') {  
         $initialize     = \&multi_sheet_excel_initialize;  
         $output_student = \&multi_sheet_excel_outputstudent;  
         $finish         = \&multi_sheet_excel_finish;  
     } elsif ($output_mode eq 'csv') {      } elsif ($output_mode eq 'csv') {
         $initialize     = \&csv_initialize;          $initialize     = \&csv_initialize;
         $output_student = \&csv_outputstudent;          $output_student = \&csv_outputstudent;
Line 168  ENDMSG Line 195  ENDMSG
     #      #
     if($c->aborted()) {  return ; }      if($c->aborted()) {  return ; }
     #      #
       # Determine which students we want to look at
       my @Students;
       if ($single_student_mode) {
           @Students = (&Apache::lonstatistics::current_student());
           $r->print(&next_and_previous_buttons());
           $r->rflush();
       } else {
           @Students = @Apache::lonstatistics::Students;
       }
       #
       # Perform generic initialization tasks
       #       Since we use lonnet::EXT to retrieve problem weights,
       #       to ensure current data we must clear the caches out.
       #       This makes sure that parameter changes at the student level
       #       are immediately reflected in the chart.
       &Apache::lonnet::clear_EXT_cache_status();
       #
       # Clean out loncoursedata's package data, just to be safe.
       &Apache::loncoursedata::clear_internal_caches();
       #
     # Call the initialize routine selected above      # Call the initialize routine selected above
     $initialize->($r);      $initialize->($r);
     foreach my $student (@Apache::lonstatistics::Students) {      foreach my $student (@Students) {
         if($c->aborted()) {           if($c->aborted()) { 
             $finish->($r);              $finish->($r);
             return ;               return ; 
Line 186  ENDMSG Line 233  ENDMSG
   
 #######################################################  #######################################################
 #######################################################  #######################################################
   sub next_and_previous_buttons {
       my $Str = '';
       $Str .= '<input type="hidden" name="SelectedStudent" value="'.
           $env{'form.SelectedStudent'}.'" />';
       #
       # Build the previous student link
       my $previous = &Apache::lonstatistics::previous_student();
       my $previousbutton = '';
       if (defined($previous)) {
           my $sname = $previous->{'username'}.':'.$previous->{'domain'};
           $previousbutton .= '<input type="button" value="'.
               'Previous Student ('.
               $previous->{'username'}.'@'.$previous->{'domain'}.')'.
               '" onclick="document.Statistics.SelectedStudent.value='.
               "'".$sname."'".';'.
               'document.Statistics.submit();" />';
       } else {
           $previousbutton .= '<input type="button" value="'.
               'Previous student (none)'.'" />';
       }
       #
       # Build the next student link
       my $next = &Apache::lonstatistics::next_student();
       my $nextbutton = '';
       if (defined($next)) {
           my $sname = $next->{'username'}.':'.$next->{'domain'};
           $nextbutton .= '<input type="button" value="'.
               'Next Student ('.
               $next->{'username'}.'@'.$next->{'domain'}.')'.
               '" onclick="document.Statistics.SelectedStudent.value='.
               "'".$sname."'".';'.
               'document.Statistics.submit();" />';
       } else {
           $nextbutton .= '<input type="button" value="'.
               'Next student (none)'.'" />';
       }
       #
       # Build the 'all students' button
       my $all = '';
       $all .= '<input type="button" value="All Students" '.
               '" onclick="document.Statistics.SelectedStudent.value='.
               "''".';'.'document.Statistics.submit();" />';
       $Str .= $previousbutton.('&nbsp;'x5).$all.('&nbsp;'x5).$nextbutton;
       return $Str;
   }
   
   #######################################################
   #######################################################
   
 sub get_student_fields_to_show {  sub get_student_fields_to_show {
     my @to_show = @Apache::lonstatistics::SelectedStudentData;      my @to_show = @Apache::lonstatistics::SelectedStudentData;
Line 198  sub get_student_fields_to_show { Line 293  sub get_student_fields_to_show {
     return @to_show;      return @to_show;
 }  }
   
 sub get_sequences_to_show {  
     my @Sequences;  
     foreach my $map_symb (@Apache::lonstatistics::SelectedMaps) {  
         foreach my $sequence (@Apache::lonstatistics::Sequences) {  
             next if ($sequence->{'symb'} ne $map_symb && $map_symb ne 'all');  
             next if ($sequence->{'num_assess'} < 1);  
             push (@Sequences,$sequence);  
         }  
     }  
     return @Sequences;  
 }  
   
   
 #######################################################  #######################################################
 #######################################################  #######################################################
   
Line 232  the chart page. Line 314  the chart page.
 #######################################################  #######################################################
 sub CreateInterface {  sub CreateInterface {
     my $Str = '';      my $Str = '';
       $Str .= &Apache::lonhtmlcommon::breadcrumbs(undef,'Chart');
 #    $Str .= &CreateLegend();  #    $Str .= &CreateLegend();
     $Str .= '<table cellspacing="5">'."\n";      $Str .= '<table cellspacing="5">'."\n";
     $Str .= '<tr>';      $Str .= '<tr>';
     $Str .= '<td align="center"><b>Sections</b></td>';      $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
     $Str .= '<td align="center"><b>Student Data</b></td>';      $Str .= '<td align="center"><b>'.&mt('Student Data</b>').'</td>';
     $Str .= '<td align="center"><b>Problem Sets</b></td>';      $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
     $Str .= '<td align="center"><b>Output Format</b></td>';      $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
       $Str .= '<td align="center"><b>'.&mt('Output Format').'</b>'.
           &Apache::loncommon::help_open_topic("Chart_Output_Formats").
           '</td>';
       $Str .= '<td align="center"><b>'.&mt('Output Data').'</b>'.
           &Apache::loncommon::help_open_topic("Chart_Output_Data").
           '</td>';
     $Str .= '</tr>'."\n";      $Str .= '</tr>'."\n";
     #      #
     $Str .= '<tr><td align="center">'."\n";      $Str .= '<tr><td align="center">'."\n";
     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);      $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
     $Str .= '</td><td align="center">';      $Str .= '</td><td align="center">';
     my $only_seq_with_assessments = sub {   
         my $s=shift;  
         if ($s->{'num_assess'} < 1) {   
             return 0;  
         } else {   
             return 1;  
         }  
     };  
     $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',      $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',
                                                       5,undef);                                                        5,undef);
     $Str .= '</td><td>'."\n";      $Str .= '</td><td>'."\n";
     $Str .= &Apache::lonstatistics::MapSelect('Maps','multiple,all',5,      $Str .= &Apache::lonhtmlcommon::StatusOptions(undef,undef,5);
                                               $only_seq_with_assessments);      $Str .= '</td><td>'."\n";
       $Str .= &Apache::lonstatistics::map_select('Maps','multiple,all',5);
     $Str .= '</td><td>'."\n";      $Str .= '</td><td>'."\n";
     $Str .= &CreateAndParseOutputSelector();      $Str .= &CreateAndParseOutputSelector();
       $Str .= '</td><td>'."\n";
       $Str .= &CreateAndParseOutputDataSelector();
     $Str .= '</td></tr>'."\n";      $Str .= '</td></tr>'."\n";
     $Str .= '</table>'."\n";      $Str .= '</table>'."\n";
       $Str .= '<input type="submit" name="Generate Chart" value="'.
           &mt('Generate Chart').'" />';
       $Str .= '&nbsp;'x5;
       $Str .= '<input type="submit" name="selectstudent" value="'.
           &mt('Select One Student').'" />';
       $Str .= '&nbsp;'x5;
       $Str .= '<input type="submit" name="ClearCache" value="'.
           &mt('Clear Caches').'" />';
       $Str .= '&nbsp;'x5;
       $Str .= 
           &mt('Status [_1]',
               '<input type="text" name="stats_status" size="60" value="" />');
       $Str .= '<br />';
     return $Str;      return $Str;
 }  }
   
Line 281  my @OutputOptions = Line 378  my @OutputOptions =
        description => 'Output HTML with each symbol linked to the problem '.         description => 'Output HTML with each symbol linked to the problem '.
    'which generated it.',     'which generated it.',
        mode => 'html',         mode => 'html',
        show => 'all',  
        show_links => 'yes',         show_links => 'yes',
        },         },
        { name  => 'HTML, with all links',
          value => 'html, with all links',
          description => 'Output HTML with each symbol linked to the problem '.
      'which generated it.  '.
              'This includes links for unattempted problems.',
          mode => 'html',
          show_links => 'all',
          },
      { name  => 'HTML, without links',       { name  => 'HTML, without links',
        value => 'html, without links',         value => 'html, without links',
        description => 'Output HTML.  By not including links, the size of the'.         description => 'Output HTML.  By not including links, the size of the'.
    ' web page is greatly reduced.  If your browser crashes on the '.     ' web page is greatly reduced.  If your browser crashes on the '.
    'full display, try this.',     'full display, try this.',
        mode => 'html',         mode => 'html',
        show => 'all',  
        show_links => 'no',  
            },  
      { name  => 'HTML, scores only',  
        value => 'html, scores only',  
        description => 'Output HTML, only showing the total number of correct'.  
    ' problems (or problem parts) and not the maximum possible for '.  
    'each student',  
        mode => 'html',  
        show => 'scores',  
        show_links => 'no',         show_links => 'no',
            },             },
      { name  => 'HTML, totals',       { name  => 'Excel',
        value => 'html, totals',         value => 'excel',
        description => 'Output HTML, but only the summary statistics for each'.         description => 'Output an Excel file (compatable with Excel 95).',
    ' sequence selected.',  
        mode => 'html',  
        show => 'totals',  
        show_links => 'no',  
        },  
      { name  => 'Excel, scores only',  
        value => 'excel, scores only',  
        description => 'Output an Excel file (compatable with Excel 95), '.  
    'with a single column for each sequence showing the students '.  
    'score.',  
        mode => 'excel',         mode => 'excel',
        show => 'scores',  
        show_links => 'no',         show_links => 'no',
            },     },
      { name  => 'Excel, totals',       { name  => 'CSV',
        value => 'excel, totals',         value => 'csv',
        description => 'Output an Excel file (compatable with Excel 95), '.         description => 'Output a comma separated values file suitable for '.
    'with two columns for each sequence, the students score on the '.             'import into a spreadsheet program.  Using this method as opposed '.
    'sequence and the students maximum possible on the sequence',             'to Excel output allows you to organize your data before importing'.
        mode => 'excel',             ' it into a spreadsheet program.',
        show => 'totals',  
        show_links => 'no',  
            },  
      { name  => 'multi-sheet Excel',  
        value => 'multi-sheet excel',  
        description => 'Output an Excel file (compatable with Excel 95), '.  
    'with a seperate worksheet for each sequence you have selected '.  
            'the data for each problem part '.  
            '(number of tries, status, points awarded) will be listed.',  
        mode => 'multi-sheet excel',  
        show => 'totals',  
        show_links => 'no',  
            },  
      { name  => 'multi-sheet Excel, by section',  
        value => 'multi-sheet excel, by section',  
        description => 'Output an Excel file (compatable with Excel 95), '.  
    'with a seperate worksheet for each sequence you have selected '.  
            'the data for each problem part '.  
            '(number of tries, status, points awarded) will be listed.  '.  
            'There will be one Excel workbook for each section selected.',  
        mode => 'multi-sheet excel',  
        show => 'by section',  
        show_links => 'no',  
            },  
      { name  => 'CSV, everything',  
        value => 'csv, everything',  
        description => '',  
        mode => 'csv',         mode => 'csv',
        show => 'all',  
        show_links => 'no',  
            },  
      { name  => 'CSV, scores only',  
        value => 'csv, scores only',  
        description => '',  
        mode => 'csv',  
        show => 'scores',  
        show_links => 'no',  
            },  
      { name  => 'CSV, totals',  
        value => 'csv, totals',  
        description => '',  
        mode => 'csv',  
        show => 'totals',  
        show_links => 'no',         show_links => 'no',
            },             },
      );       );
   
 sub OutputDescriptions {  sub OutputDescriptions {
     my $Str = '';      my $Str = '';
     $Str .= "<h2>Output Modes</h2>\n";      $Str .= "<h2>Output Formats</h2>\n";
     $Str .= "<dl>\n";      $Str .= "<dl>\n";
     foreach my $outputmode (@OutputOptions) {      foreach my $outputmode (@OutputOptions) {
  $Str .="    <dt>".$outputmode->{'name'}."</dt>\n";   $Str .="    <dt>".$outputmode->{'name'}."</dt>\n";
Line 386  sub OutputDescriptions { Line 427  sub OutputDescriptions {
   
 sub CreateAndParseOutputSelector {  sub CreateAndParseOutputSelector {
     my $Str = '';      my $Str = '';
     my $elementname = 'outputmode';      my $elementname = 'chartoutputmode';
       &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
                                               [$elementname]);
     #      #
     # Format for output options is 'mode, restrictions';      # Format for output options is 'mode, restrictions';
     my $selected = 'html, with links';      my $selected = (&Apache::loncommon::get_env_multiple('form.'.$elementname))[0];
     if (exists($ENV{'form.'.$elementname})) {      $selected = 'html, without links' if (!$selected);
         if (ref($ENV{'form.'.$elementname} eq 'ARRAY')) {  
             $selected = $ENV{'form.'.$elementname}->[0];  
         } else {  
             $selected = $ENV{'form.'.$elementname};  
         }  
     }  
     #      #
     # Set package variables describing output mode      # Set package variables describing output mode
     $show_links  = 'no';      $show_links  = 'no';
     $output_mode = 'html';      $output_mode = 'html';
     $show        = 'all';  
     foreach my $option (@OutputOptions) {      foreach my $option (@OutputOptions) {
         next if ($option->{'value'} ne $selected);          next if ($option->{'value'} ne $selected);
         $output_mode = $option->{'mode'};          $output_mode = $option->{'mode'};
         $show        = $option->{'show'};  
         $show_links  = $option->{'show_links'};          $show_links  = $option->{'show_links'};
     }      }
   
Line 415  sub CreateAndParseOutputSelector { Line 451  sub CreateAndParseOutputSelector {
     foreach my $option (@OutputOptions) {      foreach my $option (@OutputOptions) {
         $Str .= "\n".'    <option value="'.$option->{'value'}.'"';          $Str .= "\n".'    <option value="'.$option->{'value'}.'"';
         $Str .= " selected " if ($option->{'value'} eq $selected);          $Str .= " selected " if ($option->{'value'} eq $selected);
         $Str .= ">".$option->{'name'}."<\/option>";          $Str .= ">".&mt($option->{'name'})."<\/option>";
     }      }
     $Str .= "\n</select>";      $Str .= "\n</select>";
     return $Str;      return $Str;
 }  }
   
   ##
   ## Data selector stuff
   ##
   my @OutputDataOptions =
       (
        { name  => 'Scores Summary',
          base  => 'scores',
          value => 'sum and total',
          scores => 1,
          tries  => 0,
          every_problem => 0,
          sequence_sum => 1,
          sequence_max => 1,
          grand_total => 1,
          grand_maximum => 1,
          summary_table => 1,
          maximum_row => 1,
          ignore_weight => 0,
          shortdesc => 'Total Score and Maximum Possible for each '.
              'Sequence or Folder',
          longdesc => 'The score of each student as well as the '.
              ' maximum possible on each Sequence or Folder.',
          },
        { name  => 'Scores Per Problem',
          base  => 'scores',
          value => 'scores',
          scores => 1,
          tries  => 0,
          correct => 0,
          every_problem => 1,
          sequence_sum => 1,
          sequence_max => 1,
          grand_total => 1,
          grand_maximum => 1,
          summary_table => 1,
          maximum_row => 1,
          ignore_weight => 0,
          shortdesc => 'Score on each Problem Part',
          longdesc =>'The students score on each problem part, computed as'.
              'the part weight * part awarded',
          },
        { name  =>'Tries',
          base  =>'tries',
          value => 'tries',
          scores => 0,
          tries  => 1,
          correct => 0,
          every_problem => 1,
          sequence_sum => 0,
          sequence_max => 0,
          grand_total => 0,
          grand_maximum => 0,
          summary_table => 0,
          maximum_row => 0,
          ignore_weight => 0,
          shortdesc => 'Number of Tries before success on each Problem Part',
          longdesc =>'The number of tries before success on each problem part.',
          non_html_notes => 'negative values indicate an incorrect problem',
          },
        { name  =>'Parts Correct',
          base  =>'tries',
          value => 'parts correct total',
          scores => 0,
          tries  => 0,
          correct => 1,
          every_problem => 1,
          sequence_sum => 1,
          sequence_max => 1,
          grand_total => 1,
          grand_maximum => 1,
          summary_table => 1,
          maximum_row => 0,
          ignore_weight => 1,
          shortdesc => 'Number of Problem Parts completed successfully.',
          longdesc => 'The Number of Problem Parts completed successfully and '.
              'the maximum possible for each student',
          },
        );
   
   sub HTMLifyOutputDataDescriptions {
       my $Str = '';
       $Str .= "<h2>Output Data</h2>\n";
       $Str .= "<dl>\n";
       foreach my $option (@OutputDataOptions) {
           $Str .= '    <dt>'.$option->{'name'}.'</dt>';
           $Str .= '<dd>'.$option->{'longdesc'}.'</dd>'."\n";
       }
       $Str .= "</dl>\n";
       return $Str;
   }
   
   sub CreateAndParseOutputDataSelector {
       my $Str = '';
       my $elementname = 'chartoutputdata';
       #
       my $selected = (&Apache::loncommon::get_env_multiple('form.'.$elementname))[0];
       $selected = 'scores' if (!$selected);
   
       #
       $chosen_output = $OutputDataOptions[0];
       foreach my $option (@OutputDataOptions) {
           if ($option->{'value'} eq $selected) {
               $chosen_output = $option;
           }
       }
       #
       # Build the form element
       $Str = qq/<select size="5" name="$elementname">/;
       foreach my $option (@OutputDataOptions) {
           $Str .= "\n".'    <option value="'.$option->{'value'}.'"';
           $Str .= " selected " if ($option->{'value'} eq $chosen_output->{'value'});
           $Str .= ">".&mt($option->{'name'})."<\/option>";
       }
       $Str .= "\n</select>";
       return $Str;
   
   }
   
   #######################################################
   #######################################################
   sub count_parts {
       my ($navmap,$sequence) = @_;
       my @resources = &get_resources($navmap,$sequence);
       my $count = 0;
       foreach my $res (@resources) {
           $count += scalar(@{$res->parts});
       }
       return $count;
   }
   
   sub get_resources {
       my ($navmap,$sequence) = @_;
       my @resources = $navmap->retrieveResources($sequence,
                                                  sub { shift->is_problem(); },
                                                  0,0,0);
       return @resources;
   }
   
 #######################################################  #######################################################
 #######################################################  #######################################################
   
Line 446  Return a line of the chart for a student Line 620  Return a line of the chart for a student
     my $padding;      my $padding;
     my $count;      my $count;
   
       my $nodata_count; # The number of students for which there is no data
       my %prog_state;   # progress state used by loncommon PrgWin routines
       my $total_sum_width;
   
       my %width; # Holds sequence width information
       my @sequences;
       my $navmap; # Have to keep this around since weakref is a bit zealous
   
   sub html_cleanup {
       undef(%prog_state);
       undef(%width);
       #
       undef($navmap);
       undef(@sequences);
   }
   
 sub html_initialize {  sub html_initialize {
     my ($r) = @_;      my ($r) = @_;
     #      #
     $padding = ' 'x3;      $padding = ' 'x3;
     $count = 0;      $count = 0;
       $nodata_count = 0;
       &html_cleanup();
       ($navmap,@sequences) = 
           &Apache::lonstatistics::selected_sequences_with_assessments();
       if (! ref($navmap)) {
           # Unable to get data, so bail out
           $r->print("<h3>".
                     &mt('Unable to retrieve course information.').
                     '</h3>');
       }
   
       # If we're showing links, show a checkbox to open in new
       # windows.
       if ($show_links ne 'no') {
           $r->print(<<NEW_WINDOW_CHECKBOX);
   <script type="text/javascript">new_window = true;</script>
   <p><label>Show links in new window: 
   <input type="checkbox" checked="1" onclick="new_window=this.checked" />
   </label></p>
   NEW_WINDOW_CHECKBOX
       }
   
       #
       $r->print("<h3>".$env{'course.'.$env{'request.course.id'}.'.description'}.
                 "&nbsp;&nbsp;".localtime(time)."</h3>");
     #      #
       if ($chosen_output->{'base'} !~ /^final table/) {
           $r->print("<h3>".$chosen_output->{'shortdesc'}."</h3>");        
       }
     my $Str = "<pre>\n";      my $Str = "<pre>\n";
     # First, the @StudentData fields need to be listed      # First, the @StudentData fields need to be listed
     my @to_show = &get_student_fields_to_show();      my @to_show = &get_student_fields_to_show();
Line 461  sub html_initialize { Line 679  sub html_initialize {
         my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};          my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
         $Str .= $title.' 'x($width-$base).$padding;          $Str .= $title.' 'x($width-$base).$padding;
     }      }
     # Now the selected sequences need to be listed      #
     foreach my $sequence (&get_sequences_to_show) {      # Compute the column widths and output the sequence titles
         my $title = $sequence->{'title'};      my $total_count;
         my $base  = $sequence->{'base_width'};      #
         my $width = $sequence->{'width'};      # Compute sequence widths
         $Str .= $title.' 'x($width-$base).$padding;      my $starttime = Time::HiRes::time;
       foreach my $seq (@sequences) {
           my $symb = $seq->symb;
           my $title = $seq->compTitle;
           $width{$symb}->{'width_sum'} = 0;
           # Compute width of sum
           if ($chosen_output->{'sequence_sum'}) {
               if ($chosen_output->{'every_problem'}) {
                   # Use 1 digit for a space
                   $width{$symb}->{'width_sum'} += 1;            
               }
       $total_count += &count_parts($navmap,$seq);
               # Use 3 digits for the sum
               $width{$symb}->{'width_sum'} += 3;
           }
           # Compute width of maximum
           if ($chosen_output->{'sequence_max'}) {
               if ($width{$symb}->{'width_sum'}>0) {
                   # One digit for the '/'
                   $width{$symb}->{'width_sum'} +=1;
               }
               # Use 3 digits for the total
               $width{$symb}->{'width_sum'}+=3;
           }
    #
           if ($chosen_output->{'every_problem'}) {
               # one problem per digit
               $width{$symb}->{'width_parts'}= &count_parts($navmap,$seq);
               $width{$symb}->{'width_problem'} += $width{$symb}->{'width_parts'};
           } else {
               $width{$symb}->{'width_problem'} = 0;
           }
           $width{$symb}->{'width_total'} = $width{$symb}->{'width_problem'} + 
                                        $width{$symb}->{'width_sum'};
           if ($width{$symb}->{'width_total'} < length(&HTML::Entities::decode($title))) {
               $width{$symb}->{'width_total'} = length(&HTML::Entities::decode($title));
           }
           #
           # Output the sequence titles
           $Str .= $title.(' 'x($width{$symb}->{'width_total'}-
                               length($title)
                               )).$padding;
     }      }
     $Str .= "total (of shown problems)</pre>\n";      $total_sum_width = length($total_count)+1;
       $Str .= "    total</pre>\n";
     $Str .= "<pre>";      $Str .= "<pre>";
     $r->print($Str);      $r->print($Str);
     $r->rflush();      $r->rflush();
   
       $r->print(<<JS);
   <script>
   // get the left offset of a given widget as an absolute position
   function getLeftOffset (element) {
       return collect(element, "offsetLeft");
   }
   
   // get the top offset of a given widget as an absolute position
   function getTopOffset (element) {
       return collect(element, "offsetTop");
   }
   
   function collect(element, att) {
       var val = 0;
       while(element) {
           val += element[att];
           element = element.offsetParent;
       }
       return val;
   }
   
   var currentDiv;
   var oldBorder;
   var currentElement;
   function popup_score(element, score) {
       popdown_score();
       var left = getLeftOffset(element);
       var top = getTopOffset(element);
       var div = document.createElement("div");
       div.style.border = "1px solid #8888FF";
       div.style.backgroundColor = "#CCCCFF";
       div.appendChild(document.createTextNode(score));
       div.style.position = "absolute";
       div.style.top = (top - 25) + "px";
       div.style.left = (left - 10) + "px";
       currentDiv = div;
       document.body.insertBefore(div, document.body.childNodes[0]);
       oldBorder = element.style.border;
       element.style.border = "1px solid yellow";
       currentElement = element;
   }
   
   function popdown_score() {
       if (currentDiv) {
           document.body.removeChild(currentDiv);
       }
       if (currentElement) {
           currentElement.style.border = oldBorder;
       }
       currentDiv = undefined;
   }
   </script>
   JS
   
       #
       # Let the user know what we are doing
       my $studentcount = scalar(@Apache::lonstatistics::Students); 
       if ($env{'form.SelectedStudent'}) {
           $studentcount = '1';
       }
       #
       # Initialize progress window
       %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
           ($r,'HTML Chart Status',
            'HTML Chart Progress', $studentcount,
            'inline',undef,'Statistics','stats_status');
       #
       &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
                                             'Processing first student');
     return;      return;
 }  }
   
 sub html_outputstudent {  sub html_outputstudent {
     my ($r,$student) = @_;      my ($r,$student) = @_;
     my $Str = '';      my $Str = '';
       return if (! defined($navmap));
     #      #
     if($count++ % 5 == 0 && $count > 0) {      if($count++ % 5 == 0 && $count > 0) {
         $r->print("</pre><pre>");          $r->print("</pre><pre>");
Line 486  sub html_outputstudent { Line 817  sub html_outputstudent {
     my @to_show = &get_student_fields_to_show();      my @to_show = &get_student_fields_to_show();
     foreach my $field (@to_show) {      foreach my $field (@to_show) {
         my $title=$student->{$field};          my $title=$student->{$field};
           # Deal with 'comments' - how I love special cases
           if ($field eq 'comments') {
               $title = '<a href="/adm/'.$student->{'domain'}.'/'.$student->{'username'}.'/'.'aboutme#coursecomment">'.&mt('Comments').'</a>';
           }
         my $base = length($title);          my $base = length($title);
         my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};          my $width=$Apache::lonstatistics::StudentData{$field}->{'width'};
         $Str .= $title.' 'x($width-$base).$padding;          $Str .= $title.' 'x($width-$base).$padding;
Line 494  sub html_outputstudent { Line 829  sub html_outputstudent {
     my %StudentsData;      my %StudentsData;
     my @tmp = &Apache::loncoursedata::get_current_state      my @tmp = &Apache::loncoursedata::get_current_state
         ($student->{'username'},$student->{'domain'},undef,          ($student->{'username'},$student->{'domain'},undef,
          $ENV{'request.course.id'});           $env{'request.course.id'});
     if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {      if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
         %StudentsData = @tmp;          %StudentsData = @tmp;
     }      }
     if (scalar(@tmp) < 1) {      if (scalar(@tmp) < 1) {
           $nodata_count++;
         $Str .= '<font color="blue">No Course Data</font>'."\n";          $Str .= '<font color="blue">No Course Data</font>'."\n";
         $r->print($Str);          $r->print($Str);
         $r->rflush();          $r->rflush();
Line 508  sub html_outputstudent { Line 844  sub html_outputstudent {
     # By sequence build up the data      # By sequence build up the data
     my $studentstats;      my $studentstats;
     my $PerformanceStr = '';      my $PerformanceStr = '';
     foreach my $seq (&get_sequences_to_show) {      foreach my $seq (@sequences) {
         my ($performance,$score,$seq_max) =          my $symb = $seq->symb;
             &StudentPerformanceOnSequence($student,\%StudentsData,          my ($performance,$performance_length,$score,$seq_max,$rawdata);
                                           $seq,$show_links);          if ($chosen_output->{'tries'}) {
         my $ratio = $score.'/'.$seq_max;              ($performance,$performance_length,$score,$seq_max,$rawdata) =
         #                  &student_tries_on_sequence($student,\%StudentsData,
         if ($show eq 'totals') {                                             $navmap,$seq,$show_links);
             $performance = ' 'x(length($seq_max)-length($score)).$ratio;  
             $performance .= ' 'x($seq->{'width'}-length($performance));  
         } elsif ($show eq 'scores') {  
             $performance = $score;  
             $performance .= ' 'x($seq->{'width'}-length($performance));  
         } else {          } else {
             # Pad with extra spaces              ($performance,$performance_length,$score,$seq_max,$rawdata) =
             $performance .= ' 'x($seq->{'width'}-$seq_max-                  &student_performance_on_sequence($student,\%StudentsData,
                                  length($ratio)                                                   $navmap,$seq,$show_links,
                                  ).$ratio;                                                   $chosen_output->{ignore_weight});
           }
           my $ratio='';
           if ($chosen_output->{'every_problem'} && 
               $chosen_output->{'sequence_sum'}) {
               $ratio .= ' ';
           }
           if ($chosen_output->{'sequence_sum'} && $score ne ' ') {
               my $score .= sprintf("%3.0f",$score);
               $ratio .= (' 'x(3-length($score))).$score;
           } elsif($chosen_output->{'sequence_sum'}) {
               $ratio .= ' 'x3;
           }
           if ($chosen_output->{'sequence_max'}) {
               if ($chosen_output->{'sequence_sum'}) {
                   $ratio .= '/';
               }
               $ratio .= sprintf("%3.0f",$seq_max);
           }
           #
           if (! $chosen_output->{'every_problem'}) {
               $performance = '';
       $performance_length=0;
         }          }
           $performance .= ' 'x($width{$symb}->{'width_total'} -
                                $performance_length -
                                $width{$symb}->{'width_sum'}).
               $ratio;
         #          #
         $Str .= $performance.$padding;          $Str .= $performance.$padding;
         #          #
         $studentstats->{$seq->{'symb'}}->{'score'}= $score;          $studentstats->{$symb}->{'score'}= $score;
         $studentstats->{$seq->{'symb'}}->{'max'}  = $seq_max;          $studentstats->{$symb}->{'max'}  = $seq_max;
     }      }
     #      #
     # Total it up and store the statistics info.      # Total it up and store the statistics info.
     my ($score,$max) = (0,0);      my ($score,$max);
     while (my ($symb,$seq_stats) = each (%{$studentstats})) {      while (my ($symb,$seq_stats) = each (%{$studentstats})) {
         $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};          $Statistics->{$symb}->{'score'} += $seq_stats->{'score'};
         $Statistics->{$symb}->{'max'}   += $seq_stats->{'max'};          if ($Statistics->{$symb}->{'max'} < $seq_stats->{'max'}) {
         $score += $seq_stats->{'score'};              $Statistics->{$symb}->{'max'} = $seq_stats->{'max'};
           }
           if ($seq_stats->{'score'} ne ' ') {
               $score += $seq_stats->{'score'};
               $Statistics->{$symb}->{'num_students'}++;
           }
         $max   += $seq_stats->{'max'};          $max   += $seq_stats->{'max'};
     }      }
     $Str .= ' '.' 'x(length($max)-length($score)).$score.'/'.$max;      if (! defined($score)) {
           $score = ' ' x $total_sum_width;
       } else {
           $score = sprintf("%.0f",$score);
           $score = (' 'x(3-length($score))).$score;
       }
       $Str .= ' '.' 'x($total_sum_width-length($score)).$score.' / '.$max;
     $Str .= " \n";      $Str .= " \n";
       #
     $r->print($Str);      $r->print($Str);
     #      #
     $r->rflush();      $r->rflush();
       &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
     return;      return;
 }      }    
   
 sub html_finish {  sub html_finish {
     my ($r) = @_;      my ($r) = @_;
       return if (! defined($navmap));
       #
       # Check for suppressed output and close the progress window if so
     $r->print("</pre>\n");       $r->print("</pre>\n"); 
       if ($chosen_output->{'summary_table'}) {
           if ($single_student_mode) {
               $r->print(&SingleStudentTotal());
           } else {
               $r->print(&StudentAverageTotal());
           }
       }
     $r->rflush();      $r->rflush();
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
       &html_cleanup();
     return;      return;
 }  }
   
 }  sub StudentAverageTotal {
       my $Str = '<h3>'.&mt('Summary Tables').'</h3>'.$/;
 #######################################################      $Str .= '<table border=2 cellspacing="1">'."\n";
 #######################################################      $Str .= '<tr>'.
           '<th>'.&mt('Title').'</th>'.
 =pod          '<th>'.&mt('Average').'</th>'.
           '<th>'.&mt('Maximum').'</th>'.
 =head2 Multi-Sheet EXCEL subroutines          '</tr>'.$/;
       foreach my $seq (@sequences) {
 =item &multi_sheet_excel_initialize($r)          my $symb = $seq->symb;
           my $ave;
 =item &multi_sheet_excel_outputstudent($r,$student)          my $num_students = $Statistics->{$symb}->{'num_students'};
           if ($num_students > 0) {
 =item &multi_sheet_excel_finish($r)              $ave = int(100*
                          ($Statistics->{$symb}->{'score'}/$num_students)
 =cut                         )/100;
   
 #######################################################  
 #######################################################  
 {  
   
 sub multi_sheet_excel_initialize {  
     my ($r)=@_;  
     $r->print("<h1>Not yet implemented</h1>");  
     #   
     # Estimate the size of the file.  We would like to have < 5 megs of data.  
     my $max_size = 5000000;  
     my $num_students  = scalar(@Apache::lonstatistics::Students);  
     my $num_sequences = 0;  
     my $num_data_per_part  = 2; # 'status' and 'numtries'  
     my $fields_per_student = scalar(&get_student_fields_to_show());  
     my $bytes_per_field    = 20; # Back of the envelope calculation  
     foreach my $seq (&get_sequences_to_show) {  
         $num_sequences++ if ($seq->{'num_assess'} > 0);  
         $fields_per_student += $num_data_per_part * $seq->{'num_assess_parts'};  
     }  
     my $size_estimate = $fields_per_student*$num_students*$bytes_per_field;  
     #  
     # Compute number of workbooks  
     my $num_workbooks = 1;  
     if ($size_estimate > $max_size) { # try to stay under 5 megs  
         $num_workbooks += int($size_estimate / $max_size);  
     }  
     if ($show eq 'by section') {  
         if (@Apache::lonstatistics::SelectedSections > 1 &&   
             $Apache::lonstatistics::SelectedSections[0] ne 'all') {  
             $num_workbooks = scalar(@Apache::lonstatistics::SelectedSections);  
         } else {          } else {
             # @Apache::lonstatistics::Sections contains 'all' as well.              $ave = 0;
             $num_workbooks = scalar(@Apache::lonstatistics::Sections) - 1;  
         }          }
           my $max = $Statistics->{$symb}->{'max'};
           $ave = sprintf("%.2f",$ave);
           $Str .= '<tr><td>'.$seq->compTitle.'</td>'.
               '<td align="right">'.$ave.'&nbsp;</td>'.
               '<td align="right">'.$max.'&nbsp;'.'</td></tr>'."\n";
     }      }
           $Str .= "</table>\n";
     $r->print("Maximum allowed size: ".$max_size." bytes<br />");      return $Str;
     $r->print("Number of students: ".$num_students."<br />");  
     $r->print("Number of fields per student: ".$fields_per_student."<br />");  
     $r->print("Total number of fields: ".($fields_per_student*$num_students).  
               "<br />");  
     $r->print("Bytes per field: ".$bytes_per_field." (estimated)"."<br />");  
     $r->print("Estimated size: ".$size_estimate." bytes<br />");  
     $r->print("Number of workbooks: ".$num_workbooks."<br />");  
     $r->rflush();  
     return;  
 }  }
   
 sub multi_sheet_excel_outputstudent {  sub SingleStudentTotal {
     my ($r,$student) = @_;      return if (! defined($navmap));
       my $student = &Apache::lonstatistics::current_student();
       my $Str = '<h3>'.&mt('Summary table for [_1] ([_2]@[_3])',
                            $student->{'fullname'},
                            $student->{'username'},$student->{'domain'}).'</h3>';
       $Str .= $/;
       $Str .= '<table border=2 cellspacing="1">'."\n";
       $Str .= 
           '<tr>'.
           '<th>'.&mt('Sequence or Folder').'</th>';
       if ($chosen_output->{'base'} eq 'tries') {
           $Str .= '<th>'.&mt('Parts Correct').'</th>';
       } else {
           $Str .= '<th>'.&mt('Score').'</th>';
       }
       $Str .= '<th>'.&mt('Maximum').'</th>'."</tr>\n";
       my $total = 0;
       my $total_max = 0;
       foreach my $seq (@sequences) {
           my $value = $Statistics->{$seq->symb}->{'score'};
           my $max = $Statistics->{$seq->symb}->{'max'};
           $Str .= '<tr><td>'.&HTML::Entities::encode($seq->compTitle).'</td>'.
               '<td align="right">'.$value.'</td>'.
                   '<td align="right">'.$max.'</td></tr>'."\n";
           $total += $value;
           $total_max +=$max;
       }
       $Str .= '<tr><td><b>'.&mt('Total').'</b></td>'.
           '<td align="right">'.$total.'</td>'.
           '<td align="right">'.$total_max."</td></tr>\n";
       $Str .= "</table>\n";
       return $Str;
 }  }
   
 sub multi_sheet_excel_finish {  
     my ($r) = @_;  
 }  }
   
 }  
 #######################################################  #######################################################
 #######################################################  #######################################################
   
Line 651  sub multi_sheet_excel_finish { Line 1025  sub multi_sheet_excel_finish {
   
 my $excel_sheet;  my $excel_sheet;
 my $excel_workbook;  my $excel_workbook;
   my $format;
   
 my $filename;  my $filename;
 my $rows_output;  my $rows_output;
 my $cols_output;  my $cols_output;
   
 my %prog_state; # progress window state  my %prog_state; # progress window state
   my $request_aborted;
   
   my $total_formula;
   my $maximum_formula;
   my %formula_data;
   
   my $navmap;
   my @sequences;
   
   sub excel_cleanup {
       #
       undef ($excel_sheet);
       undef ($excel_workbook);
       undef ($filename);
       undef ($rows_output);
       undef ($cols_output);
       undef (%prog_state);
       undef ($request_aborted);
       undef ($total_formula);
       undef ($maximum_formula);
       #
       undef(%formula_data);
       #
       undef($navmap);
       undef(@sequences);
   }
   
 sub excel_initialize {  sub excel_initialize {
     my ($r) = @_;      my ($r) = @_;
   
       &excel_cleanup();
       ($navmap,@sequences) = 
           &Apache::lonstatistics::selected_sequences_with_assessments();
       if (! ref($navmap)) {
           # Unable to get data, so bail out
           $r->print("<h3>".
                     &mt('Unable to retrieve course information.').
                     '</h3>');
       }
       #
       my $total_columns = scalar(&get_student_fields_to_show());
       my $num_students = scalar(@Apache::lonstatistics::Students);
       #
       foreach my $seq (@sequences) {
           if ($chosen_output->{'every_problem'}) {
               $total_columns+=&count_parts($navmap,$seq);
           }
           # Add 2 because we need a 'sequence_sum' and 'total' column for each
           $total_columns += 2;
       }
       my $too_many_cols_error_message = 
           '<h2>'.&mt('Unable to Complete Request').'</h2>'.$/.
           '<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('You may consider reducing the number of <b>Sequences or Folders</b> you have selected.').'</p>'.$/.
           '<p>'.&mt('LON-CAPA can produce <b>CSV</b> files of this data or Excel files of the <b>Scores Summary</b> data.').'</p>'.$/;
       if ($chosen_output->{'base'} eq 'tries' && $total_columns > 255) {
           $r->print($too_many_cols_error_message);
           $request_aborted = 1;
       }
       if ($chosen_output->{'base'} eq 'scores' && $total_columns > 255) {
           $r->print($too_many_cols_error_message);
           $request_aborted = 1;
       }
       return if ($request_aborted);
     #      #
     $filename = '/prtspool/'.  
         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.  
             time.'_'.rand(1000000000).'.xls';  
     #      #
     $excel_workbook = undef;      $excel_workbook = undef;
     $excel_sheet = undef;      $excel_sheet = undef;
Line 671  sub excel_initialize { Line 1104  sub excel_initialize {
     $rows_output = 0;      $rows_output = 0;
     $cols_output = 0;      $cols_output = 0;
     #      #
     # Create sheet      # Determine rows 
     $excel_workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);      my $header_row = $rows_output++;
       my $description_row = $rows_output++;
       my $notes_row = $rows_output++;
       $rows_output++;        # blank row
       my $summary_header_row;
       if ($chosen_output->{'summary_table'}) {
           $summary_header_row = $rows_output++;
           $rows_output+= scalar(@sequences);
           $rows_output++;
       }
       my $sequence_name_row = $rows_output++;
       my $resource_name_row = $rows_output++;
       my $maximum_data_row = $rows_output++;
       if (! $chosen_output->{'maximum_row'}) {
           $rows_output--;
       }
       my $first_data_row = $rows_output++;
     #      #
     # Check for errors      # Create sheet
     if (! defined($excel_workbook)) {      ($excel_workbook,$filename,$format)=
         $r->log_error("Error creating excel spreadsheet $filename: $!");          &Apache::loncommon::create_workbook($r);
         $r->print("Problems creating new Excel file.  ".      return if (! defined($excel_workbook));
                   "This error has been logged.  ".  
                   "Please alert your LON-CAPA administrator");  
         return ;  
     }  
     #  
     # The excel spreadsheet stores temporary data in files, then put them  
     # together.  If needed we should be able to disable this (memory only).  
     # The temporary directory must be specified before calling 'addworksheet'.  
     # File::Temp is used to determine the temporary directory.  
     $excel_workbook->set_tempdir($Apache::lonnet::tmpdir);  
     #      #
     # Add a worksheet      # Add a worksheet
     my $sheetname = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};      my $sheetname = $env{'course.'.$env{'request.course.id'}.'.description'};
     if (length($sheetname) > 31) {      $sheetname = &Apache::loncommon::clean_excel_name($sheetname);
         $sheetname = substr($sheetname,0,31);  
     }  
     $excel_sheet = $excel_workbook->addworksheet($sheetname);      $excel_sheet = $excel_workbook->addworksheet($sheetname);
     #      #
     # Put the course description in the header      # Put the course description in the header
     $excel_sheet->write($rows_output,$cols_output++,      $excel_sheet->write($header_row,$cols_output++,
                    $ENV{'course.'.$ENV{'request.course.id'}.'.description'});                     $env{'course.'.$env{'request.course.id'}.'.description'},
                           $format->{'h1'});
     $cols_output += 3;      $cols_output += 3;
     #      #
     # Put a description of the sections listed      # Put a description of the sections listed
     my $sectionstring = '';      my $sectionstring = '';
     my @Sections = @Apache::lonstatistics::SelectedSections;      my @Sections = &Apache::lonstatistics::get_selected_sections();
     if (scalar(@Sections) > 1) {      $excel_sheet->write($header_row,$cols_output++,
         if (scalar(@Sections) > 2) {                          &Apache::lonstatistics::section_and_enrollment_description('plaintext'),
             my $last = pop(@Sections);                          $format->{'h3'});
             $sectionstring = "Sections ".join(', ',@Sections).', and '.$last;  
         } else {  
             $sectionstring = "Sections ".join(' and ',@Sections);  
         }  
     } else {  
         if ($Sections[0] eq 'all') {  
             $sectionstring = "All sections";  
         } else {  
             $sectionstring = "Section ".$Sections[0];  
         }  
     }  
     $excel_sheet->write($rows_output,$cols_output++,$sectionstring);  
     $cols_output += scalar(@Sections);  
     #      #
     # Put the date in there too      # Put the date in there too
     $excel_sheet->write($rows_output,$cols_output++,      $excel_sheet->write($header_row,$cols_output++,
                         'Compiled on '.localtime(time));                          'Compiled on '.localtime(time),$format->{'h3'});
     #      #
     $rows_output++;      $cols_output = 0;
       $excel_sheet->write($description_row,$cols_output++,
                           $chosen_output->{'shortdesc'},
                           $format->{'b'});
       #
       $cols_output = 0;
       $excel_sheet->write($notes_row,$cols_output++,
                           $chosen_output->{'non_html_notes'},
                           $format->{'i'});
       
       ##############################################
       # Output headings for the raw data
       ##############################################
     #      #
     # Add the student headers      # Add the student headers
     $cols_output = 0;      $cols_output = 0;
     foreach my $field (&get_student_fields_to_show()) {      foreach my $field (&get_student_fields_to_show()) {
         $excel_sheet->write($rows_output,$cols_output++,$field);          $excel_sheet->write($resource_name_row,$cols_output++,$field,
                               $format->{'bold'});
     }      }
     #      #
     # Add the Sequence Headers      # Add the remaining column headers
     foreach my $seq (&get_sequences_to_show) {      my $total_formula_string = '=0';
         $excel_sheet->write($rows_output,$cols_output,$seq->{'title'});      my $maximum_formula_string = '=0';
         if ($show eq 'totals') {      foreach my $seq (@sequences) {
             $excel_sheet->write($rows_output+1,$cols_output,'score');          my $symb = $seq->symb;
             $excel_sheet->write($rows_output+1,$cols_output+1,'maximum');          $excel_sheet->write($sequence_name_row,,
             $cols_output += 2;                              $cols_output,$seq->compTitle,$format->{'bold'});
           # Determine starting cell
           $formula_data{$symb}->{'Excel:startcell'}=
               &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
               ($first_data_row,$cols_output);
           $formula_data{$symb}->{'Excel:startcol'}=$cols_output;
           my $count = 0;
           if ($chosen_output->{'every_problem'}) {
               # Put the names of the problems and parts into the sheet
               foreach my $res (&get_resources($navmap,$seq)) {
                   if (scalar(@{$res->parts}) > 1) {
                       foreach my $part (@{$res->parts}) {
                           $excel_sheet->write($resource_name_row,
                                               $cols_output++,
                                               $res->compTitle.' part '.$res->part_display($part),
                                               $format->{'bold'});
                           $count++;
                       }
                   } else {
                       $excel_sheet->write($resource_name_row,
                                           $cols_output++,
                                           $res->compTitle,$format->{'bold'});
                       $count++;
                   }
               }
           }
           # Determine ending cell
           if ($count <= 1) {
               $formula_data{$symb}->{'Excel:endcell'} = $formula_data{$symb}->{'Excel:startcell'};
               $formula_data{$symb}->{'Excel:endcol'}  = $formula_data{$symb}->{'Excel:startcol'};
         } else {          } else {
               $formula_data{$symb}->{'Excel:endcell'} = 
                   &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
                   ($first_data_row,$cols_output-1);
               $formula_data{$symb}->{'Excel:endcol'} = $cols_output-1;
           }
           # Create the formula for summing up this sequence
           if (! exists($formula_data{$symb}->{'Excel:endcell'}) ||
               ! defined($formula_data{$symb}->{'Excel:endcell'})) {
               $formula_data{$symb}->{'Excel:endcell'} = $formula_data{$symb}->{'Excel:startcell'};
           }
   
           my $start = $formula_data{$symb}->{'Excel:startcell'};
           my $end = $formula_data{$symb}->{'Excel:endcell'};
           $formula_data{$symb}->{'Excel:sum'}= $excel_sheet->store_formula
               ("=IF(COUNT($start\:$end),SUM($start\:$end),\"\")");
           # Determine cell the score is held in
           $formula_data{$symb}->{'Excel:scorecell'} = 
               &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
               ($first_data_row,$cols_output);
           $formula_data{$symb}->{'Excel:scorecol'}=$cols_output;
           if ($chosen_output->{'base'} eq 'parts correct total') {
               $excel_sheet->write($resource_name_row,$cols_output++,
                                   'parts correct',
                                   $format->{'bold'});
           } elsif ($chosen_output->{'sequence_sum'}) {
               if ($chosen_output->{'correct'}) {
                   # Only reporting the number correct, so do not call it score
                   $excel_sheet->write($resource_name_row,$cols_output++,
                                       'sum',
                                       $format->{'bold'});
               } else {
                   $excel_sheet->write($resource_name_row,$cols_output++,
                                       'score',
                                       $format->{'bold'});
               }
           }
           #
           $total_formula_string.='+'.
               &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
               ($first_data_row,$cols_output-1);
           if ($chosen_output->{'sequence_max'}) {
               $excel_sheet->write($resource_name_row,$cols_output,
                                   'maximum',
                                   $format->{'bold'});
               $formula_data{$symb}->{'Excel:maxcell'} = 
                   &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
                   ($first_data_row,$cols_output);
               $formula_data{$symb}->{'Excel:maxcol'}=$cols_output;
               $maximum_formula_string.='+'.
                   &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
                   ($first_data_row,$cols_output);
             $cols_output++;              $cols_output++;
   
         }          }
     }      }
     #      if ($chosen_output->{'grand_total'}) {
     # Bookkeeping          $excel_sheet->write($resource_name_row,$cols_output++,'Total',
     if ($show eq 'totals') {                              $format->{'bold'});
         $rows_output += 2;      }
     } else {      if ($chosen_output->{'grand_maximum'}) {
         $rows_output += 1;          $excel_sheet->write($resource_name_row,$cols_output++,'Max. Total',
                               $format->{'bold'});
       }
       $total_formula = $excel_sheet->store_formula($total_formula_string);
       $maximum_formula = $excel_sheet->store_formula($maximum_formula_string);
       ##############################################
       # Output a row for MAX, if appropriate
       ##############################################
       if ($chosen_output->{'maximum_row'}) {
           $cols_output = 0;
           foreach my $field (&get_student_fields_to_show()) {
               if ($field eq 'username' || $field eq 'fullname' || 
                   $field eq 'id') {
                   $excel_sheet->write($maximum_data_row,$cols_output++,'Maximum',
                                       $format->{'bold'});
               } else {
                   $excel_sheet->write($maximum_data_row,$cols_output++,'');
               }
           }
           #
           # Add the maximums for each sequence or assessment
           my %total_cell_translation;
           my %maximum_cell_translation;
           foreach my $seq (@sequences) {
               my $symb = $seq->symb;
               $cols_output=$formula_data{$symb}->{'Excel:startcol'};
               $total_cell_translation{$formula_data{$symb}->{'Excel:scorecell'}}=
                   &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
                   ($maximum_data_row,$formula_data{$symb}->{'Excel:scorecol'});
               $maximum_cell_translation{$formula_data{$symb}->{'Excel:maxcell'}}=
                   &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
                   ($maximum_data_row,$formula_data{$symb}->{'Excel:maxcol'});
               my $weight;
               my $max = 0;
               foreach my $resource (&get_resources($navmap,$seq)) {
                   foreach my $part (@{$resource->parts}){
                       $weight = 1;
                       if ($chosen_output->{'scores'}) {
                           $weight = &Apache::lonnet::EXT
                               ('resource.'.$part.'.weight',$resource->symb,
                                undef,undef,undef);
                           if (!defined($weight) || ($weight eq '')) { 
                               $weight=1;
                           }
                       }
                       if ($chosen_output->{'scores'} &&
                           $chosen_output->{'every_problem'}) {
                           $excel_sheet->write($maximum_data_row,$cols_output++,
                                               $weight);
                       }
                       $max += $weight;
                   }
               } 
               #
               if ($chosen_output->{'sequence_sum'} && 
                   $chosen_output->{'every_problem'}) {
    my %replaceCells=
       ('^'.$formula_data{$symb}->{'Excel:startcell'}.':'.
            $formula_data{$symb}->{'Excel:endcell'}.'$' =>
        &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($maximum_data_row,$formula_data{$symb}->{'Excel:startcol'}).':'.
        &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($maximum_data_row,$formula_data{$symb}->{'Excel:endcol'}));
                   $excel_sheet->repeat_formula($maximum_data_row,$cols_output++,
                                                $formula_data{$symb}->{'Excel:sum'},undef,
        %replaceCells, %replaceCells);
   
               } elsif ($chosen_output->{'sequence_sum'}) {
                   $excel_sheet->write($maximum_data_row,$cols_output++,$max);
               }
               if ($chosen_output->{'sequence_max'}) {
                   $excel_sheet->write($maximum_data_row,$cols_output++,$max);
               }
               #
           }
           if ($chosen_output->{'grand_total'}) {
               $excel_sheet->repeat_formula($maximum_data_row,$cols_output++,
                                            $total_formula,undef,
                                            %total_cell_translation);
           }
           if ($chosen_output->{'grand_maximum'}) {
               $excel_sheet->repeat_formula($maximum_data_row,$cols_output++,
                                            $maximum_formula,undef,
                                            %maximum_cell_translation);
           }
       } # End of MAXIMUM row output  if ($chosen_output->{'maximum_row'}) {
       $rows_output = $first_data_row;
       ##############################################
       # Output summary table, which actually is above the sequence name row.
       ##############################################
       if ($chosen_output->{'summary_table'}) {
           $cols_output = 0;
           $excel_sheet->write($summary_header_row,$cols_output++,
                               'Summary Table',$format->{'bold'});
           if ($chosen_output->{'maximum_row'}) {
               $excel_sheet->write($summary_header_row,$cols_output++,
                                   'Maximum',$format->{'bold'});
           }
           $excel_sheet->write($summary_header_row,$cols_output++,
                               'Average',$format->{'bold'});
           $excel_sheet->write($summary_header_row,$cols_output++,
                               'Median',$format->{'bold'});
           $excel_sheet->write($summary_header_row,$cols_output++,
                               'Std Dev',$format->{'bold'});
           my $row = $summary_header_row+1;
           foreach my $seq (@sequences) {
               my $symb = $seq->symb;
               $cols_output = 0;
               $excel_sheet->write($row,$cols_output++,
                                   $seq->compTitle,
                                   $format->{'bold'});
               if ($chosen_output->{'maximum_row'}) {
                   $excel_sheet->write
                       ($row,$cols_output++,
                        '='.
                        &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
                        ($maximum_data_row,$formula_data{$symb}->{'Excel:scorecol'})
                        );
               }
               my $range = 
                   &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
                   ($first_data_row,$formula_data{$symb}->{'Excel:scorecol'}).
                   ':'.
                   &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
                   ($first_data_row+$num_students-1,$formula_data{$symb}->{'Excel:scorecol'});
               $excel_sheet->write($row,$cols_output++,
                                   '=AVERAGE('.$range.')');
               $excel_sheet->write($row,$cols_output++,
                                   '=MEDIAN('.$range.')');
               $excel_sheet->write($row,$cols_output++,
                                   '=STDEV('.$range.')');
               $row++;
           }
     }      }
       ##############################################
       #   Take care of non-excel initialization
       ##############################################
     #      #
     # Let the user know what we are doing      # Let the user know what we are doing
     my $studentcount = scalar(@Apache::lonstatistics::Students);       my $studentcount = scalar(@Apache::lonstatistics::Students); 
     $r->print("<h1>Compiling Excel spreadsheet for ".      if ($env{'form.SelectedStudent'}) {
               $studentcount.' student');          $studentcount = '1';
     $r->print('s') if ($studentcount > 1);      }
     $r->print("</h1>\n");      if ($studentcount > 1) {
           $r->print('<h1>'.&mt('Compiling Excel spreadsheet for [_1] students',
                                $studentcount)."</h1>\n");
       } else {
           $r->print('<h1>'.
                     &mt('Compiling Excel spreadsheet for 1 student').
                     "</h1>\n");
       }
     $r->rflush();      $r->rflush();
     #      #
     # Initialize progress window      # Initialize progress window
     %prog_state=&Apache::lonhtmlcommon::Create_PrgWin      %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
         ($r,'Excel File Compilation Status',          ($r,'Excel File Compilation Status',
          'Excel File Compilation Progress', $studentcount);           'Excel File Compilation Progress', $studentcount,
            'inline',undef,'Statistics','stats_status');
     #      #
       &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
                                             'Processing first student');
     return;      return;
 }  }
   
 sub excel_outputstudent {  sub excel_outputstudent {
     my ($r,$student) = @_;      my ($r,$student) = @_;
     return if (! defined($excel_sheet));      if ($request_aborted || ! defined($navmap) || ! defined($excel_sheet)) {
           return;
       }
     $cols_output=0;      $cols_output=0;
     #      #
     # Write out student data      # Write out student data
     my @to_show = &get_student_fields_to_show();      my @to_show = &get_student_fields_to_show();
     foreach my $field (@to_show) {      foreach my $field (@to_show) {
         $excel_sheet->write($rows_output,$cols_output++,$student->{$field});          my $value = $student->{$field};
           if ($field eq 'comments') {
               $value = &Apache::lonmsgdisplay::retrieve_instructor_comments
                   ($student->{'username'},$student->{'domain'});
           }
           $excel_sheet->write($rows_output,$cols_output++,$value);
     }      }
     #      #
     # Get student assessment data      # Get student assessment data
Line 784  sub excel_outputstudent { Line 1453  sub excel_outputstudent {
     my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},      my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},
                                                         $student->{'domain'},                                                          $student->{'domain'},
                                                         undef,                                                          undef,
                                                    $ENV{'request.course.id'});                                                     $env{'request.course.id'});
     if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {      if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
         %StudentsData = @tmp;          %StudentsData = @tmp;
     }      }
     #      #
     # Write out sequence scores and totals data      # Write out sequence scores and totals data
     foreach my $seq (&get_sequences_to_show) {      my %total_cell_translation;
         my ($performance,$score,$seq_max) =      my %maximum_cell_translation;
             &StudentPerformanceOnSequence($student,\%StudentsData,      foreach my $seq (@sequences) {
                                           $seq,'no');          my $symb = $seq->symb;
         if ($show eq 'totals' || $show eq 'scores') {          $cols_output = $formula_data{$symb}->{'Excel:startcol'};
             $excel_sheet->write($rows_output,$cols_output++,$score);          # Keep track of cells to translate in total cell
           $total_cell_translation{$formula_data{$symb}->{'Excel:scorecell'}} = 
               &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
                           ($rows_output,$formula_data{$symb}->{'Excel:scorecol'});
           # and maximum cell
           $maximum_cell_translation{$formula_data{$symb}->{'Excel:maxcell'}} = 
               &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell
               ($rows_output,$formula_data{$symb}->{'Excel:maxcol'});
           #
           my ($performance,$performance_length,$score,$seq_max,$rawdata);
           if ($chosen_output->{'tries'} || $chosen_output->{'correct'}){
               ($performance,$performance_length,$score,$seq_max,$rawdata) =
                   &student_tries_on_sequence($student,\%StudentsData,
                                              $navmap,$seq,'no');
           } else {
               ($performance,$performance_length,$score,$seq_max,$rawdata) =
                   &student_performance_on_sequence($student,\%StudentsData,
                                                    $navmap,$seq,'no',
                                                    $chosen_output->{ignore_weight});
           } 
           if ($chosen_output->{'every_problem'}) {
               if ($chosen_output->{'correct'}) {
                   # only indiciate if each item is correct or not
                   foreach my $value (@$rawdata) {
                       # positive means correct, 0 or negative means
                       # incorrect
                       $value = $value > 0 ? 1 : 0;
                       $excel_sheet->write($rows_output,$cols_output++,$value);
                   }
               } else {
                   foreach my $value (@$rawdata) {
                       if ($score eq ' ' || !defined($value)) {
                           $cols_output++;
                       } else {                        
                           $excel_sheet->write($rows_output,$cols_output++,
                                               $value);
                       }
                   }
               }
           }
           if ($chosen_output->{'sequence_sum'} && 
               $chosen_output->{'every_problem'}) {
               # Write a formula for the sum of this sequence
               my %replaceCells=
    ('^'.$formula_data{$symb}->{'Excel:startcell'}.':'.$formula_data{$symb}->{'Excel:endcell'}.'$'
    => 
    &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($rows_output,$formula_data{$symb}->{'Excel:startcol'}).':'.
    &Spreadsheet::WriteExcel::Utility::xl_rowcol_to_cell($rows_output,$formula_data{$symb}->{'Excel:endcol'})
    );
               # The undef is for the format    
       $excel_sheet->repeat_formula($rows_output,$cols_output++,
    $formula_data{$symb}->{'Excel:sum'},undef,
    %replaceCells, %replaceCells);
           } elsif ($chosen_output->{'sequence_sum'}) {
               if ($score eq ' ') {
                   $cols_output++;
               } else {
                   $excel_sheet->write($rows_output,$cols_output++,$score);
               }
         }          }
         if ($show eq 'totals') {          if ($chosen_output->{'sequence_max'}) {
             $excel_sheet->write($rows_output,$cols_output++,$seq_max);              $excel_sheet->write($rows_output,$cols_output++,$seq_max);
         }          }
     }      }
     #      #
       if ($chosen_output->{'grand_total'}) {
           $excel_sheet->repeat_formula($rows_output,$cols_output++,
                                        $total_formula,undef,
                                        %total_cell_translation);
       }
       if ($chosen_output->{'grand_maximum'}) {
           $excel_sheet->repeat_formula($rows_output,$cols_output++,
                                        $maximum_formula,undef,
                                        %maximum_cell_translation);
       }
       #
     # Bookkeeping      # Bookkeeping
     $rows_output++;       $rows_output++; 
     $cols_output=0;      $cols_output=0;
Line 813  sub excel_outputstudent { Line 1551  sub excel_outputstudent {
   
 sub excel_finish {  sub excel_finish {
     my ($r) = @_;      my ($r) = @_;
     return if (! defined($excel_sheet));      if ($request_aborted || ! defined($navmap) || ! defined($excel_sheet)) {
    &excel_cleanup();
           return;
       }
     #      #
     # Write the excel file      # Write the excel file
     $excel_workbook->close();      $excel_workbook->close();
     my $c = $r->connection();  
     #  
     return if($c->aborted());  
     #      #
     # Close the progress window      # Close the progress window
     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);      &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
Line 828  sub excel_finish { Line 1566  sub excel_finish {
     $r->print('<br />'.      $r->print('<br />'.
               '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");                '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
     $r->rflush();      $r->rflush();
       &excel_cleanup();
     return;      return;
 }  }
   
Line 851  sub excel_finish { Line 1590  sub excel_finish {
 #######################################################  #######################################################
 {  {
   
   my $outputfile;
   my $filename;
   my $request_aborted;
   my %prog_state; # progress window state
   my $navmap;
   my @sequences;
   
   sub csv_cleanup {
       undef($outputfile);
       undef($filename);
       undef($request_aborted);
       undef(%prog_state);
       #
       undef($navmap);
       undef(@sequences);
   }
   
 sub csv_initialize{  sub csv_initialize{
     my ($r) = @_;      my ($r) = @_;
     $r->print("<h1>Not implemented yet</h1>");  
       &csv_cleanup();
       ($navmap,@sequences) = 
           &Apache::lonstatistics::selected_sequences_with_assessments();
       if (! ref($navmap)) {
           # Unable to get data, so bail out
           $r->print("<h3>".
                     &mt('Unable to retrieve course information.').
                     '</h3>');
       }
       #
       # Deal with unimplemented requests
       $request_aborted = undef;
       if ($chosen_output->{'base'} =~ /final table/) {
           $r->print(<<END);
   <h2>Unable to Complete Request</h2>
   <p>
   The <b>Summary Table (Scores)</b> option is not available for non-HTML output.
   </p>
   END
          $request_aborted = 1;
       }
       return if ($request_aborted);
       #
       # Initialize progress window
       my $studentcount = scalar(@Apache::lonstatistics::Students);
       %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
           ($r,'CSV File Compilation Status',
            'CSV File Compilation Progress', $studentcount,
            'inline',undef,'Statistics','stats_status');
       #
       # Open a file
       ($outputfile,$filename) = &Apache::loncommon::create_text_file($r,'csv');
       if (! defined($outputfile)) { return ''; }
       #
       # Datestamp
       my $description = $env{'course.'.$env{'request.course.id'}.'.description'};
       print $outputfile '"'.&Apache::loncommon::csv_translate($description).'",'.
           '"'.&Apache::loncommon::csv_translate(scalar(localtime(time))).'"'.
               "\n";
       print $outputfile '"'.
           &Apache::loncommon::csv_translate
           (&Apache::lonstatistics::section_and_enrollment_description()).
           '"'."\n";
       foreach my $item ('shortdesc','non_html_notes') {
           next if (! exists($chosen_output->{$item}));
           print $outputfile 
               '"'.&Apache::loncommon::csv_translate($chosen_output->{$item}).'"'.
               "\n";
       }
       #
       # Print out the headings
       my $sequence_row = '';
       my $resource_row = undef;
       foreach my $field (&get_student_fields_to_show()) {
           $sequence_row .='"",';
           $resource_row .= '"'.&Apache::loncommon::csv_translate($field).'",';
       }
       foreach my $seq (@sequences) {
           $sequence_row .= '"'.
               &Apache::loncommon::csv_translate($seq->compTitle).'",';
           my $count = 0;
           if ($chosen_output->{'every_problem'}) {
               foreach my $res (&get_resources($navmap,$seq)) {
                   if (scalar(@{$res->parts}) < 1) {
                       next;
                   }
                   foreach my $part (@{$res->parts}) {
                       $resource_row .= '"'.
                           &Apache::loncommon::csv_translate
                           ($res->compTitle.', Part '.$res->part_display($part)).'",';
                       $count++;
                   }
               }
           }
           $sequence_row.='"",'x$count;
           if ($chosen_output->{'sequence_sum'}) {
               if($chosen_output->{'correct'}) {
                   $resource_row .= '"sum",';
               } else {
                   $resource_row .= '"score",';
               }
           }
           if ($chosen_output->{'sequence_max'}) {
               $sequence_row.= '"",';
               $resource_row .= '"maximum possible",';
           }
       }
       if ($chosen_output->{'grand_total'}) {
           $sequence_row.= '"",';
           $resource_row.= '"Total",';
       } 
       if ($chosen_output->{'grand_maximum'}) {
           $sequence_row.= '"",';
           $resource_row.= '"Maximum",';
       } 
       chomp($sequence_row);
       chomp($resource_row);
       print $outputfile $sequence_row."\n";
       print $outputfile $resource_row."\n";
     return;      return;
 }  }
   
 sub csv_outputstudent {  sub csv_outputstudent {
     my ($r,$student) = @_;      my ($r,$student) = @_;
       if ($request_aborted || ! defined($navmap) || ! defined($outputfile)) {
           return;
       }
       my $Str = '';
       #
       # Output student fields
       my @to_show = &get_student_fields_to_show();
       foreach my $field (@to_show) {
           my $value = $student->{$field};
           if ($field eq 'comments') {
               $value = &Apache::lonmsgdisplay::retrieve_instructor_comments
                   ($student->{'username'},$student->{'domain'});
           }        
           $Str .= '"'.&Apache::loncommon::csv_translate($value).'",';
       }
       #
       # Get student assessment data
       my %StudentsData;
       my @tmp = &Apache::loncoursedata::get_current_state($student->{'username'},
                                                           $student->{'domain'},
                                                           undef,
                                                      $env{'request.course.id'});
       if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
           %StudentsData = @tmp;
       }
       #
       # Output performance data
       my $total = 0;
       my $maximum = 0;
       foreach my $seq (@sequences) {
           my ($performance,$performance_length,$score,$seq_max,$rawdata);
           if ($chosen_output->{'tries'}){
               ($performance,$performance_length,$score,$seq_max,$rawdata) =
                   &student_tries_on_sequence($student,\%StudentsData,
                                              $navmap,$seq,'no');
           } else {
               ($performance,$performance_length,$score,$seq_max,$rawdata) =
                   &student_performance_on_sequence($student,\%StudentsData,
                                                    $navmap,$seq,'no',
                                                    $chosen_output->{ignore_weight});
           }
           if ($chosen_output->{'every_problem'}) {
               if ($chosen_output->{'correct'}) {
                   $score = 0;
                   # Deal with number of parts correct data
                   $Str .= '"'.join('","',( map { if ($_>0) { 
                                                      $score += 1;
                                                      1; 
                                                  } else { 
                                                      0; 
                                                  }
                                                } @$rawdata)).'",';
               } else {
                   $Str .= '"'.join('","',(@$rawdata)).'",';
               }
           }
           if ($chosen_output->{'sequence_sum'}) {
               $Str .= '"'.$score.'",';
           } 
           if ($chosen_output->{'sequence_max'}) {
               $Str .= '"'.$seq_max.'",';
           }
           $total+=$score;
           $maximum += $seq_max;
       }
       if ($chosen_output->{'grand_total'}) {
           $Str .= '"'.$total.'",';
       }
       if ($chosen_output->{'grand_maximum'}) {
           $Str .= '"'.$maximum.'",';
       }
       chop($Str);
       $Str .= "\n";
       print $outputfile $Str;
       #
       # Update the progress window
       &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
       return;
 }  }
   
 sub csv_finish {  sub csv_finish {
     my ($r) = @_;      my ($r) = @_;
       if ($request_aborted || ! defined($navmap) || ! defined($outputfile)) {
    &csv_cleanup();
           return;
       }
       close($outputfile);
       #
       # Close the progress window
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
       #
       # Tell the user where to get their csv file
       $r->print('<br />'.
                 '<a href="'.$filename.'">'.&mt('Your csv file.').'</a>'."\n");
       $r->rflush();
       &csv_cleanup();
       return;
       
   }
   
 }  }
   
   # This function will return an HTML string including a star, with
   # a mouseover popup showing the "real" value. An optional second
   # argument lets you show something other than a star.
   sub show_star {
       my $popup = shift;
       my $symbol = shift || '*';
       # Escape the popup for JS.
       $popup =~ s/([^-a-zA-Z0-9:;,._ ()|!\/?=&*])/'\\' . sprintf("%lo", ord($1))/ge;
       
       return "<span onmouseover='popup_score(this, \"$popup\");return false;' onmouseout='popdown_score();return false;' style='border: 1px solid #339933; margin: -1px;'>$symbol</span>";
 }  }
   
 #######################################################  #######################################################
Line 872  sub csv_finish { Line 1833  sub csv_finish {
   
 =pod  =pod
   
 =item &StudentPerformanceOnSequence()  =item &StudentTriesOnSequence()
   
 Inputs:  Inputs:
   
Line 892  Inputs: Line 1853  Inputs:
   
 #######################################################  #######################################################
 #######################################################  #######################################################
 sub StudentPerformanceOnSequence {  sub student_tries_on_sequence {
     my ($student,$studentdata,$seq,$links) = @_;      my ($student,$studentdata,$navmap,$seq,$links) = @_;
     $links = 'no' if (! defined($links));      $links = 'no' if (! defined($links));
     my $Str = '';      my $Str = '';
     my ($sum,$max) = (0,0);      my ($sum,$max) = (0,0);
     foreach my $resource (@{$seq->{'contents'}}) {      my $performance_length = 0;
         next if ($resource->{'type'} ne 'assessment');      my @TriesData = ();
         my $resource_data = $studentdata->{$resource->{'symb'}};      my $tries;
       my $hasdata = 0; # flag - true if the student has any data on the sequence
       foreach my $resource (&get_resources($navmap,$seq)) {
           my $resource_data = $studentdata->{$resource->symb};
         my $value = '';          my $value = '';
         foreach my $partnum (@{$resource->{'parts'}}) {          foreach my $partnum (@{$resource->parts()}) {
               $tries = undef;
             $max++;              $max++;
               $performance_length++;
             my $symbol = ' '; # default to space              my $symbol = ' '; # default to space
             #              #
               my $awarded = 0;
               if (exists($resource_data->{'resource.'.$partnum.'.awarded'})) {
                   $awarded = $resource_data->{'resource.'.$partnum.'.awarded'};
                   $awarded = 0 if (! $awarded);
               }
               #
               my $status = '';
             if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {              if (exists($resource_data->{'resource.'.$partnum.'.solved'})) {
                 my $status = $resource_data->{'resource.'.$partnum.'.solved'};                  $status = $resource_data->{'resource.'.$partnum.'.solved'};
                 if ($status eq 'correct_by_override') {              }
                     $symbol = '+';              #
                     $sum++;              my $tries = 0;
                 } elsif ($status eq 'incorrect_by_override') {              if(exists($resource_data->{'resource.'.$partnum.'.tries'})) {
                     $symbol = '-';                  $tries = $resource_data->{'resource.'.$partnum.'.tries'};
                 } elsif ($status eq 'ungraded_attempted') {                  $hasdata =1;
                     $symbol = '#';              }
                 } elsif ($status eq 'incorrect_attempted')  {              #
                     $symbol = '.';              if ($awarded > 0) {
                 } elsif ($status eq 'excused') {                  # The student has gotten the problem correct to some degree
                   if ($status eq 'excused') {
                     $symbol = 'x';                      $symbol = 'x';
                     $max--;                      $max--;
                 } elsif ($status eq 'correct_by_student' &&                  } elsif ($status eq 'correct_by_override') {
                     exists($resource_data->{'resource.'.$partnum.'.tries'})){                      $symbol = '+';
                     my $num = $resource_data->{'resource.'.$partnum.'.tries'};                      $sum++;
                     if ($num > 9) {                  } elsif ($tries > 0) {
                         $symbol = '*';                      if ($tries > 9) {
                     } elsif ($num > 0) {                          $symbol = show_star($tries);
                         $symbol = $num;  
                     } else {                      } else {
                         $symbol = ' ';                          $symbol = $tries;
                     }                      }
                     $sum++;                      $sum++;
                 } else {                  } else {
                     $symbol = ' ';                      $symbol = '+';
                       $sum++;
                 }                  }
             } else {              } else {
                 # Unsolved.  Did they try?                  # The student has the problem incorrect or it is ungraded
                 if (exists($resource_data->{'resource.'.$partnum.'.tries'})){                  if ($status eq 'excused') {
                       $symbol = 'x';
                       $max--;
                   } elsif ($status eq 'incorrect_by_override') {
                       $symbol = '-';
                   } elsif ($status eq 'ungraded_attempted') {
                       $symbol = 'u';
                   } elsif ($status eq 'incorrect_attempted' ||
                            $tries > 0)  {
                     $symbol = '.';                      $symbol = '.';
                 } else {                  } else {
                     $symbol = ' ';                      # Problem is wrong and has not been attempted.
                       $symbol=' ';
                 }                  }
             }              }
             #              #
             if ($links eq 'yes' && $symbol ne ' ') {              if (! defined($tries)) {
                 $symbol = '<a href="/adm/grades'.                  $tries = 0;
                     '?symb='.&Apache::lonnet::escape($resource->{'symb'}).              }
               if ($status =~ /^(incorrect|ungraded)/) {
                   # Bug 3390: show '-' for tries on incorrect problems 
                   # (csv & excel only)
                   push(@TriesData,-$tries);
               } else {
                   push (@TriesData,$tries);
               }
               #
               if ( ($links eq 'yes' && $symbol ne ' ') ||
                    ($links eq 'all')) {
                   if (length($symbol) > 1) {
                       &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1');
                   }
                   my $link = '/adm/grades'.
                       '?symb='.&Apache::lonnet::escape($resource->symb).
                         '&student='.$student->{'username'}.                          '&student='.$student->{'username'}.
                             '&domain='.$student->{'domain'}.                              '&userdom='.$student->{'domain'}.
                                 '&command=submission">'.$symbol.'</a>';                                  '&command=submission';
                   $symbol = &link($symbol, $link);
             }              }
             $value .= $symbol;              $value .= $symbol;
         }          }
         $Str .= $value;          $Str .= $value;
     }      }
     return ($Str,$sum,$max);      if ($seq->randompick()) {
           $max = $seq->randompick();
       }
       if (! $hasdata && $sum == 0) {
           $sum = ' ';
       }
       return ($Str,$performance_length,$sum,$max,\@TriesData);
   }
   
   =pod
   
   =item &link
   
   Inputs:
   
   =over 4
   
   =item $text
   
   =item $target
   
   =back
   
   Takes the text and creates a link to the $text that honors
   the value of 'new window' if clicked on, but uses a real 
   'href' so middle and right clicks still work.
   
   $target and $text are assumed to be already correctly escaped; i.e., it
   can be dumped out directly into the output stream as-is.
   
   =cut
   
   sub link {
       my ($text,$target) = @_;
       return 
           "<a href='$target' onclick=\"t=this.href;if(new_window)"
           ."{window.open(t)}else{return void(window."
           ."location=t)};return false;\">$text</a>";
 }  }
   
 #######################################################  #######################################################
 #######################################################  #######################################################
 sub StudentAverageTotal {  
     my ($cache, $students, $sequenceKeys)=@_;  
     my $Str = "\n<b>Summary Tables:</b>\n";  
     my %Correct = ();  
     my $ProblemsSolved = 0;  
     my $TotalProblems = 0;  
     my $StudentCount = 0;  
   
     foreach my $name (@$students) {  
         $StudentCount++;  
         foreach my $sequence (@$sequenceKeys) {  
             $Correct{$sequence} +=  
        $cache->{$name.':'.$sequence.':problemsCorrect'};  
         }  
  $ProblemsSolved += $cache->{$name.':problemsSolved'};  
         $TotalProblems += $cache->{$name.':totalProblems'};  
     }  
     if ($StudentCount) {   
         $ProblemsSolved = sprintf( "%.2f",   
                              $ProblemsSolved/$StudentCount);  
         $TotalProblems /= $StudentCount;  
     } else {  
         $ProblemsSolved = 0;  
         $TotalProblems  = 0;  
     }  
   
     $Str .= '<table border=2 cellspacing="1">'."\n";  =pod
     $Str .= '<tr><td><b>Students Count</b></td><td><b>'.  
             $StudentCount.'</b></td></tr>'."\n";  
     $Str .= '<tr><td><b>Total Problems</b></td><td><b>'.  
             $TotalProblems.'</b></td></tr>'."\n";  
     $Str .= '<tr><td><b>Average Correct</b></td><td><b>'.  
             $ProblemsSolved.'</b></td></tr>'."\n";  
     $Str .= '</table>'."\n";  
   
     $Str .= '<table border=2 cellspacing="1">'."\n";  =item &student_performance_on_sequence
     $Str .= '<tr><th>Title</th><th>Total Problems</th>'.  
             '<th>Average Correct</th></tr>'."\n";  
     foreach my $S(@$sequenceKeys) {  
         my $title=$cache->{$S.':title'};  
  #$Str .= $cache->{$S.':problems'};  
  #my @problems=split(':', $cache->{$S.':problems'});  
  #my $pCount=scalar @problems;  
  my $pCount=MaxSeqPr($cache,@$students[0],$S);  
         my $crr;  
  if ($StudentCount) {  
             $crr=sprintf( "%.2f", $Correct{$S}/$StudentCount );  
         } else {  
             $crr="0.00";  
         }  
         $Str .= '<tr><td>'.$title.  
                 '</td><td align=center>'.$pCount.  
                 '</td><td align=center>'.$crr.  
                 '</td></tr>'."\n";  
     }  
   
     $Str .= '</table>'."\n";  Inputs:
   
     return $Str;  =over 4
   
   =item $student
   
   =item $studentdata Hash ref to all student data
   
   =item $seq Hash ref, the sequence we are working on
   
   =item $links if defined we will output links to each resource.
   
   =back
   
   =cut
   
   #######################################################
   #######################################################
   sub student_performance_on_sequence {
       my ($student,$studentdata,$navmap,$seq,$links,$awarded_only) = @_;
       $links = 'no' if (! defined($links));
       my $Str = ''; # final result string
       my ($score,$max) = (0,0);
       my $performance_length = 0;
       my $symbol;
       my @ScoreData = ();
       my $partscore;
       my $hasdata = 0; # flag, 0 if there were no submissions on the sequence
       foreach my $resource (&get_resources($navmap,$seq)) {
           my $symb = $resource->symb;
           my $resource_data = $studentdata->{$symb};
           foreach my $part (@{$resource->parts()}) {
               $partscore = undef;
               my $weight;
               if (!$awarded_only){
                   $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
                                                  $symb,
                                                  $student->{'domain'},
                                                  $student->{'username'},
                                                  $student->{'section'});
               }
               if (!defined($weight) || ($weight eq '')) { 
                   $weight=1;
               }
               #
               $max += $weight; # see the 'excused' branch below...
               $performance_length++; # one character per part
               $symbol = ' '; # default to space
               #
               my $awarded;
               if (exists($resource_data->{'resource.'.$part.'.awarded'})) {
                   $awarded = $resource_data->{'resource.'.$part.'.awarded'};
                   $awarded = 0 if (! $awarded);
                   $hasdata = 1;
               }
               #
               $partscore = &Apache::grades::compute_points($weight,$awarded);
               if (! defined($awarded)) {
                   $partscore = undef;
               }
               $score += $partscore;
               $symbol = $partscore; 
               if (abs($symbol - sprintf("%.0f",$symbol)) < 0.001) {
                   $symbol = sprintf("%.0f",$symbol);
               }
               if (length($symbol) > 1) {
                   $symbol = show_star($symbol);
               }
               if (exists($resource_data->{'resource.'.$part.'.solved'}) &&
                   $resource_data->{'resource.'.$part.'.solved'} ne '') {
                   my $status = $resource_data->{'resource.'.$part.'.solved'};
                   if ($status eq 'excused') {
                       $symbol = 'x';
                       $max -= $weight; # Do not count 'excused' problems.
                   } elsif ($status eq 'ungraded_attempted') {
                       $symbol = 'u';
                   }
                   $hasdata = 1;
               } elsif ($resource_data->{'resource.'.$part.'.award'} eq 'DRAFT') {
                   $symbol = 'd';
                   $hasdata = 1;
               } elsif (!exists($resource_data->{'resource.'.$part.'.awarded'})){
                   # Unsolved.  Did they try?
                   if (exists($resource_data->{'resource.'.$part.'.tries'})){
                       $symbol = '.';
                       $hasdata = 1;
                   } else {
                       $symbol = ' ';
                   }
               }
               #
               if (! defined($partscore)) {
                   $partscore = $symbol;
               }
               push (@ScoreData,$partscore);
               #
               if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) {
                   my $link = '/adm/grades' .
                       '?symb='.&Apache::lonnet::escape($symb).
                       '&student='.$student->{'username'}.
                       '&userdom='.$student->{'domain'}.
                       '&command=submission';
                   $symbol = &link($symbol, $link);
               }
               $Str .= $symbol;
           }
       }
       if (! $hasdata && $score == 0) {
           $score = ' ';
       }
       return ($Str,$performance_length,$score,$max,\@ScoreData);
 }  }
   
 #######################################################  #######################################################
Line 1036  problems. Line 2129  problems.
 #######################################################  #######################################################
 sub CreateLegend {  sub CreateLegend {
     my $Str = "<p><pre>".      my $Str = "<p><pre>".
               "   1  correct by student in 1 try\n".                " digit score or number of tries to get correct ".
               "   7  correct by student in 7 tries\n".  
               "   *  correct by student in more than 9 tries\n".                "   *  correct by student in more than 9 tries\n".
       "   +  correct by hand grading or override\n".        "   +  correct by hand grading or override\n".
               "   -  incorrect by override\n".                "   -  incorrect by override\n".
       "   .  incorrect attempted\n".        "   .  incorrect attempted\n".
       "   #  ungraded attempted\n".        "   u  ungraded attempted\n".
                 "   d  draft answer saved but not submitted\n".
               "      not attempted (blank field)\n".                "      not attempted (blank field)\n".
       "   x  excused".        "   x  excused".
               "</pre><p>";                "</pre><p>";

Removed from v.1.36  
changed lines
  Added in v.1.136


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