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

View of /branches/gage_dev/webwork2/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6566 - (download) (as text) (annotate)
Thu Nov 25 19:19:30 2010 UTC (2 years, 5 months ago) by gage
File size: 84376 byte(s)
merge with trunk


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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9