Skip to content

Commit

Permalink
MDL-68831 quiz overrides: show a summary on the quiz info page
Browse files Browse the repository at this point in the history
  • Loading branch information
timhunt committed Dec 16, 2020
1 parent 7350f41 commit 7d11dcf
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 19 deletions.
5 changes: 5 additions & 0 deletions mod/quiz/lang/en/quiz.php
Expand Up @@ -616,6 +616,11 @@
$string['overridesforquiz'] = 'Settings overrides: {$a}';
$string['overridesnoneforgroups'] = 'No group settings overrides have been created for this quiz.';
$string['overridesnoneforusers'] = 'No user settings overrides have been created for this quiz.';
$string['overridessummary'] = 'Settings overrides exist ({$a})';
$string['overridessummarythisgroup'] = 'Settings overrides exist ({$a}) for this group';
$string['overridessummaryyourgroups'] = 'Settings overrides exist ({$a}) for your groups';
$string['overridessummarygroup'] = 'Groups: {$a}';
$string['overridessummaryuser'] = 'Users: {$a}';
$string['overrideuser'] = 'Override user';
$string['overrideusereventname'] = '{$a->quiz} - Override';
$string['pageshort'] = 'P';
Expand Down
12 changes: 3 additions & 9 deletions mod/quiz/lib.php
Expand Up @@ -1645,16 +1645,10 @@ function quiz_num_attempt_summary($quiz, $cm, $returnzero = false, $currentgroup
*/
function quiz_attempt_summary_link_to_reports($quiz, $cm, $context, $returnzero = false,
$currentgroup = 0) {
global $CFG;
$summary = quiz_num_attempt_summary($quiz, $cm, $returnzero, $currentgroup);
if (!$summary) {
return '';
}
global $PAGE;

require_once($CFG->dirroot . '/mod/quiz/report/reportlib.php');
$url = new moodle_url('/mod/quiz/report.php', array(
'id' => $cm->id, 'mode' => quiz_report_default_report($context)));
return html_writer::link($url, $summary);
return $PAGE->get_renderer('mod_quiz')->quiz_attempt_summary_link_to_reports(
$quiz, $cm, $context, $returnzero, $currentgroup);
}

/**
Expand Down
66 changes: 66 additions & 0 deletions mod/quiz/locallib.php
Expand Up @@ -968,6 +968,72 @@ function quiz_update_all_final_grades($quiz) {
}
}

/**
* Return summary of the number of settings override that exist.
*
* To get a nice display of this, see the quiz_override_summary_links()
* quiz renderer method.
*
* @param stdClass $quiz the quiz settings. Only $quiz->id is used at the moment.
* @param stdClass|cm_info $cm the cm object. Only $cm->course, $cm->groupmode and
* $cm->groupingid fields are used at the moment.
* @param int $currentgroup if there is a concept of current group where this method is being called
* (e.g. a report) pass it in here. Default 0 which means no current group.
* @return array like 'group' => 3, 'user' => 12] where 3 is the number of group overrides,
* and 12 is the number of user ones.
*/
function quiz_override_summary(stdClass $quiz, stdClass $cm, int $currentgroup = 0): array {
global $DB;

if ($currentgroup) {
// Currently only interested in one group.
$groupcount = $DB->count_records('quiz_overrides', ['quiz' => $quiz->id, 'groupid' => $currentgroup]);
$usercount = $DB->count_records_sql("
SELECT COUNT(1)
FROM {quiz_overrides} o
JOIN {groups_members} gm ON o.userid = gm.userid
WHERE o.quiz = ?
AND gm.groupid = ?
", [$quiz->id, $currentgroup]);
return ['group' => $groupcount, 'user' => $usercount, 'mode' => 'onegroup'];
}

$quizgroupmode = groups_get_activity_groupmode($cm);
$accessallgroups = ($quizgroupmode == NOGROUPS) ||
has_capability('moodle/site:accessallgroups', context_module::instance($cm->id));

if ($accessallgroups) {
// User can see all groups.
$groupcount = $DB->count_records_select('quiz_overrides',
'quiz = ? AND groupid IS NOT NULL', [$quiz->id]);
$usercount = $DB->count_records_select('quiz_overrides',
'quiz = ? AND userid IS NOT NULL', [$quiz->id]);
return ['group' => $groupcount, 'user' => $usercount, 'mode' => 'allgroups'];

} else {
// User can only see groups they are in.
$groups = groups_get_activity_allowed_groups($cm);
if (!$groups) {
return ['group' => 0, 'user' => 0, 'mode' => 'somegroups'];
}

list($groupidtest, $params) = $DB->get_in_or_equal(array_keys($groups));
$params[] = $quiz->id;

$groupcount = $DB->count_records_select('quiz_overrides',
"groupid $groupidtest AND quiz = ?", $params);
$usercount = $DB->count_records_sql("
SELECT COUNT(1)
FROM {quiz_overrides} o
JOIN {groups_members} gm ON o.userid = gm.userid
WHERE gm.groupid $groupidtest
AND o.quiz = ?
", $params);

return ['group' => $groupcount, 'user' => $usercount, 'mode' => 'somegroups'];
}
}

/**
* Efficiently update check state time on all open attempts
*
Expand Down
1 change: 1 addition & 0 deletions mod/quiz/overrides.php
Expand Up @@ -102,6 +102,7 @@
$overrides = $DB->get_records_sql($sql, $params);
}
} else {
// User overrides.
$colname = get_string('user');
list($sort, $params) = users_order_by_sql('u');
$params['quizid'] = $quiz->id;
Expand Down
63 changes: 56 additions & 7 deletions mod/quiz/renderer.php
Expand Up @@ -927,7 +927,7 @@ public function view_page_notenrolled($course, $quiz, $cm, $context, $messages)
*
* @param object $quiz the quiz settings.
* @param object $cm the course_module object.
* @param object $context the quiz context.
* @param context $context the quiz context.
* @param array $messages any access messages that should be described.
* @return string HTML to output.
*/
Expand All @@ -953,6 +953,13 @@ public function view_information($quiz, $cm, $context, $messages) {
array('class' => 'quizattemptcounts'));
}
}

if (has_any_capability(['mod/quiz:manageoverrides', 'mod/quiz:viewoverrides'], $context)) {
if ($overrideinfo = $this->quiz_override_summary_links($quiz, $cm)) {
$output .= html_writer::tag('div', $overrideinfo, ['class' => 'quizattemptcounts']);
}
}

return $output;
}

