Skip to content

Commit

Permalink
MDL-82120 gradepenalty_duedate: add new plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Nathan Nguyen committed Jun 24, 2024
1 parent 56de64b commit bebf2a2
Show file tree
Hide file tree
Showing 15 changed files with 1,412 additions and 0 deletions.
94 changes: 94 additions & 0 deletions grade/penalty/duedate/classes/gradepenalty_duedate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle 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 the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace gradepenalty_duedate;

use context_module;
use context_course;
use context_system;
use core_grades\local\penalty\grade_penalty;

/**
* Calculate penalty.
*
* @package gradepenalty_duedate
* @copyright 2024 Catalyst IT Australia Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradepenalty_duedate extends grade_penalty {

/**
* Mark will be deducted from student grade.
*
* @param float $finalgrade Final grade.
* @return float the penalty.
*/
public function calculate_penalty($finalgrade): float {
$penalty = 0.0;

// Calculate the difference between the submission date and the due date.
$diff = $this->submissiondate - $this->duedate;

// If the submission date is after the due date, calculate the penalty.
if ($diff > 0) {
// Get all penalty rules, ordered by the highest penalty first.
$penaltyrules = $this->find_effective_penalty_rules();

// Check each rule to see which rule will apply.
if (!empty($penaltyrules)) {
foreach ($penaltyrules as $penaltyrule) {
if ($diff >= $penaltyrule->get('latefor')) {
$penalty = $penaltyrule->get('penalty');
break;
}
}
}
}

// Calculate the deducted grade.
return $finalgrade * $penalty / 100;
}

/**
* Find effective penalty rule.
*
* @return array
*/
public function find_effective_penalty_rules(): array {
// Course module context id.
$modulecontext = context_module::instance($this->cm->id);

// Get all penalty rules, ordered by the highest penalty first.
$penaltyrules = penalty_rule::get_records(['contextid' => $modulecontext->id], 'sortorder DESC');

// If there is no penalty rule, go to the course context.
if (empty($penaltyrules)) {
// Find course content.
$course = get_course($this->cm->course);
$coursecontext = context_course::instance($course->id);

$penaltyrules = penalty_rule::get_records(['contextid' => $coursecontext->id], 'sortorder DESC');
}

// If there is no penalty rule, go to the system context.
if (empty($penaltyrules)) {
$systemcontext = context_system::instance();
$penaltyrules = penalty_rule::get_records(['contextid' => $systemcontext->id], 'sortorder DESC');
}

return $penaltyrules;
}
}
124 changes: 124 additions & 0 deletions grade/penalty/duedate/classes/helper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle 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 the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace gradepenalty_duedate;

defined('MOODLE_INTERNAL') || die();

require_once(__DIR__ . '/../../../lib.php');

/**
* Helper for grade penalty
*
* @package gradepenalty_duedate
* @copyright 2024 Catalyst IT Australia Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper {
/**
* Determine min and max value for latefor and penalty for a rule when updating or inserting.
* If updating, the ruleid belongs to the rule we are updating.
* If inserting, the ruleid belong to the rule which we will insert new rule before or after it
*
* @param int $ruleid the rule id we are updating or inserting
* @param int $action we are updating or inserting
* @param string $field which is 'latefor' or 'penalty'
* @param int $defaultmin default min value
* @param int $defaultmax default max value
* @return array
*/
public static function calculate_min_max_values($ruleid, $action, $field, $defaultmin, $defaultmax) {
global $DB;

$minlatefor = $defaultmin;
$maxlatefor = $defaultmax;

if ($ruleid !== 0) {
// Current rule.
$currentrule = $DB->get_record('gradepenalty_duedate_rule', ['id' => $ruleid]);

// Get the previous rule.
$previousrule = $DB->get_record('gradepenalty_duedate_rule', [
'sortorder' => $currentrule->sortorder - 1,
'contextid' => $currentrule->contextid,
]);

// Get the next rule.
$nextrule = $DB->get_record('gradepenalty_duedate_rule', [
'sortorder' => $currentrule->sortorder + 1,
'contextid' => $currentrule->contextid,
]);

if ($action === GRADEPENALTY_DUEDATE_ACTION_INSERT_ABOVE) {
// We will insert new rule above the current rule.
$minlatefor = $previousrule ? $previousrule->$field + 1 : $defaultmin;
$maxlatefor = $currentrule->$field - 1;
} else if ($action === GRADEPENALTY_DUEDATE_ACTION_INSERT_BELOW) {
// We will insert new rule below the current rule.
$minlatefor = $currentrule->$field + 1;
$maxlatefor = $nextrule ? $nextrule->$field - 1 : $defaultmax;
} else if ($action === GRADEPENALTY_DUEDATE_ACTION_UPDATE) {
// We are updating the rule, so we need to check the min and max value for the rule.
$minlatefor = $previousrule ? $previousrule->$field + 1 : $defaultmin;
$maxlatefor = $nextrule ? $nextrule->$field - 1 : $defaultmax;
}
}

return [$minlatefor, $maxlatefor];
}

