--- loncom/homework/grades.pm 2011/11/14 03:08:01 1.663 +++ loncom/homework/grades.pm 2012/05/13 01:15:30 1.673 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.663 2011/11/14 03:08:01 raeburn Exp $ +# $Id: grades.pm,v 1.673 2012/05/13 01:15:30 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -1524,17 +1524,15 @@ INNERJS function savedMsgHeader(Nmsg,usrctr,fullname) { var height = 70*Nmsg+250; - var scrollbar = "no"; if (height > 600) { height = 600; - scrollbar = "yes"; } var xpos = (screen.width-600)/2; xpos = (xpos < 0) ? '0' : xpos; var ypos = (screen.height-height)/2-30; ypos = (ypos < 0) ? '0' : ypos; - pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=700,height='+height); + pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars=yes,screenx='+xpos+',screeny='+ypos+',width=700,height='+height); pWin.focus(); pDoc = pWin.document; pDoc.$docopen; @@ -1829,14 +1827,27 @@ sub show_problem { $companswer=~s|||g; $companswer=~s|name="submit"|name="would_have_been_submit"|g; } + my $renderheading = &mt('View of the problem'); + my $answerheading = &mt('Correct answer'); + if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) { + my $stu_fullname = $env{'form.fullname'}; + if ($stu_fullname eq '') { + $stu_fullname = &Apache::loncommon::plainname($uname,$udom,'lastname'); + } + my $forwhom = &nameUserString(undef,$stu_fullname,$uname,$udom); + if ($forwhom ne '') { + $renderheading = &mt('View of the problem for[_1]',$forwhom); + $answerheading = &mt('Correct answer for[_1]',$forwhom); + } + } $rendered= '
' - .'

'.&mt('View of the problem').'

' + .'

'.$renderheading.'

' .$rendered .'
'; $companswer= '
' - .'

'.&mt('Correct answer').'

' + .'

'.$answerheading.'

' .$companswer .'
'; my $result; @@ -2508,6 +2519,183 @@ sub keywords_highlight { return $string; } +# For Tasks provide a mechanism to display previous version for one specific student + +sub show_previous_task_version { + my ($request,$symb) = @_; + if ($symb eq '') { + $request->print("Unable to handle ambiguous references."); + + return ''; + } + my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'}); + my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'}); + if (!&canview($usec)) { + $request->print('Unable to view previous version for requested student.('. + $uname.':'.$udom.' in section '.$usec.' in course id '. + $env{'request.course.id'}.')'); + return; + } + my $mode = 'both'; + my $isTask = ($symb =~/\.task$/); + if ($isTask) { + if ($env{'form.previousversion'} =~ /^\d+$/) { + if ($env{'form.fullname'} eq '') { + $env{'form.fullname'} = + &Apache::loncommon::plainname($uname,$udom,'lastname'); + } + my $probtitle=&Apache::lonnet::gettitle($symb); + $request->print("\n\n". + '
'. + '

'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom). + '

