|
|
1 | ################################################################################ |
|
|
2 | # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project |
|
|
3 | # $Id$ |
|
|
4 | ################################################################################ |
|
|
5 | |
| 1 | package WeBWorK::ContentGenerator; |
6 | package WeBWorK::ContentGenerator; |
| 2 | |
7 | |
| 3 | use CGI qw(-compile :html :form); |
8 | =head1 NAME |
|
|
9 | |
|
|
10 | WeBWorK::ContentGenerator - base class for modules that generate page content. |
|
|
11 | |
|
|
12 | =cut |
|
|
13 | |
|
|
14 | use strict; |
|
|
15 | use warnings; |
| 4 | use Apache::Constants qw(:common); |
16 | use Apache::Constants qw(:common); |
| 5 | |
17 | use CGI qw(); |
| 6 | # Send 'die' message to the browser window |
18 | use URI::Escape; |
|
|
19 | use WeBWorK::Utils qw(readFile); |
| 7 | #use CGI::Carp qw(fatalsToBrowser); |
20 | #use CGI::Carp qw(fatalsToBrowser); |
| 8 | |
21 | |
|
|
22 | ################################################################################ |
|
|
23 | # This is a very unruly file, so I'm going to use very large comments to divide |
|
|
24 | # it into logical sections. |
|
|
25 | ################################################################################ |
| 9 | |
26 | |
| 10 | # This is a superclass for Apache::WeBWorK's content generators. |
|
|
| 11 | # You are /definitely/ encouraged to read this file, since there are |
|
|
| 12 | # "abstract" functions here which show aproximately what form you would |
|
|
| 13 | # want over-ridden sub-classes to follow. |
|
|
| 14 | |
|
|
| 15 | # new(Apache::Request, WeBWorK::CourseEnvironment) |
27 | # new(Apache::Request, WeBWorK::CourseEnvironment) - create a new instance of a |
|
|
28 | # content generator. Usually only called by the dispatcher, although one might |
|
|
29 | # be able to use it for things like "sub-requests". Uh... uh... I have to think |
|
|
30 | # about that one. The dispatcher uses this idiom: |
|
|
31 | # |
|
|
32 | # |
|
|
33 | # WeBWorK::ContentGenerator::WHATEVER->new($r, $ce)->go(@whatever); |
|
|
34 | # |
|
|
35 | # and throws away the result ;) |
|
|
36 | # |
| 16 | sub new($$$) { |
37 | sub new($$$) { |
| 17 | my $invocant = shift; |
38 | my $invocant = shift; |
| 18 | my $class = ref($invocant) || $invocant; |
39 | my $class = ref($invocant) || $invocant; |
| 19 | my $self = {}; |
40 | my $self = {}; |
| 20 | ($self->{r}, $self->{courseEnvironment}) = @_; |
41 | ($self->{r}, $self->{courseEnvironment}) = @_; |
| 21 | bless $self, $class; |
42 | bless $self, $class; |
| 22 | return $self; |
43 | return $self; |
| 23 | } |
44 | } |
| 24 | |
45 | |
|
|
46 | ################################################################################ |
|
|
47 | # Invocation and template processing |
|
|
48 | ################################################################################ |
| 25 | |
49 | |
| 26 | # This is a quick and dirty function to print out all (or almost all) of the |
50 | # go(@otherArguments) - render a page, using methods from the particular |
| 27 | # fields in a form in a specified format. As you can see from the print |
51 | # subclass of ContentGenerator. @otherArguments is passed to each method, so |
| 28 | # statement, it just prints out $begining$name$middle$value$end for every |
52 | # that the dispatcher can pass CG-specific data. The order of calls looks like |
| 29 | # field who's name doesn't match $qr_omit, a quoted regex. |
53 | # this: |
| 30 | # In it's current incarnation, it should be called from subclasses only, |
54 | # |
| 31 | # by saying $self->print_form_data. Of course, you could construct a |
55 | # * &pre_header_initialize - give subclasses a chance to do initialization |
| 32 | # hashref with ->{r} being an Apache::Request, I suppose. |
56 | # necessary for generating the HTTP header. |
| 33 | |
57 | # * &header - this class provides a standard HTTP header with Content-Type |
| 34 | sub print_form_data { |
58 | # text/html. Subclasses are welcome to overload this for things like |
| 35 | my ($self, $begin, $middle, $end, $qr_omit) = @_; |
59 | # an image-creation content generator or a PDF generator. |
| 36 | my $return_string = ""; |
60 | # * &initialize - let subclasses do post-header initialization. |
| 37 | |
61 | # * any "template escapes" defined in the system template and supported by |
|
|
62 | # the subclass. Generic implementations of &title and &body are provided. |
|
|
63 | # |
|
|
64 | sub go { |
|
|
65 | my $self = shift; |
| 38 | $r=$self->{r}; |
66 | my $r = $self->{r}; |
| 39 | my @form_data = $r->param; |
67 | my $courseEnvironment = $self->{courseEnvironment}; |
|
|
68 | |
|
|
69 | $self->pre_header_initialize(@_) if $self->can("pre_header_initialize"); |
|
|
70 | $self->header(@_); |
|
|
71 | return OK if $r->header_only; |
|
|
72 | |
|
|
73 | $self->initialize(@_) if $self->can("initialize"); |
|
|
74 | $self->template($courseEnvironment->{templates}->{system}, @_); |
|
|
75 | |
|
|
76 | return OK; |
|
|
77 | } |
|
|
78 | |
|
|
79 | # template(STRING, @otherArguments) - parse a template, looking for escapes of |
|
|
80 | # the form <!--#NAME ARG1="FOO" ARG2="BAR"--> and calling a member function NAME |
|
|
81 | # (if available) for each NAME. The escapes are called like: |
|
|
82 | # |
|
|
83 | # $self->NAME(@otherArguments, \%escapeArguments) |
|
|
84 | # |
|
|
85 | # where @otherArguments originates in the dispatcher and %escapeArguments is |
|
|
86 | # parsed out of the escape itself (i.e. ARG1 => FOO, ARG2 => BAR) |
|
|
87 | # |
|
|
88 | sub template { |
|
|
89 | my ($self, $templateFile) = (shift, shift); |
|
|
90 | my $r = $self->{r}; |
|
|
91 | my $courseEnvironment = $self->{courseEnvironment}; |
|
|
92 | |
|
|
93 | # so even though the variable $/ APPEARS to contain a newline, |
|
|
94 | # <TEMPLATE> is slurping the whole file into the first element of |
|
|
95 | # @template ONLY AFTER THE TRANSLATOR RUNS. WTF!!! |
|
|
96 | # |
|
|
97 | #open(TEMPLATE, $templateFile) or die "Couldn't open template $templateFile"; |
|
|
98 | #my @template = <TEMPLATE>; |
|
|
99 | #close TEMPLATE; |
|
|
100 | # |
|
|
101 | # Let's try something else instead: |
|
|
102 | |
|
|
103 | my @template = split /\n/, readFile($templateFile); |
|
|
104 | |
| 40 | foreach my $name (@form_data) { |
105 | foreach my $line (@template) { |
| 41 | next if ($qr_omit and $name =~ /$qr_omit/); |
106 | #warn "foo: $line\n"; |
| 42 | my @values = $r->param($name); |
107 | # This is incremental regex processing. |
| 43 | foreach my $value (@values) { |
108 | # the /c is so that pos($line) doesn't die when the regex fails. |
| 44 | $return_string .= "$begin$name$middle$value$end"; |
109 | while ($line =~ m/\G(.*?)<!--#(\w*)((?:\s+.*?)?)-->/gc) { |
|
|
110 | 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) : {}; |
|
|
113 | print $before; |
|
|
114 | |
|
|
115 | if ($self->can($function)) { |
|
|
116 | print $self->$function(@_, $args); |
|
|
117 | } |
| 45 | } |
118 | } |
|
|
119 | |
|
|
120 | print substr $line, (defined(pos($line)) ? pos($line) : 0); |
| 46 | } |
121 | } |
| 47 | |
|
|
| 48 | return $return_string; |
|
|
| 49 | } |
122 | } |
| 50 | |
123 | |
| 51 | sub hidden_authen_fields { |
124 | # cook_args(STRING) - parses a string of the form ARG1="FOO" ARG2="BAR". Returns |
| 52 | my $self = shift; |
125 | # a reference to a hash containing the parsed arguments. |
| 53 | my $r = $self->{r}; |
126 | # |
| 54 | my $courseEnvironment = $self->{courseEnvironment}; |
|
|
| 55 | my $html = ""; |
|
|
| 56 | |
|
|
| 57 | foreach $param ("user","effectiveUser","key") { |
|
|
| 58 | my $value = $r->param($param); |
|
|
| 59 | $html .= input({-type=>"hidden",-name=>"$param",-value=>"$value"}); |
|
|
| 60 | } |
|
|
| 61 | return $html; |
|
|
| 62 | } |
|
|
| 63 | |
|
|
| 64 | ### Functions that subclasses /should/ override under most circumstances |
|
|
| 65 | |
|
|
| 66 | sub title { |
|
|
| 67 | return "Superclass"; |
|
|
| 68 | } |
|
|
| 69 | |
|
|
| 70 | sub body { |
|
|
| 71 | print "Generated content"; |
|
|
| 72 | ""; |
|
|
| 73 | } |
|
|
| 74 | |
|
|
| 75 | ### Functions that subclasses /may/ want to override, if they've got something |
|
|
| 76 | ### special to say |
|
|
| 77 | |
|
|
| 78 | sub pre_header_initialize {} |
|
|
| 79 | |
|
|
| 80 | sub header { |
|
|
| 81 | my $self = shift; |
|
|
| 82 | my $r=$self->{r}; |
|
|
| 83 | $r->content_type('text/html'); |
|
|
| 84 | $r->send_http_header(); |
|
|
| 85 | } |
|
|
| 86 | |
|
|
| 87 | sub initialize {} |
|
|
| 88 | |
|
|
| 89 | ### Content-generating functions that should probably not be overridden |
|
|
| 90 | ### by most subclasses |
|
|
| 91 | |
|
|
| 92 | sub logo { |
|
|
| 93 | my $self = shift; |
|
|
| 94 | return $self->{courseEnvironment}->{urls}->{logo}; |
|
|
| 95 | } |
|
|
| 96 | |
|
|
| 97 | sub htdocs_base { |
|
|
| 98 | my $self = shift; |
|
|
| 99 | return $self->{courseEnvironment}->{urls}->{base}; |
|
|
| 100 | } |
|
|
| 101 | |
|
|
| 102 | sub test_args { |
|
|
| 103 | my %args = %{$_[-1]}; |
|
|
| 104 | |
|
|
| 105 | print "<pre>"; |
|
|
| 106 | print "$_ => $args{$_}\n" foreach (keys %args); |
|
|
| 107 | print "</pre>"; |
|
|
| 108 | ""; |
|
|
| 109 | } |
|
|
| 110 | |
|
|
| 111 | # Used by &go to parse the argument fields of the template escapes |
|
|
| 112 | sub cook_args($) { |
127 | sub cook_args($) { |
|
|
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. |
| 113 | my ($raw_args) = @_; |
130 | my ($raw_args) = @_; |
| 114 | my $args = {}; |
131 | my $args = {}; |
| 115 | #my $quotable_string = qr/(?:".*?(?<*\\)"|\W*)/; |
132 | #my $quotable_string = qr/(?:".*?(?<*\\)"|\W*)/; |
| 116 | #my $quotable_string = qr/(?:".*?(?<!\\)"|\W*)/; |
133 | #my $quotable_string = qr/(?:".*?(?<!\\)"|\W*)/; |
| 117 | #my $test_string = '"hel \" lo" hello'; |
134 | #my $test_string = '"hel \" lo" hello'; |
| … | |
… | |
| 124 | } |
141 | } |
| 125 | |
142 | |
| 126 | return $args; |
143 | return $args; |
| 127 | } |
144 | } |
| 128 | |
145 | |
| 129 | # Perform substitution in a template file and print it. This should be called |
146 | ################################################################################ |
| 130 | # for all content generators that are creating HTML output, and is called by |
147 | # Macros used by content generators to render common idioms |
| 131 | # default by the &go method. |
148 | ################################################################################ |
| 132 | sub template { |
149 | |
| 133 | my ($self, $templateFile) = (shift, shift); |
150 | # pathMacro(HASHREF, LIST) - helper macro for <!--#path--> escape: the hash |
|
|
151 | # reference contains the "style", "image", and "text" arguments to the escape. |
|
|
152 | # The LIST consists of ordered key-value pairs of the form: |
|
|
153 | # |
|
|
154 | # "Page Name" => URL |
|
|
155 | # |
|
|
156 | # If the page should not have a link associated with it, the URL should be left |
|
|
157 | # empty. Authentication data is added to the URL so you don't have to. A fully- |
|
|
158 | # formed path line is returned, suitable for returning by a function |
|
|
159 | # implementing the #path escape. |
|
|
160 | # |
|
|
161 | sub pathMacro { |
|
|
162 | my $self = shift; |
|
|
163 | my %args = %{ shift() }; |
|
|
164 | my @path = @_; |
|
|
165 | my $sep; |
|
|
166 | if ($args{style} eq "image") { |
|
|
167 | $sep = CGI::img({-src=>$args{image}, -alt=>$args{text}}); |
|
|
168 | } else { |
|
|
169 | $sep = $args{text}; |
|
|
170 | } |
|
|
171 | my $auth = $self->url_authen_args; |
|
|
172 | my @result; |
|
|
173 | while (@path) { |
|
|
174 | my $name = shift @path; |
|
|
175 | my $url = shift @path; |
|
|
176 | push @result, $url |
|
|
177 | ? CGI::a({-href=>"$url?$auth"}, $name) |
|
|
178 | : $name; |
|
|
179 | } |
|
|
180 | return join($sep, @result), "\n"; |
|
|
181 | } |
|
|
182 | |
|
|
183 | sub siblingsMacro { |
|
|
184 | my $self = shift; |
|
|
185 | my @siblings = @_; |
|
|
186 | my $sep = CGI::br(); |
|
|
187 | my $auth = $self->url_authen_args; |
|
|
188 | my @result; |
|
|
189 | while (@siblings) { |
|
|
190 | my $name = shift @siblings; |
|
|
191 | my $url = shift @siblings; |
|
|
192 | push @result, $url |
|
|
193 | ? CGI::a({-href=>"$url?$auth"}, $name) |
|
|
194 | : $name; |
|
|
195 | } |
|
|
196 | return join($sep, @result), "\n"; |
|
|
197 | } |
|
|
198 | |
|
|
199 | sub navMacro { |
|
|
200 | my $self = shift; |
|
|
201 | my %args = %{ shift() }; |
|
|
202 | my @links = @_; |
|
|
203 | my $auth = $self->url_authen_args; |
|
|
204 | my @result; |
|
|
205 | while (@links) { |
|
|
206 | my $name = shift @links; |
|
|
207 | my $url = shift @links; |
|
|
208 | push @result, $url |
|
|
209 | ? CGI::a({-href=>"$url?$auth"}, $name) |
|
|
210 | : $name; |
|
|
211 | } |
|
|
212 | return join($args{separator}, @result), "\n"; |
|
|
213 | } |
|
|
214 | |
|
|
215 | # hidden_fields(LIST) - return hidden <INPUT> tags for each field mentioned in |
|
|
216 | # LIST (or all fields if list is empty), taking data from the current request. |
|
|
217 | # |
|
|
218 | sub hidden_fields($;@) { |
|
|
219 | my $self = shift; |
| 134 | my $r = $self->{r}; |
220 | my $r = $self->{r}; |
|
|
221 | my @fields = @_; |
|
|
222 | @fields or @fields = $r->param; |
| 135 | my $courseEnvironment = $self->{courseEnvironment}; |
223 | my $courseEnvironment = $self->{courseEnvironment}; |
|
|
224 | my $html = ""; |
| 136 | |
225 | |
| 137 | open(TEMPLATE, $templateFile) or die "Couldn't open template $templateFile"; |
226 | foreach my $param (@fields) { |
| 138 | my @template = <TEMPLATE>; |
227 | my $value = $r->param($param); |
| 139 | close TEMPLATE; |
228 | $html .= CGI::input({-type=>"hidden",-name=>"$param",-value=>"$value"}); |
|
|
229 | } |
|
|
230 | return $html; |
|
|
231 | } |
|
|
232 | |
|
|
233 | # hidden_authen_fields() - use hidden_fields to return hidden <INPUT> tags for |
|
|
234 | # request fields used in authentication. |
|
|
235 | # |
|
|
236 | sub hidden_authen_fields($) { |
|
|
237 | my $self = shift; |
|
|
238 | return $self->hidden_fields("user","effectiveUser","key"); |
|
|
239 | } |
|
|
240 | |
|
|
241 | # url_args(LIST) - return a URL query string (without the leading `?') |
|
|
242 | # containing values for each field mentioned in LIST, or all fields if list is |
|
|
243 | # empty. Data is taken from the current request. |
|
|
244 | # |
|
|
245 | sub url_args($;@) { |
|
|
246 | my $self = shift; |
|
|
247 | my $r = $self->{r}; |
|
|
248 | my @fields = @_; |
|
|
249 | @fields or @fields = $r->param; |
|
|
250 | my $courseEnvironment = $self->{courseEnvironment}; |
| 140 | |
251 | |
|
|
252 | my @pairs; |
|
|
253 | foreach my $param (@fields) { |
|
|
254 | my $value = $r->param($param) || ""; |
|
|
255 | push @pairs, uri_escape($param) . "=" . uri_escape($value); |
|
|
256 | } |
|
|
257 | |
|
|
258 | return join("&", @pairs); |
|
|
259 | } |
|
|
260 | |
|
|
261 | # url_authen_args() - use url_args to return a URL query string for request |
|
|
262 | # fields used in authentication. |
|
|
263 | # |
|
|
264 | sub url_authen_args($) { |
|
|
265 | my $self = shift; |
|
|
266 | my $r = $self->{r}; |
|
|
267 | return $self->url_args("user","effectiveUser","key"); |
|
|
268 | } |
|
|
269 | |
|
|
270 | # print_form_data(BEGIN, MIDDLE, END, OMIT) - return a string containing request |
|
|
271 | # fields not matched by OMIT, placing BEGIN before each field name, MIDDLE |
|
|
272 | # between each field and its value, and END after each value. Values are taken |
|
|
273 | # from the current request. OMIT is a quoted reguar expression. |
|
|
274 | # |
|
|
275 | sub print_form_data { |
|
|
276 | my ($self, $begin, $middle, $end, $qr_omit) = @_; |
|
|
277 | my $return_string = ""; |
|
|
278 | my $r=$self->{r}; |
|
|
279 | my @form_data = $r->param; |
| 141 | foreach my $line (@template) { |
280 | foreach my $name (@form_data) { |
| 142 | # This is incremental regex processing. |
281 | next if ($qr_omit and $name =~ /$qr_omit/); |
| 143 | # the /c is so that pos($line) doesn't die when the regex fails. |
282 | my @values = $r->param($name); |
| 144 | while ($line =~ m/\G(.*?)<!--#(\w*)((?:\s+.*?)?)-->/gc) { |
283 | foreach my $variable (qw(begin name middle value end)) { |
| 145 | my ($before, $function, $raw_args) = ($1, $2, $3); |
284 | no strict 'refs'; |
| 146 | # $args here will be a hashref |
285 | ${$variable} = "" unless defined ${$variable}; |
| 147 | my $args = cook_args $raw_args if $raw_args =~ /\S/; |
|
|
| 148 | print $before; |
|
|
| 149 | print $self->$function(@_, $args) if $self->can($function); |
|
|
| 150 | } |
286 | } |
| 151 | print substr $line, pos($line); |
287 | foreach my $value (@values) { |
|
|
288 | $return_string .= "$begin$name$middle$value$end"; |
| 152 | } |
289 | } |
|
|
290 | } |
|
|
291 | return $return_string; |
| 153 | } |
292 | } |
| 154 | |
293 | |
| 155 | # Do whatever needs to be done in order to get a page to the client. You |
294 | ################################################################################ |
| 156 | # probably don't want to override this unless you're not making a web page |
295 | # Generic versions of template escapes |
| 157 | # with the template. |
296 | ################################################################################ |
| 158 | sub go { |
297 | |
|
|
298 | # Reminder: here are the template functions currently defined: |
|
|
299 | # |
|
|
300 | # path |
|
|
301 | # style = text|image |
|
|
302 | # image = URL of image |
|
|
303 | # text = text separator |
|
|
304 | # quicklinks |
|
|
305 | # siblings |
|
|
306 | # nav |
|
|
307 | # style = text|image |
|
|
308 | # imageprefix = prefix to image URL |
|
|
309 | # imagesuffix = suffix to image URL |
|
|
310 | # separator = HTML to place in between links |
|
|
311 | # title |
|
|
312 | # body |
|
|
313 | |
|
|
314 | sub header { |
| 159 | my $self = shift; |
315 | my $self = shift; |
| 160 | my $r = $self->{r}; |
316 | my $r = $self->{r}; |
|
|
317 | $r->content_type('text/html'); |
|
|
318 | $r->send_http_header(); |
|
|
319 | } |
|
|
320 | |
|
|
321 | sub quicklinks { |
|
|
322 | my $self = shift; |
| 161 | my $courseEnvironment = $self->{courseEnvironment}; |
323 | my $ce = $self->{courseEnvironment}; |
| 162 | |
324 | my $root = $ce->{webworkURLs}->{root}; |
| 163 | $self->pre_header_initialize(@_); |
325 | my $courseName = $ce->{courseName}; |
| 164 | $self->header(@_); return OK if $r->header_only; |
326 | my $probSets = "$root/$courseName/?" . $self->url_authen_args(); |
| 165 | $self->initialize(@_); |
327 | # my $prefs = "$root/prefs/?" . $self->url_authen_args(); |
| 166 | |
328 | # my $help = $ce->{webworkURLs}->{docs} . "?" . $self->url_authen_args(); |
| 167 | $self->template($courseEnvironment->{templates}->{system}, @_); |
329 | my $logout = "$root/$courseName/"; |
| 168 | |
|
|
| 169 | return OK; |
330 | return |
|
|
331 | CGI::a({-href=>$probSets}, "Problem Sets"), CGI::br(), |
|
|
332 | # CGI::a({-href=>$prefs}, "User Options"), CGI::br(), |
|
|
333 | # CGI::a({-href=>$help}, "Help"), CGI::br(), |
|
|
334 | CGI::a({-href=>$logout}, "Log Out"), CGI::br(), |
|
|
335 | ; |
|
|
336 | } |
|
|
337 | |
|
|
338 | sub title { |
|
|
339 | return "WeBWorK"; |
|
|
340 | } |
|
|
341 | |
|
|
342 | sub body { |
|
|
343 | return "Generated content"; |
| 170 | } |
344 | } |
| 171 | |
345 | |
| 172 | 1; |
346 | 1; |