/**
* Whether we can insert rule above or below.
*
* @param int $ruleid the rule id which we want to insert above or below.
* @param int $action insert above or below.
*
* @return bool
*/
public static function can_insert_rule(int $ruleid, int $action): bool {
global $DB;

if ($ruleid === 0) {
return false;
}

$currentrule = $DB->get_record('gradepenalty_duedate_rule', ['id' => $ruleid]);

if ($action === GRADEPENALTY_DUEDATE_ACTION_INSERT_ABOVE) {
// Get the previous rule.
$previousrule = $DB->get_record('gradepenalty_duedate_rule', [
'sortorder' => $currentrule->sortorder - 1,
'contextid' => $currentrule->contextid,
]);
$previouslatefor = $previousrule ? $previousrule->latefor : GRADEPENALTY_DUEDATE_MIN_LATEFOR;
$previouspenalty = $previousrule ? $previousrule->penalty : GRADEPENALTY_DUEDATE_MIN_PENALTY;
// Check if we still have space for insertion (at least 1 second and 1 percent).
return $currentrule->latefor > ($previouslatefor + 1) && $currentrule->penalty > ($previouspenalty + 1);
} else if ($action === GRADEPENALTY_DUEDATE_ACTION_INSERT_BELOW) {
// Get the next rule.
$nextrule = $DB->get_record('gradepenalty_duedate_rule', [
'sortorder' => $currentrule->sortorder + 1,
'contextid' => $currentrule->contextid,
]);
$nextlatefor = $nextrule ? $nextrule->latefor : GRADEPENALTY_DUEDATE_MAX_LATEFOR;
$nextpenalty = $nextrule ? $nextrule->penalty : GRADEPENALTY_DUEDATE_MAX_PENALTY;
// Check if we still have space for insertion (at least 1 second and 1 percent).
return $currentrule->latefor < ($nextlatefor - 1) && $currentrule->penalty < ($nextpenalty - 1);
}

return false;
}
}
153 changes: 153 additions & 0 deletions grade/penalty/duedate/classes/output/form/penalty_rule_form.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle 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 the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace gradepenalty_duedate\output\form;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->libdir . '/formslib.php');
require_once(__DIR__ . '/../../../lib.php');

use gradepenalty_duedate\helper;
use gradepenalty_duedate\penalty_rule;
use moodleform;

/**
* Form to set up the penalty rules for the gradepenalty_duedate plugin.
*
* @package gradepenalty_duedate
* @copyright 2024 Catalyst IT Australia Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class penalty_rule_form extends moodleform {
/** @var int Min latefor value */
protected $minlatefor = GRADEPENALTY_DUEDATE_MIN_LATEFOR;

/** @var int Max latefor value */
protected $maxlatefor = GRADEPENALTY_DUEDATE_MAX_LATEFOR;

/** @var int Min penalty value */
protected $minpenalty = GRADEPENALTY_DUEDATE_MIN_PENALTY;

/** @var int Max penalty value */
protected $maxpenalty = GRADEPENALTY_DUEDATE_MAX_PENALTY;

/** @var int ruleid */
protected $ruleid = 0;

/** @var int contextid */
protected $contextid = 0;

/** @var int action */
protected $action = GRADEPENALTY_DUEDATE_ACTION_CREATE;

