[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 1383 - (download) (as text) (annotate)
Mon Jul 14 18:44:27 2003 UTC (9 years, 10 months ago) by gage
File size: 17136 byte(s)
Tweaked the links section a bit.  Moved instructor links
into their own submethod, since they were getting complicated.
--Mike

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9