--- loncom/interface/statistics/lonstathelpers.pm	2004/12/02 21:53:33	1.32
+++ loncom/interface/statistics/lonstathelpers.pm	2006/12/29 21:47:11	1.54
@@ -1,6 +1,6 @@
 # The LearningOnline Network with CAPA
 #
-# $Id: lonstathelpers.pm,v 1.32 2004/12/02 21:53:33 matthew Exp $
+# $Id: lonstathelpers.pm,v 1.54 2006/12/29 21:47:11 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -49,7 +49,7 @@ routines that are needed across multiple
 package Apache::lonstathelpers;
 
 use strict;
-use Apache::lonnet();
+use Apache::lonnet;
 use Apache::loncommon();
 use Apache::lonhtmlcommon();
 use Apache::loncoursedata();
@@ -60,6 +60,9 @@ use Time::Local();
 use Spreadsheet::WriteExcel();
 use GDBM_File;
 use Storable qw(freeze thaw);
+use lib '/home/httpd/lib/perl/';
+use LONCAPA;
+ 
 
 ####################################################
 ####################################################
@@ -68,8 +71,7 @@ use Storable qw(freeze thaw);
 
 =item &render_resource($resource)
 
-Input: a resource generated from 
-&Apache::loncoursedata::get_sequence_assessment_data().
+Input: a navmaps resource
 
 Retunrs: a scalar containing html for a rendering of the problem
 within a table.
