################################################################################ # WeBWorK Online Homework Delivery System # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm,v 1.29 2004/05/06 21:53:41 jj Exp $ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any later # version, or (b) the "Artistic License" which comes with this package. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################ package WeBWorK::ContentGenerator::Instructor::PGProblemEditor; use base qw(WeBWorK::ContentGenerator::Instructor); =head1 NAME WeBWorK::ContentGenerator::Instructor::ProblemSetEditor - Edit a set definition list =cut use strict; use warnings; use CGI qw(); use WeBWorK::Utils qw(readFile); use Apache::Constants qw(:common REDIRECT); use HTML::Entities; use WeBWorK::Utils::Tasks qw(fake_set fake_problem); ########################################################### # This editor will edit problem files or set header files or files, such as course_info # whose name is defined in the global.conf database # # Only files under the template directory ( or linked to this location) can be edited. # # The course information and problems are located in the course templates directory. # Course information has the name defined by courseFiles->{course_info} # # Only files under the template directory ( or linked to this location) can be edited. # # editMode = temporaryFile (view the temp file defined by course_info.txt.user_name.tmp # instead of the file course_info.txt) # The editFileSuffix is "user_name.tmp" by default. It's definition should be moved to Instructor.pm #FIXME ########################################################### #our $libraryName; #our $rowheight; sub pre_header_initialize { my ($self) = @_; my $r = $self->r; my $ce = $r->ce; my $urlpath = $r->urlpath; my $submit_button = $r->param('submit'); # obtain submit command from form # Save problem to permanent or temporary file, then redirect for viewing if (defined($submit_button) and ($submit_button eq 'Save' or $submit_button eq 'Refresh')) { my $setName = $r->urlpath->arg("setID"); my $problemNumber = $r->urlpath->arg("problemID"); # write the necessary files # return file path for viewing problem in $self->{currentSourceFilePath} # obtain the appropriate seed $self->saveFileChanges($setName, $problemNumber); ##### calculate redirect URL based on file type ##### # get some information #my $hostname = $r->hostname(); #my $port = $r->get_server_port(); #my $uri = $r->uri; my $courseName = $urlpath->arg("courseID"); my $problemSeed = ($r->param('problemSeed')) ? $r->param('problemSeed') : ''; my $displayMode = ($r->param('displayMode')) ? $r->param('displayMode') : ''; my $viewURL = ''; # problems redirect to Problem.pm $self->{file_type} eq 'problem' and do { my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", courseID => $courseName, setID => $setName, problemID => $problemNumber); $viewURL = $self->systemLink($problemPage, params => { displayMode => $displayMode, problemSeed => $problemSeed, editMode => ($submit_button eq "Save" ? "savedFile" : "temporaryFile"), sourceFilePath => $self->{currentSourceFilePath}, submiterror => $self->{submiterror}, } ); }; # set headers redirect to ProblemSet.pm $self->{file_type} eq 'set_header' and do { my $problemSetPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet", courseID => $courseName, setID => $setName); $viewURL = $self->systemLink($problemSetPage, params => { displayMode => $displayMode, problemSeed => $problemSeed, editMode => ($submit_button eq "Save" ? "savedFile" : "temporaryFile"), } ); }; # course info redirects to ProblemSets.pm $self->{file_type} eq 'course_info' and do { my $problemSetsPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets", courseID => $courseName); $viewURL = $self->systemLink($problemSetsPage, params => { editMode => ($submit_button eq "Save" ? "savedFile" : "temporaryFile"), } ); }; if ($viewURL) { $self->reply_with_redirect($viewURL); } else { die "Invalid file_type ", $self->{file_type}, " specified by saveFileChanges"; } } } sub initialize { my ($self) = @_; my $r = $self->r; my $setName = $r->urlpath->arg("setID"); my $problemNumber = $r->urlpath->arg("problemID"); # if we got to initialize(), then saveFileChanges was not called in pre_header_initialize(). # therefore we call it here: $self->saveFileChanges($setName, $problemNumber); } sub title { my $self = shift; my $r = $self->r; my $problemNumber = $r->urlpath->arg("problemID"); my $file_type = $self->{'file_type'} || ''; return "Set Header" if($file_type eq 'set_header'); return "Course Information" if($file_type eq 'course_info'); return 'Problem ' . $r->{urlpath}->name; } sub body { my ($self) = @_; my $r = $self->r; my $db = $r->db; my $ce = $r->ce; # Gathering info my $editFilePath = $self->{problemPath}; # path to the permanent file to be edited my $inputFilePath = $self->{inputFilePath}; # path to the file currently being worked with (might be a .tmp file) my $header = CGI::i("Editing problem: $inputFilePath"); ######################################################################### # Find the text for the problem, either in the tmp file, if it exists # or in the original file in the template directory ######################################################################### my $problemContents = ${$self->{r_problemContents}}; ######################################################################### # Format the page ######################################################################### # Define parameters for textarea # FIXME # Should the seed be set from some particular user instance?? # The mode list should be obtained from global.conf ultimately my $rows = 20; my $columns = 80; my $mode_list = ['plainText','formattedText','images']; my $displayMode = $self->{displayMode}; my $problemSeed = $self->{problemSeed}; my $uri = $r->uri; my $force_field = defined($r->param('sourceFilePath')) ? CGI::hidden(-name=>'sourceFilePath', -default=>$r->param('sourceFilePath')) : ''; return CGI::p($header), #CGI::start_form("POST",$r->uri,-target=>'_problem'), doesn't pass on the target parameter??? # THIS IS BECAUSE TARGET IS NOT A PARAMETER OF
!!!!!!!! qq!!, $self->hidden_authen_fields, $force_field, CGI::hidden(-name=>'file_type',-default=>$self->{file_type}), CGI::div( 'Seed: ', CGI::textfield(-name=>'problemSeed',-value=>$problemSeed), 'Mode: ', CGI::popup_menu(-name=>'displayMode', -values=>$mode_list, -default=>$displayMode), CGI::a({-href=>'http://webwork.math.rochester.edu/docs/docs/pglanguage/manpages/',-target=>"manpage_window"}, 'Manpages', ) ), CGI::p( CGI::textarea( -name => 'problemContents', -default => $problemContents, -rows => $rows, -columns => $columns, -override => 1, ), ), CGI::p( CGI::submit(-value=>'Refresh',-name=>'submit'), CGI::submit(-value=>'Save', -name=>'submit'), CGI::submit(-value=>'Revert', -name=>'submit'), CGI::submit(-value=>'Save as',-name=>'submit'), CGI::textfield(-name=>'save_to_new_file', -value=>""), ), CGI::end_form(), } ################################################################################ # Utilities ################################################################################ # saveFileChanges does most of the work. it is a separate method so that it can # be called from either pre_header_initialize() or initilize(), depending on # whether a redirect is needed or not. # # it actually does a lot more than save changes to the file being edited, and # sometimes less. sub saveFileChanges { my ($self, $setName, $problemNumber) = @_; my $r = $self->r; my $ce = $r->ce; my $db = $r->db; my $urlpath = $r->urlpath; my $courseName = $urlpath->arg("courseID"); my $user = $r->param('user'); my $effectiveUserName = $r->param('effectiveUser'); $setName = '' unless defined $setName; $problemNumber = '' unless defined $problemNumber; ##### Determine path to the file to be edited. ##### my $editFilePath = $ce->{courseDirs}->{templates}; my $problem_record = undef; my $file_type = $r->param("file_type") || ''; if ($file_type eq 'course_info') { # we are editing the course_info file $self->{file_type} = 'course_info'; # value of courseFiles::course_info is relative to templates directory $editFilePath .= '/' . $ce->{courseFiles}->{course_info}; } else { # we are editing a problem file or a set header file # FIXME there is a discrepancy in the way that the problems are found. # FIXME more error checking is needed in case the problem doesn't exist. # (i wonder what the above comments mean... -sam) if (defined $problemNumber) { if ($problemNumber == 0) { # we are editing a header file $self->{file_type} = 'set_header'; # first try getting the merged set for the effective user my $set_record = $db->getMergedSet($effectiveUserName, $setName); # checked # if that doesn't work (the set is not yet assigned), get the global record $set_record = $db->getGlobalSet($setName); # checked # bail if no set is found die "Cannot find a set record for set $setName" unless defined($set_record); $editFilePath .= '/' . $set_record->set_header; } else { # we are editing a "real" problem $self->{file_type} = 'problem'; # first try getting the merged problem for the effective user $problem_record = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked # if that doesn't work (the problem is not yet assigned), get the global record $problem_record = $db->getGlobalProblem($setName, $problemNumber) unless defined($problem_record); # checked if(not defined($problem_record)) { my $forcedSourceFile = $r->param('sourceFilePath'); # bail if no problem is found and we aren't faking it die "Cannot find a problem record for set $setName / problem $problemNumber" unless defined($forcedSourceFile); $problem_record = fake_problem($db); $problem_record->problem_id($problemNumber); $problem_record->source_file($forcedSourceFile); } $editFilePath .= '/' . $problem_record->source_file; } } } my $editFileSuffix = $user.'.tmp'; my $submit_button = $r->param('submit'); ############################################################################## # Determine the display mode # try to get problem seed from the input parameter, or from the problem record # This will be needed for viewing the problem via redirect. # They are also two of the parameters which can be set by the editor ############################################################################## my $displayMode; if (defined $r->param('displayMode')) { $displayMode = $r->param('displayMode'); } else { $displayMode = $ce->{pg}->{options}->{displayMode}; } my $problemSeed; if (defined $r->param('problemSeed')) { $problemSeed = $r->param('problemSeed'); } elsif (defined($problem_record) and $problem_record->can('problem_seed')) { $problemSeed = $problem_record->problem_seed; } # make absolutely sure that the problem seed is defined, if it hasn't been. $problemSeed = '123456' unless defined $problemSeed and $problemSeed =~/\S/; ############################################################################## # read and update the targetFile and targetFile.tmp files in the directory # if a .tmp file already exists use that, unless the revert button has been pressed. # These .tmp files are # removed when the file is finally saved. ############################################################################## my $problemContents = ''; my $currentSourceFilePath = ''; my $editErrors = ''; my $inputFilePath; if (-r "$editFilePath.$editFileSuffix") { $inputFilePath = "$editFilePath.$editFileSuffix"; } else { $inputFilePath = $editFilePath; } $inputFilePath = $editFilePath if defined($submit_button) and $submit_button eq 'Revert'; ##### handle button clicks ##### if (not defined $submit_button or $submit_button eq 'Revert' ) { # this is a fresh editing job # copy the pg file to a new file with the same name with .tmp added # store this name in the $self->currentSourceFilePath for use in body # try to read file if(-e $inputFilePath) { eval { $problemContents = WeBWorK::Utils::readFile($inputFilePath) }; $problemContents = $@ if $@; } else { # file not existing is not an error $problemContents = ''; } $currentSourceFilePath = "$editFilePath.$editFileSuffix"; $self->{currentSourceFilePath} = $currentSourceFilePath; $self->{problemPath} = $editFilePath; } elsif ($submit_button eq 'Refresh') { # grab the problemContents from the form in order to save it to the tmp file # store tmp file name in the $self->currentSourceFilePath for use in body $problemContents = $r->param('problemContents'); $currentSourceFilePath = "$editFilePath.$editFileSuffix"; $self->{currentSourceFilePath} = $currentSourceFilePath; $self->{problemPath} = $editFilePath; } elsif ($submit_button eq 'Save') { # grab the problemContents from the form in order to save it to the permanent file # later we will unlink (delete) the temporary file # store permanent file name in the $self->currentSourceFilePath for use in body $problemContents = $r->param('problemContents'); $currentSourceFilePath = "$editFilePath"; $self->{currentSourceFilePath} = $currentSourceFilePath; $self->{problemPath} = $editFilePath; } elsif ($submit_button eq 'Save as') { # grab the problemContents from the form in order to save it to a new permanent file # later we will unlink (delete) the current temporary file # store new permanent file name in the $self->currentSourceFilePath for use in body $problemContents = $r->param('problemContents'); $currentSourceFilePath = $ce->{courseDirs}->{templates} . '/' .$r->param('save_to_new_file'); $self->{currentSourceFilePath} = $currentSourceFilePath; $self->{problemPath} = $currentSourceFilePath; } else { die "Unrecognized submit command: $submit_button"; } # Handle the problem of line endings. Make sure that all of the line endings. Convert \r\n to \n $problemContents =~ s/\r\n/\n/g; $problemContents =~ s/\r/\n/g; # FIXME convert all double returns to paragraphs for .txt files # instead of doing this here, it should be done n the PLACE WHERE THE FILE IS DISPLAYED!!! #if ($self->{file_type} eq 'course_info' ) { # $problemContents =~ s/\n\n/\n

