# The LearningOnline Network with CAPA # Handler for a Placement Test course container # # $Id: lonplacementtest.pm,v 1.5 2016/05/30 03:16:28 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # # ############################################################### ############################################################### =pod =head1 NAME lonplacementtest - Handler to provide initial screen for student after log-in and/or role selection for a Placement Test course container which is currently partially completed. =head1 SYNOPSIS lonplacementtest provides an interface for the student to choose whether to continue with an existing, partially completed test, or whether to start a new test. Also provides utility functions used to compute/export scores, and increment the course-wide maxtries parameter for the user, when an instance of a test is complete. =head1 DESCRIPTION This module is used after student log-in and/or role selection for a Placement Test course contained, if there is a current, partially completed version of the test. The student is prompted to choose whether to continue with the current test or start a new one. =head1 INTERNAL SUBROUTINES =over =item check_completion() =back =cut package Apache::lonplacementtest; use strict; use Apache::Constants qw(:common :http); use Apache::lonnet; use Apache::loncommon; use Apache::lonhtmlcommon; use Apache::lonnavmaps; use Apache::lonpageflip; use Apache::lonroles; use Apache::lonparmset; use Apache::lonlocal; use LONCAPA qw(:DEFAULT :match); sub handler { my $r=shift; if ($r->header_only) { &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; return OK; } if (!$env{'request.course.fn'}) { # Not in a course. $env{'user.error.msg'}="/adm/lonplacementtest:bre:0:0:Not in a course"; return HTTP_NOT_ACCEPTABLE; } elsif ($env{'course.'.$env{'request.course.id'}.'.type'} ne 'Placement') { # Not in a Placement Test $env{'user.error.msg'}="/adm/lonplacementtest:bre:0:0:Not in a placement test"; return HTTP_NOT_ACCEPTABLE; } &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; my ($totalpoints,$incomplete) = &check_completion(undef,undef,1); if (($incomplete) && ($incomplete < 100) && (!$env{'request.role.adv'})) { $r->print(&showincomplete($incomplete)); } else { my $furl = &Apache::lonpageflip::first_accessible_resource(); my $cdesc = $env{'course.'.$env{'request.course.id'}.'.description'}; my $msg = &mt('Entering [_1] ...',$cdesc); &Apache::lonroles::redirect_user($r, &mt('Entering [_1]',$cdesc),$furl, $msg); } return OK; } sub check_completion { my ($makenew,$map,$recursive) = @_; my $navmap = Apache::lonnavmaps::navmap->new(); return unless (ref($navmap)); my @resources = $navmap->retrieveResources($map, sub { $_[0]->is_problem() },$recursive); my $currmax = 0; my $totalpoints = 0; my $totaldone = 0; my $totalnotdone = 0; my $incomplete; if (@resources) { my $firstsymb = $resources[0]->symb(); my (%bytitle,%bysymb); foreach my $res (@resources) { my $currsymb = $res->symb(); my $title = $res->compTitle; unless (exists($bytitle{$title})) { $bytitle{$title} = 0; } unless (exists($bysymb{$currsymb})) { $bysymb{$currsymb} = 0; } my $notdone = 0; my $done = 0; my %storetries; my $points = 0; foreach my $part (@{$res->parts()}) { my $tries = $res->tries($part); my $maxtries = $res->maxtries($part); if ($currmax < $maxtries) { $currmax = $maxtries; } if ($tries < $maxtries) { $notdone ++; my $tries = $res->tries($part); if ($makenew) { my @response_ids = $res->responseIds($part); if (@response_ids) { foreach my $id (@response_ids) { $storetries{"resource.$part.$id.awarded"}=0; $storetries{"resource.$part.$id.awarddetail"}='ASSIGNED_SCORE'; } $storetries{"resource.$part.tries"}=$maxtries; $storetries{"resource.$part.solved"}='incorrect_by_override'; $storetries{"resource.$part.award"}='ASSIGNED_SCORE'; $storetries{"resource.$part.awarded"}=0; } } } else { my $awarded = $res->awarded($part); my $weight = $res->weight($part); $points += $awarded * $weight; $done ++; } } if ($notdone) { $totalnotdone += $notdone; if ($makenew && keys(%storetries)) { my $result=&Apache::lonnet::cstore(\%storetries,$currsymb,$env{'request.course.id'}, $env{'user.domain'},$env{'user.name'}); } } if ($done) { $totaldone += $done; } $bytitle{$title} += $points; $bysymb{$currsymb} += $points; $totalpoints += $points; } if ($makenew) { my $newmax = $currmax + 1; my $result = &Apache::lonparmset::storeparm_by_symb_inner($firstsymb,'0_maxtries', 4,$newmax,'int_pos', $env{'user.name'}, $env{'user.domain'}); my $user = $env{'user.name'}.':'.$env{'user.domain'}; if ($user) { my %grades = ( $user => { role => $env{'request.role'}, id => $env{'environment.id'}, status => $env{'environment.inststatus'}, lastname => $env{'environment.lastname'}, firstname => $env{'environment.firstname'}, permanentemail => $env{'environment.permanentemail'}, section => $env{'request.course.sec'}, total => $totalpoints, category => '', gradebookcolumn => '', context => $map, }, ); $grades{$user}{bytitle} = \%bytitle; $grades{$user}{bysymb} = \%bysymb; my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; my $scope = 'map'; my $instcode = $env{'course.'.$env{'request.course.id'}.'.internal.coursecode'}; my $crstype = $env{'course.'.$env{'request.course.id'}.'.type'}; my $context = 'completion'; if (($cnum ne '') && ($cdom ne '')) { my %info = ( scope => $scope, instcode => $instcode, crstype => $crstype, context => $context, ); my $response = &Apache::lonnet::auto_export_grades($cdom,$cnum,\%info,\%grades); my $outcome; if (ref($response) eq 'HASH') { if ($response->{$user}) { $outcome = 'ok'; } else { $outcome = 'fail'; } } else { $outcome = $response; } unless ($outcome eq 'ok') { &Apache::lonnet::logthis("Placement Test grade export for $env{'user.name'}:$env{'user.domain'} in $env{'request.course.id'} was: $outcome"); } } } } } my $totalparts = $totalnotdone + $totaldone; if (($totalparts) && ($totalnotdone)) { if (!$totaldone) { $incomplete = 100; } else { my $undonepct = (100*$totalnotdone)/$totalparts; $incomplete = sprintf("%0d",$undonepct); } } return ($totalpoints,$incomplete); } sub is_lastres { my ($symb,$navmap) = @_; return unless (ref($navmap)); my $numforward = 0; my $currRes = $navmap->getBySymb($symb); if (ref($currRes)) { my $it = $navmap->getIterator($currRes,undef,undef,1); while ( my $res=$it->next()) { if (ref($res)) { unless ($res->symb() eq $symb) { $numforward ++; } } } } if (!$numforward) { return 1; } return; } sub has_tries { my ($symb,$navmap) = @_; return unless (ref($navmap)); my $currRes = $navmap->getBySymb($symb); if (ref($currRes)) { if ($currRes->is_problem()) { if ($currRes->tries < $currRes->maxtries) { return 1; } } } return; } sub showresult { my ($complete,$inhibitmenu) = @_; my ($score) = &Apache::lonplacementtest::check_completion(1,undef,1); my %aclt = &test_action_text(); my $output; if ($inhibitmenu) { $output = '
'; } else { my $brcrum = [{'href' => '/adm/flip?postdata=firstres%3a', 'text' => 'Test Status'},]; $output = &Apache::loncommon::start_page('Placement Test Completed', undef,{bread_crumbs=>$brcrum}); } if ($complete) { $output .= '