Expand Down Expand Up @@ -1216,15 +1223,14 @@ public function no_review_message($message) {
* Returns the same as {@link quiz_num_attempt_summary()} but wrapped in a link
* to the quiz reports.
*
* @param object $quiz the quiz object. Only $quiz->id is used at the moment.
* @param object $cm the cm object. Only $cm->course, $cm->groupmode and $cm->groupingid
* @param stdClass $quiz the quiz object. Only $quiz->id is used at the moment.
* @param stdClass $cm the cm object. Only $cm->course, $cm->groupmode and $cm->groupingid
* fields are used at the moment.
* @param object $context the quiz context.
* @param context $context the quiz context.
* @param bool $returnzero if false (default), when no attempts have been made '' is returned
* instead of 'Attempts: 0'.
* instead of 'Attempts: 0'.
* @param int $currentgroup if there is a concept of current group where this method is being
* called
* (e.g. a report) pass it in here. Default 0 which means no current group.
* called (e.g. a report) pass it in here. Default 0 which means no current group.
* @return string HTML fragment for the link.
*/
public function quiz_attempt_summary_link_to_reports($quiz, $cm, $context,
Expand All @@ -1241,6 +1247,49 @@ public function quiz_attempt_summary_link_to_reports($quiz, $cm, $context,
return html_writer::link($url, $summary);
}

/**
* Render a summary of the number of group and user overrides, with corresponding links.
*
* @param stdClass $quiz the quiz settings.
* @param stdClass|cm_info $cm the cm object.
* @param int $currentgroup currently selected group, if there is one.
* @return string HTML fragment for the link.
*/
public function quiz_override_summary_links(stdClass $quiz, stdClass $cm, $currentgroup = 0): string {

$baseurl = new moodle_url('/mod/quiz/overrides.php', ['cmid' => $cm->id]);
$counts = quiz_override_summary($quiz, $cm, $currentgroup);

$links = [];
if ($counts['group']) {
$links[] = html_writer::link(new moodle_url($baseurl, ['mode' => 'group']),
get_string('overridessummarygroup', 'quiz', $counts['group']));
}
if ($counts['user']) {
$links[] = html_writer::link(new moodle_url($baseurl, ['mode' => 'user']),
get_string('overridessummaryuser', 'quiz', $counts['user']));
}

if (!$links) {
return '';
}

$links = implode(', ', $links);
switch ($counts['mode']) {
case 'onegroup':
return get_string('overridessummarythisgroup', 'quiz', $links);

case 'somegroups':
return get_string('overridessummaryyourgroups', 'quiz', $links);

case 'allgroups':
return get_string('overridessummary', 'quiz', $links);

default:
throw new coding_exception('Unexpected mode ' . $counts['mode']);
}
}

/**
* Outputs a chart.
*
Expand Down
10 changes: 7 additions & 3 deletions mod/quiz/tests/behat/quiz_group_override.feature
Expand Up @@ -82,8 +82,10 @@ Feature: Quiz group override
| quiz | group | attempts |
| Test quiz | G1 | 2 |
| Test quiz | G2 | 2 |
When I am on the "Test quiz" "mod_quiz > Group overrides" page logged in as "teacher1"
Then "Group 1" "table_row" should exist
When I am on the "Test quiz" "mod_quiz > View" page logged in as "teacher1"
Then I should see "Settings overrides exist (Groups: 2)"
And I follow "Groups: 2"
And "Group 1" "table_row" should exist
And "Group 2" "table_row" should exist

Scenario: A teacher without accessallgroups permission should only see the group overrides within his/her groups, when the activity's group mode is "separate groups"
Expand All @@ -94,7 +96,9 @@ Feature: Quiz group override
| quiz | group | attempts |
| Test quiz | G1 | 2 |
| Test quiz | G2 | 2 |
When I am on the "Test quiz" "mod_quiz > Group overrides" page logged in as "teacher1"
When I am on the "Test quiz" "mod_quiz > View" page logged in as "teacher1"
Then I should see "Settings overrides exist (Groups: 1) for your groups"
And I follow "Groups: 1"
Then "Group 1" "table_row" should exist
And "Group 2" "table_row" should not exist

Expand Down
2 changes: 2 additions & 0 deletions mod/quiz/tests/behat/quiz_user_override.feature
Expand Up @@ -121,3 +121,5 @@ Feature: Quiz user override
And "Edit" "link" should not exist in the "Student One" "table_row"
And "Copy" "link" should not exist in the "Student One" "table_row"
And "Delete" "link" should not exist in the "Student One" "table_row"
And I am on the "Test quiz" "mod_quiz > View" page
And I should see "Settings overrides exist (Users: 2)"
102 changes: 102 additions & 0 deletions mod/quiz/tests/locallib_test.php
Expand Up @@ -839,4 +839,106 @@ public function test_quiz_retrieve_tags_for_slot_ids_combinations(

$this->assertEquals($formattedexptected, $actual);
}

public function test_quiz_override_summary() {
global $DB, $PAGE;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
/** @var mod_quiz_generator $quizgenerator */
$quizgenerator = $generator->get_plugin_generator('mod_quiz');
/** @var mod_quiz_renderer $renderer */
$renderer = $PAGE->get_renderer('mod_quiz');

// Course with quiz and a group - plus some others, to verify they don't get counted.
$course = $generator->create_course();
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'groupmode' => SEPARATEGROUPS]);
$cm = get_coursemodule_from_id('quiz', $quiz->cmid, $course->id);
$group = $generator->create_group(['courseid' => $course->id]);
$othergroup = $generator->create_group(['courseid' => $course->id]);
$otherquiz = $quizgenerator->create_instance(['course' => $course->id]);

