Skip to content

Commit 8ef9090

Browse files
authored
CTP-4088 Marker view (#12)
1 parent d42a564 commit 8ef9090

File tree

6 files changed

+277
-53
lines changed

6 files changed

+277
-53
lines changed

.github/workflows/moodle-ci.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ jobs:
7272
echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV
7373
7474
- name: Install moodle-plugin-ci
75-
run: moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1
75+
run: |
76+
moodle-plugin-ci add-plugin -b main --clone https://github.com/ucl-isd/moodle-local_assess_type.git
77+
moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1
7678
env:
7779
DB: ${{ matrix.database }}
7880
MOODLE_BRANCH: ${{ matrix.moodle-branch }}

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# moodle-block_my_feedback
2+
3+
This block shows students recent feedback across moodle courses.
4+
5+
It currently supports assignment, quiz, and turnitin.
6+
7+
It outputs some UCL specific elements which you can remove from mustache template.
8+
9+
# Version 2.0
10+
11+
For users who's role archetype is editingteacher the block shows a marker view, with summative assessments that require marking.
12+
13+
The marker view uses https://github.com/ucl-isd/moodle-local_assess_type to get which assessments have been set as summative.
14+
15+

block_my_feedback.php

+216-15
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
use core\context\user;
1818
use core_course\external\course_summary_exporter;
19+
use local_assess_type\assess_type; // UCL plugin.
1920
use mod_quiz\question\display_options;
2021

2122
/**
@@ -35,9 +36,10 @@ class block_my_feedback extends block_base {
3536
public function init() {
3637
global $USER;
3738

38-
// If $USER->firstname is not set yet do not try to use it.
3939
if (!isset($USER->firstname)) {
4040
$this->title = get_string('pluginname', 'block_my_feedback');
41+
} else if (self::is_teacher()) {
42+
$this->title = get_string('markingfor', 'block_my_feedback').' '.$USER->firstname;
4143
} else {
4244
$this->title = get_string('feedbackfor', 'block_my_feedback').' '.$USER->firstname;
4345
}
@@ -59,35 +61,233 @@ public function get_content(): stdClass {
5961
$this->content->footer = '';
6062

6163
$template = new stdClass();
62-
$template->feedback = $this->fetch_feedback($USER);
6364

64-
// Hide the block when no content.
65-
if (!$template->feedback) {
66-
return $this->content;
65+
if (self::is_teacher()) {
66+
// Teacher content.
67+
$template->mods = self::fetch_marking($USER);
68+
} else {
69+
// Student content.
70+
$template->mods = $this->fetch_feedback($USER);
71+
$template->showfeedbacktrackerlink = true;
72+
}
73+
74+
if (isset($template->mods)) {
75+
$this->content->text = $OUTPUT->render_from_template('block_my_feedback/content', $template);
6776
}
6877

69-
$this->content->text = $OUTPUT->render_from_template('block_my_feedback/content', $template);
7078
return $this->content;
7179
}
7280

81+
/**
82+
* Return if user has archetype editingteacher.
83+
*
84+
*/
85+
public static function is_teacher(): bool {
86+
global $DB, $USER;
87+
// Get id's from role where archetype is editingteacher.
88+
$roles = $DB->get_fieldset('role', 'id', ['archetype' => 'editingteacher']);
89+
90+
// Check if user has editingteacher role on any courses.
91+
list($roles, $params) = $DB->get_in_or_equal($roles, SQL_PARAMS_NAMED);
92+
$params['userid'] = $USER->id;
93+
$sql = "SELECT id
94+
FROM {role_assignments}
95+
WHERE userid = :userid
96+
AND roleid $roles";
97+
return $DB->record_exists_sql($sql, $params);
98+
}
99+
100+
/**
101+
* Return marking for a user.
102+
*
103+
* @param stdClass $user
104+
*/
105+
public static function fetch_marking(stdClass $user): ?array {
106+
// User courses.
107+
$courses = enrol_get_all_users_courses($user->id, false, ['enddate']);
108+
// Marking.
109+
$marking = [];
110+
111+
foreach ($courses as $course) {
112+
// Skip hidden courses.
113+
if (!$course->visible) {
114+
continue;
115+
}
116+
// Skip none current course.
117+
if (!self::is_course_current($course)) {
118+
continue;
119+
}
120+
// Skip if no summative assessments.
121+
if (!$summatives = assess_type::get_assess_type_records_by_courseid($course->id, assess_type::ASSESS_TYPE_SUMMATIVE)) {
122+
continue;
123+
}
124+
125+
$modinfo = get_fast_modinfo($course->id);
126+
$mods = $modinfo->get_cms();
127+
// Mod ids array to check cmid exists.
128+
$cmids = [];
129+
foreach ($mods as $mod) {
130+
$cmids[] = $mod->id;
131+
}
132+
133+
// Loop through assessments for this course.
134+
foreach ($summatives as $summative) {
135+
136+
// Check this is a course mod.
137+
if ($summative->cmid != 0) {
138+
// Skip mods where cmid is not in the course.
139+
if (!in_array($summative->cmid, $cmids)) {
140+
continue;
141+
}
142+
143+
// Begin to build mod data for template.
144+
$cmid = $summative->cmid;
145+
$mod = $modinfo->get_cm($cmid);
146+
147+
// Skip hidden mods.
148+
if (!$mod->visible) {
149+
continue;
150+
}
151+
152+
// Template.
153+
$assess = new stdClass;
154+
$assess->cmid = $cmid;
155+
$assess->modname = $mod->modname;
156+
// Get due date and require marking.
157+
$assess = self::get_mod_data($mod, $assess);
158+
159+
// Check mod has require marking (only set when there is a due date).
160+
if (isset($assess->requiremarking)) {
161+
// TODO - what is expensive here that we can do after sort and limit?
162+
$assess->name = $mod->name;
163+
$assess->coursename = $course->fullname;
164+
$assess->url = new moodle_url('/mod/'. $mod->modname. '/view.php', ['id' => $cmid]);
165+
$assess->icon = course_summary_exporter::get_course_image($course);
166+
$marking[] = $assess;
167+
}
168+
}
169+
}
170+
}
171+
172+
// Sort and return data.
173+
if ($marking) {
174+
usort($marking, function ($a, $b) {
175+
return $a->unixtimestamp <=> $b->unixtimestamp;
176+
});
177+
178+
return array_slice($marking, 0, 5);
179+
}
180+
return null;
181+
}
182+
183+
/**
184+
* Return mod data - due date & require marking.
185+
*
186+
* TODO - turnitin, quiz.
187+
*
188+
* @param cm_info $mod
189+
* @param stdClass $assess
190+
*/
191+
public static function get_mod_data($mod, $assess): ?stdClass {
192+
global $CFG;
193+
// Mods have different fields for due date, and require marking.
194+
switch ($mod->modname) {
195+
case 'assign':
196+
197+
// Check mod due date is relevant.
198+
$duedate = self::duedate_in_range($mod->customdata['duedate']);
199+
if (!$duedate) {
200+
return null;
201+
}
202+
203+
// Add dates.
204+
$assess->unixtimestamp = $duedate;
205+
$assess->duedate = date('jS M', $duedate);
206+
207+
// Require marking.
208+
require_once($CFG->dirroot.'/mod/assign/locallib.php');
209+
$context = context_module::instance($mod->id);
210+
$assignment = new assign($context, $mod, $mod->course);
211+
$assess->requiremarking = $assignment->count_submissions_need_grading();
212+
if (!$assess->requiremarking) {
213+
return null;
214+
}
215+
$assess->markingurl = new moodle_url('/mod/'. $mod->modname. '/view.php',
216+
['id' => $assess->cmid, 'action' => 'grader']
217+
);
218+
219+
// Return template data.
220+
return $assess;
221+
222+
// TODO - quiz - 'timeclose' ?.
223+
case 'quiz':
224+
return null;
225+
// TODO - turnitin.
226+
default:
227+
return null;
228+
}
229+
}
230+
231+
/**
232+
* Return if course has started (startdate) and has not ended (enddate).
233+
*
234+
* @param stdClass $course
235+
*/
236+
public static function is_course_current(stdClass $course): bool {
237+
// Start date.
238+
if ($course->startdate > time()) {
239+
return false; // Before the start date.
240+
}
241+
242+
// End date.
243+
if (isset($course->enddate)) {
244+
if ($course->enddate == 0) {
245+
return true; // Enddate is set to 0 when no end date, show course.
246+
}
247+
// Past course enddate.
248+
// Note - UCL add 3 mouths for late summer assessments, so course can end before assessments are due.
249+
if (time() > strtotime('+3 month', $course->enddate)) {
250+
return false;
251+
}
252+
}
253+
return true; // All good, show course.
254+
}
255+
256+
/**
257+
* Return if a due date in the date range.
258+
*
259+
* @param int $duedate
260+
*/
261+
public static function duedate_in_range(int $duedate): ?int {
262+
// Only show dates within UCL limits for marking.
263+
$startdate = strtotime('-2 month'); // Longer time to try retain overdue marking at the top.
264+
$cutoffdate = strtotime('+1 month');
265+
// If duedate is beyond cutoff.
266+
if ($duedate > $cutoffdate) {
267+
return false;
268+
}
269+
// If duedate is too far in the past.
270+
if ($duedate < $startdate) {
271+
return false;
272+
}
273+
return $duedate;
274+
}
275+
73276
/**
74277
* Get my feedback call for a user.
75278
*
76279
* Return users 5 most recent feedbacks.
77280
* @param stdClass $user
78281
* @return array feedback items.
79-
* @throws coding_exception
80-
* @throws dml_exception
81-
* @throws moodle_exception
82282
*/
83-
public function fetch_feedback($user): array {
283+
public function fetch_feedback($user): ?array {
84284
global $DB;
85285

86286
$submissions = $this->get_submissions($user);
87287

88288
// No feedback.
89289
if (!$submissions) {
90-
return [];
290+
return null;
91291
}
92292

93293
// Template data for mustache.
@@ -108,9 +308,9 @@ public function fetch_feedback($user): array {
108308

109309
$feedback = new stdClass();
110310
$feedback->id = $f->gradeid;
111-
$feedback->date = date('jS F', $f->lastmodified);
112-
$feedback->activityname = $f->name;
113-
$feedback->link = new moodle_url('/mod/'.$f->modname.'/view.php', ['id' => $f->cmid]);
311+
$feedback->releaseddate = date('jS M', $f->lastmodified);
312+
$feedback->name = $f->name;
313+
$feedback->url = new moodle_url('/mod/'.$f->modname.'/view.php', ['id' => $f->cmid]);
114314

115315
// Course.
116316
$course = $DB->get_record('course', ['id' => $f->course]);
@@ -138,7 +338,8 @@ public function fetch_feedback($user): array {
138338

139339
$template->feedback[] = $feedback;
140340
}
141-
return $template->feedback;
341+
342+
return $template->feedback ?: null;
142343
}
143344

144345
/**

lang/en/block_my_feedback.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@
2424

2525
$string['feedbackfor'] = 'Feedback for';
2626
$string['feedbackreport'] = "Feedback tracker";
27-
$string['feedbackreportdescription'] = "Assessments, feedback, and marks from your courses in UCL Moodle.";
27+
$string['feedbackreportdescription'] = "Assessments, feedback, and marks for all your courses in UCL Moodle.";
28+
$string['markingfor'] = 'Marking for';
2829
$string['my_feedback:addinstance'] = 'Add my feedback block';
2930
$string['my_feedback:myaddinstance'] = 'Add my feedback block';
30-
$string['norecentfeedback'] = "Recent feedback you've received will show here.";
3131
$string['pluginname'] = 'My feedback';
3232
$string['privacy:metadata'] = 'My Feedback does not store any personal data.';
33+
$string['t:due'] = "Due";
34+
$string['t:needmarking'] = "to mark";
35+

0 commit comments

Comments
 (0)