|
|
1 | ################################################################################ |
|
|
2 | # WeBWorK Online Homework Delivery System |
|
|
3 | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ |
|
|
4 | # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator.pm,v 1.75 2004/01/17 16:29:52 gage Exp $ |
|
|
5 | # |
|
|
6 | # This program is free software; you can redistribute it and/or modify it under |
|
|
7 | # the terms of either: (a) the GNU General Public License as published by the |
|
|
8 | # Free Software Foundation; either version 2, or (at your option) any later |
|
|
9 | # version, or (b) the "Artistic License" which comes with this package. |
|
|
10 | # |
|
|
11 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|
|
12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
|
13 | # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the |
|
|
14 | # Artistic License for more details. |
|
|
15 | ################################################################################ |
|
|
16 | |
| 1 | package WeBWorK::ContentGenerator; |
17 | package WeBWorK::ContentGenerator; |
| 2 | |
18 | |
| 3 | use CGI qw(-compile :html :form); |
19 | =head1 NAME |
|
|
20 | |
|
|
21 | WeBWorK::ContentGenerator - base class for modules that generate page content. |
|
|
22 | |
|
|
23 | =cut |
|
|
24 | |
|
|
25 | use strict; |
|
|
26 | use warnings; |
| 4 | use Apache::Constants qw(:common); |
27 | use Apache::Constants qw(:common); |
|
|
28 | use CGI qw(); |
|
|
29 | use URI::Escape; |
|
|
30 | use WeBWorK::Authz; |
|
|
31 | use WeBWorK::DB; |
|
|
32 | use WeBWorK::Utils qw(readFile); |
| 5 | |
33 | |
| 6 | # This is a superclass for Apache::WeBWorK's content generators. |
34 | ################################################################################ |
| 7 | # You are /definitely/ encouraged to read this file, since there are |
35 | # This is a very unruly file, so I'm going to use very large comments to divide |
| 8 | # "abstract" functions here which show aproximately what form you would |
36 | # it into logical sections. |
| 9 | # want over-ridden sub-classes to follow. go() is a particularly pertinent |
37 | ################################################################################ |
| 10 | # example. |
|
|
| 11 | |
38 | |
| 12 | # new(Apache::Request, WeBWorK::CourseEnvironment) |
39 | # new(Apache::Request, WeBWorK::CourseEnvironment, WeBWorK::DB) - create a new |
|
|
40 | # instance of a content generator. Usually only called by the dispatcher, although |
|
|
41 | # one might be able to use it for things like "sub-requests". Uh... uh... I have |
|
|
42 | # to think about that one. The dispatcher uses this idiom: |
|
|
43 | # |
|
|
44 | # WeBWorK::ContentGenerator::WHATEVER->new($r, $ce, $db)->go(@whatever); |
|
|
45 | # |
|
|
46 | # and throws away the result ;) |
|
|
47 | # |
| 13 | sub new($$$) { |
48 | sub new { |
| 14 | my $invocant = shift; |
49 | my ($invocant, $r, $ce, $db) = @_; |
| 15 | my $class = ref($invocant) || $invocant; |
50 | my $class = ref($invocant) || $invocant; |
| 16 | my $self = {}; |
51 | my $self = { |
| 17 | ($self->{r}, $self->{courseEnvironment}) = @_; |
52 | r => $r, |
|
|
53 | ce => $ce, |
|
|
54 | db => $db, |
|
|
55 | authz => WeBWorK::Authz->new($r, $ce, $db), |
|
|
56 | noContent => undef, |
|
|
57 | }; |
| 18 | bless $self, $class; |
58 | bless $self, $class; |
| 19 | return $self; |
59 | return $self; |
| 20 | } |
60 | } |
| 21 | |
61 | |
|
|
62 | ################################################################################ |
|
|
63 | # Invocation and template processing |
|
|
64 | ################################################################################ |
| 22 | |
65 | |
| 23 | # This is a quick and dirty function to print out all (or almost all) of the |
66 | # go(@otherArguments) - render a page, using methods from the particular |
| 24 | # fields in a form in a specified format. As you can see from the print |
67 | # subclass of ContentGenerator. @otherArguments is passed to each method, so |
| 25 | # statement, it just prints out $begining$name$middle$value$end for every |
68 | # that the dispatcher can pass CG-specific data. The order of calls looks like |
| 26 | # field who's name doesn't match $qr_omit, a quoted regex. |
69 | # this: |
| 27 | # In it's current incarnation, it should be called from subclasses only, |
70 | # |
| 28 | # by saying $self->print_form_data. Of course, you could construct a |
71 | # * &pre_header_initialize - give subclasses a chance to do initialization |
| 29 | # hashref with ->{r} being an Apache::Request, I suppose. |
72 | # necessary for generating the HTTP header. |
|
|
73 | # * &header - this class provides a standard HTTP header with Content-Type |
|
|
74 | # text/html. Subclasses are welcome to overload this for things like |
|
|
75 | # an image-creation content generator or a PDF generator. |
|
|
76 | # In addition, if &header returns a value, that will be the value |
|
|
77 | # returned by the entire PerlHandler. |
|
|
78 | # * &initialize - let subclasses do post-header initialization. |
|
|
79 | # * any "template escapes" defined in the system template and supported by |
|
|
80 | # the subclass. |
|
|
81 | # (if &content exists on a content generator, it is called |
|
|
82 | # and no template processing occurs.) |
|
|
83 | # |
|
|
84 | # If &pre_header_initialize or &header sets $self->{noContent} to a true value, |
|
|
85 | # &initialize will not be run and the content or template processing code |
|
|
86 | # will not be executed. This is probably only desirable if a redirect has been |
|
|
87 | # issued. |
|
|
88 | sub go { |
|
|
89 | my $self = shift; |
|
|
90 | |
|
|
91 | my $r = $self->{r}; |
|
|
92 | my $ce = $self->{ce}; |
|
|
93 | my $returnValue = OK; |
|
|
94 | |
|
|
95 | $self->pre_header_initialize(@_) if $self->can("pre_header_initialize"); |
|
|
96 | my $headerReturn = $self->header(@_); |
|
|
97 | $returnValue = $headerReturn if defined $headerReturn; |
|
|
98 | return $returnValue if $r->header_only or $self->{noContent}; |
|
|
99 | |
|
|
100 | # if the sendFile flag is set, send the file and exit; |
|
|
101 | if ($self->{sendFile}) { |
|
|
102 | return $self->sendFile; |
|
|
103 | } |
|
|
104 | |
|
|
105 | $self->initialize(@_) if $self->can("initialize"); |
|
|
106 | |
|
|
107 | # A content generator will have a "content" method if it does not |
|
|
108 | # wish to be passed through template processing, but wishes to be |
|
|
109 | # completely responsible for it's own output. |
|
|
110 | if ($self->can("content")) { |
|
|
111 | $self->content(@_); |
|
|
112 | } else { |
|
|
113 | # if the content generator specifies a custom template name, use that |
|
|
114 | # field in the $ce->{templates} hash instead of "system" if it exists. |
|
|
115 | my $templateName; |
|
|
116 | if ($self->can("templateName")) { |
|
|
117 | $templateName = $self->templateName; |
|
|
118 | } else { |
|
|
119 | $templateName = "system"; |
|
|
120 | } |
|
|
121 | $templateName = "system" unless exists $ce->{templates}->{$templateName}; |
|
|
122 | $self->template($ce->{templates}->{$templateName}, @_); |
|
|
123 | } |
|
|
124 | |
|
|
125 | return $returnValue; |
|
|
126 | } |
| 30 | |
127 | |
|
|
128 | sub sendFile { |
|
|
129 | my ($self) = @_; |
|
|
130 | |
|
|
131 | my $file = $self->{sendFile}->{source}; |
|
|
132 | |
|
|
133 | return NOT_FOUND unless -e $file; |
|
|
134 | return FORBIDDEN unless -r $file; |
|
|
135 | |
|
|
136 | open my $fh, "<", $file |
|
|
137 | or return SERVER_ERROR; |
|
|
138 | while (<$fh>) { |
|
|
139 | print $_; |
|
|
140 | } |
|
|
141 | close $fh; |
|
|
142 | |
|
|
143 | return OK; |
|
|
144 | } |
|
|
145 | |
|
|
146 | # template(STRING, @otherArguments) - parse a template, looking for escapes of |
|
|
147 | # the form <!--#NAME ARG1="FOO" ARG2="BAR"--> and calling a member function NAME |
|
|
148 | # (if available) for each NAME. The escapes are called like: |
|
|
149 | # |
|
|
150 | # $self->NAME(@otherArguments, \%escapeArguments) |
|
|
151 | # |
|
|
152 | # where @otherArguments originates in the dispatcher and %escapeArguments is |
|
|
153 | # parsed out of the escape itself (i.e. ARG1 => FOO, ARG2 => BAR) |
|
|
154 | # |
|
|
155 | sub template { |
|
|
156 | my ($self, $templateFile) = (shift, shift); |
|
|
157 | my $r = $self->{r}; |
|
|
158 | my $courseEnvironment = $self->{ce}; |
|
|
159 | my @ifstack = (1); # Start off in printing mode |
|
|
160 | # say $ifstack[-1] to get the result of the last <#!--if--> |
|
|
161 | |
|
|
162 | # so even though the variable $/ APPEARS to contain a newline, |
|
|
163 | # <TEMPLATE> is slurping the whole file into the first element of |
|
|
164 | # @template ONLY AFTER THE TRANSLATOR RUNS. WTF!!! |
|
|
165 | # |
|
|
166 | #open(TEMPLATE, $templateFile) or die "Couldn't open template $templateFile"; |
|
|
167 | #my @template = <TEMPLATE>; |
|
|
168 | #close TEMPLATE; |
|
|
169 | # |
|
|
170 | # Let's try something else instead: |
|
|
171 | my @template = split /\n/, readFile($templateFile); |
|
|
172 | |
|
|
173 | foreach my $line (@template) { |
|
|
174 | # This is incremental regex processing. |
|
|
175 | # the /c is so that pos($line) doesn't die when the regex fails. |
|
|
176 | while ($line =~ m/\G(.*?)<!--#(\w*)((?:\s+.*?)?)-->/gc) { |
|
|
177 | my ($before, $function, $raw_args) = ($1, $2, $3); |
|
|
178 | my @args = ($raw_args =~ /\S/) ? cook_args($raw_args) : (); |
|
|
179 | |
|
|
180 | if ($ifstack[-1]) { |
|
|
181 | print $before; |
|
|
182 | } |
|
|
183 | |
|
|
184 | if ($function eq "if") { |
|
|
185 | # a predicate can only be true if everything else on the ifstack is already true, for ANDing |
|
|
186 | push @ifstack, ($self->$function(@_, [@args]) && $ifstack[-1]); |
|
|
187 | } elsif ($function eq "else" and @ifstack > 1) { |
|
|
188 | $ifstack[-1] = not $ifstack[-1]; |
|
|
189 | } elsif ($function eq "endif" and @ifstack > 1) { |
|
|
190 | pop @ifstack; |
|
|
191 | } elsif ($ifstack[-1]) { |
|
|
192 | if ($self->can($function)) { |
|
|
193 | my @result = $self->$function(@_, {@args}); |
|
|
194 | if (@result) { |
|
|
195 | print @result; |
|
|
196 | } else { |
|
|
197 | warn "Template escape $function returned an empty list."; |
|
|
198 | } |
|
|
199 | } |
|
|
200 | } |
|
|
201 | } |
|
|
202 | |
|
|
203 | if ($ifstack[-1]) { |
|
|
204 | print substr($line, (defined pos $line) ? pos $line : 0), "\n"; |
|
|
205 | } |
|
|
206 | } |
|
|
207 | } |
|
|
208 | |
|
|
209 | # cook_args(STRING) - parses a string of the form ARG1="FOO" ARG2="BAR". Returns |
|
|
210 | # a list which pairs into key/values and fits nicely in {}s. |
|
|
211 | # |
|
|
212 | sub cook_args($) { # ... also used by bin/wwdb, so watch out |
|
|
213 | my ($raw_args) = @_; |
|
|
214 | my @args = (); |
|
|
215 | |
|
|
216 | # Boy I love m//g in scalar context! Go read the camel book, heathen. |
|
|
217 | # First, get the whole token with the quotes on both ends... |
|
|
218 | while ($raw_args =~ m/\G\s*(\w*)="((?:[^"\\]|\\.)*)"/g) { |
|
|
219 | my ($key, $value) = ($1, $2); |
|
|
220 | # ... then, rip out all the protecty backspaces |
|
|
221 | $value =~ s/\\(.)/$1/g; |
|
|
222 | push @args, $key => $value; |
|
|
223 | } |
|
|
224 | |
|
|
225 | return @args; |
|
|
226 | } |
|
|
227 | |
|
|
228 | # This is different. It probably shouldn't print anything (except in debugging cases) |
|
|
229 | # and it should return a boolean, not a string. &if is called in a nonstandard way |
|
|
230 | # by &template, with $args as an arrayref instead of a hashref. this is a hack! yay! |
|
|
231 | |
|
|
232 | # OK, this is a pluggin architecture. it iterates through attributes of the "if" tag, |
|
|
233 | # and for each predicate $p, it calls &if_$p in an object-oriented way, continuing the |
|
|
234 | # grand templating theme of an object-oriented pluggable architecture using ->can($). |
|
|
235 | sub if { |
|
|
236 | my ($self, $args) = @_[0,-1]; |
|
|
237 | # A single if "or"s it's components. Nesting produces "and". |
|
|
238 | |
|
|
239 | my @args = @$args; # Hahahahaha, get it?! |
|
|
240 | |
|
|
241 | if (@args % 2 != 0) { |
|
|
242 | # flip out and kill people, but do not commit seppuku |
|
|
243 | print '<!--&if recieved an uneven number of arguments. This shouldn\'t happen, but I\'ll let it slide.-->\n'; |
|
|
244 | } |
|
|
245 | |
|
|
246 | while (@args > 1) { |
|
|
247 | my ($key, $value) = (shift @args, shift @args); |
|
|
248 | |
|
|
249 | # a non-existent &if_$key is the same as a false result, but we're ORing, so it's OK |
|
|
250 | my $sub = "if_$key"; # perl doesn't like it when you try to construct a string right in a method invocation |
|
|
251 | if ($self->can("if_$key") and $self->$sub("$value")) { |
|
|
252 | return 1; |
|
|
253 | } |
|
|
254 | } |
|
|
255 | |
|
|
256 | return 0; |
|
|
257 | } |
|
|
258 | |
|
|
259 | ################################################################################ |
|
|
260 | # Macros used by content generators to render common idioms |
|
|
261 | ################################################################################ |
|
|
262 | |
|
|
263 | # pathMacro(HASHREF, LIST) - helper macro for <!--#path--> escape: the hash |
|
|
264 | # reference contains the "style", "image", and "text" arguments to the escape. |
|
|
265 | # The LIST consists of ordered key-value pairs of the form: |
|
|
266 | # |
|
|
267 | # "Page Name" => URL |
|
|
268 | # |
|
|
269 | # If the page should not have a link associated with it, the URL should be left |
|
|
270 | # empty. Authentication data is added to the URL so you don't have to. A fully- |
|
|
271 | # formed path line is returned, suitable for returning by a function |
|
|
272 | # implementing the #path escape. |
|
|
273 | # |
|
|
274 | sub pathMacro { |
|
|
275 | my $self = shift; |
|
|
276 | my %args = %{ shift() }; |
|
|
277 | my @path = @_; |
|
|
278 | my $sep; |
|
|
279 | if ($args{style} eq "image") { |
|
|
280 | $sep = CGI::img({-src=>$args{image}, -alt=>$args{text}}); |
|
|
281 | } else { |
|
|
282 | $sep = $args{text}; |
|
|
283 | } |
|
|
284 | my $auth = $self->url_authen_args; |
|
|
285 | my @result; |
|
|
286 | while (@path) { |
|
|
287 | my $name = shift @path; |
|
|
288 | my $url = shift @path; |
|
|
289 | push @result, $url |
|
|
290 | ? CGI::a({-href=>"$url?$auth"}, $name) |
|
|
291 | : $name; |
|
|
292 | } |
|
|
293 | return join($sep, @result) . "\n"; |
|
|
294 | } |
|
|
295 | |
|
|
296 | sub siblingsMacro { |
|
|
297 | my $self = shift; |
|
|
298 | my @siblings = @_; |
|
|
299 | my $sep = CGI::br(); |
|
|
300 | my $auth = $self->url_authen_args; |
|
|
301 | my @result; |
|
|
302 | while (@siblings) { |
|
|
303 | my $name = shift @siblings; |
|
|
304 | my $url = shift @siblings; |
|
|
305 | push @result, $url |
|
|
306 | ? CGI::a({-href=>"$url?$auth"}, $name) |
|
|
307 | : $name; |
|
|
308 | } |
|
|
309 | return join($sep, @result), "\n"; |
|
|
310 | } |
|
|
311 | |
|
|
312 | sub navMacro { |
|
|
313 | my $self = shift; |
|
|
314 | my %args = %{ shift() }; |
|
|
315 | my $tail = shift; |
|
|
316 | my @links = @_; |
|
|
317 | my $auth = $self->url_authen_args; |
|
|
318 | my $ce = $self->{ce}; |
|
|
319 | my $prefix = $ce->{webworkURLs}->{htdocs}."/images"; |
|
|
320 | my @result; |
|
|
321 | while (@links) { |
|
|
322 | my $name = shift @links; |
|
|
323 | my $url = shift @links; |
|
|
324 | my $img = shift @links; |
|
|
325 | my $html = |
|
|
326 | ($img && $args{style} eq "images") |
|
|
327 | ? CGI::img( |
|
|
328 | {src=>($prefix."/".$img.$args{imagesuffix}), |
|
|
329 | border=>"", |
|
|
330 | alt=>"$name"}) |
|
|
331 | : $name; |
|
|
332 | unless($img && !$url) { |
|
|
333 | push @result, $url |
|
|
334 | ? CGI::a({-href=>"$url?$auth$tail"}, $html) |
|
|
335 | : $html; |
|
|
336 | } |
|
|
337 | } |
|
|
338 | return join($args{separator}, @result) . "\n"; |
|
|
339 | } |
|
|
340 | |
|
|
341 | # hidden_fields(LIST) - return hidden <INPUT> tags for each field mentioned in |
|
|
342 | # LIST (or all fields if list is empty), taking data from the current request. |
|
|
343 | # |
|
|
344 | sub hidden_fields($;@) { |
|
|
345 | my $self = shift; |
|
|
346 | my $r = $self->{r}; |
|
|
347 | my @fields = @_; |
|
|
348 | @fields or @fields = $r->param; |
|
|
349 | my $courseEnvironment = $self->{ce}; |
|
|
350 | my $html = ""; |
|
|
351 | |
|
|
352 | foreach my $param (@fields) { |
|
|
353 | my $value = $r->param($param); |
|
|
354 | $html .= CGI::input({-type=>"hidden",-name=>"$param",-value=>"$value"}); |
|
|
355 | } |
|
|
356 | return $html; |
|
|
357 | } |
|
|
358 | |
|
|
359 | # hidden_authen_fields() - use hidden_fields to return hidden <INPUT> tags for |
|
|
360 | # request fields used in authentication. |
|
|
361 | # |
|
|
362 | sub hidden_authen_fields($) { |
|
|
363 | my $self = shift; |
|
|
364 | return $self->hidden_fields("user","effectiveUser","key"); |
|
|
365 | } |
|
|
366 | |
|
|
367 | # url_args(LIST) - return a URL query string (without the leading `?') |
|
|
368 | # containing values for each field mentioned in LIST, or all fields if list is |
|
|
369 | # empty. Data is taken from the current request. |
|
|
370 | # |
|
|
371 | sub url_args($;@) { |
|
|
372 | my $self = shift; |
|
|
373 | my $r = $self->{r}; |
|
|
374 | my @fields = @_; |
|
|
375 | @fields or @fields = $r->param; # If no fields are passed in, do them all. |
|
|
376 | my $courseEnvironment = $self->{ce}; |
|
|
377 | |
|
|
378 | my @pairs; |
|
|
379 | foreach my $param (@fields) { |
|
|
380 | my @values = $r->param($param); |
|
|
381 | foreach my $value (@values) { |
|
|
382 | push @pairs, uri_escape($param) . "=" . uri_escape($value); |
|
|
383 | } |
|
|
384 | } |
|
|
385 | |
|
|
386 | return join("&", @pairs); |
|
|
387 | } |
|
|
388 | |
|
|
389 | # url_authen_args() - use url_args to return a URL query string for request |
|
|
390 | # fields used in authentication. |
|
|
391 | # |
|
|
392 | sub url_authen_args($) { |
|
|
393 | my $self = shift; |
|
|
394 | my $r = $self->{r}; |
|
|
395 | return $self->url_args("user","effectiveUser","key"); |
|
|
396 | } |
|
|
397 | |
|
|
398 | # print_form_data(BEGIN, MIDDLE, END, OMIT) - return a string containing request |
|
|
399 | # fields not matched by OMIT, placing BEGIN before each field name, MIDDLE |
|
|
400 | # between each field and its value, and END after each value. Values are taken |
|
|
401 | # from the current request. OMIT is a quoted reguar expression. |
|
|
402 | # |
| 31 | sub print_form_data { |
403 | sub print_form_data { |
| 32 | my ($self, $begin, $middle, $end, $qr_omit) = @_; |
404 | my ($self, $begin, $middle, $end, $qr_omit) = @_; |
| 33 | my $return_string = ""; |
405 | my $return_string = ""; |
| 34 | |
|
|
| 35 | $r=$self->{r}; |
406 | my $r=$self->{r}; |
| 36 | my @form_data = $r->param; |
407 | my @form_data = $r->param; |
| 37 | foreach my $name (@form_data) { |
408 | foreach my $name (@form_data) { |
| 38 | next if ($qr_omit and $name =~ /$qr_omit/); |
409 | next if ($qr_omit and $name =~ /$qr_omit/); |
| 39 | my @values = $r->param($name); |
410 | my @values = $r->param($name); |
|
|
411 | foreach my $variable (qw(begin name middle value end)) { |
|
|
412 | no strict 'refs'; |
|
|
413 | ${$variable} = "" unless defined ${$variable}; |
|
|
414 | } |
| 40 | foreach my $value (@values) { |
415 | foreach my $value (@values) { |
| 41 | $return_string .= "$begin$name$middle$value$end"; |
416 | $return_string .= "$begin$name$middle$value$end"; |
| 42 | } |
417 | } |
| 43 | } |
418 | } |
| 44 | |
|
|
| 45 | return $return_string; |
419 | return $return_string; |
| 46 | } |
420 | } |
| 47 | |
421 | |
| 48 | sub hidden_authen_fields { |
422 | sub errorOutput($$$) { |
|
|
423 | my ($self, $error, $details) = @_; |
|
|
424 | return |
|
|
425 | CGI::h3("Software Error"), |
|
|
426 | CGI::p(<<EOF), |
|
|
427 | WeBWorK has encountered a software error while attempting to process this |
|
|
428 | problem. It is likely that there is an error in the problem itself. If you are |
|
|
429 | a student, contact your professor to have the error corrected. If you are a |
|
|
430 | professor, please consut the error output below for more informaiton. |
|
|
431 | EOF |
|
|
432 | CGI::h3("Error messages"), CGI::p(CGI::tt($error)), |
|
|
433 | CGI::h3("Error context"), CGI::p(CGI::tt($details)); |
|
|
434 | } |
|
|
435 | |
|
|
436 | sub warningOutput($$) { |
|
|
437 | my ($self, $warnings) = @_; |
|
|
438 | |
|
|
439 | my @warnings = split m/\n+/, $warnings; |
|
|
440 | |
|
|
441 | return |
|
|
442 | CGI::h3("Software Warnings"), |
|
|
443 | CGI::p(<<EOF), |
|
|
444 | WeBWorK has encountered warnings while attempting to process this problem. It |
|
|
445 | is likely that this indicates an error or ambiguity in the problem itself. If |
|
|
446 | you are a student, contact your professor to have the problem corrected. If you |
|
|
447 | are a professor, please consut the warning output below for more informaiton. |
|
|
448 | EOF |
|
|
449 | CGI::h3("Warning messages"), |
|
|
450 | CGI::ul(CGI::li(\@warnings)), |
|
|
451 | ; |
|
|
452 | } |
|
|
453 | |
|
|
454 | ################################################################################ |
|
|
455 | # Generic versions of template escapes |
|
|
456 | ################################################################################ |
|
|
457 | |
|
|
458 | # Reminder: here are the template functions currently defined: |
|
|
459 | # FIXME: this list is out of date!!!!!!!! |
|
|
460 | # |
|
|
461 | # head |
|
|
462 | # path |
|
|
463 | # style = text|image |
|
|
464 | # image = URL of image |
|
|
465 | # text = text separator |
|
|
466 | # loginstatus |
|
|
467 | # links |
|
|
468 | # siblings |
|
|
469 | # nav |
|
|
470 | # style = text|image |
|
|
471 | # imageprefix = prefix to image URL |
|
|
472 | # imagesuffix = suffix to image URL |
|
|
473 | # separator = HTML to place in between links |
|
|
474 | # title |
|
|
475 | # body |
|
|
476 | |
|
|
477 | sub header { |
| 49 | my $self = shift; |
478 | my $self = shift; |
| 50 | my $r = $self->{r}; |
479 | my $r = $self->{r}; |
| 51 | my $courseEnvironment = $self->{courseEnvironment}; |
|
|
| 52 | my $html = ""; |
|
|
| 53 | |
480 | |
| 54 | foreach $param ("user","key") { |
481 | if ($self->{sendFile}) { |
| 55 | my $value = $r->param($param); |
482 | my $contentType = $self->{sendFile}->{type}; |
| 56 | $html .= input({-type=>"hidden",-name=>"$param",-value=>"$value"}); |
483 | my $fileName = $self->{sendFile}->{name}; |
| 57 | } |
484 | $r->content_type($contentType); |
| 58 | return $html; |
485 | $r->header_out("Content-Disposition" => "attachment; filename=\"$fileName\""); |
| 59 | } |
486 | } else { |
| 60 | |
|
|
| 61 | sub pre_header_initialize {} |
|
|
| 62 | |
|
|
| 63 | sub header { |
|
|
| 64 | my $self = shift; |
|
|
| 65 | my $r=$self->{r}; |
|
|
| 66 | $r->content_type('text/html'); |
487 | $r->content_type("text/html"); |
|
|
488 | |
|
|
489 | } |
|
|
490 | |
| 67 | $r->send_http_header(); |
491 | $r->send_http_header(); |
|
|
492 | return OK; |
| 68 | } |
493 | } |
| 69 | |
494 | |
| 70 | sub initialize {} |
495 | sub loginstatus { |
| 71 | |
|
|
| 72 | sub title { |
|
|
| 73 | return "Superclass"; |
|
|
| 74 | } |
|
|
| 75 | |
|
|
| 76 | sub body { |
|
|
| 77 | print "Generated content"; |
|
|
| 78 | ""; |
|
|
| 79 | } |
|
|
| 80 | |
|
|
| 81 | sub logo { |
|
|
| 82 | my $self = shift; |
|
|
| 83 | return $self->{courseEnvironment}->{urls}->{logo}; |
|
|
| 84 | } |
|
|
| 85 | |
|
|
| 86 | sub htdocs_base { |
|
|
| 87 | my $self = shift; |
|
|
| 88 | return $self->{courseEnvironment}->{urls}->{base}; |
|
|
| 89 | } |
|
|
| 90 | |
|
|
| 91 | sub go { |
|
|
| 92 | my $self = shift; |
496 | my $self = shift; |
| 93 | my $r = $self->{r}; |
497 | my $r = $self->{r}; |
| 94 | my $courseEnvironment = $self->{courseEnvironment}; |
498 | my $ce = $self->{ce}; |
| 95 | |
|
|
| 96 | $self->pre_header_initialize(@_); |
|
|
| 97 | $self->header(@_); return OK if $r->header_only; |
|
|
| 98 | $self->initialize(@_); |
|
|
| 99 | |
499 | |
| 100 | my $templateFile = $courseEnvironment->{templates}->{system}; |
500 | my $user = $r->param("user"); |
|
|
501 | my $eUser = $r->param("effectiveUser"); |
|
|
502 | my $key = $r->param("key"); |
| 101 | |
503 | |
| 102 | open(TEMPLATE, $templateFile) or die "Couldn't open template $templateFile"; |
504 | return "" unless $key; |
| 103 | my @template = <TEMPLATE>; |
|
|
| 104 | close TEMPLATE; |
|
|
| 105 | |
505 | |
| 106 | foreach my $line (@template) { |
506 | my $exitURL = $r->uri() . "?user=$user&key=$key"; |
| 107 | # This is incremental regex processing. |
507 | |
| 108 | # the /c is so that pos($line) doesn't die when the regex fails. |
508 | my $root = $ce->{webworkURLs}->{root}; |
| 109 | while ($line =~ m/\G(.*?)<!--#(.*?)\s*-->/gc) { |
509 | my $courseID = $ce->{courseName}; |
| 110 | print "$1"; |
510 | my $logout = "$root/$courseID/logout/?" . $self->url_authen_args(); |
| 111 | print $self->$2(@_) if $self->can($2); |
511 | |
|
|
512 | print CGI::small("User:", "$user"); |
|
|
513 | |
|
|
514 | if ($user ne $eUser) { |
|
|
515 | print CGI::br(), CGI::font({-color=>'red'}, |
|
|
516 | CGI::small("Acting as:", "$eUser") |
|
|
517 | ), |
|
|
518 | CGI::br(), CGI::a({-href=>$exitURL}, |
|
|
519 | CGI::small("Stop Acting") |
|
|
520 | ); |
| 112 | } |
521 | } |
| 113 | # I thought I could use pos($line) here, but /noooooo/ |
|
|
| 114 | print substr $line, pos($line); |
|
|
| 115 | } |
|
|
| 116 | |
522 | |
|
|
523 | print CGI::br(), CGI::a({-href=>$logout}, CGI::small("Log Out")); |
|
|
524 | |
| 117 | return OK; |
525 | return ""; |
|
|
526 | } |
|
|
527 | |
|
|
528 | # FIXME: drunk code. rewrite. |
|
|
529 | # also, this should be structured s.t. subclasses can add items to the links |
|
|
530 | # area, i.e. "stacking" |
|
|
531 | sub links { |
|
|
532 | my $self = shift; |
|
|
533 | my @components = @_; |
|
|
534 | my $ce = $self->{ce}; |
|
|
535 | my $db = $self->{db}; |
|
|
536 | my $userName = $self->{r}->param("user"); |
|
|
537 | my $courseName = $ce->{courseName}; |
|
|
538 | my $root = $ce->{webworkURLs}->{root}; |
|
|
539 | |
|
|
540 | #my $Key = $db->getKey($userName); # checked |
|
|
541 | #my $key = (defiend $key |
|
|
542 | # ? $Key->key() |
|
|
543 | # : ""); |
|
|
544 | # |
|
|
545 | #return "" unless defined $key; |
|
|
546 | # This has been replaced by using "#if loggedin" in ur.template. |
|
|
547 | |
|
|
548 | # URLs to parts of the system |
|
|
549 | my $probSets = "$root/$courseName/?" . $self->url_authen_args(); |
|
|
550 | my $prefs = "$root/$courseName/options/?" . $self->url_authen_args(); |
|
|
551 | my $help = "$ce->{webworkURLs}->{docs}?" . $self->url_authen_args(); |
|
|
552 | my $logout = "$root/$courseName/logout/?" . $self->url_authen_args(); |
|
|
553 | |
|
|
554 | my $PermissionLevel = $db->getPermissionLevel($userName); # checked |
|
|
555 | my $permLevel = (defined $PermissionLevel |
|
|
556 | ? $PermissionLevel->permission() |
|
|
557 | : 0); |
|
|
558 | |
|
|
559 | return join("", |
|
|
560 | CGI::div( {style=>'font-size:larger'},CGI::a({-href=>$probSets}, "Problem Sets") |
|
|
561 | ), |
|
|
562 | CGI::a({-href=>$prefs}, "User Prefs"), CGI::br(), |
|
|
563 | CGI::a({-href=>$help,-target=>'_help_'}, "Help"), CGI::br(), |
|
|
564 | #CGI::a({-href=>$logout}, "Log Out"), CGI::br(), |
|
|
565 | ($permLevel > 0 |
|
|
566 | ? $self->instructor_links(@components) : "" |
|
|
567 | ), |
|
|
568 | ); |
|
|
569 | } |
|
|
570 | sub instructor_links { |
|
|
571 | my $self = shift; |
|
|
572 | my @components = @_; |
|
|
573 | my $args = pop(@components); # get hash of option arguments |
|
|
574 | my $courseName = $self->{ce}->{courseName}; |
|
|
575 | my $root = $self->{ce}->{webworkURLs}->{root}; |
|
|
576 | my $userName = $self->{r}->param("effectiveUser"); |
|
|
577 | $userName = $self->{r}->param("user") unless defined $userName; |
|
|
578 | my ($set, $prob) = @components; |
|
|
579 | my $instructor = "$root/$courseName/instructor/?" . $self->url_authen_args(); |
|
|
580 | my $sets = "$root/$courseName/instructor/sets/?" . $self->url_authen_args(); |
|
|
581 | my $users = "$root/$courseName/instructor/users/?" . $self->url_authen_args(); |
|
|
582 | my $email = "$root/$courseName/instructor/send_mail/?" . $self->url_authen_args(); |
|
|
583 | my $scoring = "$root/$courseName/instructor/scoring/?" . $self->url_authen_args(); |
|
|
584 | my $statsRoot = "$root/$courseName/instructor/stats"; |
|
|
585 | my $stats = $statsRoot. '/?'.$self->url_authen_args(); |
|
|
586 | my $fileXfer = "$root/$courseName/instructor/files/?" . $self->url_authen_args(); |
|
|
587 | |
|
|
588 | |
|
|
589 | # Add direct links to sets e.g. 3:4 for set3 problem 4 |
|
|
590 | my $setURL = (defined $set) |
|
|
591 | ? "$root/$courseName/instructor/sets/$set/?" . $self->url_authen_args() |
|
|
592 | : ''; |
|
|
593 | my $probURL = (defined $set && defined $prob) |
|
|
594 | ? "$root/$courseName/instructor/pgProblemEditor/$set/$prob?" . $self->url_authen_args() |
|
|
595 | : ''; |
|
|
596 | |
|
|
597 | my ($setLink, $problemLink) = ("", ""); |
|
|
598 | if ($setURL) { |
|
|
599 | $setLink = " " |
|
|
600 | . CGI::a({-href=>$setURL}, "Set $set") |
|
|
601 | . CGI::br(); |
|
|
602 | if ($probURL) { |
|
|
603 | $problemLink = " " |
|
|
604 | . CGI::a({-href=>$probURL}, "Problem $prob") |
|
|
605 | . CGI::br(); |
|
|
606 | } |
|
|
607 | } |
|
|
608 | |
|
|
609 | #my $setProb = ($setURL) |
|
|
610 | # ? CGI::a({-href=>$setURL}, $set) |
|
|
611 | # : ''; |
|
|
612 | #$setProb .= ':' . CGI::a({-href=>$probURL},$prob) if $setProb && $probURL; |
|
|
613 | |
|
|
614 | return join("", |
|
|
615 | CGI::hr(), |
|
|
616 | CGI::div( {style=>'font-size:larger'}, |
|
|
617 | CGI::a({-href=>$instructor}, "Instructor Tools") |
|
|
618 | ), |
|
|
619 | ' ',CGI::a({-href=>$users}, "User List"), CGI::br(), |
|
|
620 | ' ',CGI::a({-href=>$sets}, "Set List"), CGI::br(), |
|
|
621 | $setLink, |
|
|
622 | $problemLink, |
|
|
623 | ' ',CGI::a({-href=>$email}, "Mail Merge"), CGI::br(), |
|
|
624 | ' ',CGI::a({-href=>$scoring}, "Scoring"), CGI::br(), |
|
|
625 | ' ',CGI::a({-href=>$stats}, "Statistics"), CGI::br(), |
|
|
626 | (defined($set)) |
|
|
627 | ? ' '.CGI::a({-href=>"$statsRoot/set/$set/?".$self->url_authen_args}, "$set").CGI::br() |
|
|
628 | : '', |
|
|
629 | (defined($userName)) |
|
|
630 | ? ' '.CGI::a({-href=>"$statsRoot/student/$userName/?".$self->url_authen_args}, "$userName").CGI::br() |
|
|
631 | : '', |
|
|
632 | ' ',CGI::a({-href=>$fileXfer}, "File Transfer"), CGI::br(), |
|
|
633 | ); |
|
|
634 | } |
|
|
635 | |
|
|
636 | # &if_can will return 1 if the current object->can("do $_[1]") |
|
|
637 | sub if_can ($$) { |
|
|
638 | my ($self, $arg) = (@_); |
|
|
639 | |
|
|
640 | if ($self->can("$arg")) { |
|
|
641 | return 1; |
|
|
642 | } else { |
|
|
643 | return 0; |
|
|
644 | } |
|
|
645 | } |
|
|
646 | |
|
|
647 | # Every content generator is logged in unless it says otherwise. |
|
|
648 | sub if_loggedin($$) { |
|
|
649 | my ($self, $arg) = (@_); |
|
|
650 | |
|
|
651 | return $arg; |
|
|
652 | } |
|
|
653 | |
|
|
654 | # Handling of errors in submissions |
|
|
655 | |
|
|
656 | sub if_submiterror($$) { |
|
|
657 | my ($self, $arg) = @_; |
|
|
658 | if (exists $self->{submitError}) { |
|
|
659 | return $arg; |
|
|
660 | } else { |
|
|
661 | return !$arg; |
|
|
662 | } |
|
|
663 | } |
|
|
664 | |
|
|
665 | sub submiterror { |
|
|
666 | my ($self) = @_; |
|
|
667 | if (exists $self->{submitError}) { |
|
|
668 | return $self->{submitError}; |
|
|
669 | } else { |
|
|
670 | return ""; |
|
|
671 | } |
|
|
672 | } |
|
|
673 | |
|
|
674 | # General warning handling |
|
|
675 | |
|
|
676 | sub if_warnings($$) { |
|
|
677 | my ($self, $arg) = @_; |
|
|
678 | return $self->{r}->notes("warnings") ? $arg : !$arg; |
|
|
679 | } |
|
|
680 | |
|
|
681 | sub warnings { |
|
|
682 | my ($self) = @_; |
|
|
683 | my $r = $self->{r}; |
|
|
684 | if ($r->notes("warnings")) { |
|
|
685 | return $self->warningOutput($r->notes("warnings")); |
|
|
686 | } else { |
|
|
687 | return ""; |
|
|
688 | } |
| 118 | } |
689 | } |
| 119 | |
690 | |
| 120 | 1; |
691 | 1; |
|
|
692 | |
|
|
693 | __END__ |
|
|
694 | |
|
|
695 | =head1 AUTHOR |
|
|
696 | |
|
|
697 | Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu |
|
|
698 | and Sam Hathaway, sh002i (at) math.rochester.edu. |
|
|
699 | |
|
|
700 | =cut |