// Initial test (as admin) with no data.
$this->setAdminUser();
$this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'allgroups'],
quiz_override_summary($quiz, $cm));
$this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'onegroup'],
quiz_override_summary($quiz, $cm, $group->id));

// Editing teacher.
$teacher = $generator->create_user();
$generator->enrol_user($teacher->id, $course->id, 'editingteacher');

// Non-editing teacher.
$tutor = $generator->create_user();
$generator->enrol_user($tutor->id, $course->id, 'teacher');
$generator->create_group_member(['userid' => $tutor->id, 'groupid' => $group->id]);

// Three students.
$student1 = $generator->create_user();
$generator->enrol_user($student1->id, $course->id, 'student');
$generator->create_group_member(['userid' => $student1->id, 'groupid' => $group->id]);

$student2 = $generator->create_user();
$generator->enrol_user($student2->id, $course->id, 'student');
$generator->create_group_member(['userid' => $student2->id, 'groupid' => $othergroup->id]);

$student3 = $generator->create_user();
$generator->enrol_user($student3->id, $course->id, 'student');

// Initial test now users exist, but before overrides.
// Test as teacher.
$this->setUser($teacher);
$this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'allgroups'],
quiz_override_summary($quiz, $cm));
$this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'onegroup'],
quiz_override_summary($quiz, $cm, $group->id));

