Skip to content

Commit

Permalink
CTP-4123, CTP-4128 : add support for quiz and turnitin modules (#13)
Browse files Browse the repository at this point in the history
* CTP-4123, CTP-4128 : add support for quiz and turnitin modules
- showing turinitin parts separately

* using feedback_tracker methods for getting due dates and missing markings

* added ':' to title just for tests. will be removed again

* Initial refactor

* Turnitin duedate

* Version bump

* using feedback_tracker::get_markingurl()

* using  for feedback_tracker::count_missing_grades()

* using report_feedback_tracker\local\helper::get_turnitin_parts()

* added missing comma

* removed stray semicolon

* refactored get_mod_data() to add_mod_data()

* refactored add_mod_data()

* removed unneccessary comment

* checking duedate range (again)

* updated use of feedback_tracker::count_missing_grades()

* only showing module types supported by feedback_tracker

* removed unneccessary partenthesis wrapping

---------

Co-authored-by: Matthias Opitz <[email protected]>
Co-authored-by: Stuart Lamour <[email protected]>
  • Loading branch information
3 people authored Jan 27, 2025
1 parent 72431dc commit 79966dd
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 102 deletions.
188 changes: 87 additions & 101 deletions block_my_feedback.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use core_course\external\course_summary_exporter;
use local_assess_type\assess_type; // UCL plugin.
use mod_quiz\question\display_options;
use report_feedback_tracker\local\admin as feedback_tracker; // UCL plugin.

/**
* Block definition class for the block_my_feedback plugin.
Expand Down Expand Up @@ -103,66 +104,73 @@ public static function is_teacher(): bool {
* @param stdClass $user
*/
public static function fetch_marking(stdClass $user): ?array {
global $DB;
// User courses.
$courses = enrol_get_all_users_courses($user->id, false, ['enddate']);
// Marking.
$marking = [];

foreach ($courses as $course) {
// Skip hidden courses.
if (!$course->visible) {
continue;
}
// Skip none current course.
if (!self::is_course_current($course)) {
// Skip hidden or non-current courses.
if (!$course->visible || !self::is_course_current($course)) {
continue;
}

// Skip if no summative assessments.
if (!$summatives = assess_type::get_assess_type_records_by_courseid($course->id, assess_type::ASSESS_TYPE_SUMMATIVE)) {
continue;
}

// Get course mod ids.
$modinfo = get_fast_modinfo($course->id);
$mods = $modinfo->get_cms();
// Mod ids array to check cmid exists.
$cmids = [];
foreach ($mods as $mod) {
$cmids[] = $mod->id;
}
$cmids = array_column($mods, 'id');

// Loop through assessments for this course.
foreach ($summatives as $summative) {

// Check this is a course mod.
if ($summative->cmid != 0) {
// Skip mods where cmid is not in the course.
if (!in_array($summative->cmid, $cmids)) {
continue;
}
// Skip if not a course module or cmid doesn't exist.
if ($summative->cmid == 0 || !in_array($summative->cmid, $cmids)) {
continue;
}

// Begin to build mod data for template.
$cmid = $summative->cmid;
$mod = $modinfo->get_cm($cmid);
// Begin to build mod data for template.
$cmid = $summative->cmid;
$mod = $modinfo->get_cm($cmid);

// Skip hidden mods.
if (!$mod->visible) {
continue;
}
// Skip hidden mods.
if (!$mod->visible) {
continue;
}

// Template.
$assess = new stdClass;
$assess->cmid = $cmid;
$assess->modname = $mod->modname;
// Get due date and require marking.
$assess = self::get_mod_data($mod, $assess);

// Check mod has require marking (only set when there is a due date).
if (isset($assess->requiremarking)) {
// TODO - what is expensive here that we can do after sort and limit?
$assess->name = $mod->name;
$assess->coursename = $course->fullname;
$assess->url = new moodle_url('/mod/'. $mod->modname. '/view.php', ['id' => $cmid]);
$assess->icon = course_summary_exporter::get_course_image($course);
// Template.
$assess = new stdClass;
$assess->cmid = $cmid;
$assess->modname = $mod->modname;
$assess->name = $mod->name;
$assess->coursename = $course->fullname;
$assess->url = new moodle_url('/mod/'. $mod->modname. '/view.php', ['id' => $cmid]);
// TODO - is this expensive?
// If so should we only do it once we know we want to display it?
$assess->icon = course_summary_exporter::get_course_image($course);

// Turnitin.
if ($assess->modname === 'turnitintooltwo') {
// Fetch parts.
$turnitinparts = \report_feedback_tracker\local\helper::get_turnitin_parts($mod->instance);
foreach ($turnitinparts as $turnitinpart) {
$turnitin = clone $assess;
$turnitin->partid = $turnitinpart->id;
// Check mod has duedate and require marking.
if (self::add_mod_data($mod, $turnitin)) {
$turnitin->name = $mod->name . ' ' . $turnitinpart->partname;
$marking[] = $turnitin;
}
}
} else {
// Check mod has duedate and require marking.
if (\report_feedback_tracker\local\helper::is_supported_module($mod->modname) &&
self::add_mod_data($mod, $assess)) {
$marking[] = $assess;
}
}
Expand All @@ -183,50 +191,38 @@ public static function fetch_marking(stdClass $user): ?array {
/**
* Return mod data - due date & require marking.
*
* TODO - turnitin, quiz.
*
* @param cm_info $mod
* @param stdClass $assess
* @return bool
*/
public static function get_mod_data($mod, $assess): ?stdClass {
global $CFG;
// Mods have different fields for due date, and require marking.
switch ($mod->modname) {
case 'assign':

// Check mod has custom data, due date, and due date is in range.
if (!$mod->customdata || !isset($mod->customdata['duedate'])
|| !self::duedate_in_range($mod->customdata['duedate'])) {
return null;
}
public static function add_mod_data(cm_info $mod, stdClass $assess): bool {
global $CFG, $DB;

// Add dates.
$duedate = $mod->customdata['duedate'];
$assess->unixtimestamp = $duedate;
$assess->duedate = date('jS M', $duedate);

// Require marking.
require_once($CFG->dirroot.'/mod/assign/locallib.php');
$context = context_module::instance($mod->id);
$assignment = new assign($context, $mod, $mod->course);
$assess->requiremarking = $assignment->count_submissions_need_grading();
if (!$assess->requiremarking) {
return null;
}
$assess->markingurl = new moodle_url('/mod/'. $mod->modname. '/view.php',
['id' => $assess->cmid, 'action' => 'grader']
);

// Return template data.
return $assess;

// TODO - quiz - 'timeclose' ?.
case 'quiz':
return null;
// TODO - turnitin.
default:
return null;
// Get duedate.
if ($mod->modname === 'turnitintooltwo') {
$duedate = $DB->get_field('turnitintooltwo_parts', 'dtdue', ['id' => $assess->partid], );
} else {
$duedate = feedback_tracker::get_duedate($mod);
}

// Check mod has due date, and due date is in range.
if (($duedate === 0) || !self::duedate_in_range($duedate)) {
return false;
}

// Return null if no duedate or no marking.
if (!$assess->requiremarking = feedback_tracker::count_missing_grades($mod)) {
return false;
}

// Add date for sorting and human-readable output.
$assess->unixtimestamp = $duedate;
$assess->duedate = date('jS M', $duedate);

$assess->markingurl = feedback_tracker::get_markingurl($mod);

// Return template data.
return true;
}

/**
Expand All @@ -235,23 +231,19 @@ public static function get_mod_data($mod, $assess): ?stdClass {
* @param stdClass $course
*/
public static function is_course_current(stdClass $course): bool {
// Start date.
// Check if the course has started.
if ($course->startdate > time()) {
return false; // Before the start date.
return false;
}

// End date.
if (isset($course->enddate)) {
if ($course->enddate == 0) {
return true; // Enddate is set to 0 when no end date, show course.
}
// Past course enddate.
// Note - UCL add 3 mouths for late summer assessments, so course can end before assessments are due.
if (time() > strtotime('+3 month', $course->enddate)) {
return false;
}
// Check if the course has ended (with a 3-month grace period).
if (isset($course->enddate) &&
$course->enddate != 0 && time() > strtotime('+3 month', $course->enddate)) {
return false;
}
return true; // All good, show course.

// Course is within the valid date range.
return true;
}

/**
Expand All @@ -260,17 +252,13 @@ public static function is_course_current(stdClass $course): bool {
* @param int $duedate
*/
public static function duedate_in_range(int $duedate): ?int {
// Only show dates within UCL limits for marking.
$startdate = strtotime('-2 month'); // Longer time to try retain overdue marking at the top.
$startdate = strtotime('-2 month');
$cutoffdate = strtotime('+1 month');
// If duedate is beyond cutoff.
if ($duedate > $cutoffdate) {
return false;
}
// If duedate is too far in the past.
if ($duedate < $startdate) {
return false;

if ($duedate < $startdate || $duedate > $cutoffdate) {
return null;
}

return $duedate;
}

Expand Down Expand Up @@ -349,7 +337,6 @@ public function fetch_feedback($user): ?array {
* @param stdClass $user
* @return array
* @throws coding_exception
* @throws dml_exception
*/
public function get_submissions($user) {
global $DB;
Expand Down Expand Up @@ -408,7 +395,6 @@ public function get_submissions($user) {
*
* @param stdClass $submission
* @return bool
* @throws dml_exception
*/
public function show_quiz_submission(stdClass $submission): bool {
global $DB;
Expand Down
3 changes: 2 additions & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@

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

$plugin->version = 2024112800; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2025011300; // The current plugin version (Date: YYYYMMDDXX).
$plugin->release = '2.0';
$plugin->maturity = MATURITY_ALPHA;
$plugin->requires = 2024100700; // Requires at least Moodle version 4.2.
$plugin->component = 'block_my_feedback'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = [
'local_assess_type' => 2024091300,
'report_feedback_tracker' => 2025010600,
];

0 comments on commit 79966dd

Please sign in to comment.