Diff for /loncom/interface/statistics/lonstudentassessment.pm between versions 1.43 and 1.155

version 1.43, 2003/03/26 21:42:20 version 1.155, 2009/05/17 23:13:49
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();
   use lib '/home/httpd/lib/perl/';
   use LONCAPA;
    
   
 #######################################################  #######################################################
 #######################################################  #######################################################
Line 85  my $Statistics; Line 94  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 139  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',
                                  'Groups' => '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 191  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 199  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 237  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="'.
               &mt('Previous Student ([_1])',
               $previous->{'username'}.':'.$previous->{'domain'}).
               '" onclick="document.Statistics.SelectedStudent.value='.
               "'".$sname."'".';'.
               'document.Statistics.submit();" />';
       } else {
           $previousbutton .= '<input type="button" value="'.
               &mt('Previous Student').'" disabled="disabled" />';
       }
       #
       # 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="'.
               &mt('Next Student ([_1])',
               $next->{'username'}.':'.$next->{'domain'}).
               '" onclick="document.Statistics.SelectedStudent.value='.
               "'".$sname."'".';'.
               'document.Statistics.submit();" />';
       } else {
           $nextbutton .= '<input type="button" value="'.
               &mt('Next Student').'" disabled="disabled" />';
       }
       #
       # Build the 'all students' button
       my $all = '';
       $all .= '<input type="button" value="'.&mt('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 219  the chart page. Line 318  the chart page.
 #######################################################  #######################################################
 sub CreateInterface {  sub CreateInterface {
     my $Str = '';      my $Str = '';
       $Str .= &Apache::lonhtmlcommon::breadcrumbs('Chart','Chart_Description:Chart_Sections:Chart_Student_Data:Chart_Enrollment_Status:Chart_Sequences:Chart_Output_Formats:Chart_Output_Data');
 #    $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>'.
     $Str .= '<td align="center"><b>Student Data</b></td>';   &Apache::loncommon::help_open_topic("Chart_Sections").
     $Str .= '<td align="center"><b>Sequences and Folders</b></td>';   '</td>';
     $Str .= '<td align="center"><b>Output Format</b></td>';      $Str .= '<td align="center"><b>'.&mt('Groups').'</b>'.
    '</td>';
       $Str .= '<td align="center"><b>'.&mt('Student Data</b>').
    &Apache::loncommon::help_open_topic("Chart_Student_Data").
    '</td>';
       $Str .= '<td align="center"><b>'.&mt('Access Status').'</b>'.
    &Apache::loncommon::help_open_topic("Chart_Enrollment_Status").
    '</td>';
       $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b>'.
    &Apache::loncommon::help_open_topic("Chart_Sequences").
    '</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 {       $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',5);
         my $s=shift;      $Str .= '</td><td align="center">';
         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 .= '<p>';
       $Str .= 
           &mt('Status [_1]',
               '<input type="text" name="stats_status" size="60" value="" readonly="readonly" />');
       $Str .= '</p>';
     return $Str;      return $Str;
 }  }
   
