[system] / branches / rel-2-2-dev / webwork2 / lib / WeBWorK / ContentGenerator / Problem.pm Repository:
ViewVC logotype

Diff of /branches/rel-2-2-dev/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

Revision 392 Revision 755
1################################################################################
2# WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
3# $Id$
4################################################################################
5
1package WeBWorK::ContentGenerator::Problem; 6package WeBWorK::ContentGenerator::Problem;
2our @ISA = qw(WeBWorK::ContentGenerator); 7
3use lib '/Users/gage/webwork/xmlrpc/daemon'; 8=head1 NAME
4use lib '/Users/gage/webwork-modperl/lib'; 9
5use PGtranslator5; 10WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem.
11
12=cut
13
14use strict;
15use warnings;
6use WeBWorK::ContentGenerator; 16use base qw(WeBWorK::ContentGenerator);
7use Apache::Constants qw(:common); 17use CGI qw();
18use File::Temp qw(tempdir);
19use WeBWorK::Form;
20use WeBWorK::PG;
21use WeBWorK::PG::IO;
22use WeBWorK::Utils qw(writeLog encodeAnswers decodeAnswers ref2string);
8 23
9############################################################################### 24############################################################
10# Configuration 25#
26# user
27# effectiveUser
28# key
29#
30# displayMode
31# showOldAnswers
32# showCorrectAnswers
33# showHints
34# showSolutions
35#
36# AnSwEr# - answer blanks in problem
37#
38# redisplay - name of the "Redisplay Problem" button
39# submitAnswers - name of "Submit Answers" button
40#
11############################################################################### 41############################################################
12 42
13my $COURSE_SCRIPTS_DIRECTORY = '/Users/gage/webwork/system/courseScripts/'; 43sub pre_header_initialize {
14my $MACRO_DIRECTORY = '/Users/gage/webwork/courseData/templates/macro/'; 44 my ($self, $setName, $problemNumber) = @_;
15my $TEMPLATE_DIRECTORY = '/Users/gage/webwork/rochester_problib/'; 45 my $courseEnv = $self->{courseEnvironment};
16my $TEMP_URL = 'http://127.0.0.1/~gage/rochester_problibtmp/'; 46 my $r = $self->{r};
17##my $HTML_DIRECTORY = '/Users/gage/Sites/rochester_problib/' #already obtained from courseEnvironment 47 my $userName = $r->param('user');
18my $HTML_URL = 'http://127.0.0.1/~gage/rochester_problib/'; 48 my $effectiveUserName = $r->param('effectiveUser');
49
50 ##### database setup #####
51
52 my $cldb = WeBWorK::DB::Classlist->new($courseEnv);
53 my $wwdb = WeBWorK::DB::WW->new($courseEnv);
54 my $authdb = WeBWorK::DB::Auth->new($courseEnv);
55
56 my $user = $cldb->getUser($userName);
57 my $effectiveUser = $cldb->getUser($effectiveUserName);
58 my $set = $wwdb->getSet($effectiveUserName, $setName);
59 my $problem = $wwdb->getProblem($effectiveUserName, $setName, $problemNumber);
60 my $psvn = $wwdb->getPSVN($effectiveUserName, $setName);
61 my $permissionLevel = $authdb->getPermissions($userName);
62
63 $self->{cldb} = $cldb;
64 $self->{wwdb} = $wwdb;
65 $self->{authdb} = $authdb;
66
67 $self->{userName} = $userName;
68 $self->{user} = $user;
69 $self->{effectiveUser} = $effectiveUser;
70 $self->{set} = $set;
71 $self->{problem} = $problem;
72 $self->{permissionLevel} = $permissionLevel;
73
74 ##### form processing #####
75
76 # set options from form fields (see comment at top of file for names)
77 my $displayMode = $r->param("displayMode") || $courseEnv->{pg}->{options}->{displayMode};
78 my $redisplay = $r->param("redisplay");
79 my $submitAnswers = $r->param("submitAnswers");
80 my $checkAnswers = $r->param("checkAnswers");
81 my $previewAnswers = $r->param("previewAnswers");
82
83 # coerce form fields into CGI::Vars format
84 my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars };
85
86 $self->{displayMode} = $displayMode;
87 $self->{redisplay} = $redisplay;
88 $self->{submitAnswers} = $submitAnswers;
89 $self->{checkAnswers} = $checkAnswers;
90 $self->{previewAnswers} = $previewAnswers;
91 $self->{formFields} = $formFields;
92
93 ##### permissions #####
94
95 # are we allowed to view this problem?
96 $self->{isOpen} = time >= $set->open_date || $permissionLevel > 0;
97 return unless $self->{isOpen};
98
99 # what does the user want to do?
100 my %want = (
101 showOldAnswers => $r->param("showOldAnswers") || $courseEnv->{pg}->{options}->{showOldAnswers},
102 showCorrectAnswers => $r->param("showCorrectAnswers") || $courseEnv->{pg}->{options}->{showCorrectAnswers},
103 showHints => $r->param("showHints") || $courseEnv->{pg}->{options}->{showHints},
104 showSolutions => $r->param("showSolutions") || $courseEnv->{pg}->{options}->{showSolutions},
105 recordAnswers => $submitAnswers,
106 checkAnswers => $checkAnswers,
107 );
108
109 # are certain options enforced?
110 my %must = (
111 showOldAnswers => 0,
112 showCorrectAnswers => 0,
113 showHints => 0,
114 showSolutions => 0,
115 recordAnswers => mustRecordAnswers($permissionLevel),
116 checkAnswers => 0,
117 );
118
119 # does the user have permission to use certain options?
120 my %can = (
121 showOldAnswers => 1,
122 showCorrectAnswers => canShowCorrectAnswers($permissionLevel, $set->answer_date),
123 showHints => 1,
124 showSolutions => canShowSolutions($permissionLevel, $set->answer_date),
125 # attempts=num_correct+num_incorrect+1, as this happens before updating $problem
126 recordAnswers => canRecordAnswers($permissionLevel, $set->open_date, $set->due_date,
127 $problem->max_attempts, $problem->num_correct + $problem->num_incorrect + 1),
128 checkAnswers => canCheckAnswers($permissionLevel, $set->open_date,
129 $set->due_date, $set->answer_date, $problem->max_attempts,
130 $problem->num_correct + $problem->num_incorrect + 1),
131 );
132
133 # final values for options
134 my %will;
135 foreach (keys %must) {
136 $will{$_} = $can{$_} && ($want{$_} || $must{$_});
137 }
138
139 ##### sticky answers #####
140
141 if (not $submitAnswers and $will{showOldAnswers}) {
142 # do this only if new answers are NOT being submitted
143 my %oldAnswers = decodeAnswers($problem->last_answer);
144 $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers;
145 }
146
147 ##### translation #####
148
149 my $pg = WeBWorK::PG->new(
150 $courseEnv,
151 $effectiveUser,
152 $r->param('key'),
153 $set,
154 $problem,
155 $psvn,
156 $formFields,
157 { # translation options
158 displayMode => $displayMode,
159 showHints => $will{showHints},
160 showSolutions => $will{showSolutions},
161 refreshMath2img => $will{showHints} || $will{showSolutions},
162 processAnswers => 1,
163 },
164 );
165
166 ##### fix hint/solution options #####
167
168 $can{showHints} &&= $pg->{flags}->{hintExists};
169 $can{showSolutions} &&= $pg->{flags}->{solutionExists};
170
171 ##### store fields #####
172
173 $self->{want} = \%want;
174 $self->{must} = \%must;
175 $self->{can} = \%can;
176 $self->{will} = \%will;
177
178 $self->{pg} = $pg;
179}
19 180
20############################################################################### 181sub if_warnings($$) {
21# End configuration 182 my ($self, $arg) = @_;
22############################################################################### 183 return 0 unless $self->{isOpen};
184 return $self->{pg}->{warnings} ne "";
185}
186
187sub if_errors($$) {
188 my ($self, $arg) = @_;
189 return 0 unless $self->{isOpen};
190 return $self->{pg}->{flags}->{error_flag};
191}
192
193sub head {
194 my $self = shift;
195 return "" unless $self->{isOpen};
196 return $self->{pg}->{head_text} if $self->{pg}->{head_text};
197}
198
199sub path {
200 my $self = shift;
201 my $args = $_[-1];
202 my $setName = $self->{set}->id;
203 my $problemNumber = $self->{problem}->id;
204
205 my $ce = $self->{courseEnvironment};
206 my $root = $ce->{webworkURLs}->{root};
207 my $courseName = $ce->{courseName};
208 return $self->pathMacro($args,
209 "Home" => "$root",
210 $courseName => "$root/$courseName",
211 $setName => "$root/$courseName/$setName",
212 "Problem $problemNumber" => "",
213 );
214}
215
216sub siblings {
217 my $self = shift;
218 my $setName = $self->{set}->id;
219 my $problemNumber = $self->{problem}->id;
220
221 my $ce = $self->{courseEnvironment};
222 my $root = $ce->{webworkURLs}->{root};
223 my $courseName = $ce->{courseName};
224
225 print CGI::strong("Problems"), CGI::br();
226
227 my $wwdb = $self->{wwdb};
228 my $effectiveUser = $self->{r}->param("effectiveUser");
229 my @problems;
230 push @problems, $wwdb->getProblem($effectiveUser, $setName, $_)
231 foreach ($wwdb->getProblems($effectiveUser, $setName));
232 foreach my $problem (sort { $a->id <=> $b->id } @problems) {
233 print CGI::a({-href=>"$root/$courseName/$setName/".$problem->id."/?"
234 . $self->url_authen_args . "&displayMode=" . $self->{displayMode}},
235 "Problem ".$problem->id), CGI::br();
236 }
237}
238
239sub nav {
240 my $self = shift;
241 my $args = $_[-1];
242 my $setName = $self->{set}->id;
243 my $problemNumber = $self->{problem}->id;
244
245 my $ce = $self->{courseEnvironment};
246 my $root = $ce->{webworkURLs}->{root};
247 my $courseName = $ce->{courseName};
248
249 my $wwdb = $self->{wwdb};
250 my $effectiveUser = $self->{r}->param("effectiveUser");
251 my $tail = "&displayMode=".$self->{displayMode};
252
253 my @links = ("Problem List" , "$root/$courseName/$setName", "ProbList");
254
255 my $prevProblem = $wwdb->getProblem($effectiveUser, $setName, $problemNumber-1);
256 my $nextProblem = $wwdb->getProblem($effectiveUser, $setName, $problemNumber+1);
257 unshift @links, "Previous Problem" , ($prevProblem
258 ? "$root/$courseName/$setName/".$prevProblem->id
259 : "") , "Prev";
260 push @links, "Next Problem" , ($nextProblem
261 ? "$root/$courseName/$setName/".$nextProblem->id
262 : "") , "Next";
263
264 return $self->navMacro($args, $tail, @links);
265}
23 266
24sub title { 267sub title {
25 my ($self, $problem_set, $problem) = @_; 268 my $self = shift;
26 my $r = $self->{r}; 269 my $setName = $self->{set}->id;
27 my $user = $r->param('user'); 270 my $problemNumber = $self->{problem}->id;
28 return "Problem $problem of problem set $problem_set for $user"; 271
272 return "$setName : Problem $problemNumber";
29} 273}
30 274
31############################################################################### 275sub body {
32# 276 my $self = shift;
33# INITIALIZATION 277
34# 278 return CGI::p(CGI::font({-color=>"red"}, "This problem is not available because the problem set that contains it is not yet open."))
35# The following code initializes an instantiation of PGtranslator5 in the 279 unless $self->{isOpen};
36# parent process. This initialized object is then share with each of the 280
37# children forked from this parent process by the daemon. 281 # unpack some useful variables
38# 282 my $r = $self->{r};
39# As far as I can tell, the child processes don't share any variable values even 283 my $wwdb = $self->{wwdb};
40# though their namespaces are the same. 284 my $set = $self->{set};
41############################################################################### 285 my $problem = $self->{problem};
42# First some dummy values to use for testing. 286 my $permissionLevel = $self->{permissionLevel};
43# These should be available from the problemEnvironment(it might be ok to assume that PG and dangerousMacros 287 my $submitAnswers = $self->{submitAnswers};
44# live in the courseScripts (system level macros) directory. 288 my $checkAnswers = $self->{checkAnswers};
45 289 my $previewAnswers = $self->{previewAnswers};
46print STDERR "Begin intitalization\n"; 290 my %want = %{ $self->{want} };
47my $dummy_envir = { courseScriptsDirectory => $COURSE_SCRIPTS_DIRECTORY, 291 my %can = %{ $self->{can} };
48 displayMode => 'HTML_tth', 292 my %must = %{ $self->{must} };
49 macroDirectory => $MACRO_DIRECTORY, 293 my %will = %{ $self->{will} };
50 cgiURL => 'foo_cgiURL'}; 294 my $pg = $self->{pg};
51 295
52 296 ##### translation errors? #####
53my $PG_PL = "${COURSE_SCRIPTS_DIRECTORY}PG.pl"; 297
54my $DANGEROUS_MACROS_PL = "${COURSE_SCRIPTS_DIRECTORY}dangerousMacros.pl"; 298 if ($pg->{flags}->{error_flag}) {
55my @MODULE_LIST = ( "Exporter", "DynaLoader", "GD", "WWPlot", "Fun", 299 return $self->errorOutput($pg->{errors}, $pg->{body_text});
56 "Circle", "Label", "PGrandom", "Units", "Hermite", 300 }
57 "List", "Match","Multiple", "Select", "AlgParser", 301
58 "AnswerHash", "Fraction", "VectorField", "Complex1", 302 ##### answer processing #####
59 "Complex", "MatrixReal1", "Matrix","Distributions", 303
60 "Regression" 304 # if answers were submitted:
61); 305 if ($submitAnswers) {
62my @EXTRA_PACKAGES = ( "AlgParserWithImplicitExpand", "Expr", 306 # store answers in DB for sticky answers
63 "ExprWithImplicitExpand", "AnswerEvaluator", 307 my %answersToStore;
64 308 my %answerHash = %{ $pg->{answers} };
65); 309 $answersToStore{$_} = $answerHash{$_}->{original_student_ans}
66$INITIAL_MACRO_PACKAGES = <<END_OF_TEXT; 310 foreach (keys %answerHash);
67 DOCUMENT(); 311 my $answerString = encodeAnswers(%answersToStore,
68 loadMacros( 312 @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} });
69 "PGbasicmacros.pl", 313 $problem->last_answer($answerString);
70 "PGchoicemacros.pl", 314 $wwdb->setProblem($problem);
71 "PGanswermacros.pl",
72 "PGnumericalmacros.pl",
73 "PGgraphmacros.pl",
74 "PGauxiliaryFunctions.pl",
75 "PGmatrixmacros.pl",
76 "PGcomplexmacros.pl",
77 "PGstatisticsmacros.pl"
78 315
316 # store state in DB if it makes sense
317 if ($will{recordAnswers}) {
318 $problem->attempted(1);
319 $problem->status($pg->{state}->{recorded_score});
320 $problem->num_correct($pg->{state}->{num_of_correct_ans});
321 $problem->num_incorrect($pg->{state}->{num_of_incorrect_ans});
322 $wwdb->setProblem($problem);
323 # write to the transaction log, just to make sure
324 writeLog($self->{courseEnvironment}, "transaction",
325 $problem->id."\t".
326 $problem->set_id."\t".
327 $problem->login_id."\t".
328 $problem->source_file."\t".
329 $problem->value."\t".
330 $problem->max_attempts."\t".
331 $problem->problem_seed."\t".
332 $problem->status."\t".
333 $problem->attempted."\t".
334 $problem->last_answer."\t".
335 $problem->num_correct."\t".
336 $problem->num_incorrect
337 );
338 }
339 }
340
341 ##### output #####
342 print CGI::start_div({class=>"problemHeader"});
343 # attempt summary
344 if ($submitAnswers or $will{showCorrectAnswers}) {
345 # print this if user submitted answers OR requested correct answers
346 print $self->attemptResults($pg, $submitAnswers,
347 $will{showCorrectAnswers},
348 $pg->{flags}->{showPartialCorrectAnswers}, 1, 0);
349 } elsif ($checkAnswers) {
350 # print this if user previewed answers
351 print $self->attemptResults($pg, 1, 0, 1, 1, 0);
352 # show attempt answers
353 # don't show correct answers
354 # show attempt results (correctness)
355 # don't show attempt previews
356 } elsif ($previewAnswers) {
357 # print this if user previewed answers
358 print $self->attemptResults($pg, 1, 0, 0, 0, 1);
359 # show attempt answers
360 # don't show correct answers
361 # don't show attempt results (correctness)
362 # show attempt previews
363 }
364
365 print CGI::end_div();
366
367 print CGI::start_div({class=>"problem"});
368 #print CGI::hr();
369 # main form
370 print
371 CGI::startform("POST", $r->uri),
372 $self->hidden_authen_fields,
373 CGI::p($pg->{body_text}),
374 CGI::p($pg->{result}->{msg} ? CGI::b("Note: ") : "", CGI::i($pg->{result}->{msg})),
375 CGI::p(
376 ($can{recordAnswers}
377 ? CGI::submit(-name=>"submitAnswers",
378 -label=>"Submit Answers")
379 : ""),
380 ($can{checkAnswers}
381 ? CGI::submit(-name=>"checkAnswers",
382 -label=>"Check Answers")
383 : ""),
384 CGI::submit(-name=>"previewAnswers",
385 -label=>"Preview Answers"),
79 ); 386 );
387 # score summary
388 my $attempts = $problem->num_correct + $problem->num_incorrect;
389 my $attemptsNoun = $attempts != 1 ? "times" : "time";
390 my $lastScore = int ($problem->status * 100) . "%";
391 my ($attemptsLeft, $attemptsLeftNoun);
392 if ($problem->max_attempts == -1) {
393 # unlimited attempts
394 $attemptsLeft = "unlimited";
395 $attemptsLeftNoun = "attempts";
396 } else {
397 $attemptsLeft = $problem->max_attempts - $attempts;
398 $attemptsLeftNoun = $attemptsLeft == 1 ? "attempt" : "attempts";
399 }
400
401 my $setClosed = 0;
402 my $setClosedMessage;
403 if (time < $set->open_date or time > $set->due_date) {
404 $setClosed = 1;
405 $setClosedMessage = "This problem set is closed.";
406 if ($permissionLevel > 0) {
407 $setClosedMessage .= " Since you are a privileged user, additional attempts will be recorded.";
408 } else {
409 $setClosedMessage .= " Additional attempts will not be recorded.";
410 }
411 }
412 print CGI::p(
413 "You have attempted this problem $attempts $attemptsNoun.", CGI::br(),
414 $problem->attempted
415 ? "Your recorded score is $lastScore." . CGI::br()
416 : "",
417 $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining."
418 );
419
80 420
81 TEXT("Hello world"); 421 print
422 $self->viewOptions(),
423 CGI::endform();
82 424
83 ENDDOCUMENT(); 425 print CGI::end_div();
84 426 # feedback form
85END_OF_TEXT
86
87#These here documents have their drawbacks. KEEP END_OF_TEXT left justified!!!!!!
88
89###############################################################################
90# Now to define the body subroutine which does the hard work.
91###############################################################################
92
93
94#my $SOURCE1 = $INITIAL_MACRO_PACKAGES;
95
96sub body {
97 my ($self, $problem_set, $problem) = @_;
98 my $r = $self->{r};
99 my $courseEnvironment = $self->{courseEnvironment}; 427 my $ce = $self->{courseEnvironment};
100 my $user = $r->param('user'); 428 my $root = $ce->{webworkURLs}->{root};
429 my $courseName = $ce->{courseName};
430 my $feedbackURL = "$root/$courseName/feedback/";
431 print
432 CGI::startform("POST", $feedbackURL),
433 $self->hidden_authen_fields,
434 CGI::hidden("module", __PACKAGE__),
435 CGI::hidden("set", $set->id),
436 CGI::hidden("problem", $problem->id),
437 CGI::hidden("displayMode", $self->{displayMode}),
438 CGI::hidden("showOldAnswers", $will{showOldAnswers}),
439 CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),
440 CGI::hidden("showHints", $will{showHints}),
441 CGI::hidden("showSolutions", $will{showSolutions}),
442 CGI::p({-align=>"right"},
443 CGI::submit(-name=>"feedbackForm", -label=>"Send Feedback")
444 ),
445 CGI::endform();
101 446
447 # warning output
448 if ($pg->{warnings} ne "") {
449 print CGI::hr(), $self->warningOutput($pg->{warnings});
450 }
102 451
103 my $SOURCE1 = readFile('set0/prob1c.pg'); 452 # debugging stuff
104 print STDERR "SOURCEFILE: \n$SOURCE1\n\n"; 453 if (0) {
454 print
455 CGI::hr(),
456 CGI::h2("debugging information"),
457 CGI::h3("form fields"),
458 ref2string($self->{formFields}),
459 CGI::h3("user object"),
460 ref2string($self->{user}),
461 CGI::h3("set object"),
462 ref2string($set),
463 CGI::h3("problem object"),
464 ref2string($problem),
465 CGI::h3("PG object"),
466 ref2string($pg, {'WeBWorK::PG::Translator' => 1});
467 }
105 468
106 ########################################################################### 469 return "";
107 # The pg problem class should have a method for installing it's problemEnvironment 470}
108 ###########################################################################
109
110 $problemEnvir_rh = defineProblemEnvir($self);
111
112 471
113 ################################################################################## 472##### output utilities #####
114 # Prime the PGtranslator object and set it loose
115 ##################################################################################
116
117 473
118 ############################################################################### 474sub attemptResults($$$$$$) {
475 my $self = shift;
476 my $pg = shift;
477 my $showAttemptAnswers = shift;
478 my $showCorrectAnswers = shift;
479 my $showAttemptResults = $showAttemptAnswers && shift;
480 my $showSummary = shift;
481 my $showAttemptPreview = shift || 0;
482 my $problemResult = $pg->{result}; # the overall result of the problem
483 my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} };
484
485 my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames;
486
487 my $header = CGI::th("Part");
488 $header .= $showAttemptAnswers ? CGI::th("Entered") : "";
489 $header .= $showAttemptPreview ? CGI::th("Answer Preview") : "";
490 $header .= $showCorrectAnswers ? CGI::th("Correct") : "";
491 $header .= $showAttemptResults ? CGI::th("Result") : "";
492 $header .= $showMessages ? CGI::th("messages") : "";
493 my @tableRows = ( $header );
494 my $numCorrect;
495 foreach my $name (@answerNames) {
496 my $answerResult = $pg->{answers}->{$name};
497 my $studentAnswer = $answerResult->{student_ans}; # original_student_ans
498 my $preview = ($showAttemptPreview
499 ? $self->previewAnswer($answerResult)
500 : "");
501 my $correctAnswer = $answerResult->{correct_ans};
502 my $answerScore = $answerResult->{score};
503 my $answerMessage = $showMessages ? $answerResult->{ans_message} : "";
504
505 $numCorrect += $answerScore > 0;
506 my $resultString = $answerScore ? "correct" : "incorrect";
507
508 # get rid of the goofy prefix on the answer names (supposedly, the format
509 # of the answer names is changeable. this only fixes it for "AnSwEr"
510 $name =~ s/^AnSwEr//;
511
512 my $row = CGI::td($name);
513 $row .= $showAttemptAnswers ? CGI::td($studentAnswer) : "";
514 $row .= $showAttemptPreview ? CGI::td($preview) : "";
515 $row .= $showCorrectAnswers ? CGI::td($correctAnswer) : "";
516 $row .= $showAttemptResults ? CGI::td($resultString) : "";
517 $row .= $answerMessage ? CGI::td($answerMessage) : "";
518 push @tableRows, $row;
519 }
520
521 my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions";
522 my $scorePercent = int ($problemResult->{score} * 100) . "\%";
523 my $summary = "On this attempt, you answered $numCorrect out of "
524 . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent.";
525 return CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) . ($showSummary ? CGI::p($summary) : "");
526}
527
528sub viewOptions($) {
529 my $self = shift;
530 my $displayMode = $self->{displayMode};
531 my %must = %{ $self->{must} };
532 my %can = %{ $self->{can} };
533 my %will = %{ $self->{will} };
534
535 my $optionLine;
536 $can{showOldAnswers} and $optionLine .= join "",
537 "Show: &nbsp;",
538 CGI::checkbox(
539 -name => "showOldAnswers",
540 -checked => $will{showOldAnswers},
541 -label => "Saved answers",
542 ), "&nbsp;&nbsp;";
543 $can{showCorrectAnswers} and $optionLine .= join "",
544 CGI::checkbox(
545 -name => "showCorrectAnswers",
546 -checked => $will{showCorrectAnswers},
547 -label => "Correct answers",
548 ), "&nbsp;&nbsp;";
549 $can{showHints} and $optionLine .= join "",
550 CGI::checkbox(
551 -name => "showHints",
552 -checked => $will{showHints},
553 -label => "Hints",
554 ), "&nbsp;&nbsp;";
555 $can{showSolutions} and $optionLine .= join "",
556 CGI::checkbox(
557 -name => "showSolutions",
558 -checked => $will{showSolutions},
559 -label => "Solutions",
560 ), "&nbsp;&nbsp;";
561 $optionLine and $optionLine .= join "", CGI::br();
562
563 return CGI::div({-style=>"border: thin groove; padding: 1ex; margin: 2ex"},
564 "View equations as: &nbsp;",
565 CGI::radio_group(
566 -name => "displayMode",
567 -values => ['plainText', 'formattedText', 'images'],
568 -default => $displayMode,
569 -labels => {
570 plainText => "plain text",
571 formattedText => "formatted text",
572 images => "images",
119 573 }
120 ############################################################################### 574 ), CGI::br(),
121 #Create the PG translator. 575 $optionLine,
122 ############################################################################### 576 CGI::submit(-name=>"redisplay", -label=>"Redisplay Problem"),
123
124 my $pt = new PGtranslator5; #pt stands for problem translator;
125
126
127 # All of these hard coded directories need to be drawn from courseEnvironment.
128 # In addition I don't think that PGtranslator uses this stack internally yet.
129 # Passing these directories through the problemEnvironment variable is what
130 # is currently being done, but I don't think it is quite right, at least for most
131 # of them.
132
133
134 $pt ->rh_directories( { courseScriptsDirectory => $COURSE_SCRIPTS_DIRECTORY,
135 macroDirectory => $MACRO_DIRECTORY,
136 ,
137 templateDirectory => $TEMPLATE_DIRECTORY,
138 tempDirectory => $TEMP_DIRECTORY,
139 }
140 ); 577 );
141
142 ###############################################################################
143 # First we load the modules from courseScripts directory.
144 # These do the "heavy lifting" in terms of formatting, creating graphs, and
145 # performing other heavy duty algorithms.
146 #
147 ###############################################################################
148
149 $pt -> evaluate_modules( @MODULE_LIST);
150 $pt -> load_extra_packages( @EXTRA_PACKAGES );
151
152 ###############################################################################
153 # Load the environment constants. Some are used by the PGtranslator object but
154 # most of them are installed inside the Safe compartment where the problem
155 # runs.
156 ###############################################################################
157 #$pt -> environment($dummy_envir);
158 $pt -> environment($problemEnvir_rh);
159
160
161 # I've forgotten what this does exactly :-)
162 $pt->initialize();
163
164 ###############################################################################
165 # PG.pl contains the basic code which defines the problem interface, input and output.
166 # dangerousMacros.pl contains subroutines which have access to the hard drive and
167 # and the directory structure. All use of external resources by the problem is supposed
168 # to go through these subroutines. The idea is to put the potentially dangerous
169 # algorithms in on place so they can be watched closely.
170 # These two files are evaluated in the Safe compartment without any restrictions.
171 # They have full use of the perl commands.
172 ###############################################################################
173 my $loadErrors = $pt -> unrestricted_load($PG_PL );
174 print STDERR "$loadErrors\n" if ($loadErrors);
175 $loadErrors = $pt -> unrestricted_load($DANGEROUS_MACROS_PL);
176 print STDERR "$loadErrors\n" if ($loadErrors);
177
178 ###############################################################################
179 # Now set the mask to restrict the operations which can be performed within
180 # a problem or a macro file.
181 ###############################################################################
182 $pt-> set_mask();
183
184 # print "\nPG.pl: $PG_PL<br>\n";
185 # print "DANGEROUS_MACROS_PL: $DANGEROUS_MACROS_PL<br>\n";
186 # print "Print dummy environment<br>\n";
187 # print pretty_print_rh($dummy_envir), "<p>\n\n";
188
189 # Read in the source code for the problem
190
191 #$INITIAL_MACRO_PACKAGES =~ tr /\r/\n/; # change everything to unix line endings.
192 $SOURCE1 =~ tr /\r/\n/;
193 #print STDERR "Source again \n $SOURCE1";
194 $pt->source_string( $SOURCE1 );
195
196 ###############################################################################
197 # Install a safety filter for screening student answers. The default is now the blank
198 # filter since the answer evaluators do a pretty good job of recompiling and screening
199 # student's answers. Still, you could prohibit back ticks, or something of the kind.
200 ###############################################################################
201
202 $pt ->rf_safety_filter( \&safetyFilter); # install blank safety filter
203
204
205 print STDERR "New PGtranslator object inititialization completed.<br>\n";
206 ################################################################################
207 ## This ends the initialization of the PGtranslator object
208 ################################################################################
209
210
211 ################################################################################
212 # Run the problem (output the html text) but also store it within the object.
213 # The correct answers are also calculated and stored within the object
214 ################################################################################
215 $pt ->translate();
216
217 #print problem output
218 print "Problem goes here<p>\n";
219 print "Problem output <br>\n";
220 print "################################################################################<br<br>";
221 print ${$pt->r_text()};
222 print "<br><br>################################################################################<br>";
223 print "<p>End of problem output<br>";
224
225
226 #print source code
227 print "Source code<pre>\n";
228 print $SOURCE1;
229 print "</pre>End source code<p>";
230 ################################################################################
231 # The format for the output is described here. We'll need a local variable
232 # to handle the warnings. From within the problem the warning command
233 # has been slaved to the __WARNINGS__ routine which is defined in Global.
234 # We'll need to provide an alternate mechanism.
235 # The base64 encoding is only needed for xml transmission.
236 ################################################################################
237 print "################################################################################<br>";
238 print "Warnings output<br>";
239 my $WARNINGS = "Let this be a warning:";
240
241 print $WARNINGS;
242
243 ################################################################################
244 # Install the standard problem grader. See gage/xmlrpc/daemon.pm or processProblem8 for detailed
245 # code on how to choose which problem grader to install, depending on courseEnvironment and problem data.
246 # See also PG.pl which provides for problem by problem overrides.
247 ################################################################################
248
249 $pt->rf_problem_grader($pt->rf_std_problem_grader);
250
251 ################################################################################
252 # creates and stores a hash of answer results inside the object: $rh_answer_results
253 ################################################################################
254 $pt -> process_answers($rh->{envir}->{inputs_ref});
255
256
257 # THE UPDATE AND GRADING LOGIC COULD USE AN OVERHAUL. IT WAS SOMEWHAT CONSTRAINED
258 # BY LEGACY CONDITIONS IN THE ORIGINAL PROCESSPROBLEM8. IT'S NOT BAD
259 # BUT IT COULD PROBABLY BE MADE A LITTLE MORE STRAIGHT FORWARD.
260 ################################################################################
261 # updates the problem state stored by the translator object from the problemEnvironment data
262 ################################################################################
263
264 # $pt->rh_problem_state({ recorded_score => $rh->{problem_state}->{recorded_score},
265 # num_of_correct_ans => $rh->{problem_state}->{num_of_correct_ans} ,
266 # num_of_incorrect_ans => $rh->{problem_state}->{num_of_incorrect_ans}
267 # } );
268 ################################################################################
269 # grade the problem (and update the problem state again.)
270 ################################################################################
271
272 # Define an entry order -- the default is the order they are received from the browser.
273 # (Which as I understand it is NOT guaranteed to be the Left->Right Up-> Down order we're
274 # used to in the West.
275
276 my %PG_FLAGS = $pt->h_flags;
277 my $ra_answer_entry_order = ( defined($PG_FLAGS{ANSWER_ENTRY_ORDER}) ) ?
278 $PG_FLAGS{ANSWER_ENTRY_ORDER} : [ keys %{$pt->rh_evaluated_answers} ] ;
279 # Decide whether any answers were submitted.
280 my $answers_submitted = 0;
281 $answers_submitted = 1 if defined( $rh->{answer_form_submitted} ) and 1 == $rh->{answer_form_submitted};
282 # If there are answers, grade them
283 my ($rh_problem_result,$rh_problem_state) = $pt->grade_problem( answers_submitted => $answers_submitted,
284 ANSWER_ENTRY_ORDER => $ra_answer_entry_order
285 ); # grades the problem.
286
287 # Output format expected by Webwork.pm (and I believe processProblem8, but check.)
288 my $out = {
289 text => ${$pt ->r_text()}, # encode_base64( ${$pt ->r_text()} ),
290 header_text => $pt->r_header, # encode_base64( ${ $pt->r_header } ),
291 answers => $pt->rh_evaluated_answers,
292 errors => $pt-> errors(),
293 WARNINGS => $WARNINGS, #encode_base64($WARNINGS ),
294 problem_result => $rh_problem_result,
295 problem_state => $rh_problem_state,
296 PG_flag => \%PG_flag
297 };
298 ##########################################################################################
299 # Debugging printout of environment tables
300 ##########################################################################################
301
302 print "<P>Request item<P>\n\n";
303 print "<TABLE border=\"3\">";
304 print $self->print_form_data('<tr><td>','</td><td>','</td></tr>');
305 print "</table>\n";
306 print "path info <br>\n";
307 print $r->path_info();
308 print "<P>\n\ncourseEnvironment<P>\n\n";
309 print pretty_print_rh($courseEnvironment);
310 print "<P>\n\nproblemEnvironment<P>\n\n";
311 print pretty_print_rh($problemEnvir_rh);
312
313 ##########################################################################################
314 # End
315 ##########################################################################################
316 "";
317} 578}
318# End the"body" routine for the Problem object.
319 579
320 580sub previewAnswer($$) {
321 581 my ($self, $answerResult) = @_;
322 582 my $ce = $self->{courseEnvironment};
323 583 my $effectiveUser = $self->{effectiveUser};
324 584 my $set = $self->{set};
325######################################################################################## 585 my $problem = $self->{problem};
326# This is the problemEnvironment structure that needs to be filled out in order to provide 586 my $displayMode = $self->{displayMode};
327# information to PGtranslator which in turn supports the problem environment
328########################################################################################
329
330sub defineProblemEnvir {
331 my $self = shift;
332 my $r = $self->{r};
333 my $courseEnvironment = $self->{courseEnvironment};
334 my %envir=();
335# $envir{'refSubmittedAnswers'} = $refSubmittedAnswers if defined($refSubmittedAnswers);
336 $envir{'psvnNumber'} = 123456789;
337 $envir{'psvn'} = 123456789;
338 $envir{'studentName'} = 'Jane Doe';
339 $envir{'studentLogin'} = 'jd001m';
340 $envir{'studentID'} = 'xxx-xx-4321';
341 $envir{'sectionName'} = 'gage';
342 $envir{'sectionNumber'} = '111foobar';
343 $envir{'recitationName'} = 'gage_recitation';
344 $envir{'recitationNumber'} = '11_foobar recitation';
345 $envir{'setNumber'} = 'setAlgebraicGeometry';
346 $envir{'questionNumber'} = 43;
347 $envir{'probNum'} = 43;
348 $envir{'openDate'} = 3014438528;
349 $envir{'formattedOpenDate'} = '3/4/02';
350 $envir{'dueDate'} = 4014438528;
351 $envir{'formattedDueDate'} = '10/4/04';
352 $envir{'answerDate'} = 4014438528;
353 $envir{'formattedAnswerDate'} = '10/4/04';
354 $envir{'problemValue'} = 1;
355 $envir{'fileName'} = 'problem1';
356 $envir{'probFileName'} = 'problem1';
357 $envir{'languageMode'} = 'HTML_tth';
358 $envir{'displayMode'} = 'HTML_tth';
359 $envir{'outputMode'} = 'HTML_tth';
360 $envir{'courseName'} = $courseEnvironment ->{courseName};
361 $envir{'sessionKey'} = 'asdf';
362
363# initialize constants for PGanswermacros.pl
364 $envir{'numRelPercentTolDefault'} = .1;
365 $envir{'numZeroLevelDefault'} = 1E-14;
366 $envir{'numZeroLevelTolDefault'} = 1E-12;
367 $envir{'numAbsTolDefault'} = .001;
368 $envir{'numFormatDefault'} = '';
369 $envir{'functRelPercentTolDefault'} = .1;
370 $envir{'functZeroLevelDefault'} = 1E-14;
371 $envir{'functZeroLevelTolDefault'} = 1E-12;
372 $envir{'functAbsTolDefault'} = .001;
373 $envir{'functNumOfPoints'} = 3;
374 $envir{'functVarDefault'} = 'x';
375 $envir{'functLLimitDefault'} = .0000001;
376 $envir{'functULimitDefault'} = .9999999;
377 $envir{'functMaxConstantOfIntegration'} = 1E8;
378# kludge check definition of number of attempts again. The +1 is because this is used before the current answer is evaluated.
379 $envir{'numOfAttempts'} = 2; #&getProblemNumOfCorrectAns($probNum,$psvn)
380 # &getProblemNumOfIncorrectAns($probNum,$psvn)+1;
381
382#
383#
384# defining directorys and URLs
385 $envir{'templateDirectory'} = $courseEnvironment ->{courseDirs}->{templates};
386############ $envir{'classDirectory'} = $Global::classDirectory;
387# $envir{'cgiDirectory'} = $Global::cgiDirectory;
388# $envir{'cgiURL'} = getWebworkCgiURL();
389
390# $envir{'scriptDirectory'} = $Global::scriptDirectory;##omit
391 $envir{'webworkDocsURL'} = 'http://webwork.math.rochester.edu';
392 $envir{'externalTTHPath'} = '/usr/local/bin/tth';
393 587
394 588 # note: right now, we have to do things completely differently when we are
395# 589 # rendering math from INSIDE the translator and from OUTSIDE the translator.
396 $envir{'inputs_ref'} = $r->param; 590 # so we'll just deal with each case explicitly here. there's some code
397 $envir{'problemSeed'} = 3245; 591 # duplication that can be dealt with later by abstracting out tth/dvipng/etc.
398 $envir{'displaySolutionsQ'} = 1; 592
399 $envir{'displayHintsQ'} = 1; 593 my $tex = $answerResult->{preview_latex_string};
400 594
401# Directory values -- do we really need them here? 595 return "" unless $tex;
402 $envir{courseScriptsDirectory} = $COURSE_SCRIPTS_DIRECTORY; 596
403 $envir{macroDirectory} = $MACRO_DIRECTORY; 597 if ($displayMode eq "plainText") {
404 $envir{templateDirectory} = $TEMPLATE_DIRECTORY; 598 return $tex;
405 $envir{tempDirectory} = $TEMP_DIRECTORY; 599 } elsif ($displayMode eq "formattedText") {
406 $envir{tempURL} = $TEMP_URL; 600 my $tthCommand = $ce->{externalPrograms}->{tth}
407 $envir{htmlURL} = $HTML_URL; 601 . " -L -f5 -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n"
408 $envir{'htmlDirectory'} = $courseEnvironment ->{courseDirectory}->{html}; 602 . "\\(".$tex."\\)\n"
409 # here is a way to pass environment variables defined in webworkCourse.ph 603 . "END_OF_INPUT\n";
410# my $k; 604
411# foreach $k (keys %Global::PG_environment ) { 605 # call tth
412# $envir{$k} = $Global::PG_environment{$k}; 606 my $result = `$tthCommand`;
413# } 607 if ($?) {
414 \%envir; 608 return "<b>[tth failed: $? $@]</b>";
415}
416
417########################################################################################
418# This recursive pretty_print function will print a hash and its sub hashes.
419########################################################################################
420sub pretty_print_rh {
421 my $r_input = shift;
422 my $out = '';
423 if ( not ref($r_input) ) {
424 $out = $r_input; # not a reference
425 } elsif (is_hash_ref($r_input)) {
426 local($^W) = 0;
427 $out .= "<TABLE border = \"2\" cellpadding = \"3\" BGCOLOR = \"#FFFFFF\">";
428 foreach my $key (sort keys %$r_input ) {
429 $out .= "<tr><TD> $key</TD><TD>=&gt;</td><td>&nbsp;".pretty_print_rh($r_input->{$key}) . "</td></tr>";
430 } 609 }
431 $out .="</table>"; 610 return $result;
432 } elsif (is_array_ref($r_input) ) { 611 } elsif ($displayMode eq "images") {
433 my @array = @$r_input; 612 # how are we going to name this?
434 $out .= "( " ; 613 my $targetPathCommon = "/png/"
435 while (@array) { 614 . $effectiveUser->id . "."
436 $out .= pretty_print_rh(shift @array) . " , "; 615 . $set->id . "."
616 . $problem->id . "."
617 . $answerResult->{ans_name} . ".png";
618
619 # figure out where to put things
620 my $wd = tempdir("webwork-dvipng-XXXXXXXX", DIR => $ce->{courseDirs}->{html_temp});
621 my $latex = $ce->{externalPrograms}->{latex};
622 my $dvipng = $ce->{externalPrograms}->{dvipng};
623 my $targetPath = $ce->{courseDirs}->{html_temp} . $targetPathCommon;
624 # should use surePathToTmpFile, but we have to
625 # isolate it from the problem enivronment first
626 my $targetURL = $ce->{courseURLs}->{html_temp} . $targetPathCommon;
627
628 # call dvipng to generate a preview
629 warn $tex;
630 dvipng($wd, $latex, $dvipng, $tex, $targetPath);
631 if (-e $targetPath) {
632 return "<img src=\"$targetURL\" alt=\"$tex\" />";
633 } else {
634 return "<b>[math2img failed]</b>";
437 } 635 }
438 $out .= " )";
439 } elsif (ref($r_input) eq 'CODE') {
440 $out = "$r_input";
441 } else {
442 $out = $r_input;
443 }
444 $out;
445}
446
447sub is_hash_ref {
448 my $in =shift;
449 my $save_SIG_die_trap = $SIG{__DIE__};
450 $SIG{__DIE__} = sub {CORE::die(@_) };
451 my $out = eval{ %{ $in } };
452 $out = ($@ eq '') ? 1 : 0;
453 $@='';
454 $SIG{__DIE__} = $save_SIG_die_trap;
455 $out;
456}
457sub is_array_ref {
458 my $in =shift;
459 my $save_SIG_die_trap = $SIG{__DIE__};
460 $SIG{__DIE__} = sub {CORE::die(@_) };
461 my $out = eval{ @{ $in } };
462 $out = ($@ eq '') ? 1 : 0;
463 $@='';
464 $SIG{__DIE__} = $save_SIG_die_trap;
465 $out;
466}
467
468######
469# Utility for slurping souce files
470#######
471
472sub readFile {
473 my $input = shift; # The set and problem: 'set0/prob1.pg'
474 my $filePath =$TEMPLATE_DIRECTORY .$input;
475 print STDERR "Reading problem from file $filePath \n";
476 print STDERR "<br>Reading problem from file $filePath <br>\n";
477 my $out;
478 print "The file is readable = ", -r $filePath, "\n";
479 if (-r $filePath) {
480 open IN, "<$filePath" or print STDERR "Hey, this file was supposed to be readable\n";
481 local($/)=undef;
482 $out = <IN>;
483 close(IN);
484 } else {
485 print "Could not read file at |$filePath|";
486 print STDERR "Could not read file at |$filePath|";
487 }
488 return($out);
489}
490
491my $foo =0;
492
493# The warning mechanism. This needs to be turned into an object of its own
494###############
495## Error message routines cribbed from CGI
496###############
497
498BEGIN { #error message routines cribbed from CGI
499
500 my $CarpLevel = 0; # How many extra package levels to skip on carp.
501 my $MaxEvalLen = 0; # How much eval '...text...' to show. 0 = all.
502
503 sub longmess {
504 my $error = shift;
505 my $mess = "";
506 my $i = 1 + $CarpLevel;
507 my ($pack,$file,$line,$sub,$eval,$require);
508
509 while (($pack,$file,$line,$sub,undef,undef,$eval,$require) = caller($i++)) {
510 if ($error =~ m/\n$/) {
511 $mess .= $error;
512 }
513 else {
514 if (defined $eval) {
515 if ($require) {
516 $sub = "require $eval";
517 }
518 else {
519 $eval =~ s/[\\\']/\\$&/g;
520 if ($MaxEvalLen && length($eval) > $MaxEvalLen) {
521 substr($eval,$MaxEvalLen) = '...';
522 }
523 $sub = "eval '$eval'";
524 }
525 }
526 elsif ($sub eq '(eval)') {
527 $sub = 'eval {...}';
528 }
529
530 $mess .= "\t$sub " if $error eq "called";
531 $mess .= "$error at $file line $line\n";
532 }
533
534 $error = "called";
535 } 636 }
536
537 $mess || $error;
538 }
539} 637}
540###############
541### Our error messages for giving maximum feedback to the user for errors within problems.
542###############
543BEGIN {
544 sub PG_floating_point_exception_handler { # 1st argument is signal name
545 my($sig) = @_;
546 print "Content-type: text/html\n\n<H4>There was a floating point arithmetic error (exception SIG$sig )</H4>--perhaps
547 you divided by zero or took the square root of a negative number?
548 <BR>\n Use the back button to return to the previous page and recheck your entries.<BR>\n";
549 exit(0);
550 }
551
552 $SIG{'FPE'} = \&PG_floating_point_exception_handler;
553 638
554 sub PG_warnings_handler { 639##### permission queries #####
555 my @input = @_;
556 my $msg_string = longmess(@_);
557 my @msg_array = split("\n",$msg_string);
558 my $out_string = '';
559
560 # Extra stack information is provided in this next block
561 # If the warning message does NOT end in \n then a line
562 # number is appended (see Perl manual about warn function)
563 # The presence of the line number is detected below and extra
564 # stack information is added.
565 # To suppress the line number and the extra stack information
566 # add \n to the end of a warn message (in .pl files. In .pg
567 # files add ~~n instead
568
569 if ($input[$#input]=~/line \d*\.\s*$/) {
570 $out_string .= "##More details: <BR>\n----";
571 foreach my $line (@msg_array) {
572 chomp($line);
573 next unless $line =~/\w+\:\:/;
574 $out_string .= "----" .$line . "<BR>\n";
575 }
576 }
577 640
578 $Global::WARNINGS .="* " . join("<BR>",@input) . "<BR>\n" . $out_string . 641# this stuff should be abstracted out into the permissions system
579 "<BR>\n--------------------------------------<BR>\n<BR>\n"; 642# however, the permission system only knows about things in the
580 $Global::background_plain_url = $Global::background_warn_url; 643# course environment and the username. hmmm...
581 $Global::bg_color = '#FF99CC'; #for warnings -- this change may come too late
582 }
583 644
584 $SIG{__WARN__}=\&PG_warnings_handler; 645# also, i should fix these so that they have a consistent calling
585 646# format -- perhaps:
586 $SIG{__DIE__} = sub { 647# canPERM($courseEnv, $user, $set, $problem, $permissionLevel)
587 my $message = longmess(@_);
588 $message =~ s/\n/<BR>\n/;
589 my ($package, $filename, $line) = caller();
590 # use standard die for errors eminating from XML::Parser::Expat
591 # it uses a trapped eval which sometimes fails -- apparently on purpose
592 # and the error is handled by Expat itself. We don't want
593 # to interfer with that.
594
595 if ($package eq 'XML::Parser::Expat') {
596 die @_;
597 }
598 #print "$package $filename $line \n";
599 print
600 "Content-type: text/html\r\n\r\n <h4>Software error</h4> <p>\n\n$message\n<p>\n
601 Please inform the webwork meister.<p>\n
602 In addition to the error message above the following warnings were detected:
603 <HR>
604 $Global::WARNINGS;
605 <HR>
606 It's sometimes hard to tell exactly what has gone wrong since the
607 full error message may have been sent to
608 standard error instead of to standard out.
609 <p> To debug you can
610 <ul>
611 <li> guess what went wrong and try to fix it.
612 <li> call the offending script directly from the command line
613 of unix
614 <li> enable the debugging features by redefining
615 \$cgiURL in Global.pm and checking the redirection scripts in
616 system/cgi. This will force the standard error to be placed
617 in the standard out pipe as well.
618 <li> Run tail -f error_log <br>
619 from the unix command line to see error messages from the webserver.
620 The standard error output is being placed in the error_log file for the apache
621 web server. To run this command you have to be in the directory containing the
622 error_log or enter the full path name of the error_log. <p>
623 In a standard apache installation, this file is at /usr/local/apache/logs/error_log<p>
624 In a RedHat Linux installation, this file is at /var/log/httpd/error_log<p>
625 At Rochester this file is at /ww/logs/error_log.
626 </ul>
627 Good luck.<p>\n" ;
628 };
629 648
649sub canShowCorrectAnswers($$) {
650 my ($permissionLevel, $answerDate) = @_;
651 return $permissionLevel > 0 || time > $answerDate;
652}
630 653
654sub canShowSolutions($$) {
655 my ($permissionLevel, $answerDate) = @_;
656 return canShowCorrectAnswers($permissionLevel, $answerDate);
657}
631 658
659sub canRecordAnswers($$$$$) {
660 my ($permissionLevel, $openDate, $dueDate, $maxAttempts, $attempts) = @_;
661 my $permHigh = $permissionLevel > 0;
662 my $timeOK = time >= $openDate && time <= $dueDate;
663 my $attemptsOK = $maxAttempts == -1 || $attempts <= $maxAttempts;
664 my $recordAnswers = $permHigh || ($timeOK && $attemptsOK);
665 return $recordAnswers;
666}
667
668sub canCheckAnswers($$$$$) {
669 my ($permissionLevel, $openDate, $dueDate, $answerDate, $maxAttempts, $attempts) = @_;
670 return time >= $answerDate or canRecordAnswers($permissionLevel, $openDate, $dueDate, $maxAttempts, $attempts);
671}
672
673sub mustRecordAnswers($) {
674 my ($permissionLevel) = @_;
675 return $permissionLevel == 0;
632} 676}
633 677
6341; 6781;

Legend:
Removed from v.392  
changed lines
  Added in v.755

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9