Skip to content

Commit

Permalink
StudentQuiz: add completion criteria #604186
Browse files Browse the repository at this point in the history
  • Loading branch information
danghieu1407 authored and timhunt committed Oct 23, 2023
1 parent bbe9e4a commit c2b2439
Show file tree
Hide file tree
Showing 15 changed files with 540 additions and 8 deletions.
4 changes: 3 additions & 1 deletion attempt.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@

$studentquiz = mod_studentquiz_load_studentquiz($cmid, $context->id);

global $USER;
global $USER, $COURSE;
$userid = $USER->id;

$questionusage = question_engine::load_questions_usage_by_activity($attempt->questionusageid);
Expand Down Expand Up @@ -147,6 +147,8 @@
}

$transaction->allow_commit();
// Update completion state.
\mod_studentquiz\completion\custom_completion::update_state($COURSE, $cm);

// Navigate accordingly. If no navigation button has been submitted, then there has been a question answer attempt.
if (optional_param('next', null, PARAM_BOOL)) {
Expand Down
2 changes: 1 addition & 1 deletion backup/moodle2/backup_studentquiz_stepslib.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ protected function define_structure() {
'allowedqtypes', 'aggregated', 'excluderoles', 'forcerating', 'forcecommenting',
'commentdeletionperiod', 'reportingemail', 'digesttype', 'digestfirstday', 'privatecommenting',
'opensubmissionfrom', 'closesubmissionfrom', 'openansweringfrom', 'closeansweringfrom',
'publishnewquestion'
'publishnewquestion', 'completionpoint', 'completionquestionpublished', 'completionquestionapproved'
]);

// StudentQuiz Attempt -> User, Question usage Id.
Expand Down
131 changes: 131 additions & 0 deletions classes/completion/custom_completion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?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/>.
declare(strict_types=1);

namespace mod_studentquiz\completion;

defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../../reportlib.php');

use core_completion\activity_custom_completion;
use mod_studentquiz_report;
use cm_info;
use stdClass;

/**
* Activity custom completion subclass for the data activity.
*
* Class for defining mod_studentquiz's custom completion rules and fetching the completion statuses
* of the custom completion rules for a given data instance and a user.
*
* @package mod_studentquiz
* @copyright 2023 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_completion extends activity_custom_completion {
/**
* Fetches the completion state for a given completion rule.
*
* @param string $rule The completion rule.
* @return int The completion state.
*/
public function get_state(string $rule): int {
global $DB;
$studentquizid = $this->cm->instance;
if (!$studentquiz = $DB->get_record('studentquiz', ['id' => $studentquizid])) {
throw new \moodle_exception('Unable to find studentquiz with id ' . $studentquizid);
}
$report = new mod_studentquiz_report($this->cm->id, $this->userid);
$userstats = $report->get_user_stats();
switch ($rule) {
case 'completionpoint':
$status = $studentquiz->completionpoint <= (int) $userstats->points;
break;
case 'completionquestionpublished':
$status = $studentquiz->completionquestionpublished <= (int) $userstats->questions_created;
break;
case 'completionquestionapproved':
$status = $studentquiz->completionquestionapproved <= (int) $userstats->questions_approved;
break;
default:
$status = COMPLETION_INCOMPLETE;
break;
}

return $status ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE;
}

/**
* Fetch the list of custom completion rules that this module defines.
*
* @return array An array of custom completion rules.
*/
public static function get_defined_custom_rules(): array {
return [
'completionpoint',
'completionquestionpublished',
'completionquestionapproved',
];
}

/**
* Returns an associative array of the descriptions of custom completion rules.
*
* @return array An array of custom completion rules description.
*/
public function get_custom_rule_descriptions(): array {
$completionpoint = $this->cm->customdata->customcompletionrules['completionpoint'] ?? 0;
$completionquestionpublished = $this->cm->customdata->customcompletionrules['completionquestionpublished'] ?? 0;
$completionquestionapproved = $this->cm->customdata->customcompletionrules['completionquestionapproved'] ?? 0;

return [
'completionpoint' => get_string('completiondetail:point', 'studentquiz', $completionpoint),
'completionquestionpublished' => get_string('completiondetail:published', 'studentquiz',
$completionquestionpublished),
'completionquestionapproved' => get_string('completiondetail:approved',
'studentquiz', $completionquestionapproved),
];
}

