diff --git a/htdocs/js/PGProblemEditor/pgproblemeditor.js b/htdocs/js/PGProblemEditor/pgproblemeditor.js index ede9b97c35..70c6be34f6 100644 --- a/htdocs/js/PGProblemEditor/pgproblemeditor.js +++ b/htdocs/js/PGProblemEditor/pgproblemeditor.js @@ -382,6 +382,58 @@ } }); + const removeValidationErrors = (input) => { + input?.classList.remove('is-invalid'); + input.setCustomValidity(''); + }; + + const validateInput = (e, input, isInvalid, defaultMessage, report) => { + if (isInvalid) { + e.preventDefault(); + input.classList.add('is-invalid'); + input.setCustomValidity(input.dataset.errorMessage ?? defaultMessage); + if (report) input.reportValidity(); + return false; + } + removeValidationErrors(input); + return true; + }; + + // Validation of the target file for the save as tab. + const saveAsTargetFile = document.getElementsByName('action.save_as.target_file')?.[0]; + saveAsTargetFile?.addEventListener('keyup', () => { + if (saveAsTargetFile.value) removeValidationErrors(saveAsTargetFile); + }); + + // Validation of the target set for the save as tab. + const saveAsSaveModeRadios = document.getElementsByName('action.save_as.saveMode'); + const saveToTargetSetRadio = Array.from(saveAsSaveModeRadios).find( + (r) => r.id === 'action_save_as_saveMode_new_problem_id' || r.id === 'action_save_as_saveMode_set_header_id' + ); + const targetSetSelect = document.getElementsByName('action.save_as.targetSet')?.[0]; + const actionSaveAs = document.getElementById('save_as'); + for (const radio of saveAsSaveModeRadios) { + radio.addEventListener('change', () => removeValidationErrors(targetSetSelect)); + } + const saveToTargetSetSelected = () => { + saveToTargetSetRadio.checked = true; + if (targetSetSelect?.value) removeValidationErrors(targetSetSelect); + }; + targetSetSelect?.addEventListener('change', saveToTargetSetSelected); + targetSetSelect?.addEventListener('focusin', saveToTargetSetSelected); + + document.forms.editor?.addEventListener('submit', (e) => { + if (actionSaveAs && actionSaveAs.classList.contains('active')) { + let report = true; + for (const validationData of [ + [saveAsTargetFile, saveAsTargetFile?.value === '', 'Please enter a filename.'], + [targetSetSelect, saveToTargetSetRadio?.checked && !targetSetSelect?.value, 'Please select a set.'] + ]) { + if (!validateInput(e, ...validationData, report)) report = false; + } + } + }); + const fileType = document.getElementsByName('file_type')[0]?.value; // This is either the div containing the CodeMirror editor or the problemContents textarea in the case that diff --git a/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm b/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm index 54aa863de6..40ffe5b045 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm @@ -129,8 +129,6 @@ use constant ACTION_FORM_TITLES => { revert => x('Revert'), }; -my $BLANKPROBLEM = 'newProblem.pg'; - sub pre_header_initialize ($c) { my $ce = $c->ce; my $authz = $c->authz; @@ -145,26 +143,12 @@ sub pre_header_initialize ($c) { $c->{setID} = $c->stash('setID'); $c->{problemID} = $c->stash('problemID'); - # Parse setID which may come in with version data - $c->{fullSetID} = $c->{setID}; - if (defined $c->{fullSetID} && $c->{fullSetID} =~ /^([^,]*),v(\d+)$/) { - $c->{setID} = $1; - $c->{versionID} = $2; - } - # Determine displayMode and problemSeed that are needed for viewing the problem. # They are also two of the parameters which can be set by the editor. # Note that the problem seed may be overridden by the value obtained from the problem record later. $c->{displayMode} = $c->param('displayMode') // $ce->{pg}{options}{displayMode}; $c->{problemSeed} = (($c->param('problemSeed') // '') =~ s/^\s*|\s*$//gr) || DEFAULT_SEED(); - # Save file to permanent or temporary file, then redirect for viewing if it was requested to view in a new window. - # Any problem file "saved as" should be assigned to "Undefined_Set" and redirected to be viewed again in the editor. - # Problems "saved" or 'refreshed' are to be redirected to the Problem.pm module - # Set headers which are "saved" are to be redirected to the ProblemSet.pm page - # Hardcopy headers which are "saved" are also to be redirected to the ProblemSet.pm page - # Course info files are redirected to the ProblemSets.pm page - # Insure that file_type is defined $c->{file_type} = ($c->param('file_type') // '') =~ s/^\s*|\s*$//gr; @@ -239,7 +223,7 @@ sub initialize ($c) { } # Tell the templates if we are working on a PG file - $c->{is_pg} = !$c->{file_type} || ($c->{file_type} ne 'course_info' && $c->{file_type} ne 'hardcopy_theme'); + $c->{is_pg} = $c->{file_type} =~ /problem/ || $c->{file_type} =~ /header/; # Check permissions return @@ -261,14 +245,11 @@ sub initialize ($c) { )); } - if ($c->{file_type} eq 'blank_problem' || $c->{file_type} eq 'sample_problem') { - $c->addbadmessage($c->maketext('This file is a template. You may use "Save As" to create a new file.')); - } elsif ($c->{inputFilePath} =~ /$BLANKPROBLEM$/) { - $c->addbadmessage($c->maketext( - 'The file "[_1]" is a template. You may use "Save As" to create a new file.', - $c->shortPath($c->{inputFilePath}) - )); - } + $c->addbadmessage($c->maketext( + 'The file "[_1]" is a template. You may use "Save As" to create a new file.', + $c->shortPath($c->{inputFilePath}) + )) + if !path_is_subdir($c->{editFilePath}, $ce->{courseDirs}{templates}); # Find the text for the editor, either in the temporary file if it exists, in the original file in the template # directory, or in the problem contents gathered in the initialization phase. @@ -341,6 +322,8 @@ sub initialize ($c) { $c->{prettyProblemNumber} = join('.', jitar_id_to_seq($c->{prettyProblemNumber})) if $c->{set} && $c->{set}->assignment_type eq 'jitar'; + $c->{globalSets} = [ map { $_->[0] } $db->listGlobalSetsWhere({}, 'set_id') ] if $c->{is_pg}; + return; } @@ -523,36 +506,28 @@ sub getFilePaths ($c) { } elsif ($c->{file_type} eq 'set_header' || $c->{file_type} eq 'hardcopy_header') { my $set_record = $db->getGlobalSet($c->{setID}); - if (defined $set_record) { - my $header_file = $set_record->{ $c->{file_type} }; - if ($header_file && $header_file ne 'defaultHeader') { - if ($header_file =~ m|^/|) { - # Absolute address - $editFilePath = $header_file; - } else { - $editFilePath = "$ce->{courseDirs}{templates}/$header_file"; - } + my $header_file = defined $set_record ? $set_record->{ $c->{file_type} } : ''; + if ($header_file && $header_file ne 'defaultHeader') { + if ($header_file =~ m|^/|) { + # Absolute address + $editFilePath = $header_file; } else { - # If the set record doesn't specify the filename for a header or it specifies the defaultHeader, - # then the set uses the default from assets/pg. - $editFilePath = $ce->{webworkFiles}{screenSnippets}{setHeader} - if $c->{file_type} eq 'set_header'; - $editFilePath = $ce->{webworkFiles}{hardcopySnippets}{setHeader} - if $c->{file_type} eq 'hardcopy_header'; + $editFilePath = "$ce->{courseDirs}{templates}/$header_file"; } } else { - $c->addbadmessage("Cannot find a set record for set $c->{setID}"); - return; + # If the set record does not exist, or the set record doesn't specify the filename for a header or it + # specifies the defaultHeader, then the set uses the default from assets/pg. + $editFilePath = $ce->{webworkFiles}{screenSnippets}{setHeader} + if $c->{file_type} eq 'set_header'; + $editFilePath = $ce->{webworkFiles}{hardcopySnippets}{setHeader} + if $c->{file_type} eq 'hardcopy_header'; } } elsif ($c->{file_type} eq 'problem') { - # First try getting the merged problem for the effective user. - my $effectiveUserName = $c->param('effectiveUser'); - my $problem_record = - $c->{versionID} - ? $db->getMergedProblemVersion($effectiveUserName, $c->{setID}, $c->{versionID}, $c->{problemID}) - : $db->getMergedProblem($effectiveUserName, $c->{setID}, $c->{problemID}); - - # If that doesn't work, then the problem is not yet assigned. So get the global record. + # First try getting the merged problem for the current user. + my $problem_record = $db->getMergedProblem($c->param('user'), $c->{setID}, $c->{problemID}); + + # If that doesn't work, then the problem is not assigned to this user (or this problem belongs to a gateway + # test, since the problem editor can not deal with problems from versioned sets). So get the global record. $problem_record = $db->getGlobalProblem($c->{setID}, $c->{problemID}) unless defined $problem_record; if (defined $problem_record) { @@ -866,6 +841,7 @@ sub hardcopy_handler ($c) { hardcopy_theme => $c->param('action.hardcopy.theme') } )); + return; } sub add_problem_handler ($c) { @@ -1131,9 +1107,6 @@ sub save_as_handler ($c) { 'File "[_1]" exists. File not saved. No changes have been made.', $c->shortPath($outputFilePath) )); - $c->addbadmessage( - $c->maketext('You can change the file path for this problem manually from the "Sets Manager" page')) - if defined $c->{setID}; } if ($do_not_save) { @@ -1141,9 +1114,13 @@ sub save_as_handler ($c) { 'The text box now contains the source of the original problem. ' . 'You can recover lost edits by using the Back button on your browser.' )); - } - unless ($do_not_save) { + # If the save mode is 'add_to_set_as_new_problem', but this problem is not in a set (for example for a sample + # problem), then the redirect for this save mode will fail since there is no set or problem id. So switch to the + # 'new_independent_file' save mode. That will work since it fills in the 'Undefined_Set' for the set id and 1 + # for the problem id. + $saveMode = 'new_independent_file' if $saveMode eq 'add_to_set_as_new_problem' && !defined $c->{setID}; + } else { $c->{editFilePath} = $outputFilePath; # saveFileChanges will update the tempFilePath and inputFilePath as needed. Don't do that here. @@ -1155,91 +1132,108 @@ sub save_as_handler ($c) { # presented in the form. So set that here so that the correct redirect is chosen below. $saveMode = "new_$file_type"; } elsif ($saveMode eq 'rename' && -r $outputFilePath) { - # Modify source file path in problem. - if ($file_type eq 'set_header') { - my $setRecord = $db->getGlobalSet($c->{setID}); - $setRecord->set_header($new_file_name); - if ($db->putGlobalSet($setRecord)) { - $c->addgoodmessage($c->maketext( - 'The set header for set [_1] has been renamed to "[_2]".', $c->{setID}, - $c->shortPath($outputFilePath) - )); - } else { - $c->addbadmessage($c->maketext( - 'Unable to change the set header for set [_1]. Unknown error.', $c->{setID})); - } - } elsif ($file_type eq 'hardcopy_header') { - my $setRecord = $db->getGlobalSet($c->{setID}); - $setRecord->hardcopy_header($new_file_name); - if ($db->putGlobalSet($setRecord)) { - $c->addgoodmessage($c->maketext( - 'The hardcopy header for set [_1] has been renamed to "[_2]".', $c->{setID}, - $c->shortPath($outputFilePath) - )); - } else { - $c->addbadmessage($c->maketext( - 'Unable to change the hardcopy header for set [_1]. Unknown error.', - $c->{setID} - )); - } + my $problemRecord = $db->getGlobalProblem($c->{setID}, $c->{problemID}); + $problemRecord->source_file($new_file_name); + if ($db->putGlobalProblem($problemRecord)) { + $c->addgoodmessage($c->maketext( + 'The source file for "set [_1] / problem [_2]" has been changed from "[_3]" to "[_4]".', + $c->{setID}, + $c->{prettyProblemNumber}, + $c->shortPath($c->{sourceFilePath}), + $c->shortPath($outputFilePath) + )); } else { - my $problemRecord; - if ($c->{versionID}) { - $problemRecord = - $db->getMergedProblemVersion($c->param('effectiveUser'), $c->{setID}, $1, $c->{problemID}); - } else { - $problemRecord = $db->getGlobalProblem($c->{setID}, $c->{problemID}); - } - $problemRecord->source_file($new_file_name); - my $result = - $c->{versionID} ? $db->putProblemVersion($problemRecord) : $db->putGlobalProblem($problemRecord); - - if ($result) { - $c->addgoodmessage($c->maketext( - 'The source file for "set [_1] / problem [_2]" has been changed from "[_3]" to "[_4]".', - $c->{fullSetID}, - $c->{prettyProblemNumber}, - $c->shortPath($c->{sourceFilePath}), - $c->shortPath($outputFilePath) - )); - } else { - $c->addbadmessage($c->maketext( - 'Unable to change the source file path for set [_1], problem [_2]. Unknown error.', - $c->{fullSetID}, $c->{prettyProblemNumber} - )); + $c->addbadmessage($c->maketext( + 'Unable to change the source file path for set [_1], problem [_2]. Unknown error.', + $c->{setID}, $c->{prettyProblemNumber} + )); + } + } elsif ($saveMode eq 'set_as_heaader_for_set' && -r $outputFilePath) { + my $setID = $c->param('action.save_as.targetSet'); + if (defined $setID && $setID =~ /\S/) { + $c->{setID} = $setID; + if ($file_type eq 'set_header') { + my $setRecord = $db->getGlobalSet($c->{setID}); + $setRecord->set_header($new_file_name); + if ($db->putGlobalSet($setRecord)) { + $c->addgoodmessage($c->maketext( + 'The set header for set [_1] has been set to "[_2]".', $c->{setID}, + $c->shortPath($outputFilePath) + )); + } else { + $c->addbadmessage($c->maketext( + 'Unable to change the set header for set [_1]. Unknown error.', + $c->{setID} + )); + } + } elsif ($file_type eq 'hardcopy_header') { + my $setRecord = $db->getGlobalSet($c->{setID}); + $setRecord->hardcopy_header($new_file_name); + if ($db->putGlobalSet($setRecord)) { + $c->addgoodmessage($c->maketext( + 'The hardcopy header for set [_1] has been set to "[_2]".', $c->{setID}, + $c->shortPath($outputFilePath) + )); + } else { + $c->addbadmessage($c->maketext( + 'Unable to change the hardcopy header for set [_1]. Unknown error.', + $c->{setID} + )); + } } + } else { + my $headerType = + $file_type eq 'set_header' ? $c->maketext('set header') : $c->maketext('hardcopy header'); + $c->addbadmessage($c->maketext( + 'A new file has been created at "[_1]" with the contents below. However, ' + . 'the file has not been set as the [_2] for a set, since no target set was specified.', + $c->shortPath($outputFilePath), + $headerType + )); + $saveMode = 'new_independent_file'; } } elsif ($saveMode eq 'add_to_set_as_new_problem') { - my $set = $db->getGlobalSet($c->{setID}); + my $setID = $c->param('action.save_as.targetSet'); + if (defined $setID && $setID =~ /\S/) { + $c->{setID} = $c->param('action.save_as.targetSet'); + my $set = $db->getGlobalSet($c->{setID}); + + # For jitar sets new problems are put as top level problems at the end. + if ($set->assignment_type eq 'jitar') { + my @problemIDs = $db->listGlobalProblems($c->{setID}); + @problemIDs = sort { $a <=> $b } @problemIDs; + my @seq = jitar_id_to_seq($problemIDs[-1]); + $targetProblemNumber = seq_to_jitar_id($seq[0] + 1); + } else { + $targetProblemNumber = 1 + max($db->listGlobalProblems($c->{setID})); + } - # For jitar sets new problems are put as top level problems at the end. - if ($set->assignment_type eq 'jitar') { - my @problemIDs = $db->listGlobalProblems($c->{setID}); - @problemIDs = sort { $a <=> $b } @problemIDs; - my @seq = jitar_id_to_seq($problemIDs[-1]); - $targetProblemNumber = seq_to_jitar_id($seq[0] + 1); + my $problemRecord = addProblemToSet( + $db, $c->ce->{problemDefaults}, + setName => $c->{setID}, + sourceFile => $new_file_name, + problemID => $targetProblemNumber, + ); + assignProblemToAllSetUsers($db, $problemRecord); + $c->addgoodmessage($c->maketext( + 'Added [_1] to [_2] as problem [_3].', + $new_file_name, + $c->{setID}, + ( + $set->assignment_type eq 'jitar' + ? join('.', jitar_id_to_seq($targetProblemNumber)) + : $targetProblemNumber + ) + )); } else { - $targetProblemNumber = 1 + max($db->listGlobalProblems($c->{setID})); + $c->addbadmessage($c->maketext( + 'A new file has been created at "[_1]" with the contents below. ' + . 'However, the problem has not been added to a set, since no target set was specified.', + $c->shortPath($outputFilePath) + )); + $saveMode = 'new_independent_file'; } - - my $problemRecord = addProblemToSet( - $db, $c->ce->{problemDefaults}, - setName => $c->{setID}, - sourceFile => $new_file_name, - problemID => $targetProblemNumber, # Added to end of set - ); - assignProblemToAllSetUsers($db, $problemRecord); - $c->addgoodmessage($c->maketext( - 'Added [_1] to [_2] as problem [_3].', - $new_file_name, - $c->{setID}, - ( - $set->assignment_type eq 'jitar' - ? join('.', jitar_id_to_seq($targetProblemNumber)) - : $targetProblemNumber - ) - )); - } elsif ($saveMode eq 'new_independent_problem') { + } elsif ($saveMode eq 'new_independent_file') { $c->addgoodmessage($c->maketext( 'A new file has been created at "[_1]" with the contents below.', $c->shortPath($outputFilePath) @@ -1260,15 +1254,15 @@ sub save_as_handler ($c) { if ($saveMode eq 'new_course_info') { $problemPage = $c->url_for('instructor_problem_editor'); $new_file_type = 'course_info'; - } elsif ($saveMode eq 'new_independent_problem') { + } elsif ($saveMode eq 'new_independent_file') { $problemPage = $c->url_for('instructor_problem_editor_withset_withproblem', setID => 'Undefined_Set', problemID => 1); - $new_file_type = 'source_path_for_problem_file'; + $new_file_type = $file_type =~ /header/ ? $file_type : 'source_path_for_problem_file'; } elsif ($saveMode eq 'new_hardcopy_theme') { $problemPage = $c->url_for('instructor_problem_editor'); $new_file_type = 'hardcopy_theme'; $extra_params{hardcopy_theme} = $new_file_name =~ s|^.*\/([^/]*\.xml)|$1|r; - } elsif ($saveMode eq 'rename') { + } elsif ($saveMode eq 'rename' || $saveMode eq 'set_as_heaader_for_set') { $problemPage = $c->url_for( 'instructor_problem_editor_withset_withproblem', setID => $c->{setID}, @@ -1281,7 +1275,7 @@ sub save_as_handler ($c) { setID => $c->{setID}, problemID => $do_not_save ? $c->{problemID} : max($db->listGlobalProblems($c->{setID})) ); - $new_file_type = $file_type; + $new_file_type = 'problem'; } else { $c->addbadmessage($c->maketext( 'Please use radio buttons to choose the method for saving this file. Unknown saveMode: [_1].', $saveMode diff --git a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm index 9bb9fd7994..3edfc46755 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm @@ -1298,7 +1298,6 @@ sub initialize ($c) { my $setID = $c->stash('setID'); # Make sure these are defined for the templates. - $c->stash->{fullSetID} = $setID; $c->stash->{headers} = HEADER_ORDER(); $c->stash->{field_properties} = FIELD_PROPERTIES(); $c->stash->{display_modes} = WeBWorK::PG::DISPLAY_MODES(); diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep index 90f16ba4ab..5d7f86dbe6 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep @@ -47,8 +47,7 @@ % source_path_for_problem_file => x('Editing unassigned problem file "[_1]".') % ); % -% my $setName = stash('setID') // ''; -% my $fullSetName = $c->{fullSetID} // $setName; +% my $setName = stash('setID') // ''; % % my $fileInfo = begin
@@ -59,7 +58,7 @@ ? maketext( 'Editing problem [_1] of set [_2] in file "[_3]".', $c->{prettyProblemNumber}, - tag('span', dir => 'ltr', format_set_name_display($fullSetName)), + tag('span', dir => 'ltr', format_set_name_display($setName)), tag('span', dir => 'ltr', class => 'current-file', data => { tmp_file => $c->shortPath($c->{tempFilePath}) }, $c->shortPath($c->{inputFilePath})) @@ -77,11 +76,11 @@ <%= $fileInfo->() %> % <%= form_for current_route, method => 'POST', id => 'editor', name => 'editor', - enctype => 'application/x-www-form-urlencoded', class => 'col-12', begin =%> + enctype => 'application/x-www-form-urlencoded', class => 'col-12', novalidate => undef, begin =%> <%= $c->hidden_authen_fields =%> <%= hidden_field file_type => $c->{file_type} =%> <%= hidden_field courseID => $c->{courseID} =%> - % if (defined $setName) { + % if ($setName ne '') { <%= hidden_field hidden_set_id => $setName =%> % } % if (not_blank($c->{sourceFilePath})) { diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor/add_problem_form.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor/add_problem_form.html.ep index 5e6c4efbb3..04a008d9f5 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor/add_problem_form.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor/add_problem_form.html.ep @@ -2,17 +2,15 @@ % % last unless $c->{is_pg} && $c->{file_type} ne 'blank_problem' && $c->{file_type} ne 'sample_problem'; % -% my $allSetNames = [ map { $_->[0] =~ s/^set|\.def$//gr } $db->listGlobalSetsWhere({}, 'set_id') ]; -%
<%= label_for action_add_problem_target_set_id => maketext('Add to what set?'), class => 'col-form-label col-auto' =%>
<%= select_field 'action.add_problem.target_set' => [ - map { [ - format_set_name_display($_) => $_, $_ eq ($c->{setID} // '') ? (selected => undef) : () - ] } @$allSetNames + map { + [ format_set_name_display($_) => $_, $_ eq ($c->{setID} // '') ? (selected => undef) : () ] + } @{ $c->{globalSets} } ], id => 'action_add_problem_target_set_id', class => 'form-select form-select-sm d-inline w-auto', dir => 'ltr' =%> diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor/code_maintenance_form.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor/code_maintenance_form.html.ep index f05df5d277..f606f8aee4 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor/code_maintenance_form.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor/code_maintenance_form.html.ep @@ -14,34 +14,36 @@ <%= maketext('Perltidy Help') %>
-
- <%= radio_button 'action.code_maintenance' => 'convertCodeToPGML', - id => 'action_code_maintenance_convert_PGML', class => 'form-check-input'=%> - <%= label_for 'action_code_maintenance_convert_PGML', class => 'form-check-label', begin =%> - <%== maketext('Convert the code to PGML') =%> - <% end =%> - - - <%= maketext('PGML Conversion Help') %> - -
-
- <%= radio_button 'action.code_maintenance' => 'runPGCritic', - id => 'action_code_maintenance_run_pgcritic', class => 'form-check-input'=%> - <%= label_for 'action_code_maintenance_run_pgcritic', class => 'form-check-label', begin =%> - <%== maketext('Analyze code with PG Critic') =%> - <% end =%> - - - <%= maketext('PG Critic Help') %> - -
+ % if ($c->{file_type} !~ /header/) { +
+ <%= radio_button 'action.code_maintenance' => 'convertCodeToPGML', + id => 'action_code_maintenance_convert_PGML', class => 'form-check-input'=%> + <%= label_for 'action_code_maintenance_convert_PGML', class => 'form-check-label', begin =%> + <%== maketext('Convert the code to PGML') =%> + <% end =%> + + + <%= maketext('PGML Conversion Help') %> + +
+
+ <%= radio_button 'action.code_maintenance' => 'runPGCritic', + id => 'action_code_maintenance_run_pgcritic', class => 'form-check-input'=%> + <%= label_for 'action_code_maintenance_run_pgcritic', class => 'form-check-label', begin =%> + <%== maketext('Analyze code with PG Critic') =%> + <% end =%> + + + <%= maketext('PG Critic Help') %> + +
+ % }
diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor/hardcopy_form.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor/hardcopy_form.html.ep index 79b1d08e7b..c3796749cc 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor/hardcopy_form.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor/hardcopy_form.html.ep @@ -1,19 +1,21 @@ % last unless $c->{is_pg}; %
-
- <%= label_for action_hardcopy_seed_id => maketext('Using what seed?'), - class => 'col-form-label col-auto mb-2' =%> -
- <%= text_field 'action.hardcopy.seed' => value => $c->{problemSeed}, - id => 'action_hardcopy_seed_id', class => 'form-control form-control-sm' =%> + % if ($c->{file_type} !~ /header/) { +
+ <%= label_for action_hardcopy_seed_id => maketext('Using what seed?'), + class => 'col-form-label col-auto mb-2' =%> +
+ <%= text_field 'action.hardcopy.seed' => value => $c->{problemSeed}, + id => 'action_hardcopy_seed_id', class => 'form-control form-control-sm' =%> +
+
+ +
-
- -
-
+ % }
<%= label_for 'action_hardcopy_format_id' , class => 'col-form-label col-auto', begin =%> <%= maketext('Using which hardcopy format?') =%> diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor/save_as_form.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor/save_as_form.html.ep index 91b0364a82..5a3c4144b4 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor/save_as_form.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor/save_as_form.html.ep @@ -1,38 +1,28 @@ -% use File::Basename qw(dirname); +% use File::Basename qw(dirname basename); % -% use WeBWorK::Utils qw(not_blank); % use WeBWorK::Utils::JITAR qw(jitar_id_to_seq); % use WeBWorK::Utils::Sets qw(format_set_name_display); % % # Don't show the save as form when editing an existing course info file. % last if $c->{file_type} eq 'course_info' && -e $c->{editFilePath}; % -% my $isBlank = $c->{file_type} eq 'blank_problem' || $c->{file_type} eq 'sample_problem'; +% my $isTemplate = $c->{file_type} eq 'blank_problem' || $c->{file_type} eq 'sample_problem'; % my $isHardcopyTheme = $c->{file_type} eq 'hardcopy_theme'; -% my $templatesDir = $ce->{courseDirs}{templates}; % my $shortFilePath = - % $isBlank ? 'newProblem.pg' + % $c->{file_type} eq 'blank_problem' ? 'newProblem.pg' + % : $c->{file_type} eq 'sample_problem' ? basename($c->{editFilePath}) % : $isHardcopyTheme ? 'hardcopyThemes/' . ($c->{editFilePath} =~ s|^.*\/||r) - % : $c->{editFilePath} =~ s|^$templatesDir/||r; + % : ($c->{editFilePath} =~ s|^$ce->{courseDirs}{templates}/||r) =~ s|^$ce->{webworkDirs}{assets}/pg/||r; % % # Suggest that modifications be saved to the "local" subdirectory if its not in a writeable directory -% $shortFilePath = "local/$shortFilePath" unless $isBlank || $isHardcopyTheme || -w dirname($c->{editFilePath}); +% $shortFilePath = "local/$shortFilePath" unless $isTemplate || $isHardcopyTheme || -w dirname($c->{editFilePath}); % % # If it is an absolute path make it relative. % $shortFilePath =~ s|^/*|| if $shortFilePath =~ m|^/|; % -% my $probNum = $c->{file_type} eq 'problem' ? $c->{problemID} : 'header'; -% -% # Don't add or replace problems to sets if the set is the Undefined_Set or +% # Don't add to or replace problems in sets or replace set/hardcopy headers if the set is the Undefined_Set or % # if the problem is the blank_problem or a sample problem. -% my $can_add_problem_to_set = not_blank($c->{setID}) && $c->{setID} ne 'Undefined_Set' && !$isBlank; -% -% my $prettyProbNum = $probNum; -% if ($c->{setID}) { - % my $set = $db->getGlobalSet($c->{setID}); - % $prettyProbNum = join('.', jitar_id_to_seq($probNum)) - % if ($c->{file_type} eq 'problem' && $set && $set->assignment_type eq 'jitar'); -% } +% my $fileIsAsociatedWithSet = $c->{setID} && $c->{setID} ne 'Undefined_Set' && !$isTemplate; %
@@ -44,6 +34,7 @@ <%= text_field 'action.save_as.target_file' => $shortFilePath, id => 'action_save_as_target_file_id', class => 'form-control form-control-sm', size => 60, dir => 'ltr', + data => { error_message => maketext('Please enter a filename.') }, # Don't allow changing the file name for course info files. # The filename needs to be what is set in the course environment. $c->{file_type} eq 'course_info' ? (readonly => undef) : () =%> @@ -52,46 +43,80 @@ <%= hidden_field 'action.save_as.source_file' => $c->{editFilePath} =%> <%= hidden_field 'action.save_as.file_type' => $c->{file_type} =%>
-
- % param('copyAuxFiles', 1) unless defined param('copyAuxFiles'); - <%= check_box copyAuxFiles => 1, id => 'copyAuxFiles', class => 'form-check-input' =%> - <%= hidden_field copyAuxFiles => 0 =%> - <%= label_for copyAuxFiles => maketext('Copy auxiliary files.'), class => 'form-check-label' =%> -
- % if ($can_add_problem_to_set) { + % if ($c->{file_type} =~ /problem/) {
- <%= radio_button 'action.save_as.saveMode' => 'rename', id => 'action_save_as_saveMode_rename_id', - checked => undef, class => 'form-check-input' =%> - <%= label_for 'action_save_as_saveMode_rename_id', class => 'form-check-label', begin =%> - <%== maketext('Replace current problem: [_1]', - tag( - 'strong', - c( - tag('span', dir => 'ltr', format_set_name_display($c->{fullSetID})), - "/$prettyProbNum" - )->join('') - ) - ) =%> - <% end =%> + % param('copyAuxFiles', 1) unless defined param('copyAuxFiles'); + <%= check_box copyAuxFiles => 1, id => 'copyAuxFiles', class => 'form-check-input' =%> + <%= hidden_field copyAuxFiles => 0 =%> + <%= label_for copyAuxFiles => maketext('Copy auxiliary files.'), class => 'form-check-label' =%>
+ % if ($fileIsAsociatedWithSet) { +
+ <%= radio_button 'action.save_as.saveMode' => 'rename', id => 'action_save_as_saveMode_rename_id', + checked => undef, class => 'form-check-input' =%> + <%= label_for 'action_save_as_saveMode_rename_id', class => 'form-check-label', begin =%> + <%== maketext('Replace current problem [_1] of set [_2]', + tag('strong', + $c->{set} && $c->{set}->assignment_type eq 'jitar' + ? join('.', jitar_id_to_seq($c->{problemID})) + : $c->{problemID}), + tag('strong', tag('span', dir => 'ltr', format_set_name_display($c->{setID}))) + ) =%> + <% end =%> +
+ % }
<%= radio_button 'action.save_as.saveMode' => 'add_to_set_as_new_problem', id => 'action_save_as_saveMode_new_problem_id', class => 'form-check-input' =%> - <%= label_for 'action_save_as_saveMode_new_problem_id', class => 'form-check-label', begin =%> - <%== maketext( - 'Append to end of [_1] set', - tag('strong', dir => 'ltr', format_set_name_display($c->{fullSetID})) - ) =%> - <% end =%> + <%= label_for action_save_as_saveMode_new_problem_id => maketext('Append to end of'), + class => 'form-check-label' %> + <%= label_for action_save_as_target_set_id => maketext('set:'), class => 'form-check-label' =%> +
+ <%= select_field 'action.save_as.targetSet' => [ + [ maketext('Select a Set') => '', disabled => undef, selected => undef ], + map { + [ format_set_name_display($_) => $_, $_ eq ($c->{setID} // '') ? (selected => undef) : () ] + } @{ $c->{globalSets} } + ], + id => 'action_save_as_target_set_id', class => 'form-select form-select-sm d-inline w-auto', + data => { error_message => maketext('Please select a set.') }, + dir => 'ltr' =%> +
- % } - % if ($c->{is_pg}) {
- <%= radio_button 'action.save_as.saveMode' => 'new_independent_problem', + <%= radio_button 'action.save_as.saveMode' => 'new_independent_file', id => 'action_save_as_saveMode_independent_problem_id', class => 'form-check-input', - $can_add_problem_to_set ? () : (checked => undef) =%> + $fileIsAsociatedWithSet ? () : (checked => undef) =%> <%= label_for action_save_as_saveMode_independent_problem_id => maketext('Create unattached problem'), class => 'form-check-label' =%>
+ % } elsif ($c->{file_type} =~ /header/) { + % my $headerLabel = $c->{file_type} eq 'hardcopy_header' ? maketext('hardcopy header') : maketext('set header'); +
+ <%= radio_button 'action.save_as.saveMode' => 'set_as_heaader_for_set', + id => 'action_save_as_saveMode_set_header_id', class => 'form-check-input', + $fileIsAsociatedWithSet ? (checked => undef) : () =%> + <%= label_for action_save_as_saveMode_set_header_id => maketext('Set as [_1] for', $headerLabel), + class => 'form-check-label' %> + <%= label_for action_save_as_target_set_id => maketext('set:'), class => 'form-check-label' =%> +
+ <%= select_field 'action.save_as.targetSet' => [ + [ maketext('Select a Set') => '', disabled => undef, selected => undef ], + map { + [ format_set_name_display($_) => $_, $_ eq ($c->{setID} // '') ? (selected => undef) : () ] + } @{ $c->{globalSets} } + ], + id => 'action_save_as_target_set_id', class => 'form-select form-select-sm d-inline w-auto', + data => { error_message => maketext('Please select a set.') }, + dir => 'ltr' =%> +
+
+
+ <%= radio_button 'action.save_as.saveMode' => 'new_independent_file', + id => 'action_save_as_saveMode_independent_problem_id', class => 'form-check-input', + $fileIsAsociatedWithSet ? () : (checked => undef) =%> + <%= label_for action_save_as_saveMode_independent_problem_id => maketext('Create unattached header file'), + class => 'form-check-label' =%> +
% }
diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor/save_form.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor/save_form.html.ep index 02e08b7529..5fc90e6ea5 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor/save_form.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor/save_form.html.ep @@ -1,8 +1,6 @@ -% # Can't save blank problems without changing names, and can't save if lacking write permissions. -% last if $c->{file_type} eq 'blank_problem' - % || $c->{file_type} eq 'sample_problem' - % || $c->{editFilePath} =~ /newProblem\.pg$/ - % || !-w $c->{editFilePath}; +% use WeBWorK::Utils::Files qw(path_is_subdir); +% # Can't save files outside of the course templates directory, and can't save if lacking write permissions. +% last if !path_is_subdir($c->{editFilePath}, $ce->{courseDirs}{templates}, 1) || !-w $c->{editFilePath}; %
diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor/view_form.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor/view_form.html.ep index 3af03f728c..af826cb70e 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor/view_form.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor/view_form.html.ep @@ -3,18 +3,21 @@ %
% if ($c->{is_pg}) { -
- <%= label_for action_view_seed_id => maketext('Using what seed?'), class => 'col-form-label col-auto mb-2' =%> -
- <%= text_field 'action.view.seed' => $c->{problemSeed}, - id => 'action_view_seed_id', class => 'form-control form-control-sm' =%> + % if ($c->{file_type} !~ /header/) { +
+ <%= label_for action_view_seed_id => maketext('Using what seed?'), + class => 'col-form-label col-auto mb-2' =%> +
+ <%= text_field 'action.view.seed' => $c->{problemSeed}, + id => 'action_view_seed_id', class => 'form-control form-control-sm' =%> +
+
+ +
-
- -
-
+ % }
<%= label_for action_view_displayMode_id => maketext('Using what display mode?'), class => 'col-form-label col-auto' =%> diff --git a/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep b/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep index 5c1991d475..c291ffbe12 100644 --- a/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep +++ b/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep @@ -488,7 +488,7 @@ <%= link_to $c->systemLink(url_for( 'instructor_problem_editor_withset_withproblem', - setID => $fullSetID, problemID => $problemID + setID => $setID, problemID => $problemID )), class => 'psd_edit btn btn-secondary btn-sm', target => 'WW_Editor', diff --git a/templates/HelpFiles/InstructorPGProblemEditor.html.ep b/templates/HelpFiles/InstructorPGProblemEditor.html.ep index 553c222bfb..a5b67013c4 100644 --- a/templates/HelpFiles/InstructorPGProblemEditor.html.ep +++ b/templates/HelpFiles/InstructorPGProblemEditor.html.ep @@ -183,8 +183,8 @@

<%= maketext(q{Makes a new copy of the file you are editing at the location relative to the course's } . 'templates (~[TMPL~]) directory. You may choose to replace the current problem in the current set, ' - . 'append to the end to then end of the current set as a new problem, or create a problem that is not ' - . 'attached to a problem set.') =%> + . 'append to the end of an existing set as a new problem, or create a problem that is not attached ' + . 'to a problem set.') =%>

<%= maketext('When saving the problem in a new location (directory), by default all auxiliary files, such ' @@ -196,16 +196,12 @@ . 'for a new problem. You can add the new file to a homework set from the Library Browser or via the ' . 'set detail page of the "Sets Manager".') =%>

-

+

<%= maketext('If the original problem cannot be edited than the path name must be changed in order to be ' . 'allowed to save the problem. Adding "local/" to the beginning of the original path is the default ' . 'solution. All locally created and edited files will then appear in a subdirectory named ' . '"local".') =%>

-

- <%= maketext('A new problem whose path ends in newProblem.pg should be given a new name, for example, ' - . '"myNewProblem.pg".') =%> -

<%= maketext('Append') %>