--- trunk/webwork2/lib/WeBWorK/PG.pm 2002/08/21 15:34:35 492
+++ trunk/webwork2/lib/WeBWorK/PG.pm 2004/01/05 01:02:41 1703
@@ -1,202 +1,49 @@
################################################################################
-# WeBWorK mod_perl (c) 1995-2002 WeBWorK Team, Univeristy of Rochester
-# $Id$
+# WeBWorK Online Homework Delivery System
+# Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
+# $CVSHeader: webwork-modperl/lib/WeBWorK/PG.pm,v 1.46 2003/12/09 01:12:30 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::PG;
=head1 NAME
-WeBWorK::PG - Wrap the action of the PG Translator in an easy-to-use API.
+WeBWorK::PG - Invoke one of several PG rendering methods using an easy-to-use
+API.
=cut
use strict;
use warnings;
-use WeBWorK::DB::Classlist;
-use WeBWorK::DB::WW;
-use WeBWorK::PG::Translator;
-use WeBWorK::Problem;
-use WeBWorK::Utils qw(readFile formatDateTime);
-
-sub new($$$$$$$$) {
- my $invocant = shift;
- my $class = ref($invocant) || $invocant;
- my (
- $courseEnv,
- $userName,
- $key,
- $setName,
- $problemNumber,
- $translationOptions, # hashref containing options for the
- # translator, such as whether to show
- # hints and the display mode to use
- $formFields, # in CGI::Vars format
- ) = @_;
-
- # get database information
- my $classlist = WeBWorK::DB::Classlist->new($courseEnv);
- my $wwdb = WeBWorK::DB::WW->new($courseEnv);
- my $user = $classlist->getUser($userName);
- my $set = $wwdb->getSet($userName, $setName);
- my $psvn = $wwdb->getPSVN($userName, $setName);
-
- my $problem;
- if ($problemNumber =~ /^\d+$/) {
- $problem = $wwdb->getProblem($userName, $setName, $problemNumber);
- } else {
- # This is the fun part: if $problemNumber is NON-NUMERIC, the
- # user wants to specify a PG file directly. We manufacture a
- # Problem object using fake data and the specified source file.
- # This is potentially dangerous since an untrusted user is
- # allowed to specifiy an arbitrary file to be evaluated as PG.
- # A user of PG.pm MUST MAKE SURE that if $problemNumber is
- # supplied by an untrusted source (i.e. the Apache request),
- # it is numberic. A simple
- #
- # die unless $problemNumber =~ /^\d+$/;
- #
- # should suffice.
- $problem = WeBWorK::Problem->new(
- id => 0,
- set_id => $set->id,
- login_id => $user->id,
- source_file => $problemNumber,
- # the rest of Problem's fields are not needed
- );
- }
-
- # create a Translator
- warn "PG: creating a Translator\n";
- my $translator = WeBWorK::PG::Translator->new;
-
- # set the directory hash
- warn "PG: setting the directory hash\n";
- $translator->rh_directories({
- courseScriptsDirectory => $courseEnv->{webworkDirs}->{macros},
- macroDirectory => $courseEnv->{courseDirs}->{macros},
- templateDirectory => $courseEnv->{courseDirs}->{templates},
- tempDirectory => $courseEnv->{courseDirs}->{html_temp},
- });
-
- # evaluate modules and "extra packages"
- warn "PG: evaluating modules and \"extra packages\"\n";
- my @modules = @{ $courseEnv->{pg}->{modules} };
- foreach my $module_packages_ref (@modules) {
- my ($module, @extra_packages) = @$module_packages_ref;
- # the first item is the main package
- $translator->evaluate_modules($module);
- # the remaining items are "extra" packages
- $translator->load_extra_packages(@extra_packages);
- }
+use WeBWorK::PG::ImageGenerator;
+use WeBWorK::Utils qw(runtime_use formatDateTime makeTempDirectory);
+
+sub new {
+ shift; # throw away invocant -- we don't need it
+ my ($ce, $user, $key, $set, $problem, $psvn, $formFields,
+ $translationOptions) = @_;
- # set the environment (from defineProblemEnvir)
- warn "PG: setting the environment (from defineProblemEnvir)\n";
- $translator->environment(defineProblemEnvir(
- $courseEnv, $user, $key, $set, $problem, $psvn, $formFields, $translationOptions));
-
- # initialize the Translator
- warn "PG: initializing the Translator\n";
- $translator->initialize();
-
- # load PG.pl and dangerousMacros.pl using unrestricted_load
- # i'd like to change this at some point to have the same sort of interface to global.conf
- # that the module loading does -- have a list of macros to load unrestrictedly.
- warn "PG: loading PG.pl and dangerousMacros.pl using unrestricted_load\n";
- my $pg_pl = $courseEnv->{webworkDirs}->{macros} . "/PG.pl";
- my $dangerousMacros_pl = $courseEnv->{webworkDirs}->{macros} . "/dangerousMacros.pl";
- my $err = $translator->unrestricted_load($pg_pl);
- warn "Error while loading $pg_pl: $err" if $err;
- $err = $translator->unrestricted_load($dangerousMacros_pl);
- warn "Error while loading $dangerousMacros_pl: $err" if $err;
-
- # set the opcode mask (using default values)
- warn "PG: setting the opcode mask (using default values)\n";
- $translator->set_mask();
-
- # store the problem source
- warn "PG: storing the problem source\n";
- my $sourceFile = $problem->source_file;
- $sourceFile = $courseEnv->{courseDirs}->{templates}."/".$sourceFile
- unless ($sourceFile =~ /^\//);
- $translator->source_string(readFile($sourceFile));
-
- # install a safety filter (&safetyFilter)
- warn "PG: installing a safety filter\n";
- $translator->rf_safety_filter(\&safetyFilter);
-
- # translate the PG source into text
- warn "PG: translating the PG source into text\n";
- $translator->translate();
+ my $renderer = $ce->{pg}->{renderer};
- my ($result, $state); # we'll need these on the other side of the if block!
- if ($translationOptions->{processAnswers}) {
-
- # process student answers
- warn "PG: processing student answers\n";
- $translator->process_answers($formFields);
-
- # retrieve the problem state and give it to the translator
- warn "PG: retrieving the problem state and giving it to the translator\n";
- $translator->rh_problem_state({
- recorded_score => $problem->status,
- num_of_correct_ans => $problem->num_correct,
- num_of_incorrect_ans => $problem->num_incorrect,
- });
-
- # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by
- # the PG macro package (PG.pl)
- warn "PG: determining an entry order\n";
- my @answerOrder =
- $translator->rh_flags->{ANSWER_ENTRY_ORDER}
- ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} }
- : keys %{ $translator->rh_evaluated_answers };
-
- # install a grader -- use the one specified in the problem,
- # or fall back on the default from the course environment.
- # (two magic strings are accepted, to avoid having to
- # reference code when it would be difficult.)
- warn "PG: installing a grader\n";
- my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE}
- || $courseEnv->{pg}->{options}->{grader};
- $grader = $translator->rf_std_problem_grader
- if $grader eq "std_problem_grader";
- $grader = $translator->rf_avg_problem_grader
- if $grader eq "avg_problem_grader";
- die "Problem grader $grader is not a CODE reference."
- unless ref $grader eq "CODE";
- $translator->rf_problem_grader($grader);
-
- # grade the problem
- warn "PG: grading the problem\n";
- ($result, $state) = $translator->grade_problem(
- answers_submitted => $translationOptions->{processAnswers},
- ANSWER_ENTRY_ORDER => \@answerOrder,
- );
-
- }
+ runtime_use $renderer;
- # return an object which contains the translator and the results of
- # the translation process. this is DIFFERENT from the "format expected
- # by Webwork.pm (and I believe processProblem8, but check.)"
- return bless {
- translator => $translator,
- head_text => ${ $translator->r_header },
- body_text => ${ $translator->r_text },
- answers => $translator->rh_evaluated_answers,
- result => $result,
- state => $state,
- errors => $translator->errors, # *** what is this doing?
- warnings => undef, # *** gotta catch warnings eventually...
- flags => $translator->rh_flags,
- }, $class;
+ return $renderer->new(@_);
}
-# -----
-
-sub defineProblemEnvir($$$$$$$) {
+sub defineProblemEnvir {
my (
- $courseEnv,
+ $self,
+ $ce,
$user,
$key,
$set,
@@ -208,40 +55,44 @@
my %envir;
+ # ----------------------------------------------------------------------
+
# PG environment variables
# from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002
# any changes are noted by "ADDED:" or "REMOVED:"
# Vital state information
- # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img
+ # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img,
+ # texDisposition
- $envir{psvn} = $psvn;
- $envir{psvnNumber} = $envir{psvn};
- $envir{probNum} = $problem->id;
- $envir{questionNumber} = $envir{probNum};
+ $envir{psvn} = $set->psvn;
+ $envir{psvnNumber} = $envir{psvn};
+ $envir{probNum} = $problem->problem_id;
+ $envir{questionNumber} = $envir{probNum};
$envir{fileName} = $problem->source_file;
$envir{probFileName} = $envir{fileName};
- $envir{problemSeed} = $problem->problem_seed;
+ $envir{problemSeed} = $problem->problem_seed;
$envir{displayMode} = translateDisplayModeNames($options->{displayMode});
$envir{languageMode} = $envir{displayMode};
$envir{outputMode} = $envir{displayMode};
- $envir{displayHintsQ} = $options->{hints};
- $envir{displaySolutionsQ} = $options->{solutions};
- $envir{refreshMath2img} = $options->{refreshMath2img};
+ $envir{displayHintsQ} = $options->{showHints};
+ $envir{displaySolutionsQ} = $options->{showSolutions};
+ $envir{texDisposition} = "pdf"; # in webwork2, we use pdflatex
# Problem Information
- # ADDED: courseName
+ # ADDED: courseName, formatedDueDate
$envir{openDate} = $set->open_date;
$envir{formattedOpenDate} = formatDateTime($envir{openDate});
$envir{dueDate} = $set->due_date;
$envir{formattedDueDate} = formatDateTime($envir{dueDate});
+ $envir{formatedDueDate} = $envir{formattedDueDate}; # typo in many header files
$envir{answerDate} = $set->answer_date;
$envir{formattedAnswerDate} = formatDateTime($envir{answerDate});
- $envir{numOfAttempts} = $problem->num_correct + $problem->num_incorrect;
+ $envir{numOfAttempts} = ($problem->num_correct || 0) + ($problem->num_incorrect || 0);
$envir{problemValue} = $problem->value;
$envir{sessionKey} = $key;
- $envir{courseName} = $courseEnv->{courseName};
+ $envir{courseName} = $ce->{courseName};
# Student Information
# ADDED: studentID
@@ -250,8 +101,8 @@
$envir{sectionNumber} = $envir{sectionName};
$envir{recitationName} = $user->recitation;
$envir{recitationNumber} = $envir{recitationName};
- $envir{setNumber} = $set->id;
- $envir{studentLogin} = $user->id;
+ $envir{setNumber} = $set->set_id;
+ $envir{studentLogin} = $user->user_id;
$envir{studentName} = $user->first_name . " " . $user->last_name;
$envir{studentID} = $user->student_id;
@@ -261,34 +112,70 @@
$envir{inputs_ref} = $formFields;
# External Programs
+ # ADDED: externalLaTeXPath, externalDvipngPath,
+ # externalGif2EpsPath, externalPng2EpsPath
- $envir{externalTTHPath} = $courseEnv->{externalPrograms}->{tth};
- $envir{externalMath2imgPath} = $courseEnv->{externalPrograms}->{math2img};
+ $envir{externalTTHPath} = $ce->{externalPrograms}->{tth};
+ $envir{externalLaTeXPath} = $ce->{externalPrograms}->{latex};
+ $envir{externalDvipngPath} = $ce->{externalPrograms}->{dvipng};
+ $envir{externalGif2EpsPath} = $ce->{externalPrograms}->{gif2eps};
+ $envir{externalPng2EpsPath} = $ce->{externalPrograms}->{png2eps};
+ $envir{externalGif2PngPath} = $ce->{externalPrograms}->{gif2png};
# Directories and URLs
# REMOVED: courseName
+ # ADDED: dvipngTempDir
$envir{cgiDirectory} = undef;
$envir{cgiURL} = undef;
$envir{classDirectory} = undef;
- $envir{courseScriptsDirectory} = $courseEnv->{webworkDirs}->{macros}."/";
- $envir{htmlDirectory} = $courseEnv->{courseDirs}->{html}."/";
- $envir{htmlURL} = $courseEnv->{courseURLs}->{html};
- $envir{macroDirectory} = $courseEnv->{courseDirs}->{macros}."/";
- $envir{templateDirectory} = $courseEnv->{courseDirs}->{templates}."/";
- $envir{tempDirectory} = $courseEnv->{courseDirs}->{html_temp}."/";
- $envir{tempURL} = $courseEnv->{courseURLs}->{html_temp};
+ $envir{courseScriptsDirectory} = $ce->{pg}->{directories}->{macros}."/";
+ $envir{htmlDirectory} = $ce->{courseDirs}->{html}."/";
+ $envir{htmlURL} = $ce->{courseURLs}->{html}."/";
+ $envir{macroDirectory} = $ce->{courseDirs}->{macros}."/";
+ $envir{templateDirectory} = $ce->{courseDirs}->{templates}."/";
+ $envir{tempDirectory} = $ce->{courseDirs}->{html_temp}."/";
+ $envir{tempURL} = $ce->{courseURLs}->{html_temp}."/";
$envir{scriptDirectory} = undef;
- $envir{webworkDocsURL} = $courseEnv->{webworkURLs}->{docs};
+ $envir{webworkDocsURL} = $ce->{webworkURLs}->{docs}."/";
+
+ # Information for sending mail
+
+ $envir{mailSmtpServer} = $ce->{mail}->{smtpServer};
+ $envir{mailSmtpSender} = $ce->{mail}->{smtpSender};
+ $envir{ALLOW_MAIL_TO} = $ce->{mail}->{allowedRecipients};
# Default values for evaluating answers
- my $ansEvalDefaults = $courseEnv->{pg}->{ansEvalDefaults};
+ my $ansEvalDefaults = $ce->{pg}->{ansEvalDefaults};
$envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults);
- # Other things...
+ # ----------------------------------------------------------------------
- $envir{PROBLEM_GRADER_TO_USE} = $courseEnv->{pg}->{options}->{grader};
+ my $basename = "equation-$envir{psvn}.$envir{probNum}";
+ $basename .= ".$envir{problemSeed}" if $envir{problemSeed};
+
+ # Object for generating equation images
+ $envir{imagegen} = WeBWorK::PG::ImageGenerator->new(
+ tempDir => $ce->{webworkDirs}->{tmp}, # global temp dir
+ latex => $envir{externalLaTeXPath},
+ dvipng => $envir{externalDvipngPath},
+ useCache => 1,
+ cacheDir => $ce->{webworkDirs}->{equationCache},
+ cacheURL => $ce->{webworkURLs}->{equationCache},
+ cacheDB => $ce->{webworkFiles}->{equationCacheDB},
+ );
+
+ # Other things...
+ $envir{QUIZ_PREFIX} = $options->{QUIZ_PREFIX}; # used by quizzes
+ $envir{PROBLEM_GRADER_TO_USE} = $ce->{pg}->{options}->{grader};
+ $envir{PRINT_FILE_NAMES_FOR} = $ce->{pg}->{specialPGEnvironmentVars}->{PRINT_FILE_NAMES_FOR};
+
+ # variables for interpreting capa problems.
+ $envir{CAPA_Tools} = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_Tools};
+ $envir{CAPA_MCTools} = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_MCTools};
+ $envir{CAPA_Graphics_URL} = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_Graphics_URL};
+ $envir{CAPA_GraphicsDirectory} = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_GraphicsDirectory};
return \%envir;
}
@@ -299,11 +186,11 @@
tex => "TeX",
plainText => "HTML",
formattedText => "HTML_tth",
- images => "HTML_img"
+ images => "HTML_dpng", # "HTML_img",
}->{$name};
}
-sub safetyFilter {
+sub oldSafetyFilter {
my $answer = shift; # accepts one answer and checks it
my $submittedAnswer = $answer;
$answer = '' unless defined $answer;
@@ -317,8 +204,8 @@
}
# replace ^ with ** (for exponentiation)
# $answer =~ s/\^/**/g;
- # Return if forbidden characters are found
- unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ ) {
+ # Return if forbidden characters are found
+ unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\[\]\(\)\,\|]+$/ ) {
$answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
$errorno = "
There are forbidden characters in your answer: $submittedAnswer
";
return ($answer,$errorno);
@@ -327,6 +214,10 @@
return($answer, $errorno);
}
+sub nullSafetyFilter {
+ return shift, 0; # no errors
+}
+
1;
__END__
@@ -334,11 +225,13 @@
=head1 SYNOPSIS
$pg = WeBWorK::PG->new(
- $courseEnv, # a WeBWorK::CourseEnvironment object
- $userName,
+ $ce, # a WeBWorK::CourseEnvironment object
+ $user, # a WeBWorK::DB::Record::User object
$sessionKey,
- $setName,
- $problemNumber,
+ $set, # a WeBWorK::DB::Record::UserSet object
+ $problem, # a WeBWorK::DB::Record::UserProblem object
+ $psvn,
+ $formFields # in &WeBWorK::Form::Vars format
{ # translation options
displayMode => "images", # (plainText|formattedText|images)
showHints => 1, # (0|1)
@@ -346,7 +239,6 @@
refreshMath2img => 0, # (0|1)
processAnswers => 1, # (0|1)
},
- $formFields # in WeBWorK::Form::Vars format
);
$translator = $pg->{translator}; # WeBWorK::PG::Translator
@@ -361,15 +253,18 @@
=head1 DESCRIPTION
-WeBWorK::PG encapsulates the PG translation process, making multiple calls to
-WeBWorK::PG::Translator. Much of the flexibility of the Translator is hidden,
-instead making choices that are appropriate for the webwork-modperl system.
+WeBWorK::PG is a factory for modules which use the WeBWorK::PG API. Notable
+modules which use this API (and exist) are WeBWorK::PG::Local and
+WeBWorK::PG::Remote. The course environment key $pg{renderer} is consulted to
+determine which render to use.
+
+=head1 THE WEBWORK::PG API
-=head1 CONSTRUCTION
+Modules which support this API must implement the following method:
=over
-=item new (ENVIRONMENT, USER, KEY, SET, PROBLEM, OPTIONS, FIELDS)
+=item new ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS
The C method creates a translator, initializes it using the parameters
specified, translates a PG file, and processes answers. It returns a reference
@@ -387,7 +282,7 @@
=item USER
-the name of the user for whom to render
+a WeBWorK::User object
=item KEY
@@ -395,11 +290,24 @@
=item SET
-the name of the problem set from which to get the problem
+a WeBWorK::Set object
=item PROBLEM
-the number of the problem to render
+a WeBWorK::DB::Record::UserProblem object. The contents of the source_file
+field can specify a PG file either by absolute path or path relative to the
+"templates" directory. I
+
+=item PSVN
+
+the problem set version number
+
+=item FIELDS
+
+a reference to a hash (as returned by &WeBWorK::Form::Vars) containing form
+fields submitted by a problem processor. The translator will look for fields
+like "AnSwEr[0-9]" containing submitted student answers.
=item OPTIONS
@@ -422,7 +330,7 @@
=item refreshMath2img
boolean, force images created by math2img (in "images" mode) to be recreated,
-even if the PG source has not been updated.
+even if the PG source has not been updated. FIXME: remove this option.
=item processAnswers
@@ -430,12 +338,6 @@
=back
-=item FIELDS
-
-a reference to a hash (as returned by &WeBWorK::Form::Vars) containing form
-fields submitted by a problem processor. The translator will look for fields
-like "AnSwEr[0-9]" containing submitted student answers.
-
=back
=head2 RETURN VALUE
@@ -486,84 +388,19 @@
=back
-=head1 OPERATION
+=head1 METHODS PROVIDED BY THE BASE CLASS
-WeBWorK::PG goes through the following operations when constructed:
+The following methods are provided for use by subclasses of WeBWorK::PG.
=over
-=item Get database information
-
-Retrieve information about the current user, set, and problem from the
-database.
-
-=item Create a translator
-
-Instantiate a WeBWorK::PG::Translator object.
-
-=item Set the directory hash
-
-Set the translator's directory hash (courseScripts, macros, templates, and temp
-directories) from the course environment.
-
-=item Evaluate PG modules
-
-Using the module list from the course environment (pg->modules), perform a
-"use"-like operation to evaluate modules at runtime.
-
-=item Set the problem environment
-
-Use data from the user, set, and problem, as well as the course environemnt and
-translation options, to set the problem environment.
-
-=item Initialize the translator
-
-Call &WeBWorK::PG::Translator::initialize. What more do you want?
-
-=item Load PG.pl and dangerousMacros.pl
-
-These macros must be loaded without opcode masking, so they are loaded here.
-
-=item Set the opcode mask
-
-Set the opcode mask to the default specified by WeBWorK::PG::Translator.
-
-=item Load the problem source
-
-Give the problem source to the translator.
-
-=item Install a safety filter
-
-The safety filter is used to preprocess student input before evaluation. The
-default safety filter, &WeBWorK::PG::safetyFilter, is used.
-
-=item Translate the problem source
-
-Call &WeBWorK::PG::Translator::translate to render the problem source into the
-format given by the display mode.
-
-=item Process student answers
-
-Use form field inputs to evaluate student answers.
-
-=item Load the problem state
-
-Use values from the database to initialize the problem state, so that the
-grader will have a point of reference.
-
-=item Determine an entry order
-
-Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the
-problem. This is important for problems with dependancies among parts.
-
-=item Install a grader
+=item defineProblemEnvir ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS
-Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment,
-to install a grader.
+Generate a problem environment hash to pass to the renderer.
-=item Grade the problem
+=item translateDisplayModeNames NAME
-Use the selected grader to grade the problem.
+NAME contains
=back