################################################################################ # WeBWorK Online Homework Delivery System # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator.pm,v 1.80 2004/03/06 21:49:32 sh002i Exp $ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any later # version, or (b) the "Artistic License" which comes with this package. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################ package WeBWorK::ContentGenerator; =head1 NAME WeBWorK::ContentGenerator - base class for modules that generate page content. =head1 SYNOPSIS # start with a WeBWorK::Request object: $r use WeBWorK::ContentGenerator::SomeSubclass; my $cg = WeBWorK::ContentGenerator::SomeSubclass->new($r); my $result = $cg->go(); =head1 DESCRIPTION FIXME: write this =cut use strict; use warnings; use Apache::Constants qw(:common); use CGI qw(*ul *li); use URI::Escape; use WeBWorK::Authz; use WeBWorK::DB; use WeBWorK::Template qw(template); use WeBWorK::Utils qw(readFile); # This is a very unruly file, so I'm going to use very large comments to divide # it into logical sections. =head1 CONSTRUCTOR =over =item new($r) Create a new instance of a content generator. Supply a WeBWorK::Request object $r. =cut sub new { my ($invocant, $r) = @_; my $class = ref($invocant) || $invocant; my $self = { r => $r, # this is now a WeBWorK::Request ce => $r->ce(), # these three are here for db => $r->db(), # backward-compatability authz => $r->authz(), # with unconverted CGs noContent => undef, # this should get clobbered at some point }; bless $self, $class; return $self; } =back =cut ################################################################################ # Invocation and template processing ################################################################################ =head1 INVOCATION =over =item go() Render a page, using methods from the particular subclass of ContentGenerator. go() will call the following methods when invoked: =over =item pre_header_initialize() Give the subclass a chance to do initialization necessary before generating the HTTP header. =item header() This method provides a standard HTTP header with Content-Type text/html. Subclasses are welcome to override this for things like an image-creation content generator or a PDF generator. In addition, if header() returns a value, that will be the value returned by go(). =item initialize() Let the subclass do post-header initialization. If pre_header_initialize() or header() sets $self->{noContent} to a true value, initialize() will not be run and the content or template processing code will not be executed. This is probably only desirable if a redirect has been issued. =item template() The layout template is processed. See template() below. If the subclass implements a method named content(), it is called instead and no template processing occurs. =back =cut sub go { my $self = shift; my $r = $self->{r}; my $ce = $r->ce; my $returnValue = OK; $self->pre_header_initialize(@_) if $self->can("pre_header_initialize"); my $headerReturn = $self->header(@_); $returnValue = $headerReturn if defined $headerReturn; return $returnValue if $r->header_only or $self->{noContent}; # if the sendFile flag is set, send the file and exit; if ($self->{sendFile}) { return $self->sendFile; } $self->initialize(@_) if $self->can("initialize"); # A content generator will have a "content" method if it does not # wish to be passed through template processing, but wishes to be # completely responsible for it's own output. if ($self->can("content")) { $self->content(@_); } else { # if the content generator specifies a custom template name, use that # field in the $ce->{templates} hash instead of "system" if it exists. my $templateName; if ($self->can("templateName")) { $templateName = $self->templateName; } else { $templateName = "system"; } $templateName = "system" unless exists $ce->{templates}->{$templateName}; template($ce->{templates}->{$templateName}, $self); } return $returnValue; } =item sendFile() =cut sub sendFile { my ($self) = @_; my $file = $self->{sendFile}->{source}; return NOT_FOUND unless -e $file; return FORBIDDEN unless -r $file; open my $fh, "<", $file or return SERVER_ERROR; while (<$fh>) { print $_; } close $fh; return OK; } =back =cut ################################################################################ # Macros used by content generators to render common idioms ################################################################################ # FIXME: some of these should be moved to WeBWorK::HTML:: modules! =head1 HTML MACROS Macros used by content generators to render common idioms =over =item pathMacro($args, @path) Helper macro for escape: $args is a hash reference containing the "style", "image", "text", and "textonly" arguments to the escape. @path consists of ordered key-value pairs of the form: "Page Name" => URL If the page should not have a link associated with it, the URL should be left empty. Authentication data is added to the URL so you don't have to. A fully- formed path line is returned, suitable for returning by a function implementing the #path escape. =cut sub pathMacro { my $self = shift; my %args = %{ shift() }; my @path = @_; $args{style} = "text" if $args{textonly}; my $sep; if ($args{style} eq "image") { $sep = CGI::img({-src=>$args{image}, -alt=>$args{text}}); } else { $sep = $args{text}; } my $auth = $self->url_authen_args; my @result; while (@path) { my $name = shift @path; my $url = shift @path; if ($url and not $args{textonly}) { push @result, CGI::a({-href=>"$url?$auth"}, $name); } else { push @result, $name; } } return join($sep, @result), "\n"; } =item siblingsMacro(@siblings) =cut sub siblingsMacro { my $self = shift; my @siblings = @_; my $sep = CGI::br(); my $auth = $self->url_authen_args; my @result; while (@siblings) { my $name = shift @siblings; my $url = shift @siblings; push @result, $url ? CGI::a({-href=>"$url?$auth"}, $name) : $name; } return join($sep, @result) . "\n"; } =item navMacro($args, $tail) =cut sub navMacro { my $self = shift; my %args = %{ shift() }; my $tail = shift; my @links = @_; my $auth = $self->url_authen_args; my $ce = $self->{ce}; my $prefix = $ce->{webworkURLs}->{htdocs}."/images"; my @result; while (@links) { my $name = shift @links; my $url = shift @links; my $img = shift @links; my $html = ($img && $args{style} eq "images") ? CGI::img( {src=>($prefix."/".$img.$args{imagesuffix}), border=>"", alt=>"$name"}) : $name; unless($img && !$url) { push @result, $url ? CGI::a({-href=>"$url?$auth$tail"}, $html) : $html; } } return join($args{separator}, @result) . "\n"; } =item hidden_fields(@fields) Return hidden tags for each field mentioned in @fields (or all fields if list is empty), taking data from the current request. =cut sub hidden_fields($;@) { my $self = shift; my $r = $self->{r}; my @fields = @_; @fields or @fields = $r->param; my $courseEnvironment = $self->{ce}; my $html = ""; foreach my $param (@fields) { my $value = $r->param($param); $html .= CGI::input({-type=>"hidden",-name=>"$param",-value=>"$value"}); } return $html; } =item hidden_authen_fields() Use hidden_fields to return hidden tags for request fields used in authentication. =cut sub hidden_authen_fields($) { my $self = shift; return $self->hidden_fields("user","effectiveUser","key"); } =item url_args(@fields) Return a URL query string (without the leading `?') containing values for each field mentioned in @fields, or all fields if list is empty. Data is taken from the current request. =cut sub url_args($;@) { my $self = shift; my $r = $self->{r}; my @fields = @_; @fields or @fields = $r->param; # If no fields are passed in, do them all. my $courseEnvironment = $self->{ce}; my @pairs; foreach my $param (@fields) { my @values = $r->param($param); foreach my $value (@values) { push @pairs, uri_escape($param) . "=" . uri_escape($value); } } return join("&", @pairs); } =item url_authen_args() Use url_args to return a URL query string for request fields used in authentication. =cut sub url_authen_args($) { my $self = shift; my $r = $self->{r}; return $self->url_args("user","effectiveUser","key"); } =item nbsp($string) If string is the empty string, the HTML entity C<   > is returned. Otherwise the string is returned. =cut sub nbsp { my $self = shift; my $str = shift; ($str =~/\S/) ? $str : ' ' ; # returns non-breaking space for empty strings # tricky cases: $str =0; # $str is a complex number } =item print_form_data($begin, $middle, $end, $omit) Return a string containing request fields not matched by $omit, placing $begin before each field name, $middle between each field and its value, and $end after each value. Values are taken from the current request. $omit is a quoted reguar expression. =cut sub print_form_data { my ($self, $begin, $middle, $end, $qr_omit) = @_; my $return_string = ""; my $r=$self->{r}; my @form_data = $r->param; foreach my $name (@form_data) { next if ($qr_omit and $name =~ /$qr_omit/); my @values = $r->param($name); foreach my $variable (qw(begin name middle value end)) { no strict 'refs'; ${$variable} = "" unless defined ${$variable}; } foreach my $value (@values) { $return_string .= "$begin$name$middle$value$end"; } } return $return_string; } =item errorOutput($error, $details) =cut sub errorOutput($$$) { my ($self, $error, $details) = @_; return CGI::h3("Software Error"), CGI::p(< is true, the current real user ID is replaced with this value. =item sessionKey If C is true, the current session key is replaced with this value. =item effectiveUserID If C is true, the current effective user ID is replaced with this value. =back =cut sub systemLink { my ($self, $urlpath, %options) = @_; my $r = $self->{r}; my $authen = $options{authen} || 1; my $url = $r->location . $urlpath->path; if ($authen) { my $realUserID = $options{realUserID} || $r->param("user"); my $sessionKey = $options{sessionKey} || $r->param("key"); my $effectiveUserID = $options{effectiveUserID} || $r->param("effectiveUser"); my @params; defined $realUserID and push @params, "user=$realUserID"; defined $sessionKey and push @params, "key=$sessionKey"; defined $effectiveUserID and push @params, "effectiveUser=$effectiveUserID"; $url .= "?" . join("&", @params) if @params; } return $url; } =back =cut ################################################################################ # Generic versions of template escapes ################################################################################ =head1 THE HEADER METHOD =over =item header() The C
method is defined in WeBWorK::ContentGenerator to generate a default C of text/html and send the HTTP header. =back =cut sub header { my $self = shift; my $r = $self->{r}; if ($self->{sendFile}) { my $contentType = $self->{sendFile}->{type}; my $fileName = $self->{sendFile}->{name}; $r->content_type($contentType); $r->header_out("Content-Disposition" => "attachment; filename=\"$fileName\""); } else { $r->content_type("text/html"); } $r->send_http_header(); return OK; } =head1 TEMPLATE ESCAPE METHODS Template escape methods are invoked when a C< > construct is encountered in the template. If a method named C is defined in here or in a particular subclass, it is invoked. The following predicates are currently defined: =over =item if_can will return 1 if the current object->can("do $_[1]") =cut sub if_can ($$) { my ($self, $arg) = (@_); if ($self->can("$arg")) { return 1; } else { return 0; } } =item if_loggedin Every content generator is logged in unless it overrides this method to say otherwise. =cut sub if_loggedin($$) { my ($self, $arg) = (@_); return $arg; } =item if_submiterror =cut sub if_submiterror($$) { my ($self, $arg) = @_; if (exists $self->{submitError}) { return $arg; } else { return !$arg; } } =item if_warnings =cut sub if_warnings($$) { my ($self, $arg) = @_; return $self->{r}->notes("warnings") ? $arg : !$arg; } =back =cut 1; __END__ =head1 AUTHOR Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu and Sam Hathaway, sh002i (at) math.rochester.edu. =cut