[system] / branches / gage_dev / webwork2 / lib / WeBWorK.pm Repository:
ViewVC logotype

View of /branches/gage_dev/webwork2/lib/WeBWorK.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6967 - (download) (as text) (annotate)
Wed Jul 20 18:25:12 2011 UTC (22 months, 4 weeks ago) by gage
File size: 19691 byte(s)
merging localization code 
and some (not all) of Grant's modifications


    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK.pm,v 1.104 2010/05/15 18:44:26 gage Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK;
   18 
   19 =head1 NAME
   20 
   21 WeBWorK - Dispatch requests to the appropriate content generator.
   22 
   23 =head1 SYNOPSIS
   24 
   25  my $r = Apache->request;
   26  my $result = eval { WeBWorK::dispatch($r) };
   27  die "something bad happened: $@" if $@;
   28 
   29 =head1 DESCRIPTION
   30 
   31 C<WeBWorK> is the dispatcher for the WeBWorK system. Given an Apache request
   32 object, it performs authentication and determines which subclass of
   33 C<WeBWorK::ContentGenerator> to call.
   34 
   35 =cut
   36 
   37 BEGIN { $main::VERSION = "2.4.9"; }
   38 
   39 use strict;
   40 use warnings;
   41 use Time::HiRes qw/time/;
   42 use WeBWorK::Localize;
   43 # load WeBWorK::Constants before anything else
   44 # this sets package variables in several packages
   45 use WeBWorK::Constants;
   46 
   47 use WeBWorK::Authen;
   48 use WeBWorK::Authz;
   49 use WeBWorK::CourseEnvironment;
   50 use WeBWorK::DB;
   51 use WeBWorK::Debug;
   52 use WeBWorK::Request;
   53 use WeBWorK::Upload;
   54 use WeBWorK::URLPath;
   55 use WeBWorK::CGI;
   56 use WeBWorK::Utils qw(runtime_use writeTimingLogEntry);
   57 
   58 use mod_perl;
   59 use constant MP2 => ( exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} >= 2 );
   60 
   61 # Apache2 needs upload class
   62 BEGIN {
   63   if (MP2) {
   64     require Apache2::Upload;
   65     Apache2::Upload->import();
   66     require Apache2::RequestUtil;
   67     Apache2::RequestUtil->import();
   68   }
   69 }
   70 
   71 use constant LOGIN_MODULE => "WeBWorK::ContentGenerator::Login";
   72 use constant PROCTOR_LOGIN_MODULE => "WeBWorK::ContentGenerator::LoginProctor";
   73 
   74 BEGIN {
   75   # pre-compile all content generators
   76   # Login and LoginProctor need to be handled separately, since they don't have paths
   77   map { eval "require $_"; die $@ if $@ }
   78     WeBWorK::URLPath->all_modules,
   79     LOGIN_MODULE,
   80     PROCTOR_LOGIN_MODULE;
   81   # other candidates for preloading:
   82   # - DB Record, Schema, and Driver classes (esp. Driver::SQL as it loads DBI)
   83   # - CourseManagement subclasses (ditto. sql_single.pm)
   84   # - WeBWorK::PG::Local, which loads WeBWorK::PG::Translator
   85   # - Authen subclasses
   86 }
   87 
   88 our %SeedCE;
   89 
   90 sub dispatch($) {
   91   my ($apache) = @_;
   92   my $r = WeBWorK::Request->new($apache);
   93 
   94   my $method = $r->method;
   95   my $location = $r->location;
   96   my $uri = $r->uri;
   97   my $path_info = $r->path_info | "";
   98   my $args = $r->args || "";
   99   my $dir_config = $r->dir_config;
  100   my %conf_vars = map { $_ => $dir_config->{$_} } grep { /^webwork_/ } keys %$dir_config;
  101   @SeedCE{keys %conf_vars} = values %conf_vars;
  102 
  103   debug("\n\n===> Begin " . __PACKAGE__ . "::dispatch() <===\n\n");
  104   debug("Hi, I'm the new dispatcher!\n");
  105   debug(("-" x 80) . "\n");
  106 
  107   debug("Okay, I got some basic information:\n");
  108   debug("The apache location is $location\n");
  109   debug("The request method is $method\n");
  110   debug("The URI is $uri\n");
  111   debug("The path-info is $path_info\n");
  112   debug("The argument string is $args\n");
  113   #debug("The WeBWorK root directory is $webwork_root\n");
  114   #debug("The PG root directory is $pg_root\n");
  115   debug(("-" x 80) . "\n");
  116 
  117   debug("The first thing we need to do is munge the path a little:\n");
  118 
  119   ######################################################################
  120   # Create a URLPath  object
  121   ######################################################################
  122   my ($path) = $uri =~ m/$location(.*)/;
  123   $path = "/" if $path eq ""; # no path at all
  124 
  125   debug("We can't trust the path-info, so we make our own path.\n");
  126   debug("path-info claims: $path_info\n");
  127   debug("but it's really: $path\n");
  128   debug("(if it's empty, we set it to \"/\".)\n");
  129 
  130   $path =~ s|/+|/|g;
  131   debug("...and here it is without repeated slashes: $path\n");
  132 
  133   # lookbehind assertion for "not a slash"
  134   # matches the boundary after the last char
  135   $path =~ s|(?<=[^/])$|/|;
  136   debug("...and here it is with a trailing slash: $path\n");
  137 
  138   debug(("-" x 80) . "\n");
  139 
  140   debug("Now we need to look at the path a little to figure out where we are\n");
  141 
  142   debug("-------------------- call to WeBWorK::URLPath::newFromPath\n");
  143   my $urlPath = WeBWorK::URLPath->newFromPath($path, $r);
  144                               # pointer to parent request for access to the $ce and language translation ability
  145                               # need to add this pointer whenever a new URLPath is created.
  146   debug("-------------------- call to WeBWorK::URLPath::newFromPath\n");
  147 
  148   unless ($urlPath) {
  149     debug("This path is invalid... see you later!\n");
  150     die "The path '$path' is not valid.\n";
  151   }
  152 
  153   my $displayModule = $urlPath->module;
  154   my %displayArgs = $urlPath->args;
  155 
  156   unless ($displayModule) {
  157     debug("The display module is empty, so we can DECLINE here.\n");
  158     die "No display module found for path '$path'.";
  159   }
  160 
  161   debug("The display module for this path is: $displayModule\n");
  162   debug("...and here are the arguments we'll pass to it:\n");
  163   foreach my $key (keys %displayArgs) {
  164     debug("\t$key => $displayArgs{$key}\n");
  165   }
  166 
  167   my $selfPath = $urlPath->path;
  168   my $parent = $urlPath->parent;
  169   my $parentPath = $parent ? $parent->path : "<no parent>";
  170 
  171   debug("Reconstructing the original path gets us: $selfPath\n");
  172   debug("And we can generate the path to our parent, too: $parentPath\n");
  173   debug("(We could also figure out who our children are, but we'd need to supply additional arguments.)\n");
  174   debug(("-" x 80) . "\n");
  175 
  176   debug("The URLPath looks good, we'll add it to the request.\n");
  177   $r->urlpath($urlPath);
  178 
  179   debug("Now we want to look at the parameters we got.\n");
  180 
  181   debug("The raw params:\n");
  182   foreach my $key ($r->param) {
  183     my @vals = $r->param($key);
  184     my $vals = join(", ", map { "'$_'" } @vals);
  185     debug("\t$key => $vals\n");
  186   }
  187 
  188   #mungeParams($r);
  189   #
  190   #debug("The munged params:\n");
  191   #foreach my $key ($r->param) {
  192   # debug("\t$key\n");
  193   # debug("\t\t$_\n") foreach $r->param($key);
  194   #}
  195 
  196   debug(("-" x 80) . "\n");
  197 
  198   my $apache_hostname = $r->hostname;
  199   my $apache_port     = $r->get_server_port;
  200   my $apache_is_ssl   = ($r->subprocess_env('https') ? 1 : "");
  201   my $apache_root_url;
  202   if ($apache_is_ssl) {
  203     $apache_root_url = "https://$apache_hostname";
  204     $apache_root_url .= ":$apache_port" if $apache_port != 443;
  205   } else {
  206     $apache_root_url = "http://$apache_hostname";
  207     $apache_root_url .= ":$apache_port" if $apache_port != 80;
  208   }
  209 
  210 
  211   ####################################################################
  212   # Create Course Environment    $ce
  213   ####################################################################
  214   debug("We need to get a course environment (with or without a courseID!)\n");
  215   my $ce = eval { new WeBWorK::CourseEnvironment({
  216     %SeedCE,
  217     courseName => $displayArgs{courseID},
  218     # this is kind of a hack, but it's really the only sane way to get this
  219     # server information into the PG box
  220     apache_hostname => $apache_hostname,
  221     apache_port => $apache_port,
  222     apache_is_ssl => $apache_is_ssl,
  223     apache_root_url => $apache_root_url,
  224   }) };
  225   $@ and die "Failed to initialize course environment: $@\n";
  226   debug("Here's the course environment: $ce\n");
  227   $r->ce($ce);
  228 
  229 
  230   ######################
  231   # Localizing language
  232   ######################
  233   my $language= $ce->{language} || "en";
  234   $r->language_handle(WeBWorK::Localize->get_handle($language) );
  235 
  236   my @uploads;
  237   if (MP2) {
  238     my $upload_table = $r->upload;
  239     @uploads = values %$upload_table if defined $upload_table;
  240   } else {
  241     @uploads = $r->upload;
  242   }
  243   foreach my $u (@uploads) {
  244     # make sure it's a "real" upload
  245     next unless $u->filename;
  246 
  247     # store the upload
  248     my $upload = WeBWorK::Upload->store($u,
  249       dir => $ce->{webworkDirs}->{uploadCache}
  250     );
  251 
  252     # store the upload ID and hash in the file upload field
  253     my $id = $upload->id;
  254     my $hash = $upload->hash;
  255     $r->param($u->name => "$id $hash");
  256   }
  257 
  258   # create these out here. they should fail if they don't have the right information
  259   # this lets us not be so careful about whether these objects are defined when we use them.
  260   # instead, we just create the behavior that if they don't have a valid $db they fail.
  261   my $authz = new WeBWorK::Authz($r);
  262   $r->authz($authz);
  263 
  264   # figure out which authentication modules to use
  265   #my $user_authen_module;
  266   #my $proctor_authen_module;
  267   #if (ref $ce->{authen}{user_module} eq "HASH") {
  268   # if (exists $ce->{authen}{user_module}{$ce->{dbLayoutName}}) {
  269   #   $user_authen_module = $ce->{authen}{user_module}{$ce->{dbLayoutName}};
  270   # } else {
  271   #   $user_authen_module = $ce->{authen}{user_module}{"*"};
  272   # }
  273   #} else {
  274   # $user_authen_module = $ce->{authen}{user_module};
  275   #}
  276   #if (ref $ce->{authen}{proctor_module} eq "HASH") {
  277   # if (exists $ce->{authen}{proctor_module}{$ce->{dbLayoutName}}) {
  278   #   $proctor_authen_module = $ce->{authen}{proctor_module}{$ce->{dbLayoutName}};
  279   # } else {
  280   #   $proctor_authen_module = $ce->{authen}{proctor_module}{"*"};
  281   # }
  282   #} else {
  283   # $proctor_authen_module = $ce->{authen}{proctor_module};
  284   #}
  285 
  286   my $user_authen_module = WeBWorK::Authen::class($ce, "user_module");
  287 
  288   runtime_use $user_authen_module;
  289   my $authen = $user_authen_module->new($r);
  290   debug("Using user_authen_module $user_authen_module: $authen\n");
  291   $r->authen($authen);
  292 
  293   my $db;
  294 
  295   if ($displayArgs{courseID}) {
  296     debug("We got a courseID from the URLPath, now we can do some stuff:\n");
  297 
  298     unless (-e $ce->{courseDirs}->{root}) {
  299       die "Course '$displayArgs{courseID}' not found: $!";
  300     }
  301 
  302     debug("...we can create a database object...\n");
  303     $db = new WeBWorK::DB($ce->{dbLayout});
  304     debug("(here's the DB handle: $db)\n");
  305     $r->db($db);
  306 
  307     my $authenOK = $authen->verify;
  308     if ($authenOK) {
  309       my $userID = $r->param("user");
  310       debug("Hi, $userID, glad you made it.\n");
  311 
  312       # tell authorizer to cache this user's permission level
  313       $authz->setCachedUser($userID);
  314 
  315       debug("Now we deal with the effective user:\n");
  316       my $eUserID = $r->param("effectiveUser") || $userID;
  317       debug("userID=$userID eUserID=$eUserID\n");
  318       if ($userID ne $eUserID) {
  319         debug("userID and eUserID differ... seeing if userID has 'become_student' permission.\n");
  320         my $su_authorized = $authz->hasPermissions($userID, "become_student");
  321         if ($su_authorized) {
  322           debug("Ok, looks like you're allowed to become $eUserID. Whoopie!\n");
  323         } else {
  324           debug("Uh oh, you're not allowed to become $eUserID. Nice try!\n");
  325           die "You are not allowed to act as another user.\n";
  326         }
  327       }
  328 
  329       # set effectiveUser in case it was changed or not set to begin with
  330       $r->param("effectiveUser" => $eUserID);
  331 
  332       # if we're doing a proctored test, after the user has been authenticated
  333       # we need to also check on the proctor.  note that in the gateway quiz
  334       # module we double check this, to be sure that someone isn't taking a
  335       # proctored quiz but calling the unproctored ContentGenerator
  336       my $urlProducedPath = $urlPath->path();
  337       if ( $urlProducedPath =~ /proctored_quiz_mode/i ) {
  338         my $proctor_authen_module = WeBWorK::Authen::class($ce, "proctor_module");
  339         runtime_use $proctor_authen_module;
  340         my $authenProctor = $proctor_authen_module->new($r);
  341         debug("Using proctor_authen_module $proctor_authen_module: $authenProctor\n");
  342           my $procAuthOK = $authenProctor->verify();
  343 
  344         if (not $procAuthOK) {
  345           $displayModule = PROCTOR_LOGIN_MODULE;
  346         }
  347       }
  348     } else {
  349       debug("Bad news: authentication failed!\n");
  350       $displayModule = LOGIN_MODULE;
  351       debug("set displayModule to $displayModule\n");
  352     }
  353   }
  354 
  355   # store the time before we invoke the content generator
  356   my $cg_start = time; # this is Time::HiRes's time, which gives floating point values
  357 
  358   debug(("-" x 80) . "\n");
  359   debug("Finally, we'll load the display module...\n");
  360 
  361   runtime_use($displayModule);
  362 
  363   debug("...instantiate it...\n");
  364 
  365   my $instance = $displayModule->new($r);
  366 
  367   debug("...and call it:\n");
  368   debug("-------------------- call to ${displayModule}::go\n");
  369 
  370   my $result = $instance->go();
  371 
  372   debug("-------------------- call to ${displayModule}::go\n");
  373 
  374   my $cg_end = time;
  375   my $cg_duration = $cg_end - $cg_start;
  376   writeTimingLogEntry($ce, "[".$r->uri."]", sprintf("runTime = %.3f sec", $cg_duration)." ".$ce->{dbLayoutName}, "");
  377 
  378   debug("returning result: " . (defined $result ? $result : "UNDEF") . "\n");
  379       #@LimitedPolynomial::BOP::ISA; #FIXME this  is needed to zero out
  380       #@LimitedPolynomial::UOP::ISA;
  381       #\@LimitedPolynomial::BOP::ISA and prevent error messages of the form
  382       #[Sat May 15 14:23:08 2010] [warn] [client 127.0.0.1] [/webwork2/gage_course/test_set/6/]
  383       #Can't locate package LimitedPolynomial::BOP for @LimitedPolynomial::BOP::add::ISA at /opt/webwork/webwork2/lib/Apache/WeBWorK.pm line 115., referer: http://localhost/webwork2/gage_course/test_set/6/ no one knows why
  384   return $result;
  385 }
  386 
  387 sub mungeParams {
  388   my ($r) = @_;
  389 
  390   my @paramQueue;
  391 
  392   # remove all the params from the request, and store them in the param queue
  393   foreach my $key ($r->param) {
  394     push @paramQueue, [ $key => [ $r->param($key) ] ];
  395     $r->parms->unset($key)
  396   }
  397 
  398   # exhaust the param queue, decoding encoded params
  399   while (@paramQueue) {
  400     my ($key, $values) = @{ shift @paramQueue };
  401 
  402     if ($key =~ m/\,/) {
  403       # we have multiple params encoded in a single param
  404       # split them up and add them to the end of the queue
  405       push @paramQueue, map { [ $_, $values ] } split m/\,/, $key;
  406     } elsif ($key =~ m/\:/) {
  407       # we have a whole param encoded in a key
  408       # split it up and add it to the end of the queue
  409       my ($newKey, $newValue) = split m/\:/, $key;
  410       push @paramQueue, [ $newKey, [ $newValue ] ];
  411     } else {
  412       # this is a "normal" param
  413       # add it to the param list
  414       if (defined $r->param($key)) {
  415         # the param already exists -- append the values we have
  416         $r->param($key => [ $r->param($key), @$values ]);
  417       } else {
  418         # the param doesn't exist -- create it with the values we have
  419         $r->param($key => $values);
  420       }
  421     }
  422   }
  423 }
  424 
  425 
  426 # labeled_input subroutine
  427 #
  428 # Creates a form input element with a label added to the correct place.
  429 # Takes in up to six parameters:
  430 #
  431 # -type (type of input element), -name (name of input element), -id (id of the input element), -value (value of the input element), -label_text (the text on the label), -label_id (the id of the label)
  432 #
  433 # If any of the parameters are not specified, they default to "none".
  434 #
  435 # UPDATE: updated lable tags so that their "for" property will point to the id of the element that they are labeling. This means that entering an id for the input element becomes essentially mandatory if you want the tag to work correctly.
  436 
  437 # DEPRECATED - see below
  438 
  439 # sub labeled_input
  440 # {
  441   # my %param = (-type=>"none", -name=>"none", -value=>"none", -id=>"none", -label_text=>"none", -label_id=>"none", @_);
  442 
  443   # if($param{-type} eq "text" or $param{-type} eq "password" or $param{-type} eq "file"){
  444     # return CGI::label({-id=>$param{-label_id}, -for=>$param{-id}},$param{-label_text}).CGI::input({-type=>$param{-type}, -name=>$param{-name}, -value=>$param{-value}, -id=>$param{-id}}).CGI::br();
  445   # }
  446   # elsif($param{-type} eq "checkbox" or $param{-type} eq "radio"){
  447     # return CGI::input({-type=>$param{-type}, -name=>$param{-name}, -value=>$param{-value}, -id=>$param{-id}}).CGI::label({-id=>$param{-label_id}, -for=>$param{-id}},$param{-label_text}).CGI::br();
  448   # }
  449   # elsif($param{-type} eq "submit" or $param{-type} eq "button" or $param{-type} eq "reset"){
  450     # return CGI::input({-type=>$param{-type}, -name=>$param{-name}, -value=>$param{-value}, -id=>$param{-id}}).CGI::br();
  451   # }
  452   # else{
  453     # return "Not a valid input type";
  454   # }
  455 # }
  456 
  457 
  458 # CGI_labeled_input subroutine
  459 
  460 # A replacement to the labeled_input subroutine above, created when it was determined that the old subroutine was limited in that it did not allow for attributes other than the ones that it specified.
  461 
  462 # This subroutine rectifies that problem by taking in attributes for the input elements and label elements as hashes and simply entering them into the CGI routines, which already support attributes as hash parameters.
  463 
  464 # The way it attaches label tags is similar to the labeled_input subroutine.
  465 
  466 # This subroutine has also been expanded to be able to handle select elements.
  467 
  468 # Five parameters are taken in as a hash: -type (specifying the type of the input element), -id (specifying the id of the input element), -label_text (specifying the text to go in the label), -input_attr (a hash specifying any additional attributes for the input element, if any), and -label_attr (a hash specifying additional attributes for the label element, if any).
  469 
  470 # As before, all parameters are optional, with the scalar parameters defaulting to "none" and the hash parameters defaulting to empty.
  471 
  472 
  473 sub CGI_labeled_input
  474 {
  475   my %param = (-type=>"none", -id=>"none", -label_text=>"none", -input_attr=>{}, -label_attr=>{}, @_);
  476 
  477   $param{-input_attr}{-type} = $param{-type};
  478   $param{-input_attr}{-id} = $param{-id};
  479   $param{-label_attr}{-for} = $param{-id};
  480 
  481   if($param{-type} eq "text" or $param{-type} eq "password" or $param{-type} eq "file"){
  482     return CGI::label($param{-label_attr},$param{-label_text}).CGI::input($param{-input_attr});
  483   }
  484   elsif($param{-type} eq "checkbox" or $param{-type} eq "radio"){
  485     return CGI::input($param{-input_attr}).CGI::label($param{-label_attr},$param{-label_text});
  486   }
  487   elsif($param{-type} eq "submit" or $param{-type} eq "button" or $param{-type} eq "reset"){
  488     return CGI::input($param{-input_attr});
  489   }
  490   elsif($param{-type} eq "select"){
  491     return CGI::label($param{-label_attr},$param{-label_text}).CGI::popup_menu($param{-input_attr});
  492   }
  493   elsif($param{-type} eq "textarea"){
  494     return CGI::label($param{-label_attr},$param{-label_text}).CGI::br().CGI::br().CGI::textarea($param{-input_attr});
  495   }
  496   else{
  497     "Not a valid input type";
  498   }
  499 }
  500 
  501 # split_cap subroutine - ghe3
  502 
  503 # A sort of wrapper for the built-in split function which uses capital letters as a delimiter, and returns a string containing the separated substrings separated by a whitespace.  Used to make actionID's more readable.
  504 
  505 sub split_cap
  506 {
  507   my $str = shift;
  508 
  509   my @str_arr = split(//,$str);
  510   my $count = scalar(@str_arr);
  511 
  512   my $i = 0;
  513   my $prev = 0;
  514   my @result = ();
  515   my $hasCapital = 0;
  516   foreach(@str_arr){
  517     if($_ =~ /[A-Z]/){
  518       $hasCapital = 1;
  519       push(@result, join("", @str_arr[$prev..$i-1]));
  520       $prev = $i;
  521     }
  522     $i++;
  523   }
  524 
  525   unless($hasCapital){
  526     return $str;
  527   }
  528   else{
  529     push(@result, join("", @str_arr[$prev..$count-1]));
  530     return join(" ",@result);
  531   }
  532 }
  533 
  534 # underscore_to_whitespace subroutine
  535 
  536 # a simple subroutine for converting underscores in a given string to whitespace
  537 
  538 sub underscore_to_whitespace{
  539   my $str = shift;
  540 
  541   my @strArr = split("",$str);
  542   foreach(@strArr){
  543     if($_ eq "_"){
  544       $_ = " "
  545     }
  546   }
  547 
  548   my $result = join("",@strArr);
  549 
  550   return $result;
  551 }
  552 
  553 sub remove_duplicates{
  554   my @arr = @_;
  555 
  556   my %unique;
  557   my @result;
  558 
  559   foreach(@arr){
  560     if(defined $unique{$_}){
  561       next;
  562     }
  563     else{
  564       push(@result, $_);
  565       $unique{$_} = "seen";
  566     }
  567   }
  568 
  569   return @result;
  570 
  571 }
  572 
  573 =head1 AUTHOR
  574 
  575 Written by Dennis Lambe, malsyned at math.rochester.edu. Modified by Sam
  576 Hathaway, sh002i at math.rochester.edu.
  577 
  578 =cut
  579 
  580 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9