/**
* Define the form.
*
* @return void
*/
public function definition() {
$mform = $this->_form;

// Set up min/max for latefor and penalty.
$this->ruleid = $this->_customdata['ruleid'] ?? 0;
$this->contextid = $this->_customdata['contextid'] ?? 0;
$this->action = $this->_customdata['action'] ?? GRADEPENALTY_DUEDATE_ACTION_CREATE;

// Calculate min/max value for latefor.
list($this->minlatefor, $this->maxlatefor) = helper::calculate_min_max_values($this->ruleid, $this->action, 'latefor',
GRADEPENALTY_DUEDATE_MIN_LATEFOR, GRADEPENALTY_DUEDATE_MAX_LATEFOR);
// And for penalty.
list($this->minpenalty, $this->maxpenalty) = helper::calculate_min_max_values($this->ruleid, $this->action, 'penalty',
GRADEPENALTY_DUEDATE_MIN_PENALTY, GRADEPENALTY_DUEDATE_MAX_PENALTY);

// Hidden context id, value is stored in $mform.
$mform->addElement('hidden', 'contextid');
$mform->setType('contextid', PARAM_INT);
$mform->setDefault('contextid', $this->contextid);

// Hidden rule id, value is stored in $mform.
$mform->addElement('hidden', 'ruleid');
$mform->setType('ruleid', PARAM_INT);
$mform->setDefault('ruleid', $this->ruleid);

// Hidden action, value is stored in $mform.
$mform->addElement('hidden', 'action');
$mform->setType('action', PARAM_INT);
$mform->setDefault('action', $this->action);

// If ruleid is not 0, then we are editing an existing rule.
$rule = new penalty_rule($this->ruleid);

// Latefor field.
$mform->addElement('duration', 'latefor', get_string('latefor', 'gradepenalty_duedate'), ['defaultunit' => DAYSECS]);
$mform->setType('latefor', PARAM_INT);
// Default value. If we are updating a rule, use the current value.
$mform->setDefault('latefor', $this->action === GRADEPENALTY_DUEDATE_ACTION_UPDATE ?
$rule->get('latefor') : $this->minlatefor);
// Required rule.
$mform->addRule('latefor', get_string('required'), 'required');
// Help button.
$mform->addHelpButton('latefor', 'latefor', 'gradepenalty_duedate');

// Penalty field.
$mform->addElement('text', 'penalty', get_string('penalty', 'gradepenalty_duedate'));
$mform->setType('penalty', PARAM_INT);
// Default value. If we are updating a rule, use the current value.
$mform->setDefault('penalty', $this->action === GRADEPENALTY_DUEDATE_ACTION_UPDATE ?
$rule->get('penalty') : $this->minpenalty);
// Required rule.
$mform->addRule('penalty', get_string('required'), 'required');
// Help button.
$mform->addHelpButton('penalty', 'penalty', 'gradepenalty_duedate');

// Add buttons.
$this->add_action_buttons();
}

/**
* Make sure the latefor and penalty values are within the min/max values.
*
* @param array $data array of data from the form.
* @param array $files array of files from the form.
* @return array
*/
public function validation($data, $files) {
$errors = parent::validation($data, $files);

// Validate latefor.
// Min value.
if ($data['latefor'] < $this->minlatefor) {
$errors['latefor'] = get_string('error_latefor_minvalue', 'gradepenalty_duedate', $this->minlatefor);
}
// Max value.
if ($data['latefor'] > $this->maxlatefor) {
$errors['latefor'] = get_string('error_latefor_maxvalue', 'gradepenalty_duedate', $this->maxlatefor);
}

// Validate penalty.
// Min value.
if ($data['penalty'] < $this->minpenalty) {
$errors['penalty'] = get_string('error_penalty_minvalue', 'gradepenalty_duedate', $this->minpenalty);
}
// Max value.
if ($data['penalty'] > $this->maxpenalty) {
$errors['penalty'] = get_string('error_penalty_maxvalue', 'gradepenalty_duedate', $this->maxpenalty);
}

return $errors;
}
}
Loading

0 comments on commit bebf2a2

Please sign in to comment.