Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CTP-4088 Marker view #12

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 204 additions & 9 deletions block_my_feedback.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.


use core\context\user;
use core_course\external\course_summary_exporter;
use local_assess_type\assess_type; // TODO - add in requires...
use mod_quiz\question\display_options;
require_once($CFG->dirroot . '/mod/assign/locallib.php');

/**
* Block definition class for the block_my_feedback plugin.
Expand All @@ -40,6 +43,9 @@ public function init() {
$this->title = get_string('pluginname', 'block_my_feedback');
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
} else {
$this->title = get_string('feedbackfor', 'block_my_feedback').' '.$USER->firstname;
if (self::is_teacher()) {
$this->title = get_string('markingfor', 'block_my_feedback').' '.$USER->firstname;
}
}
}
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -59,15 +65,180 @@ public function get_content(): stdClass {
$this->content->footer = '';

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

// Hide the block when no content.
if (!$template->feedback) {
return $this->content;
if (self::is_teacher()) {
// Teacher content.
$template->marking = $this->fetch_marking($USER);
} else {
// Student content.
$template->feedback = $this->fetch_feedback($USER);
$template->showfeedbacktrackerlink = true;
}

if (isset($template->feedback) || isset($template->marking)) {
$this->content->text = $OUTPUT->render_from_template('block_my_feedback/content', $template);
}

return $this->content;
}


/**
* Return marking for a user.
*
* @param stdClass $user
*/
public function fetch_marking(stdClass $user): ?array {
global $DB, $OUTPUT;
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
// 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)) {
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
continue;
}
// Skip if no summative assessments.
if (!$summatives = assess_type::get_assess_type_records_by_courseid($course->id, "1")) {
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
continue;
}

stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
$modinfo = get_fast_modinfo($course->id);

