Skip to content

Commit

Permalink
MDL-68241 mod_h5pactivity: add grading attempts options
Browse files Browse the repository at this point in the history
  • Loading branch information
ferranrecio committed May 14, 2020
1 parent 206e179 commit e28b406
Show file tree
Hide file tree
Showing 20 changed files with 1,754 additions and 68 deletions.
Expand Up @@ -41,7 +41,7 @@ protected function define_structure() {
// Replace with the attributes and final elements that the element will handle.
$attributes = ['id'];
$finalelements = ['name', 'timecreated', 'timemodified', 'intro',
'introformat', 'grade', 'displayoptions'];
'introformat', 'grade', 'displayoptions', 'enabletracking', 'grademethod'];
$root = new backup_nested_element('h5pactivity', $attributes, $finalelements);

$attempts = new backup_nested_element('attempts');
Expand Down
86 changes: 76 additions & 10 deletions mod/h5pactivity/classes/local/attempt.php
Expand Up @@ -36,18 +36,22 @@
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class attempt {

/** @var stdClass the h5pactivity_attempts record. */
private $record;

/** @var boolean if the DB statement has been updated. */
private $scoreupdated = false;

/**
* Create a new attempt object.
*
* @param stdClass $record the h5pactivity_attempts record
*/
protected function __construct(stdClass $record) {
public function __construct(stdClass $record) {
$this->record = $record;
$this->results = null;
}
Expand Down Expand Up @@ -199,12 +203,15 @@ public function save_statement(statement $statement, string $subcontent = ''): b
}

// If no subcontent provided, results are propagated to the attempt itself.
if (empty($subcontent) && $record->rawscore) {
$this->record->rawscore = $record->rawscore;
$this->record->maxscore = $record->maxscore;
$this->record->duration = $record->duration;
$this->record->completion = $record->completion ?? null;
$this->record->success = $record->success ?? null;
if (empty($subcontent)) {
$this->set_duration($record->duration);
$this->set_completion($record->completion ?? null);
$this->set_success($record->success ?? null);
// If Maxscore is not empty means that the rawscore is valid (even if it's 0)
// and scaled score can be calculated.
if ($record->maxscore) {
$this->set_score($record->rawscore, $record->maxscore);
}
}
// Refresh current attempt.
return $this->save();
Expand All @@ -218,9 +225,56 @@ public function save_statement(statement $statement, string $subcontent = ''): b
public function save(): bool {
global $DB;
$this->record->timemodified = time();
// Calculate scaled score.
if ($this->scoreupdated) {
if (empty($this->record->maxscore)) {
$this->record->scaled = 0;
} else {
$this->record->scaled = $this->record->rawscore / $this->record->maxscore;
}
}
return $DB->update_record('h5pactivity_attempts', $this->record);
}

/**
* Set the attempt score.
*
* @param int|null $rawscore the attempt rawscore
* @param int|null $maxscore the attempt maxscore
*/
public function set_score(?int $rawscore, ?int $maxscore): void {
$this->record->rawscore = $rawscore;
$this->record->maxscore = $maxscore;
$this->scoreupdated = true;
}

/**
* Set the attempt duration.
*
* @param int|null $duration the attempt duration
*/
public function set_duration(?int $duration): void {
$this->record->duration = $duration;
}

/**
* Set the attempt completion.
*
* @param int|null $completion the attempt completion
*/
public function set_completion(?int $completion): void {
$this->record->completion = $completion;
}

/**
* Set the attempt success.
*
* @param int|null $success the attempt success
*/
public function set_success(?int $success): void {
$this->record->success = $success;
}

/**
* Delete the current attempt results from the DB.
*/
Expand Down Expand Up @@ -376,15 +430,15 @@ public function get_maxscore(): int {
* @return int the rawscore value
*/
public function get_rawscore(): int {
return $this->record->maxscore;
return $this->record->rawscore;
}

/**
* Return the attempt duration.
*
* @return int the duration value
* @return int|null the duration value
*/
public function get_duration(): int {
public function get_duration(): ?int {
return $this->record->duration;
}

Expand All @@ -405,4 +459,16 @@ public function get_completion(): ?int {
public function get_success(): ?int {
return $this->record->success;
}

/**
* Return if the attempt has been modified.
*
* Note: adding a result only add track information unless the statement does
* not specify subcontent. In this case this will update also the statement.
*
* @return bool if the attempt score have been modified
*/
public function get_scoreupdated(): bool {
return $this->scoreupdated;
}
}
214 changes: 214 additions & 0 deletions mod/h5pactivity/classes/local/grader.php
@@ -0,0 +1,214 @@
<?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/>.

/**
* H5P activity grader class.
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace mod_h5pactivity\local;

use context_module;
use cm_info;
use moodle_recordset;
use stdClass;

/**
* Class for handling H5P activity grading.
*
* @package mod_h5pactivity
* @since Moodle 3.9
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class grader {

/** @var stdClass course_module record. */
private $instance;

/** @var string idnumber course_modules idnumber. */
private $idnumber;

/**
* Class contructor.
*
* @param stdClass $instance H5Pactivity instance object
* @param string $idnumber course_modules idnumber
*/
public function __construct(stdClass $instance, string $idnumber = '') {
$this->instance = $instance;
$this->idnumber = $idnumber;
}

/**
* Delete grade item for given mod_h5pactivity instance.
*
* @return int Returns GRADE_UPDATE_OK, GRADE_UPDATE_FAILED, GRADE_UPDATE_MULTIPLE or GRADE_UPDATE_ITEM_LOCKED
*/
public function grade_item_delete(): ?int {
global $CFG;
require_once($CFG->libdir.'/gradelib.php');

return grade_update('mod/h5pactivity', $this->instance->course, 'mod', 'h5pactivity',
$this->instance->id, 0, null, ['deleted' => 1]);
}

/**
* Creates or updates grade item for the given mod_h5pactivity instance.
*
* @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
* @return int 0 if ok, error code otherwise
*/
public function grade_item_update($grades = null): int {
global $CFG;
require_once($CFG->libdir.'/gradelib.php');

$item = [];
$item['itemname'] = clean_param($this->instance->name, PARAM_NOTAGS);
$item['gradetype'] = GRADE_TYPE_VALUE;
if (!empty($this->idnumber)) {
$item['idnumber'] = $this->idnumber;
}

if ($this->instance->grade > 0) {
$item['gradetype'] = GRADE_TYPE_VALUE;
$item['grademax'] = $this->instance->grade;
$item['grademin'] = 0;
} else if ($this->instance->grade < 0) {
$item['gradetype'] = GRADE_TYPE_SCALE;
$item['scaleid'] = -$this->instance->grade;
} else {
$item['gradetype'] = GRADE_TYPE_NONE;
}

if ($grades === 'reset') {
$item['reset'] = true;
$grades = null;
}

return grade_update('mod/h5pactivity', $this->instance->course, 'mod',
'h5pactivity', $this->instance->id, 0, $grades, $item);
}

/**
* Update grades in the gradebook.
*
* @param int $userid Update grade of specific user only, 0 means all participants.
*/
public function update_grades(int $userid = 0): void {
// Scaled and none grading doesn't have grade calculation.
if ($this->instance->grade <= 0) {
$this->grade_item_update();
return;
}
// Populate array of grade objects indexed by userid.
$grades = $this->get_user_grades_for_gradebook($userid);

if (!empty($grades)) {
$this->grade_item_update($grades);
} else {
$this->grade_item_update();
}
}

/**
* Get an updated list of user grades and feedback for the gradebook.
*
* @param int $userid int or 0 for all users
* @return array of grade data formated for the gradebook api
* The data required by the gradebook api is userid,
* rawgrade,
* feedback,
* feedbackformat,
* usermodified,
* dategraded,
* datesubmitted
*/
private function get_user_grades_for_gradebook(int $userid = 0): array {
$grades = [];

// In case of using manual grading this update must delete previous automatic gradings.
if ($this->instance->grademethod == manager::GRADEMANUAL || !$this->instance->enabletracking) {
return $this->get_user_grades_for_deletion($userid);
}

$manager = manager::create_from_instance($this->instance);

$scores = $manager->get_users_scaled_score($userid);
if (!$scores) {
return $grades;
}

// Maxgrade depends on the type of grade used:
// - grade > 0: regular quantitative grading.
// - grade = 0: no grading.
// - grade < 0: scale used.
$maxgrade = floatval($this->instance->grade);

// Convert scaled scores into gradebok compatible objects.
foreach ($scores as $userid => $score) {
$grades[$userid] = [
'userid' => $userid,
'rawgrade' => $maxgrade * $score->scaled,
'dategraded' => $score->timemodified,
'datesubmitted' => $score->timemodified,
];
}

return $grades;
}

/**
* Get an deletion list of user grades and feedback for the gradebook.
*
* This method is used to delete all autmatic gradings when grading method is set to manual.
*
* @param int $userid int or 0 for all users
* @return array of grade data formated for the gradebook api
* The data required by the gradebook api is userid,
* rawgrade (null to delete),
* dategraded,
* datesubmitted
*/
private function get_user_grades_for_deletion (int $userid = 0): array {
$grades = [];

if ($userid) {
$grades[$userid] = [
'userid' => $userid,
'rawgrade' => null,
'dategraded' => time(),
'datesubmitted' => time(),
];
} else {
$manager = manager::create_from_instance($this->instance);
$users = get_enrolled_users($manager->get_context(), 'mod/h5pactivity:submit');
foreach ($users as $user) {
$grades[$user->id] = [
'userid' => $user->id,
'rawgrade' => null,
'dategraded' => time(),
'datesubmitted' => time(),
];
}
}
return $grades;
}
}

0 comments on commit e28b406

Please sign in to comment.