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