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