################################################################################
# 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