Line 268  my @OutputOptions = Line 394  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',  
            },  
      { name  => 'HTML, totals',  
        value => 'html, totals',  
        description => 'Output HTML, but only the summary statistics for each'.  
    ' sequence selected for each student.',  
        mode => 'html',  
        show => 'totals',  
        show_links => 'no',  
        },  
      { name  => 'HTML, summary table only',  
        value => 'html summary table only',  
        description => 'Output HTML, but only the final summary table for '.  
            'all students across all sequences.',  
        mode => 'html',  
        show => 'final table',  
        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',  
        show => 'scores',  
        show_links => 'no',         show_links => 'no',
            },             },
      { name  => 'Excel, totals',       { name  => 'Excel',
        value => 'excel, totals',         value => 'excel',
        description => 'Output an Excel file (compatable with Excel 95), '.         description => 'Output an Excel file (compatable with Excel 95).',
    'with two columns for each sequence, the students score on the '.  
    'sequence and the students maximum possible on the sequence',  
        mode => 'excel',         mode => 'excel',
        show => 'totals',  
        show_links => 'no',         show_links => 'no',
            },     },
      { name  => 'multi-sheet Excel',       { name  => 'CSV',
        value => 'multi-sheet excel',         value => 'csv',
        description => 'Output an Excel file (compatable with Excel 95), '.         description => 'Output a comma separated values file suitable for '.
    'with a seperate worksheet for each sequence you have selected '.             'import into a spreadsheet program.  Using this method as opposed '.
            'the data for each problem part '.             'to Excel output allows you to organize your data before importing'.
            '(number of tries, status, points awarded) will be listed.',             ' it into a spreadsheet program.',
        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 381  sub OutputDescriptions { Line 443  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 410  sub CreateAndParseOutputSelector { Line 467  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>";
       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>";      $Str .= "\n</select>";
     return $Str;      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 443  Return a line of the chart for a student Line 638  Return a line of the chart for a student
   
     my $nodata_count; # The number of students for which there is no data      my $nodata_count; # The number of students for which there is no data
     my %prog_state;   # progress state used by loncommon PrgWin routines      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) = @_;
Line 450  sub html_initialize { Line 658  sub html_initialize {
     $padding = ' 'x3;      $padding = ' 'x3;
     $count = 0;      $count = 0;
     $nodata_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') {
           my $labeltext = &mt('Show links in new window:');
           $r->print(<<NEW_WINDOW_CHECKBOX);
   <script type="text/javascript">new_window = true;</script>
   <p><label>$labeltext 
   <input type="checkbox" checked="checked" onclick="new_window=this.checked" />
   </label></p>
   NEW_WINDOW_CHECKBOX
       }
   
     #      #
     $r->print("<h3>".$ENV{'course.'.$ENV{'request.course.id'}.'.description'}.      $r->print("<h3>".$env{'course.'.$env{'request.course.id'}.'.description'}.
               "&nbsp;&nbsp;".localtime(time)."</h3>");                "&nbsp;&nbsp;".localtime(time)."</h3>");
   
     #      #
     # Set up progress window for 'final table' display only      if ($chosen_output->{'base'} !~ /^final table/) {
     if ($show eq 'final table') {          $r->print("<h3>".&mt($chosen_output->{'shortdesc'})."</h3>");        
         my $studentcount = scalar(@Apache::lonstatistics::Students);   
         %prog_state=&Apache::lonhtmlcommon::Create_PrgWin  
             ($r,'Summary Table Status',  
              'Summary Table Compilation Progress', $studentcount);  
     }      }
     my $Str = "<pre>\n";      my $Str = "<pre>\n";
     # First, the @StudentData fields need to be listed      # First, the @StudentData fields need to be listed
Line 471  sub html_initialize { Line 696  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 (&Apache::lonstatistics::Sequences_with_Assess()){  
         my $title = $sequence->{'title'};  
         my $base  = $sequence->{'base_width'};  
         my $width = $sequence->{'width'};  
         $Str .= $title.' 'x($width-$base).$padding;  
     }  
     $Str .= "total (of shown problems)</pre>\n";  
     $Str .= "<pre>";  
     #      #
     # Check for suppression of output      # Compute the column widths and output the sequence titles
     if ($show eq 'final table') {      my $total_count;
         $Str = '';      #
       # Compute sequence widths
       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 6 digits for the sum
               $width{$symb}->{'width_sum'} += 6;
           }
           # 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 6 digits for the total
               $width{$symb}->{'width_sum'}+=6;
           }
    #
           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;
     }      }
       $total_sum_width = length($total_count)+1;
       $Str .= "    total</pre>\n";
       $Str .= "<pre>";
     $r->print($Str);      $r->print($Str);
     $r->rflush();      $r->rflush();
   
       $r->print(<<JS);
   <script type="text/javascript">
   // 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 currentElement;
   function popup_score(element, score) {
       popdown_score();
       var left = getLeftOffset(element);
       var top = getTopOffset(element);
       var div = document.createElement("div");
       div.className = "LC_chrt_popup";
       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]);
       element.className = "LC_chrt_popup_up";
       currentElement = element;
   }
   
   function popdown_score() {
       if (currentDiv) {
           document.body.removeChild(currentDiv);
       }
       if (currentElement) {
           currentElement.className = 'LC_chrt_popup_exists';
       }
       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 501  sub html_outputstudent { Line 831  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 509  sub html_outputstudent { Line 843  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;
     }      } else {
     if (scalar(@tmp) < 1) {   my $error = $1;
    if (scalar(@tmp) < 1) {
       $Str .= '<span class="LC_warning">'
                      .&mt('No Course Data')
                      .'</span>'."\n";
    } else {
               $Str .= '<span class="LC_error">'
                      .&mt('Error getting student data ([_1])',$error)
                      .'</span>'."\n";
    }
         $nodata_count++;          $nodata_count++;
         return if ($show eq 'final table');  
         $Str .= '<font color="blue">No Course Data</font>'."\n";  
         $r->print($Str);          $r->print($Str);
         $r->rflush();          $r->rflush();
         return;          return;
Line 525  sub html_outputstudent { Line 866  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 (&Apache::lonstatistics::Sequences_with_Assess()) {      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.2f",$score);
               $ratio .= (' 'x(6-length($score))).$score;
           } elsif($chosen_output->{'sequence_sum'}) {
               $ratio .= ' 'x6;
           }
           if ($chosen_output->{'sequence_max'}) {
               if ($chosen_output->{'sequence_sum'}) {
                   $ratio .= '/';
               }
               my $sequence_total=sprintf("%3.2f",$seq_max);
               $ratio .= $sequence_total.(' 'x(6-length($sequence_total)));
         }          }
         #          #
           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)) {
     $Str .= " \n";          $score = ' ' x $total_sum_width;
     #      } else {
     # Check for suppressed output and update the progress window if so...          $score = sprintf("%.2f",$score);
     if ($show eq 'final table') {          $score = (' 'x(6-length($score))).$score;
         $Str = '';  
         &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,  
                                                  'last student');  
     }      }
       $Str .= ' '.' 'x($total_sum_width-length($score)).$score.' / '.$max;
       $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      # Check for suppressed output and close the progress window if so
     if ($show eq 'final table') {      $r->print("</pre>\n"); 
         &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);      if ($chosen_output->{'summary_table'}) {
     } else {          if ($single_student_mode) {
         $r->print("</pre>\n");               $r->print(&SingleStudentTotal());
           } else {
               $r->print(&StudentAverageTotal());
           }
     }      }
     $r->print(&StudentAverageTotal());  
     $r->rflush();      $r->rflush();
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
       &html_cleanup();
     return;      return;
 }  }
   
 sub StudentAverageTotal {  sub StudentAverageTotal {
     my $Str = "<h3>Summary Tables</h3>\n";      my $Str = '<h3>'.&mt('Summary Tables').'</h3>'.$/;
     my $num_students = scalar(@Apache::lonstatistics::Students);      $Str .= &Apache::loncommon::start_data_table();
     my $total_ave = 0;      $Str .= &Apache::loncommon::start_data_table_header_row().
     my $total_max = 0;          '<th>'.&mt('Title').'</th>'.
     $Str .= '<table border=2 cellspacing="1">'."\n";          '<th>'.&mt('Average').'</th>'.
     $Str .= "<tr><th>Title</th><th>Average</th><th>Maximum</th></tr>\n";          '<th>'.&mt('Maximum').'</th>'.
     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {          &Apache::loncommon::end_data_table_header_row().$/;
       foreach my $seq (@sequences) {
           my $symb = $seq->symb;
         my $ave;          my $ave;
         if ($num_students > $nodata_count) {          my $num_students = $Statistics->{$symb}->{'num_students'};
             $ave = int(100*($Statistics->{$seq->{'symb'}}->{'score'}/          if ($num_students > 0) {
                             ($num_students-$nodata_count)))/100;              $ave = int(100*
                          ($Statistics->{$symb}->{'score'}/$num_students)
                          )/100;
         } else {          } else {
             $ave = 0;              $ave = 0;
         }          }
         $total_ave += $ave;          my $max = $Statistics->{$symb}->{'max'};
         my $max = $seq->{'num_assess_parts'};          $ave = sprintf("%.2f",$ave);
         $total_max += $max;          $Str .= &Apache::loncommon::start_data_table_row().
         if ($ave == 0) {              '<td>'.$seq->compTitle.'</td>'.
             $ave = "0.00";              '<td align="right">'.$ave.'&nbsp;</td>'.
         }              '<td align="right">'.$max.'&nbsp;'.'</td>'.
         $ave .= '&nbsp;';              &Apache::loncommon::end_data_table_row()."\n";
         $max .= '&nbsp;&nbsp;&nbsp;';      }
         $Str .= '<tr><td>'.$seq->{'title'}.'</td>'.      $Str .= &Apache::loncommon::end_data_table()."\n";
             '<td align="right">'.$ave.'</td>'.  
                 '<td align="right">'.$max.'</td></tr>'."\n";  
     }  
     $total_ave = int(100*$total_ave)/100; # only two digit  
     $Str .= "</table>\n";  
     $Str .= '<table border=2 cellspacing="1">'."\n";  
     $Str .= '<tr><th>Number of Students</th><th>Average</th>'.  
         "<th>Maximum</th></tr>\n";  
     $Str .= '<tr><td>'.($num_students-$nodata_count).'</td>'.  
         '<td>'.$total_ave.'</td><td>'.$total_max.'</td>';  
     $Str .= "</table>\n";  
     return $Str;      return $Str;
 }  }
   
 }  sub SingleStudentTotal {
       return if (! defined($navmap));
 #######################################################      my $student = &Apache::lonstatistics::current_student();
 #######################################################      my $Str = '<h3>'.&mt('Summary table for [_1] ([_2]@[_3])',
                            $student->{'fullname'},
 =pod                           $student->{'username'},$student->{'domain'}).'</h3>';
       $Str .= $/;
 =head2 Multi-Sheet EXCEL subroutines      $Str .= &Apache::loncommon::start_data_table()."\n";
       $Str .= 
 =item &multi_sheet_excel_initialize($r)          &Apache::loncommon::start_data_table_header_row().
           '<th>'.&mt('Sequence or Folder').'</th>';
 =item &multi_sheet_excel_outputstudent($r,$student)      if ($chosen_output->{'base'} eq 'tries') {
           $Str .= '<th>'.&mt('Parts Correct').'</th>';
 =item &multi_sheet_excel_finish($r)      } else {
           $Str .= '<th>'.&mt('Score').'</th>';
 =cut  
   
 #######################################################  
 #######################################################  
 {  
   
 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 (&Apache::lonstatistics::Sequences_with_Assess()) {  
         $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 {  
             # @Apache::lonstatistics::Sections contains 'all' as well.  
             $num_workbooks = scalar(@Apache::lonstatistics::Sections) - 1;  
         }  
     }      }
           $Str .= '<th>'.&mt('Maximum').'</th>'.
     $r->print("Maximum allowed size: ".$max_size." bytes<br />");              &Apache::loncommon::end_data_table_header_row()."\n";
     $r->print("Number of students: ".$num_students."<br />");      my $total = 0;
     $r->print("Number of fields per student: ".$fields_per_student."<br />");      my $total_max = 0;
     $r->print("Total number of fields: ".($fields_per_student*$num_students).      foreach my $seq (@sequences) {
               "<br />");          my $value = $Statistics->{$seq->symb}->{'score'};
     $r->print("Bytes per field: ".$bytes_per_field." (estimated)"."<br />");          my $max = $Statistics->{$seq->symb}->{'max'};
     $r->print("Estimated size: ".$size_estimate." bytes<br />");          $Str .= &Apache::loncommon::start_data_table_row().
     $r->print("Number of workbooks: ".$num_workbooks."<br />");              '<td>'.&HTML::Entities::encode($seq->compTitle).'</td>'.
     $r->rflush();              '<td align="right">'.$value.'</td>'.
     return;              '<td align="right">'.$max.'</td>'.
 }              &Apache::loncommon::end_data_table_row()."\n";
           $total += $value;
 sub multi_sheet_excel_outputstudent {          $total_max +=$max;
     my ($r,$student) = @_;      }
       $Str .= &Apache::loncommon::start_data_table_row().
           '<td><b>'.&mt('Total').'</b></td>'.
           '<td align="right">'.$total.'</td>'.
           '<td align="right">'.$total_max.'</td>'.
           &Apache::loncommon::end_data_table_row()."\n";
       $Str .= &Apache::loncommon::end_data_table()."\n";
       return $Str;
 }  }
   
 sub multi_sheet_excel_finish {  
     my ($r) = @_;  
 }  }
   
 }  
 #######################################################  #######################################################
 #######################################################  #######################################################
   
