File:  [LON-CAPA] / loncom / interface / lonplacementtest.pm
Revision 1.5: download - view: text, annotated - select for diffs
Mon May 30 03:16:28 2016 UTC (7 years, 11 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_12_X, HEAD
- Bug 6808. New course container -- "Placement" for Placement Tests.
  - Student's score on a placement test can be exported by customizing
    localenroll.pm in /home/httpd/lib/perl on domain's library server.
  - When a placement test is completed by the student, the chain:
    lonnet::auto_export_grades() > lond::auto_export_grades_handler()
    > localenroll::export_grades() is used to export grades (e.g. to
    a CMS).

    1: # The LearningOnline Network with CAPA
    2: # Handler for a Placement Test course container 
    3: #
    4: # $Id: lonplacementtest.pm,v 1.5 2016/05/30 03:16:28 raeburn Exp $
    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
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
   23: #
   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();
  123:         my (%bytitle,%bysymb);
  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:             }
  130:             unless (exists($bysymb{$currsymb})) {
  131:                 $bysymb{$currsymb} = 0;
  132:             }
  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);
  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;
  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;
  177:             $bysymb{$currsymb} += $points;
  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'});
  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:                 }
  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: 
  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: 
  284: sub showresult {
  285:     my ($complete,$inhibitmenu) = @_;
  286:     my ($score) = &Apache::lonplacementtest::check_completion(1,undef,1);
  287:     my %aclt = &test_action_text();
  288:     my $output;
  289:     if ($inhibitmenu) {
  290:         $output = '<hr />';
  291:     } else {
  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:     }
  297:     if ($complete) {
  298:         $output .= '<p class="LC_info">'.&mt('Test is complete').'</p>';
  299:     }
  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;
  309: }
  310: 
  311: sub showincomplete {
  312:     my ($incomplete,$inhibitmenu) = @_;
  313:     my %aclt = &test_action_text();
  314:     my $output;
  315:     if ($incomplete == 100) {
  316:         if ($inhibitmenu) {
  317:             $output = '<hr />';
  318:         } else {
  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:                       ]);
  331:     } elsif ($incomplete) {
  332:         if ($inhibitmenu) {
  333:             $output = '<hr />';
  334:         } else {
  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();
  351:     }
  352:     return $output;
  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>