@@ -82,11 +84,10 @@ sub render_resource {
     my ($resource) = @_;
     ##
     ## Render the problem
-    my $base;
-    ($base,undef) = ($resource->{'src'} =~ m|(.*/)[^/]*$|);
-    $base = "http://".$ENV{'SERVER_NAME'}.$base;
-    my $rendered_problem = 
-        &Apache::lonnet::ssi_body($resource->{'src'});
+    my ($base) = ($resource->src =~ m|^(.*/)[^/]*$|);
+    $base="http://".$ENV{'SERVER_NAME'}.$base;
+    my ($src,$symb)=($resource->src,&escape($resource->symb));
+    my $rendered_problem = &Apache::lonnet::ssi_body($src.'?symb='.$symb);
     $rendered_problem =~ s/<\s*form\s*/<nop /g;
     $rendered_problem =~ s|(<\s*/form\s*>)|<\/nop>|g;
     return '<table bgcolor="ffffff"><tr><td>'.
@@ -100,7 +101,26 @@ sub render_resource {
 
 =pod
 
-=item &ProblemSelector($AcceptedResponseTypes)
+=item &get_resources
+
+=cut
+
+####################################################
+####################################################
+sub get_resources {
+    my ($navmap,$sequence) = @_;
+    my @resources = $navmap->retrieveResources($sequence,
+                                               sub { shift->is_problem(); },
+                                               0,0,0);
+    return @resources;
+}
+
+####################################################
+####################################################
+
+=pod
+
+=item &problem_selector($AcceptedResponseTypes)
 
 Input: scalar containing regular expression which matches response
 types to show.  '.' will yield all, '(option|radiobutton)' will match
@@ -114,43 +134,47 @@ Skips 'survey' problems.
 
 ####################################################
 ####################################################
-sub ProblemSelector {
-    my ($AcceptedResponseTypes) = @_;
+sub problem_selector {
+    my ($AcceptedResponseTypes,$sequence_addendum) = @_;
     my $Str;
     $Str = "\n<table>\n";
     my $rb_count =0;
-    foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess('all')) {
-        next if ($seq->{'num_assess'}<1);
+    my ($navmap,@sequences) = 
+        &Apache::lonstatistics::selected_sequences_with_assessments('all');
+    return $navmap if (! ref($navmap)); # error
+    foreach my $seq (@sequences) {
         my $seq_str = '';
-        foreach my $res (@{$seq->{'contents'}}) {
-            next if ($res->{'type'} ne 'assessment');
-            foreach my $part (@{$res->{'parts'}}) {
-                my $partdata = $res->{'partdata'}->{$part};
-                for (my $i=0;$i<scalar(@{$partdata->{'ResponseTypes'}});$i++){
-                    my $respid = $partdata->{'ResponseIds'}->[$i];
-                    my $resptype = $partdata->{'ResponseTypes'}->[$i];
+        foreach my $res (&get_resources($navmap,$seq)) {
+            foreach my $part (@{$res->parts}) {
+                my @response_ids   = $res->responseIds($part);
+                my @response_types = $res->responseType($part);
+                for (my $i=0;$i<scalar(@response_types);$i++){
+                    my $respid = $response_ids[$i];
+                    my $resptype = $response_types[$i];
                     if ($resptype =~ m/$AcceptedResponseTypes/) {
-                        my $value = &make_target_id({symb=>$res->{'symb'},
+                        my $value = &make_target_id({symb=>$res->symb,
                                                      part=>$part,
                                                      respid=>$respid,
                                                      resptype=>$resptype});
                         my $checked = '';
-                        if ($ENV{'form.problemchoice'} eq $value) {
+                        if ($env{'form.problemchoice'} eq $value) {
                             $checked = 'checked ';
                         }
-                        my $title = $res->{'title'};
+                        my $title = $res->compTitle;
                         if (! defined($title) || $title eq '') {
-                            ($title) = ($res->{'src'} =~ m:/([^/]*)$:);
+                            ($title) = ($res->src =~ m:/([^/]*)$:);
                         }
                         $seq_str .= '<tr>'.
                             qq{<td><input type="radio" id="$rb_count" name="problemchoice" value="$value" $checked /></td>}.
                             '<td><label for="'.$rb_count.'">'.$resptype.'</label></td>'.
                             '<td><label for="'.$rb_count.'">'.$title.'</label>';
-                        if (scalar(@{$partdata->{'ResponseIds'}}) > 1) {
+                        if (scalar(@response_ids) > 1) {
                             $seq_str .= &mt('response').' '.$respid;
                         }
+                        my $link = $res->src.'?symb='.
+                            &escape($res->symb);
                         $seq_str .= ('&nbsp;'x2).
-                            qq{<a target="preview" href="$res->{'src'}">view</a>};
+                            qq{<a target="preview" href="$link">view</a>};
                         $seq_str .= "</td></tr>\n";
                         $rb_count++;
                     }
@@ -158,8 +182,15 @@ sub ProblemSelector {
             }
         }
         if ($seq_str ne '') {
-            $Str .= '<tr><td>&nbsp</td><td colspan="2"><b>'.$seq->{'title'}.'</b></td>'.
+            $Str .= '<tr><td>&nbsp</td>'.
+                '<td colspan="2"><b>'.$seq->compTitle.'</b></td>'.
                 "</tr>\n".$seq_str;
+            if (defined($sequence_addendum)) {
+                $Str .= '<tr>'.
+                    ('<td>&nbsp</td>'x2).
+                    '<td align="right">'.$sequence_addendum.'</td>'.
+                    "</tr>\n";
+            }
         }
     }
     $Str .= "</table>\n";
@@ -193,7 +224,7 @@ and their contents.  A checkbox is provi
 ####################################################
 sub MultipleProblemSelector {
     my ($navmap,$inputname,$formname)=@_;
-    my $cid = $ENV{'request.course.id'};
+    my $cid = $env{'request.course.id'};
     my $Str;
     # Massage the input as needed.
     if (! defined($navmap)) {
@@ -234,7 +265,7 @@ END
     my $iterator = $navmap->getIterator(undef, undef, undef, 1);
     my $sequence_string;
     my $seq_id = 0;
-    my @Accumulator = (&new_accumulator($ENV{'course.'.$cid.'.description'},
+    my @Accumulator = (&new_accumulator($env{'course.'.$cid.'.description'},
                                         '',
                                         '',
                                         $seq_id++,
@@ -280,16 +311,6 @@ END
     return $Str;
 }
 
-sub get_title {
-    my ($title,$src) = @_;
-    if ($title eq '') {
-        ($title) = ($src =~ m|/([^/]+)$|);
-    } else {
-        $title =~ s/\&colon;/:/g;
-    }
-    return $title;
-}
-
 sub new_accumulator {
     my ($title,$src,$symb,$seq_id,$inputname) = @_;
     my $target;
@@ -305,10 +326,11 @@ sub new_accumulator {
                 }
                 $target .= 'id="'.$seq_id.':'.$item_id++.'" ';
                 $target.= 
-                    'value="'.&Apache::lonnet::escape($res->symb).'" />'.
+                    'value="'.&escape($res->symb).'" />'.
                     '&nbsp;'.$res->compTitle.'</label>'.
                     ('&nbsp;'x2).'<a target="preview" '.
-                    'href="'.$res->src.'">view</a>'.
+                    'href="'.$res->src.'?symb='.
+                         &escape($res->symb).'">view</a>'.
                     '</td></tr>'.$/;
             } else { 
                 if (defined($target)) {
@@ -326,15 +348,10 @@ sub new_accumulator {
 sub get_selected_symbs {
     my ($inputfield) = @_;
     my $field = 'form.'.$inputfield;
-    my @Symbs;
-    if (exists($ENV{$field})) {
-        if (! ref($ENV{$field})) {
-            @Symbs = (&Apache::lonnet::unescape($ENV{$field}));
-        } else {
-            @Symbs = (map {&Apache::lonnet::unescape($_);} @{$ENV{$field}});
-        }
-    }
-    return @Symbs;
+    my @symbs = (map {
+                     &unescape($_);
+                     } &Apache::loncommon::get_env_multiple($field));
+    return @symbs;
 }
 
 ####################################################
@@ -359,10 +376,10 @@ Used by Apache::lonstathelpers::ProblemS
 ####################################################
 sub make_target_id {
     my ($target) = @_;
-    my $id = &Apache::lonnet::escape($target->{'symb'}).':'.
-             &Apache::lonnet::escape($target->{'part'}).':'.
-             &Apache::lonnet::escape($target->{'respid'}).':'.
-             &Apache::lonnet::escape($target->{'resptype'});
+    my $id = &escape($target->{'symb'}).':'.
+             &escape($target->{'part'}).':'.
+             &escape($target->{'respid'}).':'.
+             &escape($target->{'resptype'});
     return $id;
 }
 
@@ -387,18 +404,18 @@ sub get_target_from_id {
     my ($id) = @_;
     if (! ref($id)) {
         my ($symb,$part,$respid,$resptype) = split(':',$id);
-        return ({ symb     => &Apache::lonnet::unescape($symb),
-                  part     => &Apache::lonnet::unescape($part),
-                  respid   => &Apache::lonnet::unescape($respid),
-                  resptype => &Apache::lonnet::unescape($resptype)});
+        return ({ symb     => &unescape($symb),
+                  part     => &unescape($part),
+                  respid   => &unescape($respid),
+                  resptype => &unescape($resptype)});
     } elsif (ref($id) eq 'ARRAY') {
         my @Return;
         foreach my $selected (@$id) {
             my ($symb,$part,$respid,$resptype) = split(':',$selected);
-            push(@Return,{ symb     => &Apache::lonnet::unescape($symb),
-                           part     => &Apache::lonnet::unescape($part),
-                           respid   => &Apache::lonnet::unescape($respid),
-                           resptype => &Apache::lonnet::unescape($resptype)});
+            push(@Return,{ symb     => &unescape($symb),
+                           part     => &unescape($part),
+                           respid   => &unescape($respid),
+                           resptype => &unescape($resptype)});
         }
         return \@Return;
     }
@@ -417,7 +434,7 @@ current resource.
 Inputs: $target (see &Apache::lonstathelpers::get_target_from_id())
   $AcceptableResponseTypes, regular expression matching acceptable
                             response types,
-  $granularity, either 'part', 'response', or 'part_survey'
+  $granularity, either 'part', 'response', 'part_survey', or 'part_task'
 
 Returns: three hash references, $prev, $curr, $next, which refer to the
 preceeding, current, or following problem parts or responses, depending
@@ -440,36 +457,46 @@ sub get_prev_curr_next {
     #
     # Build an array with the data we need to search through
     my @Resource;
-    foreach my $seq (&Apache::lonstatistics::Sequences_with_Assess('all')) {
-        foreach my $res (@{$seq->{'contents'}}) {
-            next if ($res->{'type'} ne 'assessment');
-            foreach my $part (@{$res->{'parts'}}) {
-                my $partdata = $res->{'partdata'}->{$part};
-                if ($partdata->{'Survey'} && ($granularity eq 'part_survey')){
+    my ($navmap,@sequences) = 
+        &Apache::lonstatistics::selected_sequences_with_assessments('all');
+    return $navmap if (! ref($navmap));
+    foreach my $seq (@sequences) {
+        my @resources = &get_resources($navmap,$seq);
+        foreach my $res (@resources) {
+            foreach my $part (@{$res->parts}) {
+                if ($res->is_survey($part) && ($granularity eq 'part_survey')){
+                    push (@Resource,
+                          { symb     => $res->symb,
+                            part     => $part,
+                            resource => $res,
+                        } );
+		} elsif ($res->is_task($part) && ($granularity eq 'part_task')){
                     push (@Resource,
-                          { symb     => $res->{symb},
+                          { symb     => $res->symb,
                             part     => $part,
                             resource => $res,
                         } );
                 } elsif ($granularity eq 'part') {
                     push (@Resource,
-                          { symb     => $res->{symb},
+                          { symb     => $res->symb,
                             part     => $part,
                             resource => $res,
                         } );
                 } elsif ($granularity eq 'response') {
+                    my @response_ids   = $res->responseIds($part);
+                    my @response_types = $res->responseType($part);
                     for (my $i=0;
-                         $i<scalar(@{$partdata->{'ResponseTypes'}});
+                         $i<scalar(@response_ids);
                          $i++){
-                        my $respid = $partdata->{'ResponseIds'}->[$i];
-                        my $resptype = $partdata->{'ResponseTypes'}->[$i];
+                        my $respid   = $response_ids[$i];
+                        my $resptype = $response_types[$i];
                         next if ($resptype !~ m/$AcceptableResponseTypes/);
                         push (@Resource,
-                              { symb     => $res->{symb},
+                              { symb     => $res->symb,
                                 part     => $part,
-                                respid   => $partdata->{'ResponseIds'}->[$i],
+                                respid   => $respid,
+                                resptype => $resptype,
                                 resource => $res,
-                                resptype => $resptype
                                 } );
                     }
                 }
@@ -481,7 +508,7 @@ sub get_prev_curr_next {
     my $curr_idx;
     for ($curr_idx=0;$curr_idx<$#Resource;$curr_idx++) {
         my $curr_item = $Resource[$curr_idx];
-        if ($granularity eq 'part' || $granularity eq 'part_survey') {
+        if ($granularity =~ /^(part|part_survey|part_task)$/) {
             if ($curr_item->{'symb'} eq $target->{'symb'} &&
                 $curr_item->{'part'} eq $target->{'part'}) {
                 last;
@@ -496,7 +523,7 @@ sub get_prev_curr_next {
         }
     }
     my $curr_item = $Resource[$curr_idx];
-    if ($granularity eq 'part' || $granularity eq 'part_survey') {
+    if ($granularity =~ /^(part|part_survey|part_task)$/) {
         if ($curr_item->{'symb'}     ne $target->{'symb'} ||
             $curr_item->{'part'}     ne $target->{'part'}) {
             # bogus symb - return nothing
@@ -527,7 +554,7 @@ sub get_prev_curr_next {
         $curr = $Resource[$curr_idx  ];
         $next = $Resource[$curr_idx+1];
     }
-    return ($prev,$curr,$next);
+    return ($navmap,$prev,$curr,$next);
 }
 
 
@@ -628,7 +655,7 @@ sub analyze_problem_as_student {
     my $symb = $resource->{'symb'};
     my $analysis = &get_from_analysis_cache($sname,$sdom,$symb);
     if (! defined($analysis)) {
-        my $courseid = $ENV{'request.course.id'};
+        my $courseid = $env{'request.course.id'};
         my $Answ=&Apache::lonnet::ssi($url,('grade_target' => 'analyze',
                                             'grade_domain' => $sdom,
                                             'grade_username' => $sname,
@@ -709,11 +736,16 @@ sub get_answer {
     my ($prefix,$key,%Answer) = @_;
     my $returnvalue;
     if (exists($Answer{$key})) {
-        my $student_answer = $Answer{$key}->[0];
-        if (! defined($student_answer)) {
-            $student_answer = $Answer{$key}->[1];
-        }
-        $returnvalue = $student_answer;
+	if (ref($Answer{$key}) eq 'HASH') {
+	    my $which = 'INTERNAL';
+	    if (!exists($Answer{$key}{$which})) {
+		$which = (sort(keys(%{ $Answer{$key} })))[0];
+	    }
+	    my $student_answer = $Answer{$key}{$which}[0][0];
+	    $returnvalue = $student_answer; 
+	} else {
+	    &Apache::lonnet::logthis("error analyzing problem. got a answer of type ".ref($Answer{$key}));
+	}
     } else {
         if (exists($Answer{$prefix.'.shown'})) {
             # The response has foils
@@ -771,7 +803,7 @@ sub load_analysis_cache {
         my $storedstring;
         my %cache_db;
         if (tie(%cache_db,'GDBM_File',$cache_filename,&GDBM_READER(),0640)) {
-            $storedstring = $cache_db{&Apache::lonnet::escape($symb)};
+            $storedstring = $cache_db{&escape($symb)};
             untie(%cache_db);
         }
         if (defined($storedstring)) {
@@ -823,7 +855,7 @@ Writes the in memory cache to disk so th
 sub write_analysis_cache {
     return if (! defined($current_symb) || ! defined($cache_filename));
     my %cache_db;
-    my $key = &Apache::lonnet::escape($current_symb);
+    my $key = &escape($current_symb);
     if (tie(%cache_db,'GDBM_File',$cache_filename,&GDBM_WRCREAT(),0640)) {
         my $storestring = freeze(\%cache);
         $cache_db{$key}=$storestring;
@@ -852,7 +884,7 @@ prior to every analysis lookup.
 #####################################################
 sub ensure_proper_cache {
     my ($symb) = @_;
-    my $cid = $ENV{'request.course.id'};
+    my $cid = $env{'request.course.id'};
     my $new_filename =  '/home/httpd/perl/tmp/'.
         'problemanalysis_'.$cid.'_analysis_cache.db';
     if (! defined($cache_filename) ||
@@ -1117,8 +1149,8 @@ sub get_problem_data {
                 }
             }
             # End of logging code
-            next if ($key !~ /^$part/);
-            $key =~ s/^$part\.//;
+            next if ($key !~ /^\Q$part\E/);
+            $key =~ s/^\Q$part\E\.//;
             if (ref($value) eq 'ARRAY') {
                 if ($key eq 'options') {
                     $Partdata{$part}->{'_Options'}=$value;
@@ -1241,7 +1273,7 @@ sub limit_by_time_form {
     } 
     $timecheckbox .= 'OnChange="javascript:toggle_limitby_activity(this.checked);" ';
     $timecheckbox .= ' />';
-    $Str .= '<legend>'.&mt('[_1] Limit by time',$timecheckbox).'</legend>';
+    $Str .= '<legend><label>'.&mt('[_1] Limit by time',$timecheckbox).'</label></legend>';
     $Str .= &mt('Start Time: [_1]',$startdateform).'<br />';
     $Str .= &mt('&nbsp;End Time: [_1]',$enddateform).'<br />';
     $Str .= '</fieldset>';
@@ -1249,8 +1281,8 @@ sub limit_by_time_form {
 }
 
 sub limit_by_time {
-    if (exists($ENV{'form.limit_by_time'}) &&
-        $ENV{'form.limit_by_time'} ne '' ) {
+    if (exists($env{'form.limit_by_time'}) &&
+        $env{'form.limit_by_time'} ne '' ) {
         return 1;
     } else {
         return 0;
@@ -1265,43 +1297,6 @@ sub get_time_limits {
     return ($starttime,$endtime);
 }
 
-
-
-####################################################
-####################################################
-
-=pod
-
-=item sections_description 
-
-Inputs: @Sections, an array of sections
-
-Returns: A text description of the sections selected.
-
-=cut
-
-####################################################
-####################################################
-sub sections_description {
-    my @Sections = @_;
-    my $sectionstring = '';
-    if (scalar(@Sections) > 1) {
-        if (scalar(@Sections) > 2) {
-            my $last = pop(@Sections);
-            $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];
-        }
-    }
-    return $sectionstring;
-}
-
 ####################################################
 ####################################################
 
@@ -1323,24 +1318,25 @@ sub manage_caches {
     my $sectionkey = 
         join(',',
              map {
-                     &Apache::lonnet::escape($_);
-                 } sort(@Apache::lonstatistics::SelectedSections)
+                     &escape($_);
+                 } sort(&Apache::lonstatistics::get_selected_sections())
              );
     my $statuskey = $Apache::lonstatistics::enrollment_status;
-    if (exists($ENV{'form.ClearCache'}) || 
-        exists($ENV{'form.updatecaches'}) || 
-        (exists($ENV{'form.firstrun'}) && $ENV{'form.firstrun'} ne 'no') ||
-        (exists($ENV{'form.prevsection'}) &&
-            $ENV{'form.prevsection'} ne $sectionkey) ||
-        (exists($ENV{'form.prevenrollstatus'}) &&
-            $ENV{'form.prevenrollstatus'} ne $statuskey)
+    if (exists($env{'form.ClearCache'}) || 
+        exists($env{'form.updatecaches'}) || 
+        (exists($env{'form.firstrun'}) && $env{'form.firstrun'} ne 'no') ||
+        (exists($env{'form.prevsection'}) &&
+            $env{'form.prevsection'} ne $sectionkey) ||
+        (exists($env{'form.prevenrollstatus'}) &&
+            $env{'form.prevenrollstatus'} ne $statuskey)
         ) {
         if (defined($update_message)) {
             $r->print($update_message);
         }
-        &Apache::lonstatistics::Gather_Full_Student_Data($r,$formname,
-                                                         $inputname);
-            
+        if (0) {
+            &Apache::lonnet::logthis('Updating mysql student data caches');
+        }
+        &gather_full_student_data($r,$formname,$inputname);
     }
     #
     my @Buttons = 
@@ -1353,7 +1349,7 @@ sub manage_caches {
          '<input type="hidden" name="prevenrollstatus" value="'.$statuskey.'" />'
          );
     #
-    if (! exists($ENV{'form.firstrun'})) {
+    if (! exists($env{'form.firstrun'})) {
         $r->print('<input type="hidden" name="firstrun" value="yes" />');
     } else {
         $r->print('<input type="hidden" name="firstrun" value="no" />');
@@ -1362,8 +1358,90 @@ sub manage_caches {
     return @Buttons;
 }
 
+sub gather_full_student_data {
+    my ($r,$formname,$inputname) = @_;
+    my $status_type;
+    if (defined($formname)) {
+        $status_type = 'inline';
+    } else {
+        $status_type = 'popup';
+    }
+    my $c = $r->connection();
+    #
+    &Apache::loncoursedata::clear_internal_caches();
+    #
+    my @Students = @Apache::lonstatistics::Students;
+    #
+    # Open the progress window
+    my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin
+        ($r,&mt('Student Data Compilation Status'),
+         &mt('Student Data Compilation Progress'), scalar(@Students),
+         $status_type,undef,$formname,$inputname);
+    #
+    while (my $student = shift @Students) {
+        return if ($c->aborted());
+        my $status = &Apache::loncoursedata::ensure_current_full_data
+            ($student->{'username'},$student->{'domain'},
+             $env{'request.course.id'});
+        &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
+                                                 &mt('last student'));
+    }
+    &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
+    $r->rflush();
+    return;
+}
+
+####################################################
+####################################################
+
+=pod
+
+=item &submission_report_form
+
+Input: The originating reportSelected value for the current stats page.
 
+Output: Scalar containing HTML with needed form elements and a link to 
+the student submission reports page.
+
+=cut
 
+####################################################
+####################################################
+sub submission_report_form {
+    my ($original_report) = @_;
+    # Note: In the link below we change the reportSelected value.  If
+    # the user hits the 'back' button on the browser after getting their
+    # student submissions report, this value may still be around.  So we
+    # output a script block to set it properly.  If the $original_report
+    # value is unset, you are just asking for trouble.
+    if (! defined($original_report)) {
+        &Apache::lonnet::logthis
+            ('someone called lonstathelpers::submission_report_form without '.
+             ' enough input.');
+    }
+    my $html = $/.
+        '<script type="Text/JavaScript">'.
+        "document.Statistics.reportSelected.value='$original_report';".
+        '</script>'.
+        '<input type="hidden" name="correctans" value="true" />'.
+        '<input type="hidden" name="prob_status" value="true" />'.
+        '<input type="hidden" name="all_sub" value="true" />';
+    my $output_selector = $/.'<select name="output">'.$/;
+    foreach ('HTML','Excel','CSV') {
+        $output_selector .= '    <option value="'.lc($_).'"';
+        if ($env{'form.output'} eq lc($_)) {
+            $output_selector .= ' selected ';
+        }
+        $output_selector .='>'.&mt($_).'</option>'.$/;
+    } 
+    $output_selector .= '</select>'.$/;
+    my $link = '<a href="javascript:'.
+       q{document.Statistics.reportSelected.value='student_submission_reports';}.
+       'document.Statistics.submit();">';
+    $html.= &mt('View data as [_1] [_2]go[_3]',$output_selector,
+                $link,'</a>').$/;
+    return $html
+}
 
 ####################################################
 ####################################################