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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4418 - (download) (as text) (annotate)
Sat Aug 26 17:34:40 2006 UTC (6 years, 8 months ago) by sh002i
File size: 81869 byte(s)
backport (gage): made the definition of $auxiliaryFilesExist more
robust.  This fixes bug 1056 and related bugs

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9