Skip to content

Commit 3e471c0

Browse files
committed
Rework problem data (or the persistence hash).
This makes it so that persistence hash is only saved to the database when answers are submitted. In the case that answers can be checked by the user (via the "Check Answers" button), then the persistence hash is saved JSON encoded in a hidden form field. That case does not need the security of saving to the database, and in that case it shouldn't be saved to the database because answers aren't being recorded and the data in that case should temporary. So a hidden form field is just right for this. I objected to the previous implementation when #1940 was submitted, but allowed it at that time, but this is how the problem_data should have been implemented. Nothing should be written to the database for this when answers are not being recorded. If an instructor is acting as a student, the previous code was saving the persistent data to the student's problem even when the instructor just enters the problem. Of course it was also saving every time the instructor did anything with the problem including using the "Preview My Answers" button, the "Check Answers" button, the "Show Correct Answers" button, the "Show Problem Grader" button. Literally any form submission of the page. That just was not thought out. The render_rpc (and html2xml) routes use the hidden form field approach to also support saving the problem data from the persistence hash. This means that problems that use the persistence hash can be tested in the PG problem editor. Note that the PERSISTENCE_HASH_UPDATE flag is removed. That didn't improve efficiency at all. The processing that was done with that was too much. Even if this were saved when it was before it would have been more efficient to just save it. The handling of the persistence hash is also reworked for PG in a corresponding pull request. PG just sends the hash, and it can contain anything that can be JSON encoded. Webwork2 just JSON encodes it and stores it, but only when answers are submitted.
1 parent f36d1c5 commit 3e471c0

File tree

8 files changed

+46
-93
lines changed

8 files changed

+46
-93
lines changed

lib/FormatRenderedProblem.pm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use warnings;
2727
use JSON;
2828
use Digest::SHA qw(sha1_base64);
2929
use Mojo::Util qw(xml_escape);
30+
use Mojo::JSON qw(encode_json);
3031
use Mojo::DOM;
3132

3233
use WeBWorK::Utils qw(getAssetURL);
@@ -280,6 +281,7 @@ sub formatRenderedProblem {
280281
showCorrectAnswersButton => $ws->{inputs_ref}{showCorrectAnswersButton} // '',
281282
showCorrectAnswersOnlyButton => $ws->{inputs_ref}{showCorrectAnswersOnlyButton} // 0,
282283
showFooter => $ws->{inputs_ref}{showFooter} // '',
284+
problem_data => encode_json($rh_result->{PERSISTENCE_HASH}),
283285
pretty_print => \&pretty_print
284286
);
285287

lib/WeBWorK/ContentGenerator/GatewayQuiz.pm

