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