[system] / trunk / webwork2 / lib / WeBWorK / ContentGenerator.pm Repository:
ViewVC logotype

View of /trunk/webwork2/lib/WeBWorK/ContentGenerator.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 836 - (download) (as text) (annotate)
Wed May 14 21:20:13 2003 UTC (10 years ago) by malsyned
File size: 14859 byte(s)
Removed the Professor content generator and link.
Made a little progress on ProblemSetList.
-Dennis

    1 ################################################################################
    2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
    3 # $Id$
    4 ################################################################################
    5 
    6 package WeBWorK::ContentGenerator;
    7 
    8 =head1 NAME
    9 
   10 WeBWorK::ContentGenerator - base class for modules that generate page content.
   11 
   12 =cut
   13 
   14 use strict;
   15 use warnings;
   16 use Apache::Constants qw(:common);
   17 use CGI qw();
   18 use URI::Escape;
   19 use WeBWorK::DB;
   20 use WeBWorK::Utils qw(readFile);
   21 
   22 ################################################################################
   23 # This is a very unruly file, so I'm going to use very large comments to divide
   24 # it into logical sections.
   25 ################################################################################
   26 
   27 # new(Apache::Request, WeBWorK::CourseEnvironment) - create a new instance of a
   28 # content generator. Usually only called by the dispatcher, although one might
   29 # be able to use it for things like "sub-requests". Uh... uh... I have to think
   30 # about that one. The dispatcher uses this idiom:
   31 #
   32 #   WeBWorK::ContentGenerator::WHATEVER->new($r, $ce)->go(@whatever);
   33 #
   34 # and throws away the result ;)
   35 #
   36 sub new($$$$) {
   37   my ($invocant, $r, $ce, $db) = @_;
   38   my $class = ref($invocant) || $invocant;
   39   my $self = {
   40     r  => $r,
   41     ce => $ce,
   42     db => $db,
   43   };
   44   bless $self, $class;
   45   return $self;
   46 }
   47 
   48 ################################################################################
   49 # Invocation and template processing
   50 ################################################################################
   51 
   52 # go(@otherArguments) - render a page, using methods from the particular
   53 # subclass of ContentGenerator. @otherArguments is passed to each method, so
   54 # that the dispatcher can pass CG-specific data. The order of calls looks like
   55 # this:
   56 #
   57 #   * &pre_header_initialize - give subclasses a chance to do initialization
   58 #     necessary for generating the HTTP header.
   59 #   * &header - this class provides a standard HTTP header with Content-Type
   60 #     text/html. Subclasses are welcome to overload this for things like
   61 #     an image-creation content generator or a PDF generator.
   62 #   * &initialize - let subclasses do post-header initialization.
   63 #   * any "template escapes" defined in the system template and supported by
   64 #     the subclass. Generic implementations of &title and &body are provided.
   65 #
   66 sub go {
   67   my $self = shift;
   68   my $r = $self->{r};
   69   my $courseEnvironment = $self->{ce};
   70 
   71   $self->pre_header_initialize(@_) if $self->can("pre_header_initialize");
   72   $self->header(@_);
   73   return OK if $r->header_only;
   74 
   75   $self->initialize(@_) if $self->can("initialize");
   76   $self->template($courseEnvironment->{templates}->{system}, @_);
   77 
   78   return OK;
   79 }
   80 
   81 # template(STRING, @otherArguments) - parse a template, looking for escapes of
   82 # the form <!--#NAME ARG1="FOO" ARG2="BAR"--> and calling a member function NAME
   83 # (if available) for each NAME. The escapes are called like:
   84 #
   85 #   $self->NAME(@otherArguments, \%escapeArguments)
   86 #
   87 # where @otherArguments originates in the dispatcher and %escapeArguments is
   88 # parsed out of the escape itself (i.e. ARG1 => FOO, ARG2 => BAR)
   89 #
   90 sub template {
   91   my ($self, $templateFile) = (shift, shift);
   92   my $r = $self->{r};
   93   my $courseEnvironment = $self->{ce};
   94   my @ifstack = (1); # Start off in printing mode
   95     # say $ifstack[-1] to get the result of the last <#!--if-->
   96 
   97   # so even though the variable $/ APPEARS to contain a newline,
   98   # <TEMPLATE> is slurping the whole file into the first element of
   99   # @template ONLY AFTER THE TRANSLATOR RUNS. WTF!!!
  100   #
  101   #open(TEMPLATE, $templateFile) or die "Couldn't open template $templateFile";
  102   #my @template = <TEMPLATE>;
  103   #close TEMPLATE;
  104   #
  105   # Let's try something else instead:
  106   my @template = split /\n/, readFile($templateFile);
  107 
  108   foreach my $line (@template) {
  109     # This is incremental regex processing.
  110     # the /c is so that pos($line) doesn't die when the regex fails.
  111     while ($line =~ m/\G(.*?)<!--#(\w*)((?:\s+.*?)?)-->/gc) {
  112       my ($before, $function, $raw_args) = ($1, $2, $3);
  113       my @args = ($raw_args =~ /\S/) ? cook_args($raw_args) : ();
  114 
  115       if ($ifstack[-1]) {
  116         print $before;
  117       }
  118 
  119       if ($function eq "if") {
  120         # a predicate can only be true if everything else on the ifstack is already true, for ANDing
  121         push @ifstack, ($self->$function(@_, [@args]) && $ifstack[-1]);
  122       } elsif ($function eq "else" and @ifstack > 1) {
  123         $ifstack[-1] = not $ifstack[-1];
  124       } elsif ($function eq "endif" and @ifstack > 1) {
  125         pop @ifstack;
  126       } elsif ($ifstack[-1]) {
  127         if ($self->can($function)) {
  128           print $self->$function(@_, {@args});
  129         }
  130       }
  131     }
  132 
  133     if ($ifstack[-1]) {
  134       print substr($line, (defined pos $line) ? pos $line : 0), "\n";
  135     }
  136   }
  137 }
  138 
  139 # cook_args(STRING) - parses a string of the form ARG1="FOO" ARG2="BAR". Returns
  140 # a list which pairs into key/values and fits nicely in {}s.
  141 #
  142 sub cook_args($) { # ... also used by bin/wwdb, so watch out
  143   my ($raw_args) = @_;
  144   my @args = ();
  145 
  146   # Boy I love m//g in scalar context!  Go read the camel book, heathen.
  147   # First, get the whole token with the quotes on both ends...
  148   while ($raw_args =~ m/\G\s*(\w*)="((?:[^"\\]|\\.)*)"/g) {
  149     my ($key, $value) = ($1, $2);
  150     # ... then, rip out all the protecty backspaces
  151     $value =~ s/\\(.)/$1/g;
  152     push @args, $key => $value;
  153   }
  154 
  155   return @args;
  156 }
  157 
  158 # This is different.  It probably shouldn't print anything (except in debugging cases)
  159 # and it should return a boolean, not a string.  &if is called in a nonstandard way
  160 # by &template, with $args as an arrayref instead of a hashref.  this is a hack!  yay!
  161 
  162 # OK, this is a pluggin architecture.  it iterates through attributes of the "if" tag,
  163 # and for each predicate $p, it calls &if_$p in an object-oriented way, continuing the
  164 # grand templating theme of an object-oriented pluggable architecture using ->can($).
  165 sub if {
  166   my ($self, $args) = @_[0,-1];
  167   # A single if "or"s it's components.  Nesting produces "and".
  168 
  169   my @args = @$args; # Hahahahaha, get it?!
  170 
  171   if (@args % 2 != 0) {
  172     # flip out and kill people, but do not commit seppuku
  173     print '<!--&if recieved an uneven number of arguments.  This shouldn\'t happen, but I\'ll let it slide.-->\n';
  174   }
  175 
  176   while (@args > 1) {
  177     my ($key, $value) = (shift @args, shift @args);
  178 
  179     # a non-existent &if_$key is the same as a false result, but we're ORing, so it's OK
  180     my $sub = "if_$key"; # perl doesn't like it when you try to construct a string right in a method invocation
  181     if ($self->can("if_$key") and $self->$sub("$value")) {
  182       return 1;
  183     }
  184   }
  185 
  186   return 0;
  187 }
  188 
  189 ################################################################################
  190 # Macros used by content generators to render common idioms
  191 ################################################################################
  192 
  193 # pathMacro(HASHREF, LIST) - helper macro for <!--#path--> escape: the hash
  194 # reference contains the "style", "image", and "text" arguments to the escape.
  195 # The LIST consists of ordered key-value pairs of the form:
  196 #
  197 #   "Page Name" => URL
  198 #
  199 # If the page should not have a link associated with it, the URL should be left
  200 # empty. Authentication data is added to the URL so you don't have to. A fully-
  201 # formed path line is returned, suitable for returning by a function
  202 # implementing the #path escape.
  203 #
  204 sub pathMacro {
  205   my $self = shift;
  206   my %args = %{ shift() };
  207   my @path = @_;
  208   my $sep;
  209   if ($args{style} eq "image") {
  210     $sep = CGI::img({-src=>$args{image}, -alt=>$args{text}});
  211   } else {
  212     $sep = $args{text};
  213   }
  214   my $auth = $self->url_authen_args;
  215   my @result;
  216   while (@path) {
  217     my $name = shift @path;
  218     my $url = shift @path;
  219     push @result, $url
  220       ? CGI::a({-href=>"$url?$auth"}, $name)
  221       : $name;
  222   }
  223   return join($sep, @result), "\n";
  224 }
  225 
  226 sub siblingsMacro {
  227   my $self = shift;
  228   my @siblings = @_;
  229   my $sep = CGI::br();
  230   my $auth = $self->url_authen_args;
  231   my @result;
  232   while (@siblings) {
  233     my $name = shift @siblings;
  234     my $url = shift @siblings;
  235     push @result, $url
  236       ? CGI::a({-href=>"$url?$auth"}, $name)
  237       : $name;
  238   }
  239   return join($sep, @result), "\n";
  240 }
  241 
  242 sub navMacro {
  243   my $self = shift;
  244   my %args = %{ shift() };
  245   my $tail = shift;
  246   my @links = @_;
  247   my $auth = $self->url_authen_args;
  248   my $ce = $self->{ce};
  249   my $prefix = $ce->{webworkURLs}->{htdocs}."/images";
  250   my @result;
  251   while (@links) {
  252     my $name = shift @links;
  253     my $url = shift @links;
  254     my $img = shift @links;
  255     my $html =
  256       ($img && $args{style} eq "images")
  257       ? CGI::img(
  258         {src=>($prefix."/".$img.$args{imagesuffix}),
  259         border=>"",
  260         alt=>"$name"})
  261       : $name;
  262     unless($img && !$url) {
  263       push @result, $url
  264         ? CGI::a({-href=>"$url?$auth$tail"}, $html)
  265         : $html;
  266     }
  267   }
  268   return join($args{separator}, @result), "\n";
  269 }
  270 
  271 # hidden_fields(LIST) - return hidden <INPUT> tags for each field mentioned in
  272 # LIST (or all fields if list is empty), taking data from the current request.
  273 #
  274 sub hidden_fields($;@) {
  275   my $self = shift;
  276   my $r = $self->{r};
  277   my @fields = @_;
  278   @fields or @fields = $r->param;
  279   my $courseEnvironment = $self->{ce};
  280   my $html = "";
  281 
  282   foreach my $param (@fields) {
  283     my $value = $r->param($param);
  284     $html .= CGI::input({-type=>"hidden",-name=>"$param",-value=>"$value"});
  285   }
  286   return $html;
  287 }
  288 
  289 # hidden_authen_fields() - use hidden_fields to return hidden <INPUT> tags for
  290 # request fields used in authentication.
  291 #
  292 sub hidden_authen_fields($) {
  293   my $self = shift;
  294   return $self->hidden_fields("user","effectiveUser","key");
  295 }
  296 
  297 # url_args(LIST) - return a URL query string (without the leading `?')
  298 # containing values for each field mentioned in LIST, or all fields if list is
  299 # empty. Data is taken from the current request.
  300 #
  301 sub url_args($;@) {
  302   my $self = shift;
  303   my $r = $self->{r};
  304   my @fields = @_;
  305   @fields or @fields = $r->param;
  306   my $courseEnvironment = $self->{ce};
  307 
  308   my @pairs;
  309   foreach my $param (@fields) {
  310     my $value = $r->param($param) || "";
  311     push @pairs, uri_escape($param) . "=" . uri_escape($value);
  312   }
  313 
  314   return join("&", @pairs);
  315 }
  316 
  317 # url_authen_args() - use url_args to return a URL query string for request
  318 # fields used in authentication.
  319 #
  320 sub url_authen_args($) {
  321   my $self = shift;
  322   my $r = $self->{r};
  323   return $self->url_args("user","effectiveUser","key");
  324 }
  325 
  326 # print_form_data(BEGIN, MIDDLE, END, OMIT) - return a string containing request
  327 # fields not matched by OMIT, placing BEGIN before each field name, MIDDLE
  328 # between each field and its value, and END after each value. Values are taken
  329 # from the current request. OMIT is a quoted reguar expression.
  330 #
  331 sub print_form_data {
  332   my ($self, $begin, $middle, $end, $qr_omit) = @_;
  333   my $return_string = "";
  334   my $r=$self->{r};
  335   my @form_data = $r->param;
  336   foreach my $name (@form_data) {
  337     next if ($qr_omit and $name =~ /$qr_omit/);
  338     my @values = $r->param($name);
  339     foreach my $variable (qw(begin name middle value end)) {
  340       no strict 'refs';
  341       ${$variable} = "" unless defined ${$variable};
  342     }
  343     foreach my $value (@values) {
  344       $return_string .= "$begin$name$middle$value$end";
  345     }
  346   }
  347   return $return_string;
  348 }
  349 
  350 sub errorOutput($$$) {
  351   my ($self, $error, $details) = @_;
  352   return
  353     CGI::h2("Software Error"),
  354     CGI::p(<<EOF),
  355 WeBWorK has encountered a software error while attempting to process this problem.
  356 It is likely that there is an error in the problem itself.
  357 If you are a student, contact your professor to have the error corrected.
  358 If you are a professor, please consut the error output below for more informaiton.
  359 EOF
  360     CGI::h3("Error messages"), CGI::blockquote(CGI::pre($error)),
  361     CGI::h3("Error context"), CGI::blockquote(CGI::pre($details));
  362 }
  363 
  364 sub warningOutput($$) {
  365   my ($self, $warnings) = @_;
  366 
  367   return
  368     CGI::h2("Software Warnings"),
  369     CGI::p(<<EOF),
  370 WeBWorK has encountered warnings while attempting to process this problem.
  371 It is likely that this indicates an error or ambiguity in the problem itself.
  372 If you are a student, contact your professor to have the problem corrected.
  373 If you are a professor, please consut the error output below for more informaiton.
  374 EOF
  375     CGI::h3("Warning messages"),
  376     CGI::blockquote(CGI::pre($warnings)),
  377   ;
  378 }
  379 
  380 ################################################################################
  381 # Generic versions of template escapes
  382 ################################################################################
  383 
  384 # Reminder: here are the template functions currently defined:
  385 #
  386 # head
  387 # path
  388 #   style = text|image
  389 #   image = URL of image
  390 #   text  = text separator
  391 # loginstatus
  392 # links
  393 # siblings
  394 # nav
  395 #   style       = text|image
  396 #   imageprefix = prefix to image URL
  397 #   imagesuffix = suffix to image URL
  398 #   separator   = HTML to place in between links
  399 # title
  400 # body
  401 
  402 sub header {
  403   my $self = shift;
  404   my $r = $self->{r};
  405   $r->content_type('text/html');
  406   $r->send_http_header();
  407 }
  408 
  409 sub loginstatus {
  410   my $self = shift;
  411   my $r = $self->{r};
  412   my $user = $r->param("user");
  413   my $eUser = $r->param("effectiveUser");
  414   my $key = $r->param("key");
  415   return "" unless $key;
  416   my $exitURL = $r->uri() . "?user=$user&key=$key";
  417   print CGI::small("User:", "$user");
  418   if ($user ne $eUser) {
  419     print CGI::br(), CGI::font({-color=>'red'},
  420         CGI::small("Acting as:", "$eUser")
  421       ),
  422       CGI::br(), CGI::a({-href=>$exitURL},
  423         CGI::small("Stop Acting")
  424       );
  425   }
  426   return "";
  427 }
  428 
  429 # *** drunk code. rewrite.
  430 # also, this should be structured s.t. subclasses can add items to the links
  431 # area, i.e. "stacking"
  432 sub links {
  433   my $self = shift;
  434   my $ce = $self->{ce};
  435   my $db = $self->{db};
  436   my $userName = $self->{r}->param("user");
  437   my $courseName = $ce->{courseName};
  438   my $root = $ce->{webworkURLs}->{root};
  439   my $permLevel = $db->getPermissionLevel($userName)->permission();
  440   my $key = $db->getKey($userName)->key();
  441   return "" unless defined $key;
  442 
  443   # URLs to parts of the system
  444   my $probSets   = "$root/$courseName/?"            . $self->url_authen_args();
  445   my $prefs      = "$root/$courseName/options/?"    . $self->url_authen_args();
  446   my $instructor = "$root/$courseName/instructor/?" . $self->url_authen_args();
  447   my $help       = "$ce->{webworkURLs}->{docs}?"    . $self->url_authen_args();
  448   my $logout     = "$root/$courseName/logout/?"     . $self->url_authen_args();
  449 
  450   return
  451     CGI::a({-href=>$probSets}, "Problem Sets"). CGI::br().
  452     CGI::a({-href=>$prefs}, "User Options"). CGI::br().
  453     ($permLevel > 0
  454       ? CGI::a({-href=>$instructor}, "Instructor") . CGI::br()
  455       : "").
  456     CGI::a({-href=>$help}, "Help"). CGI::br().
  457     CGI::a({-href=>$logout}, "Log Out"). CGI::br()
  458   ;
  459 }
  460 
  461 # &if_can will return 1 if the current object->can("do $_[1]")
  462 sub if_can ($$) {
  463   my ($self, $arg) = (@_);
  464 
  465   if ($self->can("$arg")) {
  466     return 1;
  467   } else {
  468     return 0;
  469   }
  470 }
  471 
  472 # Every content generator is logged in unless it says otherwise.
  473 sub if_loggedin($$) {
  474   my ($self, $arg) = (@_);
  475 
  476   return $arg;
  477 }
  478 
  479 1;
  480 
  481 __END__
  482 
  483 =head1 AUTHOR
  484 
  485 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu
  486 and Sam Hathaway, sh002i (at) math.rochester.edu.
  487 
  488 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9