MDL-70720 Quiz: Improve capability checks when fetching users' best grades

This commit is contained in:
Huong Nguyen 2021-04-22 11:01:09 +07:00 committed by Jenkins
parent 9c11ceac1f
commit 5444f88e4f
3 changed files with 138 additions and 18 deletions

View File

@ -521,7 +521,23 @@ class mod_quiz_external extends external_api {
}
$result = array();
$grade = quiz_get_best_grade($quiz, $user->id);
// This code was mostly copied from mod/quiz/view.php. We need to make the web service logic consistent.
// Get this user's attempts.
$attempts = quiz_get_user_attempts($quiz->id, $user->id, 'all');
$canviewgrade = false;
if ($attempts) {
if ($USER->id != $user->id) {
// No need to check the permission here. We did it at by require_capability('mod/quiz:viewreports', $context).
$canviewgrade = true;
} else {
// Work out which columns we need, taking account what data is available in each attempt.
[$notused, $alloptions] = quiz_get_combined_reviewoptions($quiz, $attempts);
$canviewgrade = $alloptions->marks >= question_display_options::MARK_AND_MAX;
}
}
$grade = $canviewgrade ? quiz_get_best_grade($quiz, $user->id) : null;
if ($grade === null) {
$result['hasgrade'] = false;

View File

@ -1155,7 +1155,7 @@ function quiz_review_option_form_to_db($fromform, $field) {
$review = 0;
foreach ($times as $whenname => $when) {
$fieldname = $field . $whenname;
if (isset($fromform->$fieldname)) {
if (!empty($fromform->$fieldname)) {
$review |= $when;
unset($fromform->$fieldname);
}

View File

@ -464,37 +464,101 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
* Test get_user_best_grade
*/
public function test_get_user_best_grade() {
global $DB;
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$questioncat = $questiongenerator->create_question_category();
// Create a new quiz.
$quizapi1 = $quizgenerator->create_instance([
'name' => 'Test Quiz API 1',
'course' => $this->course->id,
'sumgrades' => 1
]);
$quizapi2 = $quizgenerator->create_instance([
'name' => 'Test Quiz API 2',
'course' => $this->course->id,
'sumgrades' => 1,
'marksduring' => 0,
'marksimmediately' => 0,
'marksopen' => 0,
'marksclosed' => 0
]);
// Create a question.
$question = $questiongenerator->create_question('numerical', null, ['category' => $questioncat->id]);
// Add question to the quizzes.
quiz_add_quiz_question($question->id, $quizapi1);
quiz_add_quiz_question($question->id, $quizapi2);
// Create quiz object.
$quizapiobj1 = quiz::create($quizapi1->id, $this->student->id);
$quizapiobj2 = quiz::create($quizapi2->id, $this->student->id);
// Set grade to pass.
$item = grade_item::fetch([
'courseid' => $this->course->id,
'itemtype' => 'mod',
'itemmodule' => 'quiz',
'iteminstance' => $quizapi1->id,
'outcomeid' => null
]);
$item->gradepass = 80;
$item->update();
$item = grade_item::fetch([
'courseid' => $this->course->id,
'itemtype' => 'mod',
'itemmodule' => 'quiz',
'iteminstance' => $quizapi2->id,
'outcomeid' => null
]);
$item->gradepass = 80;
$item->update();
// Start the passing attempt.
$quba1 = question_engine::make_questions_usage_by_activity('mod_quiz', $quizapiobj1->get_context());
$quba1->set_preferred_behaviour($quizapiobj1->get_quiz()->preferredbehaviour);
$quba2 = question_engine::make_questions_usage_by_activity('mod_quiz', $quizapiobj2->get_context());
$quba2->set_preferred_behaviour($quizapiobj2->get_quiz()->preferredbehaviour);
// Start the testing for quizapi1 that allow the student to view the grade.
$this->setUser($this->student);
$result = mod_quiz_external::get_user_best_grade($this->quiz->id);
$result = mod_quiz_external::get_user_best_grade($quizapi1->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_user_best_grade_returns(), $result);
// No grades yet.
$this->assertFalse($result['hasgrade']);
$this->assertTrue(!isset($result['grade']));
$grade = new stdClass();
$grade->quiz = $this->quiz->id;
$grade->userid = $this->student->id;
$grade->grade = 8.9;
$grade->timemodified = time();
$grade->id = $DB->insert_record('quiz_grades', $grade);
// Start the attempt.
$timenow = time();
$attempt = quiz_create_attempt($quizapiobj1, 1, false, $timenow, false, $this->student->id);
quiz_start_new_attempt($quizapiobj1, $quba1, $attempt, 1, $timenow);
quiz_attempt_save_started($quizapiobj1, $quba1, $attempt);
$result = mod_quiz_external::get_user_best_grade($this->quiz->id);
// Process some responses from the student.
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_submitted_actions($timenow, false, [1 => ['answer' => '3.14']]);
// Finish the attempt.
$attemptobj->process_finish($timenow, false);
$result = mod_quiz_external::get_user_best_grade($quizapi1->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_user_best_grade_returns(), $result);
// Now I have grades.
$this->assertTrue($result['hasgrade']);
$this->assertEquals(8.9, $result['grade']);
$this->assertEquals(100.0, $result['grade']);
// We should not see other users grades.
$anotherstudent = self::getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($anotherstudent->id, $this->course->id, $this->studentrole->id, 'manual');
try {
mod_quiz_external::get_user_best_grade($this->quiz->id, $anotherstudent->id);
mod_quiz_external::get_user_best_grade($quizapi1->id, $anotherstudent->id);
$this->fail('Exception expected due to missing capability.');
} catch (required_capability_exception $e) {
$this->assertEquals('nopermissions', $e->errorcode);
@ -503,11 +567,11 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
// Teacher must be able to see student grades.
$this->setUser($this->teacher);
$result = mod_quiz_external::get_user_best_grade($this->quiz->id, $this->student->id);
$result = mod_quiz_external::get_user_best_grade($quizapi1->id, $this->student->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_user_best_grade_returns(), $result);
$this->assertTrue($result['hasgrade']);
$this->assertEquals(8.9, $result['grade']);
$this->assertEquals(100.0, $result['grade']);
// Invalid user.
try {
@ -517,8 +581,48 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
$this->assertEquals('invaliduser', $e->errorcode);
}
// Remove the created data.
$DB->delete_records('quiz_grades', array('id' => $grade->id));
// End the testing for quizapi1 that allow the student to view the grade.
// Start the testing for quizapi2 that do not allow the student to view the grade.
$this->setUser($this->student);
$result = mod_quiz_external::get_user_best_grade($quizapi2->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_user_best_grade_returns(), $result);
// No grades yet.
$this->assertFalse($result['hasgrade']);
$this->assertTrue(!isset($result['grade']));
// Start the attempt.
$timenow = time();
$attempt = quiz_create_attempt($quizapiobj2, 1, false, $timenow, false, $this->student->id);
quiz_start_new_attempt($quizapiobj2, $quba2, $attempt, 1, $timenow);
quiz_attempt_save_started($quizapiobj2, $quba2, $attempt);
// Process some responses from the student.
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_submitted_actions($timenow, false, [1 => ['answer' => '3.14']]);
// Finish the attempt.
$attemptobj->process_finish($timenow, false);
$result = mod_quiz_external::get_user_best_grade($quizapi2->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_user_best_grade_returns(), $result);
// Now I have grades but I will not be allowed to see it.
$this->assertFalse($result['hasgrade']);
$this->assertTrue(!isset($result['grade']));
// Teacher must be able to see student grades.
$this->setUser($this->teacher);
$result = mod_quiz_external::get_user_best_grade($quizapi2->id, $this->student->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_user_best_grade_returns(), $result);
$this->assertTrue($result['hasgrade']);
$this->assertEquals(100.0, $result['grade']);
// End the testing for quizapi2 that do not allow the student to view the grade.
}
/**