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