\n/g; #} ############################################################################## # write changes to the approriate files # FIXME make sure that the permissions are set correctly!!! # Make sure that the warning is being transmitted properly. ############################################################################## # FIXME set a local state rather continue to call on the submit button. if (defined $submit_button and $submit_button eq 'Save as' and defined $currentSourceFilePath and -e $currentSourceFilePath) { warn "File $currentSourceFilePath exists. File not saved."; } else { eval { local *OUTPUTFILE; open OUTPUTFILE, ">", $currentSourceFilePath or die "Failed to open $currentSourceFilePath"; print OUTPUTFILE $problemContents; close OUTPUTFILE; }; # any errors are caught in the next block } ########################################################### # Catch errors in saving files, clean up temp files ########################################################### my $openTempFileErrors = $@ if $@; if ($openTempFileErrors) { $self->{submiterror} = "Unable to write to $currentSourceFilePath: It is likely that the permissions in the template directory have not been set correctly. See log for details."; #diagnose errors: warn "Unable to write to $currentSourceFilePath: $openTempFileErrors"; warn "The file $currentSourceFilePath exists. \n " if -e $currentSourceFilePath; #FIXME warn "The file $currentSourceFilePath cannot be found. \n " unless -e $currentSourceFilePath; warn "The file $currentSourceFilePath does not have write permissions. \n" if -e $currentSourceFilePath and not -w $currentSourceFilePath; } else { # unlink the temporary file if there are no errors and the save button has been pushed unlink("$editFilePath.$editFileSuffix") if defined $submit_button and ($submit_button eq 'Save' or $submit_button eq 'Save as'); } # return values for use in the body subroutine $self->{inputFilePath} = $inputFilePath; $self->{displayMode} = $displayMode; $self->{problemSeed} = $problemSeed; $self->{r_problemContents} = \$problemContents; $self->{editFileSuffix} = $editFileSuffix; } 1;