################################################################################ # WeBWorK Online Homework Delivery System # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator.pm,v 1.87 2004/03/15 23:03:46 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 WeBWorK::ContentGenerator provides the framework for generating page content. "Content generators" are subclasses of this class which provide content for particular parts of the system. Default versions of methods used by the templating system are provided. Several useful methods are provided for rendering common output idioms and some miscellaneous utilities are provided. =cut use strict; use warnings; use Apache::Constants qw(:common); use Carp; use CGI::Pretty qw(*ul *li); use URI::Escape; use WeBWorK::Template qw(template); ################################################################################ =head1 CONSTRUCTOR =over =item new($r) Creates 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 ################################################################################ =head1 INVOCATION =over =item go() Generates a page, using methods from the particular subclass of ContentGenerator that is instantiated. Generatoion is broken up into several steps, to give subclasses ample control over the process. =over =item 1 go() will attempt to call the method pre_header_initialize(). This method may be implemented in subclasses which must do processing before the HTTP header is emitted. =item 2 go() will attempt to call the method header(). This method emits the HTTP header. It is defined in this class (see below), but may be overridden in subclasses which need to send different header information. For some reason, the return value of header() will be used as the result of this function, if it is defined. =item 3 At this point, go() will terminate if the request is a HEAD request or if the field $self->{noContent} contains a true value. =item 4 If the field $self->{sendFile} is defined, the method sendFile() is called to send the specified file to the client, and go() terminates. See below. =item 5 go() then attempts to call the method initialize(). This method may be implemented in subclasses which must do processing after the HTTP header is sent but before any content is sent. =item 6 The method content() is called to send the page content to client. =back =cut sub go { my ($self) = @_; 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"); $self->content(); return $returnValue; } =item sendFile() Sends the file specified in $self->{sendFile} to the client. $self->{sendFile} should be a reference to a hash containing the following fields: source => full path to the file to send type => the content type of the file name => the name that the client should give to the file upon download This method is called internally by go() if the field $self->{sendFile} is present. This mechanism relies on the header() method to send appropriate C and C headers. This mechanism is fragile and will probably be replaced by something else in the future. =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; } =item r() Returns a reference to the WeBWorK::Request object associated with this instance. =cut sub r { my ($self) = @_; return $self->{r}; } =back =cut ################################################################################ =head1 STANDARD METHODS The following are the standard content generator methods. Some are defined here, but may be overridden in a subclass. Others are not defined unless they are defined in a subclass. =over =item pre_header_initialize() Not defined in this package. May be defined by a subclass to perform any processing that must occur before the HTTP header is sent. =cut #sub pre_header_initialize { } =item header() Defined in this package. Generates and sends a default HTTP header. If the field $self->{sendFile} is present, sends the following headers (where TYPE is $self->{sendFile}->{type} and NAME is $self->{sendFile}->{name}): Content-Type: TYPE Content-Disposition: attachment; filename=NAME If $self->{sendFile} is not present, sends the following headers: Content-Type: text/html See sendFile() above for more information on the sendFile mechanism. =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; } =item initialize() Not defined in this package. May be defined by a subclass to perform any processing that must occur after the HTTP header is sent but before any content is sent. =cut #sub initialize { } =item content() Defined in this package. Print the content of the generated page. The implementation in this package uses WeBWorK::Template to define the content of the page. See WeBWorK::Template for details. If a method named templateName() exists, it it called to determine the name of the template to use. If not, the default template, "system", is used. The location of the template is looked up in the course environment. =cut sub content { my ($self) = @_; my $ce = $self->r->ce; # 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); } =back =cut # ------------------------------------------------------------------------------ =head2 Template escape handlers Template escape handlers are invoked when the template processor encounters a matching escape sequence in the template. The escapse sequence's arguments are passed to the methods as a reference to a hash. For more information, refer to WeBWorK::Template. The following template escapes handlers are defined here or may be defined in subclasses. For methods that are not defined in this package, the documentation defines the interface and behavior that any subclass implementation must follow. =over =item head() Not defined in this package. Any tags that should appear in the HEAD of the document. =cut #sub head { } =item info() Not defined in this package. Auxiliary information related to the content displayed in the C. =cut #sub info { } =item links() Defined in this package. Links that should appear on every page. =cut sub links { my ($self) = @_; my $r = $self->r; my $db = $r->db; my $urlpath = $r->urlpath; # we're linking to other places in the same course, so grab the courseID from the current path my $courseID = $urlpath->arg("courseID"); # to make things more concise my %args = ( courseID => $courseID ); my $pfx = "WeBWorK::ContentGenerator::"; my $sets = $urlpath->newFromModule("${pfx}ProblemSets", %args); my $options = $urlpath->newFromModule("${pfx}Options", %args); my $grades = $urlpath->newFromModule("${pfx}Grades", %args); my $logout = $urlpath->newFromModule("${pfx}Logout", %args); print "\n\n"; print CGI::start_ul({class=>"LinksMenu"}); print CGI::li(CGI::span({style=>"font-size:larger"}, CGI::a({href=>$self->systemLink($sets)}, "Problem Sets"))); print CGI::li(CGI::a({href=>$self->systemLink($options)}, $options->name)); print CGI::li(CGI::a({href=>$self->systemLink($grades)}, $grades->name)); print CGI::li(CGI::a({href=>$self->systemLink($logout)}, $logout->name)); my $PermissionLevel = $db->getPermissionLevel($r->param("user")); # checked my $permLevel = $PermissionLevel ? $PermissionLevel->permission : 0; if ($permLevel > 0) { my $ipfx = "${pfx}Instructor::"; my $userID = $r->param("effectiveUser"); my $setID = $urlpath->arg("setID"); my $problemID = $urlpath->arg("problemID"); my $instr = $urlpath->newFromModule("${ipfx}Index", %args); my $userList = $urlpath->newFromModule("${ipfx}UserList", %args); # set list links my $setList = $urlpath->newFromModule("${ipfx}ProblemSetList", %args); my $setDetail = $urlpath->newFromModule("${ipfx}ProblemSetEditor", %args, setID => $setID); my $problemEditor = $urlpath->newFromModule("${ipfx}PGProblemEditor", %args, setID => $setID, problemID => $problemID); my $mail = $urlpath->newFromModule("${ipfx}SendMail", %args); my $scoring = $urlpath->newFromModule("${ipfx}Scoring", %args); # statistics links my $stats = $urlpath->newFromModule("${ipfx}Stats", %args); my $userStats = $urlpath->newFromModule("${ipfx}Stats", %args, statType => "student", userID => $userID); my $setStats = $urlpath->newFromModule("${ipfx}Stats", %args, statType => "set", setID => $setID); my $files = $urlpath->newFromModule("${ipfx}FileXfer", %args); print CGI::start_li(); print CGI::span({style=>"font-size:larger"}, CGI::a({href=>$self->systemLink($instr)}, $instr->name)); print CGI::start_ul(); print CGI::li(CGI::a({href=>$self->systemLink($userList)}, $userList->name)); print CGI::start_li(); print CGI::a({href=>$self->systemLink($setList)}, $setList->name); if (defined $setID and $setID ne "") { print CGI::start_ul(); print CGI::start_li(); print CGI::a({href=>$self->systemLink($setDetail)}, $setID); if (defined $problemID and $problemID ne "") { print CGI::ul( CGI::li(CGI::a({href=>$self->systemLink($problemEditor)}, $problemID)) ); } print CGI::end_li(); print CGI::end_ul(); } print CGI::end_li(); print CGI::li(CGI::a({href=>$self->systemLink($mail)}, $mail->name)); print CGI::li(CGI::a({href=>$self->systemLink($scoring)}, $scoring->name)); print CGI::start_li(); print CGI::a({href=>$self->systemLink($stats)}, $stats->name); if (defined $userID and $userID ne "") { print CGI::ul( CGI::li(CGI::a({href=>$self->systemLink($userStats)}, $userID)) ); } if (defined $setID and $setID ne "") { print CGI::ul( CGI::li(CGI::a({href=>$self->systemLink($setStats)}, $setID)) ); } print CGI::end_li(); print CGI::li(CGI::a({href=>$self->systemLink($files)}, $files->name)); print CGI::end_ul(); print CGI::end_li(); } print CGI::end_ul(); print "\n"; return ""; } =item loginstatus() Defined in this package. Print a notification message announcing the current real user and effective user, a link to stop acting as the effective user, and a link to logout. =cut sub loginstatus { my ($self) = @_; my $r = $self->r; my $urlpath = $r->urlpath; my $key = $r->param("key"); if ($key) { my $courseID = $urlpath->arg("courseID"); my $userID = $r->param("user"); my $eUserID = $r->param("effectiveUser"); my $stopActingURL = $self->systemLink($urlpath, # current path values => { effectiveUser => $userID, }, ); my $logoutURL = $self->systemLink($urlpath->newFromModule(__PACKAGE__ . "::Logout", courseID => $courseID)); print "\n\n"; print "Logged in as $userID. "; print CGI::a({href=>$logoutURL}, "Log Out"); if ($eUserID ne $userID) { print " | Acting as $eUserID. "; print CGI::a({href=>$stopActingURL}, "Stop Acting"); } print "\n"; } return ""; } =item nav($args) Not defined in this package. Links to the previous, next, and parent objects. $args is a reference to a hash containing the following fields: style => text|image imageprefix => prefix to prepend to base image URL imagesuffix => suffix to append to base image URL separator => HTML to place in between links If C