| … | |
… | |
| 2 | # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project |
2 | # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project |
| 3 | # $Id$ |
3 | # $Id$ |
| 4 | ################################################################################ |
4 | ################################################################################ |
| 5 | |
5 | |
| 6 | package WeBWorK::ContentGenerator::Problem; |
6 | package WeBWorK::ContentGenerator::Problem; |
|
|
7 | use base qw(WeBWorK::ContentGenerator); |
| 7 | |
8 | |
| 8 | =head1 NAME |
9 | =head1 NAME |
| 9 | |
10 | |
| 10 | WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem. |
11 | WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem. |
| 11 | |
12 | |
| 12 | =cut |
13 | =cut |
| 13 | |
14 | |
| 14 | use strict; |
15 | use strict; |
| 15 | use warnings; |
16 | use warnings; |
| 16 | use base qw(WeBWorK::ContentGenerator); |
|
|
| 17 | use CGI qw(); |
17 | use CGI qw(); |
| 18 | use File::Temp qw(tempdir); |
18 | use File::Temp qw(tempdir); |
| 19 | use WeBWorK::Form; |
19 | use WeBWorK::Form; |
| 20 | use WeBWorK::PG; |
20 | use WeBWorK::PG; |
| 21 | use WeBWorK::PG::IO; |
21 | use WeBWorK::PG::IO; |
| … | |
… | |
| 35 | # |
35 | # |
| 36 | # AnSwEr# - answer blanks in problem |
36 | # AnSwEr# - answer blanks in problem |
| 37 | # |
37 | # |
| 38 | # redisplay - name of the "Redisplay Problem" button |
38 | # redisplay - name of the "Redisplay Problem" button |
| 39 | # submitAnswers - name of "Submit Answers" button |
39 | # submitAnswers - name of "Submit Answers" button |
|
|
40 | # checkAnswers - name of the "Check Answers" button |
|
|
41 | # previewAnswers - name of the "Preview Answers" button |
| 40 | # |
42 | # |
| 41 | ############################################################ |
43 | ############################################################ |
| 42 | |
44 | |
| 43 | sub pre_header_initialize { |
45 | sub pre_header_initialize { |
| 44 | my ($self, $setName, $problemNumber) = @_; |
46 | my ($self, $setName, $problemNumber) = @_; |
|
|
47 | my $r = $self->{r}; |
| 45 | my $courseEnv = $self->{ce}; |
48 | my $courseEnv = $self->{ce}; |
| 46 | my $r = $self->{r}; |
49 | my $db = $self->{db}; |
| 47 | my $userName = $r->param('user'); |
50 | my $userName = $r->param('user'); |
| 48 | my $effectiveUserName = $r->param('effectiveUser'); |
51 | my $effectiveUserName = $r->param('effectiveUser'); |
| 49 | |
52 | |
| 50 | ##### database setup ##### |
|
|
| 51 | |
|
|
| 52 | my $cldb = WeBWorK::DB::Classlist->new($courseEnv); |
|
|
| 53 | my $wwdb = WeBWorK::DB::WW->new($courseEnv); |
|
|
| 54 | my $authdb = WeBWorK::DB::Auth->new($courseEnv); |
|
|
| 55 | |
|
|
| 56 | my $user = $cldb->getUser($userName); |
53 | my $user = $db->getUser($userName); |
| 57 | my $effectiveUser = $cldb->getUser($effectiveUserName); |
54 | my $effectiveUser = $db->getUser($effectiveUserName); |
| 58 | my $set = $wwdb->getSet($effectiveUserName, $setName); |
55 | my $set = $db->getGlobalUserSet($effectiveUserName, $setName); |
| 59 | my $problem = $wwdb->getProblem($effectiveUserName, $setName, $problemNumber); |
56 | my $problem = $db->getGlobalUserProblem($effectiveUserName, $setName, $problemNumber); |
| 60 | my $psvn = $wwdb->getPSVN($effectiveUserName, $setName); |
57 | my $psvn = $set->psvn(); |
| 61 | my $permissionLevel = $authdb->getPermissions($userName); |
58 | my $permissionLevel = $db->getPermissionLevel($userName)->permission(); |
| 62 | |
|
|
| 63 | $self->{cldb} = $cldb; |
|
|
| 64 | $self->{wwdb} = $wwdb; |
|
|
| 65 | $self->{authdb} = $authdb; |
|
|
| 66 | |
59 | |
| 67 | $self->{userName} = $userName; |
60 | $self->{userName} = $userName; |
| 68 | $self->{user} = $user; |
61 | $self->{user} = $user; |
| 69 | $self->{effectiveUser} = $effectiveUser; |
62 | $self->{effectiveUser} = $effectiveUser; |
| 70 | $self->{set} = $set; |
63 | $self->{set} = $set; |
| … | |
… | |
| 195 | } |
188 | } |
| 196 | |
189 | |
| 197 | sub path { |
190 | sub path { |
| 198 | my $self = shift; |
191 | my $self = shift; |
| 199 | my $args = $_[-1]; |
192 | my $args = $_[-1]; |
| 200 | my $setName = $self->{set}->id; |
193 | my $setName = $self->{set}->set_id; |
| 201 | my $problemNumber = $self->{problem}->id; |
194 | my $problemNumber = $self->{problem}->problem_id; |
| 202 | |
195 | |
| 203 | my $ce = $self->{ce}; |
196 | my $ce = $self->{ce}; |
| 204 | my $root = $ce->{webworkURLs}->{root}; |
197 | my $root = $ce->{webworkURLs}->{root}; |
| 205 | my $courseName = $ce->{courseName}; |
198 | my $courseName = $ce->{courseName}; |
| 206 | return $self->pathMacro($args, |
199 | return $self->pathMacro($args, |
| … | |
… | |
| 211 | ); |
204 | ); |
| 212 | } |
205 | } |
| 213 | |
206 | |
| 214 | sub siblings { |
207 | sub siblings { |
| 215 | my $self = shift; |
208 | my $self = shift; |
| 216 | my $setName = $self->{set}->id; |
209 | my $setName = $self->{set}->set_id; |
| 217 | my $problemNumber = $self->{problem}->id; |
210 | my $problemNumber = $self->{problem}->problem_id; |
| 218 | |
211 | |
| 219 | my $ce = $self->{ce}; |
212 | my $ce = $self->{ce}; |
|
|
213 | my $db = $self->{db}; |
| 220 | my $root = $ce->{webworkURLs}->{root}; |
214 | my $root = $ce->{webworkURLs}->{root}; |
| 221 | my $courseName = $ce->{courseName}; |
215 | my $courseName = $ce->{courseName}; |
| 222 | |
216 | |
| 223 | print CGI::strong("Problems"), CGI::br(); |
217 | print CGI::strong("Problems"), CGI::br(); |
| 224 | |
218 | |
| 225 | my $wwdb = $self->{wwdb}; |
|
|
| 226 | my $effectiveUser = $self->{r}->param("effectiveUser"); |
219 | my $effectiveUser = $self->{r}->param("effectiveUser"); |
| 227 | my @problems; |
220 | my @problems; |
| 228 | push @problems, $wwdb->getProblem($effectiveUser, $setName, $_) |
221 | push @problems, $db->getGlobalUserProblem($effectiveUser, $setName, $_) |
| 229 | foreach ($wwdb->getProblems($effectiveUser, $setName)); |
222 | foreach ($db->listUserProblems($effectiveUser, $setName)); |
| 230 | foreach my $problem (sort { $a->id <=> $b->id } @problems) { |
223 | foreach my $problem (sort { $a->problem_id <=> $b->problem_id } @problems) { |
| 231 | print CGI::a({-href=>"$root/$courseName/$setName/".$problem->id."/?" |
224 | print CGI::a({-href=>"$root/$courseName/$setName/".$problem->problem_id."/?" |
| 232 | . $self->url_authen_args . "&displayMode=" . $self->{displayMode}}, |
225 | . $self->url_authen_args . "&displayMode=" . $self->{displayMode}}, |
| 233 | "Problem ".$problem->id), CGI::br(); |
226 | "Problem ".$problem->problem_id), CGI::br(); |
| 234 | } |
227 | } |
| 235 | } |
228 | } |
| 236 | |
229 | |
| 237 | sub nav { |
230 | sub nav { |
| 238 | my $self = shift; |
231 | my $self = shift; |
| 239 | my $args = $_[-1]; |
232 | my $args = $_[-1]; |
| 240 | my $setName = $self->{set}->id; |
233 | my $setName = $self->{set}->set_id; |
| 241 | my $problemNumber = $self->{problem}->id; |
234 | my $problemNumber = $self->{problem}->problem_id; |
| 242 | |
235 | |
| 243 | my $ce = $self->{ce}; |
236 | my $ce = $self->{ce}; |
|
|
237 | my $db = $self->{db}; |
| 244 | my $root = $ce->{webworkURLs}->{root}; |
238 | my $root = $ce->{webworkURLs}->{root}; |
| 245 | my $courseName = $ce->{courseName}; |
239 | my $courseName = $ce->{courseName}; |
| 246 | |
240 | |
| 247 | my $wwdb = $self->{wwdb}; |
241 | my $wwdb = $self->{wwdb}; |
| 248 | my $effectiveUser = $self->{r}->param("effectiveUser"); |
242 | my $effectiveUser = $self->{r}->param("effectiveUser"); |
| 249 | my $tail = "&displayMode=".$self->{displayMode}; |
243 | my $tail = "&displayMode=".$self->{displayMode}; |
| 250 | |
244 | |
| 251 | my @links = ("Problem List" , "$root/$courseName/$setName", "navProbList"); |
245 | my @links = ("Problem List" , "$root/$courseName/$setName", "navProbList"); |
| 252 | |
246 | |
| 253 | my $prevProblem = $wwdb->getProblem($effectiveUser, $setName, $problemNumber-1); |
247 | my $prevProblem = $db->getGlobalUserProblem($effectiveUser, $setName, $problemNumber-1); |
| 254 | my $nextProblem = $wwdb->getProblem($effectiveUser, $setName, $problemNumber+1); |
248 | my $nextProblem = $db->getGlobalUserProblem($effectiveUser, $setName, $problemNumber+1); |
| 255 | unshift @links, "Previous Problem" , ($prevProblem |
249 | unshift @links, "Previous Problem" , ($prevProblem |
| 256 | ? "$root/$courseName/$setName/".$prevProblem->id |
250 | ? "$root/$courseName/$setName/".$prevProblem->problem_id |
| 257 | : "") , "navPrev"; |
251 | : "") , "navPrev"; |
| 258 | push @links, "Next Problem" , ($nextProblem |
252 | push @links, "Next Problem" , ($nextProblem |
| 259 | ? "$root/$courseName/$setName/".$nextProblem->id |
253 | ? "$root/$courseName/$setName/".$nextProblem->problem_id |
| 260 | : "") , "navNext"; |
254 | : "") , "navNext"; |
| 261 | |
255 | |
| 262 | return $self->navMacro($args, $tail, @links); |
256 | return $self->navMacro($args, $tail, @links); |
| 263 | } |
257 | } |
| 264 | |
258 | |
| 265 | sub title { |
259 | sub title { |
| 266 | my $self = shift; |
260 | my $self = shift; |
| 267 | my $setName = $self->{set}->id; |
261 | my $setName = $self->{set}->set_id; |
| 268 | my $problemNumber = $self->{problem}->id; |
262 | my $problemNumber = $self->{problem}->problem_id; |
| 269 | |
263 | |
| 270 | return "$setName : Problem $problemNumber"; |
264 | return "$setName : Problem $problemNumber"; |
| 271 | } |
265 | } |
| 272 | |
266 | |
| 273 | sub body { |
267 | sub body { |
| … | |
… | |
| 276 | return CGI::p(CGI::font({-color=>"red"}, "This problem is not available because the problem set that contains it is not yet open.")) |
270 | return CGI::p(CGI::font({-color=>"red"}, "This problem is not available because the problem set that contains it is not yet open.")) |
| 277 | unless $self->{isOpen}; |
271 | unless $self->{isOpen}; |
| 278 | |
272 | |
| 279 | # unpack some useful variables |
273 | # unpack some useful variables |
| 280 | my $r = $self->{r}; |
274 | my $r = $self->{r}; |
| 281 | my $wwdb = $self->{wwdb}; |
275 | my $db = $self->{db}; |
| 282 | my $set = $self->{set}; |
276 | my $set = $self->{set}; |
| 283 | my $problem = $self->{problem}; |
277 | my $problem = $self->{problem}; |
| 284 | my $permissionLevel = $self->{permissionLevel}; |
278 | my $permissionLevel = $self->{permissionLevel}; |
| 285 | my $submitAnswers = $self->{submitAnswers}; |
279 | my $submitAnswers = $self->{submitAnswers}; |
| 286 | my $checkAnswers = $self->{checkAnswers}; |
280 | my $checkAnswers = $self->{checkAnswers}; |
| … | |
… | |
| 299 | |
293 | |
| 300 | ##### answer processing ##### |
294 | ##### answer processing ##### |
| 301 | |
295 | |
| 302 | # if answers were submitted: |
296 | # if answers were submitted: |
| 303 | if ($submitAnswers) { |
297 | if ($submitAnswers) { |
|
|
298 | # get a "pure" (unmerged) UserProblem to modify |
|
|
299 | my $pureProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id); |
| 304 | # store answers in DB for sticky answers |
300 | # store answers in DB for sticky answers |
| 305 | my %answersToStore; |
301 | my %answersToStore; |
| 306 | my %answerHash = %{ $pg->{answers} }; |
302 | my %answerHash = %{ $pg->{answers} }; |
| 307 | $answersToStore{$_} = $answerHash{$_}->{original_student_ans} |
303 | $answersToStore{$_} = $answerHash{$_}->{original_student_ans} |
| 308 | foreach (keys %answerHash); |
304 | foreach (keys %answerHash); |
| 309 | my $answerString = encodeAnswers(%answersToStore, |
305 | my $answerString = encodeAnswers(%answersToStore, |
| 310 | @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} }); |
306 | @{ $pg->{flags}->{ANSWER_ENTRY_ORDER} }); |
|
|
307 | $pureProblem->last_answer($answerString); |
| 311 | $problem->last_answer($answerString); |
308 | $problem->last_answer($answerString); |
| 312 | $wwdb->setProblem($problem); |
309 | $db->putUserProblem($pureProblem); |
| 313 | |
310 | |
| 314 | # store state in DB if it makes sense |
311 | # store state in DB if it makes sense |
| 315 | if ($will{recordAnswers}) { |
312 | if ($will{recordAnswers}) { |
|
|
313 | $problem->status($pg->{state}->{recorded_score}); |
| 316 | $problem->attempted(1); |
314 | $problem->attempted(1); |
| 317 | $problem->status($pg->{state}->{recorded_score}); |
|
|
| 318 | $problem->num_correct($pg->{state}->{num_of_correct_ans}); |
315 | $problem->num_correct($pg->{state}->{num_of_correct_ans}); |
| 319 | $problem->num_incorrect($pg->{state}->{num_of_incorrect_ans}); |
316 | $problem->num_incorrect($pg->{state}->{num_of_incorrect_ans}); |
|
|
317 | $pureProblem->status($pg->{state}->{recorded_score}); |
|
|
318 | $pureProblem->attempted(1); |
|
|
319 | $pureProblem->num_correct($pg->{state}->{num_of_correct_ans}); |
|
|
320 | $pureProblem->num_incorrect($pg->{state}->{num_of_incorrect_ans}); |
| 320 | $wwdb->setProblem($problem); |
321 | $db->putUserProblem($pureProblem); |
| 321 | # write to the transaction log, just to make sure |
322 | # write to the transaction log, just to make sure |
| 322 | writeLog($self->{ce}, "transaction", |
323 | writeLog($self->{ce}, "transaction", |
| 323 | $problem->id."\t". |
324 | $problem->problem_id."\t". |
| 324 | $problem->set_id."\t". |
325 | $problem->set_id."\t". |
| 325 | $problem->login_id."\t". |
326 | $problem->user_id."\t". |
| 326 | $problem->source_file."\t". |
327 | $problem->source_file."\t". |
| 327 | $problem->value."\t". |
328 | $problem->value."\t". |
| 328 | $problem->max_attempts."\t". |
329 | $problem->max_attempts."\t". |
| 329 | $problem->problem_seed."\t". |
330 | $problem->problem_seed."\t". |
| 330 | $problem->status."\t". |
331 | $pureProblem->status."\t". |
| 331 | $problem->attempted."\t". |
332 | $pureProblem->attempted."\t". |
| 332 | $problem->last_answer."\t". |
333 | $pureProblem->last_answer."\t". |
| 333 | $problem->num_correct."\t". |
334 | $pureProblem->num_correct."\t". |
| 334 | $problem->num_incorrect |
335 | $pureProblem->num_incorrect |
| 335 | ); |
336 | ); |
| 336 | } |
337 | } |
| 337 | } |
338 | } |
| 338 | # logging student answers |
339 | # logging student answers |
| 339 | my $pastAnswerLog = undef; |
340 | my $pastAnswerLog = undef; |
| … | |
… | |
| 345 | my $answerString = ""; |
346 | my $answerString = ""; |
| 346 | my %answerHash = %{ $pg->{answers} }; |
347 | my %answerHash = %{ $pg->{answers} }; |
| 347 | $answerString = $answerString . $answerHash{$_}->{original_student_ans}."\t" |
348 | $answerString = $answerString . $answerHash{$_}->{original_student_ans}."\t" |
| 348 | foreach (sort keys %answerHash); |
349 | foreach (sort keys %answerHash); |
| 349 | writeLog($self->{ce}, "pastAnswerList", |
350 | writeLog($self->{ce}, "pastAnswerList", |
| 350 | '|'.$problem->login_id. |
351 | '|'.$problem->user_id. |
| 351 | '|'.$problem->set_id. |
352 | '|'.$problem->set_id. |
| 352 | '|'.$problem->id.'|'."\t". |
353 | '|'.$problem->problem_id.'|'."\t". |
| 353 | time()."\t". |
354 | time()."\t". |
| 354 | $answerString, |
355 | $answerString, |
| 355 | |
356 | |
| 356 | ); |
357 | ); |
| 357 | |
358 | |
| … | |
… | |
| 464 | #print feedback form |
465 | #print feedback form |
| 465 | print |
466 | print |
| 466 | CGI::start_form(-method=>"POST", -action=>$feedbackURL),"\n", |
467 | CGI::start_form(-method=>"POST", -action=>$feedbackURL),"\n", |
| 467 | $self->hidden_authen_fields,"\n", |
468 | $self->hidden_authen_fields,"\n", |
| 468 | CGI::hidden("module", __PACKAGE__),"\n", |
469 | CGI::hidden("module", __PACKAGE__),"\n", |
| 469 | CGI::hidden("set", $set->id),"\n", |
470 | CGI::hidden("set", $set->set_id),"\n", |
| 470 | CGI::hidden("problem", $problem->id),"\n", |
471 | CGI::hidden("problem", $problem->problem_id),"\n", |
| 471 | CGI::hidden("displayMode", $self->{displayMode}),"\n", |
472 | CGI::hidden("displayMode", $self->{displayMode}),"\n", |
| 472 | CGI::hidden("showOldAnswers", $will{showOldAnswers}),"\n", |
473 | CGI::hidden("showOldAnswers", $will{showOldAnswers}),"\n", |
| 473 | CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n", |
474 | CGI::hidden("showCorrectAnswers", $will{showCorrectAnswers}),"\n", |
| 474 | CGI::hidden("showHints", $will{showHints}),"\n", |
475 | CGI::hidden("showHints", $will{showHints}),"\n", |
| 475 | CGI::hidden("showSolutions", $will{showSolutions}),"\n", |
476 | CGI::hidden("showSolutions", $will{showSolutions}),"\n", |
| … | |
… | |
| 483 | |
484 | |
| 484 | print "\n", |
485 | print "\n", |
| 485 | CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"information"),"\n", |
486 | CGI::start_form(-method=>"POST",-action=>$showPastAnswersURL,-target=>"information"),"\n", |
| 486 | $self->hidden_authen_fields,"\n", |
487 | $self->hidden_authen_fields,"\n", |
| 487 | CGI::hidden(-name => 'course', -value=>$courseName), "\n", |
488 | CGI::hidden(-name => 'course', -value=>$courseName), "\n", |
| 488 | CGI::hidden(-name => 'probNum', -value=>$problem->id), "\n", |
489 | CGI::hidden(-name => 'probNum', -value=>$problem->problem_id), "\n", |
| 489 | CGI::hidden(-name => 'setNum', -value=>$problem->set_id), "\n", |
490 | CGI::hidden(-name => 'setNum', -value=>$problem->set_id), "\n", |
| 490 | CGI::hidden(-name => 'User', -value=>$problem->login_id), "\n", |
491 | CGI::hidden(-name => 'User', -value=>$problem->user_id), "\n", |
| 491 | CGI::submit(-name => 'action', -value=>'Show Past Answers'), "\n", |
492 | CGI::submit(-name => 'action', -value=>'Show Past Answers'), "\n", |
| 492 | CGI::endform(); |
493 | CGI::endform(); |
| 493 | |
494 | |
| 494 | |
495 | |
| 495 | |
496 | |
| … | |
… | |
| 642 | # so we'll just deal with each case explicitly here. there's some code |
643 | # so we'll just deal with each case explicitly here. there's some code |
| 643 | # duplication that can be dealt with later by abstracting out tth/dvipng/etc. |
644 | # duplication that can be dealt with later by abstracting out tth/dvipng/etc. |
| 644 | |
645 | |
| 645 | my $tex = $answerResult->{preview_latex_string}; |
646 | my $tex = $answerResult->{preview_latex_string}; |
| 646 | |
647 | |
| 647 | return "" unless $tex; |
648 | return "" if $tex eq ""; |
| 648 | |
649 | |
| 649 | if ($displayMode eq "plainText") { |
650 | if ($displayMode eq "plainText") { |
| 650 | return $tex; |
651 | return $tex; |
| 651 | } elsif ($displayMode eq "formattedText") { |
652 | } elsif ($displayMode eq "formattedText") { |
| 652 | my $tthCommand = $ce->{externalPrograms}->{tth} |
653 | my $tthCommand = $ce->{externalPrograms}->{tth} |
| … | |
… | |
| 662 | return $result; |
663 | return $result; |
| 663 | } elsif ($displayMode eq "images") { |
664 | } elsif ($displayMode eq "images") { |
| 664 | # how are we going to name this? |
665 | # how are we going to name this? |
| 665 | my $targetPathCommon = "/png/" |
666 | my $targetPathCommon = "/png/" |
| 666 | . $effectiveUser->id . "." |
667 | . $effectiveUser->id . "." |
| 667 | . $set->id . "." |
668 | . $set->set_id . "." |
| 668 | . $problem->id . "." |
669 | . $problem->problem_id . "." |
| 669 | . $answerResult->{ans_name} . ".png"; |
670 | . $answerResult->{ans_name} . ".png"; |
| 670 | |
671 | |
| 671 | # figure out where to put things |
672 | # figure out where to put things |
| 672 | my $wd = tempdir("webwork-dvipng-XXXXXXXX", DIR => $ce->{courseDirs}->{html_temp}); |
673 | my $wd = tempdir("webwork-dvipng-XXXXXXXX", DIR => $ce->{courseDirs}->{html_temp}); |
| 673 | my $latex = $ce->{externalPrograms}->{latex}; |
674 | my $latex = $ce->{externalPrograms}->{latex}; |
| … | |
… | |
| 676 | # should use surePathToTmpFile, but we have to |
677 | # should use surePathToTmpFile, but we have to |
| 677 | # isolate it from the problem enivronment first |
678 | # isolate it from the problem enivronment first |
| 678 | my $targetURL = $ce->{courseURLs}->{html_temp} . $targetPathCommon; |
679 | my $targetURL = $ce->{courseURLs}->{html_temp} . $targetPathCommon; |
| 679 | |
680 | |
| 680 | # call dvipng to generate a preview |
681 | # call dvipng to generate a preview |
| 681 | warn $tex; |
|
|
| 682 | dvipng($wd, $latex, $dvipng, $tex, $targetPath); |
682 | dvipng($wd, $latex, $dvipng, $tex, $targetPath); |
| 683 | if (-e $targetPath) { |
683 | if (-e $targetPath) { |
| 684 | return "<img src=\"$targetURL\" alt=\"$tex\" />"; |
684 | return "<img src=\"$targetURL\" alt=\"$tex\" />"; |
| 685 | } else { |
685 | } else { |
| 686 | return "<b>[math2img failed]</b>"; |
686 | return "<b>[math2img failed]</b>"; |