Lines changed: 11 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -894,7 +894,6 @@ async sub pre_header_initialize ($c) {
894894
if ($c->{submitAnswers} || (($c->{previewAnswers} || $c->param('newPage')) && $can{recordAnswers})) {
895895
# If answers are being submitted, then save the problems to the database. If this is a preview or page change
896896
# and answers can be recorded, then save the last answer for future reference.
897-
# Also save the persistent data to the database even when the last answer is not saved.
898897

899898
# Deal with answers being submitted for a proctored exam. If there are no attempts left, then delete the
900899
# proctor session key so that it isn't possible to start another proctored test without being reauthorized.
@@ -920,28 +919,6 @@ async sub pre_header_initialize ($c) {
920919
($past_answers_string, $encoded_last_answer_string, $scores, $answer_types_string) =
921920
create_ans_str_from_responses($c->{formFields}, $pg_result,
922921
$pureProblem->flags =~ /:needs_grading/);
923-
924-
# Transfer persistent problem data from the PERSISTENCE_HASH:
925-
# - Get keys to update first, to avoid extra work when no updated ar
926-
# are needed. When none, we avoid the need to decode/encode JSON,
927-
# to save the pureProblem when it would not otherwise be saved.
928-
# - We are assuming that there is no need to DELETE old
929-
# persistent data if the hash is empty, even if in
930-
# potential there may be some data already in the database.
931-
my @persistent_data_keys = keys %{ $pg_result->{PERSISTENCE_HASH_UPDATED} };
932-
if (@persistent_data_keys) {
933-
my $json_data = decode_json($pureProblem->{problem_data} || '{}');
934-
for my $key (@persistent_data_keys) {
935-
$json_data->{$key} = $pg_result->{PERSISTENCE_HASH}{$key};
936-
}
937-
$pureProblem->problem_data(encode_json($json_data));
938-
939-
# If the pureProblem will not be saved below, we should save the
940-
# persistent data here before any other changes are made to it.
941-
if (($c->{submitAnswers} && !$will{recordAnswers})) {
942-
$c->db->putProblemVersion($pureProblem);
943-
}
944-
}
945922
} else {
946923
my $prefix = sprintf('Q%04d_', $problemNumbers[$i]);
947924
my @fields = sort grep {/^(?!previous).*$prefix/} (keys %{ $c->{formFields} });
@@ -974,6 +951,7 @@ async sub pre_header_initialize ($c) {
974951
$pureProblem->attempted(1);
975952
$pureProblem->num_correct($pg_result->{state}{num_of_correct_ans});
976953
$pureProblem->num_incorrect($pg_result->{state}{num_of_incorrect_ans});
954+
$pureProblem->problem_data(encode_json($pg_result->{PERSISTENCE_HASH} || '{}'));
977955

978956
# Add flags which are really a comma separated list of answer types.
979957
$pureProblem->flags($answer_types_string);
@@ -1144,45 +1122,6 @@ async sub pre_header_initialize ($c) {
11441122
# Reset start time
11451123
$c->param('startTime', '');
11461124
}
1147-
} else {
1148-
# This 'else' case includes initial load of the first page of the
1149-
# quiz and checkAnswers calls, as well as when $can{recordAnswers}
1150-
# is false.
1151-
1152-
# Save persistent data to database even in this case, when answers
1153-
# would not or cannot be recorded.
1154-
my @pureProblems = $db->getAllProblemVersions($effectiveUserID, $setID, $versionID);
1155-
for my $i (0 .. $#problems) {
1156-
# Process each problem.
1157-
my $pureProblem = $pureProblems[ $probOrder[$i] ];
1158-
my $pg_result = $pg_results[ $probOrder[$i] ];
1159-
1160-
if (ref $pg_result) {
1161-
# Transfer persistent problem data from the PERSISTENCE_HASH:
1162-
# - Get keys to update first, to avoid extra work when no updates
1163-
# are needed. When none, we avoid the need to decode/encode JSON,
1164-
# or to save the pureProblem.
1165-
# - We are assuming that there is no need to DELETE old
1166-
# persistent data if the hash is empty, even if in
1167-
# potential there may be some data already in the database.
1168-
my @persistent_data_keys = keys %{ $pg_result->{PERSISTENCE_HASH_UPDATED} };
1169-
next unless (@persistent_data_keys); # stop now if nothing to do
1170-
if ($isFakeSet) {
1171-
warn join("",
1172-
"This problem stores persistent data and this cannot be done in a fake set. ",
1173-
"Some functionality may not work properly when testing this problem in this setting.");
1174-
next;
1175-
}
1176-
1177-
my $json_data = decode_json($pureProblem->{problem_data} || '{}');
1178-
for my $key (@persistent_data_keys) {
1179-
$json_data->{$key} = $pg_result->{PERSISTENCE_HASH}{$key};
1180-
}
1181-
$pureProblem->problem_data(encode_json($json_data));
1182-
1183-
$c->db->putProblemVersion($pureProblem);
1184-
}
1185-
}
11861125
}
11871126
debug('end answer processing');
11881127

@@ -1485,7 +1424,10 @@ async sub getProblemHTML ($c, $effectiveUser, $set, $formFields, $mergedProblem)
14851424
: !$c->{previewAnswers} && $c->{will}{showCorrectAnswers} ? 1
14861425
: 0
14871426
),
1488-
debuggingOptions => getTranslatorDebuggingOptions($c->authz, $c->{userID})
1427+
debuggingOptions => getTranslatorDebuggingOptions($c->authz, $c->{userID}),
1428+
$c->{can}{checkAnswers} && defined $formFields->{ 'problem_data_' . $mergedProblem->problem_id }
1429+
? (problemData => $formFields->{ 'problem_data_' . $mergedProblem->problem_id })
1430+
: ()
14891431
},
14901432
);
14911433

@@ -1504,6 +1446,12 @@ async sub getProblemHTML ($c, $effectiveUser, $set, $formFields, $mergedProblem)
15041446
$pg->{body_text} = undef;
15051447
}
15061448

