Annotation of loncom/interface/lonplacementtest.pm, revision 1.5
1.1 raeburn 1: # The LearningOnline Network with CAPA
1.5 ! raeburn 2: # Handler for a Placement Test course container
1.1 raeburn 3: #
1.5 ! raeburn 4: # $Id: lonplacementtest.pm,v 1.4 2016/04/14 15:43:45 raeburn Exp $
1.1 raeburn 5: #
6: # Copyright Michigan State University Board of Trustees
7: #
8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
9: #
10: # LON-CAPA is free software; you can redistribute it and/or modify
11: # it under the terms of the GNU General Public License as published by
12: # the Free Software Foundation; either version 2 of the License, or
13: # (at your option) any later version.
14: #
15: # LON-CAPA is distributed in the hope that it will be useful,
16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18: # GNU General Public License for more details.
19: #
20: # You should have received a copy of the GNU General Public License
21: # along with LON-CAPA; if not, write to the Free Software
1.5 ! raeburn 22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1.4 raeburn 23: #
1.1 raeburn 24: # /home/httpd/html/adm/gpl.txt
25: #
26: # http://www.lon-capa.org/
27: #
28: #
29: ###############################################################
30: ###############################################################
31:
32: =pod
33:
34: =head1 NAME
35:
36: lonplacementtest - Handler to provide initial screen for student
37: after log-in and/or role selection for a Placement Test course container
38: which is currently partially completed.
39:
40: =head1 SYNOPSIS
41:
42: lonplacementtest provides an interface for the student to choose
43: whether to continue with an existing, partially completed test,
44: or whether to start a new test. Also provides utility functions
45: used to compute/export scores, and increment the course-wide maxtries
46: parameter for the user, when an instance of a test is complete.
47:
48: =head1 DESCRIPTION
49:
50: This module is used after student log-in and/or role selection
51: for a Placement Test course contained, if there is a current,
52: partially completed version of the test. The student is prompted
53: to choose whether to continue with the current test or start a
54: new one.
55:
56: =head1 INTERNAL SUBROUTINES
57:
58: =over
59:
60: =item check_completion()
61:
62: =back
63:
64: =cut
65:
66: package Apache::lonplacementtest;
67:
68: use strict;
69: use Apache::Constants qw(:common :http);
70: use Apache::lonnet;
71: use Apache::loncommon;
72: use Apache::lonhtmlcommon;
73: use Apache::lonnavmaps;
74: use Apache::lonpageflip;
75: use Apache::lonroles;
76: use Apache::lonparmset;
77: use Apache::lonlocal;
78: use LONCAPA qw(:DEFAULT :match);
79:
80: sub handler {
81: my $r=shift;
82: if ($r->header_only) {
83: &Apache::loncommon::content_type($r,'text/html');
84: $r->send_http_header;
85: return OK;
86: }
87: if (!$env{'request.course.fn'}) {
88: # Not in a course.
89: $env{'user.error.msg'}="/adm/lonplacementtest:bre:0:0:Not in a course";
90: return HTTP_NOT_ACCEPTABLE;
91: } elsif ($env{'course.'.$env{'request.course.id'}.'.type'} ne 'Placement') {
92: # Not in a Placement Test
93: $env{'user.error.msg'}="/adm/lonplacementtest:bre:0:0:Not in a placement test";
94: return HTTP_NOT_ACCEPTABLE;
95: }
96: &Apache::loncommon::content_type($r,'text/html');
97: $r->send_http_header;
98: my ($totalpoints,$incomplete) = &check_completion(undef,undef,1);
99: if (($incomplete) && ($incomplete < 100) && (!$env{'request.role.adv'})) {
100: $r->print(&showincomplete($incomplete));
101: } else {
102: my $furl = &Apache::lonpageflip::first_accessible_resource();
103: my $cdesc = $env{'course.'.$env{'request.course.id'}.'.description'};
104: my $msg = &mt('Entering [_1] ...',$cdesc);
105: &Apache::lonroles::redirect_user($r, &mt('Entering [_1]',$cdesc),$furl, $msg);
106: }
107: return OK;
108: }
109:
110: sub check_completion {
111: my ($makenew,$map,$recursive) = @_;
112: my $navmap = Apache::lonnavmaps::navmap->new();
113: return unless (ref($navmap));
114: my @resources = $navmap->retrieveResources($map,
115: sub { $_[0]->is_problem() },$recursive);
116: my $currmax = 0;
117: my $totalpoints = 0;
118: my $totaldone = 0;
119: my $totalnotdone = 0;
120: my $incomplete;
121: if (@resources) {
122: my $firstsymb = $resources[0]->symb();
1.5 ! raeburn 123: my (%bytitle,%bysymb);
1.1 raeburn 124: foreach my $res (@resources) {
125: my $currsymb = $res->symb();
126: my $title = $res->compTitle;
127: unless (exists($bytitle{$title})) {
128: $bytitle{$title} = 0;
129: }
1.5 ! raeburn 130: unless (exists($bysymb{$currsymb})) {
! 131: $bysymb{$currsymb} = 0;
! 132: }
1.1 raeburn 133: my $notdone = 0;
134: my $done = 0;
135: my %storetries;
136: my $points = 0;
137: foreach my $part (@{$res->parts()}) {
138: my $tries = $res->tries($part);
139: my $maxtries = $res->maxtries($part);
140: if ($currmax < $maxtries) {
141: $currmax = $maxtries;
142: }
143: if ($tries < $maxtries) {
144: $notdone ++;
145: my $tries = $res->tries($part);
1.2 raeburn 146: if ($makenew) {
147: my @response_ids = $res->responseIds($part);
148: if (@response_ids) {
149: foreach my $id (@response_ids) {
150: $storetries{"resource.$part.$id.awarded"}=0;
151: $storetries{"resource.$part.$id.awarddetail"}='ASSIGNED_SCORE';
152: }
153: $storetries{"resource.$part.tries"}=$maxtries;
154: $storetries{"resource.$part.solved"}='incorrect_by_override';
155: $storetries{"resource.$part.award"}='ASSIGNED_SCORE';
156: $storetries{"resource.$part.awarded"}=0;
1.1 raeburn 157: }
158: }
159: } else {
160: my $awarded = $res->awarded($part);
161: my $weight = $res->weight($part);
162: $points += $awarded * $weight;
163: $done ++;
164: }
165: }
166: if ($notdone) {
167: $totalnotdone += $notdone;
168: if ($makenew && keys(%storetries)) {
169: my $result=&Apache::lonnet::cstore(\%storetries,$currsymb,$env{'request.course.id'},
170: $env{'user.domain'},$env{'user.name'});
171: }
172: }
173: if ($done) {
174: $totaldone += $done;
175: }
176: $bytitle{$title} += $points;
1.5 ! raeburn 177: $bysymb{$currsymb} += $points;
1.1 raeburn 178: $totalpoints += $points;
179: }
180: if ($makenew) {
181: my $newmax = $currmax + 1;
182: my $result =
183: &Apache::lonparmset::storeparm_by_symb_inner($firstsymb,'0_maxtries',
184: 4,$newmax,'int_pos',
185: $env{'user.name'},
186: $env{'user.domain'});
1.5 ! raeburn 187: my $user = $env{'user.name'}.':'.$env{'user.domain'};
! 188: if ($user) {
! 189: my %grades = (
! 190: $user => {
! 191: role => $env{'request.role'},
! 192: id => $env{'environment.id'},
! 193: status => $env{'environment.inststatus'},
! 194: lastname => $env{'environment.lastname'},
! 195: firstname => $env{'environment.firstname'},
! 196: permanentemail => $env{'environment.permanentemail'},
! 197: section => $env{'request.course.sec'},
! 198: total => $totalpoints,
! 199: category => '',
! 200: gradebookcolumn => '',
! 201: context => $map,
! 202: },
! 203: );
! 204: $grades{$user}{bytitle} = \%bytitle;
! 205: $grades{$user}{bysymb} = \%bysymb;
! 206: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
! 207: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
! 208: my $scope = 'map';
! 209: my $instcode = $env{'course.'.$env{'request.course.id'}.'.internal.coursecode'};
! 210: my $crstype = $env{'course.'.$env{'request.course.id'}.'.type'};
! 211: my $context = 'completion';
! 212: if (($cnum ne '') && ($cdom ne '')) {
! 213: my %info = (
! 214: scope => $scope,
! 215: instcode => $instcode,
! 216: crstype => $crstype,
! 217: context => $context,
! 218: );
! 219: my $response = &Apache::lonnet::auto_export_grades($cdom,$cnum,\%info,\%grades);
! 220: my $outcome;
! 221: if (ref($response) eq 'HASH') {
! 222: if ($response->{$user}) {
! 223: $outcome = 'ok';
! 224: } else {
! 225: $outcome = 'fail';
! 226: }
! 227: } else {
! 228: $outcome = $response;
! 229: }
! 230: unless ($outcome eq 'ok') {
! 231: &Apache::lonnet::logthis("Placement Test grade export for $env{'user.name'}:$env{'user.domain'} in $env{'request.course.id'} was: $outcome");
! 232: }
! 233: }
1.1 raeburn 234: }
235: }
236: }
237: my $totalparts = $totalnotdone + $totaldone;
238: if (($totalparts) && ($totalnotdone)) {
239: if (!$totaldone) {
240: $incomplete = 100;
241: } else {
242: my $undonepct = (100*$totalnotdone)/$totalparts;
243: $incomplete = sprintf("%0d",$undonepct);
244: }
245: }
246: return ($totalpoints,$incomplete);
247: }
248:
1.2 raeburn 249: sub is_lastres {
250: my ($symb,$navmap) = @_;
251: return unless (ref($navmap));
252: my $numforward = 0;
253: my $currRes = $navmap->getBySymb($symb);
254: if (ref($currRes)) {
255: my $it = $navmap->getIterator($currRes,undef,undef,1);
256: while ( my $res=$it->next()) {
257: if (ref($res)) {
258: unless ($res->symb() eq $symb) {
259: $numforward ++;
260: }
261: }
262: }
263: }
264: if (!$numforward) {
265: return 1;
266: }
267: return;
268: }
269:
270: sub has_tries {
271: my ($symb,$navmap) = @_;
272: return unless (ref($navmap));
273: my $currRes = $navmap->getBySymb($symb);
274: if (ref($currRes)) {
275: if ($currRes->is_problem()) {
276: if ($currRes->tries < $currRes->maxtries) {
277: return 1;
278: }
279: }
280: }
281: return;
282: }
283:
1.1 raeburn 284: sub showresult {
1.2 raeburn 285: my ($complete,$inhibitmenu) = @_;
1.1 raeburn 286: my ($score) = &Apache::lonplacementtest::check_completion(1,undef,1);
287: my %aclt = &test_action_text();
1.2 raeburn 288: my $output;
1.3 raeburn 289: if ($inhibitmenu) {
290: $output = '<hr />';
291: } else {
1.2 raeburn 292: my $brcrum = [{'href' => '/adm/flip?postdata=firstres%3a',
293: 'text' => 'Test Status'},];
294: $output = &Apache::loncommon::start_page('Placement Test Completed',
295: undef,{bread_crumbs=>$brcrum});
296: }
1.1 raeburn 297: if ($complete) {
298: $output .= '<p class="LC_info">'.&mt('Test is complete').'</p>';
299: }
1.2 raeburn 300: $output .= '<p>'.&mt('You scored [quant,_1,point].',$score).'</p>'
301: .&Apache::lonhtmlcommon::actionbox(
302: ['<a href="/adm/flip?postdata=firstres%3a">'.$aclt{'newt'}.'</a></li>',
303: '<a href="/adm/logout">'.$aclt{'exit'}.'</a></li>',
304: ]);
305: unless ($inhibitmenu) {
306: $output .= &Apache::loncommon::end_page();
307: }
308: return $output;
1.1 raeburn 309: }
310:
311: sub showincomplete {
1.2 raeburn 312: my ($incomplete,$inhibitmenu) = @_;
1.1 raeburn 313: my %aclt = &test_action_text();
1.2 raeburn 314: my $output;
1.1 raeburn 315: if ($incomplete == 100) {
1.3 raeburn 316: if ($inhibitmenu) {
317: $output = '<hr />';
318: } else {
1.2 raeburn 319: my $brcrum = [{'href' => '/adm/flip?postdata=firstres%3a',
320: 'text' => 'Test Status'},];
321: $output = &Apache::loncommon::start_page('Placement Test Unattempted',
322: undef,{bread_crumbs=>$brcrum});
323: }
324: $output .= '<p class="LC_warning">'.&mt('Your Placement Test is incomplete.').'<p></p>'
325: .&mt('Currently, you have not submitted any answers for any of the questions.')
326: .'</p>'
327: .&Apache::lonhtmlcommon::actionbox(
328: ['<a href="/adm/flip?postdata=firstres%3a">'.$aclt{'begin'}.'</a></li>',
329: '<a href="/adm/logout">'.$aclt{'exit'}.'</a></li>',
330: ]);
1.1 raeburn 331: } elsif ($incomplete) {
1.3 raeburn 332: if ($inhibitmenu) {
333: $output = '<hr />';
334: } else {
1.2 raeburn 335: my $brcrum = [{'href' => '/adm/flip?postdata=endplacement%3a',
336: 'text' => 'Test Status'},];
337: $output .= &Apache::loncommon::start_page('Incomplete Placement Test',
338: undef,{bread_crumbs=>$brcrum});
339: }
340: $output .= '<p class="LC_warning">'.&mt('Your Placement Test is incomplete.').'<p></p>'
341: .&mt('Currently, you have not provided an answer for [_1]% of the questions.',$incomplete)
342: .'</p>'
343: .&Apache::lonhtmlcommon::actionbox(
344: ['<a href="/adm/flip?postdata=endplacement%3a">'.$aclt{'endt'}.'</a></li>',
345: '<a href="/adm/flip?postdata=firstanswerable%3a">'.$aclt{'comp'}.'</a></li>',
346: '<a href="/adm/logout">'.$aclt{'exit'}.'</a></li>',
347: ]);
348: }
349: unless ($inhibitmenu) {
350: $output .= &Apache::loncommon::end_page();
1.1 raeburn 351: }
1.2 raeburn 352: return $output;
1.1 raeburn 353: }
354:
355: sub test_action_text {
356: return &Apache::lonlocal::texthash(
357: 'exit' => 'Logout',
358: 'newt' => 'Start a new test',
359: 'endt' => 'Mark test as completed',
360: 'comp' => 'Go to first unanswered question',
361: 'begin' => 'Go to start',
362: );
363: }
364:
365: 1;
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>