'."\n"); + &Apache::lonxml::clear_problem_counter(); + $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode, + {'previousversion' => $env{'form.previousversion'} })); + $request->print("\n
"); + } + } + return; +} + +sub choose_task_version_form { + my ($symb,$uname,$udom,$nomenu) = @_; + my $isTask = ($symb =~/\.task$/); + my ($current,$version,$result,$js,$displayed,$rowtitle); + if ($isTask) { + my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'}, + $udom,$uname); + if (($record{'resource.0.version'} eq '') || + ($record{'resource.0.version'} < 2)) { + return ($record{'resource.0.version'}, + $record{'resource.0.version'},$result,$js); + } else { + $current = $record{'resource.0.version'}; + } + if ($env{'form.previousversion'}) { + $displayed = $env{'form.previousversion'}; + $rowtitle = &mt('Choose another version:') + } else { + $displayed = $current; + $rowtitle = &mt('Show earlier version:'); + } + $result = '
'; + my $list; + my $numversions = 0; + for (my $i=1; $i<=$record{'resource.0.version'}; $i++) { + if ($i == $current) { + if (!$env{'form.previousversion'} || $nomenu) { + next; + } else { + $list .= ''."\n"; + $numversions ++; + } + } elsif (defined($record{'resource.'.$i.'.0.status'})) { + unless ($i == $env{'form.previousversion'}) { + $numversions ++; + } + $list .= ''."\n"; + } + } + if ($numversions) { + $symb = &HTML::Entities::encode($symb,'<>"&'); + $result .= + '
'. + &Apache::loncommon::start_data_table(). + &Apache::loncommon::start_data_table_row(). + ''.$rowtitle.''. + ''. + &Apache::loncommon::end_data_table_row(); + unless ($nomenu) { + $result .= &Apache::loncommon::start_data_table_row(). + ''.&mt('Open in new window').''. + ''. + ''. + ''. + ''. + &Apache::loncommon::end_data_table_row(); + } + $result .= + &Apache::loncommon::start_data_table_row(). + ' '. + ''. + ''. + ''. + &Apache::loncommon::end_data_table_row(). + &Apache::loncommon::end_data_table(). + '
'; + $js = &previous_display_javascript($nomenu,$current); + } elsif ($displayed && $nomenu) { + $result .= ''.&mt('Close window').''; + } else { + $result .= &mt('No previous versions to show for this student'); + } + $result .= '
'; + } + return ($current,$displayed,$result,$js); +} + +sub previous_display_javascript { + my ($nomenu,$current) = @_; + my $js = <<"JSONE"; + +ENDJS + +} + #--- Called from submission routine sub processHandGrade { my ($request,$symb) = @_; @@ -4540,6 +4728,7 @@ sub displaySubByDates { &Apache::loncommon::start_data_table_header_row(). ''.&mt('Date/Time').''. ($isCODE?''.&mt('CODE').'':''). + ($isTask?''.&mt('Version').'':''). ''.&mt('Submission').''. ''.&mt('Status').''. &Apache::loncommon::end_data_table_header_row(); @@ -4560,7 +4749,9 @@ sub displaySubByDates { if (exists($$record{$version.':resource.0.version'})) { $interaction = $$record{$version.':resource.0.version'}; } - + if ($isTask && $env{'form.previousversion'}) { + next unless ($interaction == $env{'form.previousversion'}); + } my $where = ($isTask ? "$version:resource.$interaction" : "$version:resource"); $studentTable.=&Apache::loncommon::start_data_table_row(). @@ -4568,6 +4759,9 @@ sub displaySubByDates { if ($isCODE) { $studentTable.=''.$record->{$version.':resource.CODE'}.''; } + if ($isTask) { + $studentTable.=''.$interaction.''; + } my @versionKeys = split(/\:/,$$record{$version.':keys'}); my @displaySub = (); foreach my $partid (@{$parts}) { @@ -4587,7 +4781,7 @@ sub displaySubByDates { my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/) : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/)); - $displaySub[0].='' .' ' .'('.&mt('Response ID: [_1]',$responseId).')' @@ -5412,6 +5606,7 @@ sub scantron_selectphase { LastNameLength - number of columns that the last name spans BubblesPerRow - number of bubbles available in each row used to bubble an answer. (If not specified, 10 assumed). + =cut sub get_scantron_config { @@ -6909,7 +7104,7 @@ sub scantron_get_correction { .&mt('The ID on the form is [_1]', "$$scan_record{'scantron.ID'}") .'
' - .&mt('The name on the paper is [_2], [_3]', + .&mt('The name on the paper is [_1], [_2]', $$scan_record{'scantron.LastName'}, $$scan_record{'scantron.FirstName'}) .'

