From: Matt L. v. a. <we...@ma...> - 2007-08-29 04:38:06
|
Log Message: ----------- New version, see readme. Modified Files: -------------- ww_question_server: README ww_question_server/bin/setup: global.conf.base problemserver.apache-config.base setup.pl ww_question_server/lib: ProblemServer.pm ww_question_server/lib/ProblemServer: AnswerRequest.pm AnswerResponse.pm Environment.pm GeneratorResponse.pm ProblemRequest.pm ProblemResponse.pm Revision Data ------------- Index: README =================================================================== RCS file: /webwork/cvs/system/ww_question_server/README,v retrieving revision 1.5 retrieving revision 1.6 diff -LREADME -LREADME -u -r1.5 -r1.6 --- README +++ README @@ -1,31 +1,41 @@ -Webwork Problem Server ------------------------------------ +Webwork Question Server +---------------------- +Version: 0.2 (stable) +Maintainer: Matthew Leventi <mle...@gm...> +CVS: cvs.webwork.rochester.edu:/webwork/cvs/system ww_question_server + +** If your using this send me an email ** + +Note: You don't need to install the WeBWorK system for this server to function. All you need are the PG libraries. + +Whats new: +* Simple install with script. +* Code refactoring +* One translator per child, (speed boost) +* Fixed WSDL errors +* Minor bug fixes + +Prerequisites: +* Pod::WSDL CPAN Module +* Apache 1 or 2 +* mod_perl +* Apache::SOAP CPAN Module for apache 1, or Apache2::SOAP CPAN Module for apache2 +* latex and dvipng installed +* WeBWorK PG libraries installed -1) Setup - a) Change permissions for directories writable by server +Setup: + 1) Change permissions for directories writable by server chmod -R 777 htdocs/tmp chmod -R 777 tmp + 2) Run the setup program in bin/setup + perl setup.pl + Write down the WSDL path that the setup program gives you. It is what you use in the Moodle - WeBWorK Question Type. + 3) Copy the following files to the following places + bin/setup/WSDL.wsdl -> htdocs/WSDL.wsdl + bin/setup/global.conf -> conf/global.conf + bin/setup/problemserver.apache-config -> conf/problemserver.apache-config + 4) Include problemserver.apache-config in your apache configuration file. + Note: The problemserver.apache-config works for both apache 1 and 2. + ex) Include /home/you/problemserver/conf/problemserver.apache-config + 6) Restart Apache - -2) Configuration - The main changes in configuration are in the top of global.conf and in problemserver.apache2-config - In problemserver.apache2-config: - $problemserver_dir = The root directory where the problemserver is installed. - $pg_dir = The root directory where the pg is installed. - The Alias command should be changed to point to the root directory of problemserver + "/htdocs" - ex) Alias /problemserver_files /home/you/problemserver/htdocs - The Directory line should be changed to be the same as the second part of the Alias command above - ex) <Directory /home/you/problemserver/htdocs> - In global.conf - $problemServer{host} = The host the problem server is installed on - ex) "http://www.example.org"; - $pg_dir = Same as the $pg_dir for problemserver.apache2-config - $problemServerDirs{root}= Same as the $problemserver_dir for problemserver.apache2-config - - Note: Make sure the paths to the external programs are set correctly for your system. - Optionally: On line 163 you can add more paths to look for macro files. - - Finally you need to add an Include statement to your apache2.conf file. This should be located in /etc/apache2/apache2.conf under most systems. - ex) Include /home/you/problemserver/conf/problemserver.apache2-config - -3) Restart Apache2 Index: global.conf.base =================================================================== RCS file: /webwork/cvs/system/ww_question_server/bin/setup/global.conf.base,v retrieving revision 1.1 retrieving revision 1.2 diff -Lbin/setup/global.conf.base -Lbin/setup/global.conf.base -u -r1.1 -r1.2 --- bin/setup/global.conf.base +++ bin/setup/global.conf.base @@ -31,10 +31,6 @@ ################################################################################ -# The root URL (usually /webwork2), set by <Location> in Apache configuration. -$problemServerURLs{root} = "$problemServer{host}/problemserver"; - - # Contains non-web-accessible temporary files, such as TeX working directories. $problemServerDirs{tmp} = "$problemServerDirs{root}/tmp"; @@ -66,29 +62,8 @@ # equation rendering/hardcopy utiltiies -$externalPrograms{latex} = "/usr/bin/latex"; -$externalPrograms{pdflatex} = "/usr/bin/pdflatex --shell-escape"; -$externalPrograms{dvipng} = "/usr/bin/dvipng"; -$externalPrograms{tth} = "/usr/bin/tth"; - -# NetPBM - basic image manipulation utilities -# Most sites only need to configure $netpbm_prefix. -my $netpbm_prefix = "/usr/bin"; -$externalPrograms{giftopnm} = "$netpbm_prefix/giftopnm"; -$externalPrograms{ppmtopgm} = "$netpbm_prefix/ppmtopgm"; -$externalPrograms{pnmtops} = "$netpbm_prefix/pnmtops"; -$externalPrograms{pnmtopng} = "$netpbm_prefix/pnmtopng"; -$externalPrograms{pngtopnm} = "$netpbm_prefix/pngtopnm"; - -# url checker -$externalPrograms{checkurl} = "/usr/local/bin/lwp-request -mHEAD "; # or "/usr/local/bin/w3c -head " - -# image conversions utiltiies -# the source file is given on stdin, and the output expected on stdout. -$externalPrograms{gif2eps} = "$externalPrograms{giftopnm} | $externalPrograms{ppmtopgm} | $externalPrograms{pnmtops} -noturn 2>/dev/null"; -$externalPrograms{png2eps} = "$externalPrograms{pngtopnm} | $externalPrograms{ppmtopgm} | $externalPrograms{pnmtops} -noturn 2>/dev/null"; -$externalPrograms{gif2png} = "$externalPrograms{giftopnm} | $externalPrograms{pnmtopng}"; - +$externalPrograms{latex} = "MARKER_FOR_LATEX"; +$externalPrograms{dvipng} = "MARKER_FOR_DVIPNG"; ################################################################################ Index: problemserver.apache-config.base =================================================================== RCS file: /webwork/cvs/system/ww_question_server/bin/setup/problemserver.apache-config.base,v retrieving revision 1.1 retrieving revision 1.2 diff -Lbin/setup/problemserver.apache-config.base -Lbin/setup/problemserver.apache-config.base -u -r1.1 -r1.2 --- bin/setup/problemserver.apache-config.base +++ bin/setup/problemserver.apache-config.base @@ -30,6 +30,7 @@ #Load the Module push @PerlModule, 'ProblemServer'; +$ProblemServer::Host = $hostname; $ProblemServer::RootDir = $root_dir; $ProblemServer::RootPGDir = $root_pg_dir; $ProblemServer::RPCURL = $rpc_url; Index: setup.pl =================================================================== RCS file: /webwork/cvs/system/ww_question_server/bin/setup/setup.pl,v retrieving revision 1.1 retrieving revision 1.2 diff -Lbin/setup/setup.pl -Lbin/setup/setup.pl -u -r1.1 -r1.2 --- bin/setup/setup.pl +++ bin/setup/setup.pl @@ -7,24 +7,28 @@ print "Continue? (y,n):"; $input = <STDIN>; chop $input; -if($input eq "n") { - exit; -} +if($input eq "n") {exit;} -$filename = "problemserver.apache-config"; +#APACHE 1 OR 2 print "Will you be using Apache 1 or 2\n"; print "(1,2)>"; $apache = <STDIN>; chop $apache; if($apache eq "1") { - $apache = "Apache::SOAP"; + $apachecpan = "Apache::SOAP"; } elsif ($apache eq "2") { - $apache = "Apache2::SOAP"; + $apachecpan = "Apache2::SOAP"; +} else { + exit; } + +#HOSTNAME +$hostnameExample = "http://www.example.com/"; + print "Please enter the http hostname of the computer.\n"; -print "This should be a value like 'http://www.example.com'\n"; +print "This should be a value like '$hostnameExample'\n"; print ">"; $hostname = <STDIN>; chop $hostname; @@ -41,35 +45,31 @@ $pg = <STDIN>; chop $pg; -print "Do you want to configure optional components.\n"; -print "(y,n)>"; -$input = <STDIN>; -chop $input; -if($input eq "y") { - - print "Please enter a rpc URL path. Leave blank for default.\n"; - print "Default: '/problemserver_rpc'\n"; - print ">"; - $rpc = <STDIN>; - chop $rpc; - if($rpc eq "") { - $rpc = "/problemserver_rpc"; - } - - print "Please enter a URL path where equation files will be stored. Leave blank for default.\n"; - print "Default: '/problemserver_files'\n"; - print ">"; - $files = <STDIN>; - chop $files; - if($files eq "") { - $files = "/problemserver_files"; - } -} else { - $rpc = "/problemserver_rpc"; - $files = "/problemserver_files"; +print "Please enter the path to 'latex' command. Leave blank for default. \n"; +print "Default '/usr/bin/latex'\n"; +print ">"; +$latex = <STDIN>; +chop $latex; +if($latex eq "") { + $latex = "/usr/bin/latex"; +} + +print "Please enter the path to 'dvipng' command. Leave blank for default. \n"; +print "Default '/usr/bin/dvipng'\n"; +print ">"; +$dvipng = <STDIN>; +chop $dvipng; +if($dvipng eq "") { + $dvipng = "/usr/bin/dvipng"; } + + +$rpc = "/problemserver_rpc"; +$files = "/problemserver_files"; + + #WSDL FILE CREATION print "Creating WSDL File...\n"; eval "use lib '$root/lib'"; die "Your root directory is wrong." if $@; @@ -80,8 +80,8 @@ pretty => 1, withDocumentation => 0 ); + $wsdlfilename = "WSDL.wsdl"; -$filename = "problemserver.apache-config"; open(OUTP, ">$wsdlfilename") or die("Cannot open file '$wsdlfilename' for writing.\n"); print OUTP $pod->WSDL; close OUTP; @@ -90,6 +90,8 @@ #APACHE CONFIGURATION FILE CREATION print "Creating Apache Configuration File...\n"; +$conffilename = "problemserver.apache-config"; + print " Setting Variables...\n"; $additionalconf = "my \$hostname = '$hostname';\n"; $additionalconf .= "my \$root_dir = '$root';\n"; @@ -109,10 +111,10 @@ } close INPUT; $content =~ s/MARKER_FOR_CONF/$additionalconf/; -$content =~ s/MARKER_FOR_APACHE/$apache/; +$content =~ s/MARKER_FOR_APACHE/$apachecpan/; print " Writing...\n"; -open(OUTP2, ">$filename") or die("Cannot open file '$filename' for writing.\n"); +open(OUTP2, ">$conffilename") or die("Cannot open file '$conffilename' for writing.\n"); print OUTP2 $content; close OUTP2; print "Done\n"; @@ -129,17 +131,14 @@ $content .= $line; } close INPUT2; +$content =~ s/MARKER_FOR_DVIPNG/$dvipng/; +$content =~ s/MARKER_FOR_LATEX/$latex/; print " Writing...\n"; open(OUTP3, ">global.conf") or die("Cannot open file 'global.conf' for writing.\n"); print OUTP3 $content; close OUTP3; print "Done\n"; +print "Your WSDL path: '" . $hostname . $files . '/'.$wsdlfilename."'\n"; + #POST CONFIGURATION -print "\n\n\n"; -print "####POST SETUP#####\n"; -print "You need the '$apache' CPAN Module installed.\n"; -print "You need to move 'global.conf' into the conf/ directory.\n"; -print "You need to move '$filename' into the conf/ directory.\n"; -print "You need to include '$filename' in the apache configuration file.\n"; -print "You need to move '$wsdlfilename' into the htdocs/ directory. \n"; Index: ProblemServer.pm =================================================================== RCS file: /webwork/cvs/system/ww_question_server/lib/ProblemServer.pm,v retrieving revision 1.8 retrieving revision 1.9 diff -Llib/ProblemServer.pm -Llib/ProblemServer.pm -u -r1.8 -r1.9 --- lib/ProblemServer.pm +++ lib/ProblemServer.pm @@ -15,6 +15,7 @@ use ProblemServer::ProblemRequest; use ProblemServer::ProblemResponse; +use ProblemServer::GeneratorRequest; use ProblemServer::GeneratorResponse; use WeBWorK::PG::Translator; @@ -39,8 +40,12 @@ $self = {}; #Base Conf $main::VERSION = "2.3.2"; + + #Warnings are passed into self + + #Construct the Server Environment - my $serverEnviron = new ProblemServer::Environment($ProblemServer::RootDir); + my $serverEnviron = new ProblemServer::Environment(); #Keep the Default Server Environment @@ -48,6 +53,7 @@ #Keep the Default Problem Environment $self->{problemEnviron} = ($self->{serverEnviron}{problemEnviron}); + #Create Safe Compartment $self->{safe} = new Safe; @@ -55,18 +61,8 @@ return $self; } -BEGIN { -$ProblemServer::theServer = new ProblemServer(); -} - -sub translation { - my ($self,$request) = @_; - - #Install a local warn handler to collect warnings - my $warnings = ""; - local $SIG{__WARN__} = sub { $warnings .= shift } - if $self->{serverEnviron}{pg}{options}{catchWarnings}; - +sub setupTranslator { + my $self = shift; #Create Translator Object my $translator = WeBWorK::PG::Translator->new; @@ -88,469 +84,130 @@ $translator->load_extra_packages(@extra_packages); } - #DEFINE SPECIFIC CHANGES TO PROBLEM ENVIRONMENT - $self->{problemEnviron}{problemSeed} = $request->{seed}; - $self->{problemEnviron}{displayMode} = translateDisplayModeNames($request->{displayMode}); + #Only type of output is images + $self->{problemEnviron}{displayMode} = "HTML_dpng"; $self->{problemEnviron}{languageMode} = $self->{problemEnviron}{displayMode}; $self->{problemEnviron}{outputMode} = $self->{problemEnviron}{displayMode}; - #PREP IMAGE GENERATOR - my $image_generator; - if ($request->{displayMode} eq "images") { - my %imagesModeOptions = %{$self->{serverEnviron}->{pg}{displayModeOptions}{images}}; - $image_generator = WeBWorK::PG::ImageGenerator->new( - tempDir => $self->{serverEnviron}->{problemServerDirs}->{tmp}, # global temp dir - latex => $self->{serverEnviron}->{externalPrograms}->{latex}, - dvipng => $self->{serverEnviron}->{externalPrograms}->{dvipng}, - useCache => 1, - cacheDir => $self->{serverEnviron}->{problemServerDirs}{equationCache}, - cacheURL => $self->{serverEnviron}->{problemServerURLs}{equationCache}, - cacheDB => $self->{serverEnviron}->{problemServerFiles}{equationCacheDB}, - useMarkers => ($imagesModeOptions{dvipng_align} && $imagesModeOptions{dvipng_align} eq 'mysql'), - dvipng_align => $imagesModeOptions{dvipng_align}, - dvipng_depth_db => $imagesModeOptions{dvipng_depth_db}, - ); - #DEFINE CLOSURE CLASS FOR IMAGE GENERATOR - $self->{problemEnviron}{imagegen} = new ProblemServer::Utils::RestrictedClosureClass($image_generator, "add"); - } - - - - #ATTACH THE PROBLEM ENVIRONMENT TO TRANSLATOR - $translator->environment($self->{problemEnviron}); - - #INITIALIZING THE TRANSLATOR - $translator->initialize(); - - #PRE-LOAD MACRO FILES - eval{$translator->pre_load_macro_files( - $self->{safe}, - $self->{serverEnviron}->{pg}->{directories}->{macros}, - 'PG.pl', 'dangerousMacros.pl','IO.pl','PGbasicmacros.pl','PGanswermacros.pl' - )}; - warn "Error while preloading macro files: $@" if $@; - - #LOAD MACROS INTO TRANSLATOR - foreach (qw(PG.pl dangerousMacros.pl IO.pl)) { - my $macroPath = $self->{serverEnviron}->{pg}->{directories}->{macros} . "/$_"; - my $err = $translator->unrestricted_load($macroPath); - warn "Error while loading $macroPath: $err" if $err; - } - - #SET OPCODE MASK - $translator->set_mask(); - - #Retrieve Source - my $source = decode_base64($request->{code}); - #INSERT PROBLEM SOURCE CODE INTO TRANSLATOR - eval { $translator->source_string( $source ) }; - $@ and die("bad source"); - - #CREATE SAFETY FILTER - $translator->rf_safety_filter(\&ProblemServer::nullSafetyFilter); - - #RUN - $translator->translate(); - - #PROCESS ANSWERS - my ($result, $state); # we'll need these on the other side of the if block! - my $answerArray = $request->{answers}; - #die($self->{problemEnviron}->{functAbsTolDefault}); - #die($request->{answer}); - if (defined $answerArray and @{$answerArray}) { - # process student answers - #warn "PG: processing student answers\n"; - - #take array of answers and make hash - my $answerHash = {}; - for(my $i=0;$i<@{$answerArray};$i++) { - $answerHash->{$answerArray->[$i]{field}} = $answerArray->[$i]{answer}; - } - $translator->process_answers($answerHash); - - # 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 => "0", - num_of_correct_ans => "0", - num_of_incorrect_ans => "0", - }); - - # 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} || $self->{serverEnviron}->{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 => 1, - ANSWER_ENTRY_ORDER => \@answerOrder, - %{$answerHash} #FIXME? this is used by sequentialGrader is there a better way? - ); - - - } - my $displayMode = $request->{displayMode}; - - #ANSWER EVALUATION AND PREVIEW GENERATOR - my $answers = $translator->rh_evaluated_answers; - my $key; - my $preview; - my $answerResponse = {}; - my @answersArray; - - foreach $key (keys %{$answers}) { - #PREVIEW GENERATOR - $preview = $answers->{"$key"}->{"preview_latex_string"}; - $preview = "" unless defined $preview and $preview ne ""; - if ($displayMode eq "plainText") { - $preview = $preview; - } elsif ($displayMode eq "formattedText") { - #FIX THIS TO USE TTH - $preview = $preview; - } elsif ($displayMode eq "images") { - $preview = $image_generator->add($preview); - } elsif ($displayMode eq "jsMath") { - $preview =~ s/</</g; - $preview =~ s/>/>/g; - $preview = '<SPAN CLASS="math">\\displaystyle{'.$preview.'}</SPAN>'; - } - - #ANSWER STRUCT - $answerResponse = new ProblemServer::AnswerResponse; - $answerResponse->{field} = $key; - $answerResponse->{answer} = $answers->{"$key"}->{"original_student_ans"}; - $answerResponse->{answer_msg} = $answers->{"$key"}->{"ans_message"}; - $answerResponse->{correct} = $answers->{"$key"}->{"correct_ans"}; - $answerResponse->{score} = $answers->{"$key"}->{"score"}; - $answerResponse->{evaluated} = $answers->{"$key"}->{"student_ans"}; - $answerResponse->{preview} = encode_base64($preview); - push(@answersArray, $answerResponse); - } - - #GENERATE IMAGES AS NECESSARY - if ($image_generator) { - $image_generator->render(refresh => 1); - $image_generator->render( - body_text => $translator->r_text - ); - } - - - - - #WeBWorK::PG::Translator::pretty_print_rh($answers->{"0AnSwEr1"}); - #die($warnings); - - #CREATE A RESPONSE OBJECT - my $response = new ProblemServer::ProblemResponse; - $response->{id} = $request->{id}; - $response->{errors} = $translator->errors; - $response->{warnings} = $warnings; - $response->{answers} = \@answersArray; - $response->{seed} = $request->{seed}; - $response->{body_text} = encode_base64(${$translator->r_text}); - $response->{head_text} = encode_base64(${$translator->r_header}); - $response->{state} = "0"; - $response->{result} = $request->{displayMode}; - - return $response; + $self->{translator} = $translator; } -sub generator { - my ($self,$code,$seed,$trials) = @_; - - my @derivedProblems; - my @alreadyCreated; - - #Create Translator Object - my $translator = WeBWorK::PG::Translator->new; - - #Attach log modules - my @modules = @{ $self->{serverEnviron}{pg}{modules} }; - # HACK for apache2 - if (MP2) { - push @modules, ["Apache2::Log"], ["APR::Table"]; - } else { - push @modules, ["Apache::Log"]; - } - - #Evaulate all module packs - 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); - } - - #DEFINE SPECIFIC CHANGES TO PROBLEM ENVIRONMENT - $self->{problemEnviron}{displayMode} = translateDisplayModeNames("images"); - $self->{problemEnviron}{languageMode} = $self->{problemEnviron}{displayMode}; - $self->{problemEnviron}{outputMode} = $self->{problemEnviron}{displayMode}; +sub setupImageGenerator { + my $self = shift; - #PREP IMAGE GENERATOR my $image_generator; - if ("images" eq "images") { - my %imagesModeOptions = %{$self->{serverEnviron}->{pg}{displayModeOptions}{images}}; - $image_generator = WeBWorK::PG::ImageGenerator->new( - tempDir => $self->{serverEnviron}->{problemServerDirs}->{tmp}, # global temp dir - latex => $self->{serverEnviron}->{externalPrograms}->{latex}, - dvipng => $self->{serverEnviron}->{externalPrograms}->{dvipng}, - useCache => 1, - cacheDir => $self->{serverEnviron}->{problemServerDirs}{equationCache}, - cacheURL => $self->{serverEnviron}->{problemServerURLs}{equationCache}, - cacheDB => $self->{serverEnviron}->{problemServerFiles}{equationCacheDB}, - useMarkers => ($imagesModeOptions{dvipng_align} && $imagesModeOptions{dvipng_align} eq 'mysql'), - dvipng_align => $imagesModeOptions{dvipng_align}, - dvipng_depth_db => $imagesModeOptions{dvipng_depth_db}, - ); - #DEFINE CLOSURE CLASS FOR IMAGE GENERATOR - $self->{problemEnviron}{imagegen} = new ProblemServer::Utils::RestrictedClosureClass($image_generator, "add"); - } - - my $itr; - for($itr = $seed;$itr < $trials + $seed;$itr++) { - - #NEW SEED TEST - $self->{problemEnviron}{problemSeed} = $itr; + my %imagesModeOptions = %{$self->{serverEnviron}->{pg}{displayModeOptions}{images}}; + $image_generator = WeBWorK::PG::ImageGenerator->new( + tempDir => $self->{serverEnviron}->{problemServerDirs}->{tmp}, # global temp dir + latex => $self->{serverEnviron}->{externalPrograms}->{latex}, + dvipng => $self->{serverEnviron}->{externalPrograms}->{dvipng}, + useCache => 1, + cacheDir => $self->{serverEnviron}->{problemServerDirs}{equationCache}, + cacheURL => $self->{serverEnviron}->{problemServerURLs}{equationCache}, + cacheDB => $self->{serverEnviron}->{problemServerFiles}{equationCacheDB}, + useMarkers => ($imagesModeOptions{dvipng_align} && $imagesModeOptions{dvipng_align} eq 'mysql'), + dvipng_align => $imagesModeOptions{dvipng_align}, + dvipng_depth_db => $imagesModeOptions{dvipng_depth_db}, + ); + #DEFINE CLOSURE CLASS FOR IMAGE GENERATOR + $self->{problemEnviron}{imagegen} = new ProblemServer::Utils::RestrictedClosureClass($image_generator, "add"); - #ATTACH THE PROBLEM ENVIRONMENT TO TRANSLATOR - $translator->environment($self->{problemEnviron}); - - #INITIALIZING THE TRANSLATOR - $translator->initialize(); - - #PRE-LOAD MACRO FILES - eval{$translator->pre_load_macro_files( - $self->{safe}, - $self->{serverEnviron}->{pg}->{directories}->{macros}, - 'PG.pl', 'dangerousMacros.pl','IO.pl','PGbasicmacros.pl','PGanswermacros.pl' - )}; - warn "Error while preloading macro files: $@" if $@; - - #LOAD MACROS INTO TRANSLATOR - foreach (qw(PG.pl dangerousMacros.pl IO.pl)) { - my $macroPath = $self->{serverEnviron}->{pg}->{directories}->{macros} . "/$_"; - my $err = $translator->unrestricted_load($macroPath); - warn "Error while loading $macroPath: $err" if $err; - } - - #SET OPCODE MASK - $translator->set_mask(); - - #Retrieve Source - my $source = decode_base64($code); - #INSERT PROBLEM SOURCE CODE INTO TRANSLATOR - eval { $translator->source_string( $source ) }; - $@ and die("bad source"); - - #CREATE SAFETY FILTER - $translator->rf_safety_filter(\&ProblemServer::nullSafetyFilter); - - #RUN - $translator->translate(); - - #ANSWERS - #take array of answers and make hash - my $answerHash = {}; - $translator->process_answers($answerHash); - - my $answers = $translator->rh_evaluated_answers; - - #CREATE STRING REP - my $unique = ""; - my $key; - #foreach $key (keys %{$answers}) { - # $unique = $unique . "$key" . $answers->{"$key"}->{"correct_ans"}; - #} - $unique = encode_base64(${$translator->r_text}); - #IS IT UNIQUE - my $found = 0; - foreach(@alreadyCreated) { - if($_ eq $unique) { - $found = 1; - } - } - - #ADD HTML IF IT ISNT - if($found == 0) { - #GENERATE IMAGES AS NECESSARY - if ($image_generator) { - $image_generator->render( - body_text => $translator->r_text - ); - } - my $response = new ProblemServer::GeneratorResponse; - $response->{html} = encode_base64(${$translator->r_text}); - $response->{seed} = $itr; - #NEW PROBLEM... PLACE INTO ARRAY - push(@derivedProblems,$response); - push(@alreadyCreated,$unique); - } - #push(@derivedProblems,encode_base64(${$translator->r_text})); - - } - return \@derivedProblems; + $self->{imageGenerator} = $image_generator; } -sub checker{ - my ($self,$code,$seed,$answersEntry) = @_; - - #Create Translator Object - my $translator = WeBWorK::PG::Translator->new; - - #Attach log modules - my @modules = @{ $self->{serverEnviron}{pg}{modules} }; - # HACK for apache2 - if (MP2) { - push @modules, ["Apache2::Log"], ["APR::Table"]; - } else { - push @modules, ["Apache::Log"]; - } - - #Evaulate all module packs - 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); - } +BEGIN { + $ProblemServer::theServer = new ProblemServer(); + $ProblemServer::theServer->setupTranslator(); + $ProblemServer::theServer->setupImageGenerator(); +} - #DEFINE SPECIFIC CHANGES TO PROBLEM ENVIRONMENT - $self->{problemEnviron}{displayMode} = translateDisplayModeNames("images"); - $self->{problemEnviron}{languageMode} = $self->{problemEnviron}{displayMode}; - $self->{problemEnviron}{outputMode} = $self->{problemEnviron}{displayMode}; - $self->{problemEnviron}{problemSeed} = $seed; +sub runTranslator { + my ($self,$source,$seed) = @_; - #PREP IMAGE GENERATOR - my $image_generator; - if ("images" eq "images") { - my %imagesModeOptions = %{$self->{serverEnviron}->{pg}{displayModeOptions}{images}}; - $image_generator = WeBWorK::PG::ImageGenerator->new( - tempDir => $self->{serverEnviron}->{problemServerDirs}->{tmp}, # global temp dir - latex => $self->{serverEnviron}->{externalPrograms}->{latex}, - dvipng => $self->{serverEnviron}->{externalPrograms}->{dvipng}, - useCache => 1, - cacheDir => $self->{serverEnviron}->{problemServerDirs}{equationCache}, - cacheURL => $self->{serverEnviron}->{problemServerURLs}{equationCache}, - cacheDB => $self->{serverEnviron}->{problemServerFiles}{equationCacheDB}, - useMarkers => ($imagesModeOptions{dvipng_align} && $imagesModeOptions{dvipng_align} eq 'mysql'), - dvipng_align => $imagesModeOptions{dvipng_align}, - dvipng_depth_db => $imagesModeOptions{dvipng_depth_db}, - ); - #DEFINE CLOSURE CLASS FOR IMAGE GENERATOR - $self->{problemEnviron}{imagegen} = new ProblemServer::Utils::RestrictedClosureClass($image_generator, "add"); - } + $source = decode_base64($source); + #Assigning Seed + $self->{problemEnviron}{problemSeed} = $seed; - #ATTACH THE PROBLEM ENVIRONMENT TO TRANSLATOR - $translator->environment($self->{problemEnviron}); + #Setting Environment + $self->{translator}->environment($self->{problemEnviron}); - #INITIALIZING THE TRANSLATOR - $translator->initialize(); + #Initializing + $self->{translator}->initialize(); #PRE-LOAD MACRO FILES - eval{$translator->pre_load_macro_files( + eval{$self->{translator}->pre_load_macro_files( $self->{safe}, $self->{serverEnviron}->{pg}->{directories}->{macros}, 'PG.pl', 'dangerousMacros.pl','IO.pl','PGbasicmacros.pl','PGanswermacros.pl' )}; - warn "Error while preloading macro files: $@" if $@; #LOAD MACROS INTO TRANSLATOR foreach (qw(PG.pl dangerousMacros.pl IO.pl)) { my $macroPath = $self->{serverEnviron}->{pg}->{directories}->{macros} . "/$_"; - my $err = $translator->unrestricted_load($macroPath); + my $err = $self->{translator}->unrestricted_load($macroPath); warn "Error while loading $macroPath: $err" if $err; } #SET OPCODE MASK - $translator->set_mask(); + $self->{translator}->set_mask(); - #Retrieve Source - my $source = decode_base64($code); #INSERT PROBLEM SOURCE CODE INTO TRANSLATOR - eval { $translator->source_string( $source ) }; + eval { $self->{translator}->source_string( $source ) }; $@ and die("bad source"); #CREATE SAFETY FILTER - $translator->rf_safety_filter(\&ProblemServer::nullSafetyFilter); + $self->{translator}->rf_safety_filter(\&ProblemServer::nullSafetyFilter); #RUN - $translator->translate(); - - #PROCESS ANSWERS - my ($result, $state); # we'll need these on the other side of the if block! - my $answerArray = $answersEntry; - - if (defined $answerArray and @{$answerArray}) { - # process student answers - #warn "PG: processing student answers\n"; - - #take array of answers and make hash - my $answerHash = {}; - for(my $i=0;$i<@{$answerArray};$i++) { - $answerHash->{$answerArray->[$i]{field}} = $answerArray->[$i]{answer}; - } - $translator->process_answers($answerHash); - - # 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 => "0", - num_of_correct_ans => "0", - num_of_incorrect_ans => "0", - }); - - # 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} || $self->{serverEnviron}->{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 => 1, - ANSWER_ENTRY_ORDER => \@answerOrder, - %{$answerHash} #FIXME? this is used by sequentialGrader is there a better way? - ); + $self->{translator}->translate(); +} +sub runChecker { + my $self = shift; + my $answerArray = shift; - } - my $displayMode = "images"; + my $answerHash = {}; + for(my $i=0;$i<@{$answerArray};$i++) { + $answerHash->{$answerArray->[$i]{field}} = $answerArray->[$i]{answer}; + } + $self->{translator}->process_answers($answerHash); + + # retrieve the problem state and give it to the translator + #warn "PG: retrieving the problem state and giving it to the translator\n"; + $self->{translator}->rh_problem_state({ + recorded_score => "0", + num_of_correct_ans => "0", + num_of_incorrect_ans => "0", + }); + + # 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 = $self->{translator}->rh_flags->{ANSWER_ENTRY_ORDER} + ? @{ $self->{translator}->rh_flags->{ANSWER_ENTRY_ORDER} } + : keys %{ $self->{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 = $self->{translator}->rh_flags->{PROBLEM_GRADER_TO_USE} || $self->{serverEnviron}->{pg}->{options}->{grader}; + $grader = $self->{translator}->rf_std_problem_grader if $grader eq "std_problem_grader"; + $grader = $self->{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"; + $self->{translator}->rf_problem_grader($grader); + + # grade the problem + #warn "PG: grading the problem\n"; + my ($result, $state) = $self->{translator}->grade_problem( + answers_submitted => 1, + ANSWER_ENTRY_ORDER => \@answerOrder, + %{$answerHash} #FIXME? this is used by sequentialGrader is there a better way? + ); - #ANSWER EVALUATION AND PREVIEW GENERATOR - my $answers = $translator->rh_evaluated_answers; + my $answers = $self->{translator}->rh_evaluated_answers; my $key; my $preview; my $answerResponse = {}; @@ -560,18 +217,8 @@ #PREVIEW GENERATOR $preview = $answers->{"$key"}->{"preview_latex_string"}; $preview = "" unless defined $preview and $preview ne ""; - if ($displayMode eq "plainText") { - $preview = $preview; - } elsif ($displayMode eq "formattedText") { - #FIX THIS TO USE TTH - $preview = $preview; - } elsif ($displayMode eq "images") { - $preview = $image_generator->add($preview); - } elsif ($displayMode eq "jsMath") { - $preview =~ s/</</g; - $preview =~ s/>/>/g; - $preview = '<SPAN CLASS="math">\\displaystyle{'.$preview.'}</SPAN>'; - } + + $preview = $self->{imageGenerator}->add($preview); #ANSWER STRUCT $answerResponse = new ProblemServer::AnswerResponse; @@ -585,18 +232,34 @@ push(@answersArray, $answerResponse); } - #GENERATE IMAGES AS NECESSARY - if ($image_generator) { - $image_generator->render(refresh => 1); - } return \@answersArray; } -sub translateDisplayModeNames($) { - my $name = shift; - return DISPLAY_MODES()->{$name}; +sub runImageGenerator { + my $self = shift; + $self->{imageGenerator}->render(body_text => $self->{translator}->r_text); +} + +sub runImageGeneratorAnswers { + my $self = shift; + $self->{imageGenerator}->render(); +} + +sub buildProblemResponse { + my $self = shift; + my $response = new ProblemServer::ProblemResponse; + $response->{errors} = $self->{translator}->errors; + $response->{warnings} = $self->{warnings}; + $response->{output} = encode_base64(${$self->{translator}->r_text}); + $response->{seed} = $self->{translator}->{envir}{problemSeed}; + return $response; } +sub clean { + my $self = shift; + $self->{translator}->{errors} = undef; + +} sub nullSafetyFilter { return shift, 0; # no errors } @@ -605,6 +268,8 @@ #SOAP CALLABLE FUNCTIONS #################################################################################### + + =pod =begin WSDL _RETURN $string Hello World! @@ -613,45 +278,141 @@ return "hello world!"; } + =pod =begin WSDL -_IN request $ProblemServer::ProblemRequest -_RETURN $ProblemServer::ProblemResponse +_IN request $ProblemServer::ProblemRequest The Problem Request +_RETURN $ProblemServer::ProblemResponse The Problem Response =end WSDL =cut sub renderProblem { my ($self,$request) = @_; my $server = $ProblemServer::theServer; - return ProblemServer::translation($server,$request); + + $server->runTranslator($request->{code},$request->{seed}); + $server->runImageGenerator(); + + my $response = $server->buildProblemResponse(); + $server->clean(); + return $response; } =pod =begin WSDL -_IN code $string -_IN seed $string -_IN trials $string -_RETURN @string +_IN requests @ProblemServer::ProblemRequest The Problem Requests +_RETURN @ProblemServer::ProblemResponse The Problem Response +=end WSDL +=cut +sub renderProblems { + my ($self,$requests) = @_; + my $server = $ProblemServer::theServer; + + my @problems; + + foreach($requests) { + my $request = $_; + $server->runTranslator($request->{code},$request->{seed}); + $server->runImageGenerator(); + my $response = $server->buildProblemResponse(); + push(@problems,$response); + $server->clean(); + } + + return \@problems; +} + +=pod +=begin WSDL +_IN request $ProblemServer::GeneratorRequest +_RETURN $ProblemServer::GeneratorResponse +=end WSDL +=cut +sub generateProblem { + my ($self,$request) = @_; + my $server = $ProblemServer::theServer; + + my $trials = $request->{trials}; + my $problem = $request->{problem}; + my @derivedProblems; + my $found; + my $problemResponse; + for(my $itr = 0; $itr < $trials ; $itr++ ) { + $found = 0; + $server->runTranslator($problem->{code},$itr + $problem->{seed}); + $problemResponse = $server->buildProblemResponse(); + foreach(@derivedProblems) { + if($_->{output} eq $problemResponse->{output}) { + $found = 1; + } + } + if($found == 0) { + $server->runImageGenerator(); + push(@derivedProblems,$problemResponse); + } + $server->clean(); + } + + return \@derivedProblems; +} + +=pod +=begin WSDL +_IN requests @ProblemServer::GeneratorRequest +_RETURN @ProblemServer::GeneratorResponse =end WSDL =cut sub generateProblems { - my ($self,$code,$seed,$trials) = @_; + my ($self,$requests) = @_; my $server = $ProblemServer::theServer; - return ProblemServer::generator($server,$code,$seed,$trials); + + my @genResponse; + foreach($requests) { + my $request = $_; + my $trials = $request->{trials}; + my $problem = $request->{problem}; + my @derivedProblems; + my $found; + for(my $itr = 0; $itr < $trials ; $itr++ ) { + $found = 0; + $server->runTranslator($problem->{code},$trials + $problem->{seed}); + my $problemResponse = $server->buildProblemResponse(); + foreach(@derivedProblems->{output}) { + if($_ eq $problemResponse->{output}) { + $found = 1; + } + } + if($found != 1) { + $server->runImageGenerator(); + push(@derivedProblems,$problemResponse); + } + $server->clean(); + } + my $response = new ProblemServer::GeneratorResponse(); + $response->{problems} = @derivedProblems; + push(@genResponse,$response); + } + + return \@genResponse; } =pod =begin WSDL -_IN code $string -_IN seed $string +_IN request $ProblemServer::ProblemRequest _IN answers @ProblemServer::AnswerRequest _RETURN @ProblemServer::AnswerResponse =end WSDL =cut sub checkAnswers { - my ($self,$code,$seed,$answers) = @_; + my ($self,$request,$answers) = @_; my $server = $ProblemServer::theServer; - return ProblemServer::checker($server,$code,$seed,$answers); + + $server->runTranslator($request->{code},$request->{seed}); + my $result = $server->runChecker($answers); + $server->runImageGeneratorAnswers(); + + $server->clean(); + return $result; } 1; Index: GeneratorResponse.pm =================================================================== RCS file: /webwork/cvs/system/ww_question_server/lib/ProblemServer/GeneratorResponse.pm,v retrieving revision 1.2 retrieving revision 1.3 diff -Llib/ProblemServer/GeneratorResponse.pm -Llib/ProblemServer/GeneratorResponse.pm -u -r1.2 -r1.3 --- lib/ProblemServer/GeneratorResponse.pm +++ lib/ProblemServer/GeneratorResponse.pm @@ -2,16 +2,13 @@ =pod =begin WSDL - _ATTR html $string - _ATTR seed $string + _ATTR problems @ProblemServer::ProblemResponse An array of problems that were generated. =end WSDL =cut sub new { my $self = shift; - my $data = shift; $self = {}; - $self->{html} = $data->{html}; - $self->{seed} = $data->{seed}; + bless $self; return $self; } Index: AnswerResponse.pm =================================================================== RCS file: /webwork/cvs/system/ww_question_server/lib/ProblemServer/AnswerResponse.pm,v retrieving revision 1.2 retrieving revision 1.3 diff -Llib/ProblemServer/AnswerResponse.pm -Llib/ProblemServer/AnswerResponse.pm -u -r1.2 -r1.3 --- lib/ProblemServer/AnswerResponse.pm +++ lib/ProblemServer/AnswerResponse.pm @@ -2,18 +2,19 @@ =pod =begin WSDL - _ATTR field $string - _ATTR score $string - _ATTR answer $string - _ATTR answer_msg $string - _ATTR correct $string - _ATTR evaluated $string - _ATTR preview $string + _ATTR field $string The answer field + _ATTR score $string The score + _ATTR answer $string The students answer + _ATTR answer_msg $string A message about the students answer + _ATTR correct $string The correct answer + _ATTR evaluated $string The evaluated answer + _ATTR preview $string A link to the students answer image =end WSDL =cut sub new { my $self = shift; my $data = shift; + $self = {}; $self->{field} = $data->{field}; $self->{answer} = $data->{answer}; @@ -22,6 +23,7 @@ $self->{score} = $data->{score}; $self->{evaluated} = $data->{evaluated}; $self->{preview} = $data->{preview}; + bless $self; return $self; } Index: AnswerRequest.pm =================================================================== RCS file: /webwork/cvs/system/ww_question_server/lib/ProblemServer/AnswerRequest.pm,v retrieving revision 1.2 retrieving revision 1.3 diff -Llib/ProblemServer/AnswerRequest.pm -Llib/ProblemServer/AnswerRequest.pm -u -r1.2 -r1.3 --- lib/ProblemServer/AnswerRequest.pm +++ lib/ProblemServer/AnswerRequest.pm @@ -2,15 +2,15 @@ =pod =begin WSDL - _ATTR field $string - _ATTR answer $string + _ATTR field $string The Answer field + _ATTR answer $string The Answer fields value =end WSDL =cut sub new { my $self = shift; my $data = shift; $self = {}; - $self->{field} = $data->{field}; + $self->{field} = $data->{field}; $self->{answer} = $data->{answer}; bless $self; return $self; Index: ProblemResponse.pm =================================================================== RCS file: /webwork/cvs/system/ww_question_server/lib/ProblemServer/ProblemResponse.pm,v retrieving revision 1.2 retrieving revision 1.3 diff -Llib/ProblemServer/ProblemResponse.pm -Llib/ProblemServer/ProblemResponse.pm -u -r1.2 -r1.3 --- lib/ProblemServer/ProblemResponse.pm +++ lib/ProblemServer/ProblemResponse.pm @@ -2,32 +2,20 @@ =pod =begin WSDL - _ATTR id $string - _ATTR head_text $string - _ATTR body_text $string - _ATTR seed $string - _ATTR answers @ProblemServer::AnswerResponse - _ATTR warnings $string - _ATTR errors $string - _ATTR flags $string - _ATTR result $string - _ATTR state $string + _ATTR output $string The HTML output of the question + _ATTR warnings $string Any warnings from translation + _ATTR errors $string Any errors from translation + _ATTR seed $string The seed of the question. =end WSDL =cut sub new { my $self; my $data; $self = {}; - $self->{id} = "0"; - $self->{head_text} = "Default Header"; - $self->{body_text} = "Default Body"; - $self->{seed} = "0"; - $self->{answers} = {}; + $self->{output} = ""; $self->{warnings} = ""; $self->{errors} = ""; - $self->{flags} = ""; - $self->{result} = ""; - $self->{state} = ""; + $self->{seed} = ""; bless $self; return $self; } Index: ProblemRequest.pm =================================================================== RCS file: /webwork/cvs/system/ww_question_server/lib/ProblemServer/ProblemRequest.pm,v retrieving revision 1.2 retrieving revision 1.3 diff -Llib/ProblemServer/ProblemRequest.pm -Llib/ProblemServer/ProblemRequest.pm -u -r1.2 -r1.3 --- lib/ProblemServer/ProblemRequest.pm +++ lib/ProblemServer/ProblemRequest.pm @@ -3,23 +3,16 @@ =pod =begin WSDL - _ATTR id $string - _ATTR code $string - _ATTR seed $string - _ATTR displayMode $string - _ATTR answers @ProblemServer::AnswerRequest + _ATTR code $string The PG code to be translated + _ATTR seed $string The seed to be used for randomization =end WSDL =cut sub new { my $self = shift; my $data = shift; $self = {}; - $self->{id} = $data->{id}; $self->{code} = $data->{code}; $self->{seed} = $data->{seed}; - $self->{displayMode} = $data->{displayMode}; - $self->{answers} = $data->{answers}; - bless $self; return $self; } Index: Environment.pm =================================================================== RCS file: /webwork/cvs/system/ww_question_server/lib/ProblemServer/Environment.pm,v retrieving revision 1.4 retrieving revision 1.5 diff -Llib/ProblemServer/Environment.pm -Llib/ProblemServer/Environment.pm -u -r1.4 -r1.5 --- lib/ProblemServer/Environment.pm +++ lib/ProblemServer/Environment.pm @@ -8,13 +8,13 @@ BEGIN { $main::VERSION = "2.3.2"; } sub new { - my ($self,$path) = @_; + my ($self) = @_; my $safe = Safe->new; # Compile the "include" function with all opcodes available. my $include = q[ sub include { my ($file) = @_; - my $fullPath = "].$path.q[/$file"; + my $fullPath = "].$ProblemServer::RootDir.q[/$file"; # This regex matches any string that begins with "../", # ends with "/..", contains "/../", or is "..". if ($fullPath =~ m!(?:^|/)\.\.(?:/|$)!) { @@ -40,9 +40,15 @@ #die $preps; #$safe->reval($preps); - my $globalEnvironmentFile = "$path/conf/global.conf"; + my $globalEnvironmentFile = $ProblemServer::RootDir . "/conf/global.conf"; my $globalFileContents = readFile($globalEnvironmentFile); + my $settings = "\$ProblemServer::RootDir = '" . $ProblemServer::RootDir . "';\n"; + $settings .= "\$ProblemServer::RootPGDir = '" . $ProblemServer::RootPGDir . "';\n"; + $settings .= "\$ProblemServer::Host = '" . $ProblemServer::Host . "';\n"; + $settings .= "\$ProblemServer::FilesURL = '" . $ProblemServer::FilesURL . "';\n"; + $safe->reval($settings); + #die $globalFileContents; #$globalFileContents = $preps.'\n'.$globalFileContents; $safe->reval($globalFileContents); |