Skip to content

Commit

Permalink
MDL-82120 gradepenalty: add new plugin type
Browse files Browse the repository at this point in the history
  • Loading branch information
Nathan Nguyen committed Aug 22, 2024
1 parent e0a9cf2 commit 291f2f0
Show file tree
Hide file tree
Showing 17 changed files with 1,161 additions and 1 deletion.
44 changes: 43 additions & 1 deletion admin/settings/grades.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@

$temp->add(new admin_setting_configtext('gradereport_mygradeurl', new lang_string('externalurl', 'grades'),
new lang_string('externalurl_desc', 'grades'), ''));

// Enable grade penalty or not.
$temp->add(new admin_setting_configcheckbox('gradepenalty_enabled',
new lang_string('gradepenalty_enabled', 'grades'),
new lang_string('gradepenalty_enabled_help', 'grades'), 0));
}
$ADMIN->add('grades', $temp);

Expand Down Expand Up @@ -221,5 +226,42 @@
}
}

} // end of speedup
// Penalty.
if (get_config('core', 'gradepenalty_enabled')) {
$ADMIN->add('grades', new admin_category('gradepenalty', new lang_string('gradepenalty', 'grades')));

// Supported modules.
$modules = core_grades\local\penalty\manager::get_supported_modules();
if (!empty($modules)) {
$temp = new admin_settingpage('supportedplugins', new lang_string('gradepenalty_supportedplugins', 'grades'),
'moodle/grade:manage');

$options = [];
foreach ($modules as $module) {
$options[$module] = new lang_string('modulename', $module);
}
$temp->add(new admin_setting_configmultiselect('gradepenalty_supportedplugins',
new lang_string('gradepenalty_supportedplugins', 'grades'),
new lang_string('gradepenalty_supportedplugins_help', 'grades'), [], $options));

$ADMIN->add('gradepenalty', $temp);
}

// External page to manage the penalty plugins.
$temp = new admin_externalpage(
'managepenaltyplugins',
get_string('managepenaltyplugins', 'grades'),
new moodle_url('/grade/penalty/manage_penalty_plugins.php'),
'moodle/grade:manage'
);
$ADMIN->add('gradepenalty', $temp);

// Settings from each penalty plugin.
foreach (core_component::get_plugin_list('gradepenalty') as $plugin => $plugindir) {
// Include all the settings commands for this plugin if there are any.
if (file_exists($plugindir.'/settings.php')) {
include($plugindir.'/settings.php');
}
}
}
} // end of speedup
35 changes: 35 additions & 0 deletions grade/classes/hook/after_penalty_applied.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?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 core_grades\hook;

use Psr\EventDispatcher\StoppableEventInterface;

/**
* Hook after penalty is applied.
*
* This hook will be dispatched after the penalty is applied to the grade.
* Allow plugins to perform further action after penalty is applied.
*
* @package core_grades
* @copyright 2024 Catalyst IT Australia Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allow plugins to perform further action after penalty is applied.')]
#[\core\attribute\tags('grade')]
class after_penalty_applied implements StoppableEventInterface {
use grade_penalty_handler;
}
62 changes: 62 additions & 0 deletions grade/classes/hook/before_penalty_applied.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?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 core_grades\hook;

use core\plugininfo\gradepenalty;
use Psr\EventDispatcher\StoppableEventInterface;

/**
* Hook before penalty is applied.
*
* This hook will be dispatched before the penalty is applied to the grade.
* Allow plugins to do penalty calculations.
*
* @package core_grades
* @copyright 2024 Catalyst IT Australia Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allow plugins to do penalty calculations.')]
#[\core\attribute\tags('grade')]
class before_penalty_applied implements StoppableEventInterface {
use grade_penalty_handler;

/**
* Set deducted grade.
* We restrict the hook to be used by grade penalty plugins only.
*
* @param string $pluginname the plugin name
* @param float $deductedgrade The deducted grade
*/
public function apply_penalty(string $pluginname, float $deductedgrade): void {
// Check if the plugin is enabled.
if (gradepenalty::is_plugin_enabled($pluginname)) {
// Aggregate the deducted grade.
$this->deductedgrade += $deductedgrade;

// Update the final grade.
$this->gradeafterpenalty = $this->gradebeforepenalty - $this->deductedgrade;
// Cannot be negative.
$this->gradeafterpenalty = max($this->gradeitem->grademin, $this->gradeafterpenalty);
// Cannot be greater than the maximum grade.
$this->gradeafterpenalty = min($this->gradeafterpenalty, $this->gradeitem->grademax);

// Update the deducted percentage.
// The percentage can be used by modules to calculate penalty for their own grade, such as assign_grade.
$this->deductedpercentage = $this->deductedgrade / $this->gradebeforepenalty * 100;
}
}
}
101 changes: 101 additions & 0 deletions grade/classes/hook/grade_penalty_handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?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 core_grades\hook;

use core\hook\stoppable_trait;
use grade_item;

