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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6006 - (download) (as text) (annotate)
Thu Feb 12 20:04:10 2009 UTC (4 years, 3 months ago) by gage
File size: 85103 byte(s)
changes to interface imported from the 2-4-patches code that didn't make it into head

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm,v 1.94 2008/11/19 16:49:20 glarose 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 
   22 =head1 NAME
   23 
   24 WeBWorK::ContentGenerator::Instructor::PGProblemEditor - Edit a pg file
   25 
   26 =cut
   27 
   28 use strict;
   29 use warnings;
   30 #use CGI qw(-nosticky );
   31 use WeBWorK::CGI;
   32 use WeBWorK::Utils qw(readFile surePathToFile path_is_subdir);
   33 use HTML::Entities;
   34 use URI::Escape;
   35 use WeBWorK::Utils;
   36 use File::Copy;
   37 use WeBWorK::Utils::Tasks qw(fake_set fake_problem);
   38 
   39 ###########################################################
   40 # This editor will edit problem files or set header files or files, such as course_info
   41 # whose name is defined in the global.conf database
   42 #
   43 # Only files under the template directory ( or linked to this location) can be edited.
   44 #
   45 # The course information and problems are located in the course templates directory.
   46 # Course information has the name  defined by courseFiles->{course_info}
   47 #
   48 # Only files under the template directory ( or linked to this location) can be edited.
   49 #
   50 # editMode = temporaryFile    (view the temp file defined by course_info.txt.user_name.tmp
   51 #                              instead of the file course_info.txt)
   52 #            this flag is read by Problem.pm and ProblemSet.pm, perhaps others
   53 # The TEMPFILESUFFIX is "user_name.tmp" by default.  It's definition should be moved to Instructor.pm #FIXME
   54 ###########################################################
   55 
   56 ###########################################################
   57 # The behavior of this module is essentially defined
   58 # by the values of $file_type and the submit button which is placed in $action
   59 #############################################################
   60 #  File types which can be edited
   61 #
   62 #  file_type  eq 'problem'
   63 #                 this is the most common type -- this editor can be called by an instructor when viewing any problem.
   64 #                 the information for retrieving the source file is found using the problemID in order to look
   65 #                 look up the source file path.
   66 #
   67 #  file_type  eq 'source_path_for_problem_file'
   68 #                 This is the same as the 'problem' file type except that the source for the problem is found in
   69 #                 the parameter $r->param('sourceFilePath').  This path is relative to the templates directory
   70 #
   71 #  file_type  eq 'set_header'
   72 #                 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.
   73 #
   74 #  file_type  eq 'hardcopy_header'
   75 #                  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.
   76 #                  But it is used instead of set_header when producing a hardcopy of the problem set in the TeX format, instead of producing HTML
   77 #                  formatted version for use on the computer screen.
   78 #
   79 #  file_type eq 'course_info'
   80 #                 This allows editing of the course_info.txt file which gives general information about the course.  It is called from the
   81 #                 ProblemSets.pm module.
   82 #
   83 #  file_type eq 'options_info'
   84 #                 This allows editing of the options_info.txt file which gives general information about the course.  It is called from the
   85 #                 Options.pm module.
   86 #
   87 #  file_type  eq 'blank_problem'
   88 #                 This is a special call which allows one to create and edit a new PG problem.  The "stationery" source for this problem is
   89 #                 stored in the conf/snippets directory and defined in global.conf by $webworkFiles{screenSnippets}{blankProblem}
   90 #############################################################
   91 # Requested actions  -- these and the file_type determine the state of the module
   92 #      Save                       ---- action = save
   93 #      Save as                    ---- action = save_as
   94 #      View Problem               ---- action = view
   95 #      Add this problem to:       ---- action = add_problem
   96 #      Make this set header for:  ---- action = add_problem
   97 #      Revert                     ---- action = revert
   98 #      no submit button defined   ---- action = fresh_edit
   99 ###################################################
  100 #
  101 # Determining which is the correct path to the file is a mess!!! FIXME
  102 # The path to the file to be edited is eventually put in tempFilePath
  103 #
  104 #  (tempFilePath)(editFilePath)(forcedSourceFile)
  105 #input parameter is:  sourceFilePath
  106 #################################################################
  107 # params read
  108 # user
  109 # effectiveUser
  110 # submit
  111 # file_type
  112 # problemSeed
  113 # displayMode
  114 # edit_level
  115 # make_local_copy
  116 # sourceFilePath
  117 # problemContents
  118 # save_to_new_file
  119 #
  120 
  121 use constant ACTION_FORMS => [qw(view add_problem save save_as  revert)];
  122 #[qw(view save save_as revert add_problem add_header make_local_copy)];
  123 
  124 # permissions needed to perform a given action
  125 use constant FORM_PERMS => {
  126     view => "modify_student_data",
  127     add_problem => "modify_student_data",
  128     make_local_copy => "modify_student_data",
  129     save => "modify_student_data",
  130     save_as => "modify_student_data",
  131 #   rename  => "modify_student_data",
  132     revert => "modify_student_data",
  133 };
  134 
  135 our $BLANKPROBLEM = 'blankProblem.pg';
  136 # use constant BLANKPROBLEM => 'blankProblem.pg';  # doesn't work because this constant needs to be used inside a match.
  137 sub pre_header_initialize {
  138   my ($self)         = @_;
  139   my $r              = $self->r;
  140   my $ce             = $r->ce;
  141   my $urlpath        = $r->urlpath;
  142   my $authz          = $r->authz;
  143   my $user           = $r->param('user');
  144   $self->{courseID}   = $urlpath->arg("courseID");
  145   $self->{setID}      = $r->urlpath->arg("setID") ;  # using $r->urlpath->arg("setID")  ||'' causes trouble with set 0!!!
  146   $self->{problemID}  = $r->urlpath->arg("problemID");
  147 
  148   # parse setID, which may come in with version data
  149   my $fullSetID = $self->{setID};
  150   if ( $fullSetID =~ /,v(\d+)$/ ) {
  151     $self->{versionID} = $1;
  152     $self->{setID} =~ s/,v\d+$//;
  153   }
  154         $self->{fullSetID} = $fullSetID;
  155 
  156   my $submit_button   = $r->param('submit');  # obtain submit command from form
  157   my $actionID        = $r->param('action');
  158   my $file_type       = $r->param("file_type") || '';
  159   my $setName         = $self->{setID};
  160   my $versionedSetName = $self->{fullSetID};
  161   my $problemNumber   = $self->{problemID};
  162 
  163   # Check permissions
  164   return unless ($authz->hasPermissions($user, "access_instructor_tools"));
  165   return unless ($authz->hasPermissions($user, "modify_problem_sets"));
  166 
  167   ##############################################################################
  168   # displayMode   and problemSeed
  169   #
  170   # Determine the display mode
  171   # If $self->{problemSeed} was obtained within saveFileChanges from the problem_record
  172   # then it can be overridden by the value obtained from the form.
  173   # Insure that $self->{problemSeed} has some non-empty value
  174   # displayMode and problemSeed
  175   # will be needed for viewing the problem via redirect.
  176   # They are also two of the parameters which can be set by the editor
  177   ##############################################################################
  178 
  179   if (defined $r->param('displayMode')) {
  180     $self->{displayMode} = $r->param('displayMode');
  181   } else {
  182     $self->{displayMode} = $ce->{pg}->{options}->{displayMode};
  183   }
  184 
  185   # form version of problemSeed overrides version obtained from the the problem_record
  186   # inside saveFileChanges
  187   $self->{problemSeed} = $r->param('problemSeed') if (defined $r->param('problemSeed'));
  188   # Make sure that the problem seed has some value
  189   $self->{problemSeed} = '123456' unless defined $self->{problemSeed} and $self->{problemSeed} =~/\S/;
  190 
  191   ##############################################################################
  192     #############################################################################
  193   # Save file to permanent or temporary file, then redirect for viewing
  194   #############################################################################
  195   #
  196   #  Any file "saved as" should be assigned to "Undefined_Set" and redirectoed to be viewed again in the editor
  197   #
  198   #  Problems "saved" or 'refreshed' are to be redirected to the Problem.pm module
  199   #  Set headers which are "saved" are to be redirected to the ProblemSet.pm page
  200   #  Hardcopy headers which are "saved" are also to be redirected to the ProblemSet.pm page
  201   #  Course_info files are redirected to the ProblemSets.pm page
  202   #  Options_info files are redirected to the Options.pm page
  203   ##############################################################################
  204 
  205 
  206 
  207   ######################################
  208     # Insure that file_type is defined
  209   ######################################
  210   # We have already read in the file_type parameter from the form
  211   #
  212   # If this has not been defined we are  dealing with a set header
  213   # or regular problem
  214   if (defined($file_type) and ($file_type =~/\S/)) { #file_type is defined and is not blank
  215     # file type is already defined -- do nothing
  216     #warn "file type already defined as $file_type"  #FIXME debug
  217   } else {
  218       # if "sourceFilePath" is defined in the form, then we are getting the path directly.
  219     # if the problem number is defined and is 0
  220     # then we are dealing with some kind of
  221     # header file.  The default is 'set_header' which prints properly
  222     # to the screen.
  223     # If the problem number is not zero, we are dealing with a real problem
  224     ######################################
  225     if ( defined($r->param('sourceFilePath') and $r->param('sourceFilePath') =~/\S/) ) {
  226       $file_type ='source_path_for_problem_file';
  227       $file_type = 'set_header' if $r->param('sourceFilePath') =~ m!/headers/|Header\.pg$!; #FIXME this need to be cleaned up
  228     } elsif ( defined($problemNumber) ) {
  229        if ( $problemNumber =~/^\d+$/ and $problemNumber == 0 ) {  # if problem number is numeric and zero
  230                 $file_type = 'set_header' unless  $file_type eq 'set_header'
  231                                                or $file_type eq 'hardcopy_header';
  232              } else {
  233               $file_type = 'problem';
  234               #warn "setting file type to 'problem'\n";  #FIXME debug
  235              }
  236 
  237     }
  238   }
  239   die "The file_type variable has not been defined or is blank." unless defined($file_type) and $file_type =~/\S/;
  240   # clean up sourceFilePath, just in case
  241   # double check that sourceFilePath is relative to the templates file
  242   if ($file_type eq 'source_path_for_problem_file' ) {
  243     my $templatesDirectory = $ce->{courseDirs}->{templates};
  244     my $sourceFilePath = $r->param('sourceFilePath');
  245     $sourceFilePath =~ s/$templatesDirectory//;
  246     $sourceFilePath =~ s|^/||;  # remove intial /
  247     $self->{sourceFilePath} = $sourceFilePath;
  248   }
  249   $self->{file_type} = $file_type;
  250   # $self->addgoodmessage("file type is $file_type");  #FIXME debug
  251   # warn "file type is $file_type\n parameter is ".$self->r->param("file_type");
  252   ##########################################
  253   # File type is one of:     blank_problem course_info options_info problem set_header hardcopy_header source_path_for_problem_file
  254     ##########################################
  255     #
  256     # Determine the path to the file
  257     #
  258     ###########################################
  259       $self->getFilePaths($versionedSetName, $problemNumber, $file_type);
  260       #defines $self->{editFilePath}   # path to the permanent file to be edited
  261       #        $self->{tempFilePath}   # path to the permanent file to be edited  has .tmp suffix
  262       #        $self->{inputFilePath}  # path to the file for input, (might be a .tmp file)
  263 
  264 
  265 
  266     ##########################################
  267     # Default problem contents
  268     ##########################################
  269     $self->{r_problemContents}= undef;
  270 
  271     ##########################################
  272     #
  273     # Determine action
  274     #
  275     ###########################################
  276 
  277   if ($actionID) {
  278     unless (grep { $_ eq $actionID } @{ ACTION_FORMS() } ) {
  279       die "Action $actionID not found";
  280     }
  281     # Check permissions
  282     if (not FORM_PERMS()->{$actionID} or $authz->hasPermissions($user, FORM_PERMS()->{$actionID})) {
  283       my $actionHandler = "${actionID}_handler";
  284       my %genericParams =();
  285 #       foreach my $param (qw(selected_users)) {
  286 #         $genericParams{$param} = [ $r->param($param) ];
  287 #     }
  288       my %actionParams = $self->getActionParams($actionID);
  289       my %tableParams = (); # $self->getTableParams();
  290       $self->{action}= $actionID;
  291       $self->$actionHandler(\%genericParams, \%actionParams, \%tableParams);
  292     } else {
  293       $self->addbadmessage( "You are not authorized to perform this action.");
  294     }
  295   } else {
  296     $self->{action}='fresh_edit';
  297     my $actionHandler = "fresh_edit_handler";
  298     my %genericParams;
  299     my %actionParams = (); #$self->getActionParams($actionID);
  300     my %tableParams = (); # $self->getTableParams();
  301     my $problemContents = '';
  302     $self->{r_problemContents}=\$problemContents;
  303     $self->$actionHandler(\%genericParams, \%actionParams, \%tableParams);
  304   }
  305 
  306 
  307   ##############################################################################
  308   # displayMode   and problemSeed
  309   #
  310   # Determine the display mode
  311   # If $self->{problemSeed} was obtained within saveFileChanges from the problem_record
  312   # then it can be overridden by the value obtained from the form.
  313   # Insure that $self->{problemSeed} has some non-empty value
  314   # displayMode and problemSeed
  315   # will be needed for viewing the problem via redirect.
  316   # They are also two of the parameters which can be set by the editor
  317   ##############################################################################
  318 
  319   if (defined $r->param('displayMode')) {
  320     $self->{displayMode} = $r->param('displayMode');
  321   } else {
  322     $self->{displayMode} = $ce->{pg}->{options}->{displayMode};
  323   }
  324 
  325   # form version of problemSeed overrides version obtained from the the problem_record
  326   # inside saveFileChanges
  327   $self->{problemSeed} = $r->param('problemSeed') if (defined $r->param('problemSeed'));
  328   # Make sure that the problem seed has some value
  329   $self->{problemSeed} = '123456' unless defined $self->{problemSeed} and $self->{problemSeed} =~/\S/;
  330 
  331   ##############################################################################
  332   # Return
  333   #   If  file saving fails or
  334   #   if no redirects are required. No further processing takes place in this subroutine.
  335   #   Redirects are required only for the following submit values
  336   #        'Save'
  337   #        'Save as'
  338   #        'Refresh'
  339   #        add problem to set
  340   #        add set header to set
  341   #
  342     #########################################
  343 
  344     return if $self->{failure};
  345     # FIXME: even with an error we still open a new page because of the target specified in the form
  346 
  347 
  348   # Some cases do not need a redirect: save, refresh, save_as, add_problem_to_set, add_header_to_set,make_local_copy
  349   my $action = $self->{action};
  350     return ;
  351 
  352 }
  353 
  354 
  355 sub initialize  {
  356   my ($self) = @_;
  357   my $r = $self->r;
  358   my $authz = $r->authz;
  359   my $user = $r->param('user');
  360 
  361   # Check permissions
  362   return unless ($authz->hasPermissions($user, "access_instructor_tools"));
  363   return unless ($authz->hasPermissions($user, "modify_problem_sets"));
  364 
  365   my $file_type       = $r->param('file_type') || "";
  366   my $tempFilePath    = $self->{tempFilePath}; # path to the file currently being worked with (might be a .tmp file)
  367   my $inputFilePath   = $self->{inputFilePath};   # path to the file for input, (might be a .tmp file)
  368 
  369   $self->addmessage($r->param('status_message') ||'');  # record status messages carried over if this is a redirect
  370   $self->addbadmessage("Changes in this file have not yet been permanently saved.") if -r $tempFilePath;
  371   if ( not( -e $inputFilePath) ) {
  372     $self->addbadmessage("The file '".$self->shortPath($inputFilePath)."' cannot be found.");
  373   } elsif ((not -w $inputFilePath) && $file_type ne 'blank_problem' ) {
  374 
  375     $self->addbadmessage("The file '".$self->shortPath($inputFilePath)."' is protected! ".CGI::br().
  376     "To edit this text you must first make a copy of this file using the 'Save as' action below.");
  377 
  378   }
  379     if ($inputFilePath =~/$BLANKPROBLEM$/ && $file_type ne 'blank_problem') {
  380 #     $self->addbadmessage("This file '$inputFilePath' is a blank problem! ".CGI::br()."To edit this text you must
  381       $self->addbadmessage("The file '".$self->shortPath($inputFilePath)."' is a blank problem! ".CGI::br()."To edit this text you must
  382                            use the 'Save as' action below to save it to another file.");
  383     }
  384 
  385 }
  386 
  387 sub path {
  388   my ($self, $args) = @_;
  389   my $r = $self->r;
  390   my $urlpath = $r->urlpath;
  391   my $courseName  = $urlpath->arg("courseID");
  392   my $setName = $r->urlpath->arg("setID") || '';
  393   my $problemNumber = $r->urlpath->arg("problemID") || '';
  394 
  395   # we need to build a path to the problem being edited by hand, since it is not the same as the urlpath
  396   # For this page the bread crum path leads back to the problem being edited, not to the Instructor tool.
  397   my @path = ( 'WeBWork', $r->location,
  398             "$courseName", $r->location."/$courseName",
  399             "$setName",    $r->location."/$courseName/$setName",
  400             "$problemNumber", $r->location."/$courseName/$setName/$problemNumber",
  401             "Editor", ""
  402   );
  403 
  404   #print "\n<!-- BEGIN " . __PACKAGE__ . "::path -->\n";
  405   print $self->pathMacro($args, @path);
  406   #print "<!-- END " . __PACKAGE__ . "::path -->\n";
  407 
  408   return "";
  409 }
  410 sub title {
  411   my $self = shift;
  412   my $r = $self->r;
  413   my $courseName    = $r->urlpath->arg("courseID");
  414   my $setID         = $r->urlpath->arg("setID");
  415   my $problemNumber = $r->urlpath->arg("problemID");
  416   my $file_type = $self->{'file_type'} || '';
  417 
  418   return "Set Header for  set $setID" if ($file_type eq 'set_header');
  419   return "Hardcopy Header for set $setID" if ($file_type eq 'hardcopy_header');
  420   return "Course Information for course $courseName" if ($file_type eq 'course_info');
  421   return "Options Information" if ($file_type eq 'options_info');
  422 
  423   return 'Problem ' . $r->{urlpath}->name;
  424 }
  425 
  426 sub body {
  427   my ($self) = @_;
  428   my $r = $self->r;
  429   my $db = $r->db;
  430   my $ce = $r->ce;
  431   my $authz = $r->authz;
  432   my $user = $r->param('user');
  433   my $make_local_copy = $r->param('make_local_copy');
  434 
  435   # Check permissions
  436   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.")
  437     unless $authz->hasPermissions($user, "access_instructor_tools");
  438 
  439   return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problems.")
  440     unless $authz->hasPermissions($user, "modify_student_data");
  441 
  442 
  443 
  444 
  445   # Gathering info
  446   my $editFilePath    = $self->{editFilePath}; # path to the permanent file to be edited
  447   my $tempFilePath    = $self->{tempFilePath}; # path to the file currently being worked with (might be a .tmp file)
  448   my $inputFilePath   = $self->{inputFilePath};   # path to the file for input, (might be a .tmp file)
  449   my $setName         = $self->{setID} ;
  450   my $problemNumber   = $self->{problemID} ;
  451     $setName            = defined($setName) ? $setName : '';  # we need this instead of using the || construction
  452                                                               # to keep set 0 from being set to the
  453                                                               # empty string.
  454   my $fullSetName = defined( $self->{fullSetID} ) ? $self->{fullSetID} : $setName;
  455   $problemNumber      = defined($problemNumber) ? $problemNumber : '';
  456 
  457   #########################################################################
  458     # Construct url for reporting bugs:
  459   #########################################################################
  460 
  461 #   $editFilePath =~ m|([^/]*)Library|;    #find the path to the file
  462 #   my $libraryName = $1;                  # find the library, if any exists in the path name (first library is picked)
  463 #   $libraryName ='rochester' unless defined($libraryName) and $libraryName =~/\S/; # default library
  464   my $libraryName = '';
  465   if ($editFilePath =~ m|([^/]*)Library|)   {  #find the path to the file
  466     # find the library, if any exists in the path name (first library is picked)
  467     my $tempLibraryName = $1;
  468     $libraryName = ( defined($tempLibraryName) and $tempLibraryName =~/\S/ ) ?
  469                     $tempLibraryName : "Library";
  470     # things that start /Library/setFoo/probBar  are labeled as component "Library"
  471     # which refers to the SQL based problem library. (is nationalLibrary a better name?)
  472   } else {
  473     $libraryName = 'rochester';  # make sure there is some default component defined.
  474   }
  475 
  476     my $BUGZILLA = "http://bugs.webwork.rochester.edu/enter_bug.cgi?product=Problem%20libraries".
  477                    "&component=$libraryName&bug_file_loc=${editFilePath}_with_problemSeed=".$self->{problemSeed};
  478   #FIXME  # The construction of this URL is somewhat fragile.  A separate module could be devoted to intelligent reporting of bugs.
  479 
  480   #########################################################################
  481   # Find the text for the problem, either in the tmp file, if it exists
  482   # or in the original file in the template directory
  483   # or in the problem contents gathered in the initialization phase.
  484   #########################################################################
  485 
  486   my $problemContents = ${$self->{r_problemContents}};
  487 
  488   unless ( $problemContents =~/\S/)   { # non-empty contents
  489     if (-r $tempFilePath and not -d $tempFilePath) {
  490       die "tempFilePath is unsafe!" unless path_is_subdir($tempFilePath, $ce->{courseDirs}->{templates}, 1); # 1==path can be relative to dir
  491       eval { $problemContents = WeBWorK::Utils::readFile($tempFilePath) };
  492       $problemContents = $@ if $@;
  493       $inputFilePath = $tempFilePath;
  494     } elsif  (-r $editFilePath and not -d $editFilePath) {
  495       die "editFilePath is unsafe!" unless path_is_subdir($editFilePath, $ce->{courseDirs}->{templates}, 1)  # 1==path can be relative to dir
  496         || $editFilePath eq $ce->{webworkFiles}{screenSnippets}{setHeader}
  497         || $editFilePath eq $ce->{webworkFiles}{hardcopySnippets}{setHeader}
  498         || $editFilePath eq $ce->{webworkFiles}{screenSnippets}{blankProblem};
  499       eval { $problemContents = WeBWorK::Utils::readFile($editFilePath) };
  500       $problemContents = $@ if $@;
  501       $inputFilePath = $editFilePath;
  502     } else { # file not existing is not an error
  503         #warn "No file exists";
  504       $problemContents = '';
  505     }
  506   } else {
  507     #warn "obtaining input from r_problemContents";
  508   }
  509 
  510   my $protected_file = not -w $inputFilePath;
  511 
  512   my $file_type = $self->{file_type};
  513   my %titles = (
  514     problem         => CGI::b("set $fullSetName/problem $problemNumber"),
  515     blank_problem   => "blank problem",
  516     set_header      => "header file",
  517     hardcopy_header => "hardcopy header file",
  518     course_info     => "course information",
  519     options_info    => "options information",
  520     ''              => 'Unknown file type',
  521     source_path_for_problem_file => " unassigned problem file:  ".CGI::b("set $setName/problem $problemNumber"),
  522   );
  523   my $header = CGI::i("Editing $titles{$file_type} in file '".$self->shortPath($inputFilePath)."'");
  524   $header = ($self->isTempEditFilePath($inputFilePath)  ) ? CGI::div({class=>'temporaryFile'},$header) : $header;  # use colors if temporary file
  525 
  526   #########################################################################
  527   # Format the page
  528   #########################################################################
  529 
  530   # Define parameters for textarea
  531   # FIXME
  532   # Should the seed be set from some particular user instance??
  533   my $rows            = 20;
  534   my $columns         = 80;
  535   my $mode_list       = $ce->{pg}->{displayModes};
  536   my $displayMode     = $self->{displayMode};
  537   my $problemSeed     = $self->{problemSeed};
  538   my $uri             = $r->uri;
  539   my $edit_level      = $r->param('edit_level') || 0;
  540 
  541   my $force_field = (defined($self->{sourceFilePath}) and $self->{sourceFilePath} ne "") ?
  542     CGI::hidden(-name=>'sourceFilePath',
  543                 -default=>$self->{sourceFilePath}) : '';
  544 
  545   # FIXME this isn't used at all! --sam
  546   my @allSetNames = sort $db->listGlobalSets;
  547   for (my $j=0; $j<scalar(@allSetNames); $j++) {
  548     $allSetNames[$j] =~ s|^set||;
  549     $allSetNames[$j] =~ s|\.def||;
  550   }
  551   my $target = 'WW_View'; #"problem$edit_level"; # increasing edit_level gives you a new window with each edit.
  552 
  553   print CGI::script(<<EOF);
  554     function setTarget(inWindow) {
  555       document.getElementById("newWindow").checked = inWindow;
  556       updateTarget();
  557     }
  558     function updateTarget() {
  559       var inWindow = document.getElementById("newWindow").checked;
  560       document.getElementById("editor").target = (inWindow? "WW_View": "");
  561     }
  562     function setRadio(i,nw) {
  563       document.getElementById('action'+i).checked = true;
  564       setTarget(nw);
  565     }
  566 EOF
  567 
  568   print CGI::p($header),
  569 
  570     CGI::start_form({method=>"POST", id=>"editor", name=>"editor", action=>"$uri", enctype=>"application/x-www-form-urlencoded"}),
  571 
  572     $self->hidden_authen_fields,
  573     $force_field,
  574     CGI::hidden(-name=>'file_type',-default=>$self->{file_type}),
  575     CGI::div({}," | ",
  576       CGI::a({-href=>'http://webwork.math.rochester.edu/docs/docs/pglanguage/manpages/',-target=>"manpage_window"},
  577         '&nbsp;Manpages&nbsp;',
  578       )," | ",
  579       CGI::a({-href=>'http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/PGmacrosByFile',-target=>"manpage_window"},
  580         '&nbsp;macro list&nbsp;',
  581       )," | ",
  582       CGI::a({-href=>'http://webwork.maa.org/wiki/Category:Authors',-target=>"wiki_window"},
  583         '&nbsp;authoring&nbsp;info&nbsp;&amp; help&nbsp;',
  584       )," | ",
  585       CGI::a({-href=>'http://hosted2.webwork.rochester.edu/webwork2/wikiExamples/MathObjectsLabs2/2/?login_practice_user=true',-target=>"lab_window",
  586            },
  587         '&nbsp;testing&nbsp;lab&nbsp;'
  588       )," | ",
  589       CGI::a({-href=>'http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/',-target=>"doc_window"},
  590         '&nbsp;pod docs&nbsp;',
  591       )," | ",
  592       CGI::a({-href=>$BUGZILLA,-target=>"bugs_window"},
  593         '&nbsp;report problem bugs&nbsp;',
  594       )," | ",
  595     ),
  596     CGI::p(
  597       CGI::textarea(
  598         -name => 'problemContents', -default => $problemContents,
  599         -rows => $rows, -cols => $columns, -override => 1,
  600       ),
  601     );
  602 
  603 
  604 
  605 ######### print action forms
  606 
  607       print CGI::start_table({});
  608       #print CGI::Tr({}, CGI::td({-colspan=>2}, "Select an action to perform:"));
  609 
  610       my @formsToShow = @{ ACTION_FORMS() };
  611       my $default_choice = $formsToShow[0];
  612       my $i = 0;
  613       foreach my $actionID (@formsToShow) {
  614         # Check permissions
  615         #next if FORM_PERMS()->{$actionID} and not $authz->hasPermissions($user, FORM_PERMS()->{$actionID});
  616         my $actionForm = "${actionID}_form";
  617         my $newWindow = ($actionID =~ m/^(view|add_problem|save)$/)? 1: 0;
  618         my $onChange = "setRadio($i,$newWindow)";
  619         my %actionParams = $self->getActionParams($actionID);
  620         my $line_contents = $self->$actionForm($onChange, %actionParams);
  621         my $radio_params = {-type=>"radio", -name=>"action", -value=>$actionID};
  622         $radio_params->{checked}=1 if ($actionID eq $default_choice) ;
  623         $radio_params->{onclick} = "setTarget($newWindow)";
  624         $radio_params->{id} = "action$i";
  625         print CGI::Tr({-valign=>"top"},
  626           CGI::td({}, CGI::input($radio_params)),
  627           CGI::td({}, $line_contents)
  628         ) if $line_contents;
  629 
  630         $i++;
  631       }
  632       my $checkbox = CGI::input({-type=>"checkbox", -id=>"newWindow", -checked=>"checked",
  633                -onchange=>"updateTarget()"});
  634       $checkbox =~ s/\n//; # remove unwanted linebreak
  635       print CGI::Tr({}, CGI::td({-colspan=>2}, "Select above then:",
  636                         CGI::submit(-name=>'submit', -value=>"Take Action!"),
  637             CGI::script("document.write('$checkbox in another window')")));
  638       print CGI::end_table();
  639 
  640 
  641   print  CGI::end_form();
  642 
  643   print CGI::script("updateTarget()");
  644   return "";
  645 
  646 
  647 }
  648 
  649 #
  650 #  Convert long paths to [TMPL], etc.
  651 #
  652 sub shortPath {
  653   my $self = shift; my $file = shift;
  654   my $tmpl = $self->r->ce->{courseDirs}{templates};
  655   my $root = $self->r->ce->{courseDirs}{root};
  656   my $ww = $self->r->ce->{webworkDirs}{root};
  657   $file =~ s|^$tmpl|[TMPL]|; $file =~ s|^$root|[COURSE]|; $file =~ s|^$ww|[WW]|;
  658   return $file;
  659 }
  660 
  661 ################################################################################
  662 # Utilities
  663 ################################################################################
  664 
  665 sub getRelativeSourceFilePath {
  666   my ($self, $sourceFilePath) = @_;
  667 
  668   my $templatesDir = $self->r->ce->{courseDirs}->{templates};
  669   $sourceFilePath =~ s|^$templatesDir/*||; # remove templates path and any slashes that follow
  670 
  671   return $sourceFilePath;
  672 }
  673 
  674 # determineLocalFilePath   constructs a local file path parallel to a library file path
  675 
  676 #
  677 sub determineLocalFilePath {
  678   my $self= shift;        die "determineLocalFilePath is a method" unless ref($self);
  679   my $path = shift;
  680   my $default_screen_header_path   = $self->r->ce->{webworkFiles}->{hardcopySnippets}->{setHeader};
  681   my $default_hardcopy_header_path = $self->r->ce->{webworkFiles}->{screenSnippets}->{setHeader};
  682   my $setID = $self->{setID};
  683   $setID = int(rand(1000)) unless $setID =~/\S/;  # setID can be 0
  684   if ($path =~ /Library/) {
  685     #$path =~ s|^.*?Library/||;  # truncate the url up to a segment such as ...rochesterLibrary/.......
  686     $path  =~ s|^.*?Library/|local/|;  # truncate the url up to a segment such as ...rochesterLibrary/....... and prepend local
  687   } elsif ($path eq $default_screen_header_path) {
  688     $path = "set$setID/setHeader.pg";
  689   } elsif ($path eq $default_hardcopy_header_path) {
  690     $path = "set$setID/hardcopyHeader.tex";
  691   } else { # if its not in a library we'll just save it locally
  692     $path = "new_problem_".int(rand(1000)).".pg"; #l hope there aren't any collisions.
  693   }
  694     $path;
  695 
  696 }
  697 
  698 sub determineTempEditFilePath {  # this does not create the directories in the path to the file
  699                                  # it  returns an absolute path to the file
  700   my $self = shift;  die "determineTempEditFilePath is a method" unless ref($self);
  701   my $path =shift;    # this should be an absolute path to the file
  702   my $user = $self->r->param("user");
  703   $user    = int(rand(1000)) unless defined $user;
  704   my $setID = $self->{setID} || int(rand(1000));
  705   my $courseDirectory = $self->r->ce->{courseDirs};
  706   ###############
  707   # Calculate the location of the temporary file
  708   ###############
  709   my $templatesDirectory           = $courseDirectory->{templates};
  710   my $blank_file_path              = $self->r->ce->{webworkFiles}->{screenSnippets}->{blankProblem};
  711   my $default_screen_header_path   = $self->r->ce->{webworkFiles}->{hardcopySnippets}->{setHeader};
  712   my $default_hardcopy_header_path = $self->r->ce->{webworkFiles}->{screenSnippets}->{setHeader};
  713   my $tmpEditFileDirectory = $self->getTempEditFileDirectory();
  714   $self->addbadmessage("The path to the original file should be absolute") unless $path =~m|^/|;  # debug
  715   if ($path =~/^$tmpEditFileDirectory/) {
  716     $self->addbadmessage("Error: This path is already in the temporary edit directory -- no new temporary file is created. path = $path");
  717 
  718   } else {
  719     if ($path =~ /^$templatesDirectory/ ) {
  720       $path =~ s|^$templatesDirectory||;
  721       $path =~ s|^/||;   # remove the initial slash if any
  722       $path = "$tmpEditFileDirectory/$path.$user.tmp";
  723     } elsif ($path eq $blank_file_path) {
  724       $path = "$tmpEditFileDirectory/blank.$setID.$user.tmp";  # handle the case of the blank problem
  725     } elsif ($path eq $default_screen_header_path) {
  726       $path = "$tmpEditFileDirectory/screenHeader.$setID.$user.tmp";  # handle the case of the screen header in snippets
  727     } elsif ($path eq $default_hardcopy_header_path) {
  728       $path = "$tmpEditFileDirectory/hardcopyHeader.$setID.$user.tmp";  # handle the case of the hardcopy header in snippets
  729     } else {
  730       die "determineTempEditFilePath should only be used on paths within the templates directory, not on $path";
  731     }
  732   }
  733   $path;
  734 }
  735 
  736 sub determineOriginalEditFilePath {  # determine the original path to a file corresponding to a temporary edit file
  737                                      # returns path relative to the template directory
  738   my $self = shift;
  739   my $path = shift;
  740   my $user = $self->r->param("user");
  741   $self->addbadmessage("Can't determine user of temporary edit file $path.") unless defined($user);
  742   my $templatesDirectory = $self->r->ce->{courseDirs} ->{templates};
  743   my $tmpEditFileDirectory = $self->getTempEditFileDirectory();
  744   # unless path is absolute assume that it is relative to the template directory
  745   my $newpath = $path;
  746   unless ($path =~ m|^/| ) {
  747     $newpath = "$templatesDirectory/$path";
  748   }
  749   if ($self->isTempEditFilePath($newpath) ) {
  750     $newpath =~ s|^$tmpEditFileDirectory/||; # delete temp edit directory
  751     if ($newpath =~m|blank\.[^/]*$|) {                     # handle the case of the blank problem
  752       $newpath = $self->r->ce->{webworkFiles}->{screenSnippets}->{blankProblem};
  753     } elsif (($newpath =~m|hardcopyHeader\.[^/]*$|)) {     # handle the case of the hardcopy header in snippets
  754       $newpath = $self->r->ce->{webworkFiles}->{hardcopySnippets}->{setHeader};
  755     } elsif (($newpath =~m|screenHeader\.[^/]*$|)) {       # handle the case of the screen header in snippets
  756       $newpath = $self->r->ce->{webworkFiles}->{screenSnippets}->{setHeader};
  757     } else {
  758       $newpath =~ s|\.$user\.tmp$||;           # delete suffix
  759 
  760     }
  761     #$self->addgoodmessage("Original file path is $newpath"); #FIXME debug
  762   } else {
  763     $self->addbadmessage("This path |$newpath| is not the path to a temporary edit file.");
  764     # returns original path
  765   }
  766   $newpath;
  767 }
  768 
  769 sub getTempEditFileDirectory {
  770   my $self = shift;
  771   my $courseDirectory       = $self->r->ce->{courseDirs};
  772   my $templatesDirectory    = $courseDirectory->{templates};
  773   my $tmpEditFileDirectory  = (defined ($courseDirectory->{tmpEditFileDir}) ) ? $courseDirectory->{tmpEditFileDir} : "$templatesDirectory/tmpEdit";
  774   $tmpEditFileDirectory;
  775 }
  776 sub isTempEditFilePath  {
  777   my $self = shift;
  778   my $path = shift;
  779   my $templatesDirectory = $self->r->ce->{courseDirs} ->{templates};
  780   # unless path is absolute assume that it is relative to the template directory
  781   unless ($path =~ m|^/| ) {
  782     $path = "$templatesDirectory/$path";
  783   }
  784   my $tmpEditFileDirectory = $self->getTempEditFileDirectory();
  785 
  786   ($path =~/^$tmpEditFileDirectory/) ? 1: 0;
  787 }
  788 sub getFilePaths {
  789   my ($self, $setName, $problemNumber, $file_type) = @_;
  790   my $r = $self->r;
  791   my $ce = $r->ce;
  792   my $db = $r->db;
  793   my $urlpath = $r->urlpath;
  794   my $courseName = $urlpath->arg("courseID");
  795   my $user = $r->param('user');
  796   my $effectiveUserName = $r->param('effectiveUser');
  797 
  798   $setName = '' unless defined $setName;
  799   $problemNumber = '' unless defined $problemNumber;
  800 
  801   # parse possibly versioned set names
  802   my $fullSetName = $setName;
  803   my $editSetVersion = 0;
  804   if ( $setName =~ /,v(\d)+$/ ) {
  805     $editSetVersion = $1;
  806     $setName =~ s/,v\d+$//;
  807   }
  808 
  809   die 'Internal error to PGProblemEditor -- file type is not defined'  unless defined $file_type;
  810   #$self->addgoodmessage("file type is $file_type");  #FIXME remove
  811   ##########################################################
  812   # Determine path to the input file to be edited.
  813   #   The permanent path of the input file  == $editFilePath
  814   #   A temporary path to the input file    == $tempFilePath
  815   ##########################################################
  816   # Relevant parameters
  817   #     $r->param("displayMode")
  818   #     $r->param('problemSeed')
  819   #     $r->param('submit')
  820   #     $r->param('make_local_copy')
  821   #     $r->param('sourceFilePath')
  822   #     $r->param('problemContents')
  823   #     $r->param('save_to_new_file')
  824   ##########################################################################
  825   # Define the following  variables
  826   #   path to regular file -- $editFilePath;
  827   #     path to file being read (temporary or permanent)
  828   #       contents of the file being read  --- $problemContents
  829   #       $self->{r_problemContents}        =   \$problemContents;
  830     ###########################################################################
  831 
  832   my $editFilePath = $ce->{courseDirs}->{templates};
  833 
  834     ##########################################################################
  835     # Determine path to regular file, place it in $editFilePath
  836     # problemSeed is defined for the file_type = 'problem' and 'source_path_to_problem'
  837     ##########################################################################
  838   CASE:
  839   {
  840     ($file_type eq 'course_info') and do {
  841       # we are editing the course_info file
  842       # value of courseFiles::course_info is relative to templates directory
  843       $editFilePath           .= '/' . $ce->{courseFiles}->{course_info};
  844       last CASE;
  845     };
  846 
  847     ($file_type eq 'options_info') and do {
  848       # we are editing the options_info file
  849       # value of courseFiles::options_info is relative to templates directory
  850       $editFilePath           .= '/' . $ce->{courseFiles}->{options_info};
  851       last CASE;
  852     };
  853 
  854     ($file_type eq 'blank_problem') and do {
  855       $editFilePath = $ce->{webworkFiles}->{screenSnippets}->{blankProblem};
  856       $self->addbadmessage("This is a blank problem template file and can not be edited directly. "
  857                            ."Use the 'Save as' action below to create a local copy of the file and add it to the current problem set."
  858       );
  859       last CASE;
  860     };
  861 
  862     ($file_type eq 'set_header' or $file_type eq 'hardcopy_header') and do {
  863       # first try getting the merged set for the effective user
  864       # FIXME merged set is overwritten immediately with global value... WTF? --sam
  865       my $set_record = $db->getMergedSet($effectiveUserName, $setName); # checked
  866       # if that doesn't work (the set is not yet assigned), get the global record
  867       $set_record = $db->getGlobalSet($setName); # checked
  868       # bail if no set is found
  869       die "Cannot find a set record for set $setName" unless defined($set_record);
  870 
  871       my $header_file = "";
  872       $header_file = $set_record->{$file_type};
  873       if ($header_file && $header_file ne "") {
  874           if ( $header_file =~ m|^/| ) { # if absolute address
  875             $editFilePath  = $header_file;
  876           } else {
  877             $editFilePath .= '/' . $header_file;
  878           }
  879       } else {
  880           # if the set record doesn't specify the filename for a header
  881           # then the set uses the default from snippets
  882 
  883             $editFilePath = $ce->{webworkFiles}->{screenSnippets}->{setHeader} if $file_type eq 'set_header';
  884             $editFilePath = $ce->{webworkFiles}->{hardcopySnippets}->{setHeader} if $file_type eq 'hardcopy_header';
  885 
  886 #           $self->addbadmessage("'".$self->shortPath($editFilePath)."' is the default header file and cannot be edited directly.".CGI::br()."Any changes you make will have to be saved as another file.");
  887           #}
  888 
  889       }
  890       last CASE;
  891     }; #end 'set_header, hardcopy_header' case
  892 
  893     ($file_type eq 'problem') and do {
  894 
  895       # first try getting the merged problem for the effective user
  896       my $problem_record;
  897       if ( $editSetVersion ) {
  898         $problem_record = $db->getMergedProblemVersion($effectiveUserName, $setName, $editSetVersion, $problemNumber); # checked
  899       } else {
  900         $problem_record = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked
  901       }
  902 
  903       # if that doesn't work (the problem is not yet assigned), get the global record
  904       $problem_record = $db->getGlobalProblem($setName, $problemNumber) unless defined($problem_record); # checked
  905       # bail if no source path for the problem is found ;
  906         die "Cannot find a problem record for set $setName / problem $problemNumber" unless defined($problem_record);
  907       $editFilePath .= '/' . $problem_record->source_file;
  908       # define the problem seed for later use
  909       $self->{problemSeed}= $problem_record->problem_seed if  defined($problem_record) and  $problem_record->can('problem_seed') ;
  910       last CASE;
  911     };  # end 'problem' case
  912 
  913     ($file_type eq 'source_path_for_problem_file') and do {
  914       my $forcedSourceFile = $self->{sourceFilePath};
  915       # if the source file is in the temporary edit directory find the original source file
  916       # the source file is relative to the templates directory.
  917       if ($self->isTempEditFilePath($forcedSourceFile) ) {
  918           $forcedSourceFile   = $self->determineOriginalEditFilePath($forcedSourceFile);     # original file path
  919           $self->addgoodmessage("the original path to the file is $forcedSourceFile");  #FIXME debug
  920       }
  921       # bail if no source path for the problem is found ;
  922       die "Cannot find a file path to save to" unless( defined($forcedSourceFile) and ($forcedSourceFile =~ /\S/)  );
  923       $self->{problemSeed} = 1234;
  924       $editFilePath .= '/' . $forcedSourceFile;
  925       last CASE;
  926     }; # end 'source_path_for_problem_file' case
  927   }  # end CASE: statement
  928 
  929 
  930   # if a set record or problem record contains an empty blank for a header or problem source_file
  931   # we could find ourselves trying to edit /blah/templates/.toenail.tmp or something similar
  932   # which is almost undoubtedly NOT desirable
  933 
  934   if (-d $editFilePath) {
  935     my $msg = "The file '".$self->shortPath($editFilePath)."' is a directory!";
  936     $self->{failure} = 1;
  937     $self->addbadmessage($msg);
  938   }
  939   if (-e $editFilePath and not -r $editFilePath) {   #it's ok if the file doesn't exist, perhaps we're going to create it
  940                                                     # with save as
  941     my $msg = "The file '".$self->shortPath($editFilePath)."' cannot be read!";
  942     $self->{failure} = 1;
  943     $self->addbadmessage($msg);
  944   }
  945     #################################################
  946   # The path to the permanent file is now verified and stored in $editFilePath
  947   # Whew!!!
  948   #################################################
  949 
  950   my $tempFilePath = $self->determineTempEditFilePath($editFilePath);  #"$editFilePath.$TEMPFILESUFFIX";
  951   $self->{editFilePath}   = $editFilePath;
  952   $self->{tempFilePath}   = $tempFilePath;
  953   $self->{inputFilePath}  = (-r $tempFilePath) ? $tempFilePath : $editFilePath;
  954   #warn "editfile path is $editFilePath and tempFile is $tempFilePath and inputFilePath is ". $self->{inputFilePath};
  955 }
  956 sub saveFileChanges {
  957 
  958 ################################################################################
  959 # saveFileChanges does most of the work. it is a separate method so that it can
  960 # be called from either pre_header_initialize() or initilize(), depending on
  961 # whether a redirect is needed or not.
  962 #
  963 # it actually does a lot more than save changes to the file being edited, and
  964 # sometimes less.
  965 ################################################################################
  966 
  967   my ($self, $outputFilePath, $problemContents ) = @_;
  968   my $r             = $self->r;
  969   my $ce            = $r->ce;
  970 
  971   my $action        = $self->{action}||'no action';
  972   # my $editFilePath  = $self->{editFilePath}; # not used??
  973   my $sourceFilePath = $self->{sourceFilePath};
  974   my $tempFilePath  = $self->{tempFilePath};
  975 
  976   if (defined($problemContents) and ref($problemContents) ) {
  977     $problemContents = ${$problemContents};
  978   } elsif( not defined($problemContents) or $problemContents =~/\S/ ) {
  979     $problemContents = ${$self->{r_problemContents}};
  980   }
  981   ##############################################################################
  982   # read and update the targetFile and targetFile.tmp files in the directory
  983   # if a .tmp file already exists use that, unless the revert button has been pressed.
  984   # The .tmp files are removed when the file is or when the revert occurs.
  985   ##############################################################################
  986 
  987 
  988     unless (defined($outputFilePath) and $outputFilePath =~/\S/ ) {
  989       $self->addbadmessage("You must specify an file name in order to save a new file.");
  990       return "";
  991     }
  992   my $do_not_save    = 0 ;       # flag to prevent saving of file
  993   my $editErrors = '';
  994 
  995   ##############################################################################
  996   # write changes to the approriate files
  997   # FIXME  make sure that the permissions are set correctly!!!
  998   # Make sure that the warning is being transmitted properly.
  999   ##############################################################################
 1000 
 1001   my $writeFileErrors;
 1002   if ( defined($outputFilePath) and $outputFilePath =~/\S/  ) {   # save file
 1003       # Handle the problem of line endings.
 1004     # Make sure that all of the line endings are of unix type.
 1005     # Convert \r\n to \n
 1006     #$problemContents =~ s/\r\n/\n/g;
 1007     #$problemContents =~ s/\r/\n/g;
 1008 
 1009     # make sure any missing directories are created
 1010     WeBWorK::Utils::surePathToFile($ce->{courseDirs}->{templates},
 1011                                                             $outputFilePath);
 1012     die "outputFilePath is unsafe!" unless path_is_subdir($outputFilePath, $ce->{courseDirs}->{templates}, 1); # 1==path can be relative to dir
 1013 
 1014     eval {
 1015       local *OUTPUTFILE;
 1016       open OUTPUTFILE,  ">$outputFilePath"
 1017           or die "Failed to open $outputFilePath";
 1018       print OUTPUTFILE $problemContents;
 1019       close OUTPUTFILE;
 1020       # any errors are caught in the next block
 1021     };
 1022 
 1023     $writeFileErrors = $@ if $@;
 1024   }
 1025 
 1026   ###########################################################
 1027   # Catch errors in saving files,  clean up temp files
 1028   ###########################################################
 1029 
 1030   $self->{saveError} = $do_not_save;    # don't do redirects if the file was not saved.
 1031                                       # don't unlink files or send success messages
 1032 
 1033   if ($writeFileErrors) {
 1034       # get the current directory from the outputFilePath
 1035     $outputFilePath =~ m|^(/.*?/)[^/]+$|;
 1036     my $currentDirectory = $1;
 1037 
 1038     my $errorMessage;
 1039     # check why we failed to give better error messages
 1040     if ( not -w $ce->{courseDirs}->{templates} ) {
 1041       $errorMessage = "Write permissions have not been enabled in the templates directory.  No changes can be made.";
 1042     } elsif ( not -w $currentDirectory ) {
 1043       $errorMessage = "Write permissions have not been enabled in '".$self->shortPath($currentDirectory)."'.  Changes must be saved to a different directory for viewing.";
 1044     } elsif ( -e $outputFilePath and not -w $outputFilePath ) {
 1045       $errorMessage = "Write permissions have not been enabled for '".$self->shortPath($outputFilePath)."'.  Changes must be saved to another file for viewing.";
 1046     } else {
 1047       $errorMessage = "Unable to write to '".$self->shortPath($outputFilePath)."': $writeFileErrors";
 1048     }
 1049 
 1050     $self->{failure} = 1;
 1051     $self->addbadmessage(CGI::p($errorMessage));
 1052 
 1053   }
 1054   ###########################################################
 1055   # FIXME if the file is accompanied by auxiliary files transfer them as well
 1056   # if the filepath ends in   foobar/foobar.pg  then we assume there are auxiliary files
 1057   # copy the contents of the original foobar directory to the new one
 1058   #
 1059   ###########################################################
 1060     # If things have worked so far determine if the file might be accompanied by auxiliary files
 1061     # a path ending in    foo/foo.pg  is assumed to contain auxilliary files
 1062     #
 1063     my $auxiliaryFilesExist = 0;
 1064     if (defined($outputFilePath) and $outputFilePath) {
 1065           my ($dir, $prob) = $outputFilePath =~ m|([^/]+)/([^/]+)\.pg$|;  # must be a problem file ending in .pg
 1066       $auxiliaryFilesExist = 1 if (defined($dir) and defined ($prob) and $dir eq $prob);
 1067     }
 1068 
 1069     if ($auxiliaryFilesExist and not $do_not_save ) {
 1070         my $sourceDirectory = $sourceFilePath;
 1071       my $outputDirectory = $outputFilePath;
 1072         $sourceDirectory =~ s|/[^/]+\.pg$||;
 1073         $outputDirectory =~ s|/[^/]+\.pg$||;
 1074 ##############
 1075 # Transfer this to Utils::copyAuxiliaryFiles($sourceDirectory, $destinationDirectory)
 1076 ##############
 1077       my @filesToCopy;
 1078         @filesToCopy = WeBWorK::Utils::readDirectory($sourceDirectory) if -d $sourceDirectory;
 1079         foreach my $file (@filesToCopy) {
 1080             next if $file =~ /\.pg$/;   # .pg file should already be transferred
 1081           my $fromPath = "$sourceDirectory/$file";
 1082           my $toPath   = "$outputDirectory/$file";
 1083           if (-f $fromPath and -r $fromPath and not -e $toPath) { # don't copy directories, don't copy files that have already been copied
 1084         copy($fromPath, $toPath) or $writeFileErrors.= "<br> Error copying $fromPath to $toPath";
 1085         # need to use binary transfer for gif files.  File::Copy does this.
 1086         #warn "copied from $fromPath to $toPath";
 1087         #warn "files are different ",system("diff $fromPath $toPath");
 1088           }
 1089           $self->addbadmessage($writeFileErrors) if defined($writeFileErrors) and $writeFileErrors;
 1090 
 1091 
 1092         }
 1093         $self->addgoodmessage("Copied auxiliary files from $sourceDirectory to  new location at $outputDirectory");
 1094 
 1095     }
 1096  ##############
 1097  ##############
 1098 
 1099   ###########################################################
 1100   # clean up temp files on revert, save and save_as
 1101   ###########################################################
 1102   unless( $writeFileErrors or $do_not_save) {  # everything worked!  unlink and announce success!
 1103     # unlink the temporary file if there are no errors and the save button has been pushed
 1104     if (($action eq 'save' or $action eq 'save_as') and (-w $self->{tempFilePath})  ) {
 1105 
 1106                  $self->addgoodmessage("Deleting temp file at " . $self->shortPath($self->{tempFilePath}));
 1107                  die "tempFilePath is unsafe!" unless path_is_subdir($self->{tempFilePath}, $ce->{courseDirs}->{templates}, 1); # 1==path can be relative to dir
 1108                  unlink($self->{tempFilePath}) ;
 1109     }
 1110 
 1111     if ( defined($outputFilePath) and ! $self->{failure} and not $self->isTempEditFilePath($outputFilePath) ) {
 1112                 # don't announce saving of temporary editing files
 1113       my $msg = "Saved to file '".$self->shortPath($outputFilePath)."'.";
 1114 
 1115       $self->addgoodmessage($msg);
 1116       #$self->{inputFilePath} = $outputFilePath; ## DPVC -- avoid file-not-found message
 1117     }
 1118 
 1119   }
 1120 
 1121 
 1122 }  # end saveFileChanges
 1123 
 1124 
 1125 
 1126 
 1127 
 1128 sub getActionParams {
 1129   my ($self, $actionID) = @_;
 1130   my $r = $self->{r};
 1131 
 1132   my %actionParams=();
 1133   foreach my $param ($r->param) {
 1134     next unless $param =~ m/^action\.$actionID\./;
 1135     $actionParams{$param} = [ $r->param($param) ];
 1136   }
 1137   return %actionParams;
 1138 }
 1139 
 1140 sub fixProblemContents {
 1141     #NOT a method
 1142     my $problemContents = shift;
 1143     # Handle the problem of line endings.
 1144     # Make sure that all of the line endings are of unix type.
 1145     # Convert \r\n to \n
 1146     $problemContents =~ s/\r\n/\n/g;
 1147     $problemContents =~ s/\r/\n/g;
 1148     $problemContents;
 1149 }
 1150 
 1151 sub fresh_edit_handler {
 1152   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1153   #$self->addgoodmessage("fresh_edit_handler called");
 1154 }
 1155 sub view_form {
 1156   my ($self, $onChange, %actionParams) = @_;
 1157   my $file_type     = $self->{file_type};
 1158   return "" if    $file_type eq 'hardcopy_header';  # these can't yet be edited from temporary files #FIXME
 1159   my $output_string = "View";
 1160   unless ($file_type eq 'course_info' || $file_type eq 'options_info') {
 1161 
 1162     $output_string .= join(" ",
 1163       " using seed ",
 1164       CGI::textfield(-name=>'action.view.seed',-value=>$self->{problemSeed},-onfocus=>$onChange),
 1165       "and display mode ",
 1166       CGI::popup_menu(-name=>'action.view.displayMode', -values=>$self->r->ce->{pg}->{displayModes},
 1167         -default=>$self->{displayMode}, -onmousedown=>$onChange)
 1168     );
 1169   }
 1170 
 1171   return $output_string;  #FIXME  add -labels to the pop up menu
 1172 }
 1173 
 1174 sub view_handler {
 1175   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1176   my $courseName      =  $self->{courseID};
 1177   my $setName         =  $self->{setID};
 1178   my $fullSetName     =  $self->{fullSetID};
 1179   my $problemNumber   =  $self->{problemID};
 1180   my $problemSeed     = ($actionParams->{'action.view.seed'}) ?
 1181                                   $actionParams->{'action.view.seed'}->[0]
 1182                                   : 1234;
 1183   my $displayMode     = ($actionParams->{'action.view.displayMode'}) ?
 1184                                   $actionParams->{'action.view.displayMode'}->[0]
 1185                                   : $self->r->ce->{pg}->{options}->{displayMode};
 1186 
 1187   my $editFilePath        = $self->{editFilePath};
 1188   my $tempFilePath        = $self->{tempFilePath};
 1189   ########################################################
 1190   # grab the problemContents from the form in order to save it to the tmp file
 1191   ########################################################
 1192   my $problemContents     = fixProblemContents($self->r->param('problemContents'));
 1193   $self->{r_problemContents}    = \$problemContents;
 1194 
 1195 
 1196   my $do_not_save = 0;
 1197   my $file_type = $self->{file_type};
 1198   $self->saveFileChanges($tempFilePath,);
 1199 
 1200   ########################################################
 1201   # construct redirect URL and redirect
 1202   ########################################################
 1203   my $edit_level = $self->r->param("edit_level") || 0;
 1204   $edit_level++;
 1205   my $viewURL;
 1206 
 1207   my $relativeTempFilePath = $self->getRelativeSourceFilePath($tempFilePath);
 1208 
 1209   if ($file_type eq 'problem' or $file_type eq 'source_path_for_problem_file') { # redirect to Problem.pm or GatewayQuiz.pm
 1210 
 1211     # we need to know if the set is a gateway set to determine the redirect
 1212     my $globalSet = $self->r->db->getGlobalSet( $setName );
 1213 
 1214     my $problemPage;
 1215     if ( defined($globalSet) && $globalSet->assignment_type =~ /gateway/ ) {
 1216       $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::GatewayQuiz",
 1217       courseID => $courseName, setID => "Undefined_Set");
 1218       # courseID => $courseName, setID => $fullSetName);
 1219     } else {
 1220       $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::Problem",
 1221                   courseID => $courseName, setID => $setName, problemID => $problemNumber
 1222       );
 1223     }
 1224 
 1225     $viewURL = $self->systemLink($problemPage,
 1226       params => {
 1227         displayMode        => $displayMode,
 1228         problemSeed        => $problemSeed,
 1229         editMode           => "temporaryFile",
 1230         edit_level         => $edit_level,
 1231         sourceFilePath     => $relativeTempFilePath,
 1232         status_message     => uri_escape($self->{status_message})
 1233 
 1234       }
 1235     );
 1236   } elsif ($file_type eq 'set_header' ) { # redirect to ProblemSet
 1237     my $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet",
 1238       courseID => $courseName, setID => $setName,
 1239     );
 1240 
 1241     $viewURL = $self->systemLink($problemPage,
 1242       params => {
 1243         set_header         => $tempFilePath,
 1244         displayMode        => $displayMode,
 1245         problemSeed        => $problemSeed,
 1246         editMode           => "temporaryFile",
 1247         edit_level         => $edit_level,
 1248         sourceFilePath     => $relativeTempFilePath,
 1249         status_message     => uri_escape($self->{status_message})
 1250 
 1251       }
 1252     );
 1253   } elsif ($file_type eq 'hardcopy_header') { # redirect to ProblemSet?? # it's difficult to view temporary changes for hardcopy headers
 1254     my $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet",
 1255       courseID => $courseName, setID => $setName,
 1256     );
 1257 
 1258     $viewURL = $self->systemLink($problemPage,
 1259       params => {
 1260         set_header         => $tempFilePath,
 1261         displayMode        => $displayMode,
 1262         problemSeed        => $problemSeed,
 1263         editMode           => "temporaryFile",
 1264         edit_level         => $edit_level,
 1265         sourceFilePath     => $relativeTempFilePath,
 1266         status_message     => uri_escape($self->{status_message})
 1267 
 1268       }
 1269     );
 1270 
 1271   } elsif ($file_type eq 'course_info') {  # redirec to ProblemSets.pm
 1272     my $problemSetsPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets",
 1273       courseID => $courseName);
 1274     $viewURL = $self->systemLink($problemSetsPage,
 1275       params => {
 1276 
 1277         course_info        => $tempFilePath,
 1278         editMode           => "temporaryFile",
 1279         edit_level         => $edit_level,
 1280         sourceFilePath     => $relativeTempFilePath,
 1281         status_message     => uri_escape($self->{status_message})
 1282       }
 1283     );
 1284   } elsif ($file_type eq 'options_info') {  # redirec to Options.pm
 1285     my $optionsPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::Options",
 1286       courseID => $courseName);
 1287     $viewURL = $self->systemLink($optionsPage,
 1288       params => {
 1289         options_info       => $tempFilePath,
 1290         editMode           => "temporaryFile",
 1291         edit_level         => $edit_level,
 1292         sourceFilePath     => $relativeTempFilePath,
 1293         status_message     => uri_escape($self->{status_message})
 1294       }
 1295     );
 1296   } else {
 1297     die "I don't know how to redirect this file type $file_type ";
 1298   }
 1299 
 1300   $self->reply_with_redirect($viewURL);
 1301 }
 1302 
 1303 sub add_problem_form {
 1304    my $self            = shift;
 1305   my ($onChange, %actionParams) = @_;
 1306   my $r               = $self->r;
 1307   my $setName         = $self->{setID} ;
 1308   my $problemNumber   = $self->{problemID} ;
 1309     $setName            = defined($setName) ? $setName : '';  # we need this instead of using the || construction
 1310                                                               # to keep set 0 from being set to the
 1311                                                               # empty string.
 1312     my $filePath        = $self->{inputFilePath};
 1313   $setName   =~ s|^set||;
 1314   my @allSetNames = sort $r->db->listGlobalSets;
 1315   for (my $j=0; $j<scalar(@allSetNames); $j++) {
 1316     $allSetNames[$j] =~ s|^set||;
 1317     $allSetNames[$j] =~ s|\.def||;
 1318   }
 1319   my $labels = {
 1320     problem         => 'problem',
 1321     set_header      => 'set header',
 1322     hardcopy_header => 'hardcopy header',
 1323   };
 1324   return "" if $self->{file_type} eq 'course_info' || $self->{file_type} eq 'options_info';
 1325   return join(" ",
 1326     "Add to set " ,
 1327     CGI::popup_menu({name=>'action.add_problem.target_set', values=>\@allSetNames, default=>$setName, onmousedown=>$onChange}),
 1328     " as ",
 1329     CGI::popup_menu({name=>'action.add_problem.file_type', values=>['problem','set_header', 'hardcopy_header'], labels=>$labels, default=>$self->{file_type}, onmousedown=>$onChange}),
 1330 
 1331   );  #FIXME  add -lables to the pop up menu
 1332   return "";
 1333 }
 1334 
 1335 sub add_problem_handler {
 1336   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1337   #$self->addgoodmessage("add_problem_handler called");
 1338   my $courseName      =  $self->{courseID};
 1339   my $setName         =  $self->{setID};
 1340   my $problemNumber   =  $self->{problemID};
 1341   my $sourceFilePath  =  $self->{editFilePath};
 1342   my $displayMode     =  $self->{displayMode};
 1343   my $problemSeed     =  $self->{problemSeed};
 1344 
 1345   my $targetSetName         =  $actionParams->{'action.add_problem.target_set'}->[0];
 1346   my $targetFileType        =  $actionParams->{'action.add_problem.file_type'}->[0];
 1347   my $templatesPath         =  $self->r->ce->{courseDirs}->{templates};
 1348   $sourceFilePath    =~ s|^$templatesPath/||;
 1349 
 1350   my $edit_level = $self->r->param("edit_level") || 0;
 1351   $edit_level++;
 1352 
 1353   my $viewURL ='';
 1354   if ($targetFileType eq 'problem') {
 1355     my $targetProblemNumber   =  1+ WeBWorK::Utils::max( $self->r->db->listGlobalProblems($targetSetName));
 1356 
 1357     #################################################
 1358     # Update problem record
 1359     #################################################
 1360     my $problemRecord  = $self->addProblemToSet(
 1361                  setName        => $targetSetName,
 1362                  sourceFile     => $sourceFilePath,
 1363                  problemID      => $targetProblemNumber, #added to end of set
 1364     );
 1365     $self->assignProblemToAllSetUsers($problemRecord);
 1366     $self->addgoodmessage("Added $sourceFilePath to ". $targetSetName. " as problem $targetProblemNumber") ;
 1367     $self->{file_type}   = 'problem'; # change file type to problem -- if it's not already that
 1368 
 1369     #################################################
 1370     # Set up redirect Problem.pm
 1371     #################################################
 1372     my $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::Problem",
 1373       courseID  => $courseName,
 1374       setID     => $targetSetName,
 1375       problemID => $targetProblemNumber,
 1376     );
 1377     my $relativeSourceFilePath = $self->getRelativeSourceFilePath($sourceFilePath);
 1378     $viewURL = $self->systemLink($problemPage,
 1379         params => {
 1380           displayMode        => $displayMode,
 1381           problemSeed        => $problemSeed,
 1382           editMode           => "savedFile",
 1383           edit_level         => $edit_level,
 1384           sourceFilePath     => $relativeSourceFilePath,
 1385           status_message     => uri_escape($self->{status_message})
 1386 
 1387         }
 1388     );
 1389   } elsif ($targetFileType eq 'set_header')  {
 1390     #################################################
 1391     # Update set record
 1392     #################################################
 1393     my $setRecord  = $self->r->db->getGlobalSet($targetSetName);
 1394     $setRecord->set_header($sourceFilePath);
 1395     if(  $self->r->db->putGlobalSet($setRecord) ) {
 1396       $self->addgoodmessage("Added '".$self->shortPath($sourceFilePath)."' to ". $targetSetName. " as new set header ") ;
 1397     } else {
 1398       $self->addbadmessage("Unable to make '".$self->shortPath($sourceFilePath)."' the set header for $targetSetName");
 1399     }
 1400     $self->{file_type} = 'set_header'; # change file type to set_header if it not already so
 1401     #################################################
 1402     # Set up redirect
 1403     #################################################
 1404     my $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet",
 1405       courseID => $courseName, setID => $targetSetName
 1406     );
 1407     $viewURL = $self->systemLink($problemPage,
 1408         params => {
 1409           displayMode        => $displayMode,
 1410           editMode           => "savedFile",
 1411           edit_level         => $edit_level,
 1412           status_message     => uri_escape($self->{status_message})
 1413         }
 1414     );
 1415   } else {
 1416     die "Don't know what to do with target file type $targetFileType";
 1417   }
 1418 
 1419   $self->reply_with_redirect($viewURL);
 1420 }
 1421 
 1422 
 1423 sub save_form {
 1424   my ($self, $onChange, %actionParams) = @_;
 1425   my $r => $self->r;
 1426   #return "" unless defined($self->{tempFilePath}) and -e $self->{tempFilePath};
 1427   if ($self->{editFilePath} =~ /$BLANKPROBLEM$/ ) {
 1428     return "";  #Can't save blank problems without changing names
 1429   } elsif (-w $self->{editFilePath}) {
 1430 
 1431     return "Save to ".CGI::b($self->shortPath($self->{editFilePath}))." and View";
 1432 
 1433   } else {
 1434     return ""; #"Can't save -- No write permission";
 1435   }
 1436 
 1437 }
 1438 
 1439 sub save_handler {
 1440   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1441   #$self->addgoodmessage("save_handler called");
 1442   my $courseName      =  $self->{courseID};
 1443   my $setName         =  $self->{setID};
 1444   my $fullSetName     =  $self->{fullSetID};
 1445   my $problemNumber   =  $self->{problemID};
 1446   my $displayMode     =  $self->{displayMode};
 1447   my $problemSeed     =  $self->{problemSeed};
 1448 
 1449   #################################################
 1450   # grab the problemContents from the form in order to save it to a new permanent file
 1451   # later we will unlink (delete) the current temporary file
 1452   #################################################
 1453   my $problemContents = fixProblemContents($self->r->param('problemContents'));
 1454   $self->{r_problemContents} = \$problemContents;
 1455 
 1456   #################################################
 1457   # Construct the output file path
 1458   #################################################
 1459   my $editFilePath        = $self->{editFilePath};
 1460   my $outputFilePath      = $editFilePath;
 1461 
 1462   my $do_not_save = 0;
 1463   my $file_type = $self->{file_type};
 1464   $self->saveFileChanges($outputFilePath);
 1465   #################################################
 1466   # Set up redirect to Problem.pm
 1467   #################################################
 1468   my $viewURL;
 1469   ########################################################
 1470   # construct redirect URL and redirect
 1471   ########################################################
 1472   if ($file_type eq 'problem' || $file_type eq 'source_path_for_problem_file') { # redirect to Problem.pm
 1473 
 1474     # we need to know if the set is a gateway set to determine the redirect
 1475     my $globalSet = $self->r->db->getGlobalSet( $setName );
 1476     my $problemPage;
 1477     if ( defined( $globalSet) && $globalSet->assignment_type =~ /gateway/ ) {
 1478       $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::GatewayQuiz",
 1479       courseID => $courseName, setID => "Undefined_Set");
 1480       # courseID => $courseName, setID => $fullSetName);
 1481     } else {
 1482       $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::Problem",
 1483                   courseID => $courseName, setID => $setName, problemID => $problemNumber );
 1484     }
 1485 
 1486     my $relativeEditFilePath = $self->getRelativeSourceFilePath($editFilePath);
 1487 
 1488     $viewURL = $self->systemLink($problemPage,
 1489       params => {
 1490         displayMode        => $displayMode,
 1491         problemSeed        => $problemSeed,
 1492         editMode           => "savedFile",
 1493         edit_level         => 0,
 1494         sourceFilePath     => $relativeEditFilePath,
 1495         status_message     => uri_escape($self->{status_message})
 1496 
 1497       }
 1498     );
 1499   } elsif ($file_type eq 'set_header' ) { # redirect to ProblemSet
 1500     my $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet",
 1501       courseID => $courseName, setID => $setName,
 1502     );
 1503 
 1504     $viewURL = $self->systemLink($problemPage,
 1505       params => {
 1506         displayMode        => $displayMode,
 1507         problemSeed        => $problemSeed,
 1508         editMode           => "savedFile",
 1509         edit_level         => 0,
 1510         status_message     => uri_escape($self->{status_message})
 1511 
 1512       }
 1513     );
 1514   } elsif ( $file_type eq 'hardcopy_header') { # redirect to ProblemSet
 1515     my $problemPage = $self->r->urlpath->newFromModule('WeBWorK::ContentGenerator::Hardcopy',
 1516       courseID => $courseName, setID => $setName,
 1517     );
 1518 
 1519     $viewURL = $self->systemLink($problemPage,
 1520       params => {
 1521         displayMode        => $displayMode,
 1522         problemSeed        => $problemSeed,
 1523         editMode           => "savedFile",
 1524         edit_level         => 0,
 1525         status_message     => uri_escape($self->{status_message})
 1526 
 1527       }
 1528     );
 1529 
 1530   } elsif ($file_type eq 'course_info') {  # redirect to ProblemSets.pm
 1531     my $problemSetsPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets",
 1532       courseID => $courseName);
 1533     $viewURL = $self->systemLink($problemSetsPage,
 1534       params => {
 1535         editMode           => ("savedFile"),
 1536         edit_level         => 0,
 1537         status_message     => uri_escape($self->{status_message})
 1538       }
 1539     );
 1540   } elsif ($file_type eq 'options_info') {  # redirect to Options.pm
 1541     my $optionsPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::Options",
 1542       courseID => $courseName);
 1543     $viewURL = $self->systemLink($optionsPage,
 1544       params => {
 1545         editMode           => ("savedFile"),
 1546         edit_level         => 0,
 1547         status_message     => uri_escape($self->{status_message})
 1548       }
 1549     );
 1550   } elsif ($file_type eq 'source_path_for_problem_file') {  # redirect to ProblemSets.pm
 1551     my $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
 1552     courseID => $courseName, setID => $setName, problemID => $problemNumber
 1553     );
 1554     my $viewURL = $self->systemLink($problemPage,
 1555        params=>{
 1556           displayMode        => $displayMode,
 1557         problemSeed        => $problemSeed,
 1558         editMode           => "savedFile",
 1559         edit_level         => 0,
 1560         sourceFilePath     => $outputFilePath, #The path relative to the templates directory is required.
 1561         file_type          => 'source_path_for_problem_file',
 1562         status_message     => uri_escape($self->{status_message})
 1563 
 1564        }
 1565   );
 1566 
 1567   } else {
 1568     die "I don't know how to redirect this file type $file_type ";
 1569   }
 1570 
 1571   $self->reply_with_redirect($viewURL);
 1572 }
 1573 
 1574 
 1575 
 1576 sub make_local_copy_form {
 1577   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1578   my $editFilePath    = $self->{editFilePath}; # path to the permanent file to be edited
 1579   #warn "editFilePath $editFilePath inputFilePath",$self->{inputFilePath};
 1580   return "" unless -e $editFilePath;
 1581   return "" if -w $editFilePath;
 1582   return "" unless    $self->{file_type} eq 'problem'           # need problem structure to make local copy in most cases
 1583        or $self->{file_type} eq 'blank_problem'   # $editFilePath eq  $self->r->cr->{webworkFiles}{screenSnippets}{blankProblem}
 1584                    or $self->{file_type} eq 'set_header'      # $editFilePath eq  $self->r->ce->{webworkFiles}->{hardcopySnippets}->{setHeader}   # special case to make copy of screen header
 1585                    or $self->{file_type} eq 'hardcopy_header';  #  $editFilePath eq  $self->r->ce->{webworkFiles}->{screenSnippets}->{setHeader}   ;  # special case to make copy of hardcopy header
 1586                    #  or $self->{file_type} eq 'source_path_for_problem_file'; # need setID and problemID to make local copy -- can't be done in this case.
 1587   # make sure setID is well defined before allowing local copy
 1588   my $setID         = $self->{setID};
 1589   my $probNum       = ($self->{file_type} eq 'problem')? "/problem $self->{problemID}" : "";
 1590   return "" unless defined($setID) && $setID =~ m/\S/ && $setID ne 'Undefined_Set';
 1591 
 1592   return join ("",
 1593     "Save local editable copy as: [TMPL]/".($self->determineLocalFilePath($editFilePath)).CGI::br()." and use in ".CGI::b("set $setID$probNum"),
 1594     CGI::hidden(-name=>'action.make_local_copy.target_file', -value=>$self->determineLocalFilePath($editFilePath) ),
 1595     CGI::hidden(-name=>'action.make_local_copy.source_file', -value=>$editFilePath ),
 1596     CGI::hidden(-name=>'action.make_local_copy.file_type',-value=>$self->{file_type}),
 1597     CGI::hidden(-name=>'action.make_local_copy.saveMode',-value=>'rename')
 1598   );
 1599 
 1600 }
 1601 
 1602 
 1603 sub make_local_copy_handler {
 1604   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1605   foreach my $key (qw(target_file file_type saveMode source_file)) {
 1606     $actionParams->{"action.save_as.$key"}->[0] = $actionParams->{"action.make_local_copy.$key"}->[0];
 1607     #warn "action.make_local_copy.$key", @{$actionParams->{"action.make_local_copy.$key"}}
 1608   }
 1609     save_as_handler($self, $genericParams, $actionParams, $tableParams);
 1610 
 1611 
 1612 }
 1613 sub save_as_form {  # calls the save_as_handler
 1614   my ($self, $onChange, %actionParams) = @_;
 1615   my $editFilePath  = $self->{editFilePath};
 1616 # return "" unless -w $editFilePath;  ##  DPVC -- we don't need to be able to write the original in order to make a copy
 1617 
 1618 
 1619   my $templatesDir  =  $self->r->ce->{courseDirs}->{templates};
 1620   my $setID         = $self->{setID};
 1621   my $fullSetID     = $self->{fullSetID};
 1622 
 1623 
 1624   my $shortFilePath =  $editFilePath;
 1625   $shortFilePath   =~ s|^$templatesDir/||;
 1626   $shortFilePath =~ s|^.*/|| if $shortFilePath =~ m|^/|;  # if it is still an absolute path don't suggest that you save to it.
 1627 
 1628 ### --- old menu-based apparoach ---
 1629 # my $allowedActions = (defined($setID) && $setID =~/\S/ && $setID ne 'Undefined_Set') ? ['save_a_copy','rename' ] : ['save_a_copy'];
 1630 
 1631 # return CGI::popup_menu(
 1632 #            -name=>'action.save_as.saveMode', -values=>$allowedActions,
 1633 #            -default=>'rename',-labels=>{save_a_copy=>'Create a copy of file at ', rename=>'Rename file path to'},
 1634 #            -onmousedown=>$onChange
 1635 #           ). " [TMPL]/".
 1636 ###
 1637 
 1638   my $probNum = ($self->{file_type} eq 'problem')? "/problem $self->{problemID}" : "";
 1639   my $andRelink = '';
 1640   $andRelink = CGI::br().' and '.CGI::checkbox(
 1641         -name => "action.save_as.saveMode",
 1642         -value => "rename",
 1643         -label => "",
 1644         -checked => 1,
 1645         -onclick=>$onChange
 1646         ).
 1647          " use in ".CGI::b("set $fullSetID$probNum")
 1648              if defined($setID) && $setID =~ m/\S/ && $setID ne 'Undefined_Set' &&
 1649           $self->{file_type} ne 'blank_problem';
 1650   return 'Save as [TMPL]/'.
 1651           CGI::textfield(
 1652              -name=>'action.save_as.target_file', -size=>60, -value=>"$shortFilePath",  #FIXME -- you might not be able to save to this default filepath
 1653              -onfocus=>$onChange
 1654             ).
 1655       CGI::hidden(-name=>'action.save_as.source_file', -value=>$editFilePath ).
 1656       CGI::hidden(-name=>'action.save_as.file_type',-value=>$self->{file_type}).
 1657       $andRelink;
 1658 }
 1659 
 1660 sub save_as_handler {
 1661   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1662   #$self->addgoodmessage("save_as_handler called");
 1663   $self->{status_message} = ''; ## DPVC -- remove bogus old messages
 1664   my $courseName      =  $self->{courseID};
 1665   my $setName         =  $self->{setID};
 1666   my $fullSetName     =  $self->{fullSetID};
 1667   my $problemNumber   =  $self->{problemID};
 1668   my $displayMode     =  $self->{displayMode};
 1669   my $problemSeed     =  $self->{problemSeed};
 1670   my $effectiveUserName = $self->r->param('effectiveUser');
 1671 
 1672   my $do_not_save = 0;
 1673   my $saveMode       = $actionParams->{'action.save_as.saveMode'}->[0] || 'save_a_copy';
 1674   my $new_file_name  = $actionParams->{'action.save_as.target_file'}->[0] || '';
 1675   my $sourceFilePath = $actionParams->{'action.save_as.source_file'}->[0] || '';
 1676   my $file_type      = $actionParams->{'action.save_as.file_type'}->[0] || '';
 1677   $self ->{sourceFilePath} = $sourceFilePath;  # store for use in saveFileChanges
 1678   $new_file_name =~ s/^\s*//;  #remove initial and final white space
 1679   $new_file_name =~ s/\s*$//;
 1680   if ( $new_file_name !~ /\S/) { # need a non-blank file name
 1681     # setting $self->{failure} stops saving and any redirects
 1682     $do_not_save = 1;
 1683     $self->addbadmessage(CGI::p("Please specify a file to save to."));
 1684   }
 1685 
 1686   #################################################
 1687   # grab the problemContents from the form in order to save it to a new permanent file
 1688   # later we will unlink (delete) the current temporary file
 1689   #################################################
 1690   my $problemContents = fixProblemContents($self->r->param('problemContents'));
 1691   $self->{r_problemContents} = \$problemContents;
 1692   warn "problem contents is empty" unless $problemContents;
 1693   #################################################
 1694   # Rescue the user in case they forgot to end the file name with .pg
 1695   #################################################
 1696 
 1697   if($file_type eq 'problem'
 1698     or $file_type eq 'blank_problem'
 1699     or $file_type eq 'set_header') {
 1700       $new_file_name =~ s/\.pg$//; # remove it if it is there
 1701       $new_file_name .= '.pg'; # put it there
 1702 
 1703   }
 1704   #################################################
 1705   # Construct the output file path
 1706   #################################################
 1707   my $outputFilePath = $self->r->ce->{courseDirs}->{templates} . '/' .
 1708                  $new_file_name;
 1709   if (defined $outputFilePath and -e $outputFilePath) {
 1710     # setting $do_not_save stops saving and any redirects
 1711     $do_not_save = 1;
 1712     $self->addbadmessage(CGI::p("File '".$self->shortPath($outputFilePath)."' exists.
 1713     File not saved. No changes have been made.
 1714     You can change the file path for this problem manually from the 'Hmwk Sets Editor' page"));
 1715   } else {
 1716     $self->{editFilePath} = $outputFilePath;
 1717     $self->{tempFilePath} = ''; # nothing needs to be unlinked.
 1718     $self->{inputFilePath} = '';
 1719   }
 1720 
 1721 
 1722   unless ($do_not_save ) {
 1723     $self->saveFileChanges($outputFilePath);
 1724 
 1725     if ($saveMode eq 'rename' and -r $outputFilePath) {
 1726     #################################################
 1727     # Modify source file path in problem
 1728     #################################################
 1729       if ($file_type eq 'set_header' ) {
 1730         my $setRecord = $self->r->db->getGlobalSet($setName);
 1731         $setRecord->set_header($new_file_name);
 1732         if ($self->r->db->putGlobalSet($setRecord)) {
 1733           $self->addgoodmessage("The set header for set $setName has been renamed to '".$self->shortPath($outputFilePath)."'.") ;
 1734         } else {
 1735           $self->addbadmessage("Unable to change the set header for set $setName. Unknown error.");
 1736         }
 1737       } elsif ($file_type eq 'hardcopy_header' ) {
 1738         my $setRecord = $self->r->db->getGlobalSet($setName);
 1739         $setRecord->hardcopy_header($new_file_name);
 1740         if ($self->r->db->putGlobalSet($setRecord)) {
 1741           $self->addgoodmessage("The hardcopy header for set $setName has been renamed to '".$self->shortPath($outputFilePath)."'.") ;
 1742         } else {
 1743           $self->addbadmessage("Unable to change the hardcopy header for set $setName. Unknown error.");
 1744         }
 1745       } else {
 1746         my $problemRecord;
 1747         if ( $fullSetName =~ /,v(\d+)$/ ) {
 1748           $problemRecord = $self->r->db->getMergedProblemVersion($effectiveUserName, $setName, $1, $problemNumber);
 1749         } else {
 1750           $problemRecord = $self->r->db->getGlobalProblem($setName,$problemNumber);
 1751         }
 1752         $problemRecord->source_file($new_file_name);
 1753         my $result = ( $fullSetName =~ /,v(\d+)$/ ) ?
 1754           $self->r->db->putProblemVersion($problemRecord) :
 1755           $self->r->db->putGlobalProblem($problemRecord);
 1756         if  ( $result  ) {
 1757           $self->addgoodmessage("The source file for 'set $fullSetName / problem $problemNumber' has been changed from ".
 1758           $self->shortPath($sourceFilePath)." to '".$self->shortPath($outputFilePath)."'.") ;
 1759         } else {
 1760           $self->addbadmessage("Unable to change the source file path for set $fullSetName, problem $problemNumber. Unknown error.");
 1761         }
 1762       }
 1763     } elsif ($saveMode eq 'save_a_copy') {
 1764     #################################################
 1765     # Don't modify source file path in problem -- just report
 1766     #################################################
 1767 
 1768       #$self->{status_message} = '';  ## DPVC remove old messages
 1769       $self->addgoodmessage("A new file has been created at '".$self->shortPath($outputFilePath).
 1770       "' with the contents below. No changes have been made to set $setName.");
 1771     } else {
 1772       $self->addbadmessage("Don't recognize saveMode: |$saveMode|. Unknown error.");
 1773     }
 1774 
 1775   }
 1776   my $edit_level = $self->r->param("edit_level") || 0;
 1777   $edit_level++;
 1778 
 1779   #################################################
 1780   # Set up redirect
 1781   # The redirect gives the server time to detect that the new file exists.
 1782   #################################################
 1783   my $problemPage;
 1784   my $new_file_type;
 1785   if ($saveMode eq 'save_a_copy' ) {
 1786     $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
 1787       courseID => $courseName, setID => 'Undefined_Set', problemID => 'Undefined_Set'
 1788     );
 1789     $new_file_type = 'source_path_for_problem_file';
 1790   } elsif ($saveMode eq 'rename') {
 1791     $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
 1792       courseID => $courseName, setID => $setName, problemID => $problemNumber
 1793     );
 1794     $new_file_type = $file_type;
 1795   } else {
 1796     $self->addbadmessage("Don't recognize saveMode: |$saveMode|. Unknown error.");
 1797     die "Don't recognize saveMode: |$saveMode|. Unknown error."
 1798   }
 1799 
 1800   #warn "save mode is $saveMode";
 1801 
 1802   my $relativeOutputFilePath = $self->getRelativeSourceFilePath($outputFilePath);
 1803 
 1804   my $viewURL = $self->systemLink($problemPage,
 1805                  params=>{
 1806                    sourceFilePath     => $relativeOutputFilePath, #The path relative to the templates directory is required.
 1807                    problemSeed        => $problemSeed,
 1808                    edit_level         => $edit_level,
 1809                    file_type          => $new_file_type,
 1810                    status_message     => uri_escape($self->{status_message})
 1811 
 1812                  }
 1813   );
 1814 
 1815   $self->reply_with_redirect($viewURL);
 1816 return "";  # no redirect needed
 1817 }
 1818 sub revert_form {
 1819   my ($self, $onChange, %actionParams) = @_;
 1820   my $editFilePath    = $self->{editFilePath};
 1821   return "Error: The original file $editFilePath cannot be read." unless -r $editFilePath;
 1822   return "" unless defined($self->{tempFilePath}) and -e $self->{tempFilePath} ;
 1823   return "Revert to ".$self->shortPath($editFilePath) ;
 1824 
 1825 }
 1826 sub revert_handler {
 1827   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1828   my $ce = $self->r->ce;
 1829   #$self->addgoodmessage("revert_handler called");
 1830   my $editFilePath       = $self->{editFilePath};
 1831   $self->{inputFilePath} = $editFilePath;
 1832   # unlink the temp files;
 1833   die "tempFilePath is unsafe!" unless path_is_subdir($self->{tempFilePath}, $ce->{courseDirs}->{templates}, 1); # 1==path can be relative to dir
 1834   unlink($self->{tempFilePath});
 1835   $self->addgoodmessage("Deleting temp file at " . $self->shortPath($self->{tempFilePath}));
 1836   $self->{tempFilePath}  = '';
 1837   my $problemContents    ='';
 1838   $self->{r_problemContents} = \$problemContents;
 1839   $self->addgoodmessage("Reverting to original file '".$self->shortPath($editFilePath)."'");
 1840   # no redirect is needed
 1841 }
 1842 
 1843 
 1844 
 1845 
 1846 # sub make_local_copy_handler {
 1847 #   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1848 #   #$self->addgoodmessage("make_local_copy_handler called");
 1849 #
 1850 #   my $courseName      =  $self->{courseID};
 1851 #   my $setName         =  $self->{setID};
 1852 #   my $problemNumber   =  $self->{problemID};
 1853 #
 1854 #   my $displayMode     =  $self->{displayMode};
 1855 #   my $problemSeed     =  $self->{problemSeed};
 1856 #   my $do_not_save     = 0;   #error flag
 1857 #
 1858 #   my $new_file_name = $actionParams->{'action.make_local_copy.target_file'}->[0] || '';
 1859 #   my $sourceFilePath = $actionParams->{'action.make_local_copy.source_file'}->[0] || '';
 1860 #   my $file_type      = $actionParams->{'action.make_local_copy.file_type'}->[0] ||'';
 1861 #
 1862 #   my $templatesPath         =  $self->r->ce->{courseDirs}->{templates};
 1863 #   $sourceFilePath    =~ s|^$templatesPath/||; # make sure path relative to templates directory
 1864 #
 1865 #   if ( $new_file_name !~ /\S/) { # need a non-blank file name
 1866 #     # setting $self->{failure} stops saving and any redirects
 1867 #     $do_not_save = 1;
 1868 #     #warn "new file name is $new_file_name";
 1869 #     $self->addbadmessage(CGI::p("Error: File to save to not specified."));
 1870 #   }
 1871 #
 1872 #   #################################################
 1873 #   # grab the problemContents from the form in order to save it to a new permanent file
 1874 #   # later we will unlink (delete) the current temporary file
 1875 #   #################################################
 1876 #
 1877 #   my $problemContents = fixProblemContents($self->r->param('problemContents'));
 1878 #   $self->{r_problemContents} = \$problemContents;
 1879 #   warn "problem contents is empty" unless $problemContents;
 1880 #   #################################################
 1881 #   # Construct the output file path
 1882 #   #################################################
 1883 #   my $outputFilePath = $self->r->ce->{courseDirs}->{templates} . '/' .
 1884 #                  $new_file_name;
 1885 #   if (defined $outputFilePath and -e $outputFilePath) {
 1886 #     # setting $do_not_save stops saving and any redirects
 1887 #     $do_not_save = 1;
 1888 #     $self->addbadmessage(CGI::p("File '".$self->shortPath($outputFilePath)."' exists.
 1889 #     File not saved. No changes have been made.
 1890 #     You can change the file path for this problem manually from the 'Hmwk Sets Editor' page"));
 1891 #   } else {
 1892 #     #$self->addgoodmessage("Saving to file '".$self->shortPath($outputFilePath)."'.");
 1893 #   }
 1894 #
 1895 #   unless ($do_not_save ) {
 1896 #     $self->saveFileChanges($outputFilePath);
 1897 #     #################################################
 1898 #     # Modify source file in problem
 1899 #     #################################################
 1900 #       if ($file_type eq 'set_header') {
 1901 #         my $setRecord = $self->r->db->getGlobalSet($setName);
 1902 #         $setRecord->set_header($new_file_name);
 1903 #         if ($self->r->db->putGlobalSet($setRecord)) {
 1904 #           $self->addgoodmessage("The set header for set $setName has been renamed to '".$self->shortPath($outputFilePath)."'.") ;
 1905 #         } else {
 1906 #           $self->addbadmessage("Unable to change the header for set $setName. Unknown error.");
 1907 #         }
 1908 #       } else {
 1909 #         my $problemRecord = $self->r->db->getGlobalProblem($setName,$problemNumber);
 1910 #         $problemRecord->source_file($new_file_name);
 1911 #         if  ( $self->r->db->putGlobalProblem($problemRecord)  ) {
 1912 #           $self->addgoodmessage("The current source file for problem $problemNumber has been renamed to '".$self->shortPath($outputFilePath)."'.") ;
 1913 #         } else {
 1914 #           $self->addbadmessage("Unable to change the source file path for set $setName, problem $problemNumber. Unknown error.");
 1915 #         }
 1916 #       }
 1917 #
 1918 #   }
 1919 #   my $edit_level = $self->r->param("edit_level") || 0;
 1920 #   $edit_level++;
 1921 #   #################################################
 1922 #   # Set up redirect
 1923 #   #################################################
 1924 #
 1925 #   my $problemPage = $self->r->urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
 1926 #     courseID => $courseName, setID => $setName, problemID => $problemNumber
 1927 #   );
 1928 #   my $viewURL = $self->systemLink($problemPage,
 1929 #                  params=>{
 1930 #                    sourceFilePath     => $new_file_name,
 1931 #                    edit_level         => $edit_level,
 1932 #                    file_type          => $self->{file_type},
 1933 #                    status_message     => uri_escape($self->{status_message})
 1934 #
 1935 #                  }
 1936 #   );
 1937 #   $self->reply_with_redirect($viewURL);
 1938 # }
 1939 
 1940 # sub rename_form {  # see the save_as form
 1941 # #   my ($self, $onChange, %actionParams) = @_;
 1942 # #   my $problemPath = $self->{editFilePath};
 1943 # #   my $templatesDir = $self->r->ce->{courseDirs}->{templates};
 1944 # #   #warn "problemPath $problemPath $templatesDir";
 1945 # #   $problemPath   =~ s|^$templatesDir/||;
 1946 # #   $problemPath  = '' if $problemPath =~ m|^/|;  # if it is still an absolute path don't suggest that you save to it.
 1947 # #   $self->addbadmessage("problem Path is $problemPath");
 1948 # #   return join("",
 1949 # #          "Rename problem file to : [TMPL]/".CGI::textfield(-name=>'action.rename.target_file', -size=>40, -value=>$problemPath),
 1950 # #           CGI::hidden(-name=>'action.make_local_copy.source_file', -value=>$self->{editFilePath} ),
 1951 # #   );
 1952 #
 1953 #
 1954 # }
 1955 
 1956 # sub rename_handler {
 1957 #     my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1958 #     $actionParams->{'action.make_local_copy.target_file'}->[0] = $actionParams->{'action.rename.target_file'}->[0];
 1959 #   make_local_copy_handler($self, $genericParams, $actionParams, $tableParams);
 1960 # }
 1961 
 1962 
 1963 
 1964 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9