1449+
# If the user can check answers and either this is not an answer submission or the problem_data form
1450+
# parameter was previously set, then set or update the problem_data form parameter.
1451+
$c->param('problem_data_' . $mergedProblem->problem_id => encode_json($pg->{PERSISTENCE_HASH} || '{}'))
1452+
if $c->{can}{checkAnswers}
1453+
&& (!$c->{submitAnswers} || defined $c->param('problem_data_' . $mergedProblem->problem_id));
1454+
15071455
return $pg;
15081456
}
15091457

lib/WeBWorK/ContentGenerator/Problem.pm

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,9 @@ async sub pre_header_initialize ($c) {
602602
: !$c->{previewAnswers} && $will{showCorrectAnswers} ? 1
603603
: 0
604604
),
605-
debuggingOptions => getTranslatorDebuggingOptions($authz, $userID)
605+
debuggingOptions => getTranslatorDebuggingOptions($authz, $userID),
606+
$can{checkAnswers}
607+
&& defined $formFields->{problem_data} ? (problemData => $formFields->{problem_data}) : ()
606608
}
607609
);
608610

@@ -1412,6 +1414,11 @@ sub output_misc ($c) {
14121414
push(@$output, $c->hidden_field(studentNavFilter => $c->param('studentNavFilter')))
14131415
if $c->param('studentNavFilter');
14141416

1417+
# If the user can check answers and a problem_data form parameter for
1418+
# this problem has been set then add a hidden input with that data.
1419+
push(@$output, $c->hidden_field(problem_data => $c->param('problem_data')))
1420+
if $c->{can}{checkAnswers} && defined $c->param('problem_data');
1421+
14151422
return $output->join('');
14161423
}
14171424

lib/WeBWorK/Utils/ProblemProcessing.pm

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ for the problem pages, especially those generated by Problem.pm.
2323
2424
=cut
2525

