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