From 9111ee8521936375ab35ebcad981803630c059dc Mon Sep 17 00:00:00 2001
From: Jaimos Skriletz <jaimosskriletz@gmail.com>
Date: Fri, 16 Aug 2024 08:41:22 -0600
Subject: [PATCH] Add achievement items that will modify reduced scoring.

This adds achievement items that modify the reduced scoring time period
while leaving other settings alone. These are designed for classes which
use reduced scoring and keep assignments open in the reduced scoring
period for a long time (such as the end of the course).

+ ExtendReducedDate -> Extends the reduced scoring date 24 hours.
+ SuperExtendReducedDate -> Extends the reduced scoring date 48 hours.
+ NoReducedCred -> Removes the reduced scoring flag on an assignment to
  allow full credit after the reduced scoring date.
---
 lib/WeBWorK/AchievementItems.pm               |   6 +
 .../AchievementItems/ExtendReducedDate.pm     | 110 ++++++++++++++++++
 lib/WeBWorK/AchievementItems/NoReducedCred.pm | 100 ++++++++++++++++
 .../SuperExtendReducedDate.pm                 | 110 ++++++++++++++++++
 4 files changed, 326 insertions(+)
 create mode 100644 lib/WeBWorK/AchievementItems/ExtendReducedDate.pm
 create mode 100644 lib/WeBWorK/AchievementItems/NoReducedCred.pm
 create mode 100644 lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm

