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