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 (8 years, 6 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).

# 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 = '<hr />';
    } 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 .= '<p class="LC_info">'.&mt('Test is complete').'</p>';
    }
    $output .= '<p>'.&mt('You scored [quant,_1,point].',$score).'</p>'
              .&Apache::lonhtmlcommon::actionbox(
                  ['<a href="/adm/flip?postdata=firstres%3a">'.$aclt{'newt'}.'</a></li>',
                   '<a href="/adm/logout">'.$aclt{'exit'}.'</a></li>',
                  ]);
    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 = '<hr />';
        } 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 .= '<p class="LC_warning">'.&mt('Your Placement Test is incomplete.').'<p></p>'
                  .&mt('Currently, you have not submitted any answers for any of the questions.')
                  .'</p>'
                  .&Apache::lonhtmlcommon::actionbox(
                      ['<a href="/adm/flip?postdata=firstres%3a">'.$aclt{'begin'}.'</a></li>',
                       '<a href="/adm/logout">'.$aclt{'exit'}.'</a></li>',
                      ]);
    } elsif ($incomplete) {
        if ($inhibitmenu) {
            $output = '<hr />';
        } 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 .= '<p class="LC_warning">'.&mt('Your Placement Test is incomplete.').'<p></p>'
                  .&mt('Currently, you have not provided an answer for [_1]% of the questions.',$incomplete)
                  .'</p>'
                  .&Apache::lonhtmlcommon::actionbox(
                      ['<a href="/adm/flip?postdata=endplacement%3a">'.$aclt{'endt'}.'</a></li>',
                       '<a href="/adm/flip?postdata=firstanswerable%3a">'.$aclt{'comp'}.'</a></li>',
                       '<a href="/adm/logout">'.$aclt{'exit'}.'</a></li>',
                      ]);
    }
    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;

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>