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

Annotation of /branches/rel-2-1-patches/webwork2/lib/WeBWorK/ContentGenerator.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 633 - (view) (download) (as text)
Original Path: trunk/webwork2/lib/WeBWorK/ContentGenerator.pm

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 526 use WeBWorK::DB::Auth;
20 : sh002i 476 use WeBWorK::Utils qw(readFile);
21 : sh002i 555 use Carp qw(cluck);
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 469 # new(Apache::Request, WeBWorK::CourseEnvironment) - create a new instance of a
29 :     # 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 :     #
34 :     # WeBWorK::ContentGenerator::WHATEVER->new($r, $ce)->go(@whatever);
35 :     #
36 :     # and throws away the result ;)
37 :     #
38 : malsyned 305 sub new($$$) {
39 : malsyned 323 my $invocant = shift;
40 :     my $class = ref($invocant) || $invocant;
41 : malsyned 305 my $self = {};
42 :     ($self->{r}, $self->{courseEnvironment}) = @_;
43 :     bless $self, $class;
44 :     return $self;
45 :     }
46 :    
47 : sh002i 469 ################################################################################
48 :     # Invocation and template processing
49 :     ################################################################################
50 : malsyned 323
51 : sh002i 469 # go(@otherArguments) - render a page, using methods from the particular
52 :     # subclass of ContentGenerator. @otherArguments is passed to each method, so
53 :     # that the dispatcher can pass CG-specific data. The order of calls looks like
54 :     # this:
55 :     #
56 :     # * &pre_header_initialize - give subclasses a chance to do initialization
57 :     # necessary for generating the HTTP header.
58 :     # * &header - this class provides a standard HTTP header with Content-Type
59 :     # text/html. Subclasses are welcome to overload this for things like
60 :     # an image-creation content generator or a PDF generator.
61 :     # * &initialize - let subclasses do post-header initialization.
62 :     # * any "template escapes" defined in the system template and supported by
63 :     # the subclass. Generic implementations of &title and &body are provided.
64 :     #
65 :     sub go {
66 :     my $self = shift;
67 :     my $r = $self->{r};
68 :     my $courseEnvironment = $self->{courseEnvironment};
69 :    
70 :     $self->pre_header_initialize(@_) if $self->can("pre_header_initialize");
71 :     $self->header(@_);
72 :     return OK if $r->header_only;
73 : malsyned 313
74 : sh002i 469 $self->initialize(@_) if $self->can("initialize");
75 :     $self->template($courseEnvironment->{templates}->{system}, @_);
76 : malsyned 441
77 : sh002i 469 return OK;
78 :     }
79 :    
80 :     # template(STRING, @otherArguments) - parse a template, looking for escapes of
81 :     # the form <!--#NAME ARG1="FOO" ARG2="BAR"--> and calling a member function NAME
82 :     # (if available) for each NAME. The escapes are called like:
83 :     #
84 :     # $self->NAME(@otherArguments, \%escapeArguments)
85 :     #
86 :     # where @otherArguments originates in the dispatcher and %escapeArguments is
87 :     # parsed out of the escape itself (i.e. ARG1 => FOO, ARG2 => BAR)
88 :     #
89 :     sub template {
90 :     my ($self, $templateFile) = (shift, shift);
91 :     my $r = $self->{r};
92 :     my $courseEnvironment = $self->{courseEnvironment};
93 : malsyned 512 my @ifstack = (1); # Start off in printing mode
94 :     # say $ifstack[-1] to get the result of the last <#!--if-->
95 : sh002i 469
96 : sh002i 476 # so even though the variable $/ APPEARS to contain a newline,
97 :     # <TEMPLATE> is slurping the whole file into the first element of
98 :     # @template ONLY AFTER THE TRANSLATOR RUNS. WTF!!!
99 :     #
100 :     #open(TEMPLATE, $templateFile) or die "Couldn't open template $templateFile";
101 :     #my @template = <TEMPLATE>;
102 :     #close TEMPLATE;
103 :     #
104 :     # Let's try something else instead:
105 :     my @template = split /\n/, readFile($templateFile);
106 :    
107 : sh002i 469 foreach my $line (@template) {
108 :     # This is incremental regex processing.
109 :     # the /c is so that pos($line) doesn't die when the regex fails.
110 :     while ($line =~ m/\G(.*?)<!--#(\w*)((?:\s+.*?)?)-->/gc) {
111 :     my ($before, $function, $raw_args) = ($1, $2, $3);
112 : sh002i 555 my @args = ($raw_args =~ /\S/) ? cook_args($raw_args) : ();
113 :    
114 : malsyned 512 if ($ifstack[-1]) {
115 :     print $before;
116 :     }
117 : sh002i 555
118 : malsyned 633 warn '$function undefined' if !defined $function;
119 :     warn '@ifstack undefined' if !defined @ifstack;
120 :     warn '@args undefined' if !defined @args;
121 :    
122 : sh002i 558 if ($function eq "if") {
123 :     push @ifstack, $self->$function(@_, [@args]);
124 :     } elsif ($function eq "else" and @ifstack > 1) {
125 :     $ifstack[-1] = not $ifstack[-1];
126 :     } elsif ($function eq "endif" and @ifstack > 1) {
127 :     pop @ifstack;
128 :     } elsif ($ifstack[-1]) {
129 : sh002i 562 if ($self->can($function)) {
130 :     print $self->$function(@_, {@args});
131 :     }
132 : sh002i 476 }
133 : malsyned 313 }
134 : sh002i 469
135 : malsyned 512 if ($ifstack[-1]) {
136 : sh002i 562 print substr($line, (defined pos $line) ? pos $line : 0), "\n";
137 : malsyned 512 }
138 : malsyned 313 }
139 : sh002i 469 }
140 :    
141 :     # cook_args(STRING) - parses a string of the form ARG1="FOO" ARG2="BAR". Returns
142 : malsyned 512 # a list which pairs into key/values and fits nicely in {}s.
143 : sh002i 469 #
144 :     sub cook_args($) {
145 :     my ($raw_args) = @_;
146 : malsyned 512 my @args = ();
147 : malsyned 353
148 : malsyned 508 # Boy I love m//g in scalar context! Go read the camel book, heathen.
149 :     # First, get the whole token with the quotes on both ends...
150 :     while ($raw_args =~ m/\G\s*(\w*)="((?:[^"\\]|\\.)*)"/g) {
151 :     my ($key, $value) = ($1, $2);
152 :     # ... then, rip out all the protecty backspaces
153 : malsyned 522 $value =~ s/\\(.)/$1/g;
154 : malsyned 512 push @args, $key => $value;
155 : sh002i 469 }
156 :    
157 : malsyned 512 return @args;
158 : malsyned 313 }
159 :    
160 : sh002i 558 # This is different. It probably shouldn't print anything (except in debugging cases)
161 :     # and it should return a boolean, not a string. &if is called in a nonstandard way
162 :     # by &template, with $args as an arrayref instead of a hashref. this is a hack! yay!
163 :    
164 :     # OK, this is a pluggin architecture. it iterates through attributes of the "if" tag,
165 :     # and for each predicate $p, it calls &if_$p in an object-oriented way, continuing the
166 :     # grand templating theme of an object-oriented pluggable architecture using ->can($).
167 :     sub if {
168 :     my ($self, $args) = @_[0,-1];
169 :     # A single if "or"s it's components. Nesting produces "and".
170 :    
171 :     my @args = @$args; # Hahahahaha, get it?!
172 :    
173 :     if (@args % 2 != 0) {
174 :     # flip out and kill people, but do not commit seppuku
175 :     print '<!--&if recieved an uneven number of arguments. This shouldn\'t happen, but I\'ll let it slide.-->\n';
176 :     }
177 :    
178 :     while (@args > 1) {
179 :     my ($key, $value) = (shift @args, shift @args);
180 :    
181 :     # a non-existent &if_$key is the same as a false result, but we're ORing, so it's OK
182 :     my $sub = "if_$key"; # perl doesn't like it when you try to construct a string right in a method invocation
183 :     if ($self->can("if_$key") and $self->$sub("$value")) {
184 :     return 1;
185 :     }
186 :     }
187 :    
188 :     return 0;
189 :     }
190 :    
191 : sh002i 469 ################################################################################
192 :     # Macros used by content generators to render common idioms
193 :     ################################################################################
194 :    
195 :     # pathMacro(HASHREF, LIST) - helper macro for <!--#path--> escape: the hash
196 :     # reference contains the "style", "image", and "text" arguments to the escape.
197 :     # The LIST consists of ordered key-value pairs of the form:
198 :     #
199 :     # "Page Name" => URL
200 :     #
201 :     # If the page should not have a link associated with it, the URL should be left
202 :     # empty. Authentication data is added to the URL so you don't have to. A fully-
203 :     # formed path line is returned, suitable for returning by a function
204 :     # implementing the #path escape.
205 :     #
206 :     sub pathMacro {
207 : malsyned 323 my $self = shift;
208 : sh002i 469 my %args = %{ shift() };
209 :     my @path = @_;
210 :     my $sep;
211 :     if ($args{style} eq "image") {
212 :     $sep = CGI::img({-src=>$args{image}, -alt=>$args{text}});
213 :     } else {
214 :     $sep = $args{text};
215 :     }
216 :     my $auth = $self->url_authen_args;
217 :     my @result;
218 :     while (@path) {
219 :     my $name = shift @path;
220 :     my $url = shift @path;
221 :     push @result, $url
222 :     ? CGI::a({-href=>"$url?$auth"}, $name)
223 :     : $name;
224 :     }
225 :     return join($sep, @result), "\n";
226 :     }
227 :    
228 :     sub siblingsMacro {
229 :     my $self = shift;
230 :     my @siblings = @_;
231 :     my $sep = CGI::br();
232 :     my $auth = $self->url_authen_args;
233 :     my @result;
234 :     while (@siblings) {
235 :     my $name = shift @siblings;
236 :     my $url = shift @siblings;
237 :     push @result, $url
238 :     ? CGI::a({-href=>"$url?$auth"}, $name)
239 :     : $name;
240 :     }
241 :     return join($sep, @result), "\n";
242 :     }
243 :    
244 :     sub navMacro {
245 :     my $self = shift;
246 :     my %args = %{ shift() };
247 :     my @links = @_;
248 :     my $auth = $self->url_authen_args;
249 :     my @result;
250 :     while (@links) {
251 :     my $name = shift @links;
252 :     my $url = shift @links;
253 :     push @result, $url
254 :     ? CGI::a({-href=>"$url?$auth"}, $name)
255 :     : $name;
256 :     }
257 :     return join($args{separator}, @result), "\n";
258 :     }
259 :    
260 :     # hidden_fields(LIST) - return hidden <INPUT> tags for each field mentioned in
261 :     # LIST (or all fields if list is empty), taking data from the current request.
262 :     #
263 :     sub hidden_fields($;@) {
264 :     my $self = shift;
265 : malsyned 323 my $r = $self->{r};
266 : sh002i 469 my @fields = @_;
267 :     @fields or @fields = $r->param;
268 : malsyned 323 my $courseEnvironment = $self->{courseEnvironment};
269 :     my $html = "";
270 :    
271 : sh002i 469 foreach my $param (@fields) {
272 : malsyned 323 my $value = $r->param($param);
273 : malsyned 447 $html .= CGI::input({-type=>"hidden",-name=>"$param",-value=>"$value"});
274 : malsyned 323 }
275 :     return $html;
276 :     }
277 :    
278 : sh002i 469 # hidden_authen_fields() - use hidden_fields to return hidden <INPUT> tags for
279 :     # request fields used in authentication.
280 :     #
281 :     sub hidden_authen_fields($) {
282 :     my $self = shift;
283 :     return $self->hidden_fields("user","effectiveUser","key");
284 :     }
285 : sh002i 425
286 : sh002i 469 # url_args(LIST) - return a URL query string (without the leading `?')
287 :     # containing values for each field mentioned in LIST, or all fields if list is
288 :     # empty. Data is taken from the current request.
289 :     #
290 :     sub url_args($;@) {
291 : sh002i 425 my $self = shift;
292 :     my $r = $self->{r};
293 :     my @fields = @_;
294 :     @fields or @fields = $r->param;
295 :     my $courseEnvironment = $self->{courseEnvironment};
296 :    
297 : sh002i 469 my @pairs;
298 : malsyned 441 foreach my $param (@fields) {
299 : sh002i 469 my $value = $r->param($param) || "";
300 :     push @pairs, uri_escape($param) . "=" . uri_escape($value);
301 : sh002i 425 }
302 : sh002i 469
303 :     return join("&", @pairs);
304 : sh002i 425 }
305 :    
306 : sh002i 469 # url_authen_args() - use url_args to return a URL query string for request
307 :     # fields used in authentication.
308 :     #
309 :     sub url_authen_args($) {
310 :     my $self = shift;
311 :     my $r = $self->{r};
312 :     return $self->url_args("user","effectiveUser","key");
313 : malsyned 390 }
314 :    
315 : sh002i 469 # print_form_data(BEGIN, MIDDLE, END, OMIT) - return a string containing request
316 :     # fields not matched by OMIT, placing BEGIN before each field name, MIDDLE
317 :     # between each field and its value, and END after each value. Values are taken
318 :     # from the current request. OMIT is a quoted reguar expression.
319 :     #
320 :     sub print_form_data {
321 :     my ($self, $begin, $middle, $end, $qr_omit) = @_;
322 :     my $return_string = "";
323 :     my $r=$self->{r};
324 :     my @form_data = $r->param;
325 :     foreach my $name (@form_data) {
326 :     next if ($qr_omit and $name =~ /$qr_omit/);
327 :     my @values = $r->param($name);
328 :     foreach my $variable (qw(begin name middle value end)) {
329 :     no strict 'refs';
330 :     ${$variable} = "" unless defined ${$variable};
331 :     }
332 :     foreach my $value (@values) {
333 :     $return_string .= "$begin$name$middle$value$end";
334 :     }
335 :     }
336 :     return $return_string;
337 : malsyned 390 }
338 :    
339 : sh002i 469 ################################################################################
340 :     # Generic versions of template escapes
341 :     ################################################################################
342 : malsyned 390
343 : sh002i 469 # Reminder: here are the template functions currently defined:
344 :     #
345 : sh002i 555 # head
346 : sh002i 469 # path
347 :     # style = text|image
348 :     # image = URL of image
349 :     # text = text separator
350 : sh002i 505 # links
351 : sh002i 469 # siblings
352 :     # nav
353 :     # style = text|image
354 :     # imageprefix = prefix to image URL
355 :     # imagesuffix = suffix to image URL
356 :     # separator = HTML to place in between links
357 :     # title
358 :     # body
359 : malsyned 313
360 : malsyned 349 sub header {
361 : malsyned 305 my $self = shift;
362 : sh002i 469 my $r = $self->{r};
363 : malsyned 349 $r->content_type('text/html');
364 :     $r->send_http_header();
365 :     }
366 :    
367 : sh002i 526 # drunk code. rewrite.
368 : sh002i 505 sub links {
369 : malsyned 353 my $self = shift;
370 : sh002i 469 my $ce = $self->{courseEnvironment};
371 : sh002i 526 my $userName = $self->{r}->param("user");
372 :     my $courseName = $ce->{courseName};
373 : sh002i 469 my $root = $ce->{webworkURLs}->{root};
374 : sh002i 526 my $permLevel = WeBWorK::DB::Auth->new($ce)->getPermissions($userName);
375 : sh002i 562 return "" unless defined $permLevel;
376 : sh002i 526
377 : sh002i 469 my $probSets = "$root/$courseName/?" . $self->url_authen_args();
378 : sh002i 526 my $prefs = "$root/$courseName/prefs/?" . $self->url_authen_args();
379 :     my $prof = "$root/$courseName/prof/?" . $self->url_authen_args();
380 :     my $profLine;
381 :     if ($permLevel > 0) {
382 :     $profLine = CGI::a({-href=>$prof}, "Professor") . CGI::br(),
383 :     }
384 :     my $help = $ce->{webworkURLs}->{docs} . "?" . $self->url_authen_args();
385 :     my $logout = "$root/$courseName/?user=$userName";
386 :    
387 : sh002i 476 return
388 : sh002i 469 CGI::a({-href=>$probSets}, "Problem Sets"), CGI::br(),
389 : sh002i 526 CGI::a({-href=>$prefs}, "User Options"), CGI::br(),
390 :     $profLine,
391 :     CGI::a({-href=>$help}, "Help"), CGI::br(),
392 : sh002i 476 CGI::a({-href=>$logout}, "Log Out"), CGI::br(),
393 :     ;
394 : malsyned 353 }
395 :    
396 : malsyned 525 # &if_can will return 1 if the current object->can("do $_[1]")
397 :     sub if_can ($$) {
398 :     my ($self, $arg) = (@_);
399 :    
400 :     if ($self->can("$arg")) {
401 :     return 1;
402 :     } else {
403 :     return 0;
404 :     }
405 :     }
406 :    
407 : malsyned 313 1;
408 : malsyned 508
409 :     __END__
410 :    
411 :     =head1 AUTHOR
412 :    
413 :     Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu
414 :     and Sam Hathaway, sh002i (at) math.rochester.edu.
415 :    
416 :     =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9