Line 721  sub multi_sheet_excel_finish { Line 1055  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 741  sub excel_initialize { Line 1134  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 (&Apache::lonstatistics::Sequences_with_Assess()) {      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");      $r->print('<p>'
                .&mt('Compiling Excel spreadsheet for [quant,_1,student]...',$studentcount)
               ."</p>\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 854  sub excel_outputstudent { Line 1479  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 (&Apache::lonstatistics::Sequences_with_Assess()) {      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 ($show eq 'totals') {          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 ($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 883  sub excel_outputstudent { Line 1577  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);
     #      #
     # Tell the user where to get their excel file      # Tell the user where to get their excel file
     $r->print('<br />'.      $r->print('<br />'.
               '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");                '<a href="'.$filename.'">'.&mt('Your Excel spreadsheet').'</a>'."\n");
     $r->rflush();      $r->rflush();
       &excel_cleanup();
     return;      return;
 }  }
   
Line 923  sub excel_finish { Line 1618  sub excel_finish {
   
 my $outputfile;  my $outputfile;
 my $filename;  my $filename;
   my $request_aborted;
 my %prog_state; # progress window state  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) = @_;
     #   
     # Clean up      &csv_cleanup();
     $filename = undef;      ($navmap,@sequences) = 
     $outputfile = undef;          &Apache::lonstatistics::selected_sequences_with_assessments();
     undef(%prog_state);      if (! ref($navmap)) {
           # Unable to get data, so bail out
           $r->print("<h3>".
                     &mt('Unable to retrieve course information.').
                     '</h3>');
       }
     #      #
     # Open a file      # Deal with unimplemented requests
     $filename = '/prtspool/'.      $request_aborted = undef;
         $ENV{'user.name'}.'_'.$ENV{'user.domain'}.'_'.      if ($chosen_output->{'base'} =~ /final table/) {
             time.'_'.rand(1000000000).'.csv';          $r->print(<<END);
     unless ($outputfile = Apache::File->new('>/home/httpd'.$filename)) {  <h2>Unable to Complete Request</h2>
         $r->log_error("Couldn't open $filename for output $!");  <p>
         $r->print("Problems occured in writing the csv file.  ".  The <b>Summary Table (Scores)</b> option is not available for non-HTML output.
                   "This error has been logged.  ".  </p>
                   "Please alert your LON-CAPA administrator.");  END
         $outputfile = undef;         $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      # Datestamp
     my $description = $ENV{'course.'.$ENV{'request.course.id'}.'.description'};      my $description = $env{'course.'.$env{'request.course.id'}.'.description'};
     print $outputfile '"'.&Apache::loncommon::csv_translate($description).'",'.      print $outputfile '"'.&Apache::loncommon::csv_translate($description).'",'.
         '"'.&Apache::loncommon::csv_translate(scalar(localtime(time))).'"'.          '"'.&Apache::loncommon::csv_translate(scalar(localtime(time))).'"'.
             "\n";              "\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      # Print out the headings
     my $Str = '';      my $sequence_row = '';
     my $Str2 = undef;      my $resource_row = undef;
     foreach my $field (&get_student_fields_to_show()) {      foreach my $field (&get_student_fields_to_show()) {
         if ($show eq 'scores') {          $sequence_row .='"",';
             $Str .= '"'.&Apache::loncommon::csv_translate($field).'",';          $resource_row .= '"'.&Apache::loncommon::csv_translate($field).'",';
         } elsif ($show eq 'totals') {  
             $Str .= '"",'; # first row empty on the student fields  
             $Str2 .= '"'.&Apache::loncommon::csv_translate($field).'",';  
         } elsif ($show eq 'all') {  
             $Str .= '"'.&Apache::loncommon::csv_translate($field).'",';  
         }  
     }  
     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {  
         if ($show eq 'scores') {  
             $Str .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}).  
                 '",';  
         } elsif ($show eq 'totals') {  
             $Str  .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}).  
                 '","",';  
             $Str2 .= '"score","total possible",';  
         } elsif ($show eq 'all') {  
             $Str  .= '"'.&Apache::loncommon::csv_translate($seq->{'title'}).  
                 '",';  
             $Str .= '"",'x($seq->{'num_assess_parts'}-1);  
             $Str .= '"score","total possible",';  
         }  
     }      }
     chop($Str);      foreach my $seq (@sequences) {
     $Str .= "\n";          $sequence_row .= '"'.
     print $outputfile $Str;              &Apache::loncommon::csv_translate($seq->compTitle).'",';
     if (defined($Str2)) {          my $count = 0;
         chop($Str2);          if ($chosen_output->{'every_problem'}) {
         $Str2 .= "\n";              foreach my $res (&get_resources($navmap,$seq)) {
         print $outputfile $Str2;                  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'}) {
     # Initialize progress window          $sequence_row.= '"",';
     my $studentcount = scalar(@Apache::lonstatistics::Students);          $resource_row.= '"Total",';
     %prog_state=&Apache::lonhtmlcommon::Create_PrgWin      } 
         ($r,'CSV File Compilation Status',      if ($chosen_output->{'grand_maximum'}) {
          'CSV File Compilation Progress', $studentcount);          $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) = @_;
     return if (! defined($outputfile));      if ($request_aborted || ! defined($navmap) || ! defined($outputfile)) {
           return;
       }
     my $Str = '';      my $Str = '';
     #      #
     # Output student fields      # Output student fields
     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) {
         $Str .= '"'.&Apache::loncommon::csv_translate($student->{$field}).'",';          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      # Get student assessment data
Line 1014  sub csv_outputstudent { Line 1761  sub csv_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;
     }      }
     #      #
     # Output performance data      # Output performance data
     foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess()) {      my $total = 0;
         my ($performance,$score,$seq_max) =      my $maximum = 0;
             &StudentPerformanceOnSequence($student,\%StudentsData,      foreach my $seq (@sequences) {
                                           $seq,'no');          my ($performance,$performance_length,$score,$seq_max,$rawdata);
         if ($show eq 'scores') {          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.'",';              $Str .= '"'.$score.'",';
         } elsif ($show eq 'totals') {          } 
             $Str  .= '"'.$score.'","'.$seq_max.'",';          if ($chosen_output->{'sequence_max'}) {
         } elsif ($show eq 'all') {              $Str .= '"'.$seq_max.'",';
             $Str .= '"'.join('","',(split(//,$performance),$score,$seq_max)).  
                 '",';  
         }          }
           $total+=$score;
           $maximum += $seq_max;
       }
       if ($chosen_output->{'grand_total'}) {
           $Str .= '"'.$total.'",';
       }
       if ($chosen_output->{'grand_maximum'}) {
           $Str .= '"'.$maximum.'",';
     }      }
     chop($Str);      chop($Str);
     $Str .= "\n";      $Str .= "\n";
Line 1044  sub csv_outputstudent { Line 1822  sub csv_outputstudent {
   
 sub csv_finish {  sub csv_finish {
     my ($r) = @_;      my ($r) = @_;
     return if (! defined($outputfile));      if ($request_aborted || ! defined($navmap) || ! defined($outputfile)) {
    &csv_cleanup();
           return;
       }
     close($outputfile);      close($outputfile);
     #      #
     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);
     #      #
     # Tell the user where to get their csv file      # Tell the user where to get their csv file
     $r->print('<br />'.      $r->print('<br />'.
               '<a href="'.$filename.'">Your csv file.</a>'."\n");                '<a href="'.$filename.'">'.&mt('Your CSV file.').'</a>'."\n");
     $r->rflush();      $r->rflush();
       &csv_cleanup();
     return;      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 class=\"LC_chrt_popup_exists\" onmouseover='popup_score(this, \"$popup\");return false;' onmouseout='popdown_score();return false;'>$symbol</span>";
   }
   
 #######################################################  #######################################################
 #######################################################  #######################################################
   
 =pod  =pod
   
 =item &StudentPerformanceOnSequence()  =item &StudentTriesOnSequence()
   
 Inputs:  Inputs:
   
Line 1088  Inputs: Line 1879  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' && !$resource->is_task()) {
                     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++;
                 } elsif (exists($resource_data->{'resource.'.  
                                                      $partnum.'.tries'})){  
                     $symbol = '.';  
                 } 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')) {
                   my $link = '/adm/grades'.
                       '?symb='.&escape($resource->shown_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>";
   }
   
   #######################################################
   #######################################################
   
   =pod
   
   =item &student_performance_on_sequence
   
   Inputs:
   
   =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='.&escape($resource->shown_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 1172  problems. Line 2152  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.43  
changed lines
  Added in v.1.155


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