[system] / trunk / webwork / system / lib / PGtranslator.pm Repository:
ViewVC logotype

View of /trunk/webwork/system/lib/PGtranslator.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 107 - (download) (as text) (annotate)
Wed Aug 8 23:43:43 2001 UTC (11 years, 9 months ago) by gage
File size: 50608 byte(s)
Made small corrections at lines 417 and 484 which were
causing compile time errors in /ww/logs/error_log.  (One
was redefining rh_flags subroutine, the other was an
uncommented fragment of a print statement.

    1 #!/usr/local/bin/perl
    2 
    3 ################################################################################
    4 # WeBWorK
    5 #
    6 # Copyright (c) 1995-2001 University of Rochester
    7 # All rights reserved
    8 #
    9 # $Id$
   10 ################################################################################
   11 
   12 use lib '.'; use webworkInit; # WeBWorKInitLine
   13 
   14 
   15 use strict;
   16 #use sigtrap;
   17 
   18 
   19 # BEGIN {
   20 #   sub PG_floating_point_exception_handler {       # 1st argument is signal name
   21 #                          my($sig) = @_;
   22 #                          print "Content-type: text/html\n\n<H4>There was a floating point arithmetic error (exception SIG$sig )</H4>--perhaps you divided by zero or took the square root of a negative number?
   23 #                                  <BR>\n Use the back button to return to the previous page and recheck your entries.<BR>\n";
   24 #                          exit(0);
   25 #                      }
   26 #
   27 #   $SIG{'FPE'}  = \&PG_floating_point_exception_handler;
   28 #   sub PG_warnings_handler {
   29 #         $main::WARNINGS .="* " . join("\n",@_) . "<BR><BR>\n";
   30 #         $main::background_plain_url = $main::background_warn_url;
   31 #         $main::bg_color = 'FF9999';  #for warnings -- this change may come too late
   32 #   }
   33 #   $SIG{__WARN__}=\&PG_warnings_handler;
   34 # }
   35 
   36 use Net::SMTP;
   37 use Opcode;
   38 use Safe;
   39 #use CGI::Carp qw(fatalsToBrowser carp croak);
   40 
   41 #loading GD within the Safe compartment has occasionally caused infinite recursion
   42 # Putting these use statements here seems to avoid this problem
   43 # It is not clear that this is essential once things are working properly.
   44 
   45 use Exporter;
   46 use DynaLoader;
   47 #use GD;
   48 
   49 
   50 
   51 =head1 NAME
   52 
   53   PGtranslator.pm
   54 
   55 =head1 SYNPOSIS
   56 
   57 
   58 
   59     my $pt = new PGtranslator;      #create a translator;
   60     $pt->environment(\%envir);      # provide the environment variable for the problem
   61     $pt->initialize();              # initialize the translator
   62     $pt-> set_mask();               # set the operation mask for the translator safe compartment
   63     $pt->source_string($source);    # provide the source string for the problem
   64 
   65                                     # load the unprotected macro files
   66                                     # these files are evaluated with the Safe compartment wide open
   67                                     # other macros are loaded from within the problem using loadMacros
   68 
   69     $pt -> unrestricted_load("${courseScriptsDirectory}PG.pl");
   70     $pt -> unrestricted_load("${courseScriptsDirectory}dangerousMacros.pl");
   71 
   72     $pt ->translate();              # translate the problem (the out following 4 pieces of information are created)
   73     $PG_PROBLEM_TEXT_ARRAY_REF = $pt->ra_text();              # output text for the body of the HTML file (in array form)
   74     $PG_PROBLEM_TEXT_REF = $pt->r_text();                   # output text for the body of the HTML file
   75     $PG_HEADER_TEXT_REF = $pt->r_header;#\$PG_HEADER_TEXT;    # text for the header of the HTML file
   76     $PG_ANSWER_HASH_REF = $pt->rh_correct_answers;            # a hash of answer evaluators
   77     $PG_FLAGS_REF = $pt ->rh_flags;                           # misc. status flags.
   78 
   79     $pt -> process_answers(\%inputs);
   80                                     # evaluates all of the answers using submitted answers from %input
   81     my $rh_answer_results = $pt->rh_evaluated_answers;  # provides a hash of the results of evaluating the answers.
   82     my $rh_problem_result = $pt->grade_problem;         # grades the problem using the default problem grading method.
   83 
   84 
   85 =head1 DESCRIPTION
   86 
   87 This module defines an object which will translate a problem written in the Problem Generating (PG) language
   88 
   89 =cut
   90 
   91 
   92 
   93 
   94 
   95 package PGtranslator;
   96 
   97 
   98 =head2 be_strict
   99 
  100 This creates a substitute for C<use strict;> which cannot be used in PG problem
  101 sets or PG macro files.  Use this way to imitate the behavior of C<use strict;>
  102 
  103   BEGIN {
  104     be_strict(); # an alias for use strict.
  105                  # This means that all global variable
  106                  # must contain main:: as a prefix.
  107   }
  108 
  109 =cut
  110 
  111 BEGIN {
  112   sub be_strict {   # allows the use of strict within macro packages.
  113     require 'strict.pm'; strict::import();
  114   }
  115 }
  116 
  117 
  118 
  119 =head2 evaluate_modules
  120 
  121   Useage:  $obj -> evaluate_modules('WWPlot', 'Fun', 'Circle');
  122            $obj -> evaluate_modules('reset');
  123 
  124 Adds the modules WWPlot.pm, Fun.pm and Circle.pm in the courseScripts directory to the list of modules
  125 which can be used by the PG problems.  The keyword 'reset' or 'erase' erases the list of modules already loaded
  126 
  127 =cut
  128 
  129 my @class_modules = ();
  130 sub evaluate_modules{
  131   my $self = shift;
  132   my @modules = @_;
  133   # temporary  -
  134   # We  need a method for setting the course directory without calling Global.
  135   my $courseScriptsDirectory = $Global::courseScriptsDirectory;
  136   my $save_SIG_die_trap = $SIG{__DIE__};
  137     $SIG{__DIE__} = sub {CORE::die(@_) };
  138   while (@modules) {
  139     my $module_name = shift @modules;
  140     $module_name =~ s/\.pm$//;   # remove trailing .pm if someone forgot
  141     if ($module_name eq 'reset'  or $module_name eq 'erase' ) {
  142       @class_modules = ();
  143         next;
  144     }
  145     if ( -r  "${courseScriptsDirectory}${module_name}.pm"   ) {
  146       eval(qq! require "${courseScriptsDirectory}${module_name}.pm";  import ${module_name};! );
  147         warn "Errors in including the module ${courseScriptsDirectory}$module_name.pm $@" if $@;
  148                           #warn "loading $module_name";
  149     } else {
  150       eval(qq! require "${module_name}.pm";  import ${module_name};! );
  151               warn "Errors in including either the module $module_name.pm or ${courseScriptsDirectory}${module_name}.pm $@" if $@;
  152         }
  153         push(@class_modules, "\%${module_name}::");
  154     }
  155     $SIG{__DIE__} = $save_SIG_die_trap;
  156 }
  157 
  158 =head2 load_extra_packages
  159 
  160   Useage:  $obj -> load_extra_packages('AlgParserWithImplicitExpand',
  161                               'Expr','ExprWithImplicitExpand');
  162 
  163 Loads extra packages for modules that contain more than one package.  Works in conjunction with
  164 evaluate_modules.  It is assumed that the file containing the extra packages (along with the base
  165 pachage name which is the same as the name of the file minus the .pm extension) has already been
  166 loaded using evaluate_modules
  167 =cut
  168 
  169 sub load_extra_packages{
  170   my $self = shift;
  171   my @package_list = @_;
  172   my $package_name;
  173 
  174     foreach $package_name (@package_list) {
  175         eval(qq! import ${package_name};! );
  176       warn "Errors in importing the package $package_name $@" if $@;
  177         push(@class_modules, "\%${package_name}::");
  178     }
  179 }
  180 
  181 
  182 
  183 =head2  new
  184   Creates the translator object.
  185 
  186 =cut
  187 
  188 
  189 sub new {
  190     my $class = shift;
  191     my $safe_cmpt = new Safe ('PG_priv');
  192     my $self = {
  193            envir          =>  undef,
  194            PG_PROBLEM_TEXT_ARRAY_REF  =>  [],
  195            PG_PROBLEM_TEXT_REF  =>  0,
  196            PG_HEADER_TEXT_REF   =>  0,
  197            PG_ANSWER_HASH_REF   =>  {},
  198            PG_FLAGS_REF     =>  {},
  199          safe           =>  $safe_cmpt,
  200          safe_compartment_name  =>  $safe_cmpt->root,
  201          errors         =>  "",
  202          source         =>  "",
  203          rh_correct_answers   =>  {},
  204          rh_student_answers   =>  {},
  205          rh_evaluated_answers   => {},
  206          rh_problem_result      => {},
  207          rh_problem_state   => {recorded_score      => 0,   # the score recorded in the data base
  208                                 num_of_correct_ans    => 0,   # the number of correct attempts at doing the problem
  209                                 num_of_incorrect_ans  => 0,   # the number of incorrect attempts
  210                                },
  211          rf_problem_grader    => \&std_problem_grader,
  212          rf_safety_filter       => \&safetyFilter,
  213          ra_included_modules  => [@class_modules],
  214          rh_directories     => {},
  215     };
  216 
  217     bless $self, $class;
  218 }
  219 
  220 =pod
  221 
  222 (b) The following routines defined within the PG module are shared:
  223 
  224   &be_strict
  225   &read_whole_problem_file
  226   &convertPath
  227   &surePathToTmpFile
  228   &fileFromPath
  229   &directoryFromPath
  230   &createFile
  231 
  232   &includePGtext
  233 
  234   &PG_answer_eval
  235   &PG_restricted_eval
  236 
  237   &send_mail_to
  238   &PGsort
  239 
  240 In addition the environment hash C<%envir> is shared.  This variable is unpacked
  241 when PG.pl is run and provides most of the environment variables for each problem
  242 template.
  243 
  244 =for html
  245 
  246   <A href =
  247   "${Global::webworkDocsURL}techdescription/pglanguage/PGenvironment.html"> environment variables</A>
  248 
  249 =cut
  250 
  251 
  252 =pod
  253 
  254 (c) Sharing macros:
  255 
  256 The macros shared with the safe compartment are
  257             '&read_whole_problem_file'
  258               '&convertPath'
  259                     '&surePathToTmpFile'
  260                     '&fileFromPath'
  261                     '&directoryFromPath'
  262                     '&createFile'
  263                     '&PG_answer_eval'
  264                     '&PG_restricted_eval'
  265                     '&be_strict'
  266                     '&send_mail_to'
  267                     '&PGsort'
  268                     '&dumpvar'
  269                     '&includePGtext'
  270 
  271 =cut
  272 
  273 
  274   ##############################################################################
  275           # SHARE variables and routines with safe compartment
  276     my %shared_subroutine_hash = (
  277                         '&read_whole_problem_file'  => 'PGtranslator', #the values are dummies.
  278               '&convertPath'        => 'PGtranslator',
  279                     '&surePathToTmpFile'    => 'PGtranslator',
  280                     '&fileFromPath'       => 'PGtranslator',
  281                     '&directoryFromPath'    => 'PGtranslator',
  282                     '&createFile'       => 'PGtranslator',
  283                     '&PG_answer_eval'     => 'PGtranslator',
  284                     '&PG_restricted_eval'   => 'PGtranslator',
  285                     '&be_strict'        => 'PGtranslator',
  286                     '&send_mail_to'       => 'PGtranslator',
  287                     '&PGsort'         => 'PGtranslator',
  288                     '&dumpvar'          => 'PGtranslator',
  289                     '&includePGtext'          => 'PGtranslator',
  290                     );
  291 
  292 
  293 
  294 
  295 
  296 sub initialize {
  297     my $self = shift;
  298     my $safe_cmpt = $self->{safe};
  299     #print "initializing safeCompartment",$safe_cmpt -> root(), "\n";
  300 
  301     $safe_cmpt -> share(keys %shared_subroutine_hash);
  302     no strict;
  303     local(%envir) = %{ $self ->{envir} };
  304   $safe_cmpt -> share('%envir');
  305 #   local($rf_answer_eval) = sub { $self->PG_answer_eval(@_); };
  306 #   local($rf_restricted_eval) = sub { $self->PG_restricted_eval(@_); };
  307 #   $safe_cmpt -> share('$rf_answer_eval');
  308 #   $safe_cmpt -> share('$rf_restricted_eval');
  309 
  310   use strict;
  311 
  312     # end experiment
  313     $self->{ra_included_modules} = [@class_modules];
  314     $safe_cmpt -> share_from('main', $self->{ra_included_modules} ); #$self ->{ra_included_modules}
  315 
  316 }
  317 
  318 
  319 
  320 sub environment{
  321   my $self = shift;
  322   my $envirref = shift;
  323   if ( defined($envirref) )  {
  324     if (ref($envirref) eq 'HASH') {
  325       %{ $self -> {envir} } = %$envirref;
  326     } else {
  327       $self ->{errors} .= "ERROR: The environment method for PG_translate objects requires a reference to a hash";
  328     }
  329   }
  330   $self->{envir} ; #reference to current environment
  331 }
  332 
  333 =head2   Safe compartment pass through macros
  334 
  335 
  336 
  337 =cut
  338 
  339 sub mask {
  340   my $self = shift;
  341   my $mask = shift;
  342   my $safe_compartment = $self->{safe};
  343   $safe_compartment->mask($mask);
  344 }
  345 sub permit {
  346   my $self = shift;
  347   my @array = shift;
  348   my $safe_compartment = $self->{safe};
  349   $safe_compartment->permit(@array);
  350 }
  351 sub deny {
  352   my $self = shift;
  353   my @array = shift;
  354   my $safe_compartment = $self->{safe};
  355   $safe_compartment->deny(@array);
  356 }
  357 sub share_from {
  358   my $self = shift;
  359   my $pckg_name = shift;
  360   my $array_ref =shift;
  361   my $safe_compartment = $self->{safe};
  362   $safe_compartment->share_from($pckg_name,$array_ref);
  363 }
  364 
  365 sub source_string {
  366   my $self = shift;
  367   my $temp = shift;
  368   my $out;
  369   if ( ref($temp) eq 'SCALAR') {
  370     $self->{source} = $$temp;
  371     $out = $self->{source};
  372   } elsif ($temp) {
  373     $self->{source} = $temp;
  374     $out = $self->{source};
  375   }
  376   $self -> {source};
  377 }
  378 
  379 sub source_file {
  380   my $self = shift;
  381   my $filePath = shift;
  382   local(*SOURCEFILE);
  383   local($/);
  384   $/ = undef;   # allows us to treat the file as a single line
  385   my $err = "";
  386   if ( open(SOURCEFILE, "<$filePath") ) {
  387     $self -> {source} = <SOURCEFILE>;
  388     close(SOURCEFILE);
  389   } else {
  390     $self->{errors} .= "Can't open file: $filePath";
  391     croak( "Can't open file: $filePath\n" );
  392   }
  393 
  394 
  395 
  396   $err;
  397 }
  398 
  399 
  400 
  401 sub unrestricted_load {
  402   my $self = shift;
  403   my $filePath = shift;
  404   my $safe_cmpt = $self ->{safe};
  405   my $store_mask = $safe_cmpt->mask();
  406   $safe_cmpt->mask(Opcode::empty_opset());
  407   my $safe_cmpt_package_name = $safe_cmpt->root();
  408 
  409   my $macro_file_name = fileFromPath($filePath);
  410   $macro_file_name =~s/\.pl//;  # trim off the extenstion
  411   my $export_subroutine_name = "_${macro_file_name}_export";
  412     my $init_subroutine_name = "_${macro_file_name}_init";
  413     my $macro_file_loaded;
  414     no strict;
  415     $macro_file_loaded  = defined(&{"${safe_cmpt_package_name}::$init_subroutine_name"} );
  416     #print " &${safe_cmpt_package_name}::$init_subroutine_name defined = ",
  417     # $macro_file_loaded,"\n";
  418   unless ($macro_file_loaded) {
  419     #print "loading $filePath\n";
  420     ## load the $filePath file
  421     ## Using rdo insures that the $filePath file is loaded for every problem, allowing initializations to occur.
  422     ## Ordinary mortals should not be fooling with the fundamental macros in these files.
  423     my $local_errors = "";
  424     if (-r $filePath ) {
  425       $safe_cmpt -> rdo( "$filePath" ) ;
  426       #warn "There were problems compiling the file: $filePath: <BR>--$@" if $@;
  427       $local_errors ="\nThere were problems compiling the file:\n $filePath\n $@\n" if $@;
  428       $self ->{errors} .= $local_errors if $local_errors;
  429       use strict;
  430     } else {
  431       $local_errors = "Can't open file $filePath for reading\n";
  432       $self ->{errors} .= $local_errors if $local_errors;
  433     }
  434     $safe_cmpt -> mask($store_mask);
  435     $local_errors;
  436   }
  437 }
  438 
  439 sub nameSpace {
  440   my $self = shift;
  441   $self->{safe}->root;
  442 }
  443 
  444 sub a_text {
  445   my $self  = shift;
  446     @{$self->{PG_PROBLEM_TEXT_ARRAY_REF}};
  447 }
  448 
  449 sub header {
  450   my $self = shift;
  451   ${$self->{PG_HEADER_TEXT_REF}};
  452 }
  453 
  454 sub h_flags {
  455   my $self = shift;
  456   %{$self->{PG_FLAGS_REF}};
  457 }
  458 
  459 sub rh_flags {
  460   my $self = shift;
  461   $self->{PG_FLAGS_REF};
  462 }
  463 sub h_answers{
  464   my $self = shift;
  465   %{$self->{PG_ANSWER_HASH_REF}};
  466 }
  467 
  468 sub ra_text {
  469   my $self  = shift;
  470     $self->{PG_PROBLEM_TEXT_ARRAY_REF};
  471 
  472 }
  473 
  474 sub r_text {
  475   my $self  = shift;
  476     $self->{PG_PROBLEM_TEXT_REF};
  477 }
  478 
  479 sub r_header {
  480   my $self = shift;
  481   $self->{PG_HEADER_TEXT_REF};
  482 }
  483 
  484 sub rh_correct_answers {
  485   my $self = shift;
  486   my @in = @_;
  487   return $self->{rh_correct_answers} if @in == 0;
  488 
  489   if ( ref($in[0]) eq 'HASH' ) {
  490     $self->{rh_correct_answers} = { %{ $in[0] } }; # store a copy of the hash
  491   } else {
  492     $self->{rh_correct_answers} = { @in }; # store a copy of the hash
  493   }
  494   $self->{rh_correct_answers}
  495 }
  496 
  497 sub rf_problem_grader {
  498   my $self = shift;
  499   my $in = shift;
  500   return $self->{rf_problem_grader} unless defined($in);
  501   if (ref($in) =~/CODE/ ) {
  502     $self->{rf_problem_grader} = $in;
  503   } else {
  504     die "ERROR: Attempted to install a problem grader which was not a reference to a subroutine.";
  505   }
  506   $self->{rf_problem_grader}
  507 }
  508 
  509 
  510 sub errors{
  511   my $self = shift;
  512   $self->{errors};
  513 }
  514 
  515 # sub DESTROY {
  516 #     my $self = shift;
  517 #     my $nameSpace = $self->nameSpace;
  518 #   no strict 'refs';
  519 #     my $nm = "${nameSpace}::";
  520 #      my $nsp = \%{"$nm"};
  521 #       my @list = keys %$nsp;
  522 #       while (@list) {
  523 #       my $name = pop(@list);
  524 #       if  ( defined(&{$nsp->{$name}})  )  {
  525 #          #print "checking \&$name\n";
  526 #          unless (exists( $shared_subroutine_hash{"\&$name"} ) ) {
  527 #           undef( &{$nsp->{$name}} );
  528 #           #print "destroying \&$name\n";
  529 #          } else {
  530 #             #delete( $nsp->{$name} );
  531 #             #print "what is left",join(" ",%$nsp) ,"\n\n";
  532 #          }
  533 #
  534 #       }
  535 #       if  ( defined(${$nsp->{$name}})  )  {
  536 #          #undef( ${$nsp->{$name}} );         ## unless commented out download hardcopy bombs with Perl 5.6
  537 #            #print "destroying \$$name\n";
  538 #       }
  539 #       if  ( defined(@{$nsp->{$name}})  )  {
  540 #          undef( @{$nsp->{$name}} );
  541 #          #print "destroying \@$name\n";
  542 #       }
  543 #       if  ( defined(%{$nsp->{$name}})  )  {
  544 #          undef( %{$nsp->{$name}} ) unless $name =~ /::/ ;
  545 #          #print "destroying \%$name\n";
  546 #       }
  547 #       # changed for Perl 5.6
  548 #     delete ( $nsp->{$name} ) if defined($nsp->{$name});  # this must be uncommented in Perl 5.6 to reinitialize variables
  549 #     # changed for Perl 5.6
  550 #    #print "deleting $name\n";
  551 #     #undef( @{$nsp->{$name}} ) if defined(@{$nsp->{$name}});
  552 #     #undef( %{$nsp->{$name}} ) if defined(%{$nsp->{$name}}) and $name ne "main::";
  553 #    }
  554 #
  555 #   use strict;
  556 #     #print "\nObject going bye-bye\n";
  557 #
  558 # }
  559 
  560 =head2  set_mask
  561 
  562 
  563 
  564 
  565 
  566 
  567 (e) Now we close the safe compartment.  Only the certain operations can be used
  568 within PG problems and the PG macro files.  These include the subroutines
  569 shared with the safe compartment as defined above and most Perl commands which
  570 do not involve file access, access to the system or evaluation.
  571 
  572 Specifically the following are allowed
  573 
  574   time()
  575     # gives the current Unix time
  576     # used to determine whether solutions are visible.
  577   atan, sin cos exp log sqrt
  578     # arithemetic commands -- more are defined in PGauxiliaryFunctions.pl
  579 
  580 The following are specifically not allowed:
  581 
  582   eval()
  583   unlink, symlink, system, exec
  584   print require
  585 
  586 
  587 
  588 =cut
  589 
  590 ##############################################################################
  591 
  592           ## restrict the operations allowed within the safe compartment
  593 
  594 sub set_mask {
  595   my $self = shift;
  596   my $safe_cmpt = $self ->{safe};
  597     $safe_cmpt->mask(Opcode::full_opset());  # allow no operations
  598     $safe_cmpt->permit(qw(   :default ));
  599     $safe_cmpt->permit(qw(time));  # used to determine whether solutions are visible.
  600   $safe_cmpt->permit(qw( atan2 sin cos exp log sqrt ));
  601 
  602   # just to make sure we'll deny some things specifically
  603   $safe_cmpt->deny(qw(entereval));
  604   $safe_cmpt->deny(qw (  unlink symlink system exec ));
  605   $safe_cmpt->deny(qw(print require));
  606 }
  607 
  608 ############################################################################
  609 
  610 
  611 =head2  Translate
  612 
  613 
  614 =cut
  615 
  616 sub translate {
  617   my $self = shift;
  618   my @PROBLEM_TEXT_OUTPUT = ();
  619   my $safe_cmpt = $self ->{safe};
  620   my $evalString = $self -> {source};
  621   $self ->{errors} .= qq{ERROR:  This problem file was empty!\n} unless ($evalString) ;
  622   $self ->{errors} .= qq{ERROR:  You must define the environment before translating.}
  623        unless defined( $self->{envir} );
  624     # reset the error detection
  625     my $save_SIG_die_trap = $SIG{__DIE__};
  626     $SIG{__DIE__} = sub {CORE::die(@_) };
  627 
  628 
  629 
  630 =pod
  631 
  632 (3) B<Preprocess the problem text>
  633 
  634 The input text is subjected to two global replacements.
  635 First every incidence of
  636 
  637   BEGIN_TEXT
  638   problem text
  639   END_TEXT
  640 
  641 is replaced by
  642 
  643     TEXT( EV3( <<'END_TEXT' ) );
  644   problem text
  645   END_TEXT
  646 
  647 The first construction is syntactic sugar for the second. This is explained
  648 in C<PGbasicmacros.pl>.
  649 
  650 Second every incidence
  651 of \ (backslash) is replaced by \\ (double backslash).  Third each incidence of
  652 ~~ is replaced by a single backslash.
  653 
  654 This is done to alleviate a basic
  655 incompatibility between TeX and Perl. TeX uses backslashes constantly to denote
  656 a command word (as opposed to text which is to be entered literally).  Perl
  657 uses backslash to escape the following symbol.  This escape
  658 mechanism takes place immediately when a Perl script is compiled and takes
  659 place throughout the code and within every quoted string (both double and single
  660 quoted strings) with the single exception of single quoted "here" documents.
  661 That is backlashes which appear in
  662 
  663     TEXT(<<'EOF');
  664     ... text including \{   \} for example
  665     EOF
  666 
  667 are the only ones not immediately evaluated.  This behavior makes it very difficult
  668 to use TeX notation for defining mathematics within text.
  669 
  670 The initial global
  671 replacement, before compiling a PG problem, allows one to use backslashes within
  672 text without doubling them. (The anomolous behavior inside single quoted "here"
  673 documents is compensated for by the behavior of the evaluation macro EV3.) This
  674 makes typing TeX easy, but introduces one difficulty in entering normal Perl code.
  675 
  676 The second global replacement provides a work around for this -- use ~~ when you
  677 would ordinarily use a backslash in Perl code.
  678 In order to define a carriage return use ~~n rather than \n; in order to define
  679 a reference to a variable you must use ~~@array rather than \@array. This is
  680 annoying and a source of simple compiler errors, but must be lived with.
  681 
  682 The problems are not evaluated in strict mode, so global variables can be used
  683 without warnings.
  684 
  685 
  686 
  687 =cut
  688 
  689 ############################################################################
  690 
  691 
  692         ##########################################
  693         ###### PG preprocessing code #############
  694         ##########################################
  695             # BEGIN_TEXT and END_TEXT must occur on a line by themselves.
  696             $evalString =~ s/\n\s*END_TEXT[\s;]*\n/\nEND_TEXT\n/g;
  697           $evalString =~ s/\n\s*BEGIN_TEXT[\s;]*\n/\nTEXT\(EV3\(<<'END_TEXT'\)\);\n/g;
  698           $evalString =~ s/ENDDOCUMENT.*/ENDDOCUMENT();/s; # remove text after ENDDOCUMENT
  699 
  700         $evalString =~ s/\\/\\\\/g;    # \ can't be used for escapes because of TeX conflict
  701             $evalString =~ s/~~/\\/g;      # use ~~ as escape instead, use # for comments
  702 
  703 =pod
  704 
  705 (4) B<Evaluate the problem text>
  706 
  707 Evaluate the text within the safe compartment.  Save the errors. The safe
  708 compartment is a new one unless the $safeCompartment was set to zero in which
  709 case the previously defined safe compartment is used. (See item 1.)
  710 
  711 =cut
  712 
  713 
  714         my ($PG_PROBLEM_TEXT_REF, $PG_HEADER_TEXT_REF, $PG_ANSWER_HASH_REF, $PG_FLAGS_REF)
  715               =$safe_cmpt->reval("   $evalString");
  716 
  717 # This section could use some more error messages.  In particular if a problem doesn't produce the right output, the user needs
  718 # information about which problem was at fault.
  719 #
  720 #
  721 
  722         $self->{errors} .= $@;
  723 #         push(@PROBLEM_TEXT_OUTPUT   ,   split(/(\n)/,$$PG_PROBLEM_TEXT_REF)  ) if  defined($$PG_PROBLEM_TEXT_REF  );
  724           push(@PROBLEM_TEXT_OUTPUT   ,   split(/^/,$$PG_PROBLEM_TEXT_REF)  ) if  ref($PG_PROBLEM_TEXT_REF  ) eq 'SCALAR';
  725                                                                            ## This is better than using defined($$PG_PROBLEM_TEXT_REF)
  726                                                                            ## Because more pleasant feedback is given
  727                                                                            ## when the problem doesn't render.
  728            # try to get the \n to appear at the end of the line
  729 
  730         use strict;
  731         #############################################################################
  732         ##########  end  EVALUATION code                                  ###########
  733         #############################################################################
  734 
  735 =pod
  736 
  737 (5) B<Process errors>
  738 
  739 The error provided by Perl
  740 is truncated slightly and returned. In the text
  741 string which would normally contain the rendered problem.
  742 
  743 The original text string is given line numbers and concatenated to
  744 the errors.
  745 
  746 =cut
  747 
  748 
  749 
  750         ##########################################
  751     ###### PG error processing code ##########
  752     ##########################################
  753         my (@input,$lineNumber,$line);
  754         if ($self -> {errors}) {
  755                 #($self -> {errors}) =~ s/</&lt/g;
  756                 #($self -> {errors}) =~ s/>/&gt/g;
  757            #try to clean up errors so they will look ok
  758                 $self ->{errors} =~ s/\[.+?\.pl://gm;   #erase [Fri Dec 31 12:58:30 1999] processProblem7.pl:
  759                 #$self -> {errors} =~ s/eval\s+'(.|[\n|r])*$//;
  760             #end trying to clean up errors so they will look ok
  761 
  762 
  763                 push(@PROBLEM_TEXT_OUTPUT   ,  qq!\n<A NAME="problem! .
  764                     $self->{envir} ->{'probNum'} .
  765                     qq!"><PRE>        Problem!.
  766                     $self->{envir} ->{'probNum'}.
  767                     qq!\nERROR caught by PGtranslator while processing problem file:! .
  768                   $self->{envir}->{'probFileName'}.
  769                   "\n****************\r\n" .
  770                   $self -> {errors}."\r\n" .
  771                   "****************<BR>\n");
  772 
  773                 push(@PROBLEM_TEXT_OUTPUT   , "------Input Read\r\n");
  774                $self->{source} =~ s/</&lt;/g;
  775                @input=split("\n", $self->{source});
  776                $lineNumber = 1;
  777                 foreach $line (@input) {
  778                     chomp($line);
  779                     push(@PROBLEM_TEXT_OUTPUT, "$lineNumber\t\t$line\r\n");
  780                     $lineNumber ++;
  781                 }
  782                 push(@PROBLEM_TEXT_OUTPUT  ,"\n-----<BR></PRE>\r\n");
  783 
  784 
  785 
  786         }
  787 
  788 =pod
  789 
  790 (6) B<Prepare return values>
  791 
  792   Returns:
  793       $PG_PROBLEM_TEXT_ARRAY_REF -- Reference to a string containing the rendered text.
  794       $PG_HEADER_TEXT_REF -- Reference to a string containing material to placed in the header (for use by JavaScript)
  795       $PG_ANSWER_HASH_REF -- Reference to an array containing the answer evaluators.
  796       $PG_FLAGS_REF -- Reference to a hash containing flags and other references:
  797         'error_flag' is set to 1 if there were errors in rendering
  798 
  799 =cut
  800 
  801         ## we need to make sure that the other output variables are defined
  802 
  803                 ## If the eval failed with errors, one or more of these variables won't be defined.
  804                 $PG_ANSWER_HASH_REF = {}      unless defined($PG_ANSWER_HASH_REF);
  805                 $PG_HEADER_TEXT_REF = \( "" ) unless defined($PG_HEADER_TEXT_REF);
  806                 $PG_FLAGS_REF = {}            unless defined($PG_FLAGS_REF);
  807 
  808             $PG_FLAGS_REF->{'error_flag'} = 1     if $self -> {errors};
  809         my $PG_PROBLEM_TEXT                     = join("",@PROBLEM_TEXT_OUTPUT);
  810 
  811         $self ->{ PG_PROBLEM_TEXT_REF }     = \$PG_PROBLEM_TEXT;
  812         $self ->{ PG_PROBLEM_TEXT_ARRAY_REF }   = \@PROBLEM_TEXT_OUTPUT;
  813       $self ->{ PG_HEADER_TEXT_REF  }   = $PG_HEADER_TEXT_REF;
  814       $self ->{ rh_correct_answers  }   = $PG_ANSWER_HASH_REF;
  815       $self ->{ PG_FLAGS_REF      }   = $PG_FLAGS_REF;
  816       $SIG{__DIE__} = $save_SIG_die_trap;
  817       $self ->{errors};
  818 }  # end translate
  819 
  820 
  821 =head2   Answer evaluation methods
  822 
  823 =cut
  824 
  825 =head3  access methods
  826 
  827   $obj->rh_student_answers
  828 
  829 =cut
  830 
  831 
  832 
  833 sub rh_evaluated_answers {
  834   my $self = shift;
  835   my @in = @_;
  836   return $self->{rh_evaluated_answers} if @in == 0;
  837 
  838   if ( ref($in[0]) eq 'HASH' ) {
  839     $self->{rh_evaluated_answers} = { %{ $in[0] } }; # store a copy of the hash
  840   } else {
  841     $self->{rh_evaluated_answers} = { @in }; # store a copy of the hash
  842   }
  843   $self->{rh_evaluated_answers};
  844 }
  845 sub rh_problem_result {
  846   my $self = shift;
  847   my @in = @_;
  848   return $self->{rh_problem_result} if @in == 0;
  849 
  850   if ( ref($in[0]) eq 'HASH' ) {
  851     $self->{rh_problem_result} = { %{ $in[0] } }; # store a copy of the hash
  852   } else {
  853     $self->{rh_problem_result} = { @in }; # store a copy of the hash
  854   }
  855   $self->{rh_problem_result};
  856 }
  857 sub rh_problem_state {
  858   my $self = shift;
  859   my @in = @_;
  860   return $self->{rh_problem_state} if @in == 0;
  861 
  862   if ( ref($in[0]) eq 'HASH' ) {
  863     $self->{rh_problem_state} = { %{ $in[0] } }; # store a copy of the hash
  864   } else {
  865     $self->{rh_problem_state} = { @in }; # store a copy of the hash
  866   }
  867   $self->{rh_problem_state};
  868 }
  869 
  870 
  871 =head3 process_answers
  872 
  873 
  874   $obj->process_answers()
  875 
  876 
  877 =cut
  878 
  879 
  880 sub process_answers{
  881   my $self = shift;
  882   my @in = shift;
  883   my %h_student_answers;
  884   if (ref($in[0]) eq 'HASH' ) {
  885     %h_student_answers = %{ $in[0] };
  886   } else {
  887     %h_student_answers = @in;
  888   }
  889   my $rh_correct_answers = $self->rh_correct_answers();
  890   my @answer_entry_order = ( defined($self->{PG_FLAGS_REF}->{ANSWER_ENTRY_ORDER}) ) ?
  891                         @{$self->{PG_FLAGS_REF}->{ANSWER_ENTRY_ORDER}} : keys %{$rh_correct_answers};
  892 
  893   # apply each instructors answer to the corresponding student answer
  894 
  895   foreach my $ans_name ( @answer_entry_order ) {
  896       my ($ans, $errors) = $self->filter_answer( $h_student_answers{$ans_name} );
  897       no strict;
  898       # evaluate the answers inside the safe compartment.
  899       local($rf_fun,$temp_ans) = (undef,undef);
  900       if ( defined($rh_correct_answers ->{$ans_name} ) ) {
  901         $rf_fun  = $rh_correct_answers->{$ans_name};
  902       } else {
  903         warn "There is no answer evaluator for the question labeled $ans_name";
  904       }
  905       $temp_ans  = $ans;
  906       $temp_ans = '' unless defined($temp_ans);  #make sure that answer is always defined
  907                                                 # in case the answer evaluator forgets to check
  908       $self->{safe}->share('$rf_fun','$temp_ans');
  909 
  910         # reset the error detection
  911       my $save_SIG_die_trap = $SIG{__DIE__};
  912       $SIG{__DIE__} = sub {CORE::die(@_) };
  913       my $rh_ans_evaluation_result;
  914         if (ref($rf_fun) eq 'CODE' ) {
  915           $rh_ans_evaluation_result = $self->{safe} ->reval( '&{ $rf_fun }($temp_ans)' ) ;
  916           warn "Error in PGtranslator.pm::process_answers: Answer $ans_name:<BR>\n $@\n" if $@;
  917         } elsif (ref($rf_fun) eq 'AnswerEvaluator')   {
  918           $rh_ans_evaluation_result = $self->{safe} ->reval('$rf_fun->evaluate($temp_ans)');
  919           warn "Error in PGtranslator.pm::process_answers: Answer $ans_name:<BR>\n $@\n" if $@;
  920           warn "Evaluation error: Answer $ans_name:<BR>\n", $rh_ans_evaluation_result->error_flag(), " :: ",$rh_ans_evaluation_result->error_message(),"<BR>\n"
  921                        if defined($rh_ans_evaluation_result)  and defined($rh_ans_evaluation_result->error_flag());
  922         } else {
  923           warn "Error in PGtranslator.pm::process_answers: Answer $ans_name:<BR>\n Unrecognized evaluator type |", ref($rf_fun), "|";
  924         }
  925 
  926         $SIG{__DIE__} = $save_SIG_die_trap;
  927 
  928 
  929         use strict;
  930         unless ( ( ref($rh_ans_evaluation_result) eq 'HASH') or ( ref($rh_ans_evaluation_result) eq 'AnswerHash') ) {
  931           warn "Error in PGtranslator.pm::process_answers: Answer $ans_name:<BR>\n
  932                 Answer evaluators must return a hash or an AnswerEvaluator type, not type |",
  933                 ref($rh_ans_evaluation_result), "|";
  934         }
  935         $rh_ans_evaluation_result ->{ans_message} .= "$errors \n" if $errors;
  936         $rh_ans_evaluation_result ->{ans_name} = $ans_name;
  937       $self->{rh_evaluated_answers}->{$ans_name} = $rh_ans_evaluation_result;
  938 
  939   }
  940   $self->rh_evaluated_answers;
  941 
  942 }
  943 
  944 
  945 
  946 =head3 grade_problem
  947 
  948   $obj->rh_problem_state(%problem_state);  # sets the current problem state
  949   $obj->grade_problem(%form_options);
  950 
  951 
  952 =cut
  953 
  954 
  955 sub grade_problem {
  956   my $self = shift;
  957     my %form_options = @_;
  958   my $rf_grader = $self->{rf_problem_grader};
  959   ($self->{rh_problem_result},$self->{rh_problem_state} )  =
  960                     &{$rf_grader}(  $self -> {rh_evaluated_answers},
  961                                     $self -> {rh_problem_state},
  962                                     %form_options
  963                                   );
  964 
  965   ($self->{rh_problem_result}, $self->{rh_problem_state} ) ;
  966 }
  967 
  968 sub rf_std_problem_grader {
  969     my $self = shift;
  970   return \&std_problem_grader;
  971 }
  972 sub old_std_problem_grader{
  973   my $rh_evaluated_answers = shift;
  974   my %flags = @_;  # not doing anything with these yet
  975   my %evaluated_answers = %{$rh_evaluated_answers};
  976   my  $allAnswersCorrectQ=1;
  977   foreach my $ans_name (keys %evaluated_answers) {
  978   # I'm not sure if this check is really useful.
  979       if (ref($evaluated_answers{$ans_name} ) eq 'HASH' ) {
  980         $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} );
  981       } else {
  982         warn "Error: Answer $ans_name is not a hash";
  983         warn "$evaluated_answers{$ans_name}";
  984       }
  985   }
  986   # Notice that "all answers are correct" if there are no questions.
  987   { score       => $allAnswersCorrectQ,
  988     prev_tries    => 0,
  989     partial_credit  => $allAnswersCorrectQ,
  990     errors      =>  "",
  991     type              => 'old_std_problem_grader',
  992     flags       => {}, # not doing anything with these yet
  993   };  # hash output
  994 
  995 }
  996 
  997 #####################################
  998 # This is a model for plug-in problem graders
  999 #####################################
 1000 
 1001 sub std_problem_grader{
 1002   my $rh_evaluated_answers = shift;
 1003   my $rh_problem_state = shift;
 1004   my %form_options = @_;
 1005   my %evaluated_answers = %{$rh_evaluated_answers};
 1006   #  The hash $rh_evaluated_answers typically contains:
 1007   #      'answer1' => 34, 'answer2'=> 'Mozart', etc.
 1008 
 1009   # By default the  old problem state is simply passed back out again.
 1010   my %problem_state = %$rh_problem_state;
 1011 
 1012 
 1013   # %form_options might include
 1014   # The user login name
 1015   # The permission level of the user
 1016   # The studentLogin name for this psvn.
 1017   # Whether the form is asking for a refresh or is submitting a new answer.
 1018 
 1019   # initial setup of the answer
 1020   my %problem_result = ( score        => 0,
 1021                errors         => '',
 1022                type           => 'std_problem_grader',
 1023                msg          => '',
 1024              );
 1025   # Checks
 1026 
 1027   my $ansCount = keys %evaluated_answers;  # get the number of answers
 1028   unless ($ansCount > 0 ) {
 1029     $problem_result{msg} = "This problem did not ask any questions.";
 1030     return(\%problem_result,\%problem_state);
 1031   }
 1032 
 1033   if ($ansCount > 1 ) {
 1034     $problem_result{msg} = 'In order to get credit for this problem all answers must be correct.' ;
 1035   }
 1036 
 1037   unless (defined( $form_options{answers_submitted}) and $form_options{answers_submitted} == 1) {
 1038     return(\%problem_result,\%problem_state);
 1039   }
 1040 
 1041   my  $allAnswersCorrectQ=1;
 1042   foreach my $ans_name (keys %evaluated_answers) {
 1043   # I'm not sure if this check is really useful.
 1044       if ( ( ref($evaluated_answers{$ans_name} ) eq 'HASH' ) or ( ref($evaluated_answers{$ans_name}) eq 'AnswerHash' ) ) {
 1045         $allAnswersCorrectQ = 0 unless( 1 == $evaluated_answers{$ans_name}->{score} );
 1046       } else {
 1047         warn "Error: Answer $ans_name is not a hash";
 1048         warn "$evaluated_answers{$ans_name}";
 1049         warn "This probably means that the answer evaluator is for this answer is not working correctly.";
 1050         $problem_result{error} = "Error: Answer $ans_name is not a hash: $evaluated_answers{$ans_name}";
 1051       }
 1052   }
 1053   # report the results
 1054   $problem_result{score} = $allAnswersCorrectQ;
 1055 
 1056   # I don't like to put in this bit of code.
 1057   # It makes it hard to construct error free problem graders
 1058   # I would prefer to know that the problem score was numeric.
 1059     unless ($problem_state{recorded_score} =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) {
 1060       $problem_state{recorded_score} = 0;  # This gets rid of non-numeric scores
 1061     }
 1062     #
 1063   if ($allAnswersCorrectQ == 1 or $problem_state{recorded_score} == 1) {
 1064     $problem_state{recorded_score} = 1;
 1065   } else {
 1066     $problem_state{recorded_score} = 0;
 1067   }
 1068 
 1069   $problem_state{num_of_correct_ans}++ if $allAnswersCorrectQ == 1;
 1070   $problem_state{num_of_incorrect_ans}++ if $allAnswersCorrectQ == 0;
 1071   (\%problem_result, \%problem_state);
 1072 }
 1073 sub rf_avg_problem_grader {
 1074     my $self = shift;
 1075   return \&avg_problem_grader;
 1076 }
 1077 sub avg_problem_grader{
 1078     my $rh_evaluated_answers = shift;
 1079   my $rh_problem_state = shift;
 1080   my %form_options = @_;
 1081   my %evaluated_answers = %{$rh_evaluated_answers};
 1082   #  The hash $rh_evaluated_answers typically contains:
 1083   #      'answer1' => 34, 'answer2'=> 'Mozart', etc.
 1084 
 1085   # By default the  old problem state is simply passed back out again.
 1086   my %problem_state = %$rh_problem_state;
 1087 
 1088 
 1089   # %form_options might include
 1090   # The user login name
 1091   # The permission level of the user
 1092   # The studentLogin name for this psvn.
 1093   # Whether the form is asking for a refresh or is submitting a new answer.
 1094 
 1095   # initial setup of the answer
 1096   my  $total=0;
 1097   my %problem_result = ( score        => 0,
 1098                errors         => '',
 1099                type           => 'avg_problem_grader',
 1100                msg          => '',
 1101              );
 1102     my $count = keys %evaluated_answers;
 1103     $problem_result{msg} = 'You can earn partial credit on this problem.' if $count >1;
 1104     # Return unless answers have been submitted
 1105     unless ($form_options{answers_submitted} == 1) {
 1106     return(\%problem_result,\%problem_state);
 1107   }
 1108   # Answers have been submitted -- process them.
 1109   foreach my $ans_name (keys %evaluated_answers) {
 1110     $total += $evaluated_answers{$ans_name}->{score};
 1111   }
 1112   # Calculate score rounded to three places to avoid roundoff problems
 1113   $problem_result{score} = $total/$count if $count;
 1114   # increase recorded score if the current score is greater.
 1115   $problem_state{recorded_score} = $problem_result{score} if $problem_result{score} > $problem_state{recorded_score};
 1116 
 1117 
 1118     $problem_state{num_of_correct_ans}++ if $total == $count;
 1119   $problem_state{num_of_incorrect_ans}++ if $total < $count ;
 1120   warn "Error in grading this problem the total $total is larger than $count" if $total > $count;
 1121   (\%problem_result, \%problem_state);
 1122 
 1123 }
 1124 =head3 safetyFilter
 1125 
 1126   ($filtered_ans, $errors) = $obj ->filter_ans($ans)
 1127                                $obj ->rf_safety_filter()
 1128 
 1129 =cut
 1130 
 1131 sub filter_answer {
 1132   my $self = shift;
 1133   &{ $self->{rf_safety_filter} } (@_);
 1134 }
 1135 sub rf_safety_filter {
 1136   my $self = shift;
 1137   my $rf_filter = shift;
 1138   $self->{rf_safety_filter} = $rf_filter if $rf_filter and ref($rf_filter) eq 'CODE';
 1139   warn "The safety_filter must be a reference to a subroutine" unless ref($rf_filter) eq 'CODE' ;
 1140   $self->{rf_safety_filter}
 1141 }
 1142 sub safetyFilter {
 1143       my $answer = shift;  # accepts one answer and checks it
 1144       my $submittedAnswer = $answer;
 1145     $answer = '' unless defined $answer;
 1146     my ($errorno, $answerIsCorrectQ);
 1147     $answer =~ tr/\000-\037/ /;
 1148    #### Return if answer field is empty ########
 1149     unless ($answer =~ /\S/) {
 1150 #     $errorno = "<BR>No answer was submitted.";
 1151             $errorno = 0;  ## don't report blank answer as error
 1152 
 1153       return ($answer,$errorno);
 1154       }
 1155    ######### replace ^ with **    (for exponentiation)
 1156    #  $answer =~ s/\^/**/g;
 1157    ######### Return if  forbidden characters are found
 1158     unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ )  {
 1159       $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
 1160       $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
 1161 
 1162       return ($answer,$errorno);
 1163       }
 1164 
 1165     $errorno = 0;
 1166     return($answer, $errorno);
 1167 }
 1168 
 1169 ##   Check submittedAnswer for forbidden characters, etc.
 1170 #     ($submittedAnswer,$errorno) = safetyFilter($submittedAnswer);
 1171 #       $errors .= "No answer was submitted.<BR>" if $errorno == 1;
 1172 #       $errors .= "There are forbidden characters in your answer: $submittedAnswer<BR>" if $errorno ==2;
 1173 #
 1174 ##   Check correctAnswer for forbidden characters, etc.
 1175 #     unless (ref($correctAnswer) ) {  #skip check if $correctAnswer is a function
 1176 #       ($correctAnswer,$errorno) = safetyFilter($correctAnswer);
 1177 #       $errors .= "No correct answer is given in the statement of the problem.
 1178 #                   Please report this to your instructor.<BR>" if $errorno == 1;
 1179 #       $errors .= "There are forbidden characters in the problems answer.
 1180 #                   Please report this to your instructor.<BR>" if $errorno == 2;
 1181 #     }
 1182 
 1183 =head2   Private functions (not methods)
 1184 
 1185 
 1186 
 1187 =cut
 1188 
 1189 
 1190 #private functions
 1191 
 1192 sub includePGtext  {
 1193     my $evalString = shift;
 1194     if (ref($evalString) eq 'SCALAR') {
 1195       $evalString = $$evalString;
 1196     }
 1197     $evalString =~ s/\nBEGIN_TEXT/TEXT\(EV3\(<<'END_TEXT'\)\);/g;
 1198     $evalString =~ s/\\/\\\\/g;    # \ can't be used for escapes because of TeX conflict
 1199     $evalString =~ s/~~/\\/g;      # use ~~ as escape instead, use # for comments
 1200     no strict;
 1201       eval("package main; $evalString") ;
 1202       my $errors = $@;
 1203       die eval(q! "ERROR in included file:\n$main::envir{probFileName}\n $errors\n"!) if $errors;
 1204     use strict;
 1205         '';
 1206 }
 1207 
 1208 
 1209 #private IO functions
 1210 
 1211 my $REMOTE_HOST = (defined( $ENV{'REMOTE_HOST'} ) ) ? $ENV{'REMOTE_HOST'}: 'unknown host';
 1212 my $REMOTE_ADDR = (defined( $ENV{'REMOTE_ADDR'}) ) ? $ENV{'REMOTE_ADDR'}: 'unknown address';
 1213 
 1214 =head2 send_mail_to
 1215 
 1216   send_mail_to($user_address,'subject'=>$subject,'body'=>$body)
 1217 
 1218   Returns: 1 if the address is ok, otherwise a fatal error is signaled using wwerror.
 1219 
 1220 Sends $body to the address specified by $user_address provided that
 1221 the address appears in C<@{$Global::PG_environment{'ALLOW_MAIL_TO'}}>.
 1222 
 1223 This subroutine is likely to be fragile and to require tweaking when installed
 1224 in a new environment.  It uses the unix application C<sendmail>.
 1225 
 1226 =cut
 1227 
 1228 
 1229 sub send_mail_to {
 1230     my $user_address = shift;   # user must be an instructor
 1231     my %options = @_;
 1232     my $subject = '';
 1233        $subject = $options{'subject'} if defined($options{'subject'});
 1234     my $msg_body = '';
 1235        $msg_body =$options{'body'} if defined($options{'body'});
 1236     my @mail_to_allowed_list = ();
 1237        @mail_to_allowed_list = @{ $options{'ALLOW_MAIL_TO'} } if defined($options{'ALLOW_MAIL_TO'});
 1238     my $out;
 1239 
 1240     # check whether user is an instructor
 1241     my $mailing_allowed_flag =0;
 1242 
 1243 
 1244      while (@mail_to_allowed_list) {
 1245       if ($user_address eq shift @mail_to_allowed_list ) {
 1246         $mailing_allowed_flag =1;
 1247         last;
 1248       }
 1249      }
 1250     if ($mailing_allowed_flag) {
 1251     ## mail header text:
 1252     my   $email_msg ="To:  $user_address\n" .
 1253         "X-Remote-Host:  $REMOTE_HOST($REMOTE_ADDR)\n" .
 1254         "Subject: $subject\n\n" . $msg_body;
 1255       my $smtp = Net::SMTP->new($Global::smtpServer, Timeout=>10) ||
 1256       warn "Couldn't contact SMTP server.";
 1257       $smtp->mail($Global::webmaster);
 1258 
 1259     if ( $smtp->recipient($user_address)) {  # this one's okay, keep going
 1260           $smtp->data( $email_msg) ||
 1261         warn("Unknown problem sending message data to SMTP server.");
 1262       } else {      # we have a problem a problem with this address
 1263         $smtp->reset;
 1264           warn "SMTP server doesn't like this address: <$user_address>.";
 1265     }
 1266       $smtp->quit;
 1267 
 1268     } else {
 1269 
 1270     Global::wwerror("$0","There has been an error in creating this problem.\n" .
 1271                  "Please notify your instructor.\n\n" .
 1272                  "Mail is not permitted to address $user_address.\n" .
 1273                  "Permitted addresses are specified in the courseWeBWorK.ph file.",
 1274                  "","","");
 1275      $out = 0;
 1276     }
 1277 
 1278     $out;
 1279 
 1280 }
 1281 # only files are loaded first from the macroDirectory and then from the courseScriptsDirectory
 1282 # files cannot be loaded from other directories.
 1283 
 1284 
 1285 
 1286 
 1287 #
 1288 # # these have been copied over from FILE.pl.  I don't know if they need to be duplicated or not.
 1289 # ## these call backs come from PGchoice -- mostly from within the alias command.
 1290 #
 1291 
 1292 =head2   read_whole_problem_file
 1293 
 1294   read_whole_problem_file($filePath);
 1295 
 1296   Returns: A reference to a string containing
 1297            the contents of the file.
 1298 
 1299 Don't use for huge files. The file name will have .pg appended to it if it doesn't
 1300 already end in .pg.  Files may become double spaced.?  Check the join below. This is
 1301 used in importing additional .pg files as is done in the
 1302 sample problems translated from CAPA.
 1303 
 1304 =cut
 1305 
 1306 
 1307 sub read_whole_problem_file {
 1308   my $filePath = shift;
 1309     $filePath =~s/^\s*//; # get rid of initial spaces
 1310   $filePath =~s/\s*$//; # get rid of final spaces
 1311   $filePath = "$filePath.pg" unless $filePath =~ /\.pg$/;
 1312     read_whole_file($filePath);
 1313 }
 1314 
 1315 sub read_whole_file {
 1316   my $filePath = shift;
 1317     local (*INPUT);
 1318   open(INPUT, "<$filePath")|| die "$0: readWholeProblemFile subroutine: <BR>Can't read file $filePath";
 1319   local($/)=undef;
 1320   my $string = <INPUT>;  # can't append spaces because this causes trouble with <<'EOF'   \nEOF construction
 1321   close(INPUT);
 1322   \$string;
 1323 }
 1324 
 1325 
 1326 =head2 convertPath
 1327 
 1328   $path = convertPath($path);
 1329 
 1330 Normalizes the delimiters in the path using delimiter from C<&getDirDelim()>
 1331 which is defined in C<Global.pm>.
 1332 
 1333 =cut
 1334 
 1335 sub convertPath {
 1336   &main::convertPath;
 1337  }
 1338 
 1339 =head2 surePathToTmpFile
 1340 
 1341   surePathToTmpFile($path)
 1342   Returns: $path
 1343 
 1344 Defined in FILE.pl
 1345 
 1346 Creates all of the subdirectories between the directory specified
 1347 by C<&getCourseTempDirectory> and the address of the path.
 1348 
 1349 Uses
 1350 
 1351   &createDirectory($path,$Global::tmp_directory_permission, $Global::numericalGroupID)
 1352 
 1353 The path may  begin with the correct path to the temporary
 1354 directory.  Any other prefix causes a path relative to the temporary
 1355 directory to be created.
 1356 
 1357 The quality of the error checking could be improved. :-)
 1358 
 1359 =cut
 1360 
 1361 sub surePathToTmpFile {
 1362   &main::surePathToTmpFile;
 1363  }
 1364 
 1365 =head2 fileFromPath
 1366 
 1367   $fileName = fileFromPath($path)
 1368 
 1369 Defined in C<FILE.pl>.
 1370 
 1371 Uses C<&getDirDelim()> to determine the path delimiter.  Returns the last segment
 1372 of the path (after the last delimiter.)
 1373 
 1374 =cut
 1375 
 1376 sub fileFromPath {
 1377   &main::fileFromPath;
 1378 }
 1379 
 1380 =head2 directoryFromPath
 1381 
 1382 
 1383   $directoryPath = directoryFromPath($path)
 1384 
 1385 Defined in C<FILE.pl>.
 1386 
 1387 Uses C<&getDirDelim()> to determine the path delimiter.  Returns the initial segments
 1388 of the of the path (up to the last delimiter.)
 1389 
 1390 =cut
 1391 
 1392 sub directoryFromPath {
 1393   &main::directoryFromPath;
 1394 
 1395 }
 1396 
 1397 =head2 createFile
 1398 
 1399   createFile($filePath);
 1400 
 1401 Calls C<FILE.pl> version of createFile with
 1402 C<createFile($filePath,0660(permission),$Global::numericalGroupID)>
 1403 
 1404 =cut
 1405 
 1406 sub createFile {
 1407     my $filePath = shift;
 1408   &main::createFile($filePath, 0660,0);
 1409 }
 1410 
 1411 
 1412 
 1413 # This sort can cause troubles because of its special use of $a and $b
 1414 # Putting it in dangerousMacros.pl worked frequently, but not always.
 1415 # In particular ANS( ans_eva1 ans_eval2) caused trouble.
 1416 # One answer at a time did not --- very strange.
 1417 
 1418 
 1419 =head2 PGsort
 1420 
 1421 Because of the way sort is optimized in Perl, the symbols $a and $b
 1422 have special significance.
 1423 
 1424 C<sort {$a<=>$b} @list>
 1425 C<sort {$a cmp $b} @list>
 1426 
 1427 sorts the list numerically and lexically respectively.
 1428 
 1429 If C<my $a;> is used in a problem, before the sort routine is defined in a macro, then
 1430 things get badly confused.  To correct this, the following macros are defined in
 1431 dangerougMacros.pl which is evaluated before the problem template is read.
 1432 
 1433   PGsort sub { $_[0] <=> $_[1] }, @list;
 1434   PGsort sub { $_[0] cmp $_[1] }, @list;
 1435 
 1436 provide slightly slower, but safer, routines for the PG language. (The subroutines
 1437 for ordering are B<required>. Note the commas!)
 1438 
 1439 =cut
 1440 
 1441 # This sort can cause troubles because of its special use of $a and $b
 1442 # Putting it in dangerousMacros.pl worked frequently, but not always.
 1443 # In particular ANS( ans_eva1 ans_eval2) caused trouble.
 1444 # One answer at a time did not --- very strange.
 1445 
 1446 sub PGsort {
 1447   my $sort_order = shift;
 1448   die "Must supply an ordering function with PGsort: PGsort sub {\$a cmp \$b }, \@list\n" unless ref($sort_order) eq 'CODE';
 1449   sort {&$sort_order($a,$b)} @_;
 1450 }
 1451 
 1452 =head2 includePGtext
 1453 
 1454   includePGtext($string_ref, $envir_ref)
 1455 
 1456 Calls C<createPGtext> recursively with the $safeCompartment variable set to 0
 1457 so that the rendering continues in the current safe compartment.  The output
 1458 is the same as the output from createPGtext. This is used in processing
 1459 some of the sample CAPA files.
 1460 
 1461 =cut
 1462 
 1463 #this is a method for importing additional PG files from within one PG file.
 1464 # sub includePGtext {
 1465 #     my $self = shift;
 1466 #     my $string_ref =shift;
 1467 #     my $envir_ref = shift;
 1468 #     $self->environment($envir_ref);
 1469 #   $self->createPGtext($string_ref);
 1470 # }
 1471 # evaluation macros
 1472 
 1473 
 1474 
 1475 no strict;   # this is important -- I guess because eval operates on code which is not written with strict in mind.
 1476 
 1477 
 1478 
 1479 =head2 PG_restricted_eval
 1480 
 1481   PG_restricted_eval($string)
 1482 
 1483 Evaluated in package 'main'. Result of last statement is returned.
 1484 When called from within a safe compartment the safe compartment package
 1485 is 'main'.
 1486 
 1487 
 1488 =cut
 1489 
 1490 sub PG_restricted_eval {
 1491     local($string) = shift;  # local seems to be essential to make sure that the right version of $string is evaluated
 1492                              # Using my, things would work unless the contents of $string contained '$string'
 1493                              # Wheeeeeeeeeeee!!!!!!
 1494     my ($pck,$file,$line) = caller;
 1495     my $save_SIG_warn_trap = $SIG{__WARN__};  # this change doesn't seem to make any difference in how problem warnings are propagated.
 1496     $SIG{__WARN__} = sub { CORE::die @_};
 1497     my $save_SIG_die_trap = $SIG{__DIE__};
 1498     $SIG{__DIE__}= sub {CORE::die @_};
 1499     no strict;
 1500     my $out = eval  ("package main; " . $string );
 1501     my $errors =$@;
 1502     my $full_error_report = "PG_restricted_eval detected error at line $line of file $file \n"
 1503                 . $errors .
 1504                 "The calling package is $pck\n" if defined($errors) && $errors =~/\S/;
 1505     use strict;
 1506     $SIG{__DIE__} = $save_SIG_die_trap;
 1507     $SIG{__WARN__} = $save_SIG_warn_trap;
 1508 
 1509     return (wantarray) ?  ($out, $errors,$full_error_report) : $out;
 1510 }
 1511 
 1512 =head2 PG_answer_eval
 1513 
 1514 
 1515   PG_answer_eval($string)
 1516 
 1517 Evaluated in package defined by the current safe compartment.
 1518 Result of last statement is returned.
 1519 When called from within a safe compartment the safe compartment package
 1520 is 'main'.
 1521 
 1522 There is still some confusion about how these two evaluation subroutines work
 1523 and how best to define them.  It is useful to have two evaluation procedures
 1524 since at some point one might like to make the answer evaluations more stringent.
 1525 
 1526 =cut
 1527 
 1528 
 1529 sub PG_answer_eval {
 1530    local($string) = shift;   # I made this local just in case -- see PG_estricted_eval
 1531    my $errors = '';
 1532    my $full_error_report = '';
 1533    my ($pck,$file,$line) = caller;
 1534     # Because of the global variable $PG::compartment_name and $PG::safe_cmpt
 1535     # only one problem safe compartment can be active at a time.
 1536     # This might cause problems at some point.  In that case a cleverer way
 1537     # of insuring that the package stays in scope until the answer is evaluated
 1538     # will be required.
 1539 
 1540     # This is pretty tricky and doesn't always work right.
 1541     # We seem to need PG_priv instead of main when PG_answer_eval is called within a completion
 1542     # 'package PG_priv; '
 1543     my $save_SIG_warn_trap = $SIG{__WARN__};
 1544     $SIG{__WARN__} = sub { CORE::die @_};
 1545     my $save_SIG_die_trap = $SIG{__DIE__};
 1546     $SIG{__DIE__}= sub {CORE::die @_};
 1547     my $save_SIG_FPE_trap= $SIG{'FPE'};
 1548     $SIG{'FPE'} = \&Global::PG_floating_point_exception_handler;
 1549     #$SIG{'FPE'} = sub {exit(0)};  ## is this ok to comment this out?
 1550     no strict;
 1551     my $out = eval('package main;'.$string);
 1552     $out = '' unless defined($out);
 1553     $errors .=$@;
 1554 
 1555     $full_error_report = "ERROR: at line $line of file $file
 1556                 $errors
 1557                 The calling package is $pck\n" if defined($errors) && $errors =~/\S/;
 1558     use strict;
 1559     $SIG{__DIE__} = $save_SIG_die_trap;
 1560     $SIG{__WARN__} = $save_SIG_warn_trap;
 1561     $SIG{'FPE'} = $save_SIG_FPE_trap;
 1562     return (wantarray) ?  ($out, $errors,$full_error_report) : $out;
 1563 
 1564 
 1565 }
 1566 
 1567 sub dumpvar {
 1568     my ($packageName) = @_;
 1569 
 1570     local(*alias);
 1571 
 1572     sub emit {
 1573       print @_;
 1574     }
 1575 
 1576     *stash = *{"${packageName}::"};
 1577     $, = "  ";
 1578 
 1579     emit "Content-type: text/html\n\n<PRE>\n";
 1580 
 1581 
 1582     while ( ($varName, $globValue) = each %stash) {
 1583         emit "$varName\n";
 1584 
 1585   *alias = $globValue;
 1586   next if $varName=~/main/;
 1587 
 1588   if (defined($alias) ) {
 1589       emit "  \$$varName $alias \n";
 1590   }
 1591 
 1592   if ( defined(@alias) ) {
 1593       emit "  \@$varName @alias \n";
 1594   }
 1595   if (defined(%alias) ) {
 1596       emit "  %$varName \n";
 1597       foreach $key (keys %alias) {
 1598           emit "    $key => $alias{$key}\n";
 1599       }
 1600 
 1601 
 1602 
 1603   }
 1604     }
 1605     emit "</PRE></PRE>";
 1606 
 1607 
 1608 }
 1609 use strict;
 1610 
 1611 #### for error checking and debugging purposes
 1612 sub pretty_print_rh {
 1613   my $rh = shift;
 1614   foreach my $key (sort keys %{$rh})  {
 1615     warn "  $key => ",$rh->{$key},"\n";
 1616   }
 1617 }
 1618 # end evaluation subroutines
 1619 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9