foreach ($summatives as $summative) {

// Check this is a course mod.
if (isset($summative->cmid)) {
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
$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;
// 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).
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
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;
}

/**
* Return mod data - due date & require marking.
*
* TODO - turnitin, quiz.
*
* @param stdClass $mod
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
* @param stdClass $course
*/
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
public function get_mod_data($mod, $assess) {
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
// Mods have different feilds for due date, and require marking.
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
switch ($mod->modname) {
case 'assign':

// Check mod due date is relevant.
$duedate = self::duedate_in_range($mod->customdata['duedate']);
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
if (!$duedate) {
return false;
}

// Add dates.
$assess->unixtimestamp = $duedate;
$assess->duedate = date('jS F y', $duedate);

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

// TODO - quiz
case 'quiz':
// return 'timeclose';
return false;
// TODO - turnitin
default:
return false;
}
}

/**
* Return if course has started (startdate) and has not ended (enddate).
*
* @param stdClass $course
*/
public function is_course_current(stdClass $course): bool {
// Start date.
if ($course->startdate > time()){
return false; // Before the start date.
}

// End date.
if (isset($course->enddate)) {
if ($course->enddate == 0) {
return true; // Enddate is set to 0 when no end date, show course.
}
if (time() > $course->enddate){
return false; // After the end date.
}
}
return true; // All good, show course.
}
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved

$this->content->text = $OUTPUT->render_from_template('block_my_feedback/content', $template);
return $this->content;
/**
* Return if a due date in the date range.
*
* @param int $duedate
*/
public function duedate_in_range(int $duedate): ?int {
// Only show dates within a month.
$past = strtotime('-1 month');
$future = strtotime('+1 month');
// If due date is too far in the future.
if ($duedate > $future) {
return false;
}
// If due date is too far in the past.
if ($duedate < $past) {
return false;
}
return $duedate;
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -80,14 +251,14 @@ public function get_content(): stdClass {
* @throws dml_exception
* @throws moodle_exception
*/
public function fetch_feedback($user): array {
public function fetch_feedback($user): ?array {
global $DB;

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

// No feedback.
if (!$submissions) {
return [];
return null;
}

// Template data for mustache.
Expand Down Expand Up @@ -138,7 +309,11 @@ public function fetch_feedback($user): array {

$template->feedback[] = $feedback;
}
return $template->feedback;

if ($template->feedback) {
return $template->feedback;
}
return null;
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
}
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved

/**
Expand Down Expand Up @@ -228,6 +403,26 @@ public function show_quiz_submission(stdClass $submission): bool {
return false;
}

/**
* Return if user has archetype editingteacher.
*
*/
public static function is_teacher(): bool {
global $DB, $USER;

// Get id's from role where archetype is editingteacher.
$roles = $DB->get_fieldset('role', 'id', ['archetype' => 'editingteacher']);

// Check if user has editingteacher role on any courses.
list($roles, $params) = $DB->get_in_or_equal($roles, SQL_PARAMS_NAMED);
$params['userid'] = $USER->id;
$sql = "SELECT id
FROM {role_assignments}
WHERE userid = :userid
AND roleid $roles";
return $DB->record_exists_sql($sql, $params);
}

/**
* Defines in which pages this block can be added.
*
Expand Down
8 changes: 6 additions & 2 deletions lang/en/block_my_feedback.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@

$string['feedbackfor'] = 'Feedback for';
$string['feedbackreport'] = "Feedback tracker";
$string['feedbackreportdescription'] = "Assessments, feedback, and marks from your courses in UCL Moodle.";
$string['feedbackreportdescription'] = "Assessments, feedback, and marks for all your courses in UCL Moodle.";
$string['markingfor'] = 'Marking for';
$string['my_feedback:addinstance'] = 'Add my feedback block';
stuartlamour marked this conversation as resolved.
Show resolved Hide resolved
$string['my_feedback:myaddinstance'] = 'Add my feedback block';
$string['norecentfeedback'] = "Recent feedback you've received will show here.";
$string['pluginname'] = 'My feedback';
$string['privacy:metadata'] = 'My Feedback does not store any personal data.';

// Template strings.
$string['t:requiremarking'] = "require marking";
$string['t:due'] = "Due";
54 changes: 35 additions & 19 deletions templates/content.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,56 @@
}
}}

{{#feedback}}
<div class="mb-2">
<div class="media">
{{! TODO - Lots of duplication here, can we use the same template for marking and feedback? }}
{{#marking}}
<div class="d-flex">

<div class="mr-3 bg-cover rounded-circle d-inline-block" style="background-image: url('{{{icon}}}'); height: 4rem; width: 4rem;"></div>
<div class="mr-3 bg-cover bg-light rounded-circle flex-shrink-0" style="background-image: url('{{{icon}}}'); height: 4rem; width: 4rem;"></div>

<div class="media-body">
<small class="d-block">{{coursename}}</small>
<a href="{{{link}}}" class="font-weight-bold">{{activityname}}</a>
<div>
<div class="line-clamp-1 text-muted">
<small>{{{coursename}}}</small>
</div>
<a href="{{url}}" class="font-weight-bold">{{{name}}}</a>
<div>
<small>{{#str}} t:due, block_my_feedback {{/str}} {{duedate}}</small>
</div>

</div>
</div>

<div class='text-right'>
<small>{{#tutorname}}{{.}} · {{/tutorname}} {{date}}</small>
</div>
<div class='text-right'>
<small>{{requiremarking}} {{#str}} t:requiremarking, block_my_feedback {{/str}}</small>
</div>
<hr>
{{/marking}}

<hr>

</div>
{{/feedback}}
{{#feedback}}
<div class="d-flex">

<div class="mr-3 bg-cover rounded-circle flex-shrink-0" style="background-image: url('{{{icon}}}'); height: 4rem; width: 4rem;"></div>

{{^feedback}}
<p>{{#str}} norecentfeedback, block_my_feedback {{/str}}</p>
<div>
<div class="line-clamp-1 text-muted">
<small>{{coursename}}</small>
</div>
<a href="{{{link}}}" class="font-weight-bold">{{activityname}}</a>
</div>
</div>

<div class='text-right'>
<small>{{#tutorname}}{{.}} · {{/tutorname}} {{date}}</small>
</div>
<hr>
{{/feedback}}

{{#showfeedbacktrackerlink}}
<div class="bg-light pt-2 pb-3 px-3">
<span class="badge badge-warning">New</span>
<br>
<a href="{{{ config.wwwroot }}}/report/feedback_tracker/">
<i class="fa-solid fa-circle-arrow-right mr-2" aria-hidden="true"></i>{{#str}} feedbackreport, block_my_feedback {{/str}}
</a>
<br>
<small>{{#str}} feedbackreportdescription, block_my_feedback {{/str}}</small>
</div>
<hr>
<hr>
{{/showfeedbacktrackerlink}}
Loading