/**
* Trait for providing the common methods for the grade penalty hooks.
*
* @package core_grades
* @copyright 2024 Catalyst IT Australia Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait grade_penalty_handler {
use stoppable_trait;

/**
* Constructor for the hook.
*
* @param int $userid The user id
* @param grade_item $gradeitem The grade item object
* @param int $submissiondate the submission date
* @param int $duedate the due date
* @param float $gradebeforepenalty original final grade
* @param float $deductedpercentage the deducted percentage from final grade
* @param float $deductedgrade the deducted grade
* @param ?float $gradeafterpenalty grade after deduction
*/
public function __construct(
/** @var int The user id */
public readonly int $userid,
/** @var grade_item $gradeitem the grade item object*/
public readonly grade_item $gradeitem,
/** @var float the submission date */
public readonly int $submissiondate,
/** @var float the due date */
public readonly int $duedate,
/** @var float original final grade */
private float $gradebeforepenalty,
/** @var int the deducted percentage from final grade */
private float $deductedpercentage = 0.0,
/** @var float the deducted grade */
private float $deductedgrade = 0.0,
/** @var ?float grade after deduction */
private ?float $gradeafterpenalty = null,
) {
if ($this->gradeafterpenalty === null) {
$this->gradeafterpenalty = $this->gradebeforepenalty;
}
}

/**
* Get grade before penalty is applied.
*
* @return float The penalized grade
*/
public function get_grade_before_penalty(): float {
return $this->gradebeforepenalty;
}

/**
* Get the penalized grade.
*
* @return float The penalized grade
*/
public function get_grade_after_penalty(): float {
return $this->gradeafterpenalty;
}

/**
* Get the deducted percentage.
*
*/
public function get_deducted_percentage(): float {
return $this->deductedpercentage;
}

/**
* Get the deducted grade.
*
*/
public function get_deducted_grade(): float {
return $this->deductedgrade;
}

}
136 changes: 136 additions & 0 deletions grade/classes/local/penalty/manager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?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 core_grades\local\penalty;

use core\di;
use core\hook;
use core_grades\hook\after_penalty_applied;
use core_grades\hook\before_penalty_applied;
use grade_item;

/**
* Manager class for grade penalty.
*
* @package core_grades
* @copyright 2024 Catalyst IT Australia Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager {
/**
* Lists of modules which support grade penalty feature.
*
* @return array list of supported modules.
*/
public static function get_supported_modules(): array {
$plugintype = 'mod';
$mods = \core_component::get_plugin_list($plugintype);
$supported = [];
foreach ($mods as $mod => $plugindir) {
if (plugin_supports($plugintype, $mod, FEATURE_GRADE_HAS_PENALTY)) {
$supported[] = $mod;
}
}
return $supported;
}

/**
* Whether penalty feature is enabled.
*
* @return bool if penalty is enabled
*/
public static function is_penalty_enabled(): bool {
return (bool) get_config('core', 'gradepenalty_enabled');
}

/**
* Whether penalty is enabled for a module.
*
* @param string $module the module name.
* @return bool if penalty is enabled for the module.
*/
public static function is_penalty_enabled_for_module(string $module): bool {
// Return false if the penalty feature is disabled.
if (!self::is_penalty_enabled()) {
return false;
}

// Check if the module is in the enable list.
$supportedmodules = get_config('core', 'gradepenalty_supportedplugins');
if (!in_array($module, explode(',', $supportedmodules))) {
return false;
}
return true;
}

/**
* This function should be run after a raw grade is updated/created for a user.
*
* @param int $userid ID of user
* @param grade_item $gradeitem the grade item object
* @param int $submissiondate submission date
* @param int $duedate due date
* @param bool $previewonly do not update the grade if true
* @return float returns the deducted percentage.
*/
public static function apply_penalty(int $userid, grade_item $gradeitem,
int $submissiondate, int $duedate, bool $previewonly = false): float {
// If the grade item belong to a supported module.
if (!self::is_penalty_enabled_for_module($gradeitem->itemmodule)) {
return 0;
}

// Check if there is any existing grade.
$grade = $gradeitem->get_final($userid);
if (!$grade || !$grade->rawgrade) {
debugging('No raw grade found for user ' . $userid . ' and grade item ' . $gradeitem->id, DEBUG_DEVELOPER);
return 0;
} else if ($grade->rawgrade <= 0 || $grade->finalgrade <= 0) {
// There is no penalty for zero or negative grades.
return 0;
} else if ($grade->overridden > 0 || $grade->locked > 0) {
// Do not apply penalty if the grade is overridden or locked.
// We may need a separate setting to allow penalty for overridden grades.
return 0;
}

// Hook for plugins to calculate the penalty.
$beforepenaltyhook = new before_penalty_applied($userid, $gradeitem, $submissiondate, $duedate, $grade->finalgrade);
di::get(hook\manager::class)->dispatch($beforepenaltyhook);

// Apply the penalty to the grade.
if (!$previewonly) {
// Update the final grade after the penalty is applied.
$gradeitem->update_raw_grade($userid, $beforepenaltyhook->get_grade_after_penalty());

// Hook for plugins to process further after the penalty is applied to the grade.
$afterpenaltyhook = new after_penalty_applied($userid, $gradeitem, $submissiondate, $duedate,
$beforepenaltyhook->get_grade_before_penalty(),
$beforepenaltyhook->get_deducted_percentage(),
$beforepenaltyhook->get_deducted_grade(),
$beforepenaltyhook->get_grade_after_penalty()
);
di::get(hook\manager::class)->dispatch($afterpenaltyhook);
}

// Clamp the deducted percentage between 0% and 100%.
$deductedpercentage = $beforepenaltyhook->get_deducted_percentage();
$deductedpercentage = max(0, $deductedpercentage);
$deductedpercentage = min(100, $deductedpercentage);

return $deductedpercentage;
}
}
Loading

0 comments on commit 291f2f0

Please sign in to comment.