From df0b90543f8619d78f0b417b95d58828eaa6d6d3 Mon Sep 17 00:00:00 2001 From: Matthias Opitz Date: Tue, 7 Jan 2025 16:21:45 +0000 Subject: [PATCH] CTP-4123, CTP-4128 : add support for quiz and turnitin modules - showing turinitin parts separately --- block_my_feedback.php | 256 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 226 insertions(+), 30 deletions(-) diff --git a/block_my_feedback.php b/block_my_feedback.php index f7e1bb1..79951b0 100644 --- a/block_my_feedback.php +++ b/block_my_feedback.php @@ -108,6 +108,105 @@ public static function fetch_marking(stdClass $user): ?array { // Marking. $marking = []; + foreach ($courses as $course) { + // Skip hidden courses. + if (!$course->visible) { + continue; + } + // Skip none current course. + if (!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; + } + + $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; + } + + // 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; + } + + // Begin to build mod data for template. + $cmid = $summative->cmid; + $mod = $modinfo->get_cm($cmid); + + + // Skip hidden mods. + if (!$mod->visible) { + continue; + } + + // Template. + $assess = new stdClass; + $assess->cmid = $cmid; + $assess->modname = $mod->modname; + // Turnitin assessments may have multiple parts. WIP + if ($assess->modname === 'turnitintooltwo') { + $turnitinparts = self::get_turnitin_parts($mod); + foreach ($turnitinparts as $turnitinpart) { + $turnitin = clone $assess; + $turnitin->partid = $turnitinpart->id; + $turnitin = self::get_mod_data($mod, $turnitin); + + // Check mod has required marking (only set when there is a due date). + if (isset($turnitin->requiremarking)) { + // TODO - what is expensive here that we can do after sort and limit? + $turnitin->name = $mod->name . ' ' . $turnitinpart->partname;; + $turnitin->coursename = $course->fullname; + $turnitin->url = new moodle_url('/mod/'. $mod->modname. '/view.php', ['id' => $cmid]); + $turnitin->icon = course_summary_exporter::get_course_image($course); + $marking[] = $turnitin; + } + } + + } else { + // Get due date and require marking. + $assess = self::get_mod_data($mod, $assess); + } + + // Check mod has required 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); + $marking[] = $assess; + } + } + } + } + + // Sort and return data. + if ($marking) { + usort($marking, function ($a, $b) { + return $a->unixtimestamp <=> $b->unixtimestamp; + }); + + return array_slice($marking, 0, 5); + } + return null; + } + public static function fetch_marking0(stdClass $user): ?array { + // User courses. + $courses = enrol_get_all_users_courses($user->id, false, ['enddate']); + // Marking. + $marking = []; + foreach ($courses as $course) { // Skip hidden courses. if (!$course->visible) { @@ -183,49 +282,146 @@ 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 */ public static function get_mod_data($mod, $assess): ?stdClass { - global $CFG; - // Mods have different fields for due date, and require marking. + global $CFG, $DB; + // Mods have different fields for due date. switch ($mod->modname) { case 'assign': - // Check mod due date is relevant. $duedate = self::duedate_in_range($mod->customdata['duedate']); - if (!$duedate) { - return null; - } + break; + case 'quiz': + $record = $DB->get_record('quiz', ['id' => $mod->instance], 'timeclose'); + // Check if mod due date is present and relevant. + $duedate = isset($record->timeclose) ? self::duedate_in_range($record->timeclose) : false; + break; + case 'turnitintooltwo': + $record = $DB->get_record('turnitintooltwo_parts', ['id' => $assess->partid], 'dtdue'); + // Check if mod due date is present and relevant. + $duedate = isset($record->dtdue) ? self::duedate_in_range($record->dtdue) : false; + break; + default: + return null; + } + if (!$duedate) { + return null; + } - // Add dates. - $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; + // Add dates. + $assess->unixtimestamp = $duedate; + $assess->duedate = date('jS M', $duedate); + + // Require marking. + $assess->requiremarking = self::get_required_markings($mod); + 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; + + } + + /** + * Get the required markings for an assessment module. + * + * @param cm_info $mod + * @return int + * @throws dml_exception + */ + protected static function get_required_markings(cm_info $mod) { + global $CFG, $DB; + + // Assignments provide a way to count submissions that needs grading. + if ($mod->modname === 'assign') { + require_once($CFG->dirroot.'/mod/assign/locallib.php'); + $context = context_module::instance($mod->id); + $assignment = new assign($context, $mod, $mod->course); + return $assignment->count_submissions_need_grading(); + } + + // For modules other than assignments get the student IDs that have submissions. + if ($submissions = self::get_module_submissions($mod)) { + $sql = "SELECT DISTINCT gg.userid + FROM {grade_grades} gg + WHERE gg.itemid = :modid AND gg.finalgrade > :finalgrade"; + $params = ['modid' => $mod->id, 'finalgrade' => -1]; + + // Execute the query. + $studentids = $DB->get_fieldset_sql($sql, $params); + // Count and return all student IDs in submission that are not (yet) to be found in gradings. + $missinggrades = 0; + foreach ($submissions as $submitterid) { + if (!in_array($submitterid, $studentids)) { + $missinggrades++; } - $assess->markingurl = new moodle_url('/mod/'. $mod->modname. '/view.php', - ['id' => $assess->cmid, 'action' => 'grader'] - ); + } + return $missinggrades; + } + // No submissions - no missing grades. + return 0; + } - // Return template data. - return $assess; + /** + * Get an array of distinct student IDs with submissions for a given module. + * + * @param cm_info $mod + * @return array + */ + public static function get_module_submissions(cm_info $mod): array { + global $DB; - // TODO - quiz - 'timeclose' ?. - case 'quiz': - return null; - // TODO - turnitin. - default: - return null; + if ($mod) { + switch ($mod->modname) { + case 'assign': + // No need to support here, as assignments provide their own methods to count submissions and gradings. + return []; + case 'lesson': + $sql = "SELECT DISTINCT userid FROM {lesson_attempts} WHERE lessonid = :lessonid"; + $params = ['lessonid' => $mod->instance, 'correct' => 1]; + break; + case 'quiz': + $sql = "SELECT DISTINCT userid FROM {quiz_attempts} WHERE quiz = :quiz AND state = :state"; + $params = ['quiz' => $mod->instance, 'state' => 'finished']; + break; + case 'turnitintooltwo': + $sql = "SELECT DISTINCT userid FROM {turnitintooltwo_submissions} WHERE turnitintooltwoid = :turnitintooltwoid"; + $params = ['turnitintooltwoid' => $mod->instance]; + break; + case 'scorm': + return []; + case 'workshop': + $sql = "SELECT DISTINCT authorid FROM {workshop_submissions} WHERE workshopid = :workshopid"; + $params = ['workshopid' => $mod->instance]; + break; + default: + return []; + } + return $DB->get_fieldset_sql($sql, $params); } + return []; + } + + /** + * Get separate parts for a turnitin module. + * + * @param cm_info $mod + * @return array + * @throws dml_exception + */ + public static function get_turnitin_parts(cm_info $mod) { + global $DB; + + $sql = "SELECT * FROM {turnitintooltwo_parts} WHERE turnitintooltwoid = :iteminstance"; + $params = ['iteminstance' => $mod->instance]; + // Execute the query and return the result. + return $DB->get_records_sql($sql, $params); } /**