[system] / trunk / webwork-modperl / lib / WeBWorK / ContentGenerator.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork-modperl/lib/WeBWorK/ContentGenerator.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1358 - (view) (download) (as text)

1 : sh002i 455 ################################################################################
2 : sh002i 494 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
3 : sh002i 455 # $Id$
4 :     ################################################################################
5 :    
6 : malsyned 313 package WeBWorK::ContentGenerator;
7 : malsyned 305
8 : sh002i 455 =head1 NAME
9 :    
10 :     WeBWorK::ContentGenerator - base class for modules that generate page content.
11 :    
12 :     =cut
13 :    
14 : malsyned 441 use strict;
15 :     use warnings;
16 : malsyned 323 use Apache::Constants qw(:common);
17 : sh002i 455 use CGI qw();
18 : sh002i 469 use URI::Escape;
19 : sh002i 1225 use WeBWorK::Authz;
20 : sh002i 817 use WeBWorK::DB;
21 : sh002i 476 use WeBWorK::Utils qw(readFile);
22 : malsyned 390
23 : sh002i 469 ################################################################################
24 :     # This is a very unruly file, so I'm going to use very large comments to divide
25 :     # it into logical sections.
26 :     ################################################################################
27 : malsyned 390
28 : sh002i 817 # new(Apache::Request, WeBWorK::CourseEnvironment) - create a new instance of a
29 : sh002i 469 # content generator. Usually only called by the dispatcher, although one might
30 :     # be able to use it for things like "sub-requests". Uh... uh... I have to think
31 :     # about that one. The dispatcher uses this idiom:
32 :     #
33 :     # WeBWorK::ContentGenerator::WHATEVER->new($r, $ce)->go(@whatever);
34 :     #
35 :     # and throws away the result ;)
36 :     #
37 : sh002i 819 sub new($$$$) {
38 :     my ($invocant, $r, $ce, $db) = @_;
39 : malsyned 323 my $class = ref($invocant) || $invocant;
40 : sh002i 809 my $self = {
41 :     r => $r,
42 :     ce => $ce,
43 : sh002i 819 db => $db,
44 : malsyned 1017 authz => WeBWorK::Authz->new($r, $ce, $db)
45 : sh002i 809 };
46 : malsyned 305 bless $self, $class;
47 :     return $self;
48 :     }
49 :    
50 : sh002i 469 ################################################################################
51 :     # Invocation and template processing
52 :     ################################################################################
53 : malsyned 323
54 : sh002i 469 # go(@otherArguments) - render a page, using methods from the particular
55 :     # subclass of ContentGenerator. @otherArguments is passed to each method, so
56 :     # that the dispatcher can pass CG-specific data. The order of calls looks like
57 :     # this:
58 :     #
59 :     # * &pre_header_initialize - give subclasses a chance to do initialization
60 :     # necessary for generating the HTTP header.
61 :     # * &header - this class provides a standard HTTP header with Content-Type
62 :     # text/html. Subclasses are welcome to overload this for things like
63 :     # an image-creation content generator or a PDF generator.
64 :     # * &initialize - let subclasses do post-header initialization.
65 :     # * any "template escapes" defined in the system template and supported by
66 :     # the subclass. Generic implementations of &title and &body are provided.
67 :     #
68 :     sub go {
69 :     my $self = shift;
70 : sh002i 1225
71 : sh002i 469 my $r = $self->{r};
72 : sh002i 809 my $courseEnvironment = $self->{ce};
73 : sh002i 1131
74 : sh002i 469 $self->pre_header_initialize(@_) if $self->can("pre_header_initialize");
75 :     $self->header(@_);
76 :     return OK if $r->header_only;
77 : malsyned 313
78 : sh002i 469 $self->initialize(@_) if $self->can("initialize");
79 :     $self->template($courseEnvironment->{templates}->{system}, @_);
80 : sh002i 1225
81 : sh002i 469 return OK;
82 :     }
83 :    
84 :     # template(STRING, @otherArguments) - parse a template, looking for escapes of
85 :     # the form <!--#NAME ARG1="FOO" ARG2="BAR"--> and calling a member function NAME
86 :     # (if available) for each NAME. The escapes are called like:
87 :     #
88 :     # $self->NAME(@otherArguments, \%escapeArguments)
89 :     #
90 :     # where @otherArguments originates in the dispatcher and %escapeArguments is
91 :     # parsed out of the escape itself (i.e. ARG1 => FOO, ARG2 => BAR)
92 :     #
93 :     sub template {
94 :     my ($self, $templateFile) = (shift, shift);
95 :     my $r = $self->{r};
96 : sh002i 809 my $courseEnvironment = $self->{ce};
97 : malsyned 512 my @ifstack = (1); # Start off in printing mode
98 :     # say $ifstack[-1] to get the result of the last <#!--if-->
99 : sh002i 469
100 : sh002i 476 # so even though the variable $/ APPEARS to contain a newline,
101 :     # <TEMPLATE> is slurping the whole file into the first element of
102 :     # @template ONLY AFTER THE TRANSLATOR RUNS. WTF!!!
103 :     #
104 :     #open(TEMPLATE, $templateFile) or die "Couldn't open template $templateFile";
105 :     #my @template = <TEMPLATE>;
106 :     #close TEMPLATE;
107 :     #
108 :     # Let's try something else instead:
109 :     my @template = split /\n/, readFile($templateFile);
110 :    
111 : sh002i 469 foreach my $line (@template) {
112 :     # This is incremental regex processing.
113 :     # the /c is so that pos($line) doesn't die when the regex fails.
114 :     while ($line =~ m/\G(.*?)<!--#(\w*)((?:\s+.*?)?)-->/gc) {
115 :     my ($before, $function, $raw_args) = ($1, $2, $3);
116 : sh002i 555 my @args = ($raw_args =~ /\S/) ? cook_args($raw_args) : ();
117 :    
118 : malsyned 512 if ($ifstack[-1]) {
119 :     print $before;
120 :     }
121 : sh002i 555
122 : sh002i 558 if ($function eq "if") {
123 : malsyned 748 # a predicate can only be true if everything else on the ifstack is already true, for ANDing
124 :     push @ifstack, ($self->$function(@_, [@args]) && $ifstack[-1]);
125 : sh002i 558 } elsif ($function eq "else" and @ifstack > 1) {
126 :     $ifstack[-1] = not $ifstack[-1];
127 :     } elsif ($function eq "endif" and @ifstack > 1) {
128 :     pop @ifstack;
129 :     } elsif ($ifstack[-1]) {
130 : sh002i 562 if ($self->can($function)) {
131 : malsyned 1042 my @result = $self->$function(@_, {@args});
132 :     if (@result) {
133 :     print @result;
134 :     } else {
135 :     warn "Template escape $function returned an empty list.";
136 : malsyned 1017 }
137 : sh002i 562 }
138 : sh002i 476 }
139 : malsyned 313 }
140 : sh002i 469
141 : malsyned 512 if ($ifstack[-1]) {
142 : sh002i 562 print substr($line, (defined pos $line) ? pos $line : 0), "\n";
143 : malsyned 512 }
144 : malsyned 313 }
145 : sh002i 469 }
146 :    
147 :     # cook_args(STRING) - parses a string of the form ARG1="FOO" ARG2="BAR". Returns
148 : malsyned 512 # a list which pairs into key/values and fits nicely in {}s.
149 : sh002i 469 #
150 : sh002i 798 sub cook_args($) { # ... also used by bin/wwdb, so watch out
151 : sh002i 469 my ($raw_args) = @_;
152 : malsyned 512 my @args = ();
153 : malsyned 353
154 : malsyned 508 # Boy I love m//g in scalar context! Go read the camel book, heathen.
155 :     # First, get the whole token with the quotes on both ends...
156 :     while ($raw_args =~ m/\G\s*(\w*)="((?:[^"\\]|\\.)*)"/g) {
157 :     my ($key, $value) = ($1, $2);
158 :     # ... then, rip out all the protecty backspaces
159 : malsyned 522 $value =~ s/\\(.)/$1/g;
160 : malsyned 512 push @args, $key => $value;
161 : sh002i 469 }
162 :    
163 : malsyned 512 return @args;
164 : malsyned 313 }
165 :    
166 : sh002i 558 # This is different. It probably shouldn't print anything (except in debugging cases)
167 :     # and it should return a boolean, not a string. &if is called in a nonstandard way
168 :     # by &template, with $args as an arrayref instead of a hashref. this is a hack! yay!
169 :    
170 :     # OK, this is a pluggin architecture. it iterates through attributes of the "if" tag,
171 :     # and for each predicate $p, it calls &if_$p in an object-oriented way, continuing the
172 :     # grand templating theme of an object-oriented pluggable architecture using ->can($).
173 :     sub if {
174 :     my ($self, $args) = @_[0,-1];
175 :     # A single if "or"s it's components. Nesting produces "and".
176 :    
177 :     my @args = @$args; # Hahahahaha, get it?!
178 :    
179 :     if (@args % 2 != 0) {
180 :     # flip out and kill people, but do not commit seppuku
181 :     print '<!--&if recieved an uneven number of arguments. This shouldn\'t happen, but I\'ll let it slide.-->\n';
182 :     }
183 :    
184 :     while (@args > 1) {
185 :     my ($key, $value) = (shift @args, shift @args);
186 :    
187 :     # a non-existent &if_$key is the same as a false result, but we're ORing, so it's OK
188 :     my $sub = "if_$key"; # perl doesn't like it when you try to construct a string right in a method invocation
189 :     if ($self->can("if_$key") and $self->$sub("$value")) {
190 :     return 1;
191 :     }
192 :     }
193 :    
194 :     return 0;
195 :     }
196 :    
197 : sh002i 469 ################################################################################
198 :     # Macros used by content generators to render common idioms
199 :     ################################################################################
200 :    
201 :     # pathMacro(HASHREF, LIST) - helper macro for <!--#path--> escape: the hash
202 :     # reference contains the "style", "image", and "text" arguments to the escape.
203 :     # The LIST consists of ordered key-value pairs of the form:
204 :     #
205 :     # "Page Name" => URL
206 :     #
207 :     # If the page should not have a link associated with it, the URL should be left
208 :     # empty. Authentication data is added to the URL so you don't have to. A fully-
209 :     # formed path line is returned, suitable for returning by a function
210 :     # implementing the #path escape.
211 :     #
212 :     sub pathMacro {
213 : malsyned 323 my $self = shift;
214 : sh002i 469 my %args = %{ shift() };
215 :     my @path = @_;
216 :     my $sep;
217 :     if ($args{style} eq "image") {
218 :     $sep = CGI::img({-src=>$args{image}, -alt=>$args{text}});
219 :     } else {
220 :     $sep = $args{text};
221 :     }
222 :     my $auth = $self->url_authen_args;
223 :     my @result;
224 :     while (@path) {
225 :     my $name = shift @path;
226 :     my $url = shift @path;
227 :     push @result, $url
228 :     ? CGI::a({-href=>"$url?$auth"}, $name)
229 :     : $name;
230 :     }
231 : gage 1024 return join($sep, @result) . "\n";
232 : sh002i 469 }
233 :    
234 :     sub siblingsMacro {
235 :     my $self = shift;
236 :     my @siblings = @_;
237 :     my $sep = CGI::br();
238 :     my $auth = $self->url_authen_args;
239 :     my @result;
240 :     while (@siblings) {
241 :     my $name = shift @siblings;
242 :     my $url = shift @siblings;
243 :     push @result, $url
244 :     ? CGI::a({-href=>"$url?$auth"}, $name)
245 :     : $name;
246 :     }
247 :     return join($sep, @result), "\n";
248 :     }
249 :    
250 :     sub navMacro {
251 :     my $self = shift;
252 :     my %args = %{ shift() };
253 : sh002i 692 my $tail = shift;
254 : sh002i 469 my @links = @_;
255 :     my $auth = $self->url_authen_args;
256 : sh002i 809 my $ce = $self->{ce};
257 : malsyned 795 my $prefix = $ce->{webworkURLs}->{htdocs}."/images";
258 : sh002i 469 my @result;
259 :     while (@links) {
260 :     my $name = shift @links;
261 :     my $url = shift @links;
262 : malsyned 757 my $img = shift @links;
263 : malsyned 795 my $html =
264 :     ($img && $args{style} eq "images")
265 :     ? CGI::img(
266 :     {src=>($prefix."/".$img.$args{imagesuffix}),
267 :     border=>"",
268 :     alt=>"$name"})
269 :     : $name;
270 : malsyned 757 unless($img && !$url) {
271 :     push @result, $url
272 :     ? CGI::a({-href=>"$url?$auth$tail"}, $html)
273 :     : $html;
274 :     }
275 : sh002i 469 }
276 : gage 1024 return join($args{separator}, @result) . "\n";
277 : sh002i 469 }
278 :    
279 :     # hidden_fields(LIST) - return hidden <INPUT> tags for each field mentioned in
280 :     # LIST (or all fields if list is empty), taking data from the current request.
281 :     #
282 :     sub hidden_fields($;@) {
283 :     my $self = shift;
284 : malsyned 323 my $r = $self->{r};
285 : sh002i 469 my @fields = @_;
286 :     @fields or @fields = $r->param;
287 : sh002i 809 my $courseEnvironment = $self->{ce};
288 : malsyned 323 my $html = "";
289 :    
290 : sh002i 469 foreach my $param (@fields) {
291 : malsyned 323 my $value = $r->param($param);
292 : malsyned 447 $html .= CGI::input({-type=>"hidden",-name=>"$param",-value=>"$value"});
293 : malsyned 323 }
294 :     return $html;
295 :     }
296 :    
297 : sh002i 469 # hidden_authen_fields() - use hidden_fields to return hidden <INPUT> tags for
298 :     # request fields used in authentication.
299 :     #
300 :     sub hidden_authen_fields($) {
301 :     my $self = shift;
302 :     return $self->hidden_fields("user","effectiveUser","key");
303 :     }
304 : sh002i 425
305 : sh002i 469 # url_args(LIST) - return a URL query string (without the leading `?')
306 :     # containing values for each field mentioned in LIST, or all fields if list is
307 :     # empty. Data is taken from the current request.
308 :     #
309 :     sub url_args($;@) {
310 : sh002i 425 my $self = shift;
311 :     my $r = $self->{r};
312 :     my @fields = @_;
313 :     @fields or @fields = $r->param;
314 : sh002i 809 my $courseEnvironment = $self->{ce};
315 : sh002i 425
316 : sh002i 469 my @pairs;
317 : malsyned 441 foreach my $param (@fields) {
318 : sh002i 469 my $value = $r->param($param) || "";
319 :     push @pairs, uri_escape($param) . "=" . uri_escape($value);
320 : sh002i 425 }
321 : sh002i 469
322 :     return join("&", @pairs);
323 : sh002i 425 }
324 :    
325 : sh002i 469 # url_authen_args() - use url_args to return a URL query string for request
326 :     # fields used in authentication.
327 :     #
328 :     sub url_authen_args($) {
329 :     my $self = shift;
330 :     my $r = $self->{r};
331 :     return $self->url_args("user","effectiveUser","key");
332 : malsyned 390 }
333 :    
334 : sh002i 469 # print_form_data(BEGIN, MIDDLE, END, OMIT) - return a string containing request
335 :     # fields not matched by OMIT, placing BEGIN before each field name, MIDDLE
336 :     # between each field and its value, and END after each value. Values are taken
337 :     # from the current request. OMIT is a quoted reguar expression.
338 :     #
339 :     sub print_form_data {
340 :     my ($self, $begin, $middle, $end, $qr_omit) = @_;
341 :     my $return_string = "";
342 :     my $r=$self->{r};
343 :     my @form_data = $r->param;
344 :     foreach my $name (@form_data) {
345 :     next if ($qr_omit and $name =~ /$qr_omit/);
346 :     my @values = $r->param($name);
347 :     foreach my $variable (qw(begin name middle value end)) {
348 :     no strict 'refs';
349 :     ${$variable} = "" unless defined ${$variable};
350 :     }
351 :     foreach my $value (@values) {
352 :     $return_string .= "$begin$name$middle$value$end";
353 :     }
354 :     }
355 :     return $return_string;
356 : malsyned 390 }
357 :    
358 : sh002i 737 sub errorOutput($$$) {
359 :     my ($self, $error, $details) = @_;
360 :     return
361 : gage 1152 CGI::h3("Software Error"),
362 : sh002i 737 CGI::p(<<EOF),
363 : sh002i 1131 WeBWorK has encountered a software error while attempting to process this
364 :     problem. It is likely that there is an error in the problem itself. If you are
365 :     a student, contact your professor to have the error corrected. If you are a
366 :     professor, please consut the error output below for more informaiton.
367 : sh002i 737 EOF
368 : sh002i 1142 CGI::h3("Error messages"), CGI::p(CGI::tt($error)),
369 :     CGI::h3("Error context"), CGI::p(CGI::tt($details));
370 : sh002i 737 }
371 :    
372 :     sub warningOutput($$) {
373 :     my ($self, $warnings) = @_;
374 :    
375 : sh002i 1169 my @warnings = split m/\n+/, $warnings;
376 :    
377 : sh002i 737 return
378 : gage 1152 CGI::h3("Software Warnings"),
379 : sh002i 737 CGI::p(<<EOF),
380 : sh002i 1131 WeBWorK has encountered warnings while attempting to process this problem. It
381 :     is likely that this indicates an error or ambiguity in the problem itself. If
382 :     you are a student, contact your professor to have the problem corrected. If you
383 : sh002i 1142 are a professor, please consut the warning output below for more informaiton.
384 : sh002i 737 EOF
385 :     CGI::h3("Warning messages"),
386 : sh002i 1169 CGI::ul(CGI::li(\@warnings)),
387 : sh002i 737 ;
388 :     }
389 :    
390 : sh002i 469 ################################################################################
391 :     # Generic versions of template escapes
392 :     ################################################################################
393 : malsyned 390
394 : sh002i 469 # Reminder: here are the template functions currently defined:
395 : sh002i 1131 # FIXME: this list is out of date!!!!!!!!
396 : sh002i 469 #
397 : sh002i 555 # head
398 : sh002i 469 # path
399 :     # style = text|image
400 :     # image = URL of image
401 :     # text = text separator
402 : sh002i 675 # loginstatus
403 : sh002i 505 # links
404 : sh002i 469 # siblings
405 :     # nav
406 :     # style = text|image
407 :     # imageprefix = prefix to image URL
408 :     # imagesuffix = suffix to image URL
409 :     # separator = HTML to place in between links
410 :     # title
411 :     # body
412 : malsyned 313
413 : malsyned 349 sub header {
414 : malsyned 305 my $self = shift;
415 : sh002i 469 my $r = $self->{r};
416 : malsyned 349 $r->content_type('text/html');
417 :     $r->send_http_header();
418 :     }
419 :    
420 : sh002i 682 sub loginstatus {
421 :     my $self = shift;
422 :     my $r = $self->{r};
423 :     my $user = $r->param("user");
424 :     my $eUser = $r->param("effectiveUser");
425 :     my $key = $r->param("key");
426 :     return "" unless $key;
427 : sh002i 692 my $exitURL = $r->uri() . "?user=$user&key=$key";
428 : malsyned 765 print CGI::small("User:", "$user");
429 : sh002i 682 if ($user ne $eUser) {
430 : sh002i 692 print CGI::br(), CGI::font({-color=>'red'},
431 : malsyned 765 CGI::small("Acting as:", "$eUser")
432 : sh002i 692 ),
433 :     CGI::br(), CGI::a({-href=>$exitURL},
434 :     CGI::small("Stop Acting")
435 :     );
436 : sh002i 682 }
437 :     return "";
438 :     }
439 :    
440 : sh002i 1131 # FIXME: drunk code. rewrite.
441 : sh002i 667 # also, this should be structured s.t. subclasses can add items to the links
442 :     # area, i.e. "stacking"
443 : sh002i 505 sub links {
444 : malsyned 353 my $self = shift;
445 : sh002i 809 my $ce = $self->{ce};
446 : sh002i 817 my $db = $self->{db};
447 : sh002i 526 my $userName = $self->{r}->param("user");
448 :     my $courseName = $ce->{courseName};
449 : sh002i 469 my $root = $ce->{webworkURLs}->{root};
450 : sh002i 817 my $permLevel = $db->getPermissionLevel($userName)->permission();
451 :     my $key = $db->getKey($userName)->key();
452 : sh002i 646 return "" unless defined $key;
453 : sh002i 526
454 : sh002i 667 # URLs to parts of the system
455 : malsyned 836 my $probSets = "$root/$courseName/?" . $self->url_authen_args();
456 :     my $prefs = "$root/$courseName/options/?" . $self->url_authen_args();
457 :     my $instructor = "$root/$courseName/instructor/?" . $self->url_authen_args();
458 : gage 1358 my $sets = "$root/$courseName/instructor/sets/?" . $self->url_authen_args();
459 :     my $users = "$root/$courseName/instructor/users?" . $self->url_authen_args();
460 : malsyned 836 my $help = "$ce->{webworkURLs}->{docs}?" . $self->url_authen_args();
461 :     my $logout = "$root/$courseName/logout/?" . $self->url_authen_args();
462 : sh002i 526
463 : gage 1358 return join("",
464 :     CGI::a({-href=>$probSets}, "Problem&nbsp;Sets"), CGI::br(),
465 : sh002i 667 ($permLevel > 0
466 : gage 1358 ? join("",
467 :     CGI::a({-href=>$instructor}, "Instructor") , CGI::br(),
468 :     CGI::a({-href=>$sets}, "&nbsp;&nbsp;SetList") , CGI::br(),
469 :     CGI::a({-href=>$users}, "&nbsp;&nbsp;ClassList") , CGI::br(),)
470 :     : ""
471 :     ),
472 :     CGI::a({-href=>$prefs}, "User&nbsp;Prefs"), CGI::br(),
473 :     CGI::a({-href=>$help}, "Help"), CGI::br(),
474 :     CGI::a({-href=>$logout}, "Log Out"), CGI::br(),
475 :     );
476 : malsyned 353 }
477 :    
478 : malsyned 525 # &if_can will return 1 if the current object->can("do $_[1]")
479 :     sub if_can ($$) {
480 :     my ($self, $arg) = (@_);
481 :    
482 :     if ($self->can("$arg")) {
483 :     return 1;
484 :     } else {
485 :     return 0;
486 :     }
487 :     }
488 :    
489 : malsyned 748 # Every content generator is logged in unless it says otherwise.
490 :     sub if_loggedin($$) {
491 :     my ($self, $arg) = (@_);
492 :    
493 :     return $arg;
494 :     }
495 :    
496 : sh002i 1131 # Handling of errors in submissions
497 :    
498 : malsyned 1017 sub if_submiterror($$) {
499 :     my ($self, $arg) = @_;
500 :     if (exists $self->{submitError}) {
501 :     return $arg;
502 :     } else {
503 :     return !$arg;
504 :     }
505 :     }
506 :    
507 : sh002i 1131 sub submiterror {
508 :     my ($self) = @_;
509 :     if (exists $self->{submitError}) {
510 :     return $self->{submitError};
511 :     } else {
512 :     return "";
513 :     }
514 :     }
515 :    
516 :     # General warning handling
517 :    
518 :     sub if_warnings($$) {
519 :     my ($self, $arg) = @_;
520 :     return $self->{r}->notes("warnings") ? $arg : !$arg;
521 :     }
522 :    
523 :     sub warnings {
524 :     my ($self) = @_;
525 :     my $r = $self->{r};
526 :     if ($r->notes("warnings")) {
527 :     return $self->warningOutput($r->notes("warnings"));
528 :     } else {
529 :     return "";
530 :     }
531 :     }
532 :    
533 : malsyned 313 1;
534 : malsyned 508
535 :     __END__
536 :    
537 :     =head1 AUTHOR
538 :    
539 :     Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu
540 :     and Sam Hathaway, sh002i (at) math.rochester.edu.
541 :    
542 :     =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9