[system] / branches / rel-2-2-dev / webwork2 / lib / WeBWorK / ContentGenerator / Instructor / PGProblemEditor.pm Repository:
ViewVC logotype

View of /branches/rel-2-2-dev/webwork2/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3128 - (download) (as text) (annotate)
Sat Jan 29 01:23:57 2005 UTC (8 years, 4 months ago) by gage
Original Path: trunk/webwork2/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm
File size: 44938 byte(s)
Fixed problem with hardcopy editing.  This case had simply not been handled
in the code even though it's the same behavior for hardcopy headers as it is for
screen headers.

This fixes bug #763

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm,v 1.51 2004/12/21 18:44:49 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::Instructor::PGProblemEditor;
   18 use base qw(WeBWorK::ContentGenerator::Instructor);
   19 
   20 
   21 =head1 NAME
   22 
   23 WeBWorK::ContentGenerator::Instructor::PGProblemEditor - Edit a pg file
   24 
   25 =cut
   26 
   27 use strict;
   28 use warnings;
   29 use CGI qw();
   30 use WeBWorK::Utils qw(readFile surePathToFile);
   31 use Apache::Constants qw(:common REDIRECT);
   32 use HTML::Entities;
   33 use URI::Escape;
   34 use WeBWorK::Utils::Tasks qw(fake_set fake_problem);
   35 
   36 ###########################################################
   37 # This editor will edit problem files or set header files or files, such as course_info
   38 # whose name is defined in the global.conf database
   39 #
   40 # Only files under the template directory ( or linked to this location) can be edited.
   41 #
   42 # The course information and problems are located in the course templates directory.
   43 # Course information has the name  defined by courseFiles->{course_info}
   44 #
   45 #
   46 # editMode = temporaryFile    (view the temp file defined by course_info.txt.user_name.tmp
   47 #                              instead of the file course_info.txt)
   48 #            this flag is read by Problem.pm and ProblemSet.pm, perhaps others
   49 # The TEMPFILESUFFIX is "user_name.tmp" by default.  It's definition should be moved to Instructor.pm #FIXME
   50 ###########################################################
   51 
   52 ###########################################################
   53 # The behavior of this module is essentially defined
   54 # by the values of $file_type and the submit button which is placed in $action
   55 #############################################################
   56 #  File types which can be edited
   57 #
   58 #  file_type  eq 'problem'
   59 #                 this is the most common type -- this editor can be called by an instructor when viewing any problem.
   60 #                 the information for retrieving the source file is found using the problemID in order to look
   61 #                 look up the source file path.
   62 #
   63 #  file_type  eq 'problem_with_source'
   64 #                 This is the same as the 'problem' file type except that the source for the problem is found in
   65 #                 the parameter $r->param('sourceFilePath').
   66 #
   67 #  file_type  eq 'set_header'
   68 #                 This is a special case of editing the problem.  The set header is often listed as problem 0 in the set's list of problems.
   69 #
   70 #  file_type  eq 'hardcopy_header'
   71 #                  This is a special case of editing the problem.  The hardcopy_header is often listed as problem 0 in the set's list of problems.
   72 #                  But it is used instead of set_header when producing a hardcopy of the problem set in the TeX format, instead of producing HTML
   73 #                  formatted version for use on the computer screen.
   74 #
   75 #  filte_type eq 'course_info
   76 #                 This allows editing of the course_info.txt file which gives general information about the course.  It is called from the
   77 #                 ProblemSets.pm module.
   78 #
   79 #  file_type  eq 'blank_problem'
   80 #                 This is a special call which allows one to create and edit a new PG problem.  The "stationery" source for this problem is
   81 #                 stored in the conf/snippets directory and defined in global.conf by $webworkFiles{screenSnippets}{blankProblem}
   82 #############################################################
   83 # submit button actions  -- these and the file_type determine the state of the module
   84 #      Save                       ---- action = save
   85 #      Save as                    ---- action = save_as
   86 #      View Problem               ---- action = refresh
   87 #      Add this problem to:       ---- action = add_problem_to_set
   88 #      Make this set header for:  ---- action = add_set_header_to_set
   89 #      Revert                     ---- action = revert
   90 #      no submit button defined   ---- action = fresh_edit
   91 ###################################################
   92 #
   93 # Determining which is the correct path to the file is a mess!!! FIXME
   94 # The path to the file to be edited is eventually put in tempFilePath
   95 #
   96 # $problemPath is also used as is editFilePath.  let's try to regularize these.
   97 #(sourceFile) (problemPath)(tempFilePath)(editFilePath)(forcedSourceFile)(problemPath)
   98 #input parameter can be:  sourceFilePath
   99 #################################################################
  100 # params read
  101 # user
  102 # effectiveUser
  103 # submit
  104 # file_type
  105 # problemSeed
  106 # displayMode
  107 # edit_level
  108 # make_local_copy
  109 # sourceFilePath
  110 # problemContents
  111 # save_to_new_file
  112 #
  113 
  114 #our $libraryName;
  115 #our $rowheight;
  116 our $TEMPFILESUFFIX;
  117 
  118 sub pre_header_initialize {
  119   my ($self)         = @_;
  120   my $r              = $self->r;
  121   my $ce             = $r->ce;
  122   my $urlpath        = $r->urlpath;
  123   my $authz          = $r->authz;
  124   my $user           = $r->param('user');
  125   $TEMPFILESUFFIX    = $user.'.tmp';
  126 
  127   my $submit_button   = $r->param('submit');  # obtain submit command from form
  128   my $file_type       = $r->param("file_type") || '';
  129   my $setName         = $r->urlpath->arg("setID") ;  # using $r->urlpath->arg("setID")  ||'' causes trouble with set 0!!!
  130   my $problemNumber   = $r->urlpath->arg("problemID");
  131 
  132   # Check permissions
  133   return unless ($authz->hasPermissions($user, "access_instructor_tools"));
  134   return unless ($authz->hasPermissions($user, "modify_problem_sets"));
  135 
  136     #############################################################################
  137   # Save file to permanent or temporary file, then redirect for viewing
  138   #############################################################################
  139   #
  140   #  Any file "saved as" should be assigned to "Undefined_Set" and redirectoed to be viewed again in the editor
  141   #
  142   #  Problems "saved" or 'refreshed' are to be redirected to the Problem.pm module
  143   #  Set headers which are "saved" are to be redirected to the ProblemSet.pm page
  144   #  Hardcopy headers which are "saved" are aso to be redirected to the ProblemSet.pm page
  145   #  Course_info files are redirected to the ProblemSets.pm page
  146   ##############################################################################
  147 
  148 
  149 
  150   ######################################
  151     # Insure that file_type is defined
  152   ######################################
  153   # We have already read in the file_type parameter from the form
  154   #
  155   # If this has not been defined we are  dealing with a set header
  156   # or regular problem
  157   if (defined($file_type) and ($file_type =~/\S/)) { #file_type is defined and is not blank
  158     # file type is already defined -- do nothing
  159   } else {
  160       # if "sourcFilePath" is defined in the form, then we are getting the path directly.
  161     # if the problem number is defined and is 0
  162     # then we are dealing with some kind of
  163     # header file.  The default is 'set_header' which prints properly
  164     # to the screen.
  165     # If the problem number is not zero, we are dealing with a real problem
  166     ######################################
  167     if ( defined($r->param('sourceFilePath') and $r->param('sourceFilePath') =~/\S/) ) {
  168       $file_type ='source_path_for_problem_file';
  169     } elsif ( defined($problemNumber) ) {
  170        if ( $problemNumber =~/^\d+$/ and $problemNumber == 0 ) {  # if problem number is numeric and zero
  171                 $file_type = 'set_header' unless $file_type eq 'set_header'
  172                                                or $file_type eq 'hardcopy_header';
  173              } else {
  174               $file_type = 'problem';
  175              }
  176 
  177     }
  178   }
  179   die "The file_type variable has not been defined or is blank." unless defined($file_type) and $file_type =~/\S/;
  180   $self->{file_type} = $file_type;
  181 
  182   ##########################################
  183   # File type is one of:     blank_problem course_info  problem set_header hardcopy_header problem_with_source
  184     ##########################################
  185     #
  186     # Determine the path to the file
  187     #
  188     ###########################################
  189       $self->getFilePaths($setName, $problemNumber, $file_type,$TEMPFILESUFFIX);
  190       # result stored in $self->{editFilePath}, and $self->{tempFilePath}
  191     ##########################################
  192     #
  193     # Determine action
  194     #
  195     ###########################################
  196     # Submit button is one of: "add this problem to" , "add this set header to ", "Refresh"  "Revert" "Save" "Save As"
  197     $submit_button = $r->param('submit');
  198     SUBMIT_CASE: {
  199       (! defined($submit_button) ) and do {   # fresh problem to edit
  200         $self->{action} = 'fresh_edit';
  201         last SUBMIT_CASE;
  202       };
  203 
  204       ($submit_button eq 'Add this problem to: ') and do {
  205         $self->{action} = 'add_problem_to_set';
  206         last SUBMIT_CASE;
  207       };
  208 
  209       ($submit_button eq 'Make this the set header for: ') and do {
  210         $self->{action} = 'add_set_header_to_set';
  211         last SUBMIT_CASE;
  212       };
  213 
  214       ($submit_button eq 'View  problem') and  do {
  215         $self->{action} ='refresh';
  216         last SUBMIT_CASE;
  217       };
  218 
  219       ($submit_button eq 'Revert') and do {
  220         $self->{action} = 'revert';
  221         last SUBMIT_CASE;
  222       };
  223 
  224       ($submit_button eq 'Save') and do {
  225         $self->{action} = 'save';
  226         last SUBMIT_CASE;
  227       };
  228 
  229       ($submit_button eq 'Save as') and do {
  230         $self->{action} = 'save_as';
  231         last SUBMIT_CASE;
  232       };
  233 
  234       ($submit_button eq 'Add problem to: ') and do {
  235         $self->{action} = 'add_problem_to_set';
  236         last SUBMIT_CASE;
  237       };
  238       # else
  239       die "Unrecognized submit command: |$submit_button|";
  240 
  241     } # END SUBMIT_CASE
  242 
  243 
  244     ###########################################
  245     # Save file
  246     ######################################
  247 
  248     # The subroutine below writes the necessary files and obtains the appropriate seed.
  249     # and returns
  250     #         $self->{problemPath}   --- file path for viewing problem in $self->{problemPath}
  251     #         $self->{failure}
  252 
  253 
  254     $self->saveFileChanges($setName, $problemNumber, $file_type,$TEMPFILESUFFIX);
  255 
  256   ##############################################################################
  257   # displayMode   and problemSeed
  258   #
  259   # Determine the display mode
  260   # If $self->{problemSeed} was obtained within saveFileChanges from the problem_record
  261   # then it can be overridden by the value obtained from the form.
  262   # Insure that $self->{problemSeed} has some non-empty value
  263   # displayMode and problemSeed
  264   # will be needed for viewing the problem via redirect.
  265   # They are also two of the parameters which can be set by the editor
  266   ##############################################################################
  267 
  268   if (defined $r->param('displayMode')) {
  269     $self->{displayMode} = $r->param('displayMode');
  270   } else {
  271     $self->{displayMode} = $ce->{pg}->{options}->{displayMode};
  272   }
  273 
  274   # form version of problemSeed overrides version obtained from the the problem_record
  275   # inside saveFileChanges
  276   $self->{problemSeed} = $r->param('problemSeed') if (defined $r->param('problemSeed'));
  277   # Make sure that the problem seed has some value
  278   $self->{problemSeed} = '123456' unless defined $self->{problemSeed} and $self->{problemSeed} =~/\S/;
  279 
  280   ##############################################################################
  281   # Return
  282   #   If  file saving fails or
  283   #   if no redirects are required. No further processing takes place in this subroutine.
  284   #   Redirects are required only for the following submit values
  285   #        'Save'
  286   #        'Save as'
  287   #        'Refresh'
  288   #        add problem to set
  289   #        add set header to set
  290   #
  291     #########################################
  292 
  293     return if $self->{failure};
  294     # FIXME: even with an error we still open a new page because of the target specified in the form
  295 
  296 
  297   # Some cases do not need a redirect: revert,  fresh_edit
  298   my $action = $self->{action};
  299 
  300     return unless $action eq 'save'
  301              or $action eq 'refresh'
  302              or $action eq 'save_as'
  303              or $action eq 'add_problem_to_set'
  304              or $action eq 'add_set_header_to_set';
  305 
  306 
  307   ######################################
  308   # calculate redirect URL based on file type
  309   ######################################
  310   my $courseName  = $urlpath->arg("courseID");
  311   my $problemSeed = ($r->param('problemSeed')) ? $r->param('problemSeed') : '';
  312   my $displayMode = ($r->param('displayMode')) ? $r->param('displayMode') : '';
  313 
  314   my $viewURL = '';
  315 
  316   ######################################
  317   # problem file_type
  318   #     redirect to Problem.pm with setID = "Undefined_Set if "Save As" option is chosen
  319   #     redirect to Problem.pm with setID = current $setID if "Save" or "Revert" or "Refresh is chosen"
  320   ######################################
  321   REDIRECT_CASES: {
  322     ($file_type eq 'problem' or $file_type eq 'source_path_for_problem_file' or $file_type eq 'blank_problem') and do {
  323       my $sourceFilePath = $self->{problemPath};
  324       # strip off template directory prefix
  325       $sourceFilePath =~ s|^$ce->{courseDirs}->{templates}/||;
  326       if ($action eq 'save_as') { # redirect to myself
  327         my $edit_level = $r->param("edit_level") || 0;
  328         $edit_level++;
  329 
  330         my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
  331           courseID => $courseName, setID => 'Undefined_Set', problemID => 'Undefined_Problem'
  332         );
  333         $viewURL = $self->systemLink($problemPage,
  334                                      params=>{
  335                                          sourceFilePath     => $sourceFilePath,
  336                                          edit_level         => $edit_level,
  337                                          file_type          => 'source_path_for_problem_file',
  338                          status_message     => uri_escape($self->{status_message})
  339 
  340                                      }
  341         );
  342 
  343 
  344       } elsif ( $action eq 'add_problem_to_set') {
  345 
  346         my $targetSetName = $r->param('target_set');
  347         my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem",
  348           courseID => $courseName, setID => $targetSetName, problemID => scalar($r->db->listGlobalProblems($targetSetName))
  349         );
  350         $viewURL = $self->systemLink($problemPage,
  351             params => {
  352               displayMode     => $displayMode,
  353               problemSeed     => $problemSeed,
  354               editMode        => "savedFile",
  355               sourceFilePath  => $sourceFilePath,
  356               status_message     => uri_escape($self->{status_message})
  357 
  358             }
  359         );
  360       } elsif ( $action eq 'add_set_header_to_set') {
  361         my $targetSetName = $r->param('target_set');
  362         my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet",
  363           courseID => $courseName, setID => $targetSetName
  364         );
  365         $viewURL = $self->systemLink($problemPage,
  366             params => {
  367               displayMode     => $displayMode,
  368               editMode        => "savedFile",
  369               status_message     => uri_escape($self->{status_message})
  370             }
  371         );
  372       } else { # saved problems and refreshed  problems redirect to Problem.pm
  373         my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem",
  374           courseID => $courseName, setID => $setName, problemID => $problemNumber
  375         );
  376         $viewURL = $self->systemLink($problemPage,
  377           params => {
  378             displayMode     => $displayMode,
  379             problemSeed     => $problemSeed,
  380             editMode        => ($action eq "save" ? "savedFile" : "temporaryFile"),
  381             sourceFilePath  => $sourceFilePath,
  382             status_message     => uri_escape($self->{status_message})
  383 
  384           }
  385         );
  386       }
  387       last REDIRECT_CASES;
  388     };
  389     ######################################
  390     # blank_problem file_type
  391     #          redirect to Problem.pm
  392     ######################################
  393 
  394     $file_type eq 'blank_problem' and do {
  395       return;  # no redirect is needed
  396     };
  397 
  398     ######################################
  399     # set headers file_type
  400     #          redirect to ProblemSet.pm
  401     ######################################
  402 
  403     ($file_type eq 'set_header' or $file_type eq 'hardcopy_header' ) and do {
  404       if ($action eq 'save_as') { # redirect to myself
  405           my $sourceFilePath = $self->{problemPath};
  406         # strip off template directory prefix
  407         $sourceFilePath =~ s|^$ce->{courseDirs}->{templates}/||;
  408 
  409         my $edit_level = $r->param("edit_level") || 0;
  410         $edit_level++;
  411 
  412         my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
  413           courseID => $courseName, setID => 'Undefined_Set', problemID => 'Undefined_Problem'
  414         );
  415         $viewURL = $self->systemLink($problemPage,
  416                                      params=>{
  417                                          sourceFilePath  => $sourceFilePath,
  418                                          edit_level      => $edit_level,
  419                                          file_type       => 'source_path_for_problem_file',
  420                          status_message     => uri_escape($self->{status_message})
  421                                      }
  422         );
  423       } elsif ( $action eq 'add_set_header_to_set') {
  424         my $targetSetName = $r->param('target_set');
  425         my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet",
  426           courseID => $courseName, setID => $targetSetName
  427         );
  428         $viewURL = $self->systemLink($problemPage,
  429             params => {
  430               displayMode     => $displayMode,
  431               editMode        => "savedFile",
  432               status_message     => uri_escape($self->{status_message})
  433             }
  434         );
  435       } else {
  436         my $problemSetPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet",
  437           courseID => $courseName, setID => $setName);
  438         $viewURL = $self->systemLink($problemSetPage,
  439           params => {
  440             displayMode        => $displayMode,
  441             problemSeed        => $problemSeed,
  442             editMode           => ($action eq "save" ? "savedFile" : "temporaryFile"),
  443             status_message     => uri_escape($self->{status_message})
  444           }
  445         );
  446       }
  447       last REDIRECT_CASES;
  448     };
  449     ######################################
  450     # course_info file type
  451     #            redirect to ProblemSets.pm
  452     ######################################
  453     $file_type eq 'course_info' and do {
  454       my $problemSetsPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets",
  455         courseID => $courseName);
  456       $viewURL = $self->systemLink($problemSetsPage,
  457         params => {
  458           editMode => ($action eq "save" ? "savedFile" : "temporaryFile"),
  459           status_message     => uri_escape($self->{status_message})
  460         }
  461       );
  462       last REDIRECT_CASES;
  463     };
  464     # else if no redirect needed -- there must be an error.
  465     die "The file_type $file_type does not have a defined redirect procedure.";
  466   } # End REDIRECT_CASES
  467 
  468   if ($viewURL) {
  469     $self->reply_with_redirect($viewURL);
  470   } else {
  471     die "Invalid file_type $file_type specified by saveFileChanges";
  472   }
  473 }
  474 
  475 
  476 sub initialize  {
  477   my ($self) = @_;
  478   my $r = $self->r;
  479   my $authz = $r->authz;
  480   my $user = $r->param('user');
  481 
  482   # Check permissions
  483   return unless ($authz->hasPermissions($user, "access_instructor_tools"));
  484   return unless ($authz->hasPermissions($user, "modify_problem_sets"));
  485 
  486   my $tempFilePath    = $self->{tempFilePath}; # path to the file currently being worked with (might be a .tmp file)
  487   my $inputFilePath   = $self->{inputFilePath};   # path to the file for input, (might be a .tmp file)
  488   my $protected_file = (not -w $inputFilePath ) and -e $inputFilePath;  #FIXME -- let's try to insure that the input file always exists, even for revert.
  489   $self->addbadmessage("Changes in this file have not yet been permanently saved.") if -r $tempFilePath;
  490     $self->addbadmessage("This file |$inputFilePath| is protected! To edit this text you must first use 'Save As' to save it to another file.") if $protected_file;
  491 
  492 }
  493 
  494 sub path {
  495   my ($self, $args) = @_;
  496   my $r = $self->r;
  497   my $urlpath = $r->urlpath;
  498   my $courseName  = $urlpath->arg("courseID");
  499   my $setName = $r->urlpath->arg("setID") || '';
  500   my $problemNumber = $r->urlpath->arg("problemID") || '';
  501 
  502   # we need to build a path to the problem being edited by hand, since it is not the same as the urlpath
  503   # For this page the bread crum path leads back to the problem being edited, not to the Instructor tool.
  504   my @path = ( 'WeBWork', $r->location,
  505             "$courseName", $r->location."/$courseName",
  506             "$setName",    $r->location."/$courseName/$setName",
  507             "$problemNumber", $r->location."/$courseName/$setName/$problemNumber",
  508             "Editor", ""
  509   );
  510 
  511   #print "\n<!-- BEGIN " . __PACKAGE__ . "::path -->\n";
  512   print $self->pathMacro($args, @path);
  513   #print "<!-- END " . __PACKAGE__ . "::path -->\n";
  514 
  515   return "";
  516 }
  517 sub title {
  518   my $self = shift;
  519   my $r = $self->r;
  520   my $problemNumber = $r->urlpath->arg("problemID");
  521   my $file_type = $self->{'file_type'} || '';
  522   return "Set Header" if ($file_type eq 'set_header');
  523   return "Hardcopy Header" if ($file_type eq 'hardcopy_header');
  524   return "Course Information" if($file_type eq 'course_info');
  525   return 'Problem ' . $r->{urlpath}->name;
  526 }
  527 
  528 sub body {
  529   my ($self) = @_;
  530   my $r = $self->r;
  531   my $db = $r->db;
  532   my $ce = $r->ce;
  533   my $authz = $r->authz;
  534   my $user = $r->param('user');
  535   my $make_local_copy = $r->param('make_local_copy');
  536 
  537   # Check permissions
  538   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.")
  539     unless $authz->hasPermissions($user, "access_instructor_tools");
  540 
  541   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problems.")
  542     unless $authz->hasPermissions($user, "modify_student_data");
  543 
  544 
  545   # Gathering info
  546   my $editFilePath    = $self->{editFilePath}; # path to the permanent file to be edited
  547   my $tempFilePath    = $self->{tempFilePath}; # path to the file currently being worked with (might be a .tmp file)
  548   my $inputFilePath   = $self->{inputFilePath};   # path to the file for input, (might be a .tmp file)
  549   my $setName         = $r->urlpath->arg("setID") ;
  550   my $problemNumber   = $r->urlpath->arg("problemID") ;
  551     $setName            = defined($setName) ? $setName : '';  # we need this instead of using the || construction
  552                                                               # to keep set 0 from being set to the
  553                                                               # empty string.
  554     $problemNumber      = defined($problemNumber) ? $problemNumber : '';
  555 
  556 
  557   #########################################################################
  558   # Find the text for the problem, either in the tmp file, if it exists
  559   # or in the original file in the template directory
  560   # or in the problem contents gathered in the initialization phase.
  561   #########################################################################
  562 
  563   my $problemContents = ${$self->{r_problemContents}};
  564 
  565   unless ( $problemContents =~/\S/)   { # non-empty contents
  566     if (-r $tempFilePath and not -d $tempFilePath) {
  567       eval { $problemContents = WeBWorK::Utils::readFile($tempFilePath) };
  568       $problemContents = $@ if $@;
  569       $inputFilePath = $tempFilePath;
  570     } elsif  (-r $editFilePath and not -d $editFilePath) {
  571       eval { $problemContents = WeBWorK::Utils::readFile($editFilePath) };
  572       $problemContents = $@ if $@;
  573       $inputFilePath = $editFilePath;
  574     } else { # file not existing is not an error
  575         #warn "No file exists";
  576       $problemContents = '';
  577     }
  578   } else {
  579     #warn "obtaining input from r_problemContents";
  580   }
  581 
  582   my $protected_file = not -w $inputFilePath;
  583   my $header = CGI::i("Editing problem".CGI::b("set $setName/ problem $problemNumber</emphasis>").CGI::br()." in file $inputFilePath");
  584   $header = ($inputFilePath =~ /$TEMPFILESUFFIX/) ? CGI::div({class=>'temporaryFile'},$header) : $header;  # use colors if temporary file
  585 
  586   #########################################################################
  587   # Format the page
  588   #########################################################################
  589 
  590   # Define parameters for textarea
  591   # FIXME
  592   # Should the seed be set from some particular user instance??
  593   my $rows            = 20;
  594   my $columns         = 80;
  595   my $mode_list       = $ce->{pg}->{displayModes};
  596   my $displayMode     = $self->{displayMode};
  597   my $problemSeed     = $self->{problemSeed};
  598   my $uri             = $r->uri;
  599   my $edit_level      = $r->param('edit_level') || 0;
  600   my $file_type        = $self->{file_type};
  601 
  602   my $force_field = defined($r->param('sourceFilePath')) ?
  603     CGI::hidden(-name=>'sourceFilePath',
  604                 -default=>$r->param('sourceFilePath')) : '';
  605 
  606   my @allSetNames = sort $db->listGlobalSets;
  607   for (my $j=0; $j<scalar(@allSetNames); $j++) {
  608     $allSetNames[$j] =~ s|^set||;
  609     $allSetNames[$j] =~ s|\.def||;
  610   }
  611   my $target = "problem$edit_level";
  612   # Prepare Preview button
  613     my $view_problem_form = CGI::start_form({method=>"POST", name=>"editor", action=>"$uri", target=>$target, enctype=>"application/x-www-form-urlencoded"}).
  614     $self->hidden_authen_fields.
  615     $force_field.
  616     CGI::hidden(-name=>'file_type',-default=>$self->{file_type}).
  617     CGI::hidden(-name=>'problemSeed',-default=>$problemSeed).
  618     CGI::hidden(-name=>'displayMode',-default=>$displayMode).
  619     CGI::hidden(-name=>'problemContents',-default=>$problemContents).
  620     CGI::submit(-value=>'View  problem',-name=>'submit').
  621     CGI::end_form();
  622   # Prepare add to set  buttons
  623     my $add_files_to_set_buttons = '';
  624   if ($file_type eq 'problem' or $file_type eq 'source_path_for_problem_file' ) {
  625     $add_files_to_set_buttons .= CGI::submit(-value=>'Add problem to: ',-name=>'submit' ) ;
  626   }
  627   if ($file_type eq 'set_header'      # set header or the problem number is not a regular positive number
  628         or ( $file_type =~ /problem/ and ($problemNumber =~ /\D|^0$|^$/ )) ){
  629     $add_files_to_set_buttons .=CGI::submit(-value=>'Make this the set header for: ',-name=>'submit' );
  630   }
  631   # Add pop-up menu for the target set if either of these buttons has been revealed.
  632   $add_files_to_set_buttons .= CGI::popup_menu(-name=>'target_set',-values=>\@allSetNames) if $add_files_to_set_buttons;
  633 
  634 
  635   return CGI::p($header),
  636     CGI::start_form({method=>"POST", name=>"editor", action=>"$uri", target=>$target, enctype=>"application/x-www-form-urlencoded"}),
  637     $self->hidden_authen_fields,
  638     $force_field,
  639     CGI::hidden(-name=>'file_type',-default=>$self->{file_type}),
  640     CGI::div(
  641       'Seed: ',
  642       CGI::textfield(-name=>'problemSeed',-value=>$problemSeed),
  643       'Mode: ',
  644       CGI::popup_menu(-name=>'displayMode', -values=>$mode_list, -default=>$displayMode),
  645       CGI::a({-href=>'http://webwork.math.rochester.edu/docs/docs/pglanguage/manpages/',-target=>"manpage_window"},
  646         'Manpages',
  647       )
  648     ),
  649     CGI::p(
  650       CGI::textarea(
  651         -name => 'problemContents', -default => $problemContents,
  652         -rows => $rows, -columns => $columns, -override => 1,
  653       ),
  654     ),
  655     CGI::p(
  656             $add_files_to_set_buttons,
  657       CGI::br(),
  658       CGI::submit(-value=>'View  problem',-name=>'submit'),
  659       $protected_file ? CGI::submit(-value=>'Save',-name=>'submit', -disabled=>1) : CGI::submit(-value=>'Save',-name=>'submit'),
  660       CGI::submit(-value=>'Revert', -name=>'submit'),
  661       CGI::submit(-value=>'Save as',-name=>'submit'),
  662       CGI::textfield(-name=>'save_to_new_file', -size=>40, -value=>""),
  663 
  664     ),
  665     CGI::end_form();
  666 
  667 
  668 }
  669 
  670 ################################################################################
  671 # Utilities
  672 ################################################################################
  673 
  674 # saveFileChanges does most of the work. it is a separate method so that it can
  675 # be called from either pre_header_initialize() or initilize(), depending on
  676 # whether a redirect is needed or not.
  677 #
  678 # it actually does a lot more than save changes to the file being edited, and
  679 # sometimes less.
  680 sub getFilePaths {
  681   my ($self, $setName, $problemNumber, $file_type, $TEMPFILESUFFIX) = @_;
  682   my $r = $self->r;
  683   my $ce = $r->ce;
  684   my $db = $r->db;
  685   my $urlpath = $r->urlpath;
  686   my $courseName = $urlpath->arg("courseID");
  687   my $user = $r->param('user');
  688   my $effectiveUserName = $r->param('effectiveUser');
  689 
  690   $setName = '' unless defined $setName;
  691   $problemNumber = '' unless defined $problemNumber;
  692   die 'Internal error to PGProblemEditor -- file type is not defined'  unless defined $file_type;
  693 
  694   ##########################################################
  695   # Determine path to the input file to be edited.
  696   # set EditFilePath to this value
  697   #
  698   # There are potentially four files in play
  699   #   The permanent path of the input file  == $editFilePath == $self->{problemPath}
  700   #   A temporary path to the input file    == $tempFilePath== "$editFilePath.$TEMPFILESUFFIX"== $self->{problemPath}
  701   ##########################################################
  702   # Relevant parameters
  703   #     $r->param("displayMode")
  704   #     $r->param('problemSeed')
  705   #     $r->param('submit')
  706   #     $r->param('make_local_copy')
  707   #     $r->param('sourceFilePath')
  708   #     $r->param('problemContents')
  709   #     $r->param('save_to_new_file')
  710   ##########################################################################
  711   # Define the following  variables
  712   #   path to regular file -- $self->{problemPath} = $editFilePath;
  713   #     path to file being read (temporary or permanent)
  714   #               --- $self->{problemPath} = $problemPath;
  715   #       contents of the file being read  --- $problemContents
  716   #         $self->{r_problemContents}        =   \$problemContents;
  717   #       $self->{TEMPFILESUFFIX}           =   $TEMPFILESUFFIX;
  718     ###########################################################################
  719 
  720   my $editFilePath = $ce->{courseDirs}->{templates};
  721 
  722     ##########################################################################
  723     # Determine path to regular file, place it in $editFilePath
  724     # problemSeed is defined for the file_type = 'problem' and 'source_path_to_problem'
  725     ##########################################################################
  726   CASE:
  727   {
  728     ($file_type eq 'course_info') and do {
  729       # we are editing the course_info file
  730       # value of courseFiles::course_info is relative to templates directory
  731       $editFilePath           .= '/' . $ce->{courseFiles}->{course_info};
  732       last CASE;
  733     };
  734 
  735     ($file_type eq 'blank_problem') and do {
  736       $editFilePath = $ce->{webworkFiles}->{screenSnippets}->{blankProblem};
  737       $self->addbadmessage("$editFilePath is blank problem template file and cannot be edited directly.");
  738       $self->addbadmessage("Any changes you make will have to be saved as another file.");
  739       last CASE;
  740     };
  741 
  742     ($file_type eq 'set_header' or $file_type eq 'hardcopy_header') and do {
  743       # first try getting the merged set for the effective user
  744       my $set_record = $db->getMergedSet($effectiveUserName, $setName); # checked
  745       # if that doesn't work (the set is not yet assigned), get the global record
  746       $set_record = $db->getGlobalSet($setName); # checked
  747       # bail if no set is found
  748       die "Cannot find a set record for set $setName" unless defined($set_record);
  749 
  750       my $header_file = "";
  751       $header_file = $set_record->{$file_type};
  752       if ($header_file && $header_file ne "") {
  753           $editFilePath .= '/' . $header_file;
  754       } else {
  755           # if the set record doesn't specify the filename
  756           # then the set uses the default from snippets
  757           # so we'll load that file, but change where it will be saved
  758           # to and grey out the "Save" button
  759           # FIXME why does the make_local_copy variable need to be checked?
  760           # Isn't it automatic that a local copy has to be made?
  761           #if ($r->param('make_local_copy')) {
  762             $editFilePath = $ce->{webworkFiles}->{screenSnippets}->{setHeader} if $file_type eq 'set_header';
  763             $editFilePath = $ce->{webworkFiles}->{hardcopySnippets}->{setHeader} if $file_type eq 'hardcopy_header';
  764             $self->addbadmessage("$editFilePath is the default header file and cannot be edited directly.");
  765             $self->addbadmessage("Any changes you make will have to be saved as another file.");
  766           #}
  767       }
  768       last CASE;
  769     }; #end 'set_header, hardcopy_header' case
  770 
  771     ($file_type eq 'problem') and do {
  772 
  773       # first try getting the merged problem for the effective user
  774       my $problem_record = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked
  775 
  776       # if that doesn't work (the problem is not yet assigned), get the global record
  777       $problem_record = $db->getGlobalProblem($setName, $problemNumber) unless defined($problem_record); # checked
  778       # bail if no source path for the problem is found ;
  779         die "Cannot find a problem record for set $setName / problem $problemNumber" unless defined($problem_record);
  780       $editFilePath .= '/' . $problem_record->source_file;
  781       # define the problem seed for later use
  782       $self->{problemSeed}= $problem_record->problem_seed if  defined($problem_record) and  $problem_record->can('problem_seed') ;
  783       last CASE;
  784     };  # end 'problem' case
  785 
  786     ($file_type eq 'source_path_for_problem_file') and do {
  787       my $forcedSourceFile = $r->param('sourceFilePath');
  788       # bail if no source path for the problem is found ;
  789       die "Cannot find a file path to save to" unless( defined($forcedSourceFile) and ($forcedSourceFile =~ /\S/)  );
  790       $self->{problemSeed} = 1234;
  791       $editFilePath .= '/' . $forcedSourceFile;
  792       last CASE;
  793     }; # end 'source_path_for_problem_file' case
  794   }  # end CASE: statement
  795 
  796 
  797   # if a set record or problem record contains an empty blank for a header or problem source_file
  798   # we could find ourselves trying to edit /blah/templates/.toenail.tmp or something similar
  799   # which is almost undoubtedly NOT desirable
  800 
  801   if (-d $editFilePath) {
  802     my $msg = "The file $editFilePath is a directory!";
  803     $self->{failure} = 1;
  804     $self->addbadmessage($msg);
  805   }
  806   if (-e $editFilePath and not -r $editFilePath) {   #it's ok if the file doesn't exist, perhaps we're going to create it
  807                                                     # with save as
  808     my $msg = "The file $editFilePath cannot be read!";
  809     $self->{failure} = 1;
  810     $self->addbadmessage($msg);
  811   }
  812     #################################################
  813   # The path to the permanent file is now verified and stored in $editFilePath
  814   # Whew!!!
  815   #################################################
  816 
  817   my $tempFilePath = "$editFilePath.$TEMPFILESUFFIX";
  818   $self->{editFilePath}   = $editFilePath;
  819   $self->{tempFilePath}   = $tempFilePath;
  820   $self->{inputFilePath}  = (-r "$editFilePath.$TEMPFILESUFFIX") ? $tempFilePath : $editFilePath;
  821 
  822 }
  823 sub saveFileChanges {
  824   my ($self, $setName, $problemNumber, $file_type, $TEMPFILESUFFIX) = @_;
  825   my $r = $self->r;
  826   my $ce = $r->ce;
  827   my $db = $r->db;
  828   my $urlpath = $r->urlpath;
  829 
  830   my $courseName = $urlpath->arg("courseID");
  831   my $user = $r->param('user');
  832   my $effectiveUserName = $r->param('effectiveUser');
  833 
  834   $setName       = '' unless defined $setName;
  835   $problemNumber = '' unless defined $problemNumber;
  836   $file_type     = '' unless defined $file_type;
  837 
  838   my $action        = $self->{action};
  839   my $editFilePath  = $self->{editFilePath};
  840   my $tempFilePath  = $self->{tempFilePath};
  841   ##############################################################################
  842   # read and update the targetFile and targetFile.tmp files in the directory
  843   # if a .tmp file already exists use that, unless the revert button has been pressed.
  844   # These .tmp files are
  845   # removed when the file is finally saved.
  846   # Place the path of the file to be read in $problemPath.
  847   ##############################################################################
  848 
  849 
  850   my $problemContents = '';
  851   my $outputFilePath = undef;   # this is actually the output file for this subroutine
  852                                 # it is then read in as source in the body of this module
  853   my $do_not_save    = 0;       # flag to prevent saving of file
  854   my $editErrors = '';
  855 
  856   ##########################################################################
  857   # For each of the actions define the following  variables:
  858   #
  859   #   path to permanent file -- $self->{problemPath} = $editFilePath;
  860   #     path to file being read (temporary or permanent)
  861   #               --- $self->{problemPath} = $problemPath;
  862   #       contents of the file being read  --- $problemContents
  863   #         $self->{r_problemContents}        =   \$problemContents;
  864   #
  865   #################################
  866   # handle button clicks #####
  867   # Read contents of file
  868   #################################
  869   ACTION_CASES: {
  870     ($action eq 'fresh_edit') and do {
  871       # this is a fresh editing job
  872             # the original file will be read in the body
  873       last ACTION_CASES;
  874     };
  875 
  876       ($action eq 'revert') and do {
  877       # this is also fresh editing job
  878       $outputFilePath = undef;
  879       $self->addgoodmessage("Reverting to original file $editFilePath");
  880       $self->{problemPath} = $editFilePath;
  881       last ACTION_CASES;
  882     };
  883 
  884     ($action eq 'refresh') and do {
  885       # grab the problemContents from the form in order to save it to the tmp file
  886       # store tmp file name in the $self->problemPath for use in body
  887 
  888       $problemContents = $r->param('problemContents');
  889       $outputFilePath = "$editFilePath.$TEMPFILESUFFIX";
  890       $self->{problemPath} = $outputFilePath;
  891       last ACTION_CASES;
  892     };
  893 
  894     ($action eq 'save') and do {
  895       # grab the problemContents from the form in order to save it to the permanent file
  896       # later we will unlink (delete) the temporary file
  897       # store permanent file name in the $self->problemPath for use in body
  898       $problemContents = $r->param('problemContents');
  899       $outputFilePath = "$editFilePath";
  900       $self->{problemPath} = $outputFilePath;
  901       #$self->addgoodmessage("Saving to file $outputFilePath");
  902       last ACTION_CASES;
  903     };
  904 
  905     ($action eq 'save_as') and do {
  906       my $new_file_name =$r->param('save_to_new_file') || '';
  907       #################################################
  908       #bail unless this new file name has been defined
  909       #################################################
  910       if ( $new_file_name !~ /\S/) { # need a non-blank file name
  911           # setting $self->{failure} stops saving and any redirects
  912           $do_not_save = 1;
  913           warn "new file name is $new_file_name";
  914           $self->addbadmessage(CGI::p("Please specify a file to save to."));
  915           last ACTION_CASES;  #stop processing
  916       }
  917       #################################################
  918       # grab the problemContents from the form in order to save it to a new permanent file
  919       # later we will unlink (delete) the current temporary file
  920       # store new permanent file name in the $self->problemPath for use in body
  921       #################################################
  922       $problemContents = $r->param('problemContents');
  923 
  924       #################################################
  925       # Rescue the user in case they forgot to end the file name with .pg
  926       #################################################
  927       if($self->{file_type} eq 'problem'
  928         or $self->{file_type} eq 'blank_problem'
  929         or $self->{file_type} eq 'set_header') {
  930           $new_file_name =~ s/\.pg$//; # remove it if it is there
  931           $new_file_name .= '.pg'; # put it there
  932 
  933       }
  934 
  935       #################################################
  936       # check to prevent overwrites:
  937       #################################################
  938       $outputFilePath = $ce->{courseDirs}->{templates} . '/' .
  939                      $new_file_name;
  940 
  941       if (defined $outputFilePath and -e $outputFilePath) {
  942         # setting $do_not_save stops saving and any redirects
  943         $do_not_save = 1;
  944         $self->addbadmessage(CGI::p("File $outputFilePath exists.  File not saved."));
  945       } else {
  946         #$self->addgoodmessage("Saving to file $outputFilePath.");
  947       }
  948       $self->{problemPath} = $outputFilePath;
  949       last ACTION_CASES;
  950     };
  951     ($action eq 'add_problem_to_set') and do {
  952         my $sourceFile = $editFilePath;
  953         my $targetSetName  = $r->param('target_set');
  954         my $freeProblemID  = WeBWorK::Utils::max($db->listGlobalProblems($setName)) + 1;
  955         $sourceFile    =~ s|^$ce->{courseDirs}->{templates}/||;
  956         my $problemRecord  = $self->addProblemToSet(
  957                      setName        => $targetSetName,
  958                      sourceFile     => $sourceFile,
  959                      problemID      => $freeProblemID
  960         );
  961         $self->assignProblemToAllSetUsers($problemRecord);
  962         $self->addgoodmessage("Added $sourceFile to ". $targetSetName. " as problem $freeProblemID") ;
  963         $outputFilePath = undef;   # don't save any files
  964         $self->{problemPath} = $editFilePath;
  965 
  966     };
  967     ($action eq 'add_set_header_to_set') and do {
  968         my $sourceFile = $editFilePath;
  969         my $targetSetName  = $r->param('target_set');
  970         $sourceFile    =~ s|^$ce->{courseDirs}->{templates}/||;
  971         my $setRecord  = $db->getGlobalSet($targetSetName);
  972         $setRecord->set_header($sourceFile);
  973         if(  $db->putGlobalSet($setRecord) ) {
  974           $self->addgoodmessage("Added $sourceFile to ". $targetSetName. " as new set header ") ;
  975         } else {
  976           $do_not_save = 1 ;
  977           $self->addbadmessage("Unable to make $sourceFile the set header for $targetSetName");
  978         }
  979         # change file type to set_header if it not already so
  980         $self->{file_type} = 'set_header';
  981         $outputFilePath = undef;   # don't save any files
  982         $self->{problemPath} = $editFilePath;
  983 
  984     };
  985       last ACTION_CASES;
  986 
  987     die "Unrecognized action command: $action";
  988   }; # end ACTION_CASES
  989 
  990 
  991 
  992   ##############################################################################
  993   # write changes to the approriate files
  994   # FIXME  make sure that the permissions are set correctly!!!
  995   # Make sure that the warning is being transmitted properly.
  996   ##############################################################################
  997 
  998   my $writeFileErrors;
  999   if ( defined($outputFilePath) and $outputFilePath =~/\S/ and ! $do_not_save ) {   # save file
 1000       # Handle the problem of line endings.
 1001     # Make sure that all of the line endings are of unix type.
 1002     # Convert \r\n to \n
 1003     $problemContents =~ s/\r\n/\n/g;
 1004     $problemContents =~ s/\r/\n/g;
 1005 
 1006     # make sure any missing directories are created
 1007     $outputFilePath = WeBWorK::Utils::surePathToFile($ce->{courseDirs}->{templates},
 1008                                                             $outputFilePath);
 1009 
 1010     eval {
 1011       local *OUTPUTFILE;
 1012       open OUTPUTFILE, ">", $outputFilePath
 1013           or die "Failed to open $outputFilePath";
 1014       print OUTPUTFILE $problemContents;
 1015       close OUTPUTFILE;
 1016     };  # any errors are caught in the next block
 1017 
 1018     $writeFileErrors = $@ if $@;
 1019   }
 1020 
 1021   ###########################################################
 1022   # Catch errors in saving files,  clean up temp files
 1023   ###########################################################
 1024 
 1025   $self->{failure} = $do_not_save;    # don't do redirects if the file was not saved.
 1026                                       # don't unlink files or send success messages
 1027 
 1028   if ($writeFileErrors) {
 1029       # get the current directory from the outputFilePath
 1030     $outputFilePath =~ m|^(/.*?/)[^/]+$|;
 1031     my $currentDirectory = $1;
 1032 
 1033     my $errorMessage;
 1034     # check why we failed to give better error messages
 1035     if ( not -w $ce->{courseDirs}->{templates} ) {
 1036       $errorMessage = "Write permissions have not been enabled in the templates directory.  No changes can be made.";
 1037     } elsif ( not -w $currentDirectory ) {
 1038       $errorMessage = "Write permissions have not been enabled in $currentDirectory.  Changes must be saved to a different directory for viewing.";
 1039     } elsif ( -e $outputFilePath and not -w $outputFilePath ) {
 1040       $errorMessage = "Write permissions have not been enabled for $outputFilePath.  Changes must be saved to another file for viewing.";
 1041     } else {
 1042       $errorMessage = "Unable to write to $outputFilePath: $writeFileErrors";
 1043     }
 1044 
 1045     $self->{failure} = 1;
 1046     $self->addbadmessage(CGI::p($errorMessage));
 1047 
 1048   }
 1049   unless( $writeFileErrors or $do_not_save) {  # everything worked!  unlink and announce success!
 1050     # unlink the temporary file if there are no errors and the save button has been pushed
 1051     if ($action eq 'save' or $action eq 'save_as' or $action eq 'revert') {
 1052                  unlink($self->{tempFilePath}) ;
 1053     }
 1054     if ( defined($outputFilePath) and ! $self->{failure} )  {
 1055       my $msg = "Saved to file: $outputFilePath";
 1056       $self->addgoodmessage($msg);
 1057     }
 1058 
 1059   }
 1060 
 1061   # return values for use in the body subroutine
 1062   #  The path to the current permanent file being edited:
 1063   #           $self->{problemPath} = $editFilePath;
 1064   #  The path to the current temporary file (if any). If no temporary file this the same
 1065   #  as the permanent file path:
 1066   #       $self->{outputFilePath} = $outputFilePath;
 1067   #
 1068 
 1069   $self->{r_problemContents}        =   \$problemContents;
 1070 }  # end saveFileChanges
 1071 
 1072 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9