From 0fa323da6b91e17df219737b27e329466e60a653 Mon Sep 17 00:00:00 2001
From: Nathan Wallach <tani@technion.ac.il>
Date: Wed, 19 Apr 2023 15:49:07 +0300
Subject: [PATCH] Patches to allow preventing scaffold from changing which
 sections are open when Preview is called: (a) Allow preventing opening an
 additional section when all answers of the last open section are correct
 during processing a Preview render, and (b) Allow preventing scaffold closing
 previously open sections if an answer to a prior section became incorrect
 when processing a Preview render.

Control switches control if these modified behaviors are used or not.

The defaults maintain the prior behavior.

Some contributions to a prior version of this code by Glenn Rice.
---
 macros/core/scaffold.pl | 71 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 64 insertions(+), 7 deletions(-)

diff --git a/macros/core/scaffold.pl b/macros/core/scaffold.pl
index 4070b432a2..547388d740 100644
--- a/macros/core/scaffold.pl
+++ b/macros/core/scaffold.pl
@@ -186,6 +186,21 @@ =head1 DESCRIPTION
 have the student open it by hand before anwering the questions.  In
 this case, set this value to 0 (it is 1 by default).
 
+=item C<S<< allow_next_section_to_open_on_preview => 0 or 1 >>>
+
+This determines whether Preview My Answers should be allowed to open
+a new section. Set this to 0 to prevent the next section from opening
+after Preview My Answers is clicked (it is 1 by default).
+
+=item C<S<< prevent_close_by_preview => 0 or 1 >>>
+
+This determines whether Preview My Answers should be allowed to close
+a section which was open before, if the prior answers were changed to be
+incorrect. By default the setting is 0, so such section closures can
+occur due to a Preview, while when set to 1 sections will not be closed by
+Preview.
+
+
 =item C<S<< numbered => 0 or 1 >>>
 
 This determines whether each section is automatically numbered before
@@ -316,6 +331,8 @@ sub Begin {
 	unshift(@scaffolds, $self);
 	$scaffold = $self;
 	$scaffold_depth++;
+	$self->{isPreview}     = undef;
+	$self->{key_last_open} = "ScAfFoLd_${PREFIX}_SC-" . $self->{number} . "_last_open";
 	$self->{previous_output} =
 		[ splice(@{$PG_OUTPUT}, 0) ];    # get output and clear it without changing the array pointer
 	$self->{output} = [];                # the contents of the scaffold
@@ -338,6 +355,12 @@ sub End {
 	$self->hide_other_results(@{ $self->{open} });         # hide results of unnopened sections in the results table
 	push(@$PG_OUTPUT, @{ $self->{previous_output} }, @{ $self->{output} })
 		;                                                  # put back original output and scaffold output
+
+	if (!$self->{isPreview}) {
+		# Record the last_open value for future renders
+		main::update_persistent_data($self->{key_last_open}, $self->{open}[-1] ? $self->{open}[-1] : 0);
+	}
+
 	delete $self->{previous_output};
 	delete $self->{output};                                # don't need these any more
 	shift(@scaffolds);
@@ -372,6 +395,11 @@ sub new {
 		hardcopy_is_open          => "always",                  # open all possible sections in hardcopy
 		open_first_section        => 1,                         # 0 means don't open any sections initially
 		numbered                  => 0,                         # 1 means sections will be printed with their number
+
+		# control whether Preview should be allowed to open/close sections
+		allow_next_section_to_open_on_preview => 1,             # can Preview open another section
+		prevent_close_by_preview              => 0,             # can Preview close previously open sections
+
 		@_,
 		number     => ++$scaffold_no,                           # the number for this scaffold
 		depth      => $scaffold_depth,                          # the nesting depth for this scaffold
@@ -402,7 +430,7 @@ sub start_section {
 
 #
 #  Add the content from the current section into the scaffold's output
-#  and remove the current_section (so we can tell that no sectionis open).
+#  and remove the current_section (so we can tell that no section is open).
 #
 sub end_section {
 	my $self = shift;
@@ -435,11 +463,40 @@ sub section_answers {
 		$evaluator->{rh_ans}{ans_message} = "";
 		delete $evaluator->{rh_ans}{error_message};
 	}
-	foreach my $name (@_) { $self->{scores}{$name} = $answers{$name}{score} if $answers{$name} }
+
+	my $section_no      = $self->{section_no};
+	my $prior_last_open = main::get_persistent_data($self->{key_last_open});
+
+	my $myLastSet;    # we save the name of the last answer field whose score was set
+	for my $name (@_) {
+		if ($answers{$name}) {
+			$self->{scores}{$name} = $answers{$name}{score};
+			$myLastSet = $name;
+		}
+	}
+	if ($myLastSet) {
+		$self->{isPreview} = $answers{$myLastSet}{isPreview} if (!defined($self->{isPreview}));
+		if ($self->{isPreview}) {    # Only handle for Preview mode
+			if (
+				($section_no == $prior_last_open)    # Only should consider modifying the last previously open section
+				&& !$self->{allow_next_section_to_open_on_preview}
+				)
+			{
+				# This should prevent the next section from opening on Preview
+				# if all answers to this section are correct, unless it would open anyway.
+				$self->{scores}{$myLastSet} = 0;
+			} elsif (($section_no < $prior_last_open) && $self->{prevent_close_by_preview}) {
+				# This section was open before - AVOID closing it due to a change in prior sections and a Preview
+				for my $name (@{ $self->{ans_names} }) {
+					$self->{scores}{$name} = 1.0 if ($answers{$name});    # to prevent it from being closed
+				}
+			}
+		}
+	}
 }
 
 #
-#  Add the given sections to the list of sections to be openned
+#  Add the given sections to the list of sections to be opened
 #  for this scaffold
 #
 sub is_open {
@@ -601,7 +658,7 @@ sub new {
 #  Adds the necessary HTML around the content of the section.
 #
 #    First, determine the is_correct and can_open status and save them.
-#    Then check if the section is to be openned, and if so, add it
+#    Then check if the section is to be opened, and if so, add it
 #    to the open list of the scaffold.
 #
 #    The $PG_OUTPUT variable holds just the contents of this section,
@@ -745,11 +802,11 @@ sub new_answers {
 package Section::can_open;
 
 #
-#  Always can be openned
+#  Always can be opened
 #
 sub always { return 1 }
 #
-#  Can be openned when all the answers from previous sections are correct
+#  Can be opened when all the answers from previous sections are correct
 #
 sub when_previous_correct {
 	my $section = shift;
@@ -772,7 +829,7 @@ sub incorrect {
 	return !$section->{is_correct};
 }
 #
-#  Never can be openned
+#  Never can be opened
 #
 sub never { return 0 }