/**
* Returns an array of all completion rules, in the order they should be displayed to users.
*
* @return array An array of all completion rules.
*/
public function get_sort_order(): array {
$defaults = [
'completionview',
];

return array_merge($defaults, self::get_defined_custom_rules());
}


/**
* Update state for the completion.
*
* @param stdClass $course The course object.
* @param stdClass|cm_info $cm Course module.
* @param int|null $userid Id of user creating the question.
*/
public static function update_state(stdClass $course, $cm, ?int $userid = null): void {
$completion = new \completion_info($course);
if ($completion->is_enabled($cm)) {
$completion->update_state($cm, COMPLETION_UNKNOWN, $userid);
}
}

}
4 changes: 4 additions & 0 deletions classes/external/update_question_state.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ public static function execute($courseid, $cmid, $studentquizquestionid, $state)
$statename = studentquiz_helper::$statename[$state];
mod_studentquiz_event_notification_question($statename, $studentquizquestion, $course, $cm);
}
$userid = $studentquizquestion->get_question()->createdby;
// Update completion state.
\mod_studentquiz\completion\custom_completion::update_state($course, $cm, $userid);

$result = [];
$result['status'] = get_string('api_state_change_success_title', 'studentquiz');
$result['message'] = get_string('api_state_change_success_content', 'studentquiz');
Expand Down
5 changes: 3 additions & 2 deletions classes/observer.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ class mod_studentquiz_observer {
* @param \core\event\question_created $event
*/
public static function question_created(\core\event\question_created $event) {
global $CFG;

global $CFG, $COURSE;
require_once($CFG->dirroot . '/mod/studentquiz/locallib.php');

if ($event->contextlevel == CONTEXT_MODULE) {
Expand All @@ -44,6 +43,8 @@ public static function question_created(\core\event\question_created $event) {
if ($cm->modname == 'studentquiz') {
mod_studentquiz_ensure_studentquiz_question_record($event->objectid, $event->contextinstanceid);
utils::ensure_question_version_status_is_correct($event->objectid);
// Update completion state.
\mod_studentquiz\completion\custom_completion::update_state($COURSE, $cm);
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion db/install.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/studentquiz/db" VERSION="20220810" COMMENT="mod_studentquiz database layout"
<XMLDB PATH="mod/studentquiz/db" VERSION="20230727" COMMENT="mod_studentquiz database layout"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
Expand Down Expand Up @@ -36,6 +36,9 @@
<FIELD NAME="digesttype" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Email digest type"/>
<FIELD NAME="digestfirstday" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="First day of week"/>
<FIELD NAME="privatecommenting" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Property if studentquiz is enable private discussions"/>
<FIELD NAME="completionpoint" TYPE="int" LENGTH="9" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Nonzero if a certain minimum amount of points required to mark this forum complete for a user."/>
<FIELD NAME="completionquestionpublished" TYPE="int" LENGTH="9" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Nonzero if a certain minimum number of unique authored questions required to mark this forum complete for a user."/>
<FIELD NAME="completionquestionapproved" TYPE="int" LENGTH="9" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Nonzero if a certain minimum number of unique approved questions required to mark this forum complete for a user."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down
48 changes: 48 additions & 0 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -1675,5 +1675,53 @@ function xmldb_studentquiz_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2023053000, 'studentquiz');
}

if ($oldversion < 2023081700) {

// Define field completionpoint to be added to studentquiz.
$table = new xmldb_table('studentquiz');
$field = new xmldb_field('completionpoint', XMLDB_TYPE_INTEGER, '9', null, XMLDB_NOTNULL, null,
'0', 'privatecommenting');

// Conditionally launch add field completionpoint.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Studentquiz savepoint reached.
upgrade_mod_savepoint(true, 2023081700, 'studentquiz');
}

if ($oldversion < 2023081701) {

// Define field completionquestionpublished to be added to studentquiz.
$table = new xmldb_table('studentquiz');
$field = new xmldb_field('completionquestionpublished', XMLDB_TYPE_INTEGER, '9', null, XMLDB_NOTNULL, null,
'0', 'completionpoint');

// Conditionally launch add field completionquestionpublished.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Studentquiz savepoint reached.
upgrade_mod_savepoint(true, 2023081701, 'studentquiz');
}

if ($oldversion < 2023081702) {

// Define field completionquestionapproved to be added to studentquiz.
$table = new xmldb_table('studentquiz');
$field = new xmldb_field('completionquestionapproved', XMLDB_TYPE_INTEGER, '9', null, XMLDB_NOTNULL, null,
'0', 'completionquestionpublished');

// Conditionally launch add field completionquestionapproved.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Studentquiz savepoint reached.
upgrade_mod_savepoint(true, 2023081702, 'studentquiz');
}

return true;
}
15 changes: 15 additions & 0 deletions lang/en/studentquiz.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@
$string['commentcolumnexplainprivate'] = "Number of private comments. A blue background means that you have at least one unread comment.";
$string['commenthistory'] = 'Comment history';
$string['comment_veryshort'] = 'C';
$string['completiondetail:approved'] = 'Minimum number of unique approved questions: {$a}';
$string['completiondetail:published'] = 'Minimum number of unique authored questions: {$a}';
$string['completiondetail:point'] = 'Minimum amount of points: {$a}';

$string['completionpoint'] = 'Minimum amount of points required:';
$string['completionpointgroup'] = 'Require points';
$string['completionpointgroup_help'] = 'Students earn points as specified under the Ranking settings, e.g. 10 points for creating a question, 5 points for a teacher approving the students\' question, 3 points for the student rating another\'s question. By entering a numeric value in the field, students will only complete the StudentQuiz once they have accumulated enough points.';

$string['completionquestionapproved'] = 'Minimum number of unique approved questions required:';
$string['completionquestionapprovedgroup'] = 'Require created approved questions';
$string['completionquestionapprovedgroup_help'] = 'the minimum number of unique questions that a student must author and be approved before the activity is completed. This option can be used with either the Question publishing "Requires approval before publishing" or "Auto-approval" setting, but won\'t be as effective with the latter setting, in case auto-approved questions are later hidden, deleted, or otherwise removed.';

$string['completionquestionpublished'] = 'Minimum number of unique authored questions required:';
$string['completionquestionpublishedgroup'] = 'Require published questions';
$string['completionquestionpublishedgroup_help'] = 'The minimum number of unique questions that a student must author before the activity is completed. Note that this is a simple numerical check - two questions that are hidden/deleted have still been authored.';
$string['confirmdeletecomment'] = 'Are you sure you want to delete this comment?';
$string['createnewquestion'] = 'Create new question';
$string['createnewquestionfirst'] = 'Create first question';
Expand Down
38 changes: 38 additions & 0 deletions lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ function studentquiz_supports($feature) {
return true;
case FEATURE_MOD_PURPOSE:
return MOD_PURPOSE_COLLABORATION;
case FEATURE_COMPLETION_TRACKS_VIEWS:
return true;
case FEATURE_COMPLETION_HAS_RULES:
return true;
default:
return null;
}
Expand Down Expand Up @@ -381,6 +385,40 @@ function studentquiz_get_extra_capabilities() {
return question_get_all_capabilities();
}

/**
* Add a get_coursemodule_info function in case any studentquiz type wants to add 'extra' information
* for the course (see resource).
*
* Given a course_module object, this function returns any "extra" information that may be needed
* when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
*
* @param stdClass $coursemodule The coursemodule object (record).
* @return cached_cm_info An object on information.
*/
function studentquiz_get_coursemodule_info(stdClass $coursemodule): cached_cm_info {
global $DB;

$studentquiz = $DB->get_record('studentquiz',
['id' => $coursemodule->instance], 'id, name, completionpoint, completionquestionpublished,
completionquestionapproved');
if (!$studentquiz) {
return false;
}

$info = new cached_cm_info();
$info->customdata = (object) [];

// Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
$info->customdata->customcompletionrules['completionpoint'] = $studentquiz->completionpoint;
$info->customdata->customcompletionrules['completionquestionpublished'] = $studentquiz->completionquestionpublished;
$info->customdata->customcompletionrules['completionquestionapproved'] =
$studentquiz->completionquestionapproved;
}

return $info;
}

/* File API */

/**
Expand Down
Loading

0 comments on commit c2b2439

Please sign in to comment.