26-
use Mojo::JSON qw(encode_json);
2726
use Email::Stuffer;
2827
use Try::Tiny;
2928
use Mojo::JSON qw(encode_json decode_json);
@@ -67,27 +66,6 @@ async sub process_and_log_answer ($c) {
6766
my $pureProblem = $db->getUserProblem($problem->user_id, $problem->set_id, $problem->problem_id);
6867
my $answer_log = $ce->{courseFiles}{logs}{answer_log};
6968

70-
# Transfer persistent problem data from the PERSISTENCE_HASH:
71-
# - Get keys to update first, to avoid extra work when no updates
72-
# are needed. When none, we avoid the need to decode/encode JSON,
73-
# or to save the pureProblem.
74-
# - We are assuming that there is no need to DELETE old
75-
# persistent data if the hash is empty, even if in
76-
# potential there may be some data already in the database.
77-
if (defined($pureProblem)) {
78-
my @persistent_data_keys = keys %{ $pg->{PERSISTENCE_HASH_UPDATED} };
79-
if (@persistent_data_keys) {
80-
my $json_data = decode_json($pureProblem->{problem_data} || '{}');
81-
for my $key (@persistent_data_keys) {
82-
$json_data->{$key} = $pg->{PERSISTENCE_HASH}{$key};
83-
}
84-
$pureProblem->problem_data(encode_json($json_data));
85-
if (!$submitAnswers) { # would not be saved below
86-
$db->putUserProblem($pureProblem);
87-
}
88-
}
89-
}
90-
9169
my ($encoded_last_answer_string, $scores2, $answer_types_string);
9270
my $scoreRecordedMessage = '';
9371

@@ -134,13 +112,13 @@ async sub process_and_log_answer ($c) {
134112
# this stores previous answers to the problem to provide "sticky answers"
135113
if ($submitAnswers) {
136114
if (defined $pureProblem) {
137-
# store answers in DB for sticky answers
138-
my %answersToStore;
139-
140115
# store last answer to database for use in "sticky" answers
141116
$problem->last_answer($encoded_last_answer_string);
142117
$pureProblem->last_answer($encoded_last_answer_string);
143118

119+
# Also store persistent problem data.
120+
$pureProblem->problem_data(encode_json($pg->{PERSISTENCE_HASH} || '{}'));
121+
144122
# store state in DB if it makes sense
145123
if ($will{recordAnswers}) {
146124
my $score =
@@ -268,6 +246,11 @@ async sub process_and_log_answer ($c) {
268246
}
269247
}
270248

249+
# If the user can check answers and either this is not an answer submission or the problem_data form parameter was
250+
# previously set, then set or update the problem_data form parameter.
251+
$c->param(problem_data => encode_json($pg->{PERSISTENCE_HASH} || '{}'))
252+
if $c->{can}{checkAnswers} && (!$submitAnswers || defined $c->param('problem_data'));
253+
271254
$c->{scoreRecordedMessage} = $scoreRecordedMessage;
272255
return $scoreRecordedMessage;
273256
}

lib/WeBWorK/Utils/Rendering.pm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ sub constructPGOptions ($ce, $user, $set, $problem, $psvn, $formFields, $transla
133133
$options{needs_grading} = ($problem->flags // '') =~ /:needs_grading$/;
134134

135135
# Persistent problem data
136-
$options{PERSISTENCE_HASH} = decode_json($problem->problem_data || '{}');
136+
$options{PERSISTENCE_HASH} = decode_json($translationOptions->{problemData}
137+
// (defined $problem->problem_data && $problem->problem_data ne '' ? $problem->problem_data : '{}'));
137138

138139
# Language
139140
$options{language} = $ce->{language};
@@ -284,8 +285,7 @@ sub renderPG ($c, $effectiveUser, $set, $problem, $psvn, $formFields, $translati
284285
map { $_ => $pg->{pgcore}{PG_alias}{resource_list}{$_}{uri} }
285286
keys %{ $pg->{pgcore}{PG_alias}{resource_list} }
286287
};
287-
$ret->{PERSISTENCE_HASH_UPDATED} = $pg->{pgcore}{PERSISTENCE_HASH_UPDATED};
288-
$ret->{PERSISTENCE_HASH} = $pg->{pgcore}{PERSISTENCE_HASH};
288+
$ret->{PERSISTENCE_HASH} = $pg->{pgcore}{PERSISTENCE_HASH};
289289
}
290290

291291
# Save the problem source. This is used by Caliper::Entity. Why?

lib/WebworkWebservice/RenderProblem.pm

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ async sub renderProblem {
252252
show_pg_info => $rh->{show_pg_info} // 0,
253253
show_answer_hash_info => $rh->{show_answer_hash_info} // 0,
254254
show_answer_group_info => $rh->{show_answer_group_info} // 0
255-
}
255+
},
256+
defined $rh->{problem_data} && $rh->{problem_data} ne '' ? (problemData => $rh->{problem_data}) : ()
256257
};
257258

258259
$ce->{pg}{specialPGEnvironmentVars}{problemPreamble} = { TeX => '', HTML => '' } if $rh->{noprepostambles};
@@ -270,6 +271,7 @@ async sub renderProblem {
270271
errors => $pg->{errors},
271272
pg_warnings => $pg->{warnings},
272273
PG_ANSWERS_HASH => $pg->{PG_ANSWERS_HASH},
274+
PERSISTENCE_HASH => $pg->{PERSISTENCE_HASH},
273275
problem_result => $pg->{result},
274276
problem_state => $pg->{state},
275277
flags => $pg->{flags},

templates/ContentGenerator/GatewayQuiz.html.ep

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,16 @@
644644
<%= hidden_field 'probstatus' . $problems->[ $probOrder->[$i] ]{problem_id}
645645
=> $c->{probStatus}[ $probOrder->[$i] ] %>
646646
% }
647+
%
648+
% # If the user can check answers and a problem_data form parameter for
649+
% # this problem has been set then add a hidden input with that data.
650+
% if (
651+
% $c->{can}{checkAnswers}
652+
% && defined $c->param('problem_data_' . $problems->[ $probOrder->[$i] ]{problem_id})
653+
% ) {
654+
<%= hidden_field 'problem_data_' . $problems->[ $probOrder->[$i] ]{problem_id}
655+
=> param('problem_data_' . $problems->[ $probOrder->[$i] ]{problem_id}) =%>
656+
% }
647657
% }
648658
%
649659
<%= $jumpLinks->() =%>

templates/RPCRenderFormats/default.html.ep

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
%= hidden_field showCorrectAnswersOnlyButton => $showCorrectAnswersOnlyButton
101101
%= hidden_field showFooter => $showFooter
102102
%= hidden_field extra_header_text => $extra_header_text
103+
%= hidden_field problem_data => $problem_data
103104
% if ($formatName eq 'debug' && $ws->{inputs_ref}{clientDebug}) {
104105
%= hidden_field clientDebug => $ws->{inputs_ref}{clientDebug}
105106
% }

0 commit comments

Comments
 (0)