'; @@ -7518,7 +7713,8 @@ sub scantron_get_maxbubble { my $response_number = 0; my $bubble_line = 0; foreach my $resource (@resources) { - my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,$udom,undef,$bubbles_per_row); + my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname, + $udom,undef,$bubbles_per_row); if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) { foreach my $part_id (@{$parts}) { my $lines; @@ -7723,7 +7919,8 @@ sub scantron_process_students { } my ($analysis,$parts) = &scantron_partids_tograde($resource,$env{'request.course.id'}, - $env{'user.name'},$env{'user.domain'},1,$bubbles_per_row); + $env{'user.name'},$env{'user.domain'}, + 1,$bubbles_per_row); $grader_partids_by_symb{$ressymb} = $parts; if (ref($analysis) eq 'HASH') { if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') { @@ -7750,11 +7947,8 @@ SCANTRONFORM my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam')); my $count=&get_todo_count($scanlines,$scan_data); - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Bubblesheet Status', - 'Bubblesheet Progress',$count, - 'inline',undef,'scantronupload'); - &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, - 'Processing first student'); + my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count); + &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student'); $r->print('
'); my $start=&Time::HiRes::time(); my $i=-1; @@ -7786,8 +7980,7 @@ SCANTRONFORM my $line=&scantron_get_line($scanlines,$scan_data,$i); if ($line=~/^[\s\cz]*$/) { next; } if ($started) { - &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, - 'last student'); + &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student'); } $started=1; my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, @@ -7817,7 +8010,8 @@ SCANTRONFORM if ((exists($grader_randomlists_by_symb{$ressymb})) || (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) { my ($analysis,$parts) = - &scantron_partids_tograde($resource,$env{'request.course.id'},$uname,$udom,undef,$bubbles_per_row); + &scantron_partids_tograde($resource,$env{'request.course.id'}, + $uname,$udom,undef,$bubbles_per_row); $partids_by_symb{$ressymb} = $parts; } else { $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb}; @@ -7946,7 +8140,8 @@ sub graders_resources_pass { my $ressymb = $resource->symb(); my ($analysis,$parts) = &scantron_partids_tograde($resource,$env{'request.course.id'}, - $env{'user.name'},$env{'user.domain'},1,$bubbles_per_row); + $env{'user.name'},$env{'user.domain'}, + 1,$bubbles_per_row); $grader_partids_by_symb->{$ressymb} = $parts; if (ref($analysis) eq 'HASH') { if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') { @@ -8267,7 +8462,8 @@ sub checkscantron_results { my $map=$navmap->getResourceByUrl($sequence); my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); my (%grader_partids_by_symb,%grader_randomlists_by_symb); - &graders_resources_pass(\@resources,\%grader_partids_by_symb, \%grader_randomlists_by_symb); + &graders_resources_pass(\@resources,\%grader_partids_by_symb, + \%grader_randomlists_by_symb,$bubbles_per_row); my ($uname,$udom); my (%scandata,%lastname,%bylast); @@ -8278,9 +8474,7 @@ sub checkscantron_results { my %completedstudents; my $count=&Apache::grades::get_todo_count($scanlines,$scan_data); - my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Bubblesheet/Submissions Comparison Status', - 'Progress of Bubblesheet Data/Submission Records Comparison',$count, - 'inline',undef,'checkscantron'); + my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count); my ($username,$domain,$started); my $nav_error; &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse. @@ -8289,8 +8483,7 @@ sub checkscantron_results { return ''; } - &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, - 'Processing first student'); + &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student'); my $start=&Time::HiRes::time(); my $i=-1; @@ -8300,8 +8493,7 @@ sub checkscantron_results { my $line=&Apache::grades::scantron_get_line($scanlines,$scan_data,$i); if ($line=~/^[\s\cz]*$/) { next; } if ($started) { - &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, - 'last student'); + &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student'); } $started=1; my $scan_record= @@ -8333,7 +8525,9 @@ sub checkscantron_results { if ((exists($grader_randomlists_by_symb{$ressymb})) || (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) { (my $analysis,$parts) = - &scantron_partids_tograde($resource,$env{'request.course.id'},$username,$domain,undef,$bubbles_per_row); + &scantron_partids_tograde($resource,$env{'request.course.id'}, + $username,$domain,undef, + $bubbles_per_row); } else { $parts = $grader_partids_by_symb{$ressymb}; } @@ -8553,7 +8747,7 @@ sub letter_to_digits { sub href_symb_cmd { my ($symb,$cmd)=@_; - return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&command='.$cmd; + return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&command='.$cmd; } sub grading_menu { @@ -8913,7 +9107,8 @@ sub process_clicker { my $pincorrect=&mt("Percentage points for incorrect solution"); my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype', {'iclicker' => 'i>clicker', - 'interwrite' => 'interwrite PRS'}); + 'interwrite' => 'interwrite PRS', + 'turning' => 'Turning Technologies'}); $symb = &Apache::lonenc::check_encrypt($symb); $result.= &Apache::lonhtmlcommon::scripttag(<'. ''. &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses', @@ -9223,6 +9421,32 @@ sub interwrite_eval { return ($errormsg,$number); } +sub turning_eval { + my ($questiontitles,$responses)=@_; + my $number=0; + my $errormsg=''; + foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) { + my %components=&Apache::loncommon::record_sep($line); + my @entries=map {$components{$_}} (sort(keys(%components))); + if ($#entries>$number) { $number=$#entries; } + my $id=$entries[0]; + my @idresponses; + $id=~s/^[\#0]+//; + unless ($id) { next; } + for (my $idx=1;$idx<=$#entries;$idx++) { + $entries[$idx]=~s/\,/\;/g; + $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+\;]+//g; + push(@idresponses,$entries[$idx]); + } + $$responses{$id}=join(',',@idresponses); + } + for (my $i=1; $i<=$number; $i++) { + $$questiontitles[$i]=&mt('Question [_1]',$i); + } + return ($errormsg,$number); +} + + sub assign_clicker_grades { my ($r,$symb)=@_; if (!$symb) {return '';} @@ -9360,13 +9584,17 @@ sub navmap_errormsg { } sub startpage { - my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag) = @_; - unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"}); - $r->print(&Apache::loncommon::start_page('Grading',undef, - {'bread_crumbs' => $crumbs})); - &Apache::lonquickgrades::startGradeScreen($r,($env{'form.symb'}?'probgrading':'grading')); + my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js) = @_; + if ($nomenu) { + $r->print(&Apache::loncommon::start_page("Student's Version",$js,{'only_body' => '1'})); + } else { + unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"}); + $r->print(&Apache::loncommon::start_page('Grading',$js, + {'bread_crumbs' => $crumbs})); + &Apache::lonquickgrades::startGradeScreen($r,($env{'form.symb'}?'probgrading':'grading')); + } unless ($nodisplayflag) { - $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag)); + $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp)); } } @@ -9388,23 +9616,25 @@ sub handler { } &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}); +# see what command we need to execute + + my @commands=&Apache::loncommon::get_env_multiple('form.command'); + my $command=$commands[0]; + &init_perm(); if (!$env{'request.course.id'}) { - # Not in a course. - $env{'user.error.msg'}="/adm/grades::vgr:0:0:Cannot display grades page outside course context"; - return HTTP_NOT_ACCEPTABLE; + unless ((&Apache::lonnet::allowed('usc',$env{'request.role.domain'})) && + ($command =~ /^scantronupload/)) { + # Not in a course. + $env{'user.error.msg'}="/adm/grades::vgr:0:0:Cannot display grades page outside course context"; + return HTTP_NOT_ACCEPTABLE; + } } elsif (!%perm) { $request->internal_redirect('/adm/quickgrades'); } &Apache::loncommon::content_type($request,'text/html'); $request->send_http_header; - -# see what command we need to execute - - my @commands=&Apache::loncommon::get_env_multiple('form.command'); - my $command=$commands[0]; - if ($#commands > 0) { &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands)); } @@ -9427,8 +9657,29 @@ sub handler { &select_problem($request); } else { if ($command eq 'submission' && $perm{'vgr'}) { - &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}]); + my ($stuvcurrent,$stuvdisp,$versionform,$js); + if (($env{'form.student'} ne '') && ($env{'form.userdom'} ne '')) { + ($stuvcurrent,$stuvdisp,$versionform,$js) = + &choose_task_version_form($symb,$env{'form.student'}, + $env{'form.userdom'}); + } + &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}],undef,undef,$stuvcurrent,$stuvdisp,undef,$js); + if ($versionform) { + $request->print($versionform); + } + $request->print('
'); ($env{'form.student'} eq '' ? &listStudents($request,$symb) : &submission($request,0,0,$symb)); + } elsif ($command eq 'versionsub' && $perm{'vgr'}) { + my ($stuvcurrent,$stuvdisp,$versionform,$js) = + &choose_task_version_form($symb,$env{'form.student'}, + $env{'form.userdom'}, + $env{'form.inhibitmenu'}); + &startpage($request,$symb,[{href=>"", text=>"Previous Student Version"}],undef,undef,$stuvcurrent,$stuvdisp,$env{'form.inhibitmenu'},$js); + if ($versionform) { + $request->print($versionform); + } + $request->print('
'); + $request->print(&show_previous_task_version($request,$symb)); } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) { &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'}, {href=>'',text=>'Select student'}],1,1); @@ -9566,8 +9817,11 @@ sub handler { if ($ssi_error) { &ssi_print_error($request); } - &Apache::lonquickgrades::endGradeScreen($request); - $request->print(&Apache::loncommon::end_page()); + if ($env{'form.inhibitmenu'}) { + $request->print(&Apache::loncommon::end_page()); + } else { + &Apache::lonquickgrades::endGradeScreen($request); + } &reset_caches(); return OK; } @@ -9638,6 +9892,75 @@ ssi_with_retries() =over +=head1 Routines to display previous version of a Task for a specific student + +Tasks are graded pass/fail. Students who have yet to pass a particular Task +can receive another opportunity. Access to tasks is slot-based. If a slot +requires a proctor to check-in the student, a new version of the Task will +be created when the student is checked in to the new opportunity. + +If a particular student has tried two or more versions of a particular task, +the submission screen provides a user with vgr privileges (e.g., a Course +Coordinator) the ability to display a previous version worked on by the +student. By default, the current version is displayed. If a previous version +has been selected for display, submission data are only shown that pertain +to that particular version, and the interface to submit grades is not shown. + +=over 4 + +=item show_previous_task_version() + +Displays a specified version of a student's Task, as the student sees it. + +Inputs: 2 + request - request object + symb - unique symb for current instance of resource + +Output: None. + +Side Effects: calls &show_problem() to print version of Task, with + version contained in form item: $env{'form.previousversion'} + +=item choose_task_version_form() + +Displays a web form used to select which version of a student's view of a +Task should be displayed. Either launches a pop-up window, or replaces +content in existing pop-up, or replaces page in main window. + +Inputs: 4 + symb - unique symb for current instance of resource + uname - username of student + udom - domain of student + nomenu - 1 if display is in a pop-up window, and hence no menu + breadcrumbs etc., are displayed + +Output: 4 + current - student's current version + displayed - student's version being displayed + result - scalar containing HTML for web form used to switch to + a different version (or a link to close window, if pop-up). + js - javascript for processing selection in versions web form + +Side Effects: None. + +=item previous_display_javascript() + +Inputs: 2 + nomenu - 1 if display is in a pop-up window, and hence no menu + breadcrumbs etc., are displayed. + current - student's current version number. + +Output: 1 + js - javascript for processing selection in versions web form. + +Side Effects: None. + +=back + +=head1 Routines to process bubblesheet data. + +=over 4 + =item scantron_get_correction() : Builds the interface screen to interact with the operator to fix a @@ -9743,7 +10066,9 @@ ssi_with_retries() =item navmap_errormsg() : Returns HTML mark-up inside a
with a link to re-initialize the course. - Should be called whenever the request to instantiate a navmap object fails. + Should be called whenever the request to instantiate a navmap object fails. + +=back =back