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

Diff of /trunk/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm

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

Revision 415 Revision 1326
1################################################################################
2# WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
3# $Id$
4################################################################################
5
1package WeBWorK::ContentGenerator::Problem; 6package WeBWorK::ContentGenerator::Problem;
2use base qw(WeBWorK::ContentGenerator); 7use base qw(WeBWorK::ContentGenerator);
3 8
9=head1 NAME
10
11WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem.
12
13=cut
14my $timer0_ON=1; # times pg translation phase
4use strict; 15use strict;
5use warnings; 16use warnings;
6use Apache::Constants qw(:common); 17use CGI qw();
7use WeBWorK::ContentGenerator; 18use File::Path qw(rmtree);
19use WeBWorK::Form;
8use WeBWorK::PG; 20use WeBWorK::PG;
21use WeBWorK::PG::ImageGenerator;
22use WeBWorK::PG::IO;
23use WeBWorK::Utils qw(writeLog encodeAnswers decodeAnswers ref2string makeTempDirectory);
24use WeBWorK::DB::Utils qw(global2user user2global findDefaults);
25use WeBWorK::Timing;
9 26
10# "Classic" form fields from processProblem8.pl 27############################################################
11# 28#
12# user - user ID 29# user
13# key - session key 30# effectiveUser
14# course - course name 31# key
15# probSetKey - USUALLY known as the PSVN
16# probNum - problem number a.k.a. ID a.k.a. name
17# 32#
18# Mode - display mode (HTML, HTML_tth, or typeset or whatever it's called) 33# displayMode
19# show_old_answers - whether or not student's old answers should be filled in 34# showOldAnswers
20# ShowAns - asks for correct answer to be shown -- only available for instructors 35# showCorrectAnswers
21# answer$i - student answers 36# showHints
22# showEdit - checks if the ShowEditor button should be shown and clicked 37# showSolutions
23# showSol - checks if the solution button ishould be shown and clicked
24# 38#
25# source - contains modified problem source when called from the web-based problem editor 39# AnSwEr# - answer blanks in problem
26# seed - contains problem seed when called from the web-based problem editor 40#
27# readSourceFromHTMLQ - if true, problem is read from 'source' instead of file 41# redisplay - name of the "Redisplay Problem" button
28# action - submit button clicked to invoke script (alledgedly) 42# submitAnswers - name of "Submit Answers" button
29# 'Save updated version' 43# checkAnswers - name of the "Check Answers" button
30# 'Read problem from disk' 44# previewAnswers - name of the "Preview Answers" button
31# 'Submit Answers' 45#
32# 'Preview Answers' 46############################################################
33# 'Preview Again' 47
34# probFileName - name of the PG file being edited 48sub pre_header_initialize {
35# languageType - afaik, always set to 'pg' 49 my ($self, $setName, $problemNumber) = @_;
50 my $r = $self->{r};
51 my $courseEnv = $self->{ce};
52 my $db = $self->{db};
53 my $userName = $r->param('user');
54 my $effectiveUserName = $r->param('effectiveUser');
55 my $key = $r->param('key');
56 my $user = $db->getUser($userName);
57 my $effectiveUser = $db->getUser($effectiveUserName);
58 my $permissionLevel = $db->getPermissionLevel($userName)->permission();
59
60 # obtain the merged set for $effectiveUser
61 my $set = $db->getMergedSet($effectiveUserName, $setName);
62
63 # obtain the merged problem for $effectiveUser
64 my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber);
65
66 my $editMode = $r->param("editMode");
67
68 if ($permissionLevel > 0 and defined $editMode) {
69 # professors are allowed to fabricate sets and problems not
70 # assigned to them (or anyone). this allows them to use the
71 # editor to
72
73 # if that is not yet defined obtain the global set, convert
74 # it to a user set, and add fake user data
75 unless (defined $set) {
76 my $userSetClass = $db->{set_user}->{record};
77 $set = global2user($userSetClass,
78 $db->getGlobalSet($setName));
79 die "Set $setName does not exist"
80 unless defined $set;
81 $set->psvn(0);
82 }
83
84 # if that is not yet defined obtain the global problem,
85 # convert it to a user problem, and add fake user data
86 unless (defined $problem) {
87 my $userProblemClass = $db->{problem_user}->{record};
88 $problem = global2user($userProblemClass,
89 $db->getGlobalProblem($setName,$problemNumber));
90 die "Problem $problemNumber in set $setName does not exist"
91 unless defined $problem;
92 $problem->user_id($effectiveUserName);
93 $problem->problem_seed(0);
94 $problem->status(0);
95 $problem->attempted(0);
96 $problem->last_answer("");
97 $problem->num_correct(0);
98 $problem->num_incorrect(0);
99 }
100
101 # now we're sure we have valid UserSet and UserProblem objects
102 # yay!
103
104 # now deal with possible editor overrides:
105
106 # if the caller is asking to override the source file, and
107 # editMode calls for a temporary file, do so
108 my $sourceFilePath = $r->param("sourceFilePath");
109 if (defined $sourceFilePath and $editMode eq "temporaryFile") {
110 $problem->source_file($sourceFilePath);
111 }
112
113 # if the caller is asking to override the problem seed, do so
114 my $problemSeed = $r->param("problemSeed");
115 if (defined $problemSeed) {
116 $problem->problem_seed($problemSeed);
117 }
118 } else {
119 # students can't view problems not assigned to them
120 die "Set $setName is not assigned to $effectiveUserName"
121 unless defined $set;
122 die "Problem $problemNumber in set $setName is not assigned to $effectiveUserName"
123 unless defined $problem;
124 }
125
126 $self->{userName} = $userName;
127 $self->{effectiveUserName} = $effectiveUserName;
128 $self->{user} = $user;
129 $self->{effectiveUser} = $effectiveUser;
130 $self->{permissionLevel} = $permissionLevel;
131 $self->{set} = $set;
132 $self->{problem} = $problem;
133 $self->{editMode} = $editMode;
134
135 ##### form processing #####
136
137 # set options from form fields (see comment at top of file for names)
138 my $displayMode = $r->param("displayMode") || $courseEnv->{pg}->{options}->{displayMode};
139 my $redisplay = $r->param("redisplay");
140 my $submitAnswers = $r->param("submitAnswers");
141 my $checkAnswers = $r->param("checkAnswers");
142 my $previewAnswers = $r->param("previewAnswers");
143
144 # fields which may be defined when using Problem Editor
145 #my $override_seed = ($permissionLevel>=10) ? $r->param('problemSeed') : undef;
146 #my $override_problem_source = ($permissionLevel>=10) ? $r->param('sourceFilePath') : undef;
147 #my $editMode = undef;
148 #my $submit_button = $r->param('submit_button');
149 #if ( defined($submit_button ) ) {
150 # $editMode = "temporaryFile" if $submit_button eq 'Refresh';
151 # $editMode = 'savedFile' if $submit_button eq 'Save';
152 #}
153 #
154 ##override using the source file data from the form field
155 #$problem->source_file($override_problem_source) if defined($override_problem_source);
156 #$problem->problem_seed($override_seed) if defined($override_seed);
157 #
158 ## store path to source file for title.
159 #$self->{problem_source_name} = $problem->source_file;
160 #$self->{edit_mode} = $editMode;
161 #$self->{current_problem_source} = (defined($override_problem_source) ) ?
162
163 # coerce form fields into CGI::Vars format
164 my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars };
165
166 $self->{displayMode} = $displayMode;
167 $self->{redisplay} = $redisplay;
168 $self->{submitAnswers} = $submitAnswers;
169 $self->{checkAnswers} = $checkAnswers;
170 $self->{previewAnswers} = $previewAnswers;
171 $self->{formFields} = $formFields;
172
173 ##### permissions #####
174
175 # are we allowed to view this problem?
176 $self->{isOpen} = time >= $set->open_date || $permissionLevel > 0;
177 return unless $self->{isOpen};
178
179 # what does the user want to do?
180 my %want = (
181 showOldAnswers => $r->param("showOldAnswers") || $courseEnv->{pg}->{options}->{showOldAnswers},
182 showCorrectAnswers => $r->param("showCorrectAnswers") || $courseEnv->{pg}->{options}->{showCorrectAnswers},
183 showHints => $r->param("showHints") || $courseEnv->{pg}->{options}->{showHints},
184 showSolutions => $r->param("showSolutions") || $courseEnv->{pg}->{options}->{showSolutions},
185 recordAnswers => $submitAnswers,
186 checkAnswers => $checkAnswers,
187 );
188
189 # are certain options enforced?
190 my %must = (
191 showOldAnswers => 0,
192 showCorrectAnswers => 0,
193 showHints => 0,
194 showSolutions => 0,
195 recordAnswers => mustRecordAnswers($permissionLevel),
196 checkAnswers => 0,
197 );
198
199 # does the user have permission to use certain options?
200 my %can = (
201 showOldAnswers => 1,
202 showCorrectAnswers => canShowCorrectAnswers($permissionLevel, $set->answer_date),
203 showHints => 1,
204 showSolutions => canShowSolutions($permissionLevel, $set->answer_date),
205 recordAnswers => canRecordAnswers($permissionLevel, $set->open_date, $set->due_date,
206 $problem->max_attempts, $problem->num_correct + $problem->num_incorrect + 1),
207 # attempts=num_correct+num_incorrect+1, as this happens before updating $problem
208 checkAnswers => canCheckAnswers($permissionLevel, $set->answer_date),
209 );
210
211 # final values for options
212 my %will;
213 foreach (keys %must) {
214 $will{$_} = $can{$_} && ($want{$_} || $must{$_});
215 }
216
217 ##### sticky answers #####
218
219 if (not ($submitAnswers or $previewAnswers or $checkAnswers) and $will{showOldAnswers}) {
220 # do this only if new answers are NOT being submitted
221 my %oldAnswers = decodeAnswers($problem->last_answer);
222 $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers;
223 }
224
225 ##### translation #####
226
227 $WeBWorK::timer0->continue("begin pg processing") if $timer0_ON;
228 my $pg = WeBWorK::PG->new(
229 $courseEnv,
230 $effectiveUser,
231 $key,
232 $set,
233 $problem,
234 $set->psvn, # FIXME: this field should be removed
235 $formFields,
236 { # translation options
237 displayMode => $displayMode,
238 showHints => $will{showHints},
239 showSolutions => $will{showSolutions},
240 refreshMath2img => $will{showHints} || $will{showSolutions},
241 processAnswers => 1,
242 },
243 );
244
245 $WeBWorK::timer0->continue("end pg processing") if $timer0_ON;
246 ##### fix hint/solution options #####
247
248 $can{showHints} &&= $pg->{flags}->{hintExists};
249 $can{showSolutions} &&= $pg->{flags}->{solutionExists};
250
251 ##### store fields #####
252
253 $self->{want} = \%want;
254 $self->{must} = \%must;
255 $self->{can} = \%can;
256 $self->{will} = \%will;
257
258 $self->{pg} = $pg;
259}
260
261#sub if_warnings($$) {
262# my ($self, $arg) = @_;
263# return 0 unless $self->{isOpen};
264# return $self->{pg}->{warnings} ne "";
265#}
266
267sub if_errors($$) {
268 my ($self, $arg) = @_;
269 return 0 unless $self->{isOpen};
270 return $self->{pg}->{flags}->{error_flag};
271}
272
273sub head {
274 my $self = shift;
275 return "" unless $self->{isOpen};
276 return $self->{pg}->{head_text} if $self->{pg}->{head_text};
277}
278
279sub options {
280 my $self = shift;
281 return join("",
282 CGI::start_form("POST", $self->{r}->uri),
283 $self->hidden_authen_fields,
284 CGI::hr(),
285 CGI::start_div({class=>"viewOptions"}),
286 $self->viewOptions(),
287 CGI::end_div(),
288 CGI::end_form()
289 );
290}
291
292sub path {
293 my $self = shift;
294 my $args = $_[-1];
295 my $setName = $self->{set}->set_id;
296 my $problemNumber = $self->{problem}->problem_id;
297
298 my $ce = $self->{ce};
299 my $root = $ce->{webworkURLs}->{root};
300 my $courseName = $ce->{courseName};
301 return $self->pathMacro($args,
302 "Home" => "$root",
303 $courseName => "$root/$courseName",
304 $setName => "$root/$courseName/$setName",
305 "Problem $problemNumber" => "",
306 );
307}
308
309sub siblings {
310 my $self = shift;
311 my $setName = $self->{set}->set_id;
312 my $problemNumber = $self->{problem}->problem_id;
313
314 my $ce = $self->{ce};
315 my $db = $self->{db};
316 my $root = $ce->{webworkURLs}->{root};
317 my $courseName = $ce->{courseName};
318 print CGI::strong("Problems"), CGI::br();
319
320 my $effectiveUser = $self->{r}->param("effectiveUser");
321 my @problemIDs = $db->listUserProblems($effectiveUser, $setName);
322 foreach my $problem (sort { $a <=> $b } @problemIDs) {
323 print CGI::a({-href=>"$root/$courseName/$setName/".$problem."/?"
324 . $self->url_authen_args . "&displayMode=" . $self->{displayMode}},
325 "Problem ".$problem), CGI::br();
326 }
327
328 return "";
329}
330
331sub nav {
332 $WeBWorK::timer0->continue("begin nav subroutine") if $timer0_ON;
333 my $self = shift;
334 my $args = $_[-1];
335 my $setName = $self->{set}->set_id;
336 my $problemNumber = $self->{problem}->problem_id;
337
338 my $ce = $self->{ce};
339 my $db = $self->{db};
340 my $root = $ce->{webworkURLs}->{root};
341 my $courseName = $ce->{courseName};
342
343 my $wwdb = $self->{wwdb};
344 my $effectiveUser = $self->{r}->param("effectiveUser");
345 my $tail = "&displayMode=".$self->{displayMode};
346
347 my @links = ("Problem List" , "$root/$courseName/$setName", "navProbList");
348
349 my @problemIDs = $db->listUserProblems($effectiveUser, $setName);
350 my ($prevID, $nextID);
351 foreach my $id (@problemIDs) {
352 $prevID = $id if $id < $problemNumber
353 and (not defined $prevID or $id > $prevID);
354 $nextID = $id if $id > $problemNumber
355 and (not defined $nextID or $id < $nextID);
356 }
357 unshift @links, "Previous Problem" , ($prevID
358 ? "$root/$courseName/$setName/".$prevID
359 : "") , "navPrev";
360 push @links, "Next Problem" , ($nextID
361 ? "$root/$courseName/$setName/".$nextID
362 : "") , "navNext";
363
364 my $result = $self->navMacro($args, $tail, @links);
365 $WeBWorK::timer0->continue("end nav subroutine") if $timer0_ON;
366 return $result;
367}
36 368
37sub title { 369sub title {
38 my ($self, $problem_set, $problem) = @_; 370 my $self = shift;
39 my $r = $self->{r}; 371 my $setName = $self->{set}->set_id;
40 my $user = $r->param('user'); 372 my $problemNumber = $self->{problem}->problem_id;
41 return "Problem $problem of problem set $problem_set for $user"; 373
374 return "$setName : Problem $problemNumber";
42} 375}
43 376
44sub body { 377sub body {
45 my ($self, $problem_set, $problem) = @_; 378 my $self = shift;
46 379
47 # we have to call init_translator like this: 380 return CGI::p(CGI::font({-color=>"red"}, "This problem is not available because the problem set that contains it is not yet open."))
48 my $pt = WeBWorK::PG->new($courseEnv, $userName, $setName, $problemNumber, $formData); 381 unless $self->{isOpen};
49 382
50 # 383 # unpack some useful variables
384 my $r = $self->{r};
385 my $db = $self->{db};
386 my $set = $self->{set};
387 my $problem = $self->{problem};
388 my $editMode = $self->{editMode};
389 my $permissionLevel = $self->{permissionLevel};
390 my $submitAnswers = $self->{submitAnswers};
391 my $checkAnswers = $self->{checkAnswers};
392 my $previewAnswers = $self->{previewAnswers};
393 my %want = %{ $self->{want} };
394 my %can = %{ $self->{can} };
395 my %must = %{ $self->{must} };
396 my %will = %{ $self->{will} };
397 my $pg = $self->{pg};
51 398
52 # ----- this is not a place of honor ----- 399 ##### translation errors? #####
53 400
54 # Run the problem (output the html text) but also store it within the object. 401 if ($pg->{flags}->{error_flag}) {
55 # The correct answers are also calculated and stored within the object 402 return $self->errorOutput($pg->{errors}, $pg->{body_text});
56 $pt ->translate(); 403 }
57 404
58 # print problem output 405 ##### answer processing #####
59 print "Problem goes here<p>\n"; 406 $WeBWorK::timer0->continue("begin answer processing") if $timer0_ON;
60 print "Problem output <br>\n"; 407 # if answers were submitted:
61 print "<HR>"; 408 my $scoreRecordedMessage;
62 print ${$pt->r_text()}; 409 if ($submitAnswers) {
63 print "<HR>"; 410 # get a "pure" (unmerged) UserProblem to modify
64 print "<p>End of problem output<br>"; 411 # this will be undefined if the problem has not been assigned to this user
65 412 my $pureProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id);
66 413 if (defined $pureProblem) {
67 # print source code 414 # store answers in DB for sticky answers
68 print "Source code<pre>\n"; 415 my %answersToStore;
69 print $SOURCE1; 416 my %answerHash = %{ $pg->{answers} };
70 print "</pre>End source code<p>"; 417 $answersToStore{$_} = $answerHash{$_}->{original_student_ans}
71 418 foreach (keys %answerHash);
72 # The format for the output is described here. We'll need a local variable 419 my $answerString = encodeAnswers(%answersToStore,
73 # to handle the warnings. From within the problem the warning command 420 @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} });
74 # has been slaved to the __WARNINGS__ routine which is defined in Global. 421
75 # We'll need to provide an alternate mechanism. 422 # store last answer to database
76 # The base64 encoding is only needed for xml transmission. 423 $problem->last_answer($answerString);
77 print "<hr>"; 424 $pureProblem->last_answer($answerString);
78 print "Warnings output<br>"; 425 $db->putUserProblem($pureProblem);
79 my $WARNINGS = "Let this be a warning:"; 426
80 427 # store state in DB if it makes sense
81 print $WARNINGS; 428 if ($will{recordAnswers}) {
82 429 $problem->status($pg->{state}->{recorded_score});
83 # Install the standard problem grader. See gage/xmlrpc/daemon.pm or processProblem8 for detailed 430 $problem->attempted(1);
84 # code on how to choose which problem grader to install, depending on courseEnvironment and problem data. 431 $problem->num_correct($pg->{state}->{num_of_correct_ans});
85 # See also PG.pl which provides for problem by problem overrides. 432 $problem->num_incorrect($pg->{state}->{num_of_incorrect_ans});
86 $pt->rf_problem_grader($pt->rf_std_problem_grader); 433 $pureProblem->status($pg->{state}->{recorded_score});
87 434 $pureProblem->attempted(1);
88 # creates and stores a hash of answer results inside the object: $rh_answer_results 435 $pureProblem->num_correct($pg->{state}->{num_of_correct_ans});
89 $pt -> process_answers($rh->{envir}->{inputs_ref}); 436 $pureProblem->num_incorrect($pg->{state}->{num_of_incorrect_ans});
90 437 if ($db->putUserProblem($pureProblem)) {
91 438 $scoreRecordedMessage = "Your score was recorded.";
92 # THE UPDATE AND GRADING LOGIC COULD USE AN OVERHAUL. IT WAS SOMEWHAT CONSTRAINED 439 } else {
93 # BY LEGACY CONDITIONS IN THE ORIGINAL PROCESSPROBLEM8. IT'S NOT BAD 440 $scoreRecordedMessage = "Your score was not recorded because there was a failure in storing the problem record to the database.";
94 # BUT IT COULD PROBABLY BE MADE A LITTLE MORE STRAIGHT FORWARD. 441 }
95 # 442 # write to the transaction log, just to make sure
96 # updates the problem state stored by the translator object from the problemEnvironment data 443 writeLog($self->{ce}, "transaction",
97 444 $problem->problem_id."\t".
98 # $pt->rh_problem_state({ recorded_score => $rh->{problem_state}->{recorded_score}, 445 $problem->set_id."\t".
99 # num_of_correct_ans => $rh->{problem_state}->{num_of_correct_ans} , 446 $problem->user_id."\t".
100 # num_of_incorrect_ans => $rh->{problem_state}->{num_of_incorrect_ans} 447 $problem->source_file."\t".
101 # } ); 448 $problem->value."\t".
102 449 $problem->max_attempts."\t".
103 # grade the problem (and update the problem state again.) 450 $problem->problem_seed."\t".
104 # 451 $pureProblem->status."\t".
105 # Define an entry order -- the default is the order they are received from the browser. 452 $pureProblem->attempted."\t".
106 # (Which as I understand it is NOT guaranteed to be the Left->Right Up-> Down order we're 453 $pureProblem->last_answer."\t".
107 # used to in the West. 454 $pureProblem->num_correct."\t".
108 455 $pureProblem->num_incorrect
109 my %PG_FLAGS = $pt->h_flags; 456 );
110 my $ra_answer_entry_order = ( defined($PG_FLAGS{ANSWER_ENTRY_ORDER}) ) ? 457 } else {
111 $PG_FLAGS{ANSWER_ENTRY_ORDER} : [ keys %{$pt->rh_evaluated_answers} ] ; 458 if (time < $set->open_date or time > $set->due_date) {
112 # Decide whether any answers were submitted. 459 $scoreRecordedMessage = "Your score was not recorded because this problem set is closed.";
113 my $answers_submitted = 0; 460 } else {
114 $answers_submitted = 1 if defined( $rh->{answer_form_submitted} ) and 1 == $rh->{answer_form_submitted}; 461 $scoreRecordedMessage = "Your score was not recorded.";
115 # If there are answers, grade them 462 }
116 my ($rh_problem_result,$rh_problem_state) = $pt->grade_problem( answers_submitted => $answers_submitted, 463 }
117 ANSWER_ENTRY_ORDER => $ra_answer_entry_order 464 } else {
118 ); # grades the problem. 465 $scoreRecordedMessage = "Your score was not recorded because this problem has not been built for you.";
119
120 # Output format expected by Webwork.pm (and I believe processProblem8, but check.)
121 my $out = {
122 text => ${$pt ->r_text()}, # encode_base64( ${$pt ->r_text()} ),
123 header_text => $pt->r_header, # encode_base64( ${ $pt->r_header } ),
124 answers => $pt->rh_evaluated_answers,
125 errors => $pt-> errors(),
126 WARNINGS => $WARNINGS, #encode_base64($WARNINGS ),
127 problem_result => $rh_problem_result,
128 problem_state => $rh_problem_state,
129 PG_flag => \%PG_FLAGS
130 };
131
132 # Debugging printout of environment tables
133 print "<P>Request item<P>\n\n";
134 print "<TABLE border=\"3\">";
135 print $self->print_form_data('<tr><td>','</td><td>','</td></tr>');
136 print "</table>\n";
137 print "path info <br>\n";
138 print $r->path_info();
139 print "<P>\n\ncourseEnvironment<P>\n\n";
140 print pretty_print_rh($courseEnvironment);
141 print "<P>\n\nproblemEnvironment<P>\n\n";
142 print pretty_print_rh($problemEnvir_rh);
143
144 "";
145}
146
147sub pretty_print_rh {
148 my $r_input = shift;
149 my $out = '';
150 if ( not ref($r_input) ) {
151 $out = $r_input; # not a reference
152 } elsif (is_hash_ref($r_input)) {
153 local($^W) = 0;
154 $out .= "<TABLE border = \"2\" cellpadding = \"3\" BGCOLOR = \"#FFFFFF\">";
155 foreach my $key (sort keys %$r_input ) {
156 $out .= "<tr><TD> $key</TD><TD>=&gt;</td><td>&nbsp;".pretty_print_rh($r_input->{$key}) . "</td></tr>";
157 } 466 }
158 $out .="</table>"; 467 }
159 } elsif (is_array_ref($r_input) ) { 468
160 my @array = @$r_input; 469 # logging student answers
161 $out .= "( " ; 470 my $pastAnswerLog = undef;
162 while (@array) { 471 if (defined( $self->{ce}->{webworkFiles}->{logs}->{'pastAnswerList'} )) {
163 $out .= pretty_print_rh(shift @array) . " , "; 472 $pastAnswerLog = $self->{ce}->{webworkFiles}->{logs}->{'pastAnswerList'};
473 if ($submitAnswers and defined $pastAnswerLog) {
474 my $answerString = "";
475 my %answerHash = %{ $pg->{answers} };
476 $answerString = $answerString . $answerHash{$_}->{original_student_ans}."\t"
477 foreach (sort keys %answerHash);
478 $answerString = '' unless defined($answerString); # insure string is defined.
479 writeLog($self->{ce}, "pastAnswerList",
480 '|'.$problem->user_id.
481 '|'.$problem->set_id.
482 '|'.$problem->problem_id.'|'."\t".
483 time()."\t".
484 $answerString,
485 );
164 } 486 }
165 $out .= " )"; 487 }
166 } elsif (ref($r_input) eq 'CODE') { 488
167 $out = "$r_input"; 489 $WeBWorK::timer0->continue("end answer processing") if $timer0_ON;
490
491 ##### output #####
492
493 print CGI::start_div({class=>"problemHeader"});
494
495 # custom message for editor
496 if ($permissionLevel >= 10 and defined $editMode) {
497 if ($editMode eq "temporaryFile") {
498 print CGI::p(CGI::i("Editing temporary file: ", $problem->source_file));
499 } elsif ($editMode eq "savedFile") {
500 print CGI::p(CGI::i("Problem saved to: ", $problem->source_file));
501 }
502 }
503
504 # attempt summary
505 if ($submitAnswers or $will{showCorrectAnswers}) {
506 # print this if user submitted answers OR requested correct answers
507 print $self->attemptResults($pg, $submitAnswers,
508 $will{showCorrectAnswers},
509 $pg->{flags}->{showPartialCorrectAnswers}, 1, 1);
510 } elsif ($checkAnswers) {
511 # print this if user previewed answers
512 print $self->attemptResults($pg, 1, 0, 1, 1, 1);
513 # show attempt answers
514 # don't show correct answers
515 # show attempt results (correctness)
516 # don't show attempt previews
517 } elsif ($previewAnswers) {
518 # print this if user previewed answers
519 print $self->attemptResults($pg, 1, 0, 0, 0, 1);
520 # show attempt answers
521 # don't show correct answers
522 # don't show attempt results (correctness)
523 # show attempt previews
524 }
525
526 print CGI::end_div();
527
528 print CGI::start_div({class=>"problem"});
529
530 # main form
531 print
532 CGI::startform("POST", $r->uri),
533 $self->hidden_authen_fields,
534 CGI::p($pg->{body_text}),
535 CGI::p($pg->{result}->{msg} ? CGI::b("Note: ") : "", CGI::i($pg->{result}->{msg})),
536 CGI::p(
537 ($can{recordAnswers}
538 ? CGI::submit(-name=>"submitAnswers",
539 -label=>"Submit Answers")
540 : ""),
541 ($can{checkAnswers}
542 ? CGI::submit(-name=>"checkAnswers",
543 -label=>"Check Answers")
544 : ""),
545 CGI::submit(-name=>"previewAnswers",
546 -label=>"Preview Answers"),
547 );
548 print CGI::end_div();
549
550 print CGI::start_div({class=>"scoreSummary"});
551
552 # score summary
553 my $attempts = $problem->num_correct + $problem->num_incorrect;
554 my $attemptsNoun = $attempts != 1 ? "times" : "time";
555 my $lastScore = sprintf("%.0f%%", $problem->status * 100); # Round to whole number
556 my ($attemptsLeft, $attemptsLeftNoun);
557 if ($problem->max_attempts == -1) {
558 # unlimited attempts
559 $attemptsLeft = "unlimited";
560 $attemptsLeftNoun = "attempts";
168 } else { 561 } else {
169 $out = $r_input; 562 $attemptsLeft = $problem->max_attempts - $attempts;
563 $attemptsLeftNoun = $attemptsLeft == 1 ? "attempt" : "attempts";
564 }
565
566 my $setClosed = 0;
567 my $setClosedMessage;
568 if (time < $set->open_date or time > $set->due_date) {
569 $setClosed = 1;
570 $setClosedMessage = "This problem set is closed.";
571 if ($permissionLevel > 0) {
572 $setClosedMessage .= " However, since you are a privileged user, additional attempts will be recorded.";
573 } else {
574 $setClosedMessage .= " Additional attempts will not be recorded.";
170 } 575 }
171 $out; 576 }
577 print CGI::p(
578 $submitAnswers ? $scoreRecordedMessage . CGI::br() : "",
579 "You have attempted this problem $attempts $attemptsNoun.", CGI::br(),
580 $problem->attempted
581 ? "Your recorded score is $lastScore." . CGI::br()
582 : "",
583 $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining."
584 );
585 print CGI::end_div();
586
587 # save state for viewOptions
588 print CGI::hidden(
589 -name => "showOldAnswers",
590 -value => $will{showOldAnswers}
591 ),
592 CGI::hidden(
593 -name => "showCorrectAnswers",
594 -value => $will{showCorrectAnswers}
595 ),
596 CGI::hidden(
597 -name => "showHints",
598 -value => $will{showHints}),
599 CGI::hidden(
600 -name => "showSolutions",
601 -value => $will{showSolutions},
602 ),
603 CGI::hidden(
604 -name => "displayMode",
605 -value => $self->{displayMode}
606 );
607
608 # end of main form
609 print CGI::endform();
610
611 # stuff we need below (pull these out at the beginning?)
612 my $ce = $self->{ce};
613 my $root = $ce->{webworkURLs}->{root};
614 my $courseName = $ce->{courseName};
615
616 print CGI::start_div({class=>"problemFooter"});
617
618 # arguments for answer inspection button
619 my $prof_url = $ce->{webworkURLs}->{oldProf};
620 my $cgi_url = $prof_url;
621 $cgi_url=~ s|/[^/]*$||; # clip profLogin.pl
622 my $authen_args = $self->url_authen_args();
623 my $showPastAnswersURL = "$cgi_url/showPastAnswers.pl";
624
625 # print answer inspection button
626 if ($self->{permissionLevel} > 0) {
627 print "\n",
628 CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"information"),"\n",
629 $self->hidden_authen_fields,"\n",
630 CGI::hidden(-name => 'course', -value=>$courseName), "\n",
631 CGI::hidden(-name => 'probNum', -value=>$problem->problem_id), "\n",
632 CGI::hidden(-name => 'setNum', -value=>$problem->set_id), "\n",
633 CGI::hidden(-name => 'User', -value=>$problem->user_id), "\n",
634 CGI::p( {-align=>"left"},
635 CGI::submit(-name => 'action', -value=>'Show Past Answers')
636 ), "\n",
637 CGI::endform();
638 }
639
640 #print CGI::end_div();
641 #
642 #print CGI::start_div();
643
644 # arguments for feedback form
645 my $feedbackURL = "$root/$courseName/feedback/";
646
647 #print feedback form
648 print
649 CGI::start_form(-method=>"POST", -action=>$feedbackURL),"\n",
650 $self->hidden_authen_fields,"\n",
651 CGI::hidden("module", __PACKAGE__),"\n",
652 CGI::hidden("set", $set->set_id),"\n",
653 CGI::hidden("problem", $problem->problem_id),"\n",
654 CGI::hidden("displayMode", $self->{displayMode}),"\n",
655 CGI::hidden("showOldAnswers", $will{showOldAnswers}),"\n",
656 CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n",
657 CGI::hidden("showHints", $will{showHints}),"\n",
658 CGI::hidden("showSolutions", $will{showSolutions}),"\n",
659 CGI::p({-align=>"left"},
660 CGI::submit(-name=>"feedbackForm", -label=>"Contact instructor")
661 ),
662 CGI::endform(),"\n";
663
664 # FIXME print editor link
665 # print editor link if the user is an instructor AND the file is not in temporary editing mode
666 if ($self->{permissionLevel}>=10 and ( (not defined($self->{edit_mode})) or $self->{edit_mode} eq 'savedFile') ) {
667 print CGI::a({-href=>$ce->{webworkURLs}->{root}."/$courseName/instructor/pgProblemEditor/".$set->set_id.
668 '/'.$problem->problem_id.'?'.$self->url_authen_args},'Edit this problem');
669 }
670
671 print CGI::end_div();
672
673 # warning output
674 #if ($pg->{warnings} ne "") {
675 # print CGI::hr(), $self->warningOutput($pg->{warnings});
676 #}
677
678 # debugging stuff
679 if (0) {
680 print
681 CGI::hr(),
682 CGI::h2("debugging information"),
683 CGI::h3("form fields"),
684 ref2string($self->{formFields}),
685 CGI::h3("user object"),
686 ref2string($self->{user}),
687 CGI::h3("set object"),
688 ref2string($set),
689 CGI::h3("problem object"),
690 ref2string($problem),
691 CGI::h3("PG object"),
692 ref2string($pg, {'WeBWorK::PG::Translator' => 1});
693 }
694
695 return "";
172} 696}
173 697
174sub is_hash_ref { 698##### output utilities #####
699
700sub attemptResults($$$$$$) {
701 my $self = shift;
175 my $in =shift; 702 my $pg = shift;
176 my $save_SIG_die_trap = $SIG{__DIE__}; 703 my $showAttemptAnswers = shift;
177 $SIG{__DIE__} = sub {CORE::die(@_) }; 704 my $showCorrectAnswers = shift;
178 my $out = eval{ %{ $in } }; 705 my $showAttemptResults = $showAttemptAnswers && shift;
179 $out = ($@ eq '') ? 1 : 0; 706 my $showSummary = shift;
180 $@=''; 707 my $showAttemptPreview = shift || 0;
181 $SIG{__DIE__} = $save_SIG_die_trap; 708 my $ce = $self->{ce};
182 $out; 709 my $problemResult = $pg->{result}; # the overall result of the problem
710 my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} };
711
712 my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames;
713
714 my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview";
715 my $imgGen = WeBWorK::PG::ImageGenerator->new(
716 tempDir => $ce->{webworkDirs}->{tmp},
717 dir => $ce->{courseDirs}->{html_temp},
718 url => $ce->{courseURLs}->{html_temp},
719 basename => $basename,
720 latex => $ce->{externalPrograms}->{latex},
721 dvipng => $ce->{externalPrograms}->{dvipng},
722 );
723
724 my $header;
725 #$header .= CGI::th("Part");
726 $header .= $showAttemptAnswers ? CGI::th("Entered") : "";
727 $header .= $showAttemptPreview ? CGI::th("Answer Preview") : "";
728 $header .= $showCorrectAnswers ? CGI::th("Correct") : "";
729 $header .= $showAttemptResults ? CGI::th("Result") : "";
730 $header .= $showMessages ? CGI::th("messages") : "";
731 my @tableRows = ( $header );
732 my $numCorrect;
733 foreach my $name (@answerNames) {
734 my $answerResult = $pg->{answers}->{$name};
735 my $studentAnswer = $answerResult->{student_ans}; # original_student_ans
736 my $preview = ($showAttemptPreview
737 ? $self->previewAnswer($answerResult, $imgGen)
738 : "");
739 my $correctAnswer = $answerResult->{correct_ans};
740 my $answerScore = $answerResult->{score};
741 my $answerMessage = $showMessages ? $answerResult->{ans_message} : "";
742
743 $numCorrect += $answerScore > 0;
744 my $resultString = $answerScore ? "correct" : "incorrect";
745
746 # get rid of the goofy prefix on the answer names (supposedly, the format
747 # of the answer names is changeable. this only fixes it for "AnSwEr"
748 $name =~ s/^AnSwEr//;
749
750 my $row;
751 #$row .= CGI::td($name);
752 $row .= $showAttemptAnswers ? CGI::td(nbsp($studentAnswer)) : "";
753 $row .= $showAttemptPreview ? CGI::td(nbsp($preview)) : "";
754 $row .= $showCorrectAnswers ? CGI::td(nbsp($correctAnswer)) : "";
755 $row .= $showAttemptResults ? CGI::td(nbsp($resultString)) : "";
756 $row .= $answerMessage ? CGI::td(nbsp($answerMessage)) : "";
757 push @tableRows, $row;
758 }
759
760 # render equation images
761 $imgGen->render(refresh => 1);
762
763 my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions";
764 my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100);
765 my $summary = "On this attempt, you answered $numCorrect out of "
766 . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent.";
767 return CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows)) . ($showSummary ? CGI::p({class=>'emphasis'},$summary) : "");
183} 768}
184sub is_array_ref { 769sub nbsp {
185 my $in =shift; 770 my $str = shift;
186 my $save_SIG_die_trap = $SIG{__DIE__}; 771 ($str eq '') ? '&nbsp;' : $str ; # returns non-breaking space for empty strings
187 $SIG{__DIE__} = sub {CORE::die(@_) }; 772}
188 my $out = eval{ @{ $in } }; 773sub viewOptions($) {
189 $out = ($@ eq '') ? 1 : 0; 774 my $self = shift;
190 $@=''; 775 my $displayMode = $self->{displayMode};
191 $SIG{__DIE__} = $save_SIG_die_trap; 776 my %must = %{ $self->{must} };
192 $out; 777 my %can = %{ $self->{can} };
778 my %will = %{ $self->{will} };
779
780 my $optionLine;
781 $can{showOldAnswers} and $optionLine .= join "",
782 "Show: &nbsp;".CGI::br(),
783 CGI::checkbox(
784 -name => "showOldAnswers",
785 -checked => $will{showOldAnswers},
786 -label => "Saved answers",
787 ), "&nbsp;&nbsp;".CGI::br();
788 $can{showCorrectAnswers} and $optionLine .= join "",
789 CGI::checkbox(
790 -name => "showCorrectAnswers",
791 -checked => $will{showCorrectAnswers},
792 -label => "Correct answers",
793 ), "&nbsp;&nbsp;".CGI::br();
794 $can{showHints} and $optionLine .= join "",
795 CGI::checkbox(
796 -name => "showHints",
797 -checked => $will{showHints},
798 -label => "Hints",
799 ), "&nbsp;&nbsp;".CGI::br();
800 $can{showSolutions} and $optionLine .= join "",
801 CGI::checkbox(
802 -name => "showSolutions",
803 -checked => $will{showSolutions},
804 -label => "Solutions",
805 ), "&nbsp;&nbsp;".CGI::br();
806 $optionLine and $optionLine .= join "", CGI::br();
807
808 return CGI::div({-style=>"border: thin groove; padding: 1ex; margin: 2ex align: left"},
809 "View&nbsp;equations&nbsp;as:&nbsp;&nbsp;&nbsp;&nbsp;".CGI::br(),
810 CGI::radio_group(
811 -name => "displayMode",
812 -values => ['plainText', 'formattedText', 'images'],
813 -default => $displayMode,
814 -linebreak=>'true',
815 -labels => {
816 plainText => "plain",
817 formattedText => "formatted",
818 images => "images",
819 }
820 ), CGI::br(),CGI::hr(),
821 $optionLine,
822 CGI::submit(-name=>"redisplay", -label=>"Save Options"),
823 );
824}
825
826sub previewAnswer($$) {
827 my ($self, $answerResult, $imgGen) = @_;
828 my $ce = $self->{ce};
829 my $effectiveUser = $self->{effectiveUser};
830 my $set = $self->{set};
831 my $problem = $self->{problem};
832 my $displayMode = $self->{displayMode};
833
834 # note: right now, we have to do things completely differently when we are
835 # rendering math from INSIDE the translator and from OUTSIDE the translator.
836 # so we'll just deal with each case explicitly here. there's some code
837 # duplication that can be dealt with later by abstracting out tth/dvipng/etc.
838
839 my $tex = $answerResult->{preview_latex_string};
840
841 return "" unless defined $tex and $tex ne "";
842
843 if ($displayMode eq "plainText") {
844 return $tex;
845 } elsif ($displayMode eq "formattedText") {
846 my $tthCommand = $ce->{externalPrograms}->{tth}
847 . " -L -f5 -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n"
848 . "\\(".$tex."\\)\n"
849 . "END_OF_INPUT\n";
850
851 # call tth
852 my $result = `$tthCommand`;
853 if ($?) {
854 return "<b>[tth failed: $? $@]</b>";
855 }
856 return $result;
857 } elsif ($displayMode eq "images") {
858 ## how are we going to name this?
859 #my $targetPathCommon = "/m2i/"
860 # . $effectiveUser->user_id . "."
861 # . $set->set_id . "."
862 # . $problem->problem_id . "."
863 # . $answerResult->{ans_name} . ".png";
864 #
865 ## figure out where to put things
866 #my $wd = makeTempDirectory($ce->{courseDirs}->{html_temp}, "webwork-dvipng");
867 #my $latex = $ce->{externalPrograms}->{latex};
868 #my $dvipng = $ce->{externalPrograms}->{dvipng};
869 #my $targetPath = $ce->{courseDirs}->{html_temp} . $targetPathCommon;
870 # # should use surePathToTmpFile, but we have to
871 # # isolate it from the problem enivronment first
872 #my $targetURL = $ce->{courseURLs}->{html_temp} . $targetPathCommon;
873 #
874 ## call dvipng to generate a preview
875 #dvipng($wd, $latex, $dvipng, $tex, $targetPath);
876 #rmtree($wd, 0, 0);
877 #if (-e $targetPath) {
878 # return "<img src=\"$targetURL\" alt=\"$tex\" />";
879 #} else {
880 # return "<b>[math2img failed]</b>";
881 #}
882 $imgGen->add($answerResult->{preview_latex_string});
883
884 }
885}
886
887##### logging subroutine ####
888
889
890
891##### permission queries #####
892
893# this stuff should be abstracted out into the permissions system
894# however, the permission system only knows about things in the
895# course environment and the username. hmmm...
896
897# also, i should fix these so that they have a consistent calling
898# format -- perhaps:
899# canPERM($courseEnv, $user, $set, $problem, $permissionLevel)
900
901sub canShowCorrectAnswers($$) {
902 my ($permissionLevel, $answerDate) = @_;
903 return $permissionLevel > 0 || time > $answerDate;
904}
905
906sub canShowSolutions($$) {
907 my ($permissionLevel, $answerDate) = @_;
908 return canShowCorrectAnswers($permissionLevel, $answerDate);
909}
910
911sub canRecordAnswers($$$$$) {
912 my ($permissionLevel, $openDate, $dueDate, $maxAttempts, $attempts) = @_;
913 my $permHigh = $permissionLevel > 0;
914 my $timeOK = time >= $openDate && time <= $dueDate;
915 my $attemptsOK = $maxAttempts == -1 || $attempts <= $maxAttempts;
916 my $recordAnswers = $permHigh || ($timeOK && $attemptsOK);
917 return $recordAnswers;
918}
919
920sub canCheckAnswers($$) {
921 my ($permissionLevel, $answerDate) = @_;
922 my $permHigh = $permissionLevel > 0;
923 my $timeOK = time >= $answerDate;
924 my $recordAnswers = $permHigh || $timeOK;
925 return $recordAnswers;
926}
927
928sub mustRecordAnswers($) {
929 my ($permissionLevel) = @_;
930 return $permissionLevel == 0;
193} 931}
194 932
1951; 9331;
196
197__END__
198
199my $foo =0;
200
201# The warning mechanism. This needs to be turned into an object of its own
202###############
203## Error message routines cribbed from CGI
204###############
205
206BEGIN { #error message routines cribbed from CGI
207
208 my $CarpLevel = 0; # How many extra package levels to skip on carp.
209 my $MaxEvalLen = 0; # How much eval '...text...' to show. 0 = all.
210
211 sub longmess {
212 my $error = shift;
213 my $mess = "";
214 my $i = 1 + $CarpLevel;
215 my ($pack,$file,$line,$sub,$eval,$require);
216
217 while (($pack,$file,$line,$sub,undef,undef,$eval,$require) = caller($i++)) {
218 if ($error =~ m/\n$/) {
219 $mess .= $error;
220 }
221 else {
222 if (defined $eval) {
223 if ($require) {
224 $sub = "require $eval";
225 }
226 else {
227 $eval =~ s/[\\\']/\\$&/g;
228 if ($MaxEvalLen && length($eval) > $MaxEvalLen) {
229 substr($eval,$MaxEvalLen) = '...';
230 }
231 $sub = "eval '$eval'";
232 }
233 }
234 elsif ($sub eq '(eval)') {
235 $sub = 'eval {...}';
236 }
237
238 $mess .= "\t$sub " if $error eq "called";
239 $mess .= "$error at $file line $line\n";
240 }
241
242 $error = "called";
243 }
244
245 $mess || $error;
246 }
247}
248###############
249### Our error messages for giving maximum feedback to the user for errors within problems.
250###############
251BEGIN {
252 sub PG_floating_point_exception_handler { # 1st argument is signal name
253 my($sig) = @_;
254 print "Content-type: text/html\n\n<H4>There was a floating point arithmetic error (exception SIG$sig )</H4>--perhaps
255 you divided by zero or took the square root of a negative number?
256 <BR>\n Use the back button to return to the previous page and recheck your entries.<BR>\n";
257 exit(0);
258 }
259
260 $SIG{'FPE'} = \&PG_floating_point_exception_handler;
261#!/usr/bin/perl -w
262 sub PG_warnings_handler {
263 my @input = @_;
264 my $msg_string = longmess(@_);
265 my @msg_array = split("\n",$msg_string);
266 my $out_string = '';
267
268 # Extra stack information is provided in this next block
269 # If the warning message does NOT end in \n then a line
270 # number is appended (see Perl manual about warn function)
271 # The presence of the line number is detected below and extra
272 # stack information is added.
273 # To suppress the line number and the extra stack information
274 # add \n to the end of a warn message (in .pl files. In .pg
275 # files add ~~n instead
276
277 if ($input[$#input]=~/line \d*\.\s*$/) {
278 $out_string .= "##More details: <BR>\n----";
279 foreach my $line (@msg_array) {
280 chomp($line);
281 next unless $line =~/\w+\:\:/;
282 $out_string .= "----" .$line . "<BR>\n";
283 }
284 }
285
286 $Global::WARNINGS .="* " . join("<BR>",@input) . "<BR>\n" . $out_string .
287 "<BR>\n--------------------------------------<BR>\n<BR>\n";
288 $Global::background_plain_url = $Global::background_warn_url;
289 $Global::bg_color = '#FF99CC'; #for warnings -- this change may come too late
290 }
291
292 $SIG{__WARN__}=\&PG_warnings_handler;
293
294 $SIG{__DIE__} = sub {
295 my $message = longmess(@_);
296 $message =~ s/\n/<BR>\n/;
297 my ($package, $filename, $line) = caller();
298 # use standard die for errors eminating from XML::Parser::Expat
299 # it uses a trapped eval which sometimes fails -- apparently on purpose
300 # and the error is handled by Expat itself. We don't want
301 # to interfer with that.
302
303 if ($package eq 'XML::Parser::Expat') {
304 die @_;
305 }
306 #print "$package $filename $line \n";
307 print
308 "Content-type: text/html\r\n\r\n <h4>Software error</h4> <p>\n\n$message\n<p>\n
309 Please inform the webwork meister.<p>\n
310 In addition to the error message above the following warnings were detected:
311 <HR>
312 $Global::WARNINGS;
313 <HR>
314 It's sometimes hard to tell exactly what has gone wrong since the
315 full error message may have been sent to
316 standard error instead of to standard out.
317 <p> To debug you can
318 <ul>
319 <li> guess what went wrong and try to fix it.
320 <li> call the offending script directly from the command line
321 of unix
322 <li> enable the debugging features by redefining
323 \$cgiURL in Global.pm and checking the redirection scripts in
324 system/cgi. This will force the standard error to be placed
325 in the standard out pipe as well.
326 <li> Run tail -f error_log <br>
327 from the unix command line to see error messages from the webserver.
328 The standard error output is being placed in the error_log file for the apache
329 web server. To run this command you have to be in the directory containing the
330 error_log or enter the full path name of the error_log. <p>
331 In a standard apache installation, this file is at /usr/local/apache/logs/error_log<p>
332 In a RedHat Linux installation, this file is at /var/log/httpd/error_log<p>
333 At Rochester this file is at /ww/logs/error_log.
334 </ul>
335 Good luck.<p>\n" ;
336 };
337
338
339
340}

Legend:
Removed from v.415  
changed lines
  Added in v.1326

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9