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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2939 - (download) (as text) (annotate)
Wed Oct 20 16:45:34 2004 UTC (8 years, 8 months ago) by sh002i
File size: 29935 byte(s)
add check for undefiend user param (closes bug #714). use $userID for
variable containing the ID of a user (not $User, which is typically used
for a user RECORD).

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm,v 1.52 2004/10/12 02:30:14 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::ContentGenerator::Hardcopy;
   18 use base qw(WeBWorK::ContentGenerator);
   19 
   20 
   21 =head1 NAME
   22 
   23 WeBWorK::ContentGenerator::Hardcopy - generate a PDF version of one or more
   24 problem sets.
   25 
   26 =cut
   27 
   28 ################################################################################
   29 ##
   30 ##   WARNING: This file has been hacked so that it will download
   31 ##            TeX files rather than displaying them in the browser.
   32 ##            In particular, if a TeX file is requested then
   33 ##            the value of the variable $pdfFileURL (in spite of its name)
   34 ##            will be the URL for the texFile, i.e.,
   35 ##                $pdfFileURL = $texFileURL  if TeX file is requested
   36 ##
   37 ##            wheeler@indiana.edu, 7/9/04
   38 ##
   39 ################################################################################
   40 
   41 use strict;
   42 use warnings;
   43 use CGI qw();
   44 use File::Path qw(rmtree);
   45 use WeBWorK::Form;
   46 use WeBWorK::PG;
   47 use WeBWorK::Utils qw(readFile makeTempDirectory);
   48 use Apache::Constants qw(:common REDIRECT);
   49 
   50 =head1 CONFIGURATION VARIABLES
   51 
   52 =over
   53 
   54 =item $PreserveTempFiles
   55 
   56 If true, don't delete temporary files.
   57 
   58 =cut
   59 
   60 our $PreserveTempFiles = 0 unless defined $PreserveTempFiles;
   61 
   62 =back
   63 
   64 =cut
   65 
   66 sub pre_header_initialize {
   67   my ($self) = @_;
   68   my $r = $self->r;
   69   my $ce = $r->ce;
   70   my $db = $r->db;
   71   my $authz = $r->authz;
   72   my $userID  = $r->param("user");
   73 
   74   my $singleSet       = $r->urlpath->arg("setID");
   75   my @sets            = $r->param("hcSet");
   76   my @users           = $r->param("hcUser");
   77   my $hardcopy_format = $r->param('hardcopy_format') ? $r->param('hardcopy_format') : '';
   78 
   79   # add singleSet to the list of sets
   80   if (defined $singleSet and $singleSet ne "") {
   81     $singleSet =~ s/^set//;
   82     unshift @sets, $singleSet unless grep { $_ eq $singleSet } @sets;
   83   }
   84   #die "single set is $singleSet and sets is ", join("|",@sets);
   85   # default user is the effectiveUser
   86   unless (@users) {
   87     unshift @users, $r->param("effectiveUser");
   88   }
   89 
   90   # this should never happen, but apparently it did once (see bug #714), so we check for it
   91   die "Parameter 'user' not defined. Can't continue." unless defined $userID;
   92 
   93   $self->{user}            = $db->getUser($userID); # checked
   94   die "user ", $userID, " (real user) not found."
   95     unless $self->{user};
   96 
   97   $self->{effectiveUser}   = $db->getUser($r->param("effectiveUser")); # checked
   98   die "user ", $r->param("effectiveUser"), " (effective user) not found."
   99     unless $self->{effectiveUser};
  100 
  101   #my $PermissionLevel = $db->getPermissionLevel($r->param("user")); # checked
  102   #if ($PermissionLevel) {
  103   # $self->{permissionLevel} = $PermissionLevel->permission();
  104   #} else {
  105   # die "permission level for user ", $r->param("user"), " (real user) not found.";
  106   #}
  107 
  108   $self->{sets}            = \@sets;
  109   $self->{users}           = \@users;
  110   $self->{hardcopy_format} = $hardcopy_format;
  111   $self->{errors}          = [];
  112   $self->{warnings}        = [];
  113 
  114   # is the user allowed to request multiple sets/users at a time?
  115   my $multiSet = $authz->hasPermissions($userID, "download_hardcopy_multiset");
  116   my $multiUser = $authz->hasPermissions($userID, "download_hardcopy_multiuser");
  117 
  118   if (@sets > 1 and not $multiSet) {
  119     $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again."];
  120   }
  121   if (@users > 1 and not $multiUser) {
  122     $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple users. Please select a single user and try again."];
  123   }
  124   if ($users[0] ne $self->{effectiveUser}->user_id and not $multiUser) {
  125     $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for other users."];
  126   }
  127 
  128   unless ($self->{generationError}) {
  129     if ($r->param("generateHardcopy")) {
  130       #my ($tempDir, $fileName) = eval { $self->generateHardcopy() };
  131       my ($pdfFileURL) = eval { $self->generateHardcopy() };
  132 
  133       $self->{generationError} = $@ if $@;
  134       #warn "pdfFileURL is $pdfFileURL";
  135       #warn "generation error is ".$self->{generationError};
  136       #warn "hardcopy_format is ".$self->{hardcopy_format};
  137       if ($self->{generationError}) {
  138         # In this case no correct pdf file was generated.
  139         # throw the error up higher.
  140         # The error is reported in body.
  141         # the tempDir was removed in generateHardcopy
  142 #     } elsif ( $self->{hardcopy_format} eq 'tex')   {
  143 #       # Only tex output was asked for, proceed to have the tex output
  144 #       # handled by the subroutine "body".
  145       } else {
  146         # information for redirect
  147         $self->{pdfFileURL} = $pdfFileURL;
  148       }
  149     }
  150   }
  151 }
  152 
  153 sub header {
  154   my ($self) = @_;
  155   my $r = $self->r;
  156 
  157   if (exists $self->{pdfFileURL}) {
  158     $r->header_out(Location => $self->{pdfFileURL} );
  159     $self->{noContent} = 1;
  160     return REDIRECT;
  161   }
  162   $r->content_type("text/html");
  163   $r->send_http_header();
  164 }
  165 
  166 # -----
  167 
  168 #sub path {
  169 # my ($self, $args) = @_;
  170 #
  171 # my $ce = $self->{ce};
  172 # my $root = $ce->{webworkURLs}->{root};
  173 # my $courseName = $ce->{courseName};
  174 # return $self->pathMacro($args,
  175 #   "Home" => "$root",
  176 #   $courseName => "$root/$courseName",
  177 #   "Hardcopy Generator" => "",
  178 # );
  179 #}
  180 #
  181 #sub title {
  182 # return "Hardcopy Generator";
  183 #}
  184 
  185 sub body {
  186   my ($self) = @_;
  187 
  188   if ($self->{generationError}) {
  189     if (ref $self->{generationError} eq "ARRAY") {
  190       my ($disposition, @rest) = @{$self->{generationError}};
  191       if ($disposition eq "PGFAIL") {
  192         $self->multiErrorOutput(@{$self->{errors}});
  193         return "";
  194       } elsif ($disposition eq "FAIL") {
  195         print $self->errorOutput(@rest);
  196         return "";
  197       } elsif ($disposition eq "RETRY") {
  198         print $self->errorOutput(@rest);
  199       } else { # a "simple" error
  200         print CGI::p(CGI::font({-color=>"red"}, @rest));
  201       }
  202     } else {
  203       # not something we were expecting...
  204       die $self->{generationError};
  205     }
  206   }
  207   if (@{$self->{warnings}}) {
  208     # FIXME: this code will only be reached if there was also a
  209     # generation error, because otherwise the module will send
  210     # the PDF instead. DAMN!
  211     $self->multiWarningOutput(@{$self->{warnings}});
  212   }
  213 # if ($self->{hardcopy_format} eq 'tex') {
  214 #   my $r_tex_content = $self->{r_tex_content};
  215 #   return $$r_tex_content;
  216 # }
  217   $self->displayForm();
  218 }
  219 
  220 sub multiErrorOutput($@) {
  221   my ($self, @errors) = @_;
  222 
  223   print CGI::h2("Compile Errors");
  224   print CGI::p(<<EOF);
  225 WeBWorK has encountered one or more  errors while attempting to process
  226 these problem sets. It is likely that there are errors in the problems
  227 themselves. If you are a student, contact your professor to have the errors
  228 corrected. If you are a professor, please consult the error output below for
  229 more information.
  230 EOF
  231   foreach my $error (@errors) {
  232       my $user = $error->{user};
  233       my $userName = $user->user_id . ' ('.$user->first_name.' '.$user->last_name. ')';
  234     print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}, "for $userName");
  235     print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message}));
  236     print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context}));
  237   }
  238 }
  239 
  240 sub multiWarningOutput($@) {
  241   my ($self, @warnings) = @_;
  242 
  243   print CGI::h2("Software Warnings");
  244   print CGI::p(<<EOF);
  245 WeBWorK has encountered one or more warnings while attempting to process these
  246 problem sets. It is likely that this indicates errors or ambiguitiees in the
  247 problems themselves. If you are a student, contact your professor to have the
  248 problems corrected. If you are a professor, please consut the warning output
  249 below for more informaiton.
  250 EOF
  251   foreach my $warning (@warnings) {
  252     print CGI::h3("Set: ", $warning->{set}, ", Problem: ", $warning->{problem});
  253     print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($warning->{message}));
  254   }
  255 }
  256 
  257 # -----
  258 
  259 sub displayForm($) {
  260   my ($self) = @_;
  261   my $r      = $self->r;
  262   my $db     = $r->db;
  263   my $authz  = $r->authz;
  264   my $userID   =  $r->param("user");
  265   my $ss= '';
  266   my $aa= ' a ';
  267   if ($authz->hasPermissions($userID, "download_hardcopy_multiuser")) {
  268     $ss= 's';
  269     $aa= ' ';
  270   }
  271 
  272   print CGI::start_p(), "Select the problem set$ss for which to generate${aa}hardcopy version$ss.";
  273   if ($authz->hasPermissions($userID, "download_hardcopy_multiuser")) {
  274     print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair.";
  275   }
  276   print CGI::end_p();
  277 
  278   my $download_texQ = $authz->hasPermissions($userID, "download_hardcopy_format_tex");
  279 
  280   #  ##########construct action URL #################
  281   my $ce         = $r->ce;
  282   my $root       = $ce->{webworkURLs}->{root};
  283   my $courseName = $ce->{courseName};
  284   my $actionURL  = "$root/$courseName/hardcopy/";
  285   #  ################################################
  286 
  287   my $phrase_for_privileged_users = '';
  288   $phrase_for_privileged_users ='to privileged users or' if $authz->hasPermissions($userID, "download_hardcopy_multiuser");
  289 
  290   print CGI::start_form(-method=>"POST", -action=>$actionURL);
  291   print $self->hidden_authen_fields();
  292   print CGI::h3("Options");
  293   print CGI::p("You may choose to show any of the following data. Correct answers and solutions are only available $phrase_for_privileged_users after the answer date of the problem set.");
  294   print CGI::p(
  295     CGI::checkbox(
  296       -name    => "showCorrectAnswers",
  297       -checked => $r->param("showCorrectAnswers") || 0,
  298       -label   => "Correct answers",
  299     ), CGI::br(),
  300     CGI::checkbox(
  301       -name    => "showHints",
  302       -checked => $r->param("showHints") || 0,
  303       -label   => "Hints",
  304     ), CGI::br(),
  305     CGI::checkbox(
  306       -name    => "showSolutions",
  307       -checked => $r->param("showSolutions") || 0,
  308       -label   => "Solutions",
  309     ),
  310   );
  311   print CGI::start_table({-width=>"100%"}), CGI::start_Tr({-valign=>"top"});
  312 
  313   my $multiSet          = $authz->hasPermissions($userID, "download_hardcopy_multiset");
  314   my $multiUser         = $authz->hasPermissions($userID, "download_hardcopy_multiuser");
  315   my $preOpenSets       = $authz->hasPermissions($userID, "view_unopened_sets");
  316   my $unpublishedSets   = $authz->hasPermissions($userID, "view_unpublished_sets");
  317   my $effectiveUserName = $self->{effectiveUser}->user_id;
  318   my @setNames          = $db->listUserSets($effectiveUserName);
  319   my @sets              = $db->getMergedSets( map { [$effectiveUserName, $_] }  @setNames ); # checked
  320   @sets                 = grep { defined $_ and ($preOpenSets or $_->open_date < time) and ($unpublishedSets or $_->published) } @sets;
  321   @sets                 = sort { $a->set_id cmp $b->set_id } @sets;
  322   @setNames             = map( {$_->set_id } @sets );  # get sorted version of setNames
  323   my %setLabels         = map( {($_->set_id, "set ".$_->set_id )} @sets );
  324   my (@users, @userNames,%userLabels);
  325 
  326   if ($multiUser) {
  327     @userNames    = $db->listUsers();
  328     @users        = $db->getUsers(@userNames); # checked
  329     @users = grep { defined $_ } @users;
  330     @users        = sort { $a->last_name cmp $b->last_name } @users;
  331     @userNames    = map( {$_->user_id} @users );  # get sorted version of user names
  332     %userLabels   = map( {($_->user_id , $_->last_name .", ". $_->first_name ." --- ". $_->user_id   ) } @users );
  333   }
  334   # set selection menu
  335   {
  336     print CGI::start_td();
  337     my $number_of_sets   = @{$self->{sets}};
  338     print CGI::h3("Sets: $number_of_sets pre-selected");
  339     print CGI::scrolling_list(-name=>'hcSet',
  340                  -values=>\@setNames,
  341                  -labels=>\%setLabels,
  342                  -size  => 10,
  343                  -multiple => $multiSet,
  344                  -defaults => $self->{sets},
  345     );
  346     print CGI::end_td();
  347   }
  348 
  349   # user selection menu
  350   if ($multiUser) {
  351     print CGI::start_td();
  352     my $number_of_users       =   @{$self->{users}};
  353     print CGI::h3("Users: $number_of_users pre-selected");
  354 
  355     print CGI::scrolling_list(-name=>'hcUser',
  356                  -values=>\@userNames,
  357                  -labels=>\%userLabels,
  358                  -size  => 10,
  359                  -multiple => 'true',
  360                  -defaults => $self->{users},
  361     );
  362     print CGI::end_td();
  363   }
  364 
  365   print CGI::end_Tr(), CGI::end_table();
  366   if ($download_texQ) {  # provide choice of pdf or tex output
  367     print CGI::p( {-align => "center"},
  368         CGI::radio_group(
  369               -name=>"hardcopy_format",
  370               -values=>['pdf', 'tex'],
  371               -default=>'pdf',
  372               -labels=>{'tex'=>'TeX','pdf'=>'PDF'}
  373         ),
  374     );
  375   } else {   # only pdf output available
  376     print CGI::hidden(-name=>'hardcopy_format',-value=>'pdf');
  377   }
  378   print CGI::p({-align=>"center"},
  379     CGI::submit(-name=>"generateHardcopy", -label=>"Generate Hardcopy"));
  380   print CGI::end_form();
  381 
  382   return "";
  383 }
  384 
  385 sub generateHardcopy($) {
  386   my ($self) = @_;
  387   my $r      = $self->r;
  388   my $ce     = $r->ce;
  389   my $authz  = $r->authz;
  390   my $userID   = $r->param("user");
  391   my @sets = @{$self->{sets}};
  392   my @users = @{$self->{users}};
  393   my $multiSet = $authz->hasPermissions($userID, "download_hardcopy_multiset");
  394   my $multiUser = $authz->hasPermissions($userID, "download_hardcopy_multiuser");
  395   # sanity checks
  396   unless (@sets) {
  397     die ["RETRY", "No sets were specified."];
  398   }
  399   unless (@users) {
  400     die ["RETRY", "No users were specified."];
  401   }
  402 
  403   # determine where hardcopy is going to go
  404   my $tempDir = makeTempDirectory($ce->{webworkDirs}->{tmp}, "webwork-hardcopy");
  405 
  406   # determine name of PDF file  #FIXME it might be best to have the effective user in here somewhere
  407   my $courseName = $ce->{courseName};
  408   my $fileNameSet = (@sets > 1 ? "multiset" : $sets[0]);
  409   my $fileNameUser = (@users > 1 ? "multiuser" : $users[0]);
  410   my $fileName = "$courseName.$fileNameUser.$fileNameSet.pdf";
  411 
  412   # for each user ... generate TeX for each set
  413   my $tex;
  414   #
  415   # the document tex preamble
  416   $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{preamble});
  417   # separate users by page break, or something
  418   foreach my $user (@users) {
  419     $tex .=  $self->getMultiSetTeX($user, @sets);
  420       if (@users) {
  421       # separate users, but not after the last set
  422       $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{userDivider});
  423     }
  424 
  425   }
  426   # the document postamble
  427   $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{postamble});
  428 
  429   # deal with PG errors
  430   if (@{$self->{errors}}) {
  431     die ["PGFAIL"];
  432   }
  433 
  434   # FIXME: add something like:
  435   #if (@{$self->{warnings}}) {
  436   # $self->{generationWarnings} = 1;
  437   #}
  438   # ???????
  439 
  440   # "try" to generate pdf or return TeX file
  441   my $pdfFileURL = undef;
  442   if ($self->{hardcopy_format} eq 'pdf' ) {
  443     my $errors = '';
  444     $pdfFileURL = eval { $self->latex2pdf($tex, $tempDir, $fileName) };
  445     if ($@) {
  446       $errors = $@;
  447       #$errors =~ s/\n/<br>/g;  # make this readable on HTML FIXME make this a Utils. filter (Error2HTML)
  448       # clean up temp directory
  449       # FIXME this clean up done in latex2pdf?  rmtree($tempDir);
  450       die ["FAIL", "Failed to generate PDF from tex", $errors]; #throw error to subroutine body
  451     } else {
  452         # pass the relative temp file path back up to go subroutine
  453         # to have an appropriate redirect generated.
  454 
  455 
  456     }
  457   } elsif ($self->{hardcopy_format} eq 'tex')    {
  458 
  459     my $TeXdownloadFileName = "$courseName.$fileNameUser.$fileNameSet.tex";
  460 
  461     # Location for hardcopy file to be downloaded
  462     # FIXME  this should use surePathToTmpFile
  463     my $hardcopyTempDirectory = $ce->{courseDirs}->{html_temp}."/hardcopy";
  464     mkdir ($hardcopyTempDirectory)  or die "Unable to make $hardcopyTempDirectory" unless -e $hardcopyTempDirectory;
  465     my $hardcopyFilePath        =  "$hardcopyTempDirectory/$TeXdownloadFileName";
  466     my $hardcopyFileURL         =  $ce->{courseURLs}->{html_temp}."/hardcopy/$TeXdownloadFileName";
  467     $self->{hardcopyFilePath}   =  $hardcopyFilePath;
  468     $self->{hardcopyFileURL}    =  $hardcopyFileURL;
  469     # write the tex file
  470     local *TEX;
  471     open TEX, ">", $hardcopyFilePath or die "Failed to open $hardcopyFilePath: $!\n".CGI::br();
  472     print TEX $tex;
  473     close TEX;
  474 
  475     $pdfFileURL = $hardcopyFileURL;
  476 
  477     if ($PreserveTempFiles) {
  478       warn "Temporary directory preserved at '$tempDir'.\n";
  479     } else {
  480       rmtree($tempDir);
  481     }
  482 
  483 #      $tex = protect_HTML($tex);
  484 #      #$tex =~ s/\n/\<br\>\n/g;
  485 #      $tex = join('', ("<pre>\n",$tex,"\n</pre>\n"));
  486 #   $self->{r_tex_content} = \$tex;
  487 
  488   } else {
  489 
  490 
  491     die["FAIL", "Hard copy format |".$self->{hardcopy_format}. "| not recognized."];
  492 
  493   }
  494   #return $tempDir, $fileName;
  495   # return $pdfFilePath;
  496   return $pdfFileURL;
  497 }
  498 
  499 # -----
  500 
  501 sub latex2pdf {
  502   # this is a little ad-hoc function which I will replace with a LaTeX
  503   # module at some point (or put it in Utils).
  504   my ($self, $tex, $tempDir, $fileName) = @_;
  505   my $r = $self->r;
  506   my $ce = $r->ce;
  507 
  508   my $finalFile = "$tempDir/$fileName";
  509 
  510   # Location for hardcopy file to be downloaded
  511   # FIXME  this should use surePathToTmpFile
  512   my $hardcopyTempDirectory = $ce->{courseDirs}->{html_temp}."/hardcopy";
  513   mkdir ($hardcopyTempDirectory)  or die "Unable to make $hardcopyTempDirectory" unless -e $hardcopyTempDirectory;
  514   my $hardcopyFilePath        =  "$hardcopyTempDirectory/$fileName";
  515   my $hardcopyFileURL         =  $ce->{courseURLs}->{html_temp}."/hardcopy/$fileName";
  516   $self->{hardcopyFilePath}   =  $hardcopyFilePath;
  517   $self->{hardcopyFileURL}    =  $hardcopyFileURL;
  518 
  519   ## create a temporary directory for tex to shit in
  520   # - we're using the existing temp dir. now
  521 
  522   my $wd = $tempDir;
  523   my $texFile = "$wd/hardcopy.tex";
  524   my $pdfFile = "$wd/hardcopy.pdf";
  525   my $logFile = "$wd/hardcopy.log";
  526 
  527   # write the tex file
  528   local *TEX;
  529   open TEX, ">", $texFile or die "Failed to open $texFile: $!\n".CGI::br();
  530   print TEX $tex;
  531   close TEX;
  532 
  533   # call pdflatex - we don't want to chdir in the mod_perl process, as
  534   # that might step on the feet of other things (esp. in Apache 2.0)
  535   my $pdflatex = $ce->{externalPrograms}->{pdflatex};
  536   my $pdflatexResult = system "cd $wd && $pdflatex $texFile";
  537 
  538   # Even with errors there may be a valid pdfFile.  Move it to where we can get it.
  539   if (-e $pdfFile) {
  540 
  541        # moving to course tmp/hardcopy directory
  542       system "/bin/mv", $pdfFile, $hardcopyFilePath
  543       and die "Failed to mv: $pdfFile to  $hardcopyFilePath<br> Quite likely this means that there ".
  544               "is not sufficient write permission for some directory.<br>$!\n".CGI::br();
  545   }
  546   # Alert the world that the tex file did not process perfectly.
  547   if ($pdflatexResult) {
  548     # something bad happened
  549     my $textErrorMessage = "Call to $pdflatex failed: $!\n".CGI::br();
  550 
  551     if (-e $hardcopyFilePath ) {
  552        # FIXME  Misuse of html tags!!!
  553       $textErrorMessage.= "<h4>Some pdf output was produced and is available ". CGI::a({-href=>$hardcopyFileURL},"here.</h4>").CGI::hr();
  554     }
  555     # report logfile
  556     if (-e $logFile) {
  557       $textErrorMessage .= "pdflatex ran, but did not succeed. This suggests an error in the TeX\n".CGI::br();
  558       $textErrorMessage .= "version of one of the problems, or a problem with the pdflatex system.\n".CGI::br();
  559       my $logFileContents = eval { readTexErrorLog($logFile) };
  560       $logFileContents    .=  CGI::hr().CGI::hr();
  561       $logFileContents    .= eval { formatTexFile($texFile)     };
  562       if ($@) {
  563         $textErrorMessage .= "Additionally, the pdflatex log file could not be read, though it exists.\n".CGI::br();
  564       } else {
  565         $textErrorMessage .= "The essential contents of the TeX log are as follows:\n".CGI::hr().CGI::br();
  566         $textErrorMessage .= "$logFileContents\n".CGI::br().CGI::br();
  567       }
  568     } else {
  569       $textErrorMessage .= "No log file was created, suggesting that pdflatex never ran. Check the WeBWorK\n".CGI::br();
  570       $textErrorMessage .= "configuration to ensure that the path to pdflatex is correct.\n".CGI::br();
  571     }
  572     die $textErrorMessage;
  573   }
  574 
  575 
  576 
  577   ## remove temporary directory
  578   if ($PreserveTempFiles) {
  579     warn "Working directory preserved at '$wd'.\n";
  580   } else {
  581     rmtree($wd, 0, 0);
  582   }
  583 
  584 
  585   -e $hardcopyFilePath or die "Failed to create $finalFile for no apparent reason.\n";
  586   # return hardcopyFilePath;
  587   return $hardcopyFileURL;
  588 }
  589 
  590 # -----
  591 # FIXME move to Utils? probably not
  592 
  593 sub readTexErrorLog {
  594   my $filePath = shift;
  595   my $print_error_switch = 0;
  596   my $line='';
  597   my @message=();
  598   #local($/ ) = "\n";
  599     open(LOGFILE,"<$filePath") or die "Can't read $filePath";
  600     while (<LOGFILE>) {
  601       $line = $_;
  602       $print_error_switch = 1  if $line =~ /^!/;  # after a fatal error start printing messages
  603     push(@message, protect_HTML($line)) if $print_error_switch;
  604     }
  605     close(LOGFILE);
  606     join("<br>\n",@message);
  607 }
  608 
  609 sub formatTexFile {
  610   my $texFilePath   = shift;
  611     open (TEXFILE, "$texFilePath")
  612                  or die "Can't open tex source file: path= $texFilePath: $!";
  613 
  614   my @message       = ();
  615     push @message, '<BR>\n<h3>TeX Source File:</h3><BR>\n',     ;
  616 
  617     my $lineNumber    = 1;
  618     while (<TEXFILE>) {
  619     push @message, protect_HTML("$lineNumber $_")."\n";
  620         $lineNumber++;
  621     }
  622     close(TEXFILE);
  623     #push @message, '</pre>';
  624     join("<br>\n",@message);
  625 }
  626 sub protect_HTML {
  627   my $line = shift;
  628   chomp($line);
  629   $line =~s/\&/&amp;/g;
  630   $line =~s/</&lt;/g;
  631   $line =~s/>/&gt;/g;
  632   $line;
  633 }
  634 sub texBlockComment(@) { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; }
  635 
  636 sub getMultiSetTeX {
  637   my ($self, $effectiveUserName,@sets) = @_;
  638   my $ce = $self->r->ce;
  639   my $tex = "";
  640 
  641 
  642 
  643   while (defined (my $setName = shift @sets)) {
  644     $tex .= $self->getSetTeX($effectiveUserName, $setName);
  645     if (@sets) {
  646       # divide sets, but not after the last set
  647       $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider});
  648     }
  649   }
  650 
  651 
  652 
  653   return $tex;
  654 }
  655 
  656 sub getSetTeX {
  657   my ($self, $effectiveUserName, $setName) = @_;
  658   my $r = $self->r;
  659   my $ce = $r->ce;
  660   my $db = $r->db;
  661 
  662   # FIXME (debug code line next)
  663   # print STDERR "Creating set $setName for $effectiveUserName \n";
  664 
  665   # FIXME We could define a default for the effective user if no correct name is passed in.
  666   # I'm not sure that it is wise.
  667   my $effectiveUser = $db->getUser($effectiveUserName); # checked
  668   die "effective user ($effectiveUserName) does not exist."
  669     unless defined $effectiveUser;
  670 
  671   my @problemNumbers = sort { $a <=> $b }
  672     $db->listUserProblems($effectiveUserName, $setName);
  673 
  674   # get header and footer
  675   my $set       = $db->getMergedSet($effectiveUserName, $setName); # checked
  676   my $setHeader = (ref($set) && $set->hardcopy_header) ? $set->hardcopy_header: $ce->{webworkFiles}->{hardcopySnippets}->{setHeader};
  677   # database doesn't support the following yet :(
  678   #my $setFooter = $wwdb->getMergedSet($effectiveUserName, $setName)->set_footer
  679   # || $ce->{webworkFiles}->{hardcopySnippets}->{setFooter};
  680   # so we don't allow per-set customization, which is probably okay :)
  681   my $setFooter = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter};
  682 
  683   my $tex = "";
  684 
  685   # render header
  686   $tex .= texBlockComment("BEGIN $setName : $setHeader");
  687   $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setHeader);
  688 
  689   # render each problem
  690   while (my $problemNumber = shift @problemNumbers) {
  691           #
  692           #  DPVC -- do problem divider ABOVE the problem, rather than below it
  693           #
  694           $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider});
  695     #
  696     #  /DPVC
  697     #
  698     $tex .= texBlockComment("BEGIN $setName : $problemNumber");
  699     $tex .= $self->getProblemTeX($effectiveUser,$setName, $problemNumber);
  700     #
  701     #  DPVC -- no need for it here since we do it above
  702     #
  703     #if (@problemNumbers) {
  704     # # divide problems, but not after the last problem
  705     # $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider});
  706     #}
  707     #
  708     # /DPVC
  709     #
  710   }
  711 
  712   # render footer
  713   $tex .= texBlockComment("BEGIN $setName : $setFooter");
  714   $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setFooter);
  715 
  716   return $tex;
  717 }
  718 
  719 sub getProblemTeX {
  720     $WeBWorK::timer1 ->continue("hardcopy: begin processing problem") if defined($WeBWorK::timer1);
  721   my ($self, $effectiveUser, $setName, $problemNumber, $pgFile) = @_;
  722   my $r      = $self->r;
  723   my $ce     = $r->ce;
  724   my $db     = $r->db;
  725   my $authz  = $r->authz;
  726   my $userID   = $r->param("user");
  727   # Should we provide a default user ? I think not FIXME
  728 
  729   # $effectiveUser = $self->{effectiveUser} unless defined($effectiveUser);
  730   my $permissionLevel = $self->{permissionLevel};
  731   my $set  = $db->getMergedSet($effectiveUser->user_id, $setName); # checked
  732   unless (ref($set) )  {  # return error if no set is defined
  733     push(@{$self->{warnings}},
  734          setName => $setName,
  735          problem => 0,
  736          message => "No set $setName exists for ".$effectiveUser->first_name.' '.
  737                         $effectiveUser->last_name.' ('.$effectiveUser->user_id.' )'
  738       );
  739       return "No set $setName for ".$effectiveUser->user_id;
  740   }
  741 
  742   my $preOpenSets = $authz->hasPermissions($userID, "view_unopened_sets");
  743   my $unpublishedSets = $authz->hasPermissions($userID, "view_unpublished_sets");
  744     unless ( ($preOpenSets or $set->open_date < time) and ($unpublishedSets or $set->published) )  {  # return error if set is invisible
  745     push(@{$self->{warnings}},
  746          setName => $setName,
  747          problem => 0,
  748          message => "The set $setName is hidden for ".$effectiveUser->first_name.' '.
  749                         $effectiveUser->last_name.' ('.$effectiveUser->user_id.' )'
  750       );
  751       return "The set $setName is not yet ready for ".$effectiveUser->user_id;
  752   }
  753   my $psvn = $set->psvn();
  754 
  755   # decide what to do about problem number
  756   my $problem;
  757   if ($problemNumber) {  # problem number defined and not zero
  758     $problem = $db->getMergedProblem($effectiveUser->user_id, $setName, $problemNumber); # checked
  759   } elsif ($pgFile) {
  760     $problem = WeBWorK::DB::Record::UserProblem->new(
  761       set_id => $set->set_id,
  762       problem_id => 0,
  763       login_id => $effectiveUser->user_id,
  764       source_file => $pgFile,
  765       # the rest of Problem's fields are not needed, i think
  766     );
  767   }
  768   unless (ref($problem) )  {  # return error if no problem is defined
  769       $problemNumber = 'undefined problem number' unless defined($problemNumber);
  770       $setName       = 'undefined set Name' unless defined($setName);
  771       my $msg        = "Problem $setName/problem $problemNumber not assigned to ".
  772                     $effectiveUser->first_name.' '.
  773                         $effectiveUser->last_name.' ('.$effectiveUser->user_id.' )';
  774     push(@{$self->{warnings}},
  775          setName => $setName,
  776          problem => $problemNumber,
  777          message => $msg,
  778       );
  779       $msg =~ s/_/\\_/;  # escape underbars to protect them from TeX FIXME--this could be more general??
  780       return $msg;
  781   }
  782   # figure out if we're allowed to get solutions and call PG->new accordingly.
  783   my $showCorrectAnswers = $r->param("showCorrectAnswers") || 0;
  784   my $showHints          = $r->param("showHints") || 0;
  785   my $showSolutions      = $r->param("showSolutions") || 0;
  786   unless ($authz->hasPermissions($userID, "view_answers") or time > $set->answer_date) {
  787     $showCorrectAnswers = 0;
  788     $showSolutions      = 0;
  789   }
  790 
  791   my $pg = WeBWorK::PG->new(
  792     $ce,
  793     $effectiveUser,
  794     $r->param('key'),
  795     $set,
  796     $problem,
  797     $psvn,
  798     {}, # no form fields!
  799     { # translation options
  800       displayMode     => "tex",
  801       showHints       => $showHints,
  802       showSolutions   => $showSolutions,
  803       processAnswers  => $showCorrectAnswers,
  804     },
  805   );
  806 
  807   if ($pg->{warnings} ne "") {
  808     push @{$self->{warnings}}, {
  809       set     => $setName,
  810       problem => $problemNumber,
  811       message => $pg->{warnings},
  812     };
  813   }
  814 
  815   if ($pg->{flags}->{error_flag}) {
  816     push @{$self->{errors}}, {
  817       set     => $setName,
  818       problem => $problemNumber,
  819       user    => $effectiveUser,
  820       message => $pg->{errors},
  821       context => $pg->{body_text},
  822     };
  823     # if there was an error, body_text contains
  824     # the error context, not TeX code FIXME (should this error context be used?)
  825     $pg->{body_text} = ''; #   FIXME using undef causes error unless it is caught undef;
  826   } else {
  827     # append list of correct answers to body text
  828     if ($showCorrectAnswers && $problemNumber != 0) {
  829             #
  830             #  DPVC  -- Adjusted spacing here, and added \small and italics.
  831             #           Put the answer in verbatim mode to make it display as typed
  832             #           by the author, rather than use hacks for ^ and _.  What about
  833             #           vectors (where TeX will complain about < and > outside of
  834             #     math mode)?  Do we need hacks for them, too?
  835             #           This also fixes a bug when the answer begins with [
  836             #           where \item would think this was an optional parameter
  837             #           (otherwise we need to do "\\item{}$correctanswer\n").
  838             #
  839       my $correctTeX = "\\par{\\small{\\it Correct Answers:}\n"
  840                                        . "\\vspace{-\\parskip}\\begin{itemize}\n";
  841       foreach my $ansName (@{$pg->{flags}->{ANSWER_ENTRY_ORDER}}) {
  842         my $correctAnswer = $pg->{answers}->{$ansName}->{correct_ans};
  843         #$correctAnswer =~ s/\^/\\\^\{\}/g;
  844         #$correctAnswer =~ s/\_/\\\_/g;
  845         $correctTeX .= "\\item\\begin{verbatim}$correctAnswer\\end{verbatim}\n";
  846       }
  847       $correctTeX .= "\\end{itemize}}\\par\n";
  848       #
  849       # /DPVC
  850       #
  851       $pg->{body_text} .= $correctTeX;
  852     }
  853   }
  854   $WeBWorK::timer1 ->continue("hardcopy: end processing problem") if defined($WeBWorK::timer1);
  855   return $pg->{body_text};
  856 }
  857 
  858 sub texInclude {
  859   my ($self, $texFile) = @_;
  860   my $tex = "";
  861 
  862   $tex .= texBlockComment("BEGIN: $texFile");
  863   eval {
  864     $tex .= readFile($texFile)
  865   };
  866   if ($@) {
  867     $tex .= texBlockComment($@);
  868   }
  869 
  870   return $tex;
  871 }
  872 
  873 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9