[system] / branches / gage_dev / webwork2 / lib / WeBWorK / ContentGenerator / Problem.pm Repository:
ViewVC logotype

Diff of /branches/gage_dev/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm

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

Revision 2446 Revision 4158
1################################################################################ 1################################################################################
2# WeBWorK Online Homework Delivery System 2# WeBWorK Online Homework Delivery System
3# Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 3# Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
4# $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Problem.pm,v 1.152 2004/07/06 16:12:09 sh002i Exp $ 4# $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Problem.pm,v 1.200 2006/05/21 00:51:25 gage Exp $
5# 5#
6# This program is free software; you can redistribute it and/or modify it under 6# This program is free software; you can redistribute it and/or modify it under
7# the terms of either: (a) the GNU General Public License as published by the 7# the terms of either: (a) the GNU General Public License as published by the
8# Free Software Foundation; either version 2, or (at your option) any later 8# Free Software Foundation; either version 2, or (at your option) any later
9# version, or (b) the "Artistic License" which comes with this package. 9# version, or (b) the "Artistic License" which comes with this package.
25 25
26use strict; 26use strict;
27use warnings; 27use warnings;
28use CGI qw(); 28use CGI qw();
29use File::Path qw(rmtree); 29use File::Path qw(rmtree);
30use WeBWorK::Debug;
30use WeBWorK::Form; 31use WeBWorK::Form;
31use WeBWorK::PG; 32use WeBWorK::PG;
32use WeBWorK::PG::ImageGenerator; 33use WeBWorK::PG::ImageGenerator;
33use WeBWorK::PG::IO; 34use WeBWorK::PG::IO;
34use WeBWorK::Utils qw(writeLog writeCourseLog encodeAnswers decodeAnswers ref2string makeTempDirectory); 35use WeBWorK::Utils qw(readFile writeLog writeCourseLog encodeAnswers decodeAnswers ref2string makeTempDirectory path_is_subdir sortByName);
35use WeBWorK::DB::Utils qw(global2user user2global findDefaults); 36use WeBWorK::DB::Utils qw(global2user user2global findDefaults);
36use WeBWorK::Timing; 37use URI::Escape;
37 38
38use WeBWorK::Utils::Tasks qw(fake_set fake_problem); 39use WeBWorK::Utils::Tasks qw(fake_set fake_problem);
39 40
40############################################################ 41################################################################################
42# CGI param interface to this module (up-to-date as of v1.153)
43################################################################################
44
45# Standard params:
41# 46#
42# user 47# user - user ID of real user
43# effectiveUser 48# key - session key
44# key 49# effectiveUser - user ID of effective user
45# 50#
46# editMode 51# Integration with PGProblemEditor:
47# sourceFilePath - path to file to be editted
48# problemSeed - problem seed for editted problem
49#
50# displayMode - type of display (ie formatted, images, asciimath, etc)
51#
52# showOldAnswers
53# showCorrectAnswers
54# showHints
55# showSolutions
56# 52#
57# AnSwEr# - answer blanks in problem 53# editMode - if set, indicates alternate problem source location.
54# can be "temporaryFile" or "savedFile".
58# 55#
59# redisplay - name of the "Redisplay Problem" button 56# sourceFilePath - path to file to be edited
60# submitAnswers - name of "Submit Answers" button 57# problemSeed - force problem seed to value
61# checkAnswers - name of the "Check Answers" button 58# success - success message to display
62# previewAnswers - name of the "Preview Answers" button 59# failure - failure message to display
63#
64# success - success message (from PGProblemEditor)
65# failure - failure message (from PGProblemEditor)
66# 60#
61# Rendering options:
62#
63# displayMode - name of display mode to use
64#
65# showOldAnswers - request that last entered answer be shown (if allowed)
66# showCorrectAnswers - request that correct answers be shown (if allowed)
67# showHints - request that hints be shown (if allowed)
68# showSolutions - request that solutions be shown (if allowed)
69#
70# Problem interaction:
71#
72# AnSwEr# - answer blanks in problem
73#
74# redisplay - name of the "Redisplay Problem" button
75# submitAnswers - name of "Submit Answers" button
76# checkAnswers - name of the "Check Answers" button
77# previewAnswers - name of the "Preview Answers" button
78
67############################################################ 79################################################################################
80# "can" methods
81################################################################################
68 82
83# Subroutines to determine if a user "can" perform an action. Each subroutine is
84# called with the following arguments:
85#
86# ($self, $User, $EffectiveUser, $Set, $Problem)
87
88# Note that significant parts of the "can" methods are lifted into the
89# GatewayQuiz module. It isn't direct, however, because of the necessity
90# of dealing with versioning there.
91
92sub can_showOldAnswers {
93 #my ($self, $User, $EffectiveUser, $Set, $Problem) = @_;
94
95 return 1;
96}
97
98sub can_showCorrectAnswers {
99 my ($self, $User, $EffectiveUser, $Set, $Problem) = @_;
100 my $authz = $self->r->authz;
101
102 return
103 after($Set->answer_date)
104 ||
105 $authz->hasPermissions($User->user_id, "show_correct_answers_before_answer_date")
106 ;
107}
108
109sub can_showHints {
110 #my ($self, $User, $EffectiveUser, $Set, $Problem) = @_;
111
112 return 1;
113}
114
115sub can_showSolutions {
116 my ($self, $User, $EffectiveUser, $Set, $Problem) = @_;
117 my $authz = $self->r->authz;
118
119 return
120 after($Set->answer_date)
121 ||
122 $authz->hasPermissions($User->user_id, "show_solutions_before_answer_date")
123 ;
124}
125
126sub can_recordAnswers {
127 my ($self, $User, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_;
128 my $authz = $self->r->authz;
129 my $thisAttempt = $submitAnswers ? 1 : 0;
130 if ($User->user_id ne $EffectiveUser->user_id) {
131 return $authz->hasPermissions($User->user_id, "record_answers_when_acting_as_student");
132 }
133 if (before($Set->open_date)) {
134 return $authz->hasPermissions($User->user_id, "record_answers_before_open_date");
135 } elsif (between($Set->open_date, $Set->due_date)) {
136 my $max_attempts = $Problem->max_attempts;
137 my $attempts_used = $Problem->num_correct + $Problem->num_incorrect + $thisAttempt;
138 if ($max_attempts == -1 or $attempts_used < $max_attempts) {
139 return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_with_attempts");
140 } else {
141 return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_without_attempts");
142 }
143 } elsif (between($Set->due_date, $Set->answer_date)) {
144 return $authz->hasPermissions($User->user_id, "record_answers_after_due_date");
145 } elsif (after($Set->answer_date)) {
146 return $authz->hasPermissions($User->user_id, "record_answers_after_answer_date");
147 }
148}
149
150sub can_checkAnswers {
151 my ($self, $User, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_;
152 my $authz = $self->r->authz;
153 my $thisAttempt = $submitAnswers ? 1 : 0;
154
155 if (before($Set->open_date)) {
156 return $authz->hasPermissions($User->user_id, "check_answers_before_open_date");
157 } elsif (between($Set->open_date, $Set->due_date)) {
158 my $max_attempts = $Problem->max_attempts;
159 my $attempts_used = $Problem->num_correct + $Problem->num_incorrect + $thisAttempt;
160 if ($max_attempts == -1 or $attempts_used < $max_attempts) {
161 return $authz->hasPermissions($User->user_id, "check_answers_after_open_date_with_attempts");
162 } else {
163 return $authz->hasPermissions($User->user_id, "check_answers_after_open_date_without_attempts");
164 }
165 } elsif (between($Set->due_date, $Set->answer_date)) {
166 return $authz->hasPermissions($User->user_id, "check_answers_after_due_date");
167 } elsif (after($Set->answer_date)) {
168 return $authz->hasPermissions($User->user_id, "check_answers_after_answer_date");
169 }
170}
171
172# Helper functions for calculating times
173sub before { return time <= $_[0] }
174sub after { return time >= $_[0] }
175sub between { my $t = time; return $t > $_[0] && $t < $_[1] }
176
177# Reset the default in some cases
178sub set_showOldAnswers_default {
179 my ($self, $ce, $userName, $authz, $set) = @_;
180 # these people always use the system/course default, so don't
181 # override the value of ...->{showOldAnswers}
182 return if $authz->hasPermissions($userName, "can_always_use_show_old_answers_default");
183 # this person should always default to 0
184 $ce->{pg}->{options}->{showOldAnswers} = 0
185 unless ($authz->hasPermissions($userName, "can_show_old_answers_by_default"));
186 # we are after the due date, so default to not showing it
187 $ce->{pg}->{options}->{showOldAnswers} = 0 if $set->{due_date} && after($set->{due_date});
188}
189
190################################################################################
191# output utilities
192################################################################################
193
194# Note: the substance of attemptResults is lifted into GatewayQuiz.pm,
195# with some changes to the output format
196
197sub attemptResults {
198 my $self = shift;
199 my $pg = shift;
200 my $showAttemptAnswers = shift;
201 my $showCorrectAnswers = shift;
202 my $showAttemptResults = $showAttemptAnswers && shift;
203 my $showSummary = shift;
204 my $showAttemptPreview = shift || 0;
205
206 my $ce = $self->r->ce;
207
208 my $problemResult = $pg->{result}; # the overall result of the problem
209 my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} };
210
211 my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames;
212
213 my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview";
214
215 # to make grabbing these options easier, we'll pull them out now...
216 my %imagesModeOptions = %{$ce->{pg}->{displayModeOptions}->{images}};
217
218 my $imgGen = WeBWorK::PG::ImageGenerator->new(
219 tempDir => $ce->{webworkDirs}->{tmp},
220 latex => $ce->{externalPrograms}->{latex},
221 dvipng => $ce->{externalPrograms}->{dvipng},
222 useCache => 1,
223 cacheDir => $ce->{webworkDirs}->{equationCache},
224 cacheURL => $ce->{webworkURLs}->{equationCache},
225 cacheDB => $ce->{webworkFiles}->{equationCacheDB},
226 dvipng_align => $imagesModeOptions{dvipng_align},
227 dvipng_depth_db => $imagesModeOptions{dvipng_depth_db},
228 );
229
230 my $header;
231 #$header .= CGI::th("Part");
232 $header .= $showAttemptAnswers ? CGI::th("Entered") : "";
233 $header .= $showAttemptPreview ? CGI::th("Answer Preview") : "";
234 $header .= $showCorrectAnswers ? CGI::th("Correct") : "";
235 $header .= $showAttemptResults ? CGI::th("Result") : "";
236 $header .= $showMessages ? CGI::th("Messages") : "";
237 my $fully = '';
238 my @tableRows = ( $header );
239 my $numCorrect = 0;
240 my $numBlanks =0;
241 my $tthPreambleCache;
242 foreach my $name (@answerNames) {
243 my $answerResult = $pg->{answers}->{$name};
244 my $studentAnswer = $answerResult->{student_ans}; # original_student_ans
245 my $preview = ($showAttemptPreview
246 ? $self->previewAnswer($answerResult, $imgGen, \$tthPreambleCache)
247 : "");
248 my $correctAnswer = $answerResult->{correct_ans};
249 my $answerScore = $answerResult->{score};
250 my $answerMessage = $showMessages ? $answerResult->{ans_message} : "";
251 $answerMessage =~ s/\n/<BR>/g;
252 $numCorrect += $answerScore >= 1;
253 $numBlanks++ unless $studentAnswer =~/\S/ || $answerScore >= 1; # unless student answer contains entry
254 my $resultString = $answerScore >= 1 ? "correct" :
255 $answerScore > 0 ? int($answerScore*100)."% correct" :
256 "incorrect";
257 $fully = 'completely ' if $answerScore >0 and $answerScore < 1;
258
259 # get rid of the goofy prefix on the answer names (supposedly, the format
260 # of the answer names is changeable. this only fixes it for "AnSwEr"
261 #$name =~ s/^AnSwEr//;
262
263 my $row;
264 #$row .= CGI::td($name);
265 $row .= $showAttemptAnswers ? CGI::td($self->nbsp($studentAnswer)) : "";
266 $row .= $showAttemptPreview ? CGI::td($self->nbsp($preview)) : "";
267 $row .= $showCorrectAnswers ? CGI::td($self->nbsp($correctAnswer)) : "";
268 $row .= $showAttemptResults ? CGI::td($self->nbsp($resultString)) : "";
269 $row .= $showMessages ? CGI::td({-class=>"Message"},$self->nbsp($answerMessage)) : "";
270 push @tableRows, $row;
271 }
272
273 # render equation images
274 $imgGen->render(refresh => 1);
275
276# my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions";
277 my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100);
278# FIXME -- I left the old code in in case we have to back out.
279# my $summary = "On this attempt, you answered $numCorrect out of "
280# . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent.";
281 my $summary = "";
282 unless (defined($problemResult->{summary}) and $problemResult->{summary} =~ /\S/) {
283 if (scalar @answerNames == 1) { #default messages
284 if ($numCorrect == scalar @answerNames) {
285 $summary .= CGI::div({class=>"ResultsWithoutError"},"The answer above is correct.");
286 } else {
287 $summary .= CGI::div({class=>"ResultsWithError"},"The answer above is NOT ${fully}correct.");
288 }
289 } else {
290 if ($numCorrect == scalar @answerNames) {
291 $summary .= CGI::div({class=>"ResultsWithoutError"},"All of the answers above are correct.");
292 }
293 unless ($numCorrect + $numBlanks == scalar( @answerNames)) {
294 $summary .= CGI::div({class=>"ResultsWithError"},"At least one of the answers above is NOT ${fully}correct.");
295 }
296 if ($numBlanks) {
297 my $s = ($numBlanks>1)?'':'s';
298 $summary .= CGI::div({class=>"ResultsAlert"},"$numBlanks of the questions remain$s unanswered.");
299 }
300 }
301 } else {
302 $summary = $problemResult->{summary}; # summary has been defined by grader
303 }
304 return
305 CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows))
306 . ($showSummary ? CGI::p({class=>'attemptResultsSummary'},$summary) : "");
307}
308
309
310# Note: previewAnswer is lifted into GatewayQuiz.pm
311
312sub previewAnswer {
313 my ($self, $answerResult, $imgGen, $tthPreambleCache) = @_;
314 my $ce = $self->r->ce;
315 my $effectiveUser = $self->{effectiveUser};
316 my $set = $self->{set};
317 my $problem = $self->{problem};
318 my $displayMode = $self->{displayMode};
319
320 # note: right now, we have to do things completely differently when we are
321 # rendering math from INSIDE the translator and from OUTSIDE the translator.
322 # so we'll just deal with each case explicitly here. there's some code
323 # duplication that can be dealt with later by abstracting out tth/dvipng/etc.
324
325 my $tex = $answerResult->{preview_latex_string};
326
327 return "" unless defined $tex and $tex ne "";
328
329 if ($displayMode eq "plainText") {
330 return $tex;
331 } elsif ($displayMode eq "formattedText") {
332
333 # read the TTH preamble, or use the cached copy passed in from the caller
334 my $tthPreamble='';
335 if (defined $$tthPreambleCache) {
336 $tthPreamble = $$tthPreambleCache;
337 } else {
338 my $tthPreambleFile = $ce->{courseDirs}->{templates} . "/tthPreamble.tex";
339 if (-r $tthPreambleFile) {
340 $tthPreamble = readFile($tthPreambleFile);
341 # thanks to Jim Martino. each line in the definition file should end with
342 #a % to prevent adding supurious paragraphs to output:
343 $tthPreamble =~ s/(.)\n/$1%\n/g;
344 # solves the problem if the file doesn't end with a return:
345 $tthPreamble .="%\n";
346 # store preamble in cache:
347 $$tthPreambleCache = $tthPreamble;
348 } else {
349 }
350 }
351
352 # construct TTH command line
353 my $tthCommand = $ce->{externalPrograms}->{tth}
354 . " -L -f5 -u -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n"
355 . $tthPreamble . "\\[" . $tex . "\\]\n"
356 . "END_OF_INPUT\n";
357
358 # call tth
359 my $result = `$tthCommand`;
360 if ($?) {
361 return "<b>[tth failed: $? $@]</b>";
362 } else {
363 # avoid border problems in tables and remove unneeded initial <br>
364 $result =~ s/(<table [^>]*)>/$1 CLASS="ArrayLayout">/gi;
365 $result =~ s!\s*<br clear="all" />!!;
366 return $result;
367 }
368
369 } elsif ($displayMode eq "images") {
370 $imgGen->add($tex);
371 } elsif ($displayMode eq "jsMath") {
372 $tex =~ s/</&lt;/g; $tex =~ s/>/&gt;/g;
373 return '<SPAN CLASS="math">\\displaystyle{'.$tex.'}</SPAN>';
374 }
375}
376
377################################################################################
378# Template escape implementations
379################################################################################
69 380
70sub pre_header_initialize { 381sub pre_header_initialize {
71 my ($self) = @_; 382 my ($self) = @_;
72 my $r = $self->r; 383 my $r = $self->r;
73 my $ce = $r->ce; 384 my $ce = $r->ce;
78 my $setName = $urlpath->arg("setID"); 389 my $setName = $urlpath->arg("setID");
79 my $problemNumber = $r->urlpath->arg("problemID"); 390 my $problemNumber = $r->urlpath->arg("problemID");
80 my $userName = $r->param('user'); 391 my $userName = $r->param('user');
81 my $effectiveUserName = $r->param('effectiveUser'); 392 my $effectiveUserName = $r->param('effectiveUser');
82 my $key = $r->param('key'); 393 my $key = $r->param('key');
394 my $editMode = $r->param("editMode");
83 395
84 my $user = $db->getUser($userName); # checked 396 my $user = $db->getUser($userName); # checked
85 die "record for user $userName (real user) does not exist." 397 die "record for user $userName (real user) does not exist."
86 unless defined $user; 398 unless defined $user;
87 399
88 my $effectiveUser = $db->getUser($effectiveUserName); # checked 400 my $effectiveUser = $db->getUser($effectiveUserName); # checked
89 die "record for user $effectiveUserName (effective user) does not exist." 401 die "record for user $effectiveUserName (effective user) does not exist."
90 unless defined $effectiveUser; 402 unless defined $effectiveUser;
91 403
92 my $PermissionLevel = $db->getPermissionLevel($userName); # checked
93 die "permission level record for user $userName does not exist (but the user does? odd...)"
94 unless defined $PermissionLevel;
95 my $permissionLevel = $PermissionLevel->permission;
96
97 # obtain the merged set for $effectiveUser 404 # obtain the merged set for $effectiveUser
98 my $set = $db->getMergedSet($effectiveUserName, $setName); # checked 405 my $set = $db->getMergedSet($effectiveUserName, $setName); # checked
406
407 $self->set_showOldAnswers_default($ce, $userName, $authz, $set);
408
409# gateway check here: we want to be sure that someone isn't trying to take
410# a GatewayQuiz through the regular problem/homework mechanism, thereby
411# circumventing the versioning, time limits, etc.
412 if (defined $set and defined $set->assignment_type and $set->assignment_type() =~ /gateway/) {
413 unless ($editMode eq "temporaryFile" and $authz->hasPermissions($userName, "modify_student_data")) {
414 die('Invalid access attempt: the Problem ContentGenerator was called for a GatewayQuiz assignment.' );
415 }
416 }
417
418 # Database fix (in case of undefined published values)
419 # this is only necessary because some people keep holding to ww1.9 which did not have a published field
420 # make sure published is set to 0 or 1
421 if ( $set and $set->published ne "0" and $set->published ne "1") {
422 my $globalSet = $db->getGlobalSet($set->set_id);
423 $globalSet->published("1"); # defaults to published
424 $db->putGlobalSet($globalSet);
425 $set = $db->getMergedSet($effectiveUserName, $setName);
426 } else {
427 # don't do anything just yet, maybe we're a professor and we're
428 # fabricating a set or haven't assigned it to ourselves just yet
429 }
99 430
100 # obtain the merged problem for $effectiveUser 431 # obtain the merged problem for $effectiveUser
101 my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked 432 my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked
102 433
103 my $editMode = $r->param("editMode");
104
105 if ($authz->hasPermissions($userName, "modify_problem_sets")) { 434 if ($authz->hasPermissions($userName, "modify_problem_sets")) {
106 # professors are allowed to fabricate sets and problems not 435 # professors are allowed to fabricate sets and problems not
107 # assigned to them (or anyone). this allows them to use the 436 # assigned to them (or anyone). this allows them to use the
108 # editor to 437 # editor to
109 438
110 # if that is not yet defined obtain the global set, convert 439 # if a User Set does not exist for this user and this set
111 # it to a user set, and add fake user data 440 # then we check the Global Set
441 # if that does not exist we create a fake set
442 # if it does, we add fake user data
112 unless (defined $set) { 443 unless (defined $set) {
113 my $userSetClass = $db->{set_user}->{record}; 444 my $userSetClass = $db->{set_user}->{record};
114 my $globalSet = $db->getGlobalSet($setName); # checked 445 my $globalSet = $db->getGlobalSet($setName); # checked
115 # if the global set doesn't exist either, bail! 446
116 if(not defined $globalSet) { 447 if (not defined $globalSet) {
117 $set = fake_set($db); 448 $set = fake_set($db);
118 } else { 449 } else {
119 $set = global2user($userSetClass, $globalSet); 450 $set = global2user($userSetClass, $globalSet);
120 $set->psvn(0); 451 $set->psvn(0);
121
122 # FIXME: This is a temporary fix to fill in the database
123 # We want the published field to contain either 1 or 0 so if it has not been set to 0, default to 1
124 # this will fill in all the empty fields but not change anything that has been specifically set to 1 or 0
125 $globalSet->published("1") unless $globalSet->published eq "0";
126 $db->putGlobalSet($globalSet);
127 } 452 }
128 } 453 }
129 454
130 # if that is not yet defined obtain the global problem, 455 # if that is not yet defined obtain the global problem,
131 # convert it to a user problem, and add fake user data 456 # convert it to a user problem, and add fake user data
133 my $userProblemClass = $db->{problem_user}->{record}; 458 my $userProblemClass = $db->{problem_user}->{record};
134 my $globalProblem = $db->getGlobalProblem($setName, $problemNumber); # checked 459 my $globalProblem = $db->getGlobalProblem($setName, $problemNumber); # checked
135 # if the global problem doesn't exist either, bail! 460 # if the global problem doesn't exist either, bail!
136 if(not defined $globalProblem) { 461 if(not defined $globalProblem) {
137 my $sourceFilePath = $r->param("sourceFilePath"); 462 my $sourceFilePath = $r->param("sourceFilePath");
463 die "sourceFilePath is unsafe!" unless path_is_subdir($sourceFilePath, $ce->{courseDirs}->{templates}, 1); # 1==path can be relative to dir
138 # These are problems from setmaker. If declared invalid, they won't come up 464 # These are problems from setmaker. If declared invalid, they won't come up
139 $self->{invalidProblem} = $self->{invalidSet} = 1 unless defined $sourceFilePath; 465 $self->{invalidProblem} = $self->{invalidSet} = 1 unless defined $sourceFilePath;
140# die "Problem $problemNumber in set $setName does not exist" unless defined $sourceFilePath; 466# die "Problem $problemNumber in set $setName does not exist" unless defined $sourceFilePath;
141 $problem = fake_problem($db); 467 $problem = fake_problem($db);
142 $problem->problem_id(1); 468 $problem->problem_id(1);
160 # now deal with possible editor overrides: 486 # now deal with possible editor overrides:
161 487
162 # if the caller is asking to override the source file, and 488 # if the caller is asking to override the source file, and
163 # editMode calls for a temporary file, do so 489 # editMode calls for a temporary file, do so
164 my $sourceFilePath = $r->param("sourceFilePath"); 490 my $sourceFilePath = $r->param("sourceFilePath");
165 if (defined $sourceFilePath and 491 if (defined $editMode and $editMode eq "temporaryFile" and defined $sourceFilePath) {
166 (not defined $editMode or $editMode eq "temporaryFile")) { 492 die "sourceFilePath is unsafe!" unless path_is_subdir($sourceFilePath, $ce->{courseDirs}->{templates}, 1); # 1==path can be relative to dir
167 $problem->source_file($sourceFilePath); 493 $problem->source_file($sourceFilePath);
168 } 494 }
169 495
170 # if the problem does not have a source file or no source file has been passed in 496 # if the problem does not have a source file or no source file has been passed in
171 # then this is really an invalid problem (probably from a bad URL) 497 # then this is really an invalid problem (probably from a bad URL)
178 } 504 }
179 505
180 my $publishedClass = ($set->published) ? "Published" : "Unpublished"; 506 my $publishedClass = ($set->published) ? "Published" : "Unpublished";
181 my $publishedText = ($set->published) ? "visible to students." : "hidden from students."; 507 my $publishedText = ($set->published) ? "visible to students." : "hidden from students.";
182 $self->addmessage(CGI::p("This set is " . CGI::font({class=>$publishedClass}, $publishedText))); 508 $self->addmessage(CGI::p("This set is " . CGI::font({class=>$publishedClass}, $publishedText)));
183 } else {
184
185 # students can't view problems not assigned to them
186 509
510 # test for additional set validity if it's not already invalid
511 } else {
187 # A set is valid if it exists and if it is either published or the user is privileged. 512 # A set is valid if it exists and if it is either published or the user is privileged.
188 $self->{invalidSet} = ((grep /^$setName/, $db->listUserSets($effectiveUserName)) == 0)
189 || not defined $set
190 || !($set->published || $authz->hasPermissions($userName, "view_unpublished_sets")); 513 $self->{invalidSet} = !(defined $set and ($set->published || $authz->hasPermissions($userName, "view_unpublished_sets")));
191 $self->{invalidProblem} = ((grep /^$problemNumber/, $db->listUserProblems($effectiveUserName, $setName)) == 0)
192 || not defined $problem
193 || !($set->published || $authz->hasPermissions($userName, "view_unpublished_sets")); 514 $self->{invalidProblem} = !(defined $problem and ($set->published || $authz->hasPermissions($userName, "view_unpublished_sets")));
194 515
195 $self->addbadmessage(CGI::p("This problem will not count towards your grade.")) if $problem and not $problem->value and not $self->{invalidProblem}; 516 $self->addbadmessage(CGI::p("This problem will not count towards your grade.")) if $problem and not $problem->value and not $self->{invalidProblem};
196 } 517 }
197 518
198 $self->{userName} = $userName; 519 $self->{userName} = $userName;
199 $self->{effectiveUserName} = $effectiveUserName; 520 $self->{effectiveUserName} = $effectiveUserName;
200 $self->{user} = $user; 521 $self->{user} = $user;
201 $self->{effectiveUser} = $effectiveUser; 522 $self->{effectiveUser} = $effectiveUser;
202 $self->{permissionLevel} = $permissionLevel;
203 $self->{set} = $set; 523 $self->{set} = $set;
204 $self->{problem} = $problem; 524 $self->{problem} = $problem;
205 $self->{editMode} = $editMode; 525 $self->{editMode} = $editMode;
206 526
207 ##### form processing ##### 527 ##### form processing #####
221 $self->{checkAnswers} = $checkAnswers; 541 $self->{checkAnswers} = $checkAnswers;
222 $self->{previewAnswers} = $previewAnswers; 542 $self->{previewAnswers} = $previewAnswers;
223 $self->{formFields} = $formFields; 543 $self->{formFields} = $formFields;
224 544
225 # get result and send to message 545 # get result and send to message
226 my $success = $r->param("sucess"); 546 my $status_message = $r->param("status_message");
227 my $failure = $r->param("failure");
228 $self->addbadmessage(CGI::p($failure)) if $failure;
229 $self->addgoodmessage(CGI::p($success)) if $success; 547 $self->addmessage(CGI::p("$status_message")) if $status_message;
230 548
231 # now that we've set all the necessary variables quit out if the set or problem is invalid 549 # now that we've set all the necessary variables quit out if the set or problem is invalid
232 return if $self->{invalidSet} || $self->{invalidProblem}; 550 return if $self->{invalidSet} || $self->{invalidProblem};
233 551
234 ##### permissions ##### 552 ##### permissions #####
235 553
236 # are we allowed to view this problem? 554 # are we allowed to view this problem?
237 $self->{isOpen} = time >= $set->open_date || $authz->hasPermissions($userName, "view_unopened_sets"); 555 $self->{isOpen} = after($set->open_date) || $authz->hasPermissions($userName, "view_unopened_sets");
238 return unless $self->{isOpen}; 556 return unless $self->{isOpen};
239 557
240 # what does the user want to do? 558 # what does the user want to do?
559 #FIXME There is a problem with checkboxes -- if they are not checked they are invisible. Hence if the default mode in $ce is 1
560 # there is no way to override this. Probably this is ok for the last three options, but it was definitely not ok for showing
561 # saved answers which is normally on, but you want to be able to turn it off! This section should be moved to ContentGenerator
562 # so that you can set these options anywhere. We also need mechanisms for making them sticky.
563 # Note: ProblemSet and ProblemSets might set showOldAnswers to '', which
564 # needs to be treated as if it is not set.
241 my %want = ( 565 my %want = (
242 showOldAnswers => $r->param("showOldAnswers") || $ce->{pg}->{options}->{showOldAnswers}, 566 showOldAnswers => (defined($r->param("showOldAnswers")) and $r->param("showOldAnswers") ne '') ? $r->param("showOldAnswers") : $ce->{pg}->{options}->{showOldAnswers},
243 showCorrectAnswers => $r->param("showCorrectAnswers") || $ce->{pg}->{options}->{showCorrectAnswers}, 567 showCorrectAnswers => $r->param("showCorrectAnswers") || $ce->{pg}->{options}->{showCorrectAnswers},
244 showHints => $r->param("showHints") || $ce->{pg}->{options}->{showHints}, 568 showHints => $r->param("showHints") || $ce->{pg}->{options}->{showHints},
245 showSolutions => $r->param("showSolutions") || $ce->{pg}->{options}->{showSolutions}, 569 showSolutions => $r->param("showSolutions") || $ce->{pg}->{options}->{showSolutions},
246 recordAnswers => $submitAnswers, 570 recordAnswers => $submitAnswers,
247 checkAnswers => $checkAnswers, 571 checkAnswers => $checkAnswers,
572 getSubmitButton => 1,
248 ); 573 );
249 574
250 # are certain options enforced? 575 # are certain options enforced?
251 my %must = ( 576 my %must = (
252 showOldAnswers => 0, 577 showOldAnswers => 0,
253 showCorrectAnswers => 0, 578 showCorrectAnswers => 0,
254 showHints => 0, 579 showHints => 0,
255 showSolutions => 0, 580 showSolutions => 0,
256 recordAnswers => mustRecordAnswers($permissionLevel), 581 recordAnswers => ! $authz->hasPermissions($userName, "avoid_recording_answers"),
257 checkAnswers => 0, 582 checkAnswers => 0,
583 getSubmitButton => 0,
258 ); 584 );
259 585
260 # does the user have permission to use certain options? 586 # does the user have permission to use certain options?
587 my @args = ($user, $effectiveUser, $set, $problem);
261 my %can = ( 588 my %can = (
262 showOldAnswers => 1, 589 showOldAnswers => $self->can_showOldAnswers(@args),
263 showCorrectAnswers => canShowCorrectAnswers($permissionLevel, $set->answer_date), 590 showCorrectAnswers => $self->can_showCorrectAnswers(@args),
264 showHints => 1, 591 showHints => $self->can_showHints(@args),
265 showSolutions => canShowSolutions($permissionLevel, $set->answer_date), 592 showSolutions => $self->can_showSolutions(@args),
266 recordAnswers => canRecordAnswers($permissionLevel, $set->open_date, $set->due_date, 593 recordAnswers => $self->can_recordAnswers(@args, 0),
267 $problem->max_attempts, $problem->num_correct + $problem->num_incorrect + 1), 594 checkAnswers => $self->can_checkAnswers(@args, $submitAnswers),
268 # attempts=num_correct+num_incorrect+1, as this happens before updating $problem 595 getSubmitButton => $self->can_recordAnswers(@args, $submitAnswers),
269 checkAnswers => canCheckAnswers($permissionLevel, $set->due_date),
270 );
271
272 # more complicated logic for showing check answer button:
273 # checkAnswers button shows up after due date -- once a student can't record anymore
274 # checkAnswers button always shows up when an instructor or TA is acting
275 # as someone else (the $user and $effectiveUserName aren't the same).
276 $can{checkAnswers} = (
277 # $can{recordAnswers} will be false if the due date has passed OR the
278 # student has used up all of her attempts
279 ($can{checkAnswers} and not $can{recordAnswers})
280 or
281 (
282 # FIXME: this is not the right way to check for this.
283 # also, canCheckAnswers() will show this button if the permission
284 # level is positive, which is always true when an instructor is
285 # acting as a student
286 defined($userName)
287 and
288 defined($effectiveUserName)
289 and
290 ($userName ne $effectiveUserName)
291 )
292 );
293
294 # more complicated logic for showing "submit answer" button:
295 # We hide the submit answer button if someone is acting as a student
296 # This prevents errors where you accidently submit the answer for a student
297 # Not sure whether this a feature or a bug
298 $can{recordAnswers} = (
299 $can{recordAnswers}
300 and not
301 (
302 # FIXME: this is not the right way to check for this.
303 defined($userName)
304 and
305 defined($effectiveUserName)
306 and
307 ($userName ne $effectiveUserName)
308 )
309 ); 596 );
310 597
311 # final values for options 598 # final values for options
312 my %will; 599 my %will;
313 foreach (keys %must) { 600 foreach (keys %must) {
322 $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers; 609 $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers;
323 } 610 }
324 611
325 ##### translation ##### 612 ##### translation #####
326 613
327 $WeBWorK::timer->continue("begin pg processing") if defined($WeBWorK::timer); 614 debug("begin pg processing");
328 my $pg = WeBWorK::PG->new( 615 my $pg = WeBWorK::PG->new(
329 $ce, 616 $ce,
330 $effectiveUser, 617 $effectiveUser,
331 $key, 618 $key,
332 $set, 619 $set,
340 refreshMath2img => $will{showHints} || $will{showSolutions}, 627 refreshMath2img => $will{showHints} || $will{showSolutions},
341 processAnswers => 1, 628 processAnswers => 1,
342 }, 629 },
343 ); 630 );
344 631
345 $WeBWorK::timer->continue("end pg processing") if defined($WeBWorK::timer); 632 debug("end pg processing");
633
346 ##### fix hint/solution options ##### 634 ##### fix hint/solution options #####
347 635
348 $can{showHints} &&= $pg->{flags}->{hintExists} 636 $can{showHints} &&= $pg->{flags}->{hintExists}
349 &&= $pg->{flags}->{showHintLimit}<=$pg->{state}->{num_of_incorrect_ans}; 637 &&= $pg->{flags}->{showHintLimit}<=$pg->{state}->{num_of_incorrect_ans};
350 $can{showSolutions} &&= $pg->{flags}->{solutionExists}; 638 $can{showSolutions} &&= $pg->{flags}->{solutionExists};
375 return $self->{pg}->{head_text} if $self->{pg}->{head_text}; 663 return $self->{pg}->{head_text} if $self->{pg}->{head_text};
376} 664}
377 665
378sub options { 666sub options {
379 my ($self) = @_; 667 my ($self) = @_;
668 #warn "doing options in Problem";
380 669
670 # don't show options if we don't have anything to show
381 return "" if $self->{invalidProblem}; 671 return "" if $self->{invalidSet} or $self->{invalidProblem};
382 my $sourceFilePathfield = ''; 672 return "" unless $self->{isOpen};
383 if($self->r->param("sourceFilePath")) {
384 $sourceFilePathfield = CGI::hidden(-name => "sourceFilePath",
385 -value => $self->r->param("sourceFilePath"));
386 }
387 673
388 return join("", 674 my $displayMode = $self->{displayMode};
389 CGI::start_form("POST", $self->{r}->uri), 675 my %can = %{ $self->{can} };
390 $self->hidden_authen_fields, 676
391 $sourceFilePathfield, 677 my @options_to_show = "displayMode";
392 CGI::hr(), 678 push @options_to_show, "showOldAnswers" if $can{showOldAnswers};
393 CGI::start_div({class=>"viewOptions"}), 679 push @options_to_show, "showHints" if $can{showHints};
394 $self->viewOptions(), 680 push @options_to_show, "showSolutions" if $can{showSolutions};
395 CGI::end_div(), 681
396 CGI::end_form() 682 return $self->optionsMacro(
683 options_to_show => \@options_to_show,
684 extra_params => ["editMode", "sourceFilePath"],
397 ); 685 );
398} 686}
399 687
400sub siblings { 688sub siblings {
401 my ($self) = @_; 689 my ($self) = @_;
409 my $courseID = $urlpath->arg("courseID"); 697 my $courseID = $urlpath->arg("courseID");
410 my $setID = $self->{set}->set_id; 698 my $setID = $self->{set}->set_id;
411 my $eUserID = $r->param("effectiveUser"); 699 my $eUserID = $r->param("effectiveUser");
412 my @problemIDs = sort { $a <=> $b } $db->listUserProblems($eUserID, $setID); 700 my @problemIDs = sort { $a <=> $b } $db->listUserProblems($eUserID, $setID);
413 701
702 print CGI::start_div({class=>"info-box", id=>"fisheye"});
703 print CGI::h2("Problems");
414 print CGI::start_ul({class=>"LinksMenu"}); 704 #print CGI::start_ul({class=>"LinksMenu"});
415 print CGI::start_li(); 705 #print CGI::start_li();
416 print CGI::span({style=>"font-size:larger"}, "Problems"); 706 #print CGI::span({style=>"font-size:larger"}, "Problems");
417 print CGI::start_ul(); 707 print CGI::start_ul();
418 708
419 foreach my $problemID (@problemIDs) { 709 foreach my $problemID (@problemIDs) {
420 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", 710 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem",
421 courseID => $courseID, setID => $setID, problemID => $problemID); 711 courseID => $courseID, setID => $setID, problemID => $problemID);
422 print CGI::li(CGI::a({href=>$self->systemLink($problemPage, params=>{displayMode => $self->{displayMode}})}, "Problem $problemID")); 712 print CGI::li(CGI::a( {href=>$self->systemLink($problemPage,
713 params=>{ displayMode => $self->{displayMode},
714 showOldAnswers => $self->{will}->{showOldAnswers}
715 })}, "Problem $problemID")
716 );
423 } 717 }
424 718
425 print CGI::end_ul(); 719 print CGI::end_ul();
426 print CGI::end_li(); 720 #print CGI::end_li();
427 print CGI::end_ul(); 721 #print CGI::end_ul();
722 print CGI::end_div();
428 723
429 return ""; 724 return "";
430} 725}
431 726
432sub nav { 727sub nav {
470 push @links, "Next Problem", $r->location . $nextPage->path, "navNext"; 765 push @links, "Next Problem", $r->location . $nextPage->path, "navNext";
471 } else { 766 } else {
472 push @links, "Next Problem", "", "navNext"; 767 push @links, "Next Problem", "", "navNext";
473 } 768 }
474 769
475 my $tail = "&displayMode=".$self->{displayMode}; 770 my $tail = "";
771
772 $tail .= "&displayMode=".$self->{displayMode} if defined $self->{displayMode};
773 $tail .= "&showOldAnswers=".$self->{will}->{showOldAnswers}
774 if defined $self->{will}->{showOldAnswers};
476 return $self->navMacro($args, $tail, @links); 775 return $self->navMacro($args, $tail, @links);
477} 776}
478 777
479sub title { 778sub title {
480 my ($self) = @_; 779 my ($self) = @_;
481 780
482 # using the url arguments won't break if the set/problem are invalid 781 # using the url arguments won't break if the set/problem are invalid
483 my $setID = $self->r->urlpath->arg("setID"); 782 my $setID = WeBWorK::ContentGenerator::underscore2nbsp($self->r->urlpath->arg("setID"));
484 my $problemID = $self->r->urlpath->arg("problemID"); 783 my $problemID = $self->r->urlpath->arg("problemID");
485 784
486 return "$setID : $problemID"; 785 return "$setID: Problem $problemID";
487} 786}
488 787
489sub body { 788sub body {
490 my $self = shift; 789 my $self = shift;
491 my $r = $self->r; 790 my $r = $self->r;
492 my $ce = $r->ce; 791 my $ce = $r->ce;
493 my $db = $r->db; 792 my $db = $r->db;
494 my $authz = $r->authz; 793 my $authz = $r->authz;
495 my $urlpath = $r->urlpath; 794 my $urlpath = $r->urlpath;
496 my $user = $r->param('user'); 795 my $user = $r->param('user');
796 my $effectiveUser = $r->param('effectiveUser');
497 797
498 if ($self->{invalidSet}) { 798 if ($self->{invalidSet}) {
499 return CGI::div({class=>"ResultsWithError"}, 799 return CGI::div({class=>"ResultsWithError"},
500 CGI::p("The selected problem set (" . $urlpath->arg("setID") . ") is not a valid set for " . $r->param("effectiveUser") . ".")); 800 CGI::p("The selected homework set (" . $urlpath->arg("setID") . ") is not a valid set for " . $r->param("effectiveUser") . "."));
501 } 801 }
502 802
503 if ($self->{invalidProblem}) { 803 if ($self->{invalidProblem}) {
504 return CGI::div({class=>"ResultsWithError"}, 804 return CGI::div({class=>"ResultsWithError"},
505 CGI::p("The selected problem (" . $urlpath->arg("problemID") . ") is not a valid problem for set " . $self->{set}->set_id . ".")); 805 CGI::p("The selected problem (" . $urlpath->arg("problemID") . ") is not a valid problem for set " . $self->{set}->set_id . "."));
506 } 806 }
507 807
508 unless ($self->{isOpen}) { 808 unless ($self->{isOpen}) {
509 return CGI::div({class=>"ResultsWithError"}, 809 return CGI::div({class=>"ResultsWithError"},
510 CGI::p("This problem is not available because the problem set that contains it is not yet open.")); 810 CGI::p("This problem is not available because the homework set that contains it is not yet open."));
511 } 811 }
512 # unpack some useful variables 812 # unpack some useful variables
513 my $set = $self->{set}; 813 my $set = $self->{set};
514 my $problem = $self->{problem}; 814 my $problem = $self->{problem};
515 my $editMode = $self->{editMode}; 815 my $editMode = $self->{editMode};
516 my $permissionLevel = $self->{permissionLevel};
517 my $submitAnswers = $self->{submitAnswers}; 816 my $submitAnswers = $self->{submitAnswers};
518 my $checkAnswers = $self->{checkAnswers}; 817 my $checkAnswers = $self->{checkAnswers};
519 my $previewAnswers = $self->{previewAnswers}; 818 my $previewAnswers = $self->{previewAnswers};
520 my %want = %{ $self->{want} }; 819 my %want = %{ $self->{want} };
521 my %can = %{ $self->{can} }; 820 my %can = %{ $self->{can} };
523 my %will = %{ $self->{will} }; 822 my %will = %{ $self->{will} };
524 my $pg = $self->{pg}; 823 my $pg = $self->{pg};
525 824
526 my $courseName = $urlpath->arg("courseID"); 825 my $courseName = $urlpath->arg("courseID");
527 826
827 # FIXME: move editor link to top, next to problem number.
828 # format as "[edit]" like we're doing with course info file, etc.
829 # add edit link for set as well.
528 my $editorLink = ""; 830 my $editorLink = "";
529 # if we are here without a real problem set, carry that through 831 # if we are here without a real homework set, carry that through
530 my $forced_field = []; 832 my $forced_field = [];
531 $forced_field = ['sourceFilePath' => $r->param("sourceFilePath")] if 833 $forced_field = ['sourceFilePath' => $r->param("sourceFilePath")] if
532 ($set->set_id eq 'Undefined_Set'); 834 ($set->set_id eq 'Undefined_Set');
533 if ($authz->hasPermissions($user, "modify_problem_sets")) { 835 if ($authz->hasPermissions($user, "modify_problem_sets")) {
534 my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", 836 my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
535 courseID => $courseName, setID => $set->set_id, problemID => $problem->problem_id); 837 courseID => $courseName, setID => $set->set_id, problemID => $problem->problem_id);
536 my $editorURL = $self->systemLink($editorPage, params=>$forced_field); 838 my $editorURL = $self->systemLink($editorPage, params=>$forced_field);
537 $editorLink = CGI::a({href=>$editorURL}, "Edit this problem"); 839 $editorLink = CGI::p(CGI::a({href=>$editorURL,target =>'WW_Editor'}, "Edit this problem"));
538 } 840 }
539 841
540 ##### translation errors? ##### 842 ##### translation errors? #####
541 843
542 if ($pg->{flags}->{error_flag}) { 844 if ($pg->{flags}->{error_flag}) {
845 if ($authz->hasPermissions($user, "view_problem_debugging_info")) {
543 print $self->errorOutput($pg->{errors}, $pg->{body_text}); 846 print $self->errorOutput($pg->{errors}, $pg->{body_text});
847 } else {
848 print $self->errorOutput($pg->{errors}, "You do not have permission to view the details of this error.");
849 }
544 print $editorLink; 850 print $editorLink;
545 return ""; 851 return "";
546 } 852 }
547 853
548 ##### answer processing ##### 854 ##### answer processing #####
549 $WeBWorK::timer->continue("begin answer processing") if defined($WeBWorK::timer); 855 debug("begin answer processing");
550 # if answers were submitted: 856 # if answers were submitted:
551 my $scoreRecordedMessage; 857 my $scoreRecordedMessage;
552 my $pureProblem; 858 my $pureProblem;
553 if ($submitAnswers) { 859 if ($submitAnswers) {
554 # get a "pure" (unmerged) UserProblem to modify 860 # get a "pure" (unmerged) UserProblem to modify
606 $pureProblem->last_answer."\t". 912 $pureProblem->last_answer."\t".
607 $pureProblem->num_correct."\t". 913 $pureProblem->num_correct."\t".
608 $pureProblem->num_incorrect 914 $pureProblem->num_incorrect
609 ); 915 );
610 } else { 916 } else {
611 if (time < $set->open_date or time > $set->due_date) { 917 if (before($set->open_date) or after($set->due_date)) {
612 $scoreRecordedMessage = "Your score was not recorded because this problem set is closed."; 918 $scoreRecordedMessage = "Your score was not recorded because this homework set is closed.";
613 } else { 919 } else {
614 $scoreRecordedMessage = "Your score was not recorded."; 920 $scoreRecordedMessage = "Your score was not recorded.";
615 } 921 }
616 } 922 }
617 } else { 923 } else {
618 $scoreRecordedMessage = "Your score was not recorded because this problem has not been built for you."; 924 $scoreRecordedMessage = "Your score was not recorded because this problem has not been assigned to you.";
619 } 925 }
620 } 926 }
621 927
622 # logging student answers 928 # logging student answers
623 929
624 my $answer_log = $self->{ce}->{courseFiles}->{logs}->{'answer_log'}; 930 my $answer_log = $self->{ce}->{courseFiles}->{logs}->{'answer_log'};
625 if ( defined($answer_log ) and defined($pureProblem)) { 931 if ( defined($answer_log ) and defined($pureProblem)) {
626 if ($submitAnswers ) { 932 if ($submitAnswers && !$authz->hasPermissions($effectiveUser, "dont_log_past_answers")) {
627 my $answerString = ""; 933 my $answerString = ""; my $scores = "";
628 my %answerHash = %{ $pg->{answers} }; 934 my %answerHash = %{ $pg->{answers} };
629 # FIXME this is the line 552 error. make sure original student ans is defined. 935 # FIXME this is the line 552 error. make sure original student ans is defined.
630 # The fact that it is not defined is probably due to an error in some answer evaluator. 936 # The fact that it is not defined is probably due to an error in some answer evaluator.
631 # But I think it is useful to suppress this error message in the log. 937 # But I think it is useful to suppress this error message in the log.
632 foreach (sort keys %answerHash) { 938 foreach (sortByName(undef, keys %answerHash)) {
633 my $student_ans = $answerHash{$_}->{original_student_ans} ||''; 939 my $orig_ans = $answerHash{$_}->{original_student_ans};
940 my $student_ans = defined $orig_ans ? $orig_ans : '';
634 $answerString .= $student_ans."\t" 941 $answerString .= $student_ans."\t";
942 $scores .= $answerHash{$_}->{score} >= 1 ? "1" : "0";
635 } 943 }
636 $answerString = '' unless defined($answerString); # insure string is defined. 944 $answerString = '' unless defined($answerString); # insure string is defined.
637 writeCourseLog($self->{ce}, "answer_log", 945 writeCourseLog($self->{ce}, "answer_log",
638 join("", 946 join("",
639 '|', $problem->user_id, 947 '|', $problem->user_id,
640 '|', $problem->set_id, 948 '|', $problem->set_id,
641 '|', $problem->problem_id, 949 '|', $problem->problem_id,
642 '|',"\t", 950 '|', $scores, "\t",
643 time(),"\t", 951 time(),"\t",
644 $answerString, 952 $answerString,
645 ), 953 ),
646 ); 954 );
647 955
648 } 956 }
649 } 957 }
650 958
651 $WeBWorK::timer->continue("end answer processing") if defined($WeBWorK::timer); 959 debug("end answer processing");
652 960
653 ##### output ##### 961 ##### output #####
654
655 print CGI::start_div({class=>"problemHeader"});
656
657 # custom message for editor 962 # custom message for editor
658 if ($authz->hasPermissions($user, "modify_problem_sets") and defined $editMode) { 963 if ($authz->hasPermissions($user, "modify_problem_sets") and defined $editMode) {
659 if ($editMode eq "temporaryFile") { 964 if ($editMode eq "temporaryFile") {
660 print CGI::p(CGI::i("Editing temporary file: ", $problem->source_file)); 965 print CGI::p(CGI::div({class=>'temporaryFile'}, "Viewing temporary file: ", $problem->source_file));
661 } elsif ($editMode eq "savedFile") { 966 } elsif ($editMode eq "savedFile") {
662 # taken care of in the initialization phase 967 # taken care of in the initialization phase
663 } 968 }
664 } 969 }
970 print CGI::start_div({class=>"problemHeader"});
971
972
665 973
666 # attempt summary 974 # attempt summary
667 #FIXME -- the following is a kludge: if showPartialCorrectAnswers is negative don't show anything. 975 #FIXME -- the following is a kludge: if showPartialCorrectAnswers is negative don't show anything.
668 # until after the due date 976 # until after the due date
669 # do I need to check $will{showCorrectAnswers} to make preflight work?? 977 # do I need to check $will{showCorrectAnswers} to make preflight work??
670 if (($pg->{flags}->{showPartialCorrectAnswers}>= 0 and $submitAnswers) ) { 978 if (($pg->{flags}->{showPartialCorrectAnswers} >= 0 and $submitAnswers) ) {
671 # print this if user submitted answers OR requested correct answers 979 # print this if user submitted answers OR requested correct answers
672 980
673 print $self->attemptResults($pg, 1, 981 print $self->attemptResults($pg, 1,
674 $will{showCorrectAnswers}, 982 $will{showCorrectAnswers},
675 $pg->{flags}->{showPartialCorrectAnswers}, 1, 1); 983 $pg->{flags}->{showPartialCorrectAnswers}, 1, 1);
676 } elsif ($checkAnswers) { 984 } elsif ($checkAnswers) {
677 # print this if user previewed answers 985 # print this if user previewed answers
678 print CGI::div({class=>'ResultsWithError'},"ANSWERS ONLY CHECKED -- ",CGI::br(),"ANSWERS NOT RECORDED", CGI::br() ); 986 print CGI::div({class=>'ResultsWithError'},"ANSWERS ONLY CHECKED -- ANSWERS NOT RECORDED"), CGI::br();
679 print $self->attemptResults($pg, 1, $will{showCorrectAnswers}, 1, 1, 1); 987 print $self->attemptResults($pg, 1, $will{showCorrectAnswers}, 1, 1, 1);
680 # show attempt answers 988 # show attempt answers
681 # show correct answers if asked 989 # show correct answers if asked
682 # show attempt results (correctness) 990 # show attempt results (correctness)
683 # show attempt previews 991 # show attempt previews
684 } elsif ($previewAnswers) { 992 } elsif ($previewAnswers) {
685 # print this if user previewed answers 993 # print this if user previewed answers
686 print CGI::div({class=>'ResultsWithError'},"PREVIEW ONLY -- NOT RECORDED"),CGI::br(),$self->attemptResults($pg, 1, 0, 0, 0, 1); 994 print CGI::div({class=>'ResultsWithError'},"PREVIEW ONLY -- ANSWERS NOT RECORDED"),CGI::br(),$self->attemptResults($pg, 1, 0, 0, 0, 1);
687 # show attempt answers 995 # show attempt answers
688 # don't show correct answers 996 # don't show correct answers
689 # don't show attempt results (correctness) 997 # don't show attempt results (correctness)
690 # show attempt previews 998 # show attempt previews
691 } 999 }
692 1000
693 print CGI::end_div(); 1001 print CGI::end_div();
694 1002
1003 # main form
1004 print "\n";
1005 print CGI::start_form(-method=>"POST", -action=> $r->uri,-name=>"problemMainForm", onSubmit=>"submitAction()");
1006 print $self->hidden_authen_fields;
1007 print "\n";
695 print CGI::start_div({class=>"problem"}); 1008 print CGI::start_div({class=>"problem"});
696
697 # main form
698 print
699 CGI::startform("POST", $r->uri),
700 $self->hidden_authen_fields,
701 CGI::p($pg->{body_text}), 1009 print CGI::p($pg->{body_text});
702 CGI::p($pg->{result}->{msg} ? CGI::b("Note: ") : "", CGI::i($pg->{result}->{msg})), 1010 print CGI::p(CGI::b("Note: "), CGI::i($pg->{result}->{msg})) if $pg->{result}->{msg};
703 CGI::p( 1011 print $editorLink; # this is empty unless it is appropriate to have an editor link.
1012 print CGI::end_div();
1013
1014 print CGI::start_p();
1015
704 ($can{showCorrectAnswers} 1016 if ($can{showCorrectAnswers}) {
705 ? CGI::checkbox( 1017 print CGI::checkbox(
706 -name => "showCorrectAnswers", 1018 -name => "showCorrectAnswers",
707 -checked => $will{showCorrectAnswers}, 1019 -checked => $will{showCorrectAnswers},
708 -label => "Show correct answers", 1020 -label => "Show correct answers",
709 ) ." "
710 : "" ),
711 ($can{showHints}
712 ? '<div style="color:red">'. CGI::checkbox(
713 -name => "showHints",
714 -checked => $will{showHints},
715 -label => "Show Hints",
716 ) . "</div> "
717 : " " ),
718 ($can{showSolutions}
719 ? CGI::checkbox(
720 -name => "showSolutions",
721 -checked => $will{showSolutions},
722 -label => "Show Solutions",
723 ) . " "
724 : " " ),CGI::br(),
725 CGI::submit(-name=>"previewAnswers",
726 -label=>"Preview Answers"),
727 ($can{recordAnswers}
728 ? CGI::submit(-name=>"submitAnswers",
729 -label=>"Submit Answers")
730 : ""),
731 ( $can{checkAnswers}
732 ? CGI::submit(-name=>"checkAnswers",
733 -label=>"Check Answers")
734 : ""),
735 ); 1021 );
1022 }
1023 if ($can{showHints}) {
1024 print CGI::div({style=>"color:red"},
1025 CGI::checkbox(
1026 -name => "showHints",
1027 -checked => $will{showHints},
1028 -label => "Show Hints",
1029 )
1030 );
1031 }
1032 if ($can{showSolutions}) {
1033 print CGI::checkbox(
1034 -name => "showSolutions",
1035 -checked => $will{showSolutions},
1036 -label => "Show Solutions",
1037 );
1038 }
1039
1040 if ($can{showCorrectAnswers} or $can{showHints} or $can{showSolutions}) {
1041 print CGI::br();
1042 }
1043
1044 print CGI::submit(-name=>"previewAnswers", -label=>"Preview Answers");
1045 if ($can{checkAnswers}) {
1046 print CGI::submit(-name=>"checkAnswers", -label=>"Check Answers");
1047 }
1048 if ($can{getSubmitButton}) {
1049 if ($user ne $effectiveUser) {
1050 # if acting as a student, make it clear that answer submissions will
1051 # apply to the student's records, not the professor's.
1052 print CGI::submit(-name=>"submitAnswers", -label=>"Submit Answers for $effectiveUser");
1053 } else {
1054 print CGI::submit(-name=>"submitAnswers", -label=>"Submit Answers");
1055 }
1056 }
1057
736 print CGI::end_div(); 1058 print CGI::end_p();
737 1059
738 print CGI::start_div({class=>"scoreSummary"}); 1060 print CGI::start_div({class=>"scoreSummary"});
739 1061
740 # score summary 1062 # score summary
741 my $attempts = $problem->num_correct + $problem->num_incorrect; 1063 my $attempts = $problem->num_correct + $problem->num_incorrect;
742 my $attemptsNoun = $attempts != 1 ? "times" : "time"; 1064 my $attemptsNoun = $attempts != 1 ? "times" : "time";
1065 my $problem_status = $problem->status || 0;
743 my $lastScore = sprintf("%.0f%%", $problem->status * 100); # Round to whole number 1066 my $lastScore = sprintf("%.0f%%", $problem_status * 100); # Round to whole number
744 my ($attemptsLeft, $attemptsLeftNoun); 1067 my ($attemptsLeft, $attemptsLeftNoun);
745 if ($problem->max_attempts == -1) { 1068 if ($problem->max_attempts == -1) {
746 # unlimited attempts 1069 # unlimited attempts
747 $attemptsLeft = "unlimited"; 1070 $attemptsLeft = "unlimited";
748 $attemptsLeftNoun = "attempts"; 1071 $attemptsLeftNoun = "attempts";
751 $attemptsLeftNoun = $attemptsLeft == 1 ? "attempt" : "attempts"; 1074 $attemptsLeftNoun = $attemptsLeft == 1 ? "attempt" : "attempts";
752 } 1075 }
753 1076
754 my $setClosed = 0; 1077 my $setClosed = 0;
755 my $setClosedMessage; 1078 my $setClosedMessage;
756 if (time < $set->open_date or time > $set->due_date) { 1079 if (before($set->open_date) or after($set->due_date)) {
757 $setClosed = 1; 1080 $setClosed = 1;
1081 if (before($set->open_date)) {
1082 $setClosedMessage = "This homework set is not yet open.";
1083 } elsif (after($set->due_date)) {
758 $setClosedMessage = "This problem set is closed."; 1084 $setClosedMessage = "This homework set is closed.";
1085 }
1086 }
1087 #if (before($set->open_date) or after($set->due_date)) {
1088 # $setClosed = 1;
1089 # $setClosedMessage = "This homework set is closed.";
759 if ($authz->hasPermissions($user, "view_answers")) { 1090 # if ($authz->hasPermissions($user, "view_answers")) {
760 $setClosedMessage .= " However, since you are a privileged user, additional attempts will be recorded."; 1091 # $setClosedMessage .= " However, since you are a privileged user, additional attempts will be recorded.";
761 } else { 1092 # } else {
762 $setClosedMessage .= " Additional attempts will not be recorded."; 1093 # $setClosedMessage .= " Additional attempts will not be recorded.";
763 } 1094 # }
764 } 1095 #}
765 1096 unless (defined( $pg->{state}->{state_summary_msg}) and $pg->{state}->{state_summary_msg}=~/\S/) {
766 my $notCountedMessage = ($problem->value) ? "" : "(This problem will not count towards your grade.)"; 1097 my $notCountedMessage = ($problem->value) ? "" : "(This problem will not count towards your grade.)";
767 print CGI::p( 1098 print CGI::p(
768 $submitAnswers ? $scoreRecordedMessage . CGI::br() : "", 1099 $submitAnswers ? $scoreRecordedMessage . CGI::br() : "",
769 "You have attempted this problem $attempts $attemptsNoun.", CGI::br(), 1100 "You have attempted this problem $attempts $attemptsNoun.", CGI::br(),
1101 $submitAnswers ?"You received a score of ".sprintf("%.0f%%", $pg->{result}->{score} * 100)." for this attempt.".CGI::br():'',
770 $problem->attempted 1102 $problem->attempted
771 ? "Your recorded score is $lastScore. $notCountedMessage" . CGI::br() 1103 ? "Your overall recorded score is $lastScore. $notCountedMessage" . CGI::br()
772 : "", 1104 : "",
773 $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining." 1105 $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining."
774 ); 1106 );
1107 }else {
1108 print CGI::p($pg->{state}->{state_summary_msg});
1109 }
775 print CGI::end_div(); 1110 print CGI::end_div();
776 1111
777 # save state for viewOptions 1112 # save state for viewOptions
778 print CGI::hidden( 1113 print CGI::hidden(
779 -name => "showOldAnswers", 1114 -name => "showOldAnswers",
829 CGI::submit(-name => 'action', -value=>'Show Past Answers') 1164 CGI::submit(-name => 'action', -value=>'Show Past Answers')
830 ), "\n", 1165 ), "\n",
831 CGI::endform(); 1166 CGI::endform();
832 } 1167 }
833 1168
834 # feedback form url 1169 ## feedback form url
835 my $feedbackPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Feedback", 1170 #my $feedbackPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Feedback",
836 courseID => $courseName); 1171 # courseID => $courseName);
837 my $feedbackURL = $self->systemLink($feedbackPage, authen => 0); # no authen info for form action 1172 #my $feedbackURL = $self->systemLink($feedbackPage, authen => 0); # no authen info for form action
838 1173 #
839 #print feedback form 1174 ##print feedback form
840 print 1175 #print
841 CGI::start_form(-method=>"POST", -action=>$feedbackURL),"\n", 1176 # CGI::start_form(-method=>"POST", -action=>$feedbackURL),"\n",
842 $self->hidden_authen_fields,"\n", 1177 # $self->hidden_authen_fields,"\n",
843 CGI::hidden("module", __PACKAGE__),"\n", 1178 # CGI::hidden("module", __PACKAGE__),"\n",
844 CGI::hidden("set", $set->set_id),"\n", 1179 # CGI::hidden("set", $set->set_id),"\n",
845 CGI::hidden("problem", $problem->problem_id),"\n", 1180 # CGI::hidden("problem", $problem->problem_id),"\n",
846 CGI::hidden("displayMode", $self->{displayMode}),"\n", 1181 # CGI::hidden("displayMode", $self->{displayMode}),"\n",
847 CGI::hidden("showOldAnswers", $will{showOldAnswers}),"\n", 1182 # CGI::hidden("showOldAnswers", $will{showOldAnswers}),"\n",
848 CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n", 1183 # CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n",
849 CGI::hidden("showHints", $will{showHints}),"\n", 1184 # CGI::hidden("showHints", $will{showHints}),"\n",
850 CGI::hidden("showSolutions", $will{showSolutions}),"\n", 1185 # CGI::hidden("showSolutions", $will{showSolutions}),"\n",
851 CGI::p({-align=>"left"}, 1186 # CGI::p({-align=>"left"},
852 CGI::submit(-name=>"feedbackForm", -label=>"Email instructor") 1187 # CGI::submit(-name=>"feedbackForm", -label=>"Email instructor")
853 ), 1188 # ),
854 CGI::endform(),"\n"; 1189 # CGI::endform(),"\n";
855 1190
856 # FIXME print editor link 1191 print $self->feedbackMacro(
857 print $editorLink; #empty unless it is appropriate to have an editor link. 1192 module => __PACKAGE__,
1193 set => $self->{set}->set_id,
1194 problem => $problem->problem_id,
1195 displayMode => $self->{displayMode},
1196 showOldAnswers => $will{showOldAnswers},
1197 showCorrectAnswers => $will{showCorrectAnswers},
1198 showHints => $will{showHints},
1199 showSolutions => $will{showSolutions},
1200 );
858 1201
859 print CGI::end_div(); 1202 print CGI::end_div();
860 1203
861 # debugging stuff 1204 # debugging stuff
862 if (0) { 1205 if (0) {
876 } 1219 }
877 1220
878 return ""; 1221 return "";
879} 1222}
880 1223
881##### output utilities #####
882
883sub attemptResults {
884 my $self = shift;
885 my $pg = shift;
886 my $showAttemptAnswers = shift;
887 my $showCorrectAnswers = shift;
888 my $showAttemptResults = $showAttemptAnswers && shift;
889 my $showSummary = shift;
890 my $showAttemptPreview = shift || 0;
891
892 my $ce = $self->r->ce;
893
894 my $problemResult = $pg->{result}; # the overall result of the problem
895 my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} };
896
897 my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames;
898
899 my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview";
900
901 # to make grabbing these options easier, we'll pull them out now...
902 my %imagesModeOptions = %{$ce->{pg}->{displayModeOptions}->{images}};
903
904 my $imgGen = WeBWorK::PG::ImageGenerator->new(
905 tempDir => $ce->{webworkDirs}->{tmp},
906 latex => $ce->{externalPrograms}->{latex},
907 dvipng => $ce->{externalPrograms}->{dvipng},
908 useCache => 1,
909 cacheDir => $ce->{webworkDirs}->{equationCache},
910 cacheURL => $ce->{webworkURLs}->{equationCache},
911 cacheDB => $ce->{webworkFiles}->{equationCacheDB},
912 dvipng_align => $imagesModeOptions{dvipng_align},
913 dvipng_depth_db => $imagesModeOptions{dvipng_depth_db},
914 );
915
916 my $header;
917 #$header .= CGI::th("Part");
918 $header .= $showAttemptAnswers ? CGI::th("Entered") : "";
919 $header .= $showAttemptPreview ? CGI::th("Answer Preview") : "";
920 $header .= $showCorrectAnswers ? CGI::th("Correct") : "";
921 $header .= $showAttemptResults ? CGI::th("Result") : "";
922 $header .= $showMessages ? CGI::th("Messages") : "";
923 my @tableRows = ( $header );
924 my $numCorrect = 0;
925 foreach my $name (@answerNames) {
926 my $answerResult = $pg->{answers}->{$name};
927 my $studentAnswer = $answerResult->{student_ans}; # original_student_ans
928 my $preview = ($showAttemptPreview
929 ? $self->previewAnswer($answerResult, $imgGen)
930 : "");
931 my $correctAnswer = $answerResult->{correct_ans};
932 my $answerScore = $answerResult->{score};
933 my $answerMessage = $showMessages ? $answerResult->{ans_message} : "";
934 #FIXME --Can we be sure that $answerScore is an integer-- could the problem give partial credit?
935 $numCorrect += $answerScore > 0;
936 my $resultString = $answerScore == 1 ? "correct" : "incorrect";
937
938 # get rid of the goofy prefix on the answer names (supposedly, the format
939 # of the answer names is changeable. this only fixes it for "AnSwEr"
940 #$name =~ s/^AnSwEr//;
941
942 my $row;
943 #$row .= CGI::td($name);
944 $row .= $showAttemptAnswers ? CGI::td($self->nbsp($studentAnswer)) : "";
945 $row .= $showAttemptPreview ? CGI::td($self->nbsp($preview)) : "";
946 $row .= $showCorrectAnswers ? CGI::td($self->nbsp($correctAnswer)) : "";
947 $row .= $showAttemptResults ? CGI::td($self->nbsp($resultString)) : "";
948 $row .= $showMessages ? CGI::td($self->nbsp($answerMessage)) : "";
949 push @tableRows, $row;
950 }
951
952 # render equation images
953 $imgGen->render(refresh => 1);
954
955# my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions";
956 my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100);
957# FIXME -- I left the old code in in case we have to back out.
958# my $summary = "On this attempt, you answered $numCorrect out of "
959# . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent.";
960 my $summary = "";
961 if (scalar @answerNames == 1) {
962 if ($numCorrect == scalar @answerNames) {
963 $summary .= CGI::div({class=>"ResultsWithoutError"},"The above answer is correct.");
964 } else {
965 $summary .= CGI::div({class=>"ResultsWithError"},"The above answer is NOT correct.");
966 }
967 } else {
968 if ($numCorrect == scalar @answerNames) {
969 $summary .= CGI::div({class=>"ResultsWithoutError"},"All of the above answers are correct.");
970 } else {
971 $summary .= CGI::div({class=>"ResultsWithError"},"At least one of the above answers is NOT correct.");
972 }
973 }
974
975 return
976 CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows))
977 . ($showSummary ? CGI::p({class=>'emphasis'},$summary) : "");
978}
979
980#sub nbsp {
981# my $str = shift;
982# ($str =~/\S/) ? $str : '&nbsp;' ; # returns non-breaking space for empty strings
983# # tricky cases: $str =0;
984# # $str is a complex number
985#}
986
987sub viewOptions {
988 my ($self) = @_;
989 my $ce = $self->r->ce;
990
991 # don't show options if we don't have anything to show
992 return if $self->{invalidSet} or $self->{invalidProblem};
993 return unless $self->{isOpen};
994
995 my $displayMode = $self->{displayMode};
996 my %must = %{ $self->{must} };
997 my %can = %{ $self->{can} };
998 my %will = %{ $self->{will} };
999
1000 my $optionLine;
1001 $can{showOldAnswers} and $optionLine .= join "",
1002 "Show: &nbsp;".CGI::br(),
1003 CGI::checkbox(
1004 -name => "showOldAnswers",
1005 -checked => $will{showOldAnswers},
1006 -label => "Saved answers",
1007 ), "&nbsp;&nbsp;".CGI::br();
1008
1009 $optionLine and $optionLine .= join "", CGI::br();
1010
1011 my %display_modes = %{WeBWorK::PG::DISPLAY_MODES()};
1012 my @active_modes = grep { exists $display_modes{$_} }
1013 @{$ce->{pg}->{displayModes}};
1014 my $modeLine = (scalar(@active_modes)>1) ?
1015 "View&nbsp;equations&nbsp;as:&nbsp;&nbsp;&nbsp;&nbsp;".CGI::br().
1016 CGI::radio_group(
1017 -name => "displayMode",
1018 -values => \@active_modes,
1019 -default => $displayMode,
1020 -linebreak=>'true',
1021 -labels => {
1022 plainText => "plain",
1023 formattedText => "formatted",
1024 images => "images",
1025 jsMath => "jsMath",
1026 asciimath => "asciimath",
1027 },
1028 ). CGI::br().CGI::hr() : '';
1029
1030 return CGI::div({-style=>"border: thin groove; padding: 1ex; margin: 2ex align: left"},
1031 $modeLine,
1032 $optionLine,
1033 CGI::submit(-name=>"redisplay", -label=>"Apply Options"),
1034 );
1035}
1036
1037sub previewAnswer {
1038 my ($self, $answerResult, $imgGen) = @_;
1039 my $ce = $self->r->ce;
1040 my $effectiveUser = $self->{effectiveUser};
1041 my $set = $self->{set};
1042 my $problem = $self->{problem};
1043 my $displayMode = $self->{displayMode};
1044
1045 # note: right now, we have to do things completely differently when we are
1046 # rendering math from INSIDE the translator and from OUTSIDE the translator.
1047 # so we'll just deal with each case explicitly here. there's some code
1048 # duplication that can be dealt with later by abstracting out tth/dvipng/etc.
1049
1050 my $tex = $answerResult->{preview_latex_string};
1051
1052 return "" unless defined $tex and $tex ne "";
1053
1054 if ($displayMode eq "plainText") {
1055 return $tex;
1056 } elsif ($displayMode eq "formattedText") {
1057 my $tthCommand = $ce->{externalPrograms}->{tth}
1058 . " -L -f5 -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n"
1059 . "\\(".$tex."\\)\n"
1060 . "END_OF_INPUT\n";
1061
1062 # call tth
1063 my $result = `$tthCommand`;
1064 if ($?) {
1065 return "<b>[tth failed: $? $@]</b>";
1066 }
1067 return $result;
1068 } elsif ($displayMode eq "images") {
1069 $imgGen->add($tex);
1070 } elsif ($displayMode eq "jsMath") {
1071
1072 return '<DIV CLASS="math">'.$tex.'</DIV>' ;
1073
1074
1075
1076
1077 }
1078}
1079
1080##### permission queries #####
1081
1082# this stuff should be abstracted out into the permissions system
1083# however, the permission system only knows about things in the
1084# course environment and the username. hmmm...
1085
1086# also, i should fix these so that they have a consistent calling
1087# format -- perhaps:
1088# canPERM($ce, $user, $set, $problem, $permissionLevel)
1089
1090sub canShowCorrectAnswers($$) {
1091 my ($permissionLevel, $answerDate) = @_;
1092 return $permissionLevel > 0 || time > $answerDate;
1093}
1094
1095sub canShowSolutions($$) {
1096 my ($permissionLevel, $answerDate) = @_;
1097 return canShowCorrectAnswers($permissionLevel, $answerDate);
1098}
1099
1100sub canRecordAnswers($$$$$) {
1101 my ($permissionLevel, $openDate, $dueDate, $maxAttempts, $attempts) = @_;
1102 my $permHigh = $permissionLevel > 0;
1103 my $timeOK = time >= $openDate && time <= $dueDate;
1104 my $attemptsOK = $maxAttempts == -1 || $attempts <= $maxAttempts;
1105 my $recordAnswers = $permHigh || ($timeOK && $attemptsOK);
1106 return $recordAnswers;
1107}
1108
1109sub canCheckAnswers($$) {
1110 my ($permissionLevel, $dueDate) = @_;
1111 my $permHigh = $permissionLevel > 0;
1112 my $timeOK = time >= $dueDate;
1113 my $recordAnswers = $permHigh || $timeOK;
1114 return $recordAnswers;
1115}
1116
1117sub mustRecordAnswers($) {
1118 my ($permissionLevel) = @_;
1119 return $permissionLevel == 0;
1120}
11211; 12241;

Legend:
Removed from v.2446  
changed lines
  Added in v.4158

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9