From: Gavin L. v. a. <we...@ma...> - 2005-02-22 23:22:56
|
Log Message: ----------- Gateway update: Include code to check due dates against submission time, rather than current time, by adding a version_last_attempt_time entry to set_user. Also allow viewing of proctored sets without proctor authorization once they can no longer be submitted for a grade. Tags: ---- rel-2-1-a1 Modified Files: -------------- webwork2/lib/WeBWorK/ContentGenerator: GatewayQuiz.pm LoginProctor.pm Revision Data ------------- Index: GatewayQuiz.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm,v retrieving revision 1.9.4.8 retrieving revision 1.9.4.9 diff -Llib/WeBWorK/ContentGenerator/GatewayQuiz.pm -Llib/WeBWorK/ContentGenerator/GatewayQuiz.pm -u -r1.9.4.8 -r1.9.4.9 --- lib/WeBWorK/ContentGenerator/GatewayQuiz.pm +++ lib/WeBWorK/ContentGenerator/GatewayQuiz.pm @@ -125,20 +125,25 @@ # gateway change here: add $submitAnswers as an optional additional argument # to be included if it's defined +# we also allow for a version_last_attempt_time which is the time the set was +# submitted; if that's present we use that instead of the current time to +# decide if we can record the answers. this deals with the time between the +# submission time and the proctor authorization. sub can_recordAnswers { my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_; my $authz = $self->r->authz; -# warn("canrecord: set open/close = " . $Set->open_date . ", " . $Set->due_date . "\n"); + my $submitTime = ( defined($Set->version_last_attempt_time()) && + $Set->version_last_attempt_time() ) ? + $Set->version_last_attempt_time() : time(); if ($User->user_id ne $EffectiveUser->user_id) { return $authz->hasPermissions($User->user_id, "record_answers_when_acting_as_student"); } - if (before($Set->open_date)) { -# warn("ack1\n"); + if (before($Set->open_date, $submitTime)) { return $authz->hasPermissions($User->user_id, "record_answers_before_open_date"); - } elsif (between($Set->open_date, $Set->due_date)) { + } elsif (between($Set->open_date, $Set->due_date, $submitTime)) { # gateway change here; we look at maximum attempts per version, not for the set, # to determine the number of attempts allowed @@ -146,31 +151,36 @@ my $addOne = defined( $submitAnswers ) ? $submitAnswers : 0; my $max_attempts = $Set->attempts_per_version(); my $attempts_used = $Problem->num_correct+$Problem->num_incorrect+$addOne; -# warn("max_attempts = $max_attempts, addOne = $addOne, attempts_used = $attempts_used\n"); if ($max_attempts == -1 or $attempts_used < $max_attempts) { return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_with_attempts"); } else { return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_without_attempts"); } - } elsif (between($Set->due_date, $Set->answer_date)) { -# warn("ack2\n"); + } elsif (between($Set->due_date, $Set->answer_date, $submitTime)) { return $authz->hasPermissions($User->user_id, "record_answers_after_due_date"); - } elsif (after($Set->answer_date)) { -# warn("ack3\n"); + } elsif (after($Set->answer_date, $submitTime)) { return $authz->hasPermissions($User->user_id, "record_answers_after_answer_date"); } } # gateway change here: add $submitAnswers as an optional additional argument # to be included if it's defined +# we also allow for a version_last_attempt_time which is the time the set was +# submitted; if that's present we use that instead of the current time to +# decide if we can check the answers. this deals with the time between the +# submission time and the proctor authorization. sub can_checkAnswers { my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_; my $authz = $self->r->authz; - if (before($Set->open_date)) { + my $submitTime = ( defined($Set->version_last_attempt_time()) && + $Set->version_last_attempt_time() ) ? + $Set->version_last_attempt_time() : time(); + + if (before($Set->open_date, $submitTime)) { return $authz->hasPermissions($User->user_id, "check_answers_before_open_date"); - } elsif (between($Set->open_date, $Set->due_date)) { + } elsif (between($Set->open_date, $Set->due_date, $submitTime)) { # gateway change here; we look at maximum attempts per version, not for the set, # to determine the number of attempts allowed @@ -184,17 +194,19 @@ } else { return $authz->hasPermissions($User->user_id, "check_answers_after_open_date_without_attempts"); } - } elsif (between($Set->due_date, $Set->answer_date)) { + } elsif (between($Set->due_date, $Set->answer_date, $submitTime)) { return $authz->hasPermissions($User->user_id, "check_answers_after_due_date"); - } elsif (after($Set->answer_date)) { + } elsif (after($Set->answer_date, $submitTime)) { return $authz->hasPermissions($User->user_id, "check_answers_after_answer_date"); } } # Helper functions for calculating times -sub before { return time <= $_[0] } -sub after { return time >= $_[0] } -sub between { my $t = time; return $t > $_[0] && $t < $_[1] } +# gateway change here: we allow an optional additional argument to use as the +# time to check rather than time() +sub before { return (@_==2) ? $_[1] <= $_[0] : time <= $_[0] } +sub after { return (@_==2) ? $_[1] <= $_[0] : time >= $_[0] } +sub between { my $t = (@_==3) ? $_[2] : time; return $t > $_[0] && $t < $_[1] } ################################################################################ # output utilities @@ -300,9 +312,6 @@ # my $summary = "On this attempt, you answered $numCorrect out of " # . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent."; -# *GW* / FIXME we probably want to change this to give a more logical answer -# for gateway tests - my $summary = ""; if (scalar @answerNames == 1) { if ($numCorrect == scalar @answerNames) { @@ -510,6 +519,7 @@ # unproctored test. so we get the versioned set here my $set = $db->getMergedVersionedSet($effectiveUserName, $setName, $requestedVersion); + unless (defined $set) { my $userSetClass = $ce->{dbLayout}->{set_user}->{record}; $set = global2user($userSetClass, $db->getGlobalSet($setName)); @@ -521,22 +531,22 @@ my $setVersionName = $set->set_id(); my ($setVersionNumber) = ($setVersionName =~ /.*,v(\d+)$/); -# debugging global2user -# if ( defined($set) ) { -# my $str = ''; -# foreach ( map {$_} $db->newUserSet->FIELDS ) { -# $str .= "** $_ => " . $set->$_ . "\n"; -# } -# warn("** got set $setName (reqVer = $requestedVersion)\n$str\n"); -# } - -# FIXME: this needs to check the set version, not the template, for the -# case of a finished proctored test FIXME -# and check for a valid proctor login, to be sure that no one is trying -# to abuse the url path to sneak in the back door on a proctored test - die("Set $setName requires a valid proctor login.") - if ( $tmplSet->assignment_type() =~ /proctored/i && - ! WeBWorK::Authen->new($r, $ce, $db)->verifyProctor() ); +# proctor check to be sure that no one is trying to abuse the url path to sneak +# in the back door on a proctored test +# in the dispatcher we make sure that every call with a proctored url has a +# valid proctor authentication. so if we're here either we were called with +# an unproctored url, or we have a valid proctor authentication. +# this check is to be sure we have a valid proctor authentication for any test +# that has a proctored assignment type, preventing someone from trying to +# go to a proctored test with a hacked unproctored URL + if ( ( $requestedVersion && $set->assignment_type() =~ /proctored/i ) || + ( ! $requestedVersion && $tmplSet->assignment_type() =~ /proctored/i ) + ) { +# check against the requested set, if that is the one we're using, or against +# the template if no version was specified. + die("Set $setName requires a valid proctor login.") + if ( ! WeBWorK::Authen->new($r, $ce, $db)->verifyProctor() ); + } ################################# # assemble gateway parameters @@ -646,14 +656,7 @@ $setVersionName = "$setName,v$setVersionNumber"; $set = $db->getMergedVersionedSet($userName,$setName, $setVersionNumber); -# debugging problem merging -# if ( defined($set) ) { -# my $str = ''; -# foreach ( map {$_} $db->newUserSet->FIELDS ) { -# $str .= "** $_ => >" . $set->$_ . "<\n"; -# } -# warn("** got set $setName (reqVer = $requestedVersion)\n$str\n"); -# } + $Problem = $db->getMergedVersionedProblem($userName,$setName, $setVersionName,1); # because we're creating this on the fly, it should be published @@ -663,6 +666,7 @@ $set->open_date( $timeNow ); $set->due_date( $timeNow+$timeLimit ); $set->answer_date( $timeNow+$timeLimit ); + $set->version_last_attempt_time( 0 ); # put this new info into the database. note that this means that -all- of # the merged information gets put back into the database. as long as # the version doesn't have a long lifespan, this is ok... @@ -683,7 +687,7 @@ } elsif ( $currentNumAttempts < $maxAttemptsPerVersion && $timeNow < $set->due_date() ) { - if ( between($timeNow, $set->open_date(), $set->due_date()) ) { + if ( between($set->open_date(), $set->due_date(), $timeNow) ) { $versionIsOpen = 1; } else { $versionIsOpen = 0; # redundant; default is 0 @@ -731,7 +735,7 @@ $authz->hasPermissions($effectiveUserName, "record_answers_when_acting_as_student") ) ) { - if ( between($timeNow, $set->open_date(), $set->due_date()) ) { + if ( between($set->open_date(), $set->due_date(), $timeNow) ) { $versionIsOpen = 1; } else { $versionIsOpen = 0; # redundant; default is 0 @@ -899,10 +903,6 @@ # this is the actual translation of each problem. errors are stored in # @{$self->{errors}} in each case -# warn("going into gph: merged problem:\n"); -# foreach my $kk ( keys %$ProblemN ) { -# warn(" $kk => " . $ProblemN->{$kk} . "\n"); -# } my $pg = $self->getProblemHTML( $self->{effectiveUser}, $setVersionName, $formFields, $ProblemN ); push(@pg_results, $pg); @@ -972,6 +972,8 @@ my $user = $r->param('user'); my $effectiveUser = $r->param('effectiveUser'); + my $timeNow = time(); + ######################################### # preliminary error checking and output ######################################### @@ -1176,12 +1178,48 @@ join("", '|', $problems[$i]->user_id, '|', $problems[$i]->set_id, '|', $problems[$i]->problem_id, - '|', "\t", time(), "\t", + '|', "\t$timeNow\t", $answerString), ); } } } # end loop through problems + +# additional set-level submitAnswers database manipulation: this is all +# for versioned sets/gateway tests +# we want to save the time that a set was submitted, and for proctored +# tests we want to reset the assignment type after a set is submitted +# for the last time so that it's possible to look at it without getting +# proctor authorization + if ( $will{recordAnswers} || ( ! $can{recordAnswersNextTime} && + $set->assignment_type() eq 'proctored_gateway' ) ) { +# warn("in put set conditional\n"); + + my $setName = $set->set_id(); +# I started out getting the set back out of the database, but I don't think +# this is needed here. the only manipulation of the $set object is internal, +# so I think it's safe to just use the $set that we have +# # note that getMergedVersionedSet returns the requested set if the set name is +# # versioned ("name,vN"), or the latest set if no version is specified (that +# # is, it gives us the set we're working with) +# my $cleanSet = $db->getMergedVersionedSet($user,$setName); + + if ( $will{recordAnswers} ) { +# $cleanSet->version_last_attempt_time( $timeNow ); + $set->version_last_attempt_time( $timeNow ); +# warn("set last attempt time in clean set " . $set->set_id() . " to $timeNow\n"); + } + if ( ! $can{recordAnswersNextTime} && + $set->assignment_type() eq 'proctored_gateway' ) { +# $cleanSet->assignment_type( 'gateway' ); + $set->assignment_type( 'gateway' ); + } +# $db->putVersionedUserSet( $cleanSet ); + $db->putVersionedUserSet( $set ); + } + +# warn("in submitanswers conditional\n"); + } # end if submitAnswers conditional $WeBWorK::timer->continue("end answer processing") if defined( $WeBWorK::timer ); @@ -1202,10 +1240,6 @@ } } -# FIXME the following should probably be in the gateway -# template file, once that exists FIXME -# print CGI::h3("Gateway $setName"); - if ( $overallScore > -1 ) { my $divClass = ''; my $ansRecorded = 1; @@ -1234,6 +1268,8 @@ } if ( ! $can{recordAnswersNextTime} ) { +# if we can't record answers any more, then we're finished with this set +# version. print the appropriate message to that effect. print CGI::start_div({class=>"gwMessage"}); my $mesg = ( $requestedVersion ) ? '' : ", because you have used all available attempts on it or " . @@ -1245,12 +1281,12 @@ "You may, however, check your answers to see what you did" . " right or wrong."), "\n\n"; print CGI::end_div(); + } else { # FIXME: This assumes that there IS a time limit! # FIXME: We need to drop this out gracefully if there isn't! # set up a timer - my $timeNow = time(); my $timeLeft = $set->due_date() - $timeNow; # this is in seconds print CGI::start_div({class=>"gwTiming"}); print CGI::startform({-name=>"gwtimer", -method=>"POST", @@ -1275,8 +1311,16 @@ # "The current time is ", scalar(localtime())), "\n\n"; } +# this is a brutal hack to get a URL that won't require a proctor login if +# we've submitted a proctored test for the last time. above we've reset the +# assignment_type in this case, so we'll use that to decide if we should +# give a path to an unproctored test. note that this substitution leaves +# unproctored test URLs unchanged + my $action = $r->uri(); + $action =~ s/proctored_quiz_mode/quiz_mode/ + if ( $set->assignment_type() eq 'gateway' ); - print CGI::startform({-name=>"gwquiz", -method=>"POST", -action=>$r->uri}), $self->hidden_authen_fields, + print CGI::startform({-name=>"gwquiz", -method=>"POST", -action=>$action}), $self->hidden_authen_fields, $self->hidden_proctor_authen_fields; # FIXME RETURNTO @@ -1332,9 +1376,6 @@ # print CGI::start_div({class=>"problemHeader", -style=>"border: solid black 1px;"}); -# FIXME: I've coded the "ResultsWithError" style into the recordMessage strings -# FIXME: manually; this should be pushed out to the template or stylesheet. - if ($pg->{flags}->{showPartialCorrectAnswers} >= 0 && $submitAnswers) { if ( $scoreRecordedMessage[$probOrder[$i]] ne "Your score on this problem was recorded." ) { @@ -1513,11 +1554,6 @@ # FIXME I'm not sure that problem_id is what we want here FIXME my $problemNumber = $mergedProblem->problem_id; -# WARN STATEMENT HERE -# warn("merged problem:\n"); -# foreach my $kk ( keys %$mergedProblem ) { -# warn(" $kk => " . $mergedProblem->{$kk} . "\n"); -# } my $pg = WeBWorK::PG->new( $ce, @@ -1538,9 +1574,9 @@ }, ); -# FIXME is problem_id the correct thing in the following two stanzas? FIXME -# the original version had "problem number", which is what we want. I think -# problem_id will work, too +# FIXME is problem_id the correct thing in the following two stanzas? +# FIXME the original version had "problem number", which is what we want. +# FIXME I think problem_id will work, too if ($pg->{warnings} ne "") { push @{$self->{warnings}}, { set => $setVersionName, @@ -1561,8 +1597,7 @@ # the error context, not TeX code $pg->{body_text} = undef; } -# WARN STATEMENT HERE -# warn("body text = " . $pg->{body_text} . "\n"); + return $pg; } Index: LoginProctor.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/ContentGenerator/Attic/LoginProctor.pm,v retrieving revision 1.1.2.3 retrieving revision 1.1.2.4 diff -Llib/WeBWorK/ContentGenerator/LoginProctor.pm -Llib/WeBWorK/ContentGenerator/LoginProctor.pm -u -r1.1.2.3 -r1.1.2.4 --- lib/WeBWorK/ContentGenerator/LoginProctor.pm +++ lib/WeBWorK/ContentGenerator/LoginProctor.pm @@ -28,14 +28,19 @@ use warnings; use CGI qw(); use WeBWorK::Utils qw(readFile dequote); +use WeBWorK::ContentGenerator::GatewayQuiz qw(can_recordAnswers); # This content generator is NOT logged in. +# FIXME I'm not sure this is really what we want for the proctor login, +# FIXME but I also don't know what this actually does, so I'm ignoring it +# FIXME for now. sub if_loggedin { my ($self, $arg) = @_; return !$arg; } +# FIXME This needs to be updated for LoginProctor sub info { my ($self) = @_; my $r = $self->r; @@ -74,34 +79,97 @@ my $db = $r->db; my $urlpath = $r->urlpath; - # get some stuff together - my $user = $r->param("effectiveUser") || ""; + # convenient data variables + my $effectiveUser = $r->param("effectiveUser") || ""; + my $user = $r->param("user"); my $proctorUser = $r->param("proctor_user") || ""; my $key = $r->param("proctor_key"); my $passwd = $r->param("proctor_passwd") || ""; my $course = $urlpath->arg("courseID"); + my $setID = $urlpath->arg("setID"); + my $timeNow = time(); + + # data collection + my $submitAnswers = $r->param("submitAnswers"); + my $EffectiveUser = $db->getUser($effectiveUser); + my $User = $db->getUser($user); + + my $effectiveUserFullName = $EffectiveUser->first_name() . " " . + $EffectiveUser->last_name(); + + # save the userset for use below + my $UserSet; + # version_last_attempt_time conditional: if we're submitting the set + # for the last time we need to save the submission time. + if ( $submitAnswers ) { + + # getMergedVersionedSet returns either the set requested (if the setID + # is versioned, "setName,vN") or the latest set (if not). This should + # be by default the set we want. + $UserSet = $db->getMergedVersionedSet($effectiveUser, $setID); + # this should never error out, but we'll check anyway + die("Proctor login generated for grade attempt on a nonexistent " . + "set?!\n") if ( ! defined($UserSet) ); + + # we need these to get a problem from the set + my $setVersionName = ( $setID =~ /,v(\d+)$/ ) ? $setID : + $UserSet->set_id(); + $setID =~ s/,v\d+$//; + + # we only save the submission time if the attempt will be recorded, + # so we have to do some research to determine if that's the case + my $PermissionLevel = $db->getPermissionLevel($user); + my $Problem = + $db->getMergedVersionedProblem($effectiveUser, $setID, + $setVersionName, 1); + # set last_attempt_time if appropriate + if ( WeBWorK::ContentGenerator::GatewayQuiz::can_recordAnswers($self,$User, $PermissionLevel, + $EffectiveUser, $UserSet, $Problem) ) { + $UserSet->version_last_attempt_time( $timeNow ); + $db->putVersionedUserSet( $UserSet ); + } + } - my $User = $db->getUser($user); # use this for user's name - my $username = $User->first_name() . " " . $User->last_name(); - # WeBWorK::Authen::verifyProctor will set the note "authen_error" - # if invalid authentication is found. If this is done, it's a signal to - # us to yell at the user for doing that, since Authen isn't a content- - # generating module. + print CGI::p(CGI::strong("Proctor authorization required."), "\n\n"); + # WeBWorK::Authen::verifyProctor will set the note "authen_error" + # if invalid authentication is found. If this is done, it's a signal to + # us to yell at the user for doing that, since Authen isn't a content- + # generating module. if ($r->notes("authen_error")) { print CGI::div({class=>"ResultsWithError"}, CGI::p($r->notes("authen_error")) ); } - print CGI::p(CGI::strong("This assignment requires a proctor " . - "authorization to start or grade an " . - "assignment.")),"\n\n", - CGI::div({style=>"background-color:#ddddff;"}, - CGI::p("User's uniqname is: ", CGI::strong("$user"),"\n", - CGI::br(), - "User's name is: ", CGI::strong("$username"),"\n")), - "\n"; + # also print a message about submission times if we're submitting + # an answer + if ( $submitAnswers ) { + my $dueTime = $UserSet->due_date(); + my $timeLimit = $UserSet->version_time_limit(); + my ($color, $msg) = ("#ddddff", ""); + + if ( $dueTime < $timeNow ) { + $color = "#ffffaa"; + $msg = CGI::br() . "\nThe time limit on this assignment " . + "was exceeded.\nThe assignment may be checked, but " . + "the result will not be counted.\n"; + } + my $style = "background-color: $color; color: black; " . + "border: solid black 1px; padding: 2px;"; + print CGI::div({-style=>$style}, + CGI::strong("Grading assignment: ", CGI::br(), + "Submission time: ", + scalar(localtime($timeNow)), CGI::br(), + "Due: ", + scalar(localtime($dueTime)), $msg)); + } + + print CGI::div({style=>"background-color:#ddddff;"}, + CGI::p("User's uniqname is: ", + CGI::strong("$effectiveUser"),"\n", + CGI::br(),"User's name is: ", + CGI::strong("$effectiveUserFullName"),"\n")),"\n"; print CGI::startform({-method=>"POST", -action=>$r->uri}); |