[system] / branches / rel-2-4-patches / webwork2 / lib / WeBWorK / PG.pm Repository:
ViewVC logotype

Diff of /branches/rel-2-4-patches/webwork2/lib/WeBWorK/PG.pm

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

Revision 555 Revision 698
17use File::Temp qw(tempdir); 17use File::Temp qw(tempdir);
18use WeBWorK::DB::Classlist; 18use WeBWorK::DB::Classlist;
19use WeBWorK::DB::WW; 19use WeBWorK::DB::WW;
20use WeBWorK::PG::Translator; 20use WeBWorK::PG::Translator;
21use WeBWorK::Problem; 21use WeBWorK::Problem;
22use WeBWorK::Utils qw(readFile formatDateTime); 22use WeBWorK::Utils qw(readFile formatDateTime writeTimingLogEntry);
23 23
24sub new($$$$$$$$) { 24sub new($$$$$$$$) {
25 my $invocant = shift; 25 my $invocant = shift;
26 my $class = ref($invocant) || $invocant; 26 my $class = ref($invocant) || $invocant;
27 my ( 27 my (
35 $translationOptions, # hashref containing options for the 35 $translationOptions, # hashref containing options for the
36 # translator, such as whether to show 36 # translator, such as whether to show
37 # hints and the display mode to use 37 # hints and the display mode to use
38 ) = @_; 38 ) = @_;
39 39
40# # get database information 40 # write timing log entry
41# my $classlist = WeBWorK::DB::Classlist->new($courseEnv); 41 writeTimingLogEntry($courseEnv, "WeBWorK::PG::new",
42# my $wwdb = WeBWorK::DB::WW->new($courseEnv); 42 "user=".$user->id.",problem=".$courseEnv->{courseName}."/".$set->id."/".$problem->id.",mode=".$translationOptions->{displayMode},
43# my $user = $classlist->getUser($userName); 43 "begin");
44# my $set = $wwdb->getSet($userName, $setName); 44
45# my $psvn = $wwdb->getPSVN($userName, $setName); 45 # install a local warn handler to collect warnings
46# 46 my $warnings = "";
47# my $problem; 47 local $SIG{__WARN__} = sub { $warnings .= shift }
48# if ($problemNumber =~ /^\d+$/) { 48 if $courseEnv->{pg}->{options}->{catchWarnings};
49# $problem = $wwdb->getProblem($userName, $setName, $problemNumber);
50# } else {
51# # This is the fun part: if $problemNumber is NON-NUMERIC, the
52# # user wants to specify a PG file directly. We manufacture a
53# # Problem object using fake data and the specified source file.
54# # This is potentially dangerous since an untrusted user is
55# # allowed to specifiy an arbitrary file to be evaluated as PG.
56# # A user of PG.pm MUST MAKE SURE that if $problemNumber is
57# # supplied by an untrusted source (i.e. the Apache request),
58# # it is numberic. A simple
59# #
60# # die unless $problemNumber =~ /^\d+$/;
61# #
62# # should suffice.
63# $problem = WeBWorK::Problem->new(
64# id => 0,
65# set_id => $set->id,
66# login_id => $user->id,
67# source_file => $problemNumber,
68# # the rest of Problem's fields are not needed
69# );
70# }
71 49
72 # create a Translator 50 # create a Translator
73 warn "PG: creating a Translator\n"; 51 #warn "PG: creating a Translator\n";
74 my $translator = WeBWorK::PG::Translator->new; 52 my $translator = WeBWorK::PG::Translator->new;
75 53
76 # set the directory hash 54 # set the directory hash
77 warn "PG: setting the directory hash\n"; 55 #warn "PG: setting the directory hash\n";
78 $translator->rh_directories({ 56 $translator->rh_directories({
79 courseScriptsDirectory => $courseEnv->{webworkDirs}->{macros}, 57 courseScriptsDirectory => $courseEnv->{webworkDirs}->{macros},
80 macroDirectory => $courseEnv->{courseDirs}->{macros}, 58 macroDirectory => $courseEnv->{courseDirs}->{macros},
81 templateDirectory => $courseEnv->{courseDirs}->{templates}, 59 templateDirectory => $courseEnv->{courseDirs}->{templates},
82 tempDirectory => $courseEnv->{courseDirs}->{html_temp}, 60 tempDirectory => $courseEnv->{courseDirs}->{html_temp},
83 }); 61 });
84 62
85 # evaluate modules and "extra packages" 63 # evaluate modules and "extra packages"
86 warn "PG: evaluating modules and \"extra packages\"\n"; 64 #warn "PG: evaluating modules and \"extra packages\"\n";
87 my @modules = @{ $courseEnv->{pg}->{modules} }; 65 my @modules = @{ $courseEnv->{pg}->{modules} };
88 foreach my $module_packages_ref (@modules) { 66 foreach my $module_packages_ref (@modules) {
89 my ($module, @extra_packages) = @$module_packages_ref; 67 my ($module, @extra_packages) = @$module_packages_ref;
90 # the first item is the main package 68 # the first item is the main package
91 $translator->evaluate_modules($module); 69 $translator->evaluate_modules($module);
92 # the remaining items are "extra" packages 70 # the remaining items are "extra" packages
93 $translator->load_extra_packages(@extra_packages); 71 $translator->load_extra_packages(@extra_packages);
94 } 72 }
95 73
96 # set the environment (from defineProblemEnvir) 74 # set the environment (from defineProblemEnvir)
97 warn "PG: setting the environment (from defineProblemEnvir)\n"; 75 #warn "PG: setting the environment (from defineProblemEnvir)\n";
98 my $envir = defineProblemEnvir( 76 my $envir = defineProblemEnvir(
99 $courseEnv, 77 $courseEnv,
100 $user, 78 $user,
101 $key, 79 $key,
102 $set, 80 $set,
106 $translationOptions, 84 $translationOptions,
107 ); 85 );
108 $translator->environment($envir); 86 $translator->environment($envir);
109 87
110 # initialize the Translator 88 # initialize the Translator
111 warn "PG: initializing the Translator\n"; 89 #warn "PG: initializing the Translator\n";
112 $translator->initialize(); 90 $translator->initialize();
113 91
114 # load PG.pl and dangerousMacros.pl using unrestricted_load 92 # load IO.pl, PG.pl, and dangerousMacros.pl using unrestricted_load
115 # i'd like to change this at some point to have the same sort of interface to global.conf 93 # i'd like to change this at some point to have the same sort of interface to global.conf
116 # that the module loading does -- have a list of macros to load unrestrictedly. 94 # that the module loading does -- have a list of macros to load unrestrictedly.
117 warn "PG: loading PG.pl and dangerousMacros.pl using unrestricted_load\n"; 95 #warn "PG: loading IO.pl, PG.pl, and dangerousMacros.pl using unrestricted_load\n";
96 foreach (qw(IO.pl PG.pl dangerousMacros.pl)) {
118 my $pg_pl = $courseEnv->{webworkDirs}->{macros} . "/PG.pl"; 97 my $macroPath = $courseEnv->{webworkDirs}->{macros} . "/$_";
119 my $dangerousMacros_pl = $courseEnv->{webworkDirs}->{macros} . "/dangerousMacros.pl";
120 my $err = $translator->unrestricted_load($pg_pl); 98 my $err = $translator->unrestricted_load($macroPath);
121 warn "Error while loading $pg_pl: $err" if $err; 99 warn "Error while loading $macroPath: $err" if $err;
122 $err = $translator->unrestricted_load($dangerousMacros_pl); 100 }
123 warn "Error while loading $dangerousMacros_pl: $err" if $err;
124 101
125 # set the opcode mask (using default values) 102 # set the opcode mask (using default values)
126 warn "PG: setting the opcode mask (using default values)\n"; 103 #warn "PG: setting the opcode mask (using default values)\n";
127 $translator->set_mask(); 104 $translator->set_mask();
128 105
129 # store the problem source 106 # store the problem source
130 warn "PG: storing the problem source\n"; 107 #warn "PG: storing the problem source\n";
131 my $sourceFile = $problem->source_file; 108 my $sourceFile = $problem->source_file;
132 $sourceFile = $courseEnv->{courseDirs}->{templates}."/".$sourceFile 109 $sourceFile = $courseEnv->{courseDirs}->{templates}."/".$sourceFile
133 unless ($sourceFile =~ /^\//); 110 unless ($sourceFile =~ /^\//);
134 eval { $translator->source_string(readFile($sourceFile)) }; 111 eval { $translator->source_string(readFile($sourceFile)) };
135 if ($@) { 112 if ($@) {
143EOF 120EOF
144 answers => {}, 121 answers => {},
145 result => {}, 122 result => {},
146 state => {}, 123 state => {},
147 errors => "Failed to read the problem source file.", 124 errors => "Failed to read the problem source file.",
148 warnings => undef, 125 warnings => $warnings,
149 flags => {error_flag => 1}, 126 flags => {error_flag => 1},
150 }, $class; 127 }, $class;
151 } 128 }
152 129
153 # install a safety filter (&safetyFilter) 130 # install a safety filter (&safetyFilter)
154 warn "PG: installing a safety filter\n"; 131 #warn "PG: installing a safety filter\n";
155 $translator->rf_safety_filter(\&safetyFilter); 132 $translator->rf_safety_filter(\&safetyFilter);
156 133
134 # write timing log entry -- the translator is now all set up
135 writeTimingLogEntry($courseEnv, "WeBWorK::PG::new",
136 "initialized",
137 "intermediate");
138
157 # translate the PG source into text 139 # translate the PG source into text
158 warn "PG: translating the PG source into text\n"; 140 #warn "PG: translating the PG source into text\n";
159 $translator->translate(); 141 $translator->translate();
160 142
161 # after we're done translating, we may have to clean up after the translator. 143 # after we're done translating, we may have to clean up after the translator.
162 # for example, 'images' mode uses a tempdir for dvipng's temp files. We have 144 # for example, 'images' mode uses a tempdir for dvipng's temp files. We have
163 # to remove it. 145 # to remove it.
164 if ($translationOptions->{displayMode} eq 'images' && $envir->{dvipngTempDir}) { 146 if ($translationOptions->{displayMode} eq 'images' && $envir->{dvipngTempDir}) {
165 rmtree($envir->{dvipngTempDir}, 0, 1); 147 rmtree($envir->{dvipngTempDir}, 0, 0);
166 } 148 }
167 149
168 my ($result, $state); # we'll need these on the other side of the if block! 150 my ($result, $state); # we'll need these on the other side of the if block!
169 if ($translationOptions->{processAnswers}) { 151 if ($translationOptions->{processAnswers}) {
170 152
171 # process student answers 153 # process student answers
172 warn "PG: processing student answers\n"; 154 #warn "PG: processing student answers\n";
173 $translator->process_answers($formFields); 155 $translator->process_answers($formFields);
174 156
175 # retrieve the problem state and give it to the translator 157 # retrieve the problem state and give it to the translator
176 warn "PG: retrieving the problem state and giving it to the translator\n"; 158 #warn "PG: retrieving the problem state and giving it to the translator\n";
177 $translator->rh_problem_state({ 159 $translator->rh_problem_state({
178 recorded_score => $problem->status, 160 recorded_score => $problem->status,
179 num_of_correct_ans => $problem->num_correct, 161 num_of_correct_ans => $problem->num_correct,
180 num_of_incorrect_ans => $problem->num_incorrect, 162 num_of_incorrect_ans => $problem->num_incorrect,
181 }); 163 });
182 164
183 # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by 165 # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by
184 # the PG macro package (PG.pl) 166 # the PG macro package (PG.pl)
185 warn "PG: determining an entry order\n"; 167 #warn "PG: determining an entry order\n";
186 my @answerOrder = 168 my @answerOrder =
187 $translator->rh_flags->{ANSWER_ENTRY_ORDER} 169 $translator->rh_flags->{ANSWER_ENTRY_ORDER}
188 ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} } 170 ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} }
189 : keys %{ $translator->rh_evaluated_answers }; 171 : keys %{ $translator->rh_evaluated_answers };
190 172
191 # install a grader -- use the one specified in the problem, 173 # install a grader -- use the one specified in the problem,
192 # or fall back on the default from the course environment. 174 # or fall back on the default from the course environment.
193 # (two magic strings are accepted, to avoid having to 175 # (two magic strings are accepted, to avoid having to
194 # reference code when it would be difficult.) 176 # reference code when it would be difficult.)
195 warn "PG: installing a grader\n"; 177 #warn "PG: installing a grader\n";
196 my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE} 178 my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE}
197 || $courseEnv->{pg}->{options}->{grader}; 179 || $courseEnv->{pg}->{options}->{grader};
198 $grader = $translator->rf_std_problem_grader 180 $grader = $translator->rf_std_problem_grader
199 if $grader eq "std_problem_grader"; 181 if $grader eq "std_problem_grader";
200 $grader = $translator->rf_avg_problem_grader 182 $grader = $translator->rf_avg_problem_grader
202 die "Problem grader $grader is not a CODE reference." 184 die "Problem grader $grader is not a CODE reference."
203 unless ref $grader eq "CODE"; 185 unless ref $grader eq "CODE";
204 $translator->rf_problem_grader($grader); 186 $translator->rf_problem_grader($grader);
205 187
206 # grade the problem 188 # grade the problem
207 warn "PG: grading the problem\n"; 189 #warn "PG: grading the problem\n";
208 ($result, $state) = $translator->grade_problem( 190 ($result, $state) = $translator->grade_problem(
209 answers_submitted => $translationOptions->{processAnswers}, 191 answers_submitted => $translationOptions->{processAnswers},
210 ANSWER_ENTRY_ORDER => \@answerOrder, 192 ANSWER_ENTRY_ORDER => \@answerOrder,
211 ); 193 );
212 194
213 } 195 }
196
197 # write timing log entry
198 writeTimingLogEntry($courseEnv, "WeBWorK::PG::new", "", "end");
214 199
215 # return an object which contains the translator and the results of 200 # return an object which contains the translator and the results of
216 # the translation process. this is DIFFERENT from the "format expected 201 # the translation process. this is DIFFERENT from the "format expected
217 # by Webwork.pm (and I believe processProblem8, but check.)" 202 # by Webwork.pm (and I believe processProblem8, but check.)"
218 return bless { 203 return bless {
220 head_text => ${ $translator->r_header }, 205 head_text => ${ $translator->r_header },
221 body_text => ${ $translator->r_text }, 206 body_text => ${ $translator->r_text },
222 answers => $translator->rh_evaluated_answers, 207 answers => $translator->rh_evaluated_answers,
223 result => $result, 208 result => $result,
224 state => $state, 209 state => $state,
225 errors => $translator->errors, # *** what is this doing? 210 errors => $translator->errors,
226 warnings => undef, # *** gotta catch warnings eventually... 211 warnings => $warnings,
227 flags => $translator->rh_flags, 212 flags => $translator->rh_flags,
228 }, $class; 213 }, $class;
229} 214}
230 215
231# ----- 216# -----
247 # PG environment variables 232 # PG environment variables
248 # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002 233 # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002
249 # any changes are noted by "ADDED:" or "REMOVED:" 234 # any changes are noted by "ADDED:" or "REMOVED:"
250 235
251 # Vital state information 236 # Vital state information
252 # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img 237 # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img,
238 # texDisposition
253 239
254 $envir{psvn} = $psvn; 240 $envir{psvn} = $psvn;
255 $envir{psvnNumber} = $envir{psvn}; 241 $envir{psvnNumber} = $envir{psvn};
256 $envir{probNum} = $problem->id; 242 $envir{probNum} = $problem->id;
257 $envir{questionNumber} = $envir{probNum}; 243 $envir{questionNumber} = $envir{probNum};
259 $envir{probFileName} = $envir{fileName}; 245 $envir{probFileName} = $envir{fileName};
260 $envir{problemSeed} = $problem->problem_seed; 246 $envir{problemSeed} = $problem->problem_seed;
261 $envir{displayMode} = translateDisplayModeNames($options->{displayMode}); 247 $envir{displayMode} = translateDisplayModeNames($options->{displayMode});
262 $envir{languageMode} = $envir{displayMode}; 248 $envir{languageMode} = $envir{displayMode};
263 $envir{outputMode} = $envir{displayMode}; 249 $envir{outputMode} = $envir{displayMode};
264 $envir{displayHintsQ} = $options->{hints}; 250 $envir{displayHintsQ} = $options->{showHints};
265 $envir{displaySolutionsQ} = $options->{solutions}; 251 $envir{displaySolutionsQ} = $options->{showSolutions};
266 $envir{refreshMath2img} = $options->{refreshMath2img}; 252 $envir{refreshMath2img} = $options->{refreshMath2img};
253 $envir{texDisposition} = "pdf"; # in webwork-modperl, we use pdflatex
267 254
268 # Problem Information 255 # Problem Information
269 # ADDED: courseName 256 # ADDED: courseName, formatedDueDate
270 257
271 $envir{openDate} = $set->open_date; 258 $envir{openDate} = $set->open_date;
272 $envir{formattedOpenDate} = formatDateTime($envir{openDate}); 259 $envir{formattedOpenDate} = formatDateTime($envir{openDate});
273 $envir{dueDate} = $set->due_date; 260 $envir{dueDate} = $set->due_date;
274 $envir{formattedDueDate} = formatDateTime($envir{dueDate}); 261 $envir{formattedDueDate} = formatDateTime($envir{dueDate});
262 $envir{formatedDueDate} = $envir{formattedDueDate}; # typo in many header files
275 $envir{answerDate} = $set->answer_date; 263 $envir{answerDate} = $set->answer_date;
276 $envir{formattedAnswerDate} = formatDateTime($envir{answerDate}); 264 $envir{formattedAnswerDate} = formatDateTime($envir{answerDate});
277 $envir{numOfAttempts} = $problem->num_correct + $problem->num_incorrect; 265 $envir{numOfAttempts} = ($problem->num_correct || 0) + ($problem->num_incorrect || 0);
278 $envir{problemValue} = $problem->value; 266 $envir{problemValue} = $problem->value;
279 $envir{sessionKey} = $key; 267 $envir{sessionKey} = $key;
280 $envir{courseName} = $courseEnv->{courseName}; 268 $envir{courseName} = $courseEnv->{courseName};
281 269
282 # Student Information 270 # Student Information
295 # REMOVED: refSubmittedAnswers 283 # REMOVED: refSubmittedAnswers
296 284
297 $envir{inputs_ref} = $formFields; 285 $envir{inputs_ref} = $formFields;
298 286
299 # External Programs 287 # External Programs
300 # ADDED: externalLaTeXPath, externalDvipngPath, externalMath2imgPath 288 # ADDED: externalLaTeXPath, externalDvipngPath,
289 # externalGif2EpsPath, externalPng2EpsPath
301 290
302 $envir{externalTTHPath} = $courseEnv->{externalPrograms}->{tth}; 291 $envir{externalTTHPath} = $courseEnv->{externalPrograms}->{tth};
303 $envir{externalLaTeXPath} = $courseEnv->{externalPrograms}->{latex}; 292 $envir{externalLaTeXPath} = $courseEnv->{externalPrograms}->{latex};
304 $envir{externalDvipngPath} = $courseEnv->{externalPrograms}->{dvipng}; 293 $envir{externalDvipngPath} = $courseEnv->{externalPrograms}->{dvipng};
294 $envir{externalGif2EpsPath} = $courseEnv->{externalPrograms}->{gif2eps};
295 $envir{externalPng2EpsPath} = $courseEnv->{externalPrograms}->{png2eps};
305 $envir{externalMath2imgPath} = $courseEnv->{externalPrograms}->{math2img}; 296 $envir{externalGif2PngPath} = $courseEnv->{externalPrograms}->{gif2png};
306 297
307 # Directories and URLs 298 # Directories and URLs
308 # REMOVED: courseName 299 # REMOVED: courseName
309 # ADDED: dvipngTempDir 300 # ADDED: dvipngTempDir
310
311 301
312 $envir{cgiDirectory} = undef; 302 $envir{cgiDirectory} = undef;
313 $envir{cgiURL} = undef; 303 $envir{cgiURL} = undef;
314 $envir{classDirectory} = undef; 304 $envir{classDirectory} = undef;
315 $envir{courseScriptsDirectory} = $courseEnv->{webworkDirs}->{macros}."/"; 305 $envir{courseScriptsDirectory} = $courseEnv->{webworkDirs}->{macros}."/";
316 $envir{htmlDirectory} = $courseEnv->{courseDirs}->{html}."/"; 306 $envir{htmlDirectory} = $courseEnv->{courseDirs}->{html}."/";
317 $envir{htmlURL} = $courseEnv->{courseURLs}->{html}; 307 $envir{htmlURL} = $courseEnv->{courseURLs}->{html}."/";
318 $envir{macroDirectory} = $courseEnv->{courseDirs}->{macros}."/"; 308 $envir{macroDirectory} = $courseEnv->{courseDirs}->{macros}."/";
319 $envir{templateDirectory} = $courseEnv->{courseDirs}->{templates}."/"; 309 $envir{templateDirectory} = $courseEnv->{courseDirs}->{templates}."/";
320 $envir{tempDirectory} = $courseEnv->{courseDirs}->{html_temp}."/"; 310 $envir{tempDirectory} = $courseEnv->{courseDirs}->{html_temp}."/";
321 $envir{tempURL} = $courseEnv->{courseURLs}->{html_temp}; 311 $envir{tempURL} = $courseEnv->{courseURLs}->{html_temp}."/";
322 $envir{scriptDirectory} = undef; 312 $envir{scriptDirectory} = undef;
323 $envir{webworkDocsURL} = $courseEnv->{webworkURLs}->{docs}; 313 $envir{webworkDocsURL} = $courseEnv->{webworkURLs}->{docs}."/";
324 $envir{dvipngTempDir} = $options->{displayMode} eq 'images' 314 $envir{dvipngTempDir} = $options->{displayMode} eq 'images'
325 ? tempdir("webwork-dvipng-XXXXXXXX", TMPDIR => 1) 315 ? tempdir("webwork-dvipng-XXXXXXXX", DIR => $envir{tempDirectory})
326 : undef; 316 : undef;
317
318 # Information for sending mail
319
320 $envir{mailSmtpServer} = $courseEnv->{mail}->{smtpServer};
321 $envir{mailSmtpSender} = $courseEnv->{mail}->{smtpSender};
327 322
328 # Default values for evaluating answers 323 # Default values for evaluating answers
329 324
330 my $ansEvalDefaults = $courseEnv->{pg}->{ansEvalDefaults}; 325 my $ansEvalDefaults = $courseEnv->{pg}->{ansEvalDefaults};
331 $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults); 326 $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults);
332 327
333 # Other things... 328 # Other things...
334 329
335 $envir{PROBLEM_GRADER_TO_USE} = $courseEnv->{pg}->{options}->{grader}; 330 $envir{PROBLEM_GRADER_TO_USE} = $courseEnv->{pg}->{options}->{grader};
331 $envir{ALLOW_MAIL_TO} = $courseEnv->{email}->{allowedRecipients};
336 332
337 return \%envir; 333 return \%envir;
338} 334}
339 335
340sub translateDisplayModeNames($) { 336sub translateDisplayModeNames($) {
359 $errorno = 0; ## don't report blank answer as error 355 $errorno = 0; ## don't report blank answer as error
360 return ($answer,$errorno); 356 return ($answer,$errorno);
361 } 357 }
362 # replace ^ with ** (for exponentiation) 358 # replace ^ with ** (for exponentiation)
363 # $answer =~ s/\^/**/g; 359 # $answer =~ s/\^/**/g;
364 # Return if forbidden characters are found 360 # Return if forbidden characters are found
365 unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ ) { 361 unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ ) {
366 $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c; 362 $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
367 $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>"; 363 $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
368 return ($answer,$errorno); 364 return ($answer,$errorno);
369 } 365 }

Legend:
Removed from v.555  
changed lines
  Added in v.698

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9