[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 392 Revision 2735
1################################################################################
2# WeBWorK Online Homework Delivery System
3# Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4# $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Problem.pm,v 1.159 2004/08/26 01:34:30 jj Exp $
5#
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
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.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14# Artistic License for more details.
15################################################################################
16
1package WeBWorK::ContentGenerator::Problem; 17package WeBWorK::ContentGenerator::Problem;
2our @ISA = qw(WeBWorK::ContentGenerator);
3use lib '/Users/gage/webwork/xmlrpc/daemon';
4use lib '/Users/gage/webwork-modperl/lib';
5use PGtranslator5;
6use WeBWorK::ContentGenerator; 18use base qw(WeBWorK::ContentGenerator);
7use Apache::Constants qw(:common);
8 19
20=head1 NAME
21
22WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem.
23
24=cut
25
26use strict;
27use warnings;
28use CGI qw();
29use File::Path qw(rmtree);
30use WeBWorK::Form;
31use WeBWorK::PG;
32use WeBWorK::PG::ImageGenerator;
33use WeBWorK::PG::IO;
34use WeBWorK::Utils qw(writeLog writeCourseLog encodeAnswers decodeAnswers ref2string makeTempDirectory);
35use WeBWorK::DB::Utils qw(global2user user2global findDefaults);
36use WeBWorK::Timing;
37
38use WeBWorK::Utils::Tasks qw(fake_set fake_problem);
39
9############################################################################### 40################################################################################
10# Configuration 41# CGI param interface to this module (up-to-date as of v1.153)
11############################################################################### 42################################################################################
12 43
13my $COURSE_SCRIPTS_DIRECTORY = '/Users/gage/webwork/system/courseScripts/'; 44# Standard params:
14my $MACRO_DIRECTORY = '/Users/gage/webwork/courseData/templates/macro/'; 45#
15my $TEMPLATE_DIRECTORY = '/Users/gage/webwork/rochester_problib/'; 46# user - user ID of real user
16my $TEMP_URL = 'http://127.0.0.1/~gage/rochester_problibtmp/'; 47# key - session key
17##my $HTML_DIRECTORY = '/Users/gage/Sites/rochester_problib/' #already obtained from courseEnvironment 48# effectiveUser - user ID of effective user
18my $HTML_URL = 'http://127.0.0.1/~gage/rochester_problib/'; 49#
50# Integration with PGProblemEditor:
51#
52# editMode - if set, indicates alternate problem source location.
53# can be "temporaryFile" or "savedFile".
54#
55# sourceFilePath - path to file to be edited
56# problemSeed - force problem seed to value
57# success - success message to display
58# failure - failure message to display
59#
60# Rendering options:
61#
62# displayMode - name of display mode to use
63#
64# showOldAnswers - request that last entered answer be shown (if allowed)
65# showCorrectAnswers - request that correct answers be shown (if allowed)
66# showHints - request that hints be shown (if allowed)
67# showSolutions - request that solutions be shown (if allowed)
68#
69# Problem interaction:
70#
71# AnSwEr# - answer blanks in problem
72#
73# redisplay - name of the "Redisplay Problem" button
74# submitAnswers - name of "Submit Answers" button
75# checkAnswers - name of the "Check Answers" button
76# previewAnswers - name of the "Preview Answers" button
19 77
20############################################################################### 78################################################################################
21# End configuration 79# "can" methods
22############################################################################### 80################################################################################
23 81
24sub title { 82# Subroutines to determine if a user "can" perform an action. Each subroutine is
25 my ($self, $problem_set, $problem) = @_; 83# called with the following arguments:
26 my $r = $self->{r}; 84#
27 my $user = $r->param('user'); 85# ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem)
28 return "Problem $problem of problem set $problem_set for $user";
29}
30 86
87sub can_showOldAnswers {
88 #my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_;
89
90 return 1;
91}
92
93sub can_showCorrectAnswers {
94 my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_;
95 my $authz = $self->r->authz;
96
97 return
98 after($Set->answer_date)
99 ||
100 $authz->hasPermissions($User->user_id, "show_correct_answers_before_answer_date")
101 ;
102}
103
104sub can_showHints {
105 #my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_;
106
107 return 1;
108}
109
110sub can_showSolutions {
111 my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem) = @_;
112 my $authz = $self->r->authz;
113
114 return
115 after($Set->answer_date)
116 ||
117 $authz->hasPermissions($User->user_id, "show_solutions_before_answer_date")
118 ;
119}
120
121sub can_recordAnswers {
122 my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_;
123 my $authz = $self->r->authz;
124 my $thisAttempt = $submitAnswers ? 1 : 0;
125 if ($User->user_id ne $EffectiveUser->user_id) {
126 return $authz->hasPermissions($User->user_id, "record_answers_when_acting_as_student");
127 }
128 if (before($Set->open_date)) {
129 return $authz->hasPermissions($User->user_id, "record_answers_before_open_date");
130 } elsif (between($Set->open_date, $Set->due_date)) {
131 my $max_attempts = $Problem->max_attempts;
132 my $attempts_used = $Problem->num_correct + $Problem->num_incorrect + $thisAttempt;
133 if ($max_attempts == -1 or $attempts_used < $max_attempts) {
134 return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_with_attempts");
135 } else {
136 return $authz->hasPermissions($User->user_id, "record_answers_after_open_date_without_attempts");
137 }
138 } elsif (between($Set->due_date, $Set->answer_date)) {
139 return $authz->hasPermissions($User->user_id, "record_answers_after_due_date");
140 } elsif (after($Set->answer_date)) {
141 return $authz->hasPermissions($User->user_id, "record_answers_after_answer_date");
142 }
143}
144
145sub can_checkAnswers {
146 my ($self, $User, $PermissionLevel, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_;
147 my $authz = $self->r->authz;
148 my $thisAttempt = $submitAnswers ? 1 : 0;
149
150 if (before($Set->open_date)) {
151 return $authz->hasPermissions($User->user_id, "check_answers_before_open_date");
152 } elsif (between($Set->open_date, $Set->due_date)) {
153 my $max_attempts = $Problem->max_attempts;
154 my $attempts_used = $Problem->num_correct + $Problem->num_incorrect + $thisAttempt;
155 if ($max_attempts == -1 or $attempts_used < $max_attempts) {
156 return $authz->hasPermissions($User->user_id, "check_answers_after_open_date_with_attempts");
157 } else {
158 return $authz->hasPermissions($User->user_id, "check_answers_after_open_date_without_attempts");
159 }
160 } elsif (between($Set->due_date, $Set->answer_date)) {
161 return $authz->hasPermissions($User->user_id, "check_answers_after_due_date");
162 } elsif (after($Set->answer_date)) {
163 return $authz->hasPermissions($User->user_id, "check_answers_after_answer_date");
164 }
165}
166
167# Helper functions for calculating times
168sub before { return time <= $_[0] }
169sub after { return time >= $_[0] }
170sub between { my $t = time; return $t > $_[0] && $t < $_[1] }
171
31############################################################################### 172################################################################################
32# 173# output utilities
33# INITIALIZATION
34#
35# The following code initializes an instantiation of PGtranslator5 in the
36# parent process. This initialized object is then share with each of the
37# children forked from this parent process by the daemon.
38#
39# As far as I can tell, the child processes don't share any variable values even
40# though their namespaces are the same.
41############################################################################### 174################################################################################
42# First some dummy values to use for testing.
43# These should be available from the problemEnvironment(it might be ok to assume that PG and dangerousMacros
44# live in the courseScripts (system level macros) directory.
45 175
46print STDERR "Begin intitalization\n"; 176sub attemptResults {
47my $dummy_envir = { courseScriptsDirectory => $COURSE_SCRIPTS_DIRECTORY, 177 my $self = shift;
48 displayMode => 'HTML_tth', 178 my $pg = shift;
49 macroDirectory => $MACRO_DIRECTORY, 179 my $showAttemptAnswers = shift;
50 cgiURL => 'foo_cgiURL'}; 180 my $showCorrectAnswers = shift;
51 181 my $showAttemptResults = $showAttemptAnswers && shift;
52 182 my $showSummary = shift;
53my $PG_PL = "${COURSE_SCRIPTS_DIRECTORY}PG.pl"; 183 my $showAttemptPreview = shift || 0;
54my $DANGEROUS_MACROS_PL = "${COURSE_SCRIPTS_DIRECTORY}dangerousMacros.pl";
55my @MODULE_LIST = ( "Exporter", "DynaLoader", "GD", "WWPlot", "Fun",
56 "Circle", "Label", "PGrandom", "Units", "Hermite",
57 "List", "Match","Multiple", "Select", "AlgParser",
58 "AnswerHash", "Fraction", "VectorField", "Complex1",
59 "Complex", "MatrixReal1", "Matrix","Distributions",
60 "Regression"
61);
62my @EXTRA_PACKAGES = ( "AlgParserWithImplicitExpand", "Expr",
63 "ExprWithImplicitExpand", "AnswerEvaluator",
64
65);
66$INITIAL_MACRO_PACKAGES = <<END_OF_TEXT;
67 DOCUMENT();
68 loadMacros(
69 "PGbasicmacros.pl",
70 "PGchoicemacros.pl",
71 "PGanswermacros.pl",
72 "PGnumericalmacros.pl",
73 "PGgraphmacros.pl",
74 "PGauxiliaryFunctions.pl",
75 "PGmatrixmacros.pl",
76 "PGcomplexmacros.pl",
77 "PGstatisticsmacros.pl"
78
79 );
80
81 TEXT("Hello world");
82
83 ENDDOCUMENT();
84
85END_OF_TEXT
86 184
87#These here documents have their drawbacks. KEEP END_OF_TEXT left justified!!!!!!
88
89###############################################################################
90# Now to define the body subroutine which does the hard work.
91###############################################################################
92
93
94#my $SOURCE1 = $INITIAL_MACRO_PACKAGES;
95
96sub body {
97 my ($self, $problem_set, $problem) = @_;
98 my $r = $self->{r}; 185 my $ce = $self->r->ce;
99 my $courseEnvironment = $self->{courseEnvironment};
100 my $user = $r->param('user');
101 186
187 my $problemResult = $pg->{result}; # the overall result of the problem
188 my @answerNames = @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} };
102 189
103 my $SOURCE1 = readFile('set0/prob1c.pg'); 190 my $showMessages = $showAttemptAnswers && grep { $pg->{answers}->{$_}->{ans_message} } @answerNames;
104 print STDERR "SOURCEFILE: \n$SOURCE1\n\n";
105 191
106 ########################################################################### 192 my $basename = "equation-" . $self->{set}->psvn. "." . $self->{problem}->problem_id . "-preview";
107 # The pg problem class should have a method for installing it's problemEnvironment
108 ###########################################################################
109 193
110 $problemEnvir_rh = defineProblemEnvir($self); 194 # to make grabbing these options easier, we'll pull them out now...
195 my %imagesModeOptions = %{$ce->{pg}->{displayModeOptions}->{images}};
111 196
112 197 my $imgGen = WeBWorK::PG::ImageGenerator->new(
113 ################################################################################## 198 tempDir => $ce->{webworkDirs}->{tmp},
114 # Prime the PGtranslator object and set it loose 199 latex => $ce->{externalPrograms}->{latex},
115 ################################################################################## 200 dvipng => $ce->{externalPrograms}->{dvipng},
116 201 useCache => 1,
117 202 cacheDir => $ce->{webworkDirs}->{equationCache},
118 ############################################################################### 203 cacheURL => $ce->{webworkURLs}->{equationCache},
119 204 cacheDB => $ce->{webworkFiles}->{equationCacheDB},
120 ############################################################################### 205 dvipng_align => $imagesModeOptions{dvipng_align},
121 #Create the PG translator. 206 dvipng_depth_db => $imagesModeOptions{dvipng_depth_db},
122 ###############################################################################
123
124 my $pt = new PGtranslator5; #pt stands for problem translator;
125
126
127 # All of these hard coded directories need to be drawn from courseEnvironment.
128 # In addition I don't think that PGtranslator uses this stack internally yet.
129 # Passing these directories through the problemEnvironment variable is what
130 # is currently being done, but I don't think it is quite right, at least for most
131 # of them.
132
133
134 $pt ->rh_directories( { courseScriptsDirectory => $COURSE_SCRIPTS_DIRECTORY,
135 macroDirectory => $MACRO_DIRECTORY,
136 ,
137 templateDirectory => $TEMPLATE_DIRECTORY,
138 tempDirectory => $TEMP_DIRECTORY,
139 }
140 ); 207 );
141 208
142 ############################################################################### 209 my $header;
143 # First we load the modules from courseScripts directory. 210 #$header .= CGI::th("Part");
144 # These do the "heavy lifting" in terms of formatting, creating graphs, and 211 $header .= $showAttemptAnswers ? CGI::th("Entered") : "";
145 # performing other heavy duty algorithms. 212 $header .= $showAttemptPreview ? CGI::th("Answer Preview") : "";
146 # 213 $header .= $showCorrectAnswers ? CGI::th("Correct") : "";
147 ############################################################################### 214 $header .= $showAttemptResults ? CGI::th("Result") : "";
148 215 $header .= $showMessages ? CGI::th("Messages") : "";
149 $pt -> evaluate_modules( @MODULE_LIST); 216 my @tableRows = ( $header );
150 $pt -> load_extra_packages( @EXTRA_PACKAGES ); 217 my $numCorrect = 0;
151 218 foreach my $name (@answerNames) {
152 ############################################################################### 219 my $answerResult = $pg->{answers}->{$name};
153 # Load the environment constants. Some are used by the PGtranslator object but 220 my $studentAnswer = $answerResult->{student_ans}; # original_student_ans
154 # most of them are installed inside the Safe compartment where the problem 221 my $preview = ($showAttemptPreview
155 # runs. 222 ? $self->previewAnswer($answerResult, $imgGen)
156 ############################################################################### 223 : "");
157 #$pt -> environment($dummy_envir); 224 my $correctAnswer = $answerResult->{correct_ans};
158 $pt -> environment($problemEnvir_rh); 225 my $answerScore = $answerResult->{score};
159 226 my $answerMessage = $showMessages ? $answerResult->{ans_message} : "";
160 227 #FIXME --Can we be sure that $answerScore is an integer-- could the problem give partial credit?
161 # I've forgotten what this does exactly :-) 228 $numCorrect += $answerScore > 0;
162 $pt->initialize(); 229 my $resultString = $answerScore == 1 ? "correct" : "incorrect";
163 230
164 ############################################################################### 231 # get rid of the goofy prefix on the answer names (supposedly, the format
165 # PG.pl contains the basic code which defines the problem interface, input and output. 232 # of the answer names is changeable. this only fixes it for "AnSwEr"
166 # dangerousMacros.pl contains subroutines which have access to the hard drive and 233 #$name =~ s/^AnSwEr//;
167 # and the directory structure. All use of external resources by the problem is supposed 234
168 # to go through these subroutines. The idea is to put the potentially dangerous 235 my $row;
169 # algorithms in on place so they can be watched closely. 236 #$row .= CGI::td($name);
170 # These two files are evaluated in the Safe compartment without any restrictions. 237 $row .= $showAttemptAnswers ? CGI::td($self->nbsp($studentAnswer)) : "";
171 # They have full use of the perl commands. 238 $row .= $showAttemptPreview ? CGI::td($self->nbsp($preview)) : "";
172 ############################################################################### 239 $row .= $showCorrectAnswers ? CGI::td($self->nbsp($correctAnswer)) : "";
173 my $loadErrors = $pt -> unrestricted_load($PG_PL ); 240 $row .= $showAttemptResults ? CGI::td($self->nbsp($resultString)) : "";
174 print STDERR "$loadErrors\n" if ($loadErrors); 241 $row .= $showMessages ? CGI::td($self->nbsp($answerMessage)) : "";
175 $loadErrors = $pt -> unrestricted_load($DANGEROUS_MACROS_PL); 242 push @tableRows, $row;
176 print STDERR "$loadErrors\n" if ($loadErrors);
177
178 ###############################################################################
179 # Now set the mask to restrict the operations which can be performed within
180 # a problem or a macro file.
181 ###############################################################################
182 $pt-> set_mask();
183
184 # print "\nPG.pl: $PG_PL<br>\n";
185 # print "DANGEROUS_MACROS_PL: $DANGEROUS_MACROS_PL<br>\n";
186 # print "Print dummy environment<br>\n";
187 # print pretty_print_rh($dummy_envir), "<p>\n\n";
188
189 # Read in the source code for the problem
190
191 #$INITIAL_MACRO_PACKAGES =~ tr /\r/\n/; # change everything to unix line endings.
192 $SOURCE1 =~ tr /\r/\n/;
193 #print STDERR "Source again \n $SOURCE1";
194 $pt->source_string( $SOURCE1 );
195
196 ###############################################################################
197 # Install a safety filter for screening student answers. The default is now the blank
198 # filter since the answer evaluators do a pretty good job of recompiling and screening
199 # student's answers. Still, you could prohibit back ticks, or something of the kind.
200 ###############################################################################
201
202 $pt ->rf_safety_filter( \&safetyFilter); # install blank safety filter
203
204
205 print STDERR "New PGtranslator object inititialization completed.<br>\n";
206 ################################################################################
207 ## This ends the initialization of the PGtranslator object
208 ################################################################################
209
210
211 ################################################################################
212 # Run the problem (output the html text) but also store it within the object.
213 # The correct answers are also calculated and stored within the object
214 ################################################################################
215 $pt ->translate();
216
217 #print problem output
218 print "Problem goes here<p>\n";
219 print "Problem output <br>\n";
220 print "################################################################################<br<br>";
221 print ${$pt->r_text()};
222 print "<br><br>################################################################################<br>";
223 print "<p>End of problem output<br>";
224
225
226 #print source code
227 print "Source code<pre>\n";
228 print $SOURCE1;
229 print "</pre>End source code<p>";
230 ################################################################################
231 # The format for the output is described here. We'll need a local variable
232 # to handle the warnings. From within the problem the warning command
233 # has been slaved to the __WARNINGS__ routine which is defined in Global.
234 # We'll need to provide an alternate mechanism.
235 # The base64 encoding is only needed for xml transmission.
236 ################################################################################
237 print "################################################################################<br>";
238 print "Warnings output<br>";
239 my $WARNINGS = "Let this be a warning:";
240
241 print $WARNINGS;
242
243 ################################################################################
244 # Install the standard problem grader. See gage/xmlrpc/daemon.pm or processProblem8 for detailed
245 # code on how to choose which problem grader to install, depending on courseEnvironment and problem data.
246 # See also PG.pl which provides for problem by problem overrides.
247 ################################################################################
248
249 $pt->rf_problem_grader($pt->rf_std_problem_grader);
250
251 ################################################################################
252 # creates and stores a hash of answer results inside the object: $rh_answer_results
253 ################################################################################
254 $pt -> process_answers($rh->{envir}->{inputs_ref});
255
256
257 # THE UPDATE AND GRADING LOGIC COULD USE AN OVERHAUL. IT WAS SOMEWHAT CONSTRAINED
258 # BY LEGACY CONDITIONS IN THE ORIGINAL PROCESSPROBLEM8. IT'S NOT BAD
259 # BUT IT COULD PROBABLY BE MADE A LITTLE MORE STRAIGHT FORWARD.
260 ################################################################################
261 # updates the problem state stored by the translator object from the problemEnvironment data
262 ################################################################################
263
264 # $pt->rh_problem_state({ recorded_score => $rh->{problem_state}->{recorded_score},
265 # num_of_correct_ans => $rh->{problem_state}->{num_of_correct_ans} ,
266 # num_of_incorrect_ans => $rh->{problem_state}->{num_of_incorrect_ans}
267 # } );
268 ################################################################################
269 # grade the problem (and update the problem state again.)
270 ################################################################################
271
272 # Define an entry order -- the default is the order they are received from the browser.
273 # (Which as I understand it is NOT guaranteed to be the Left->Right Up-> Down order we're
274 # used to in the West.
275
276 my %PG_FLAGS = $pt->h_flags;
277 my $ra_answer_entry_order = ( defined($PG_FLAGS{ANSWER_ENTRY_ORDER}) ) ?
278 $PG_FLAGS{ANSWER_ENTRY_ORDER} : [ keys %{$pt->rh_evaluated_answers} ] ;
279 # Decide whether any answers were submitted.
280 my $answers_submitted = 0;
281 $answers_submitted = 1 if defined( $rh->{answer_form_submitted} ) and 1 == $rh->{answer_form_submitted};
282 # If there are answers, grade them
283 my ($rh_problem_result,$rh_problem_state) = $pt->grade_problem( answers_submitted => $answers_submitted,
284 ANSWER_ENTRY_ORDER => $ra_answer_entry_order
285 ); # grades the problem.
286
287 # Output format expected by Webwork.pm (and I believe processProblem8, but check.)
288 my $out = {
289 text => ${$pt ->r_text()}, # encode_base64( ${$pt ->r_text()} ),
290 header_text => $pt->r_header, # encode_base64( ${ $pt->r_header } ),
291 answers => $pt->rh_evaluated_answers,
292 errors => $pt-> errors(),
293 WARNINGS => $WARNINGS, #encode_base64($WARNINGS ),
294 problem_result => $rh_problem_result,
295 problem_state => $rh_problem_state,
296 PG_flag => \%PG_flag
297 };
298 ##########################################################################################
299 # Debugging printout of environment tables
300 ##########################################################################################
301
302 print "<P>Request item<P>\n\n";
303 print "<TABLE border=\"3\">";
304 print $self->print_form_data('<tr><td>','</td><td>','</td></tr>');
305 print "</table>\n";
306 print "path info <br>\n";
307 print $r->path_info();
308 print "<P>\n\ncourseEnvironment<P>\n\n";
309 print pretty_print_rh($courseEnvironment);
310 print "<P>\n\nproblemEnvironment<P>\n\n";
311 print pretty_print_rh($problemEnvir_rh);
312
313 ##########################################################################################
314 # End
315 ##########################################################################################
316 "";
317}
318# End the"body" routine for the Problem object.
319
320
321
322
323
324
325########################################################################################
326# This is the problemEnvironment structure that needs to be filled out in order to provide
327# information to PGtranslator which in turn supports the problem environment
328########################################################################################
329
330sub defineProblemEnvir {
331 my $self = shift;
332 my $r = $self->{r};
333 my $courseEnvironment = $self->{courseEnvironment};
334 my %envir=();
335# $envir{'refSubmittedAnswers'} = $refSubmittedAnswers if defined($refSubmittedAnswers);
336 $envir{'psvnNumber'} = 123456789;
337 $envir{'psvn'} = 123456789;
338 $envir{'studentName'} = 'Jane Doe';
339 $envir{'studentLogin'} = 'jd001m';
340 $envir{'studentID'} = 'xxx-xx-4321';
341 $envir{'sectionName'} = 'gage';
342 $envir{'sectionNumber'} = '111foobar';
343 $envir{'recitationName'} = 'gage_recitation';
344 $envir{'recitationNumber'} = '11_foobar recitation';
345 $envir{'setNumber'} = 'setAlgebraicGeometry';
346 $envir{'questionNumber'} = 43;
347 $envir{'probNum'} = 43;
348 $envir{'openDate'} = 3014438528;
349 $envir{'formattedOpenDate'} = '3/4/02';
350 $envir{'dueDate'} = 4014438528;
351 $envir{'formattedDueDate'} = '10/4/04';
352 $envir{'answerDate'} = 4014438528;
353 $envir{'formattedAnswerDate'} = '10/4/04';
354 $envir{'problemValue'} = 1;
355 $envir{'fileName'} = 'problem1';
356 $envir{'probFileName'} = 'problem1';
357 $envir{'languageMode'} = 'HTML_tth';
358 $envir{'displayMode'} = 'HTML_tth';
359 $envir{'outputMode'} = 'HTML_tth';
360 $envir{'courseName'} = $courseEnvironment ->{courseName};
361 $envir{'sessionKey'} = 'asdf';
362
363# initialize constants for PGanswermacros.pl
364 $envir{'numRelPercentTolDefault'} = .1;
365 $envir{'numZeroLevelDefault'} = 1E-14;
366 $envir{'numZeroLevelTolDefault'} = 1E-12;
367 $envir{'numAbsTolDefault'} = .001;
368 $envir{'numFormatDefault'} = '';
369 $envir{'functRelPercentTolDefault'} = .1;
370 $envir{'functZeroLevelDefault'} = 1E-14;
371 $envir{'functZeroLevelTolDefault'} = 1E-12;
372 $envir{'functAbsTolDefault'} = .001;
373 $envir{'functNumOfPoints'} = 3;
374 $envir{'functVarDefault'} = 'x';
375 $envir{'functLLimitDefault'} = .0000001;
376 $envir{'functULimitDefault'} = .9999999;
377 $envir{'functMaxConstantOfIntegration'} = 1E8;
378# kludge check definition of number of attempts again. The +1 is because this is used before the current answer is evaluated.
379 $envir{'numOfAttempts'} = 2; #&getProblemNumOfCorrectAns($probNum,$psvn)
380 # &getProblemNumOfIncorrectAns($probNum,$psvn)+1;
381
382#
383#
384# defining directorys and URLs
385 $envir{'templateDirectory'} = $courseEnvironment ->{courseDirs}->{templates};
386############ $envir{'classDirectory'} = $Global::classDirectory;
387# $envir{'cgiDirectory'} = $Global::cgiDirectory;
388# $envir{'cgiURL'} = getWebworkCgiURL();
389
390# $envir{'scriptDirectory'} = $Global::scriptDirectory;##omit
391 $envir{'webworkDocsURL'} = 'http://webwork.math.rochester.edu';
392 $envir{'externalTTHPath'} = '/usr/local/bin/tth';
393
394
395#
396 $envir{'inputs_ref'} = $r->param;
397 $envir{'problemSeed'} = 3245;
398 $envir{'displaySolutionsQ'} = 1;
399 $envir{'displayHintsQ'} = 1;
400
401# Directory values -- do we really need them here?
402 $envir{courseScriptsDirectory} = $COURSE_SCRIPTS_DIRECTORY;
403 $envir{macroDirectory} = $MACRO_DIRECTORY;
404 $envir{templateDirectory} = $TEMPLATE_DIRECTORY;
405 $envir{tempDirectory} = $TEMP_DIRECTORY;
406 $envir{tempURL} = $TEMP_URL;
407 $envir{htmlURL} = $HTML_URL;
408 $envir{'htmlDirectory'} = $courseEnvironment ->{courseDirectory}->{html};
409 # here is a way to pass environment variables defined in webworkCourse.ph
410# my $k;
411# foreach $k (keys %Global::PG_environment ) {
412# $envir{$k} = $Global::PG_environment{$k};
413# } 243 }
414 \%envir; 244
245 # render equation images
246 $imgGen->render(refresh => 1);
247
248# my $numIncorrectNoun = scalar @answerNames == 1 ? "question" : "questions";
249 my $scorePercent = sprintf("%.0f%%", $problemResult->{score} * 100);
250# FIXME -- I left the old code in in case we have to back out.
251# my $summary = "On this attempt, you answered $numCorrect out of "
252# . scalar @answerNames . " $numIncorrectNoun correct, for a score of $scorePercent.";
253 my $summary = "";
254 if (scalar @answerNames == 1) {
255 if ($numCorrect == scalar @answerNames) {
256 $summary .= CGI::div({class=>"ResultsWithoutError"},"The above answer is correct.");
257 } else {
258 $summary .= CGI::div({class=>"ResultsWithError"},"The above answer is NOT correct.");
259 }
260 } else {
261 if ($numCorrect == scalar @answerNames) {
262 $summary .= CGI::div({class=>"ResultsWithoutError"},"All of the above answers are correct.");
263 } else {
264 $summary .= CGI::div({class=>"ResultsWithError"},"At least one of the above answers is NOT correct.");
265 }
266 }
267
268 return
269 CGI::table({-class=>"attemptResults"}, CGI::Tr(\@tableRows))
270 . ($showSummary ? CGI::p({class=>'emphasis'},$summary) : "");
415} 271}
416 272
417######################################################################################## 273sub viewOptions {
418# This recursive pretty_print function will print a hash and its sub hashes. 274 my ($self) = @_;
419######################################################################################## 275 my $ce = $self->r->ce;
420sub pretty_print_rh { 276
421 my $r_input = shift; 277 # don't show options if we don't have anything to show
422 my $out = ''; 278 return if $self->{invalidSet} or $self->{invalidProblem};
423 if ( not ref($r_input) ) { 279 return unless $self->{isOpen};
424 $out = $r_input; # not a reference 280
425 } elsif (is_hash_ref($r_input)) { 281 my $displayMode = $self->{displayMode};
426 local($^W) = 0; 282 my %must = %{ $self->{must} };
427 $out .= "<TABLE border = \"2\" cellpadding = \"3\" BGCOLOR = \"#FFFFFF\">"; 283 my %can = %{ $self->{can} };
428 foreach my $key (sort keys %$r_input ) { 284 my %will = %{ $self->{will} };
429 $out .= "<tr><TD> $key</TD><TD>=&gt;</td><td>&nbsp;".pretty_print_rh($r_input->{$key}) . "</td></tr>"; 285
286 my $optionLine;
287 $can{showOldAnswers} and $optionLine .= join "",
288 "Show: &nbsp;".CGI::br(),
289 CGI::checkbox(
290 -name => "showOldAnswers",
291 -checked => $will{showOldAnswers},
292 -label => "Saved answers",
293 ), "&nbsp;&nbsp;".CGI::br();
294
295 $optionLine and $optionLine .= join "", CGI::br();
296
297 my %display_modes = %{WeBWorK::PG::DISPLAY_MODES()};
298 my @active_modes = grep { exists $display_modes{$_} }
299 @{$ce->{pg}->{displayModes}};
300 my $modeLine = (scalar(@active_modes) > 1) ?
301 "View&nbsp;equations&nbsp;as:&nbsp;&nbsp;&nbsp;&nbsp;".CGI::br().
302 CGI::radio_group(
303 -name => "displayMode",
304 -values => \@active_modes,
305 -default => $displayMode,
306 -linebreak=>'true',
307 -labels => {
308 plainText => "plain",
309 formattedText => "formatted",
310 images => "images",
311 jsMath => "jsMath",
312 asciimath => "asciimath",
313 },
314 ). CGI::br().CGI::hr() : '';
315
316 return CGI::div({-style=>"border: thin groove; padding: 1ex; margin: 2ex align: left"},
317 $modeLine,
318 $optionLine,
319 CGI::submit(-name=>"redisplay", -label=>"Apply Options"),
320 );
321}
322
323sub previewAnswer {
324 my ($self, $answerResult, $imgGen) = @_;
325 my $ce = $self->r->ce;
326 my $effectiveUser = $self->{effectiveUser};
327 my $set = $self->{set};
328 my $problem = $self->{problem};
329 my $displayMode = $self->{displayMode};
330
331 # note: right now, we have to do things completely differently when we are
332 # rendering math from INSIDE the translator and from OUTSIDE the translator.
333 # so we'll just deal with each case explicitly here. there's some code
334 # duplication that can be dealt with later by abstracting out tth/dvipng/etc.
335
336 my $tex = $answerResult->{preview_latex_string};
337
338 return "" unless defined $tex and $tex ne "";
339
340 if ($displayMode eq "plainText") {
341 return $tex;
342 } elsif ($displayMode eq "formattedText") {
343 my $tthCommand = $ce->{externalPrograms}->{tth}
344 . " -L -f5 -r 2> /dev/null <<END_OF_INPUT; echo > /dev/null\n"
345 . "\\(".$tex."\\)\n"
346 . "END_OF_INPUT\n";
347
348 # call tth
349 my $result = `$tthCommand`;
350 if ($?) {
351 return "<b>[tth failed: $? $@]</b>";
352 } else {
353 return $result;
430 } 354 }
431 $out .="</table>"; 355 } elsif ($displayMode eq "images") {
432 } elsif (is_array_ref($r_input) ) { 356 $imgGen->add($tex);
433 my @array = @$r_input; 357 } elsif ($displayMode eq "jsMath") {
434 $out .= "( " ; 358 return '<DIV CLASS="math">'.$tex.'</DIV>' ;
435 while (@array) {
436 $out .= pretty_print_rh(shift @array) . " , ";
437 } 359 }
438 $out .= " )"; 360}
439 } elsif (ref($r_input) eq 'CODE') { 361
440 $out = "$r_input"; 362################################################################################
363# Template escape implementations
364################################################################################
365
366sub pre_header_initialize {
367 my ($self) = @_;
368 my $r = $self->r;
369 my $ce = $r->ce;
370 my $db = $r->db;
371 my $authz = $r->authz;
372 my $urlpath = $r->urlpath;
373
374 my $setName = $urlpath->arg("setID");
375 my $problemNumber = $r->urlpath->arg("problemID");
376 my $userName = $r->param('user');
377 my $effectiveUserName = $r->param('effectiveUser');
378 my $key = $r->param('key');
379
380 my $user = $db->getUser($userName); # checked
381 die "record for user $userName (real user) does not exist."
382 unless defined $user;
383
384 my $effectiveUser = $db->getUser($effectiveUserName); # checked
385 die "record for user $effectiveUserName (effective user) does not exist."
386 unless defined $effectiveUser;
387
388 my $PermissionLevel = $db->getPermissionLevel($userName); # checked
389 die "permission level record for user $userName does not exist (but the user does? odd...)"
390 unless defined $PermissionLevel;
391 my $permissionLevel = $PermissionLevel->permission;
392
393 # obtain the merged set for $effectiveUser
394 my $set = $db->getMergedSet($effectiveUserName, $setName); # checked
395
396 # obtain the merged problem for $effectiveUser
397 my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked
398
399 my $editMode = $r->param("editMode");
400
401 if ($authz->hasPermissions($userName, "modify_problem_sets")) {
402 # professors are allowed to fabricate sets and problems not
403 # assigned to them (or anyone). this allows them to use the
404 # editor to
405
406 # if that is not yet defined obtain the global set, convert
407 # it to a user set, and add fake user data
408 unless (defined $set) {
409 my $userSetClass = $db->{set_user}->{record};
410 my $globalSet = $db->getGlobalSet($setName); # checked
411 # if the global set doesn't exist either, bail!
412 if(not defined $globalSet) {
413 $set = fake_set($db);
441 } else { 414 } else {
442 $out = $r_input; 415 $set = global2user($userSetClass, $globalSet);
443 } 416 $set->psvn(0);
444 $out;
445}
446 417
447sub is_hash_ref { 418 # FIXME: This is a temporary fix to fill in the database
448 my $in =shift; 419 # We want the published field to contain either 1 or 0 so if it has not been set to 0, default to 1
449 my $save_SIG_die_trap = $SIG{__DIE__}; 420 # this will fill in all the empty fields but not change anything that has been specifically set to 1 or 0
450 $SIG{__DIE__} = sub {CORE::die(@_) }; 421 $globalSet->published("1") unless $globalSet->published eq "0";
451 my $out = eval{ %{ $in } }; 422 $db->putGlobalSet($globalSet);
452 $out = ($@ eq '') ? 1 : 0;
453 $@='';
454 $SIG{__DIE__} = $save_SIG_die_trap;
455 $out;
456}
457sub is_array_ref {
458 my $in =shift;
459 my $save_SIG_die_trap = $SIG{__DIE__};
460 $SIG{__DIE__} = sub {CORE::die(@_) };
461 my $out = eval{ @{ $in } };
462 $out = ($@ eq '') ? 1 : 0;
463 $@='';
464 $SIG{__DIE__} = $save_SIG_die_trap;
465 $out;
466}
467
468######
469# Utility for slurping souce files
470#######
471
472sub readFile {
473 my $input = shift; # The set and problem: 'set0/prob1.pg'
474 my $filePath =$TEMPLATE_DIRECTORY .$input;
475 print STDERR "Reading problem from file $filePath \n";
476 print STDERR "<br>Reading problem from file $filePath <br>\n";
477 my $out;
478 print "The file is readable = ", -r $filePath, "\n";
479 if (-r $filePath) {
480 open IN, "<$filePath" or print STDERR "Hey, this file was supposed to be readable\n";
481 local($/)=undef;
482 $out = <IN>;
483 close(IN);
484 } else {
485 print "Could not read file at |$filePath|";
486 print STDERR "Could not read file at |$filePath|";
487 }
488 return($out);
489}
490
491my $foo =0;
492
493# The warning mechanism. This needs to be turned into an object of its own
494###############
495## Error message routines cribbed from CGI
496###############
497
498BEGIN { #error message routines cribbed from CGI
499
500 my $CarpLevel = 0; # How many extra package levels to skip on carp.
501 my $MaxEvalLen = 0; # How much eval '...text...' to show. 0 = all.
502
503 sub longmess {
504 my $error = shift;
505 my $mess = "";
506 my $i = 1 + $CarpLevel;
507 my ($pack,$file,$line,$sub,$eval,$require);
508
509 while (($pack,$file,$line,$sub,undef,undef,$eval,$require) = caller($i++)) {
510 if ($error =~ m/\n$/) {
511 $mess .= $error;
512 }
513 else {
514 if (defined $eval) {
515 if ($require) {
516 $sub = "require $eval";
517 }
518 else {
519 $eval =~ s/[\\\']/\\$&/g;
520 if ($MaxEvalLen && length($eval) > $MaxEvalLen) {
521 substr($eval,$MaxEvalLen) = '...';
522 }
523 $sub = "eval '$eval'";
524 }
525 }
526 elsif ($sub eq '(eval)') {
527 $sub = 'eval {...}';
528 }
529
530 $mess .= "\t$sub " if $error eq "called";
531 $mess .= "$error at $file line $line\n";
532 }
533
534 $error = "called";
535 }
536
537 $mess || $error;
538 }
539}
540###############
541### Our error messages for giving maximum feedback to the user for errors within problems.
542###############
543BEGIN {
544 sub PG_floating_point_exception_handler { # 1st argument is signal name
545 my($sig) = @_;
546 print "Content-type: text/html\n\n<H4>There was a floating point arithmetic error (exception SIG$sig )</H4>--perhaps
547 you divided by zero or took the square root of a negative number?
548 <BR>\n Use the back button to return to the previous page and recheck your entries.<BR>\n";
549 exit(0);
550 }
551
552 $SIG{'FPE'} = \&PG_floating_point_exception_handler;
553
554 sub PG_warnings_handler {
555 my @input = @_;
556 my $msg_string = longmess(@_);
557 my @msg_array = split("\n",$msg_string);
558 my $out_string = '';
559
560 # Extra stack information is provided in this next block
561 # If the warning message does NOT end in \n then a line
562 # number is appended (see Perl manual about warn function)
563 # The presence of the line number is detected below and extra
564 # stack information is added.
565 # To suppress the line number and the extra stack information
566 # add \n to the end of a warn message (in .pl files. In .pg
567 # files add ~~n instead
568
569 if ($input[$#input]=~/line \d*\.\s*$/) {
570 $out_string .= "##More details: <BR>\n----";
571 foreach my $line (@msg_array) {
572 chomp($line);
573 next unless $line =~/\w+\:\:/;
574 $out_string .= "----" .$line . "<BR>\n";
575 } 423 }
576 } 424 }
577 425
578 $Global::WARNINGS .="* " . join("<BR>",@input) . "<BR>\n" . $out_string . 426 # if that is not yet defined obtain the global problem,
579 "<BR>\n--------------------------------------<BR>\n<BR>\n"; 427 # convert it to a user problem, and add fake user data
580 $Global::background_plain_url = $Global::background_warn_url; 428 unless (defined $problem) {
581 $Global::bg_color = '#FF99CC'; #for warnings -- this change may come too late 429 my $userProblemClass = $db->{problem_user}->{record};
430 my $globalProblem = $db->getGlobalProblem($setName, $problemNumber); # checked
431 # if the global problem doesn't exist either, bail!
432 if(not defined $globalProblem) {
433 my $sourceFilePath = $r->param("sourceFilePath");
434 # These are problems from setmaker. If declared invalid, they won't come up
435 $self->{invalidProblem} = $self->{invalidSet} = 1 unless defined $sourceFilePath;
436# die "Problem $problemNumber in set $setName does not exist" unless defined $sourceFilePath;
437 $problem = fake_problem($db);
438 $problem->problem_id(1);
439 $problem->source_file($sourceFilePath);
440 $problem->user_id($effectiveUserName);
441 } else {
442 $problem = global2user($userProblemClass, $globalProblem);
443 $problem->user_id($effectiveUserName);
444 $problem->problem_seed(0);
445 $problem->status(0);
446 $problem->attempted(0);
447 $problem->last_answer("");
448 $problem->num_correct(0);
449 $problem->num_incorrect(0);
450 }
582 } 451 }
452
453 # now we're sure we have valid UserSet and UserProblem objects
454 # yay!
455
456 # now deal with possible editor overrides:
457
458 # if the caller is asking to override the source file, and
459 # editMode calls for a temporary file, do so
460 my $sourceFilePath = $r->param("sourceFilePath");
461 if (defined $sourceFilePath and
462 (not defined $editMode or $editMode eq "temporaryFile")) {
463 $problem->source_file($sourceFilePath);
464 }
465
466 # if the problem does not have a source file or no source file has been passed in
467 # then this is really an invalid problem (probably from a bad URL)
468 $self->{invalidProblem} = not (defined $sourceFilePath or $problem->source_file);
469
470 # if the caller is asking to override the problem seed, do so
471 my $problemSeed = $r->param("problemSeed");
472 if (defined $problemSeed) {
473 $problem->problem_seed($problemSeed);
474 }
583 475
584 $SIG{__WARN__}=\&PG_warnings_handler; 476 my $publishedClass = ($set->published) ? "Published" : "Unpublished";
477 my $publishedText = ($set->published) ? "visible to students." : "hidden from students.";
478 $self->addmessage(CGI::p("This set is " . CGI::font({class=>$publishedClass}, $publishedText)));
479 } else {
585 480
586 $SIG{__DIE__} = sub { 481 # students can't view problems not assigned to them
587 my $message = longmess(@_); 482
588 $message =~ s/\n/<BR>\n/; 483 # A set is valid if it exists and if it is either published or the user is privileged.
589 my ($package, $filename, $line) = caller(); 484 $self->{invalidSet} = ((grep /^$setName/, $db->listUserSets($effectiveUserName)) == 0)
590 # use standard die for errors eminating from XML::Parser::Expat 485 || not defined $set
591 # it uses a trapped eval which sometimes fails -- apparently on purpose 486 || !($set->published || $authz->hasPermissions($userName, "view_unpublished_sets"));
592 # and the error is handled by Expat itself. We don't want 487 $self->{invalidProblem} = ((grep /^$problemNumber/, $db->listUserProblems($effectiveUserName, $setName)) == 0)
593 # to interfer with that. 488 || not defined $problem
594 489 || !($set->published || $authz->hasPermissions($userName, "view_unpublished_sets"));
595 if ($package eq 'XML::Parser::Expat') { 490
596 die @_; 491 $self->addbadmessage(CGI::p("This problem will not count towards your grade.")) if $problem and not $problem->value and not $self->{invalidProblem};
597 } 492 }
598 #print "$package $filename $line \n"; 493
494 $self->{userName} = $userName;
495 $self->{effectiveUserName} = $effectiveUserName;
496 $self->{user} = $user;
497 $self->{effectiveUser} = $effectiveUser;
498 $self->{permissionLevel} = $permissionLevel;
499 $self->{set} = $set;
500 $self->{problem} = $problem;
501 $self->{editMode} = $editMode;
502
503 ##### form processing #####
504
505 # set options from form fields (see comment at top of file for names)
506 my $displayMode = $r->param("displayMode") || $ce->{pg}->{options}->{displayMode};
507 my $redisplay = $r->param("redisplay");
508 my $submitAnswers = $r->param("submitAnswers");
509 my $checkAnswers = $r->param("checkAnswers");
510 my $previewAnswers = $r->param("previewAnswers");
511
512 my $formFields = { WeBWorK::Form->new_from_paramable($r)->Vars };
513
514 $self->{displayMode} = $displayMode;
515 $self->{redisplay} = $redisplay;
516 $self->{submitAnswers} = $submitAnswers;
517 $self->{checkAnswers} = $checkAnswers;
518 $self->{previewAnswers} = $previewAnswers;
519 $self->{formFields} = $formFields;
520
521 # get result and send to message
522 my $success = $r->param("sucess");
523 my $failure = $r->param("failure");
524 $self->addbadmessage(CGI::p($failure)) if $failure;
525 $self->addgoodmessage(CGI::p($success)) if $success;
526
527 # now that we've set all the necessary variables quit out if the set or problem is invalid
528 return if $self->{invalidSet} || $self->{invalidProblem};
529
530 ##### permissions #####
531
532 # are we allowed to view this problem?
533 $self->{isOpen} = after($set->open_date) || $authz->hasPermissions($userName, "view_unopened_sets");
534 return unless $self->{isOpen};
535
536 # what does the user want to do?
537 my %want = (
538 showOldAnswers => $r->param("showOldAnswers") || $ce->{pg}->{options}->{showOldAnswers},
539 showCorrectAnswers => $r->param("showCorrectAnswers") || $ce->{pg}->{options}->{showCorrectAnswers},
540 showHints => $r->param("showHints") || $ce->{pg}->{options}->{showHints},
541 showSolutions => $r->param("showSolutions") || $ce->{pg}->{options}->{showSolutions},
542 recordAnswers => $submitAnswers,
543 checkAnswers => $checkAnswers,
544 getSubmitButton => 1,
545 );
546
547 # are certain options enforced?
548 my %must = (
549 showOldAnswers => 0,
550 showCorrectAnswers => 0,
551 showHints => 0,
552 showSolutions => 0,
553 recordAnswers => ! $authz->hasPermissions($userName, "avoid_recording_answers"),
554 checkAnswers => 0,
555 getSubmitButton => 0,
556 );
557
558 # does the user have permission to use certain options?
559 my @args = ($user, $PermissionLevel, $effectiveUser, $set, $problem);
560 my %can = (
561 showOldAnswers => $self->can_showOldAnswers(@args),
562 showCorrectAnswers => $self->can_showCorrectAnswers(@args),
563 showHints => $self->can_showHints(@args),
564 showSolutions => $self->can_showSolutions(@args),
565 recordAnswers => $self->can_recordAnswers(@args, 0),
566 checkAnswers => $self->can_checkAnswers(@args, $submitAnswers),
567 getSubmitButton => $self->can_recordAnswers(@args, $submitAnswers),
568 );
569
570# # does the user have permission to use certain options?
571# my %can = (
572# showOldAnswers => 1,
573# showCorrectAnswers => canShowCorrectAnswers($permissionLevel, $set->answer_date),
574# showHints => 1,
575# showSolutions => canShowSolutions($permissionLevel, $set->answer_date),
576# recordAnswers => canRecordAnswers($permissionLevel, $set->open_date, $set->due_date,
577# $problem->max_attempts, $problem->num_correct + $problem->num_incorrect + 1),
578# # attempts=num_correct+num_incorrect+1, as this happens before updating $problem
579# checkAnswers => canCheckAnswers($permissionLevel, $set->due_date),
580# );
581#
582# # more complicated logic for showing check answer button:
583# # checkAnswers button shows up after due date -- once a student can't record anymore
584# # checkAnswers button always shows up when an instructor or TA is acting
585# # as someone else (the $user and $effectiveUserName aren't the same).
586# $can{checkAnswers} = (
587# # $can{recordAnswers} will be false if the due date has passed OR the
588# # student has used up all of her attempts
589# ($can{checkAnswers} and not $can{recordAnswers})
590# or
591# (
592# # FIXME: this is not the right way to check for this.
593# # also, canCheckAnswers() will show this button if the permission
594# # level is positive, which is always true when an instructor is
595# # acting as a student
596# defined($userName)
597# and
598# defined($effectiveUserName)
599# and
600# ($userName ne $effectiveUserName)
601# )
602# );
603#
604# # more complicated logic for showing "submit answer" button:
605# # We hide the submit answer button if someone is acting as a student
606# # This prevents errors where you accidently submit the answer for a student
607# # Not sure whether this a feature or a bug
608# $can{recordAnswers} = (
609# $can{recordAnswers}
610# and not
611# (
612# # FIXME: this is not the right way to check for this.
613# defined($userName)
614# and
615# defined($effectiveUserName)
616# and
617# ($userName ne $effectiveUserName)
618# )
619# );
620
621 # final values for options
622 my %will;
623 foreach (keys %must) {
624 $will{$_} = $can{$_} && ($want{$_} || $must{$_});
625 }
626
627 ##### sticky answers #####
628
629 if (not ($submitAnswers or $previewAnswers or $checkAnswers) and $will{showOldAnswers}) {
630 # do this only if new answers are NOT being submitted
631 my %oldAnswers = decodeAnswers($problem->last_answer);
632 $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers;
633 }
634
635 ##### translation #####
636
637 $WeBWorK::timer->continue("begin pg processing") if defined($WeBWorK::timer);
638 my $pg = WeBWorK::PG->new(
639 $ce,
640 $effectiveUser,
641 $key,
642 $set,
643 $problem,
644 $set->psvn, # FIXME: this field should be removed
645 $formFields,
646 { # translation options
647 displayMode => $displayMode,
648 showHints => $will{showHints},
649 showSolutions => $will{showSolutions},
650 refreshMath2img => $will{showHints} || $will{showSolutions},
651 processAnswers => 1,
652 },
653 );
654
655 $WeBWorK::timer->continue("end pg processing") if defined($WeBWorK::timer);
656
657 ##### fix hint/solution options #####
658
659 $can{showHints} &&= $pg->{flags}->{hintExists}
660 &&= $pg->{flags}->{showHintLimit}<=$pg->{state}->{num_of_incorrect_ans};
661 $can{showSolutions} &&= $pg->{flags}->{solutionExists};
662
663 ##### store fields #####
664
665 $self->{want} = \%want;
666 $self->{must} = \%must;
667 $self->{can} = \%can;
668 $self->{will} = \%will;
669 $self->{pg} = $pg;
670}
671
672sub if_errors($$) {
673 my ($self, $arg) = @_;
674
675 if ($self->{isOpen}) {
676 return $self->{pg}->{flags}->{error_flag} ? $arg : !$arg;
677 } else {
678 return !$arg;
679 }
680}
681
682sub head {
683 my ($self) = @_;
684
685 return "" unless $self->{isOpen};
686 return $self->{pg}->{head_text} if $self->{pg}->{head_text};
687}
688
689sub options {
690 my ($self) = @_;
691
692 return "" if $self->{invalidProblem};
693 my $sourceFilePathfield = '';
694 if($self->r->param("sourceFilePath")) {
695 $sourceFilePathfield = CGI::hidden(-name => "sourceFilePath",
696 -value => $self->r->param("sourceFilePath"));
697 }
698
699 return join("",
700 CGI::start_form("POST", $self->{r}->uri),
701 $self->hidden_authen_fields,
702 $sourceFilePathfield,
703 CGI::hr(),
704 CGI::start_div({class=>"viewOptions"}),
705 $self->viewOptions(),
706 CGI::end_div(),
707 CGI::end_form()
708 );
709}
710
711sub siblings {
712 my ($self) = @_;
713 my $r = $self->r;
714 my $db = $r->db;
715 my $urlpath = $r->urlpath;
716
717 # can't show sibling problems if the set is invalid
718 return "" if $self->{invalidSet};
719
720 my $courseID = $urlpath->arg("courseID");
721 my $setID = $self->{set}->set_id;
722 my $eUserID = $r->param("effectiveUser");
723 my @problemIDs = sort { $a <=> $b } $db->listUserProblems($eUserID, $setID);
724
725 print CGI::start_ul({class=>"LinksMenu"});
726 print CGI::start_li();
727 print CGI::span({style=>"font-size:larger"}, "Problems");
728 print CGI::start_ul();
729
730 foreach my $problemID (@problemIDs) {
731 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem",
732 courseID => $courseID, setID => $setID, problemID => $problemID);
733 print CGI::li(CGI::a({href=>$self->systemLink($problemPage, params=>{displayMode => $self->{displayMode}})}, "Problem $problemID"));
734 }
735
736 print CGI::end_ul();
737 print CGI::end_li();
738 print CGI::end_ul();
739
740 return "";
741}
742
743sub nav {
744 my ($self, $args) = @_;
745 my $r = $self->r;
746 my $db = $r->db;
747 my $urlpath = $r->urlpath;
748
749 my $courseID = $urlpath->arg("courseID");
750 my $setID = $self->{set}->set_id if !($self->{invalidSet});
751 my $problemID = $self->{problem}->problem_id if !($self->{invalidProblem});
752 my $eUserID = $r->param("effectiveUser");
753
754 my ($prevID, $nextID);
755
756 if (!$self->{invalidProblem}) {
757 my @problemIDs = $db->listUserProblems($eUserID, $setID);
758 foreach my $id (@problemIDs) {
759 $prevID = $id if $id < $problemID
760 and (not defined $prevID or $id > $prevID);
761 $nextID = $id if $id > $problemID
762 and (not defined $nextID or $id < $nextID);
763 }
764 }
765
766 my @links;
767
768 if ($prevID) {
769 my $prevPage = $urlpath->newFromModule(__PACKAGE__,
770 courseID => $courseID, setID => $setID, problemID => $prevID);
771 push @links, "Previous Problem", $r->location . $prevPage->path, "navPrev";
772 } else {
773 push @links, "Previous Problem", "", "navPrev";
774 }
775
776 push @links, "Problem List", $r->location . $urlpath->parent->path, "navProbList";
777
778 if ($nextID) {
779 my $nextPage = $urlpath->newFromModule(__PACKAGE__,
780 courseID => $courseID, setID => $setID, problemID => $nextID);
781 push @links, "Next Problem", $r->location . $nextPage->path, "navNext";
782 } else {
783 push @links, "Next Problem", "", "navNext";
784 }
785
786 my $tail = "&displayMode=".$self->{displayMode};
787 return $self->navMacro($args, $tail, @links);
788}
789
790sub title {
791 my ($self) = @_;
792
793 # using the url arguments won't break if the set/problem are invalid
794 my $setID = $self->r->urlpath->arg("setID");
795 my $problemID = $self->r->urlpath->arg("problemID");
796
797 return "$setID : $problemID";
798}
799
800sub body {
801 my $self = shift;
802 my $r = $self->r;
803 my $ce = $r->ce;
804 my $db = $r->db;
805 my $authz = $r->authz;
806 my $urlpath = $r->urlpath;
807 my $user = $r->param('user');
808 my $effectiveUser = $r->param('effectiveUser');
809
810 if ($self->{invalidSet}) {
811 return CGI::div({class=>"ResultsWithError"},
812 CGI::p("The selected problem set (" . $urlpath->arg("setID") . ") is not a valid set for " . $r->param("effectiveUser") . "."));
813 }
814
815 if ($self->{invalidProblem}) {
816 return CGI::div({class=>"ResultsWithError"},
817 CGI::p("The selected problem (" . $urlpath->arg("problemID") . ") is not a valid problem for set " . $self->{set}->set_id . "."));
818 }
819
820 unless ($self->{isOpen}) {
821 return CGI::div({class=>"ResultsWithError"},
822 CGI::p("This problem is not available because the problem set that contains it is not yet open."));
823 }
824 # unpack some useful variables
825 my $set = $self->{set};
826 my $problem = $self->{problem};
827 my $editMode = $self->{editMode};
828 my $permissionLevel = $self->{permissionLevel};
829 my $submitAnswers = $self->{submitAnswers};
830 my $checkAnswers = $self->{checkAnswers};
831 my $previewAnswers = $self->{previewAnswers};
832 my %want = %{ $self->{want} };
833 my %can = %{ $self->{can} };
834 my %must = %{ $self->{must} };
835 my %will = %{ $self->{will} };
836 my $pg = $self->{pg};
837
838 my $courseName = $urlpath->arg("courseID");
839
840 # FIXME: move editor link to top, next to problem number.
841 # format as "[edit]" like we're doing with course info file, etc.
842 # add edit link for set as well.
843 my $editorLink = "";
844 # if we are here without a real problem set, carry that through
845 my $forced_field = [];
846 $forced_field = ['sourceFilePath' => $r->param("sourceFilePath")] if
847 ($set->set_id eq 'Undefined_Set');
848 if ($authz->hasPermissions($user, "modify_problem_sets")) {
849 my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
850 courseID => $courseName, setID => $set->set_id, problemID => $problem->problem_id);
851 my $editorURL = $self->systemLink($editorPage, params=>$forced_field);
852 $editorLink = CGI::a({href=>$editorURL}, "Edit this problem");
853 }
854
855 ##### translation errors? #####
856
857 if ($pg->{flags}->{error_flag}) {
858 print $self->errorOutput($pg->{errors}, $pg->{body_text});
859 print $editorLink;
860 return "";
861 }
862
863 ##### answer processing #####
864 $WeBWorK::timer->continue("begin answer processing") if defined($WeBWorK::timer);
865 # if answers were submitted:
866 my $scoreRecordedMessage;
867 my $pureProblem;
868 if ($submitAnswers) {
869 # get a "pure" (unmerged) UserProblem to modify
870 # this will be undefined if the problem has not been assigned to this user
871 $pureProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); # checked
872 if (defined $pureProblem) {
873 # store answers in DB for sticky answers
874 my %answersToStore;
875 my %answerHash = %{ $pg->{answers} };
876 $answersToStore{$_} = $self->{formFields}->{$_} #$answerHash{$_}->{original_student_ans} -- this may have been modified for fields with multiple values. Don't use it!!
877 foreach (keys %answerHash);
878
879 # There may be some more answers to store -- one which are auxiliary entries to a primary answer. Evaluating
880 # matrices works in this way, only the first answer triggers an answer evaluator, the rest are just inputs
881 # however we need to store them. Fortunately they are still in the input form.
882 my @extra_answer_names = @{ $pg->{flags}->{KEPT_EXTRA_ANSWERS}};
883 $answersToStore{$_} = $self->{formFields}->{$_} foreach (@extra_answer_names);
884
885 # Now let's encode these answers to store them -- append the extra answers to the end of answer entry order
886 my @answer_order = (@{$pg->{flags}->{ANSWER_ENTRY_ORDER}}, @extra_answer_names);
887 my $answerString = encodeAnswers(%answersToStore,
888 @answer_order);
889
890 # store last answer to database
891 $problem->last_answer($answerString);
892 $pureProblem->last_answer($answerString);
893 $db->putUserProblem($pureProblem);
894
895 # store state in DB if it makes sense
896 if ($will{recordAnswers}) {
897 $problem->status($pg->{state}->{recorded_score});
898 $problem->attempted(1);
899 $problem->num_correct($pg->{state}->{num_of_correct_ans});
900 $problem->num_incorrect($pg->{state}->{num_of_incorrect_ans});
901 $pureProblem->status($pg->{state}->{recorded_score});
902 $pureProblem->attempted(1);
903 $pureProblem->num_correct($pg->{state}->{num_of_correct_ans});
904 $pureProblem->num_incorrect($pg->{state}->{num_of_incorrect_ans});
905 if ($db->putUserProblem($pureProblem)) {
906 $scoreRecordedMessage = "Your score was recorded.";
907 } else {
908 $scoreRecordedMessage = "Your score was not recorded because there was a failure in storing the problem record to the database.";
909 }
910 # write to the transaction log, just to make sure
911 writeLog($self->{ce}, "transaction",
912 $problem->problem_id."\t".
913 $problem->set_id."\t".
914 $problem->user_id."\t".
915 $problem->source_file."\t".
916 $problem->value."\t".
917 $problem->max_attempts."\t".
918 $problem->problem_seed."\t".
919 $pureProblem->status."\t".
920 $pureProblem->attempted."\t".
921 $pureProblem->last_answer."\t".
922 $pureProblem->num_correct."\t".
923 $pureProblem->num_incorrect
924 );
925 } else {
926 if (before($set->open_date) or after($set->due_date)) {
927 $scoreRecordedMessage = "Your score was not recorded because this problem set is closed.";
928 } else {
929 $scoreRecordedMessage = "Your score was not recorded.";
930 }
931 }
932 } else {
933 $scoreRecordedMessage = "Your score was not recorded because this problem has not been assigned to you.";
934 }
935 }
936
937 # logging student answers
938
939 my $answer_log = $self->{ce}->{courseFiles}->{logs}->{'answer_log'};
940 if ( defined($answer_log ) and defined($pureProblem)) {
941 if ($submitAnswers && !$authz->hasPermissions($effectiveUser, "dont_log_past_answers")) {
942 my $answerString = ""; my $scores = "";
943 my %answerHash = %{ $pg->{answers} };
944 # FIXME this is the line 552 error. make sure original student ans is defined.
945 # The fact that it is not defined is probably due to an error in some answer evaluator.
946 # But I think it is useful to suppress this error message in the log.
947 foreach (sort keys %answerHash) {
948 my $orig_ans = $answerHash{$_}->{original_student_ans};
949 my $student_ans = defined $orig_ans ? $orig_ans : '';
950 $answerString .= $student_ans."\t";
951 $scores .= $answerHash{$_}->{score} >= 1 ? "1" : "0";
952 }
953 $answerString = '' unless defined($answerString); # insure string is defined.
954 writeCourseLog($self->{ce}, "answer_log",
955 join("",
956 '|', $problem->user_id,
957 '|', $problem->set_id,
958 '|', $problem->problem_id,
959 '|', $scores, "\t",
960 time(),"\t",
961 $answerString,
962 ),
963 );
964
965 }
966 }
967
968 $WeBWorK::timer->continue("end answer processing") if defined($WeBWorK::timer);
969
970 ##### output #####
971
972 print CGI::start_div({class=>"problemHeader"});
973
974 # custom message for editor
975 if ($authz->hasPermissions($user, "modify_problem_sets") and defined $editMode) {
976 if ($editMode eq "temporaryFile") {
977 print CGI::p(CGI::i("Editing temporary file: ", $problem->source_file));
978 } elsif ($editMode eq "savedFile") {
979 # taken care of in the initialization phase
980 }
981 }
982
983 # attempt summary
984 #FIXME -- the following is a kludge: if showPartialCorrectAnswers is negative don't show anything.
985 # until after the due date
986 # do I need to check $will{showCorrectAnswers} to make preflight work??
987 if (($pg->{flags}->{showPartialCorrectAnswers} >= 0 and $submitAnswers) ) {
988 # print this if user submitted answers OR requested correct answers
989
990 print $self->attemptResults($pg, 1,
991 $will{showCorrectAnswers},
992 $pg->{flags}->{showPartialCorrectAnswers}, 1, 1);
993 } elsif ($checkAnswers) {
994 # print this if user previewed answers
995 print CGI::div({class=>'ResultsWithError'},"ANSWERS ONLY CHECKED -- ",CGI::br(),"ANSWERS NOT RECORDED", CGI::br() );
996 print $self->attemptResults($pg, 1, $will{showCorrectAnswers}, 1, 1, 1);
997 # show attempt answers
998 # show correct answers if asked
999 # show attempt results (correctness)
1000 # show attempt previews
1001 } elsif ($previewAnswers) {
1002 # print this if user previewed answers
1003 print CGI::div({class=>'ResultsWithError'},"PREVIEW ONLY -- NOT RECORDED"),CGI::br(),$self->attemptResults($pg, 1, 0, 0, 0, 1);
1004 # show attempt answers
1005 # don't show correct answers
1006 # don't show attempt results (correctness)
1007 # show attempt previews
1008 }
1009
1010 print CGI::end_div();
1011
1012 # main form
1013 print CGI::startform("POST", $r->uri);
1014 print $self->hidden_authen_fields;
1015
1016 print CGI::start_div({class=>"problem"});
1017 print CGI::p($pg->{body_text});
1018 print CGI::p(CGI::b("Note: "), CGI::i($pg->{result}->{msg})) if $pg->{result}->{msg};
1019 print CGI::end_div();
1020
1021 print CGI::start_p();
1022
1023 if ($can{showCorrectAnswers}) {
1024 print CGI::checkbox(
1025 -name => "showCorrectAnswers",
1026 -checked => $will{showCorrectAnswers},
1027 -label => "Show correct answers",
1028 );
1029 }
1030 if ($can{showHints}) {
1031 print CGI::div({style=>"color:red"},
1032 CGI::checkbox(
1033 -name => "showHints",
1034 -checked => $will{showHints},
1035 -label => "Show Hints",
1036 )
1037 );
1038 }
1039 if ($can{showSolutions}) {
1040 print CGI::checkbox(
1041 -name => "showSolutions",
1042 -checked => $will{showSolutions},
1043 -label => "Show Solutions",
1044 );
1045 }
1046
1047 if ($can{showCorrectAnswers} or $can{showHints} or $can{showSolutions}) {
1048 print CGI::br();
1049 }
1050
1051 print CGI::submit(-name=>"previewAnswers", -label=>"Preview Answers");
1052 if ($can{checkAnswers}) {
1053 print CGI::submit(-name=>"checkAnswers", -label=>"Check Answers");
1054 }
1055 if ($can{getSubmitButton}) {
1056 if ($user ne $effectiveUser) {
1057 # if acting as a student, make it clear that answer submissions will
1058 # apply to the student's records, not the professor's.
1059 print CGI::submit(-name=>"submitAnswers", -label=>"Submit Answers for $effectiveUser");
1060 } else {
1061 print CGI::submit(-name=>"submitAnswers", -label=>"Submit Answers");
1062 }
1063 }
1064
1065 print CGI::end_p();
1066
1067 print CGI::start_div({class=>"scoreSummary"});
1068
1069 # score summary
1070 my $attempts = $problem->num_correct + $problem->num_incorrect;
1071 my $attemptsNoun = $attempts != 1 ? "times" : "time";
1072 my $lastScore = sprintf("%.0f%%", $problem->status * 100); # Round to whole number
1073 my ($attemptsLeft, $attemptsLeftNoun);
1074 if ($problem->max_attempts == -1) {
1075 # unlimited attempts
1076 $attemptsLeft = "unlimited";
1077 $attemptsLeftNoun = "attempts";
1078 } else {
1079 $attemptsLeft = $problem->max_attempts - $attempts;
1080 $attemptsLeftNoun = $attemptsLeft == 1 ? "attempt" : "attempts";
1081 }
1082
1083 my $setClosed = 0;
1084 my $setClosedMessage;
1085 if (before($set->open_date) or after($set->due_date)) {
1086 $setClosed = 1;
1087 $setClosedMessage = "This problem set is closed.";
1088 if ($authz->hasPermissions($user, "view_answers")) {
1089 $setClosedMessage .= " However, since you are a privileged user, additional attempts will be recorded.";
1090 } else {
1091 $setClosedMessage .= " Additional attempts will not be recorded.";
1092 }
1093 }
1094
1095 my $notCountedMessage = ($problem->value) ? "" : "(This problem will not count towards your grade.)";
1096 print CGI::p(
1097 $submitAnswers ? $scoreRecordedMessage . CGI::br() : "",
1098 "You have attempted this problem $attempts $attemptsNoun.", CGI::br(),
1099 $problem->attempted
1100 ? "Your recorded score is $lastScore. $notCountedMessage" . CGI::br()
1101 : "",
1102 $setClosed ? $setClosedMessage : "You have $attemptsLeft $attemptsLeftNoun remaining."
1103 );
1104 print CGI::end_div();
1105
1106 # save state for viewOptions
1107 print CGI::hidden(
1108 -name => "showOldAnswers",
1109 -value => $will{showOldAnswers}
1110 ),
1111
1112 CGI::hidden(
1113 -name => "displayMode",
1114 -value => $self->{displayMode}
1115 );
1116 print( CGI::hidden(
1117 -name => 'editMode',
1118 -value => $self->{editMode},
1119 )
1120 ) if defined($self->{editMode}) and $self->{editMode} eq 'temporaryFile';
1121 print( CGI::hidden(
1122 -name => 'sourceFilePath',
1123 -value => $self->{problem}->{source_file}
1124 )) if defined($self->{problem}->{source_file});
1125
1126 print( CGI::hidden(
1127 -name => 'problemSeed',
1128 -value => $r->param("problemSeed")
1129 )) if defined($r->param("problemSeed"));
1130
1131 # end of main form
1132 print CGI::endform();
1133
1134 print CGI::start_div({class=>"problemFooter"});
1135
1136 ## arguments for answer inspection button
1137 #my $prof_url = $ce->{webworkURLs}->{oldProf};
1138 #my $webworkURL = $ce->{webworkURLs}->{root};
1139 #my $cgi_url = $prof_url;
1140 #$cgi_url=~ s|/[^/]*$||; # clip profLogin.pl
1141 #my $authen_args = $self->url_authen_args();
1142 #my $showPastAnswersURL = "$webworkURL/$courseName/instructor/show_answers/";
1143
1144 my $pastAnswersPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ShowAnswers",
1145 courseID => $courseName);
1146 my $showPastAnswersURL = $self->systemLink($pastAnswersPage, authen => 0); # no authen info for form action
1147
1148 # print answer inspection button
1149 if ($authz->hasPermissions($user, "view_answers")) {
1150 print "\n",
1151 CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"information"),"\n",
1152 $self->hidden_authen_fields,"\n",
1153 CGI::hidden(-name => 'courseID', -value=>$courseName), "\n",
1154 CGI::hidden(-name => 'problemID', -value=>$problem->problem_id), "\n",
1155 CGI::hidden(-name => 'setID', -value=>$problem->set_id), "\n",
1156 CGI::hidden(-name => 'studentUser', -value=>$problem->user_id), "\n",
1157 CGI::p( {-align=>"left"},
1158 CGI::submit(-name => 'action', -value=>'Show Past Answers')
1159 ), "\n",
1160 CGI::endform();
1161 }
1162
1163 # feedback form url
1164 my $feedbackPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Feedback",
1165 courseID => $courseName);
1166 my $feedbackURL = $self->systemLink($feedbackPage, authen => 0); # no authen info for form action
1167
1168 #print feedback form
1169 print
1170 CGI::start_form(-method=>"POST", -action=>$feedbackURL),"\n",
1171 $self->hidden_authen_fields,"\n",
1172 CGI::hidden("module", __PACKAGE__),"\n",
1173 CGI::hidden("set", $set->set_id),"\n",
1174 CGI::hidden("problem", $problem->problem_id),"\n",
1175 CGI::hidden("displayMode", $self->{displayMode}),"\n",
1176 CGI::hidden("showOldAnswers", $will{showOldAnswers}),"\n",
1177 CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n",
1178 CGI::hidden("showHints", $will{showHints}),"\n",
1179 CGI::hidden("showSolutions", $will{showSolutions}),"\n",
1180 CGI::p({-align=>"left"},
1181 CGI::submit(-name=>"feedbackForm", -label=>"Email instructor")
1182 ),
1183 CGI::endform(),"\n";
1184
1185 # FIXME print editor link
1186 print $editorLink; #empty unless it is appropriate to have an editor link.
1187
1188 print CGI::end_div();
1189
1190 # debugging stuff
1191 if (0) {
599 print 1192 print
600 "Content-type: text/html\r\n\r\n <h4>Software error</h4> <p>\n\n$message\n<p>\n 1193 CGI::hr(),
601 Please inform the webwork meister.<p>\n 1194 CGI::h2("debugging information"),
602 In addition to the error message above the following warnings were detected: 1195 CGI::h3("form fields"),
603 <HR> 1196 ref2string($self->{formFields}),
604 $Global::WARNINGS; 1197 CGI::h3("user object"),
605 <HR> 1198 ref2string($self->{user}),
606 It's sometimes hard to tell exactly what has gone wrong since the 1199 CGI::h3("set object"),
607 full error message may have been sent to 1200 ref2string($set),
608 standard error instead of to standard out. 1201 CGI::h3("problem object"),
609 <p> To debug you can 1202 ref2string($problem),
610 <ul> 1203 CGI::h3("PG object"),
611 <li> guess what went wrong and try to fix it. 1204 ref2string($pg, {'WeBWorK::PG::Translator' => 1});
612 <li> call the offending script directly from the command line
613 of unix
614 <li> enable the debugging features by redefining
615 \$cgiURL in Global.pm and checking the redirection scripts in
616 system/cgi. This will force the standard error to be placed
617 in the standard out pipe as well.
618 <li> Run tail -f error_log <br>
619 from the unix command line to see error messages from the webserver.
620 The standard error output is being placed in the error_log file for the apache
621 web server. To run this command you have to be in the directory containing the
622 error_log or enter the full path name of the error_log. <p>
623 In a standard apache installation, this file is at /usr/local/apache/logs/error_log<p>
624 In a RedHat Linux installation, this file is at /var/log/httpd/error_log<p>
625 At Rochester this file is at /ww/logs/error_log.
626 </ul>
627 Good luck.<p>\n" ;
628 }; 1205 }
629 1206
630 1207 return "";
631
632} 1208}
633 1209
6341; 12101;

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9