Parent Directory
|
Revision Log
Allow making a local copy of a library problem with a single button push. The user interface is still cluttered. I think I'll rewrite the bottom button rows using radio buttons and a 'Take action' submit button as on the Classlist and Homework set editor pages.
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm,v 1.56 2005/07/30 17:26:45 gage 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 =head1 NAME 22 23 WeBWorK::ContentGenerator::Instructor::PGProblemEditor - Edit a pg file 24 25 =cut 26 27 use strict; 28 use warnings; 29 use CGI qw(); 30 use WeBWorK::Utils qw(readFile surePathToFile); 31 use Apache::Constants qw(:common REDIRECT); 32 use HTML::Entities; 33 use URI::Escape; 34 use WeBWorK::Utils; 35 use WeBWorK::Utils::Tasks qw(fake_set fake_problem); 36 37 ########################################################### 38 # This editor will edit problem files or set header files or files, such as course_info 39 # whose name is defined in the global.conf database 40 # 41 # Only files under the template directory ( or linked to this location) can be edited. 42 # 43 # The course information and problems are located in the course templates directory. 44 # Course information has the name defined by courseFiles->{course_info} 45 # 46 # Only files under the template directory ( or linked to this location) can be edited. 47 # 48 # editMode = temporaryFile (view the temp file defined by course_info.txt.user_name.tmp 49 # instead of the file course_info.txt) 50 # this flag is read by Problem.pm and ProblemSet.pm, perhaps others 51 # The TEMPFILESUFFIX is "user_name.tmp" by default. It's definition should be moved to Instructor.pm #FIXME 52 ########################################################### 53 54 ########################################################### 55 # The behavior of this module is essentially defined 56 # by the values of $file_type and the submit button which is placed in $action 57 ############################################################# 58 # File types which can be edited 59 # 60 # file_type eq 'problem' 61 # this is the most common type -- this editor can be called by an instructor when viewing any problem. 62 # the information for retrieving the source file is found using the problemID in order to look 63 # look up the source file path. 64 # 65 # file_type eq 'problem_with_source' 66 # This is the same as the 'problem' file type except that the source for the problem is found in 67 # the parameter $r->param('sourceFilePath'). 68 # 69 # file_type eq 'set_header' 70 # 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. 71 # 72 # file_type eq 'hardcopy_header' 73 # 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. 74 # But it is used instead of set_header when producing a hardcopy of the problem set in the TeX format, instead of producing HTML 75 # formatted version for use on the computer screen. 76 # 77 # filte_type eq 'course_info 78 # This allows editing of the course_info.txt file which gives general information about the course. It is called from the 79 # ProblemSets.pm module. 80 # 81 # file_type eq 'blank_problem' 82 # This is a special call which allows one to create and edit a new PG problem. The "stationery" source for this problem is 83 # stored in the conf/snippets directory and defined in global.conf by $webworkFiles{screenSnippets}{blankProblem} 84 ############################################################# 85 # submit button actions -- these and the file_type determine the state of the module 86 # Save ---- action = save 87 # Save as ---- action = save_as 88 # View Problem ---- action = refresh 89 # Add this problem to: ---- action = add_problem_to_set 90 # Make this set header for: ---- action = add_set_header_to_set 91 # Revert ---- action = revert 92 # no submit button defined ---- action = fresh_edit 93 ################################################### 94 # 95 # Determining which is the correct path to the file is a mess!!! FIXME 96 # The path to the file to be edited is eventually put in tempFilePath 97 # 98 # $problemPath is also used as is editFilePath. let's try to regularize these. 99 #(sourceFile) (problemPath)(tempFilePath)(editFilePath)(forcedSourceFile)(problemPath) 100 #input parameter can be: sourceFilePath 101 ################################################################# 102 # params read 103 # user 104 # effectiveUser 105 # submit 106 # file_type 107 # problemSeed 108 # displayMode 109 # edit_level 110 # make_local_copy 111 # sourceFilePath 112 # problemContents 113 # save_to_new_file 114 # 115 116 #our $libraryName; 117 #our $rowheight; 118 our $TEMPFILESUFFIX; 119 120 sub pre_header_initialize { 121 my ($self) = @_; 122 my $r = $self->r; 123 my $ce = $r->ce; 124 my $urlpath = $r->urlpath; 125 my $authz = $r->authz; 126 my $user = $r->param('user'); 127 $TEMPFILESUFFIX = $user.'.tmp'; 128 129 my $submit_button = $r->param('submit'); # obtain submit command from form 130 my $file_type = $r->param("file_type") || ''; 131 my $setName = $r->urlpath->arg("setID") ; # using $r->urlpath->arg("setID") ||'' causes trouble with set 0!!! 132 my $problemNumber = $r->urlpath->arg("problemID"); 133 134 # Check permissions 135 return unless ($authz->hasPermissions($user, "access_instructor_tools")); 136 return unless ($authz->hasPermissions($user, "modify_problem_sets")); 137 138 ############################################################################# 139 # Save file to permanent or temporary file, then redirect for viewing 140 ############################################################################# 141 # 142 # Any file "saved as" should be assigned to "Undefined_Set" and redirectoed to be viewed again in the editor 143 # 144 # Problems "saved" or 'refreshed' are to be redirected to the Problem.pm module 145 # Set headers which are "saved" are to be redirected to the ProblemSet.pm page 146 # Hardcopy headers which are "saved" are aso to be redirected to the ProblemSet.pm page 147 # Course_info files are redirected to the ProblemSets.pm page 148 ############################################################################## 149 150 151 152 ###################################### 153 # Insure that file_type is defined 154 ###################################### 155 # We have already read in the file_type parameter from the form 156 # 157 # If this has not been defined we are dealing with a set header 158 # or regular problem 159 if (defined($file_type) and ($file_type =~/\S/)) { #file_type is defined and is not blank 160 # file type is already defined -- do nothing 161 } else { 162 # if "sourcFilePath" is defined in the form, then we are getting the path directly. 163 # if the problem number is defined and is 0 164 # then we are dealing with some kind of 165 # header file. The default is 'set_header' which prints properly 166 # to the screen. 167 # If the problem number is not zero, we are dealing with a real problem 168 ###################################### 169 if ( defined($r->param('sourceFilePath') and $r->param('sourceFilePath') =~/\S/) ) { 170 $file_type ='source_path_for_problem_file'; 171 } elsif ( defined($problemNumber) ) { 172 if ( $problemNumber =~/^\d+$/ and $problemNumber == 0 ) { # if problem number is numeric and zero 173 $file_type = 'set_header' unless $file_type eq 'set_header' 174 or $file_type eq 'hardcopy_header'; 175 } else { 176 $file_type = 'problem'; 177 } 178 179 } 180 } 181 die "The file_type variable has not been defined or is blank." unless defined($file_type) and $file_type =~/\S/; 182 $self->{file_type} = $file_type; 183 184 ########################################## 185 # File type is one of: blank_problem course_info problem set_header hardcopy_header problem_with_source 186 ########################################## 187 # 188 # Determine the path to the file 189 # 190 ########################################### 191 $self->getFilePaths($setName, $problemNumber, $file_type,$TEMPFILESUFFIX); 192 # result stored in $self->{editFilePath}, and $self->{tempFilePath} 193 ########################################## 194 # 195 # Determine action 196 # 197 ########################################### 198 # Submit button is one of: "add this problem to" , "add this set header to ", "Refresh" "Revert" "Save" "Save As" 199 $submit_button = $r->param('submit'); 200 SUBMIT_CASE: { 201 (! defined($submit_button) ) and do { # fresh problem to edit 202 $self->{action} = 'fresh_edit'; 203 last SUBMIT_CASE; 204 }; 205 206 ($submit_button eq 'Add this problem to: ') and do { 207 $self->{action} = 'add_problem_to_set'; 208 last SUBMIT_CASE; 209 }; 210 211 ($submit_button eq 'Make this the set header for: ') and do { 212 $self->{action} = 'add_set_header_to_set'; 213 last SUBMIT_CASE; 214 }; 215 216 ($submit_button eq 'View problem') and do { 217 $self->{action} ='refresh'; 218 last SUBMIT_CASE; 219 }; 220 221 ($submit_button eq 'Revert') and do { 222 $self->{action} = 'revert'; 223 last SUBMIT_CASE; 224 }; 225 226 ($submit_button eq 'Save') and do { 227 $self->{action} = 'save'; 228 last SUBMIT_CASE; 229 }; 230 231 ($submit_button eq 'Save as') and do { 232 $self->{action} = 'save_as'; 233 last SUBMIT_CASE; 234 }; 235 236 ($submit_button eq 'Add problem to: ') and do { 237 $self->{action} = 'add_problem_to_set'; 238 last SUBMIT_CASE; 239 }; 240 ($submit_button eq 'Make local copy at: ') and do { 241 $self->{action} = 'make_local_copy'; 242 last SUBMIT_CASE; 243 }; 244 # else 245 die "Unrecognized submit command: |$submit_button|"; 246 247 } # END SUBMIT_CASE 248 249 250 ########################################### 251 # Save file 252 ###################################### 253 254 # The subroutine below writes the necessary files and obtains the appropriate seed. 255 # and returns 256 # $self->{problemPath} --- file path for viewing problem in $self->{problemPath} 257 # $self->{failure} 258 259 260 $self->saveFileChanges($setName, $problemNumber, $file_type,$TEMPFILESUFFIX); 261 262 ############################################################################## 263 # displayMode and problemSeed 264 # 265 # Determine the display mode 266 # If $self->{problemSeed} was obtained within saveFileChanges from the problem_record 267 # then it can be overridden by the value obtained from the form. 268 # Insure that $self->{problemSeed} has some non-empty value 269 # displayMode and problemSeed 270 # will be needed for viewing the problem via redirect. 271 # They are also two of the parameters which can be set by the editor 272 ############################################################################## 273 274 if (defined $r->param('displayMode')) { 275 $self->{displayMode} = $r->param('displayMode'); 276 } else { 277 $self->{displayMode} = $ce->{pg}->{options}->{displayMode}; 278 } 279 280 # form version of problemSeed overrides version obtained from the the problem_record 281 # inside saveFileChanges 282 $self->{problemSeed} = $r->param('problemSeed') if (defined $r->param('problemSeed')); 283 # Make sure that the problem seed has some value 284 $self->{problemSeed} = '123456' unless defined $self->{problemSeed} and $self->{problemSeed} =~/\S/; 285 286 ############################################################################## 287 # Return 288 # If file saving fails or 289 # if no redirects are required. No further processing takes place in this subroutine. 290 # Redirects are required only for the following submit values 291 # 'Save' 292 # 'Save as' 293 # 'Refresh' 294 # add problem to set 295 # add set header to set 296 # 297 ######################################### 298 299 return if $self->{failure}; 300 # FIXME: even with an error we still open a new page because of the target specified in the form 301 302 303 # Some cases do not need a redirect: save, refresh, save_as, add_problem_to_set, add_header_to_set,make_local_copy 304 my $action = $self->{action}; 305 306 return unless $action eq 'save' 307 or $action eq 'refresh' 308 or $action eq 'save_as' 309 or $action eq 'add_problem_to_set' 310 or $action eq 'make_local_copy' 311 or $action eq 'add_set_header_to_set'; 312 313 314 ###################################### 315 # calculate redirect URL based on file type 316 ###################################### 317 my $courseName = $urlpath->arg("courseID"); 318 my $problemSeed = ($r->param('problemSeed')) ? $r->param('problemSeed') : ''; 319 my $displayMode = ($r->param('displayMode')) ? $r->param('displayMode') : ''; 320 321 my $viewURL = ''; 322 323 ###################################### 324 # problem file_type 325 # redirect to Problem.pm with setID = "Undefined_Set if "Save As" option is chosen 326 # redirect to Problem.pm with setID = current $setID if "Save" or "Revert" or "Refresh is chosen" 327 ###################################### 328 REDIRECT_CASES: { 329 ($file_type eq 'problem' or $file_type eq 'source_path_for_problem_file' or $file_type eq 'blank_problem') and do { 330 my $sourceFilePath = $self->{problemPath}; 331 # strip off template directory prefix 332 $sourceFilePath =~ s|^$ce->{courseDirs}->{templates}/||; 333 if ($action eq 'save_as') { # redirect to myself 334 my $edit_level = $r->param("edit_level") || 0; 335 $edit_level++; 336 337 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", 338 courseID => $courseName, setID => 'Undefined_Set', problemID => 'Undefined_Problem' 339 ); 340 $viewURL = $self->systemLink($problemPage, 341 params=>{ 342 sourceFilePath => $sourceFilePath, 343 edit_level => $edit_level, 344 file_type => 'source_path_for_problem_file', 345 status_message => uri_escape($self->{status_message}) 346 347 } 348 ); 349 350 351 } elsif ( $action eq 'add_problem_to_set') { 352 353 my $targetSetName = $r->param('target_set'); 354 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", 355 courseID => $courseName, setID => $targetSetName, 356 problemID => WeBWorK::Utils::max( $r->db->listGlobalProblems($targetSetName)) 357 ); 358 $viewURL = $self->systemLink($problemPage, 359 params => { 360 displayMode => $displayMode, 361 problemSeed => $problemSeed, 362 editMode => "savedFile", 363 sourceFilePath => $sourceFilePath, 364 status_message => uri_escape($self->{status_message}) 365 366 } 367 ); 368 } elsif ($action eq 'make_local_copy') { # redirect to myself 369 my $edit_level = $r->param("edit_level") || 0; 370 $edit_level++; 371 372 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", 373 courseID => $courseName, setID => $setName, problemID => $problemNumber 374 ); 375 $viewURL = $self->systemLink($problemPage, 376 params=>{ 377 sourceFilePath => $sourceFilePath, 378 edit_level => $edit_level, 379 file_type => 'source_path_for_problem_file', 380 status_message => uri_escape($self->{status_message}) 381 382 } 383 ); 384 385 386 } elsif ( $action eq 'add_set_header_to_set') { 387 my $targetSetName = $r->param('target_set'); 388 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet", 389 courseID => $courseName, setID => $targetSetName 390 ); 391 $viewURL = $self->systemLink($problemPage, 392 params => { 393 displayMode => $displayMode, 394 editMode => "savedFile", 395 status_message => uri_escape($self->{status_message}) 396 } 397 ); 398 } else { # saved problems and refreshed problems redirect to Problem.pm 399 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", 400 courseID => $courseName, setID => $setName, problemID => $problemNumber 401 ); 402 $viewURL = $self->systemLink($problemPage, 403 params => { 404 displayMode => $displayMode, 405 problemSeed => $problemSeed, 406 editMode => ($action eq "save" ? "savedFile" : "temporaryFile"), 407 sourceFilePath => $sourceFilePath, 408 status_message => uri_escape($self->{status_message}) 409 410 } 411 ); 412 } 413 last REDIRECT_CASES; 414 }; 415 ###################################### 416 # blank_problem file_type 417 # redirect to Problem.pm 418 ###################################### 419 420 $file_type eq 'blank_problem' and do { 421 return; # no redirect is needed 422 }; 423 424 ###################################### 425 # set headers file_type 426 # redirect to ProblemSet.pm 427 ###################################### 428 429 ($file_type eq 'set_header' or $file_type eq 'hardcopy_header' ) and do { 430 if ($action eq 'save_as') { # redirect to myself 431 my $sourceFilePath = $self->{problemPath}; 432 # strip off template directory prefix 433 $sourceFilePath =~ s|^$ce->{courseDirs}->{templates}/||; 434 435 my $edit_level = $r->param("edit_level") || 0; 436 $edit_level++; 437 438 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", 439 courseID => $courseName, setID => 'Undefined_Set', problemID => 'Undefined_Problem' 440 ); 441 $viewURL = $self->systemLink($problemPage, 442 params=>{ 443 sourceFilePath => $sourceFilePath, 444 edit_level => $edit_level, 445 file_type => 'source_path_for_problem_file', 446 status_message => uri_escape($self->{status_message}) 447 } 448 ); 449 } elsif ( $action eq 'add_set_header_to_set') { 450 my $targetSetName = $r->param('target_set'); 451 my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet", 452 courseID => $courseName, setID => $targetSetName 453 ); 454 $viewURL = $self->systemLink($problemPage, 455 params => { 456 displayMode => $displayMode, 457 editMode => "savedFile", 458 status_message => uri_escape($self->{status_message}) 459 } 460 ); 461 } else { 462 my $problemSetPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet", 463 courseID => $courseName, setID => $setName); 464 $viewURL = $self->systemLink($problemSetPage, 465 params => { 466 displayMode => $displayMode, 467 problemSeed => $problemSeed, 468 editMode => ($action eq "save" ? "savedFile" : "temporaryFile"), 469 status_message => uri_escape($self->{status_message}) 470 } 471 ); 472 } 473 last REDIRECT_CASES; 474 }; 475 ###################################### 476 # course_info file type 477 # redirect to ProblemSets.pm 478 ###################################### 479 $file_type eq 'course_info' and do { 480 my $problemSetsPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets", 481 courseID => $courseName); 482 $viewURL = $self->systemLink($problemSetsPage, 483 params => { 484 editMode => ($action eq "save" ? "savedFile" : "temporaryFile"), 485 status_message => uri_escape($self->{status_message}) 486 } 487 ); 488 last REDIRECT_CASES; 489 }; 490 # else if no redirect needed -- there must be an error. 491 die "The file_type $file_type does not have a defined redirect procedure."; 492 } # End REDIRECT_CASES 493 494 if ($viewURL) { 495 $self->reply_with_redirect($viewURL); 496 } else { 497 die "Invalid file_type $file_type specified by saveFileChanges"; 498 } 499 } 500 501 502 sub initialize { 503 my ($self) = @_; 504 my $r = $self->r; 505 my $authz = $r->authz; 506 my $user = $r->param('user'); 507 508 # Check permissions 509 return unless ($authz->hasPermissions($user, "access_instructor_tools")); 510 return unless ($authz->hasPermissions($user, "modify_problem_sets")); 511 512 my $tempFilePath = $self->{tempFilePath}; # path to the file currently being worked with (might be a .tmp file) 513 my $inputFilePath = $self->{inputFilePath}; # path to the file for input, (might be a .tmp file) 514 my $protected_file = (not -w $inputFilePath ) and -e $inputFilePath; #FIXME -- let's try to insure that the input file always exists, even for revert. 515 516 $self->addmessage($r->param('status_message') ||''); # record status messages carried over if this is a redirect 517 $self->addbadmessage("Changes in this file have not yet been permanently saved.") if -r $tempFilePath; 518 519 $self->addbadmessage("This file '$inputFilePath' is protected! ".CGI::br()."To edit this text you must either 'Make a local copy' of this problem, or 520 use 'Save As' to save it to another file.") if $protected_file; 521 522 } 523 524 sub path { 525 my ($self, $args) = @_; 526 my $r = $self->r; 527 my $urlpath = $r->urlpath; 528 my $courseName = $urlpath->arg("courseID"); 529 my $setName = $r->urlpath->arg("setID") || ''; 530 my $problemNumber = $r->urlpath->arg("problemID") || ''; 531 532 # we need to build a path to the problem being edited by hand, since it is not the same as the urlpath 533 # For this page the bread crum path leads back to the problem being edited, not to the Instructor tool. 534 my @path = ( 'WeBWork', $r->location, 535 "$courseName", $r->location."/$courseName", 536 "$setName", $r->location."/$courseName/$setName", 537 "$problemNumber", $r->location."/$courseName/$setName/$problemNumber", 538 "Editor", "" 539 ); 540 541 #print "\n<!-- BEGIN " . __PACKAGE__ . "::path -->\n"; 542 print $self->pathMacro($args, @path); 543 #print "<!-- END " . __PACKAGE__ . "::path -->\n"; 544 545 return ""; 546 } 547 sub title { 548 my $self = shift; 549 my $r = $self->r; 550 my $problemNumber = $r->urlpath->arg("problemID"); 551 my $file_type = $self->{'file_type'} || ''; 552 return "Set Header" if ($file_type eq 'set_header'); 553 return "Hardcopy Header" if ($file_type eq 'hardcopy_header'); 554 return "Course Information" if($file_type eq 'course_info'); 555 return 'Problem ' . $r->{urlpath}->name; 556 } 557 558 sub body { 559 my ($self) = @_; 560 my $r = $self->r; 561 my $db = $r->db; 562 my $ce = $r->ce; 563 my $authz = $r->authz; 564 my $user = $r->param('user'); 565 my $make_local_copy = $r->param('make_local_copy'); 566 567 # Check permissions 568 return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.") 569 unless $authz->hasPermissions($user, "access_instructor_tools"); 570 571 return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problems.") 572 unless $authz->hasPermissions($user, "modify_student_data"); 573 574 575 # Gathering info 576 my $editFilePath = $self->{editFilePath}; # path to the permanent file to be edited 577 my $tempFilePath = $self->{tempFilePath}; # path to the file currently being worked with (might be a .tmp file) 578 my $inputFilePath = $self->{inputFilePath}; # path to the file for input, (might be a .tmp file) 579 my $setName = $r->urlpath->arg("setID") ; 580 my $problemNumber = $r->urlpath->arg("problemID") ; 581 $setName = defined($setName) ? $setName : ''; # we need this instead of using the || construction 582 # to keep set 0 from being set to the 583 # empty string. 584 $problemNumber = defined($problemNumber) ? $problemNumber : ''; 585 586 587 ######################################################################### 588 # Find the text for the problem, either in the tmp file, if it exists 589 # or in the original file in the template directory 590 # or in the problem contents gathered in the initialization phase. 591 ######################################################################### 592 593 my $problemContents = ${$self->{r_problemContents}}; 594 595 unless ( $problemContents =~/\S/) { # non-empty contents 596 if (-r $tempFilePath and not -d $tempFilePath) { 597 eval { $problemContents = WeBWorK::Utils::readFile($tempFilePath) }; 598 $problemContents = $@ if $@; 599 $inputFilePath = $tempFilePath; 600 } elsif (-r $editFilePath and not -d $editFilePath) { 601 eval { $problemContents = WeBWorK::Utils::readFile($editFilePath) }; 602 $problemContents = $@ if $@; 603 $inputFilePath = $editFilePath; 604 } else { # file not existing is not an error 605 #warn "No file exists"; 606 $problemContents = ''; 607 } 608 } else { 609 #warn "obtaining input from r_problemContents"; 610 } 611 612 my $protected_file = not -w $inputFilePath; 613 my $header = CGI::i("Editing problem".CGI::b("set $setName/ problem $problemNumber</emphasis>").CGI::br()." in file $inputFilePath"); 614 $header = ($inputFilePath =~ /$TEMPFILESUFFIX/) ? CGI::div({class=>'temporaryFile'},$header) : $header; # use colors if temporary file 615 616 ######################################################################### 617 # Format the page 618 ######################################################################### 619 620 # Define parameters for textarea 621 # FIXME 622 # Should the seed be set from some particular user instance?? 623 my $rows = 20; 624 my $columns = 80; 625 my $mode_list = $ce->{pg}->{displayModes}; 626 my $displayMode = $self->{displayMode}; 627 my $problemSeed = $self->{problemSeed}; 628 my $uri = $r->uri; 629 my $edit_level = $r->param('edit_level') || 0; 630 my $file_type = $self->{file_type}; 631 632 my $force_field = defined($r->param('sourceFilePath')) ? 633 CGI::hidden(-name=>'sourceFilePath', 634 -default=>$r->param('sourceFilePath')) : ''; 635 636 my @allSetNames = sort $db->listGlobalSets; 637 for (my $j=0; $j<scalar(@allSetNames); $j++) { 638 $allSetNames[$j] =~ s|^set||; 639 $allSetNames[$j] =~ s|\.def||; 640 } 641 my $target = "problem$edit_level"; 642 # Prepare Preview button 643 my $view_problem_form = CGI::start_form({method=>"POST", name=>"editor", action=>"$uri", target=>$target, enctype=>"application/x-www-form-urlencoded"}). 644 $self->hidden_authen_fields. 645 $force_field. 646 CGI::hidden(-name=>'file_type',-default=>$self->{file_type}). 647 CGI::hidden(-name=>'problemSeed',-default=>$problemSeed). 648 CGI::hidden(-name=>'displayMode',-default=>$displayMode). 649 CGI::hidden(-name=>'problemContents',-default=>$problemContents). 650 CGI::submit(-value=>'View problem',-name=>'submit'). 651 CGI::end_form(); 652 # Prepare add to set buttons 653 my $add_files_to_set_buttons = ''; 654 if ($file_type eq 'problem' or $file_type eq 'source_path_for_problem_file' or $file_type eq 'blank_problem' ) { 655 $add_files_to_set_buttons .= CGI::submit(-value=>'Add problem to: ',-name=>'submit' ) ; 656 } 657 if ($file_type eq 'set_header' # set header or the problem number is not a regular positive number 658 or ( $file_type =~ /problem/ and ($problemNumber =~ /\D|^0$|^$/ )) ){ 659 $add_files_to_set_buttons .=CGI::submit(-value=>'Make this the set header for: ',-name=>'submit' ); 660 } 661 # Add pop-up menu for the target set if either of these buttons has been revealed. 662 $add_files_to_set_buttons .= CGI::popup_menu(-name=>'target_set',-values=>\@allSetNames, -default=>$setName) if $add_files_to_set_buttons; 663 664 665 return CGI::p($header), 666 CGI::start_form({method=>"POST", name=>"editor", action=>"$uri", target=>$target, enctype=>"application/x-www-form-urlencoded"}), 667 $self->hidden_authen_fields, 668 $force_field, 669 CGI::hidden(-name=>'file_type',-default=>$self->{file_type}), 670 CGI::div( 671 'Seed: ', 672 CGI::textfield(-name=>'problemSeed',-value=>$problemSeed), 673 'Mode: ', 674 CGI::popup_menu(-name=>'displayMode', -values=>$mode_list, -default=>$displayMode), 675 CGI::a({-href=>'http://webwork.math.rochester.edu/docs/docs/pglanguage/manpages/',-target=>"manpage_window"}, 676 ' Manpages ', 677 ), 678 CGI::a({-href=>'http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/PGmacrosByFile',-target=>"manpage_window"}, 679 ' macro list ', 680 ), 681 CGI::a({-href=>'http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/',-target=>"manpage_window"}, 682 ' pod docs ', 683 ), 684 ), 685 CGI::p( 686 CGI::textarea( 687 -name => 'problemContents', -default => $problemContents, 688 -rows => $rows, -columns => $columns, -override => 1, 689 ), 690 ), 691 CGI::p( 692 $add_files_to_set_buttons, 693 CGI::br(), 694 CGI::submit(-value=>'View problem',-name=>'submit'), 695 $protected_file ? CGI::submit(-value=>'Save',-name=>'submit', -disabled=>1) : CGI::submit(-value=>'Save',-name=>'submit'), 696 CGI::submit(-value=>'Revert', -name=>'submit'), 697 CGI::submit(-value=>'Save as',-name=>'submit'), 698 CGI::textfield(-name=>'save_to_new_file', -size=>40, -value=>""), 699 700 ), 701 # allow one to make a local copy if the viewed file can't be edited. #FIXME the method for determining the localfilePath needs work 702 (-w $editFilePath) ? "" : CGI::p(CGI::hr(), 703 CGI::submit(-value=>'Make local copy at: ',-name=>'submit'), "[TMPL]/".determineLocalFilePath($editFilePath), 704 CGI::hidden(-name=>'local_copy_file_path', -value=>determineLocalFilePath($editFilePath) ) 705 ), 706 CGI::end_form(); 707 708 709 } 710 711 ################################################################################ 712 # Utilities 713 ################################################################################ 714 715 # determineLocalFilePath constructs a local file path parallel to a library file path 716 # This is a subroutine, not a method 717 # 718 sub determineLocalFilePath { 719 my $path = shift; 720 if ($path =~ /Library/) { 721 $path =~ s|^.*?Library/||; # truncate the url up to a segment such as ...rochesterLibrary/....... 722 } else { # if its not in a library we'll just save it locally 723 $path = "new_problem_".rand(40); #l hope there aren't any collisions. 724 } 725 $path; 726 727 } 728 sub getFilePaths { 729 my ($self, $setName, $problemNumber, $file_type, $TEMPFILESUFFIX) = @_; 730 my $r = $self->r; 731 my $ce = $r->ce; 732 my $db = $r->db; 733 my $urlpath = $r->urlpath; 734 my $courseName = $urlpath->arg("courseID"); 735 my $user = $r->param('user'); 736 my $effectiveUserName = $r->param('effectiveUser'); 737 738 $setName = '' unless defined $setName; 739 $problemNumber = '' unless defined $problemNumber; 740 die 'Internal error to PGProblemEditor -- file type is not defined' unless defined $file_type; 741 742 ########################################################## 743 # Determine path to the input file to be edited. 744 # set EditFilePath to this value 745 # 746 # There are potentially four files in play 747 # The permanent path of the input file == $editFilePath == $self->{problemPath} 748 # A temporary path to the input file == $tempFilePath== "$editFilePath.$TEMPFILESUFFIX"== $self->{problemPath} 749 ########################################################## 750 # Relevant parameters 751 # $r->param("displayMode") 752 # $r->param('problemSeed') 753 # $r->param('submit') 754 # $r->param('make_local_copy') 755 # $r->param('sourceFilePath') 756 # $r->param('problemContents') 757 # $r->param('save_to_new_file') 758 ########################################################################## 759 # Define the following variables 760 # path to regular file -- $self->{problemPath} = $editFilePath; 761 # path to file being read (temporary or permanent) 762 # --- $self->{problemPath} = $problemPath; 763 # contents of the file being read --- $problemContents 764 # $self->{r_problemContents} = \$problemContents; 765 # $self->{TEMPFILESUFFIX} = $TEMPFILESUFFIX; 766 ########################################################################### 767 768 my $editFilePath = $ce->{courseDirs}->{templates}; 769 770 ########################################################################## 771 # Determine path to regular file, place it in $editFilePath 772 # problemSeed is defined for the file_type = 'problem' and 'source_path_to_problem' 773 ########################################################################## 774 CASE: 775 { 776 ($file_type eq 'course_info') and do { 777 # we are editing the course_info file 778 # value of courseFiles::course_info is relative to templates directory 779 $editFilePath .= '/' . $ce->{courseFiles}->{course_info}; 780 last CASE; 781 }; 782 783 ($file_type eq 'blank_problem') and do { 784 $editFilePath = $ce->{webworkFiles}->{screenSnippets}->{blankProblem}; 785 $self->addbadmessage("$editFilePath is blank problem template file and should not be edited directly. " 786 ."First use 'Save as' to make a local copy, then add the file to the current problem set, then edit the file." 787 ); 788 last CASE; 789 }; 790 791 ($file_type eq 'set_header' or $file_type eq 'hardcopy_header') and do { 792 # first try getting the merged set for the effective user 793 my $set_record = $db->getMergedSet($effectiveUserName, $setName); # checked 794 # if that doesn't work (the set is not yet assigned), get the global record 795 $set_record = $db->getGlobalSet($setName); # checked 796 # bail if no set is found 797 die "Cannot find a set record for set $setName" unless defined($set_record); 798 799 my $header_file = ""; 800 $header_file = $set_record->{$file_type}; 801 if ($header_file && $header_file ne "") { 802 $editFilePath .= '/' . $header_file; 803 } else { 804 # if the set record doesn't specify the filename 805 # then the set uses the default from snippets 806 # so we'll load that file, but change where it will be saved 807 # to and grey out the "Save" button 808 # FIXME why does the make_local_copy variable need to be checked? 809 # Isn't it automatic that a local copy has to be made? 810 #if ($r->param('make_local_copy')) { 811 $editFilePath = $ce->{webworkFiles}->{screenSnippets}->{setHeader} if $file_type eq 'set_header'; 812 $editFilePath = $ce->{webworkFiles}->{hardcopySnippets}->{setHeader} if $file_type eq 'hardcopy_header'; 813 $self->addbadmessage("$editFilePath is the default header file and cannot be edited directly."); 814 $self->addbadmessage("Any changes you make will have to be saved as another file."); 815 #} 816 } 817 last CASE; 818 }; #end 'set_header, hardcopy_header' case 819 820 ($file_type eq 'problem') and do { 821 822 # first try getting the merged problem for the effective user 823 my $problem_record = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked 824 825 # if that doesn't work (the problem is not yet assigned), get the global record 826 $problem_record = $db->getGlobalProblem($setName, $problemNumber) unless defined($problem_record); # checked 827 # bail if no source path for the problem is found ; 828 die "Cannot find a problem record for set $setName / problem $problemNumber" unless defined($problem_record); 829 $editFilePath .= '/' . $problem_record->source_file; 830 # define the problem seed for later use 831 $self->{problemSeed}= $problem_record->problem_seed if defined($problem_record) and $problem_record->can('problem_seed') ; 832 last CASE; 833 }; # end 'problem' case 834 835 ($file_type eq 'source_path_for_problem_file') and do { 836 my $forcedSourceFile = $r->param('sourceFilePath'); 837 # bail if no source path for the problem is found ; 838 die "Cannot find a file path to save to" unless( defined($forcedSourceFile) and ($forcedSourceFile =~ /\S/) ); 839 $self->{problemSeed} = 1234; 840 $editFilePath .= '/' . $forcedSourceFile; 841 last CASE; 842 }; # end 'source_path_for_problem_file' case 843 } # end CASE: statement 844 845 846 # if a set record or problem record contains an empty blank for a header or problem source_file 847 # we could find ourselves trying to edit /blah/templates/.toenail.tmp or something similar 848 # which is almost undoubtedly NOT desirable 849 850 if (-d $editFilePath) { 851 my $msg = "The file $editFilePath is a directory!"; 852 $self->{failure} = 1; 853 $self->addbadmessage($msg); 854 } 855 if (-e $editFilePath and not -r $editFilePath) { #it's ok if the file doesn't exist, perhaps we're going to create it 856 # with save as 857 my $msg = "The file $editFilePath cannot be read!"; 858 $self->{failure} = 1; 859 $self->addbadmessage($msg); 860 } 861 ################################################# 862 # The path to the permanent file is now verified and stored in $editFilePath 863 # Whew!!! 864 ################################################# 865 866 my $tempFilePath = "$editFilePath.$TEMPFILESUFFIX"; 867 $self->{editFilePath} = $editFilePath; 868 $self->{tempFilePath} = $tempFilePath; 869 $self->{inputFilePath} = (-r "$editFilePath.$TEMPFILESUFFIX") ? $tempFilePath : $editFilePath; 870 871 } 872 873 ################################################################################ 874 # saveFileChanges does most of the work. it is a separate method so that it can 875 # be called from either pre_header_initialize() or initilize(), depending on 876 # whether a redirect is needed or not. 877 # 878 # it actually does a lot more than save changes to the file being edited, and 879 # sometimes less. 880 ################################################################################ 881 sub saveFileChanges { 882 my ($self, $setName, $problemNumber, $file_type, $TEMPFILESUFFIX) = @_; 883 my $r = $self->r; 884 my $ce = $r->ce; 885 my $db = $r->db; 886 my $urlpath = $r->urlpath; 887 888 my $courseName = $urlpath->arg("courseID"); 889 my $user = $r->param('user'); 890 my $effectiveUserName = $r->param('effectiveUser'); 891 892 $setName = '' unless defined $setName; 893 $problemNumber = '' unless defined $problemNumber; 894 $file_type = '' unless defined $file_type; 895 896 my $action = $self->{action}; 897 my $editFilePath = $self->{editFilePath}; 898 my $tempFilePath = $self->{tempFilePath}; 899 ############################################################################## 900 # read and update the targetFile and targetFile.tmp files in the directory 901 # if a .tmp file already exists use that, unless the revert button has been pressed. 902 # These .tmp files are 903 # removed when the file is finally saved. 904 # Place the path of the file to be read in $problemPath. 905 ############################################################################## 906 907 908 my $problemContents = ''; 909 my $outputFilePath = undef; # this is actually the output file for this subroutine 910 # it is then read in as source in the body of this module 911 my $do_not_save = 0; # flag to prevent saving of file 912 my $editErrors = ''; 913 914 ########################################################################## 915 # For each of the actions define the following variables: 916 # 917 # path to permanent file -- $self->{problemPath} = $editFilePath; 918 # path to file being read (temporary or permanent) 919 # --- $self->{problemPath} = $problemPath; 920 # contents of the file being read --- $problemContents 921 # $self->{r_problemContents} = \$problemContents; 922 # 923 ################################# 924 # handle button clicks ##### 925 # Read contents of file 926 ################################# 927 ACTION_CASES: { 928 ($action eq 'fresh_edit') and do { 929 # this is a fresh editing job 930 # the original file will be read in the body 931 last ACTION_CASES; 932 }; 933 934 ($action eq 'revert') and do { 935 # this is also fresh editing job 936 $outputFilePath = undef; 937 $self->addgoodmessage("Reverting to original file $editFilePath"); 938 $self->{problemPath} = $editFilePath; 939 $self->{inputFilePath}=$editFilePath; 940 last ACTION_CASES; 941 }; 942 943 ($action eq 'refresh') and do { 944 # grab the problemContents from the form in order to save it to the tmp file 945 # store tmp file name in the $self->problemPath for use in body 946 947 $problemContents = $r->param('problemContents'); 948 $outputFilePath = "$editFilePath.$TEMPFILESUFFIX"; 949 $self->{problemPath} = $outputFilePath; 950 last ACTION_CASES; 951 }; 952 953 ($action eq 'save') and do { 954 # grab the problemContents from the form in order to save it to the permanent file 955 # later we will unlink (delete) the temporary file 956 # store permanent file name in the $self->problemPath for use in body 957 $problemContents = $r->param('problemContents'); 958 $outputFilePath = "$editFilePath"; 959 $self->{problemPath} = $outputFilePath; 960 #$self->addgoodmessage("Saving to file $outputFilePath"); 961 last ACTION_CASES; 962 }; 963 964 ($action eq 'save_as') and do { 965 my $new_file_name =$r->param('save_to_new_file') || ''; 966 ################################################# 967 #bail unless this new file name has been defined 968 ################################################# 969 if ( $new_file_name !~ /\S/) { # need a non-blank file name 970 # setting $self->{failure} stops saving and any redirects 971 $do_not_save = 1; 972 warn "new file name is $new_file_name"; 973 $self->addbadmessage(CGI::p("Please specify a file to save to.")); 974 last ACTION_CASES; #stop processing 975 } 976 ################################################# 977 # grab the problemContents from the form in order to save it to a new permanent file 978 # later we will unlink (delete) the current temporary file 979 # store new permanent file name in the $self->problemPath for use in body 980 ################################################# 981 $problemContents = $r->param('problemContents'); 982 983 ################################################# 984 # Rescue the user in case they forgot to end the file name with .pg 985 ################################################# 986 if($self->{file_type} eq 'problem' 987 or $self->{file_type} eq 'blank_problem' 988 or $self->{file_type} eq 'set_header') { 989 $new_file_name =~ s/\.pg$//; # remove it if it is there 990 $new_file_name .= '.pg'; # put it there 991 992 } 993 994 ################################################# 995 # check to prevent overwrites: 996 ################################################# 997 $outputFilePath = $ce->{courseDirs}->{templates} . '/' . 998 $new_file_name; 999 1000 if (defined $outputFilePath and -e $outputFilePath) { 1001 # setting $do_not_save stops saving and any redirects 1002 $do_not_save = 1; 1003 $self->addbadmessage(CGI::p("File $outputFilePath exists. File not saved.")); 1004 } else { 1005 #$self->addgoodmessage("Saving to file $outputFilePath."); 1006 } 1007 $self->{problemPath} = $outputFilePath; 1008 last ACTION_CASES; 1009 }; 1010 ($action eq 'make_local_copy') and do { 1011 my $new_file_name =$r->param('local_copy_file_path') || ''; 1012 ################################################# 1013 #bail unless this new file name has been defined 1014 ################################################# 1015 if ( $new_file_name !~ /\S/) { # need a non-blank file name 1016 # setting $self->{failure} stops saving and any redirects 1017 $do_not_save = 1; 1018 warn "new file name is $new_file_name"; 1019 $self->addbadmessage(CGI::p("Please specify a file to save to.")); 1020 last ACTION_CASES; #stop processing 1021 } 1022 ################################################# 1023 # grab the problemContents from the form in order to save it to a new permanent file 1024 # later we will unlink (delete) the current temporary file 1025 # store new permanent file name in the $self->problemPath for use in body 1026 ################################################# 1027 $problemContents = $r->param('problemContents'); 1028 1029 ################################################# 1030 # Rescue the user in case they forgot to end the file name with .pg 1031 ################################################# 1032 if($self->{file_type} eq 'problem' 1033 or $self->{file_type} eq 'blank_problem' 1034 or $self->{file_type} eq 'set_header') { 1035 $new_file_name =~ s/\.pg$//; # remove it if it is there 1036 $new_file_name .= '.pg'; # put it there 1037 1038 } 1039 1040 ################################################# 1041 # check to prevent overwrites: 1042 ################################################# 1043 $outputFilePath = $ce->{courseDirs}->{templates} . '/' . 1044 $new_file_name; 1045 1046 if (defined $outputFilePath and -e $outputFilePath) { 1047 # setting $do_not_save stops saving and any redirects 1048 $do_not_save = 1; 1049 $self->addbadmessage(CGI::p("There is already a file at [TMPL]/$new_file_name. File not saved.")); 1050 } else { 1051 $self->addgoodmessage("A local copy of $editFilePath is being made...") ; 1052 } 1053 $self->{problemPath} = $outputFilePath; 1054 ################################################# 1055 # if new file has been successfully saved change the file path name for the problem 1056 ################################################# 1057 unless ($do_not_save) { 1058 my $problemRecord = $db->getGlobalProblem($setName,$problemNumber); 1059 $problemRecord->source_file($new_file_name); 1060 if ( $db->putGlobalProblem($problemRecord) ) { 1061 $self->addgoodmessage("A local, editable, copy of $new_file_name has been made for problem $problemNumber.") ; 1062 $self->{problemPath} = $outputFilePath; # define the file path for redirect 1063 } else { 1064 $self->addbadmessage("Unable to change the source file path for set $setName, problem $problemNumber. Unknown error."); 1065 } 1066 } 1067 1068 1069 1070 last ACTION_CASES; 1071 }; 1072 ($action eq 'add_problem_to_set') and do { 1073 my $sourceFile = $editFilePath; 1074 my $targetSetName = $r->param('target_set'); 1075 my $freeProblemID = WeBWorK::Utils::max($db->listGlobalProblems($targetSetName)) + 1; 1076 $sourceFile =~ s|^$ce->{courseDirs}->{templates}/||; 1077 my $problemRecord = $self->addProblemToSet( 1078 setName => $targetSetName, 1079 sourceFile => $sourceFile, 1080 problemID => $freeProblemID 1081 ); 1082 $self->assignProblemToAllSetUsers($problemRecord); 1083 $self->addgoodmessage("Added $sourceFile to ". $targetSetName. " as problem $freeProblemID") ; 1084 $outputFilePath = undef; # don't save any files 1085 $self->{problemPath} = $editFilePath; 1086 1087 }; 1088 ($action eq 'add_set_header_to_set') and do { 1089 my $sourceFile = $editFilePath; 1090 my $targetSetName = $r->param('target_set'); 1091 $sourceFile =~ s|^$ce->{courseDirs}->{templates}/||; 1092 my $setRecord = $db->getGlobalSet($targetSetName); 1093 $setRecord->set_header($sourceFile); 1094 if( $db->putGlobalSet($setRecord) ) { 1095 $self->addgoodmessage("Added $sourceFile to ". $targetSetName. " as new set header ") ; 1096 } else { 1097 $do_not_save = 1 ; 1098 $self->addbadmessage("Unable to make $sourceFile the set header for $targetSetName"); 1099 } 1100 # change file type to set_header if it not already so 1101 $self->{file_type} = 'set_header'; 1102 $outputFilePath = undef; # don't save any files 1103 $self->{problemPath} = $editFilePath; 1104 1105 }; 1106 last ACTION_CASES; 1107 1108 die "Unrecognized action command: $action"; 1109 }; # end ACTION_CASES 1110 1111 1112 1113 ############################################################################## 1114 # write changes to the approriate files 1115 # FIXME make sure that the permissions are set correctly!!! 1116 # Make sure that the warning is being transmitted properly. 1117 ############################################################################## 1118 1119 my $writeFileErrors; 1120 if ( defined($outputFilePath) and $outputFilePath =~/\S/ and ! $do_not_save ) { # save file 1121 # Handle the problem of line endings. 1122 # Make sure that all of the line endings are of unix type. 1123 # Convert \r\n to \n 1124 $problemContents =~ s/\r\n/\n/g; 1125 $problemContents =~ s/\r/\n/g; 1126 1127 # make sure any missing directories are created 1128 $outputFilePath = WeBWorK::Utils::surePathToFile($ce->{courseDirs}->{templates}, 1129 $outputFilePath); 1130 1131 eval { 1132 local *OUTPUTFILE; 1133 open OUTPUTFILE, ">", $outputFilePath 1134 or die "Failed to open $outputFilePath"; 1135 print OUTPUTFILE $problemContents; 1136 close OUTPUTFILE; 1137 }; # any errors are caught in the next block 1138 1139 $writeFileErrors = $@ if $@; 1140 } 1141 1142 ########################################################### 1143 # Catch errors in saving files, clean up temp files 1144 ########################################################### 1145 1146 $self->{failure} = $do_not_save; # don't do redirects if the file was not saved. 1147 # don't unlink files or send success messages 1148 1149 if ($writeFileErrors) { 1150 # get the current directory from the outputFilePath 1151 $outputFilePath =~ m|^(/.*?/)[^/]+$|; 1152 my $currentDirectory = $1; 1153 1154 my $errorMessage; 1155 # check why we failed to give better error messages 1156 if ( not -w $ce->{courseDirs}->{templates} ) { 1157 $errorMessage = "Write permissions have not been enabled in the templates directory. No changes can be made."; 1158 } elsif ( not -w $currentDirectory ) { 1159 $errorMessage = "Write permissions have not been enabled in $currentDirectory. Changes must be saved to a different directory for viewing."; 1160 } elsif ( -e $outputFilePath and not -w $outputFilePath ) { 1161 $errorMessage = "Write permissions have not been enabled for $outputFilePath. Changes must be saved to another file for viewing."; 1162 } else { 1163 $errorMessage = "Unable to write to $outputFilePath: $writeFileErrors"; 1164 } 1165 1166 $self->{failure} = 1; 1167 $self->addbadmessage(CGI::p($errorMessage)); 1168 1169 } 1170 unless( $writeFileErrors or $do_not_save) { # everything worked! unlink and announce success! 1171 # unlink the temporary file if there are no errors and the save button has been pushed 1172 if ($action eq 'save' or $action eq 'save_as' or $action eq 'revert') { 1173 unlink($self->{tempFilePath}) ; 1174 } 1175 if ( defined($outputFilePath) and ! $self->{failure} ) { 1176 my $msg = "Saved to file: $outputFilePath"; 1177 $self->addgoodmessage($msg); 1178 } 1179 1180 } 1181 1182 # return values for use in the body subroutine 1183 # The path to the current permanent file being edited: 1184 # $self->{problemPath} = $editFilePath; 1185 # The path to the current temporary file (if any). If no temporary file this the same 1186 # as the permanent file path: 1187 # $self->{outputFilePath} = $outputFilePath; 1188 # 1189 1190 $self->{r_problemContents} = \$problemContents; 1191 } # end saveFileChanges 1192 1193 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |