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