// Test as tutor.
$this->setUser($tutor);
$this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'somegroups'],
quiz_override_summary($quiz, $cm));
$this->assertEquals(['group' => 0, 'user' => 0, 'mode' => 'onegroup'],
quiz_override_summary($quiz, $cm, $group->id));
$this->assertEquals('', $renderer->quiz_override_summary_links($quiz, $cm));

// Quiz setting overrides for students 1 and 3.
$quizgenerator->create_override(['quiz' => $quiz->id, 'userid' => $student1->id, 'attempts' => 2]);
$quizgenerator->create_override(['quiz' => $quiz->id, 'userid' => $student3->id, 'attempts' => 2]);
$quizgenerator->create_override(['quiz' => $quiz->id, 'groupid' => $group->id, 'attempts' => 3]);
$quizgenerator->create_override(['quiz' => $quiz->id, 'groupid' => $othergroup->id, 'attempts' => 3]);
$quizgenerator->create_override(['quiz' => $otherquiz->id, 'userid' => $student2->id, 'attempts' => 2]);

// Test as teacher.
$this->setUser($teacher);
$this->assertEquals(['group' => 2, 'user' => 2, 'mode' => 'allgroups'],
quiz_override_summary($quiz, $cm));
$this->assertEquals('Settings overrides exist (Groups: 2, Users: 2)',
// Links checked by Behat, so strip them for these tests.
html_to_text($renderer->quiz_override_summary_links($quiz, $cm), 0, false));
$this->assertEquals(['group' => 1, 'user' => 1, 'mode' => 'onegroup'],
quiz_override_summary($quiz, $cm, $group->id));
$this->assertEquals('Settings overrides exist (Groups: 1, Users: 1) for this group',
html_to_text($renderer->quiz_override_summary_links($quiz, $cm, $group->id), 0, false));

// Test as tutor.
$this->setUser($tutor);
$this->assertEquals(['group' => 1, 'user' => 1, 'mode' => 'somegroups'],
quiz_override_summary($quiz, $cm));
$this->assertEquals('Settings overrides exist (Groups: 1, Users: 1) for your groups',
html_to_text($renderer->quiz_override_summary_links($quiz, $cm), 0, false));
$this->assertEquals(['group' => 1, 'user' => 1, 'mode' => 'onegroup'],
quiz_override_summary($quiz, $cm, $group->id));
$this->assertEquals('Settings overrides exist (Groups: 1, Users: 1) for this group',
html_to_text($renderer->quiz_override_summary_links($quiz, $cm, $group->id), 0, false));

// Now set the quiz to be group mode: no groups, and re-test as tutor.
// In this case, the tutor should see all groups.
$DB->set_field('course_modules', 'groupmode', NOGROUPS, ['id' => $cm->id]);
$cm = get_coursemodule_from_id('quiz', $quiz->cmid, $course->id);

$this->assertEquals(['group' => 2, 'user' => 2, 'mode' => 'allgroups'],
quiz_override_summary($quiz, $cm));
$this->assertEquals('Settings overrides exist (Groups: 2, Users: 2)',
html_to_text($renderer->quiz_override_summary_links($quiz, $cm), 0, false));
}
}

0 comments on commit 7d11dcf

Please sign in to comment.