| … | |
… | |
| 14 | use strict; |
14 | use strict; |
| 15 | use warnings; |
15 | use warnings; |
| 16 | use Apache::Constants qw(:common); |
16 | use Apache::Constants qw(:common); |
| 17 | use CGI qw(); |
17 | use CGI qw(); |
| 18 | use URI::Escape; |
18 | use URI::Escape; |
|
|
19 | use WeBWorK::DB::Auth; |
| 19 | use WeBWorK::Utils qw(readFile); |
20 | use WeBWorK::Utils qw(readFile); |
| 20 | #use CGI::Carp qw(fatalsToBrowser); |
21 | use Carp qw(cluck); |
| 21 | |
22 | |
| 22 | ################################################################################ |
23 | ################################################################################ |
| 23 | # This is a very unruly file, so I'm going to use very large comments to divide |
24 | # This is a very unruly file, so I'm going to use very large comments to divide |
| 24 | # it into logical sections. |
25 | # it into logical sections. |
| 25 | ################################################################################ |
26 | ################################################################################ |
| … | |
… | |
| 87 | # |
88 | # |
| 88 | sub template { |
89 | sub template { |
| 89 | my ($self, $templateFile) = (shift, shift); |
90 | my ($self, $templateFile) = (shift, shift); |
| 90 | my $r = $self->{r}; |
91 | my $r = $self->{r}; |
| 91 | my $courseEnvironment = $self->{courseEnvironment}; |
92 | my $courseEnvironment = $self->{courseEnvironment}; |
|
|
93 | my @ifstack = (1); # Start off in printing mode |
|
|
94 | # say $ifstack[-1] to get the result of the last <#!--if--> |
| 92 | |
95 | |
| 93 | # so even though the variable $/ APPEARS to contain a newline, |
96 | # so even though the variable $/ APPEARS to contain a newline, |
| 94 | # <TEMPLATE> is slurping the whole file into the first element of |
97 | # <TEMPLATE> is slurping the whole file into the first element of |
| 95 | # @template ONLY AFTER THE TRANSLATOR RUNS. WTF!!! |
98 | # @template ONLY AFTER THE TRANSLATOR RUNS. WTF!!! |
| 96 | # |
99 | # |
| 97 | #open(TEMPLATE, $templateFile) or die "Couldn't open template $templateFile"; |
100 | #open(TEMPLATE, $templateFile) or die "Couldn't open template $templateFile"; |
| 98 | #my @template = <TEMPLATE>; |
101 | #my @template = <TEMPLATE>; |
| 99 | #close TEMPLATE; |
102 | #close TEMPLATE; |
| 100 | # |
103 | # |
| 101 | # Let's try something else instead: |
104 | # Let's try something else instead: |
| 102 | |
|
|
| 103 | my @template = split /\n/, readFile($templateFile); |
105 | my @template = split /\n/, readFile($templateFile); |
| 104 | |
106 | |
| 105 | foreach my $line (@template) { |
107 | foreach my $line (@template) { |
| 106 | #warn "foo: $line\n"; |
|
|
| 107 | # This is incremental regex processing. |
108 | # This is incremental regex processing. |
| 108 | # the /c is so that pos($line) doesn't die when the regex fails. |
109 | # the /c is so that pos($line) doesn't die when the regex fails. |
| 109 | while ($line =~ m/\G(.*?)<!--#(\w*)((?:\s+.*?)?)-->/gc) { |
110 | while ($line =~ m/\G(.*?)<!--#(\w*)((?:\s+.*?)?)-->/gc) { |
| 110 | my ($before, $function, $raw_args) = ($1, $2, $3); |
111 | my ($before, $function, $raw_args) = ($1, $2, $3); |
| 111 | # $args here will be a hashref |
|
|
| 112 | my $args = $raw_args =~ /\S/ ? cook_args($raw_args) : {}; |
112 | my @args = ($raw_args =~ /\S/) ? cook_args($raw_args) : (); |
| 113 | print $before; |
|
|
| 114 | |
113 | |
| 115 | if ($self->can($function)) { |
114 | if ($ifstack[-1]) { |
| 116 | print $self->$function(@_, $args); |
115 | print $before; |
| 117 | } |
116 | } |
|
|
117 | |
|
|
118 | if ($function eq "if") { |
|
|
119 | # a predicate can only be true if everything else on the ifstack is already true, for ANDing |
|
|
120 | push @ifstack, ($self->$function(@_, [@args]) && $ifstack[-1]); |
|
|
121 | } elsif ($function eq "else" and @ifstack > 1) { |
|
|
122 | $ifstack[-1] = not $ifstack[-1]; |
|
|
123 | } elsif ($function eq "endif" and @ifstack > 1) { |
|
|
124 | pop @ifstack; |
|
|
125 | } elsif ($ifstack[-1]) { |
|
|
126 | if ($self->can($function)) { |
|
|
127 | print $self->$function(@_, {@args}); |
|
|
128 | } |
|
|
129 | } |
| 118 | } |
130 | } |
| 119 | |
131 | |
|
|
132 | if ($ifstack[-1]) { |
| 120 | print substr $line, (defined(pos($line)) ? pos($line) : 0); |
133 | print substr($line, (defined pos $line) ? pos $line : 0), "\n"; |
|
|
134 | } |
| 121 | } |
135 | } |
| 122 | } |
136 | } |
| 123 | |
137 | |
| 124 | # cook_args(STRING) - parses a string of the form ARG1="FOO" ARG2="BAR". Returns |
138 | # cook_args(STRING) - parses a string of the form ARG1="FOO" ARG2="BAR". Returns |
| 125 | # a reference to a hash containing the parsed arguments. |
139 | # a list which pairs into key/values and fits nicely in {}s. |
| 126 | # |
140 | # |
| 127 | sub cook_args($) { |
141 | sub cook_args($) { # ... also used by bin/wwdb, so watch out |
| 128 | # There are a bunch of commented-out lines that I am using to remind myself |
|
|
| 129 | # That I want to write a better regex sometime. |
|
|
| 130 | my ($raw_args) = @_; |
142 | my ($raw_args) = @_; |
| 131 | my $args = {}; |
143 | my @args = (); |
| 132 | #my $quotable_string = qr/(?:".*?(?<*\\)"|\W*)/; |
|
|
| 133 | #my $quotable_string = qr/(?:".*?(?<!\\)"|\W*)/; |
|
|
| 134 | #my $test_string = '"hel \" lo" hello'; |
|
|
| 135 | |
144 | |
| 136 | #warn $test_string =~ m/($quotable_string)/ ? $1 : "false"; |
145 | # Boy I love m//g in scalar context! Go read the camel book, heathen. |
| 137 | |
146 | # First, get the whole token with the quotes on both ends... |
| 138 | while ($raw_args =~ m/\G\s*(\w*)="(.*?)"/g) { |
147 | while ($raw_args =~ m/\G\s*(\w*)="((?:[^"\\]|\\.)*)"/g) { |
| 139 | #while ($raw_args =~ m/\G\s*($quotable_string)=($quotable_string)/g) { |
148 | my ($key, $value) = ($1, $2); |
| 140 | $args->{$1} = $2; |
149 | # ... then, rip out all the protecty backspaces |
|
|
150 | $value =~ s/\\(.)/$1/g; |
|
|
151 | push @args, $key => $value; |
| 141 | } |
152 | } |
| 142 | |
153 | |
| 143 | return $args; |
154 | return @args; |
|
|
155 | } |
|
|
156 | |
|
|
157 | # This is different. It probably shouldn't print anything (except in debugging cases) |
|
|
158 | # and it should return a boolean, not a string. &if is called in a nonstandard way |
|
|
159 | # by &template, with $args as an arrayref instead of a hashref. this is a hack! yay! |
|
|
160 | |
|
|
161 | # OK, this is a pluggin architecture. it iterates through attributes of the "if" tag, |
|
|
162 | # and for each predicate $p, it calls &if_$p in an object-oriented way, continuing the |
|
|
163 | # grand templating theme of an object-oriented pluggable architecture using ->can($). |
|
|
164 | sub if { |
|
|
165 | my ($self, $args) = @_[0,-1]; |
|
|
166 | # A single if "or"s it's components. Nesting produces "and". |
|
|
167 | |
|
|
168 | my @args = @$args; # Hahahahaha, get it?! |
|
|
169 | |
|
|
170 | if (@args % 2 != 0) { |
|
|
171 | # flip out and kill people, but do not commit seppuku |
|
|
172 | print '<!--&if recieved an uneven number of arguments. This shouldn\'t happen, but I\'ll let it slide.-->\n'; |
|
|
173 | } |
|
|
174 | |
|
|
175 | while (@args > 1) { |
|
|
176 | my ($key, $value) = (shift @args, shift @args); |
|
|
177 | |
|
|
178 | # a non-existent &if_$key is the same as a false result, but we're ORing, so it's OK |
|
|
179 | my $sub = "if_$key"; # perl doesn't like it when you try to construct a string right in a method invocation |
|
|
180 | if ($self->can("if_$key") and $self->$sub("$value")) { |
|
|
181 | return 1; |
|
|
182 | } |
|
|
183 | } |
|
|
184 | |
|
|
185 | return 0; |
| 144 | } |
186 | } |
| 145 | |
187 | |
| 146 | ################################################################################ |
188 | ################################################################################ |
| 147 | # Macros used by content generators to render common idioms |
189 | # Macros used by content generators to render common idioms |
| 148 | ################################################################################ |
190 | ################################################################################ |
| … | |
… | |
| 197 | } |
239 | } |
| 198 | |
240 | |
| 199 | sub navMacro { |
241 | sub navMacro { |
| 200 | my $self = shift; |
242 | my $self = shift; |
| 201 | my %args = %{ shift() }; |
243 | my %args = %{ shift() }; |
|
|
244 | my $tail = shift; |
| 202 | my @links = @_; |
245 | my @links = @_; |
| 203 | my $auth = $self->url_authen_args; |
246 | my $auth = $self->url_authen_args; |
|
|
247 | my $ce = $self->{courseEnvironment}; |
|
|
248 | my $prefix = $ce->{webworkURLs}->{htdocs}."/images"; |
| 204 | my @result; |
249 | my @result; |
| 205 | while (@links) { |
250 | while (@links) { |
| 206 | my $name = shift @links; |
251 | my $name = shift @links; |
| 207 | my $url = shift @links; |
252 | my $url = shift @links; |
| 208 | push @result, $url |
253 | my $img = shift @links; |
| 209 | ? CGI::a({-href=>"$url?$auth"}, $name) |
254 | my $html = |
|
|
255 | ($img && $args{style} eq "images") |
|
|
256 | ? CGI::img( |
|
|
257 | {src=>($prefix."/".$img.$args{imagesuffix}), |
|
|
258 | border=>"", |
|
|
259 | alt=>"$name"}) |
| 210 | : $name; |
260 | : $name; |
|
|
261 | unless($img && !$url) { |
|
|
262 | push @result, $url |
|
|
263 | ? CGI::a({-href=>"$url?$auth$tail"}, $html) |
|
|
264 | : $html; |
|
|
265 | } |
| 211 | } |
266 | } |
| 212 | return join($args{separator}, @result), "\n"; |
267 | return join($args{separator}, @result), "\n"; |
| 213 | } |
268 | } |
| 214 | |
269 | |
| 215 | # hidden_fields(LIST) - return hidden <INPUT> tags for each field mentioned in |
270 | # hidden_fields(LIST) - return hidden <INPUT> tags for each field mentioned in |
| … | |
… | |
| 289 | } |
344 | } |
| 290 | } |
345 | } |
| 291 | return $return_string; |
346 | return $return_string; |
| 292 | } |
347 | } |
| 293 | |
348 | |
|
|
349 | sub errorOutput($$$) { |
|
|
350 | my ($self, $error, $details) = @_; |
|
|
351 | return |
|
|
352 | CGI::h2("Software Error"), |
|
|
353 | CGI::p(<<EOF), |
|
|
354 | WeBWorK has encountered a software error while attempting to process this problem. |
|
|
355 | It is likely that there is an error in the problem itself. |
|
|
356 | If you are a student, contact your professor to have the error corrected. |
|
|
357 | If you are a professor, please consut the error output below for more informaiton. |
|
|
358 | EOF |
|
|
359 | CGI::h3("Error messages"), CGI::blockquote(CGI::pre($error)), |
|
|
360 | CGI::h3("Error context"), CGI::blockquote(CGI::pre($details)); |
|
|
361 | } |
|
|
362 | |
|
|
363 | sub warningOutput($$) { |
|
|
364 | my ($self, $warnings) = @_; |
|
|
365 | |
|
|
366 | return |
|
|
367 | CGI::h2("Software Warnings"), |
|
|
368 | CGI::p(<<EOF), |
|
|
369 | WeBWorK has encountered warnings while attempting to process this problem. |
|
|
370 | It is likely that this indicates an error or ambiguity in the problem itself. |
|
|
371 | If you are a student, contact your professor to have the problem corrected. |
|
|
372 | If you are a professor, please consut the error output below for more informaiton. |
|
|
373 | EOF |
|
|
374 | CGI::h3("Warning messages"), |
|
|
375 | CGI::blockquote(CGI::pre($warnings)), |
|
|
376 | ; |
|
|
377 | } |
|
|
378 | |
| 294 | ################################################################################ |
379 | ################################################################################ |
| 295 | # Generic versions of template escapes |
380 | # Generic versions of template escapes |
| 296 | ################################################################################ |
381 | ################################################################################ |
| 297 | |
382 | |
| 298 | # Reminder: here are the template functions currently defined: |
383 | # Reminder: here are the template functions currently defined: |
| 299 | # |
384 | # |
|
|
385 | # head |
| 300 | # path |
386 | # path |
| 301 | # style = text|image |
387 | # style = text|image |
| 302 | # image = URL of image |
388 | # image = URL of image |
| 303 | # text = text separator |
389 | # text = text separator |
| 304 | # quicklinks |
390 | # loginstatus |
|
|
391 | # links |
| 305 | # siblings |
392 | # siblings |
| 306 | # nav |
393 | # nav |
| 307 | # style = text|image |
394 | # style = text|image |
| 308 | # imageprefix = prefix to image URL |
395 | # imageprefix = prefix to image URL |
| 309 | # imagesuffix = suffix to image URL |
396 | # imagesuffix = suffix to image URL |
| … | |
… | |
| 316 | my $r = $self->{r}; |
403 | my $r = $self->{r}; |
| 317 | $r->content_type('text/html'); |
404 | $r->content_type('text/html'); |
| 318 | $r->send_http_header(); |
405 | $r->send_http_header(); |
| 319 | } |
406 | } |
| 320 | |
407 | |
|
|
408 | sub loginstatus { |
|
|
409 | my $self = shift; |
|
|
410 | my $r = $self->{r}; |
|
|
411 | my $user = $r->param("user"); |
|
|
412 | my $eUser = $r->param("effectiveUser"); |
|
|
413 | my $key = $r->param("key"); |
|
|
414 | return "" unless $key; |
|
|
415 | my $exitURL = $r->uri() . "?user=$user&key=$key"; |
|
|
416 | print CGI::small("User:", "$user"); |
|
|
417 | if ($user ne $eUser) { |
|
|
418 | print CGI::br(), CGI::font({-color=>'red'}, |
|
|
419 | CGI::small("Acting as:", "$eUser") |
|
|
420 | ), |
|
|
421 | CGI::br(), CGI::a({-href=>$exitURL}, |
|
|
422 | CGI::small("Stop Acting") |
|
|
423 | ); |
|
|
424 | } |
|
|
425 | return ""; |
|
|
426 | } |
|
|
427 | |
|
|
428 | # *** drunk code. rewrite. |
|
|
429 | # also, this should be structured s.t. subclasses can add items to the links |
|
|
430 | # area, i.e. "stacking" |
| 321 | sub quicklinks { |
431 | sub links { |
| 322 | my $self = shift; |
432 | my $self = shift; |
| 323 | my $ce = $self->{courseEnvironment}; |
433 | my $ce = $self->{courseEnvironment}; |
|
|
434 | my $userName = $self->{r}->param("user"); |
|
|
435 | my $courseName = $ce->{courseName}; |
| 324 | my $root = $ce->{webworkURLs}->{root}; |
436 | my $root = $ce->{webworkURLs}->{root}; |
| 325 | my $courseName = $ce->{courseName}; |
437 | my $permLevel = WeBWorK::DB::Auth->new($ce)->getPermissions($userName); |
|
|
438 | my $key = WeBWorK::DB::Auth->new($ce)->getKey($userName); |
|
|
439 | return "" unless defined $key; |
|
|
440 | |
|
|
441 | # URLs to parts of the system |
| 326 | my $probSets = "$root/$courseName/?" . $self->url_authen_args(); |
442 | my $probSets = "$root/$courseName/?" . $self->url_authen_args(); |
| 327 | # my $prefs = "$root/prefs/?" . $self->url_authen_args(); |
443 | my $prefs = "$root/$courseName/options/?" . $self->url_authen_args(); |
|
|
444 | my $prof = "$root/$courseName/prof/?" . $self->url_authen_args(); |
| 328 | # my $help = $ce->{webworkURLs}->{docs} . "?" . $self->url_authen_args(); |
445 | my $help = "$ce->{webworkURLs}->{docs}?" . $self->url_authen_args(); |
| 329 | my $logout = "$root/$courseName/"; |
446 | my $logout = "$root/$courseName/logout/?" . $self->url_authen_args(); |
|
|
447 | |
| 330 | return |
448 | return |
| 331 | CGI::a({-href=>$probSets}, "Problem Sets"), CGI::br(), |
449 | CGI::a({-href=>$probSets}, "Problem Sets"), CGI::br(), |
| 332 | # CGI::a({-href=>$prefs}, "User Options"), CGI::br(), |
450 | CGI::a({-href=>$prefs}, "User Options"), CGI::br(), |
|
|
451 | ($permLevel > 0 |
|
|
452 | ? CGI::a({-href=>$prof}, "Professor") . CGI::br() |
|
|
453 | : ""), |
| 333 | # CGI::a({-href=>$help}, "Help"), CGI::br(), |
454 | CGI::a({-href=>$help}, "Help"), CGI::br(), |
| 334 | CGI::a({-href=>$logout}, "Log Out"), CGI::br(), |
455 | CGI::a({-href=>$logout}, "Log Out"), CGI::br(), |
| 335 | ; |
456 | ; |
| 336 | } |
457 | } |
| 337 | |
458 | |
| 338 | sub title { |
459 | # &if_can will return 1 if the current object->can("do $_[1]") |
| 339 | return "WeBWorK"; |
460 | sub if_can ($$) { |
|
|
461 | my ($self, $arg) = (@_); |
|
|
462 | |
|
|
463 | if ($self->can("$arg")) { |
|
|
464 | return 1; |
|
|
465 | } else { |
|
|
466 | return 0; |
|
|
467 | } |
| 340 | } |
468 | } |
| 341 | |
469 | |
| 342 | sub body { |
470 | # Every content generator is logged in unless it says otherwise. |
| 343 | return "Generated content"; |
471 | sub if_loggedin($$) { |
|
|
472 | my ($self, $arg) = (@_); |
|
|
473 | |
|
|
474 | return $arg; |
| 344 | } |
475 | } |
| 345 | |
476 | |
| 346 | 1; |
477 | 1; |
|
|
478 | |
|
|
479 | __END__ |
|
|
480 | |
|
|
481 | =head1 AUTHOR |
|
|
482 | |
|
|
483 | Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu |
|
|
484 | and Sam Hathaway, sh002i (at) math.rochester.edu. |
|
|
485 | |
|
|
486 | =cut |