diff --git a/lib/WeBWorK/AchievementItems.pm b/lib/WeBWorK/AchievementItems.pm
index 8081c7bd6e..7585142b5d 100644
--- a/lib/WeBWorK/AchievementItems.pm
+++ b/lib/WeBWorK/AchievementItems.pm
@@ -27,11 +27,14 @@ use constant ITEMS => [ qw(
 	HalfCreditProb
 	FullCreditProb
 	ReducedCred
+	NoReducedCred
 	ExtendDueDate
+	ExtendReducedDate
 	DoubleSet
 	ResurrectHW
 	Surprise
 	SuperExtendDueDate
+	SuperExtendReducedDate
 	HalfCreditSet
 	FullCreditSet
 	AddNewTestGW
@@ -110,15 +113,18 @@ END {
 	use WeBWorK::AchievementItems::DuplicateProb;
 	use WeBWorK::AchievementItems::ExtendDueDateGW;
 	use WeBWorK::AchievementItems::ExtendDueDate;
+	use WeBWorK::AchievementItems::ExtendReducedDate;
 	use WeBWorK::AchievementItems::FullCreditProb;
 	use WeBWorK::AchievementItems::FullCreditSet;
 	use WeBWorK::AchievementItems::HalfCreditProb;
 	use WeBWorK::AchievementItems::HalfCreditSet;
 	use WeBWorK::AchievementItems::ReducedCred;
+	use WeBWorK::AchievementItems::NoReducedCred;
 	use WeBWorK::AchievementItems::ResetIncorrectAttempts;
 	use WeBWorK::AchievementItems::ResurrectGW;
 	use WeBWorK::AchievementItems::ResurrectHW;
 	use WeBWorK::AchievementItems::SuperExtendDueDate;
+	use WeBWorK::AchievementItems::SuperExtendReducedDate;
 	use WeBWorK::AchievementItems::Surprise;
 }
 
diff --git a/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm b/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm
new file mode 100644
index 0000000000..f3d78333ae
--- /dev/null
+++ b/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm
@@ -0,0 +1,110 @@
+################################################################################
+# WeBWorK Online Homework Delivery System
+# Copyright &copy; 2000-2024 The WeBWorK Project, https://github.com/openwebwork
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of either: (a) the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version, or (b) the "Artistic License" which comes with this package.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
+# Artistic License for more details.
+################################################################################
+
+package WeBWorK::AchievementItems::ExtendReducedDate;
+use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
+
+# Item to extend a close date by 24 hours.
+
+use WeBWorK::Utils           qw(x nfreeze_base64 thaw_base64);
+use WeBWorK::Utils::DateTime qw(between);
+use WeBWorK::Utils::Sets     qw(format_set_name_display);
+
+use constant ONE_DAY => 86400;
+
+sub new ($class) {
+	return bless {
+		id          => 'ExtendReducedDate',
+		name        => x('Scroll of Extension'),
+		description => x(
+			'Adds 24 hours to the reduced scoring date of an assignment.  You will have to resubmit '
+				. 'any problems that have already been penalized to earn full credit.  You cannot '
+				. 'extend the reduced scoring date beyond the due date of an assignment.'
+		)
+	}, $class;
+}
+
+sub print_form ($self, $sets, $setProblemIds, $c) {
+	my @openSets;
+
+	# Nothing to do if reduced scoring is not enabled.
+	return unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring};
+
+	for my $i (0 .. $#$sets) {
+		my $new_date = 0;
+		if ($sets->[$i]->reduced_scoring_date() && $sets->[$i]->reduced_scoring_date() < $sets->[$i]->due_date()) {
+			$new_date = $sets->[$i]->reduced_scoring_date() + ONE_DAY;
+			$new_date = $sets->[$i]->due_date() if $sets->[$i]->due_date() < $new_date;
+		}
+		push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ])
+			if ($new_date
+				&& between($sets->[$i]->open_date, $new_date)
+				&& $sets->[$i]->assignment_type eq 'default'
+				&& $sets->[$i]->enable_reduced_scoring);
+	}
+
+	return unless @openSets;
+
+	return $c->c(
+		$c->tag(
+			'p',
+			$c->maketext('Choose the assignment whose reduced scoring date you would like to extend by 24 hours.')
+		),
+		WeBWorK::AchievementItems::form_popup_menu_row(
+			$c,
+			id         => 'ext_reduced_set_id',
+			label_text => $c->maketext('Assignment Name'),
+			values     => \@openSets,
+			menu_attr  => { dir => 'ltr' }
+		)
+	)->join('');
+}
+
+sub use_item ($self, $userName, $c) {
+	my $db = $c->db;
+	my $ce = $c->ce;
+
+	# Validate data
+
+	# Nothing to do if reduced scoring is not enabled.
+	return 'Reduced scoring disabled.' unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring};
+
+	my $globalUserAchievement = $db->getGlobalUserAchievement($userName);
+	return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash;
+
+	my $globalData = thaw_base64($globalUserAchievement->frozen_hash);
+	return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} };
+
+	my $setID = $c->param('ext_reduced_set_id');
+	return 'You need to input a Set Name' unless defined $setID;
+
+	my $set     = $db->getMergedSet($userName, $setID);
+	my $userSet = $db->getUserSet($userName, $setID);
+	return q{Couldn't find that set!} unless $set && $userSet;
+
+	# Add time to the reduced scoring date, keeping in mind this cannot extend past the due date.
+	my $new_date = $set->reduced_scoring_date() + ONE_DAY;
+	$new_date = $set->due_date() if $set->due_date() < $new_date;
+	$userSet->reduced_scoring_date($new_date);
+	$db->putUserSet($userSet);
+
+	$globalData->{ $self->{id} }--;
+	$globalUserAchievement->frozen_hash(nfreeze_base64($globalData));
+	$db->putGlobalUserAchievement($globalUserAchievement);
+
+	return;
+}
+
+1;
diff --git a/lib/WeBWorK/AchievementItems/NoReducedCred.pm b/lib/WeBWorK/AchievementItems/NoReducedCred.pm
new file mode 100644
index 0000000000..cc302dcd0b
--- /dev/null
+++ b/lib/WeBWorK/AchievementItems/NoReducedCred.pm
@@ -0,0 +1,100 @@
+################################################################################
+# WeBWorK Online Homework Delivery System
+# Copyright &copy; 2000-2024 The WeBWorK Project, https://github.com/openwebwork
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of either: (a) the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version, or (b) the "Artistic License" which comes with this package.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
+# Artistic License for more details.
+################################################################################
+
+package WeBWorK::AchievementItems::NoReducedCred;
+use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
+
+# Item to remove reduce credit scoring period from a set.
+# Reduced scoring needs to be enabled for this item to be useful.
+
+use WeBWorK::Utils           qw(x nfreeze_base64 thaw_base64);
+use WeBWorK::Utils::DateTime qw(between);
+use WeBWorK::Utils::Sets     qw(format_set_name_display);
+
+sub new ($class) {
+	return bless {
+		id          => 'NoReducedCred',
+		name        => x('Potion of Power'),
+		description => x(
+			'Remove reduced scoring penalties from an open assignemnt.  You will have to resubmit '
+				. 'any problems that have already been penalized to earn full credit on them.'
+		)
+	}, $class;
+}
+
+sub print_form ($self, $sets, $setProblemIds, $c) {
+	my @openSets;
+
+	# Nothing to do if reduced scoring is not enabled.
+	return unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring};
+
+	# Only show open sets that have reduced scoring enabled.
+	for my $i (0 .. $#$sets) {
+		push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ])
+			if (between($sets->[$i]->open_date, $sets->[$i]->due_date)
+				&& $sets->[$i]->assignment_type eq 'default'
+				&& $sets->[$i]->enable_reduced_scoring);
+	}
+
+	return unless @openSets;
+
+	return $c->c(
+		$c->tag('p', $c->maketext('Choose the assignment to remove the reduced scoring pentaly from.')),
+		WeBWorK::AchievementItems::form_popup_menu_row(
+			$c,
+			id         => 'no_reduce_set_id',
+			label_text => $c->maketext('Assignment Name'),
+			values     => \@openSets,
+			menu_attr  => { dir => 'ltr' }
+		)
+	)->join('');
+}
+
+sub use_item ($self, $userName, $c) {
+	my $db = $c->db;
+	my $ce = $c->ce;
+
+	# Validate data
+
+	return q{This item won't work unless your instructor enables the reduced scoring feature.  }
+		. 'Let your instructor know that you received this message.'
+		unless $ce->{pg}{ansEvalDefaults}{enableReducedScoring};
+
+	my $globalUserAchievement = $db->getGlobalUserAchievement($userName);
+	return "No achievement data?!?!?!" unless $globalUserAchievement->frozen_hash;
+
+	my $globalData = thaw_base64($globalUserAchievement->frozen_hash);
+	return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} };
+
+	my $setID = $c->param('no_reduce_set_id');
+	return "You need to input a Set Name" unless defined $setID;
+
+	my $set     = $db->getMergedSet($userName, $setID);
+	my $userSet = $db->getUserSet($userName, $setID);
+	return "Couldn't find that set!" unless $set && $userSet;
+
+	# Remove reduced scoring from the set and set the reduced scoring date to be the due date.
+	$userSet->enable_reduced_scoring(0);
+	$userSet->reduced_scoring_date($set->due_date());
+	$db->putUserSet($userSet);
+
+	$globalData->{ $self->{id} }--;
+	$globalUserAchievement->frozen_hash(nfreeze_base64($globalData));
+	$db->putGlobalUserAchievement($globalUserAchievement);
+
+	return;
+}
+
+1;
diff --git a/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm b/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm
new file mode 100644
index 0000000000..5996b58984
--- /dev/null
+++ b/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm
@@ -0,0 +1,110 @@
+################################################################################
+# WeBWorK Online Homework Delivery System
+# Copyright &copy; 2000-2024 The WeBWorK Project, https://github.com/openwebwork
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of either: (a) the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version, or (b) the "Artistic License" which comes with this package.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
+# Artistic License for more details.
+################################################################################
+
+package WeBWorK::AchievementItems::SuperExtendReducedDate;
+use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
+
+# Item to extend a close date by 48 hours.
+
+use WeBWorK::Utils           qw(x nfreeze_base64 thaw_base64);
+use WeBWorK::Utils::DateTime qw(between);
+use WeBWorK::Utils::Sets     qw(format_set_name_display);
+
+use constant TWO_DAYS => 172800;
+
+sub new ($class) {
+	return bless {
+		id          => 'SuperExtendReducedDate',
+		name        => x('Scroll of Longevity'),
+		description => x(
+			'Adds 48 hours to the reduced scoring date of an assignment.  You will have to resubmit '
+				. 'any problems that have already been penalized to earn full credit.  You cannot '
+				. 'extend the reduced scoring date beyond the due date of an assignment.'
+		)
+	}, $class;
+}
+
+sub print_form ($self, $sets, $setProblemIds, $c) {
+	my @openSets;
+
+	# Nothing to do if reduced scoring is not enabled.
+	return unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring};
+
+	for my $i (0 .. $#$sets) {
+		my $new_date = 0;
+		if ($sets->[$i]->reduced_scoring_date() && $sets->[$i]->reduced_scoring_date() < $sets->[$i]->due_date()) {
+			$new_date = $sets->[$i]->reduced_scoring_date() + TWO_DAYS;
+			$new_date = $sets->[$i]->due_date() if $sets->[$i]->due_date() < $new_date;
+		}
+		push(@openSets, [ format_set_name_display($sets->[$i]->set_id) => $sets->[$i]->set_id ])
+			if ($new_date
+				&& between($sets->[$i]->open_date, $new_date)
+				&& $sets->[$i]->assignment_type eq 'default'
+				&& $sets->[$i]->enable_reduced_scoring);
+	}
+
+	return unless @openSets;
+
+	return $c->c(
+		$c->tag(
+			'p',
+			$c->maketext('Choose the assignment whose reduced scoring date you would like to extend by 48 hours.')
+		),
+		WeBWorK::AchievementItems::form_popup_menu_row(
+			$c,
+			id         => 'super_ext_reduced_set_id',
+			label_text => $c->maketext('Assignment Name'),
+			values     => \@openSets,
+			menu_attr  => { dir => 'ltr' }
+		)
+	)->join('');
+}
+
+sub use_item ($self, $userName, $c) {
+	my $db = $c->db;
+	my $ce = $c->ce;
+
+	# Validate data
+
+	# Nothing to do if reduced scoring is not enabled.
+	return 'Reduce scoring disabled.' unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring};
+
+	my $globalUserAchievement = $db->getGlobalUserAchievement($userName);
+	return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash;
+
+	my $globalData = thaw_base64($globalUserAchievement->frozen_hash);
+	return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} };
+
+	my $setID = $c->param('super_ext_reduced_set_id');
+	return 'You need to input a Set Name' unless defined $setID;
+
+	my $set     = $db->getMergedSet($userName, $setID);
+	my $userSet = $db->getUserSet($userName, $setID);
+	return q{Couldn't find that set!} unless $set && $userSet;
+
+	# Add time to the reduced scoring date, keeping in mind this cannot extend past the due date.
+	my $new_date = $set->reduced_scoring_date() + TWO_DAYS;
+	$new_date = $set->due_date() if $set->due_date() < $new_date;
+	$userSet->reduced_scoring_date($new_date);
+	$db->putUserSet($userSet);
+
+	$globalData->{ $self->{id} }--;
+	$globalUserAchievement->frozen_hash(nfreeze_base64($globalData));
+	$db->putGlobalUserAchievement($globalUserAchievement);
+
+	return;
+}
+
+1;