'.&mt('Test is complete').'

'; } $output .= '

'.&mt('You scored [quant,_1,point].',$score).'

' .&Apache::lonhtmlcommon::actionbox( [''.$aclt{'newt'}.'', ''.$aclt{'exit'}.'', ]); unless ($inhibitmenu) { $output .= &Apache::loncommon::end_page(); } return $output; } sub showincomplete { my ($incomplete,$inhibitmenu) = @_; my %aclt = &test_action_text(); my $output; if ($incomplete == 100) { if ($inhibitmenu) { $output = '
'; } else { my $brcrum = [{'href' => '/adm/flip?postdata=firstres%3a', 'text' => 'Test Status'},]; $output = &Apache::loncommon::start_page('Placement Test Unattempted', undef,{bread_crumbs=>$brcrum}); } $output .= '

'.&mt('Your Placement Test is incomplete.').'

' .&mt('Currently, you have not submitted any answers for any of the questions.') .'

' .&Apache::lonhtmlcommon::actionbox( [''.$aclt{'begin'}.'', ''.$aclt{'exit'}.'', ]); } elsif ($incomplete) { if ($inhibitmenu) { $output = '
'; } else { my $brcrum = [{'href' => '/adm/flip?postdata=endplacement%3a', 'text' => 'Test Status'},]; $output .= &Apache::loncommon::start_page('Incomplete Placement Test', undef,{bread_crumbs=>$brcrum}); } $output .= '

'.&mt('Your Placement Test is incomplete.').'

' .&mt('Currently, you have not provided an answer for [_1]% of the questions.',$incomplete) .'

' .&Apache::lonhtmlcommon::actionbox( [''.$aclt{'endt'}.'', ''.$aclt{'comp'}.'', ''.$aclt{'exit'}.'', ]); } unless ($inhibitmenu) { $output .= &Apache::loncommon::end_page(); } return $output; } sub test_action_text { return &Apache::lonlocal::texthash( 'exit' => 'Logout', 'newt' => 'Start a new test', 'endt' => 'Mark test as completed', 'comp' => 'Go to first unanswered question', 'begin' => 'Go to start', ); } 1;