Parent Directory
|
Revision Log
replaced tempdir() with makeTempDirectory() -sam
1 ################################################################################ 2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project 3 # $Id$ 4 ################################################################################ 5 6 package WeBWorK::ContentGenerator::GatewayQuiz; 7 use base qw(WeBWorK::ContentGenerator); 8 use File::Path qw(rmtree); 9 use WeBWorK::Form; 10 use WeBWorK::PG; 11 use WeBWorK::PG::IO; 12 use WeBWorK::Utils qw(writeLog encodeAnswers decodeAnswers ref2string makeTempDirectory); 13 use WeBWorK::DB::Utils qw(global2user user2global findDefaults); 14 15 =head1 NAME 16 17 WeBWorK::ContentGenerator::GatewayQuiz - display an index of the problems in a 18 problem set. (modifying this from ProblemSet.pm) 19 20 =cut 21 22 use strict; 23 use warnings; 24 use CGI qw(); 25 26 sub pre_header_initialize { 27 my ($self, $setName) = @_; 28 my $r = $self->{r}; 29 my $courseEnv = $self->{ce}; 30 my $db = $self->{db}; 31 my $userName = $r->param('user'); 32 my $effectiveUserName = $r->param('effectiveUser'); 33 my $key = $r->param('key'); 34 my $user = $db->getUser($userName); 35 my $effectiveUser = $db->getUser($effectiveUserName); 36 37 # obtain the effective user set, or if that is not yet defined obtain global set 38 my $set = $db->getMergedSet($effectiveUserName, $setName); 39 unless (defined $set) { 40 my $userSetClass = $courseEnv->{dbLayout}->{set_user}->{record}; 41 $set = global2user($userSetClass, $db->getGlobalSet($setName)); 42 $set->psvn('000'); 43 } 44 45 # FIXME obtain first problem for recording number of attempts FIXME 46 my $problem = $db->getMergedProblem($effectiveUser->user_id, $setName, 1); 47 48 my $psvn = $set->psvn(); 49 50 $self->{set} = $set; 51 $self->{problem} = $problem; 52 53 ##### get and save permission levels ##### 54 55 my $permissionLevel = $db->getPermissionLevel($userName)->permission(); 56 57 $self->{userName} = $userName; 58 $self->{user} = $user; 59 $self->{effectiveUser} = $effectiveUser; 60 $self->{permissionLevel} = $permissionLevel; 61 62 ##### form processing ##### 63 64 # set options from form fields (see comment at top of file for names) 65 my $displayMode = $r->param("displayMode") || $courseEnv->{pg}->{options}->{displayMode}; 66 my $redisplay = $r->param("redisplay"); 67 my $submitAnswers = $r->param("submitAnswers"); 68 my $checkAnswers = $r->param("checkAnswers"); 69 my $previewAnswers = $r->param("previewAnswers"); 70 71 72 # coerce form fields into CGI::Vars format 73 my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars }; 74 75 $self->{displayMode} = $displayMode; 76 $self->{redisplay} = $redisplay; 77 $self->{submitAnswers} = $submitAnswers; 78 $self->{checkAnswers} = $checkAnswers; 79 $self->{previewAnswers} = $previewAnswers; 80 $self->{formFields} = $formFields; 81 82 ##### permissions ##### 83 84 # are we allowed to view this quiz? 85 $self->{isOpen} = time >= $set->open_date || $permissionLevel > 0; 86 return unless $self->{isOpen}; 87 88 # what does the user want to do? 89 my %want = ( 90 showOldAnswers => $r->param("showOldAnswers") || $courseEnv->{pg}->{options}->{showOldAnswers}, 91 showCorrectAnswers => $r->param("showCorrectAnswers") || $courseEnv->{pg}->{options}->{showCorrectAnswers}, 92 showHints => $r->param("showHints") || $courseEnv->{pg}->{options}->{showHints}, 93 showSolutions => $r->param("showSolutions") || $courseEnv->{pg}->{options}->{showSolutions}, 94 recordAnswers => defined($submitAnswers), 95 ); 96 97 # are certain options enforced? 98 my %must = ( 99 showOldAnswers => 0, 100 showCorrectAnswers => 0, 101 showHints => 0, 102 showSolutions => 0, 103 recordAnswers => mustRecordAnswers($permissionLevel), 104 checkAnswers => 1, 105 ); 106 107 # does the user have permission to use certain options? 108 # QUIZ MAX ATTEMPTS should be set quiz wide FIXME 109 my $QUIZ_MAX_ATTEMPTS=100; 110 my %can = ( 111 showOldAnswers => 1, 112 showCorrectAnswers => canShowCorrectAnswers($permissionLevel, $set->answer_date), 113 showHints => 1, 114 showSolutions => canShowSolutions($permissionLevel, $set->answer_date), 115 recordAnswers => canRecordAnswers($permissionLevel, $set->open_date, $set->due_date, 116 $QUIZ_MAX_ATTEMPTS, $problem->num_correct + $problem->num_incorrect + 1), 117 # attempts=num_correct+num_incorrect+1, as this happens before updating $problem 118 checkAnswers => canCheckAnswers($permissionLevel, $set->answer_date), 119 ); 120 121 # final values for options 122 my %will; 123 foreach (keys %must) { 124 $will{$_} = $must{$_} || ($can{$_} && $want{$_}) ; 125 } 126 # warn "\n want"; 127 # WeBWorK::Utils::pretty_print_rh(\%want); 128 # warn "can"; 129 # WeBWorK::Utils::pretty_print_rh(\%can); 130 # warn "must"; 131 # WeBWorK::Utils::pretty_print_rh(\%must); 132 # warn "will"; 133 # WeBWorK::Utils::pretty_print_rh(\%will); 134 135 ##### store fields ##### 136 137 $self->{want} = \%want; 138 $self->{must} = \%must; 139 $self->{can} = \%can; 140 $self->{will} = \%will; 141 142 143 # 144 # #### sticky answers ##### FIXME 145 # 146 # if (not $submitAnswers and $will{showOldAnswers}) { 147 # do this only if new answers are NOT being submitted 148 # my %oldAnswers = decodeAnswers($problem->last_answer); 149 # $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers; 150 # } 151 152 ######### translate problems ############ 153 my @problemNumbers = $db->listUserProblems($effectiveUserName, $setName); 154 155 my @pg_results = (); 156 foreach my $problemNumber (sort {$a<=> $b } @problemNumbers) { 157 my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); 158 my $pg = $self->getProblemHTML($self->{effectiveUser}, $setName, $problemNumber); 159 push(@pg_results, $pg); 160 } 161 $self->{ra_pg_results}=\@pg_results; 162 163 164 } 165 sub initialize { 166 my ($self, $setName) = @_; 167 my $courseEnvironment = $self->{ce}; 168 my $r = $self->{r}; 169 my $db = $self->{db}; 170 my $userName = $r->param("user"); 171 my $effectiveUserName = $r->param("effectiveUser"); 172 173 my $user = $db->getUser($userName); 174 my $effectiveUser = $db->getUser($effectiveUserName); 175 my $set = $db->getMergedSet($effectiveUserName, $setName); 176 my $permissionLevel = $db->getPermissionLevel($userName)->permission(); 177 178 $self->{userName} = $userName; 179 $self->{user} = $user; 180 $self->{effectiveUser} = $effectiveUser; 181 $self->{set} = $set; 182 $self->{permissionLevel} = $permissionLevel; 183 184 ##### permissions ##### 185 186 $self->{isOpen} = time >= $set->open_date || $permissionLevel > 0; 187 } 188 189 sub path { 190 my ($self, $setName, $args) = @_; 191 192 my $ce = $self->{ce}; 193 my $root = $ce->{webworkURLs}->{root}; 194 my $courseName = $ce->{courseName}; 195 return $self->pathMacro($args, 196 "Home" => "$root", 197 $courseName => "$root/$courseName", 198 $setName => "", 199 ); 200 } 201 202 sub nav { 203 my ($self, $setName, $args) = @_; 204 205 my $ce = $self->{ce}; 206 my $root = $ce->{webworkURLs}->{root}; 207 my $courseName = $ce->{courseName}; 208 my @links = ("Problem Sets" , "$root/$courseName", "navUp"); 209 my $tail = ""; 210 211 return $self->navMacro($args, $tail, @links); 212 } 213 214 215 sub siblings { 216 my ($self, $setName) = @_; 217 return ""; 218 } 219 220 sub title { 221 my ($self, $setName) = @_; 222 223 return $setName; 224 } 225 226 227 228 sub body { 229 my $self = shift; 230 231 return CGI::p(CGI::font({-color=>"red"}, "This problem is not available because the problem set that contains it is not yet open.")) 232 unless $self->{isOpen}; 233 234 # unpack some useful variables 235 236 my $r = $self->{r}; 237 my $db = $self->{db}; 238 my $set = $self->{set}; 239 my $problem = $self->{problem}; 240 my $permissionLevel = $self->{permissionLevel}; 241 my $submitAnswers = $self->{submitAnswers}; 242 my $checkAnswers = $self->{checkAnswers}; 243 my $previewAnswers = $self->{previewAnswers}; 244 my %want = %{ $self->{want} }; 245 my %can = %{ $self->{can} }; 246 my %must = %{ $self->{must} }; 247 my %will = %{ $self->{will} }; 248 249 # coerce form fields into CGI::Vars format 250 251 return CGI::p(CGI::font({-color=>"red"}, "This problem set is not available because it is not yet open.")) 252 unless ($self->{isOpen}); 253 254 print CGI::h3("This is an experimental gateway quiz format"); 255 256 print "Number of attempts is ". ($problem->num_correct + $problem->num_incorrect + 1); 257 258 print 259 CGI::startform("POST", $r->uri), 260 $self->hidden_authen_fields; 261 262 #my $set = $db->getMergedSet($effectiveUserName, $setName); 263 #my @problemNumbers = $db->listUserProblems($effectiveUserName, $setName); 264 my @pg_results = @{ $self->{ra_pg_results} }; 265 my $problemNumber = 0; 266 foreach my $pg (@pg_results) { 267 $problemNumber++; 268 print CGI::p("Problem $problemNumber"); 269 # FIXME determine when to see correct answers etc. 270 print $self->attemptResults($pg, 1,1,1, 1, 1 ) if $submitAnswers or $checkAnswers; 271 print CGI::p( $pg->{body_text}); 272 print "\n\n", CGI::hr(),CGI::hr(),"\n\n"; 273 274 275 276 } 277 print CGI::p( #FIXME 278 ($will{recordAnswers}) 279 ? CGI::submit(-name=>"submitAnswers", 280 -label=>"Submit Quiz") 281 : "", 282 (not $will{recordAnswers}) 283 ? CGI::submit(-name=>"checkAnswers", 284 -label=>"Check Answers") 285 : "", 286 CGI::submit(-name=>"previewAnswers", 287 -label=>"Preview Answers"), 288 ); 289 # print CGI::end_table(); 290 291 # feedback form 292 my $ce = $self->{ce}; 293 my $root = $ce->{webworkURLs}->{root}; 294 my $courseName = $ce->{courseName}; 295 my $feedbackURL = "$root/$courseName/feedback/"; 296 print 297 CGI::startform("POST", $feedbackURL), 298 $self->hidden_authen_fields, 299 CGI::hidden("module", __PACKAGE__), 300 CGI::hidden("set", $self->{set}->set_id), 301 CGI::p({-align=>"right"}, 302 CGI::submit(-name=>"feedbackForm", -label=>"Send Feedback") 303 ), 304 CGI::endform(); 305 306 return ""; 307 } 308 309 sub viewOptions($) { 310 my $self = shift; 311 my $displayMode = $self->{displayMode}; 312 my %must = %{ $self->{must} }; 313 my %can = %{ $self->{can} }; 314 my %will = %{ $self->{will} }; 315 316 my $optionLine; 317 $can{showOldAnswers} and $optionLine .= join "", 318 "Show: ".CGI::br(), 319 CGI::checkbox( 320 -name => "showOldAnswers", 321 -checked => $will{showOldAnswers}, 322 -label => "Saved answers", 323 ), " ".CGI::br(); 324 $can{showCorrectAnswers} and $optionLine .= join "", 325 CGI::checkbox( 326 -name => "showCorrectAnswers", 327 -checked => $will{showCorrectAnswers}, 328 -label => "Correct answers", 329 ), " ".CGI::br(); 330 $can{showHints} and $optionLine .= join "", 331 CGI::checkbox( 332 -name => "showHints", 333 -checked => $will{showHints}, 334 -label => "Hints", 335 ), " ".CGI::br(); 336 $can{showSolutions} and $optionLine .= join "", 337 CGI::checkbox( 338 -name => "showSolutions", 339 -checked => $will{showSolutions}, 340 -label => "Solutions", 341 ), " ".CGI::br(); 342 $optionLine and $optionLine .= join "", CGI::br(); 343 344 return CGI::div({-style=>"border: thin groove; padding: 1ex; margin: 2ex align: left"}, 345 "View equations as: ".CGI::br(), 346 CGI::radio_group( 347 -name => "displayMode", 348 -values => ['plainText', 'formattedText', 'images'], 349 -default => $displayMode, 350 -linebreak=>'true', 351 -labels => { 352 plainText => "plain", 353 formattedText => "formatted", 354 images => "images", 355 } 356 ), CGI::br(),CGI::hr(), 357 $optionLine, 358 CGI::submit(-name=>"redisplay", -label=>"Save Options"), 359 ); 360 } 361 sub options { 362 my $self = shift; 363 return join("", 364 CGI::start_form("POST", $self->{r}->uri), 365 $self->hidden_authen_fields, 366 CGI::hr(), 367 CGI::start_div({class=>"viewOptions"}), 368 $self->viewOptions(), 369 CGI::end_div(), 370 CGI::end_form() 371 ); 372 } 373 374 375 376 ########################################################################### 377 # Evaluation utilties 378 ############################################################################ 379 sub getProblemHTML { 380 my ($self, $effectiveUser, $setName, $problemNumber, $pgFile) = @_; 381 my $r = $self->{r}; 382 my $ce = $self->{ce}; 383 my $db = $self->{db}; 384 my $key = $r->param('key'); 385 # Should we provide a default user ? I think not FIXME 386 # $effectiveUser = $self->{effectiveUser} unless defined($effectiveUser); 387 388 my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars }; 389 390 my $permissionLevel = $self->{permissionLevel}; 391 my $set = $db->getMergedSet($effectiveUser->user_id, $setName); 392 my $psvn = $set->psvn(); 393 394 # decide what to do about problem number 395 my $problem; 396 if ($problemNumber) { 397 $problem = $db->getMergedProblem($effectiveUser->user_id, $setName, $problemNumber); 398 } elsif ($pgFile) { 399 $problem = WeBWorK::DB::Record::UserProblem->new( 400 set_id => $set->set_id, 401 problem_id => 0, 402 login_id => $effectiveUser->user_id, 403 source_file => $pgFile, 404 # the rest of Problem's fields are not needed, i think 405 ); 406 } 407 408 # figure out if we're allowed to get solutions and call PG->new accordingly. 409 my $showCorrectAnswers = $self->{will}->{showCorrectAnswers}; 410 my $showHints = $self->{will}->{showHints}; 411 my $showSolutions = $self->{will}->{showSolutions}; 412 my $processAnswers = $self->{will}->{checkAnswers}; 413 414 unless ($permissionLevel > 0 or time > $set->answer_date) { 415 $showCorrectAnswers = 0; 416 $showSolutions = 0; 417 } 418 419 # FIXME WeBWorK::Utils::pretty_print_rh($formFields); 420 my $pg = WeBWorK::PG->new( 421 $ce, 422 $effectiveUser, 423 $key, 424 $set, 425 $problem, 426 $psvn, 427 $formFields, 428 { # translation options 429 displayMode => "images", 430 showHints => $showHints, 431 showSolutions => $showSolutions, 432 refreshMath2img => $showHints || $showSolutions, 433 processAnswers => 1, 434 QUIZ_PREFIX => 'Q'.sprintf("%04d",$problemNumber).'_', 435 }, 436 ); 437 438 if ($pg->{warnings} ne "") { 439 push @{$self->{warnings}}, { 440 set => $setName, 441 problem => $problemNumber, 442 message => $pg->{warnings}, 443 }; 444 } 445 446 if ($pg->{flags}->{error_flag}) { 447 push @{$self->{errors}}, { 448 set => $setName, 449 problem => $problemNumber, 450 message => $pg->{errors}, 451 context => $pg->{body_text}, 452 }; 453 # if there was an error, body_text contains 454 # the error context, not TeX code 455 $pg->{body_text} = undef; 456 } 457 458 #return '<br>hi FIXME'."effective User $effectiveUser, setName $setName, probNum $problemNumber, file: $pgFile". 459 return $pg; 460 } 461 ##### output utilities ##### 462 sub problemListRow($$$) { 463 my $self = shift; 464 my $set = shift; 465 my $problem = shift; 466 467 my $name = $problem->problem_id; 468 my $interactiveURL = "$name/?" . $self->url_authen_args; 469 my $interactive = CGI::a({-href=>$interactiveURL}, "Problem $name"); 470 my $attempts = $problem->num_correct + $problem->num_incorrect; 471 my $remaining = $problem->max_attempts < 0 472 ? "unlimited" 473 : $problem->max_attempts - $attempts; 474 my $status = sprintf("%.0f%%", $problem->status * 100); # round to whole number 475 476 return CGI::Tr(CGI::td({-nowrap=>1}, [ 477 $interactive, 478 $attempts, 479 $remaining, 480 $status, 481 ])); 482 } 483 sub nbsp { 484 my $str = shift; 485 ($str) ? $str : ' '; # returns non-breaking space for empty strings 486 } 487 sub previewAnswer($$) { 488 my ($self, $answerResult) = @_; 489 my $ce = $self->{ce}; 490 my $effectiveUser = $self->{effectiveUser}; 491 my $set = $self->{set}; 492 my $problem = $self->{problem}; 493 my $displayMode = $self->{displayMode}; 494 495 # note: right now, we have to do things completely differently when we are 496 # rendering math from INSIDE the translator and from OUTSIDE the translator. 497 # so we'll just deal with each case explicitly here. there's some code 498 # duplication that can be dealt with later by abstracting out tth/dvipng/etc. 499 500 my $tex = $answerResult->{preview_latex_string}; #FIXME 501 502 return "" if not defined($tex) or $tex eq "" ; 503 504 if ($displayMode eq "plainText") { 505 return $tex; 506 } elsif ($displayMode eq "formattedText") { 507 my $tthCommand = $ce->{externalPrograms}->{tth} 508 . " -L -f5 -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n" 509 . "\\(".$tex."\\)\n" 510 . "END_OF_INPUT\n"; 511 512 # call tth 513 my $result = `$tthCommand`; 514 if ($?) { 515 return "<b>[tth failed: $? $@]</b>"; 516 } 517 return $result; 518 } elsif ($displayMode eq "images") { 519 # how are we going to name this? 520 my $targetPathCommon = "/m2i/" 521 . $effectiveUser->user_id . "." 522 . $set->set_id . "." 523 . $problem->problem_id . "." 524 . $answerResult->{ans_name} . ".png"; 525 526 # figure out where to put things 527 my $wd = makeTempDirectory($ce->{courseDirs}->{html_temp}, "webwork-dvipng"); 528 my $latex = $ce->{externalPrograms}->{latex}; 529 my $dvipng = $ce->{externalPrograms}->{dvipng}; 530 my $targetPath = $ce->{courseDirs}->{html_temp} . $targetPathCommon; 531 # should use surePathToTmpFile, but we have to 532 # isolate it from the problem enivronment first 533 my $targetURL = $ce->{courseURLs}->{html_temp} . $targetPathCommon; 534 535 # call dvipng to generate a preview 536 dvipng($wd, $latex, $dvipng, $tex, $targetPath); 537 rmtree($wd, 0, 0); 538 if (-e $targetPath) { 539 return "<img src=\"$targetURL\" alt=\"$tex\" />"; 540 } else { 541 return "<b>[math2img failed]</b>"; 542 } 543 } 544 } 545 546 547 548 sub attemptResults($$$$$$) { 549 my $self = shift; 550 my $pg = shift; 551 my $showAttemptAnswers = shift; 552 my $showCorrectAnswers = shift; 553 my $showAttemptResults = $showAttemptAnswers && shift; 554 my $showSummary = shift; 555 my $showAttemptPreview = shift || 0; 556 my $problemResult = $pg->{result}; # the overall result of the problem 557 my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} }; 558 559 my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames; 560 561 my $header = CGI::th("Part"); 562 $header .= $showAttemptAnswers ? CGI::th("Entered") : ""; 563 $header .= $showAttemptPreview ? CGI::th("Answer Preview") : ""; 564 $header .= $showCorrectAnswers ? CGI::th("Correct") : ""; 565 $header .= $showAttemptResults ? CGI::th("Result") : ""; 566 $header .= $showMessages ? CGI::th("messages") : ""; 567 my @tableRows = ( $header ); 568 my $numCorrect; 569 foreach my $name (@answerNames) { 570 my $answerResult = $pg->{answers}->{$name}; 571 572 my $studentAnswer = $answerResult->{student_ans}; # original_student_ans 573 574 my $preview = ($showAttemptPreview 575 ? $self->previewAnswer($answerResult) 576 : ""); 577 my $correctAnswer = $answerResult->{correct_ans}; 578 my $answerScore = $answerResult->{score}; 579 my $answerMessage = $showMessages ? $answerResult->{ans_message} : ""; 580 581 $numCorrect += $answerScore > 0; 582 my $resultString = $answerScore ? "correct" : "incorrect"; 583 584 # get rid of the goofy prefix on the answer names (supposedly, the format 585 # of the answer names is changeable. this only fixes it for "AnSwEr" 586 $name =~ s/^AnSwEr//; 587 588 my $row = CGI::td($name); 589 $row .= $showAttemptAnswers ? CGI::td(nbsp($studentAnswer)) : ""; 590 $row .= $showAttemptPreview ? CGI::td(nbsp($preview)) : ""; 591 $row .= $showCorrectAnswers ? CGI::td(nbsp($correctAnswer)) : ""; 592 $row .= $showAttemptResults ? CGI::td(nbsp($resultString)) : ""; 593 $row .= $answerMessage ? CGI::td(nbsp($answerMessage)) : ""; 594 push @tableRows, $row; 595 } 596 597 my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions"; 598 my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100); 599 my $summary = "On this attempt, you answered $numCorrect out of " 600 . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent."; 601 return CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) . ($showSummary ? CGI::p($summary) : ""); 602 } 603 604 ##### logging subroutine #### 605 606 607 608 ##### permission queries ##### 609 610 # this stuff should be abstracted out into the permissions system 611 # however, the permission system only knows about things in the 612 # course environment and the username. hmmm... 613 614 # also, i should fix these so that they have a consistent calling 615 # format -- perhaps: 616 # canPERM($courseEnv, $user, $set, $problem, $permissionLevel) 617 618 sub canShowCorrectAnswers($$) { 619 my ($permissionLevel, $answerDate) = @_; 620 return $permissionLevel > 0 || time > $answerDate; 621 } 622 623 sub canShowSolutions($$) { 624 my ($permissionLevel, $answerDate) = @_; 625 return canShowCorrectAnswers($permissionLevel, $answerDate); 626 } 627 628 sub canRecordAnswers($$$$$) { 629 my ($permissionLevel, $openDate, $dueDate, $maxAttempts, $attempts) = @_; 630 my $permHigh = $permissionLevel > 0; 631 my $timeOK = time >= $openDate && time <= $dueDate; 632 my $attemptsOK = $maxAttempts == -1 || $attempts <= $maxAttempts; 633 my $recordAnswers = $permHigh || ($timeOK && $attemptsOK); 634 return $recordAnswers; 635 } 636 637 sub canCheckAnswers($$) { 638 my ($permissionLevel, $answerDate) = @_; 639 my $permHigh = $permissionLevel > 0; 640 my $timeOK = time >= $answerDate; 641 my $recordAnswers = $permHigh || $timeOK; 642 return $recordAnswers; 643 } 644 645 sub mustRecordAnswers($) { 646 my ($permissionLevel) = @_; 647 return $permissionLevel == 0; 648 } 649 650 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |