Skip to content

Commit

Permalink
MDL-63564 mod_quiz: support removal of multiple users in a context
Browse files Browse the repository at this point in the history
This issue is a part of the MDL-62560 Epic.
  • Loading branch information
rezaies authored and Mihail Geshoski committed Nov 2, 2018
1 parent 92d26b3 commit 0db66dc
Show file tree
Hide file tree
Showing 2 changed files with 205 additions and 8 deletions.
118 changes: 110 additions & 8 deletions mod/quiz/classes/privacy/provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,22 @@
* Privacy Subsystem implementation for mod_quiz.
*
* @package mod_quiz
* @category privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace mod_quiz\privacy;

use \core_privacy\local\request\writer;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\deletion_criteria;
use \core_privacy\local\metadata\collection;
use \core_privacy\manager;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\deletion_criteria;
use core_privacy\local\request\transform;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
use core_privacy\manager;

defined('MOODLE_INTERNAL') || die();

Expand All @@ -48,7 +51,10 @@ class provider implements
\core_privacy\local\metadata\provider,

// This plugin currently implements the original plugin_provider interface.
\core_privacy\local\request\plugin\provider {
\core_privacy\local\request\plugin\provider,

// This plugin is capable of determining which users have data within it.
\core_privacy\local\request\core_userlist_provider {

/**
* Get the list of contexts that contain user information for the specified user.
Expand Down Expand Up @@ -176,6 +182,52 @@ public static function get_contexts_for_userid(int $userid) : contextlist {
return $resultset;
}

/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();

if (!$context instanceof \context_module) {
return;
}

$params = [
'cmid' => $context->instanceid,
'modname' => 'quiz',
];

// Users who attempted the quiz.
$sql = "SELECT qa.userid
FROM {course_modules} cm
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {quiz} q ON q.id = cm.instance
JOIN {quiz_attempts} qa ON qa.quiz = q.id
WHERE cm.id = :cmid AND qa.preview = 0";
$userlist->add_from_sql('userid', $sql, $params);

// Users with quiz overrides.
$sql = "SELECT qo.userid
FROM {course_modules} cm
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {quiz} q ON q.id = cm.instance
JOIN {quiz_overrides} qo ON qo.quiz = q.id
WHERE cm.id = :cmid";
$userlist->add_from_sql('userid', $sql, $params);

// Question usages in context.
// This includes where a user is the manual marker on a question attempt.
$sql = "SELECT qa.uniqueid
FROM {course_modules} cm
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {quiz} q ON q.id = cm.instance
JOIN {quiz_attempts} qa ON qa.quiz = q.id
WHERE cm.id = :cmid AND qa.preview = 0";
\core_question\privacy\provider::get_users_in_context_from_sql($userlist, 'qn', $sql, $params);
}

/**
* Export all user data for the specified user, in the specified contexts.
*
Expand Down Expand Up @@ -366,6 +418,56 @@ public static function delete_data_for_user(approved_contextlist $contextlist) {
}
}

/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;

$context = $userlist->get_context();

if ($context->contextlevel != CONTEXT_MODULE) {
// Only quiz module will be handled.
return;
}

$cm = get_coursemodule_from_id('quiz', $context->instanceid);
if (!$cm) {
// Only quiz module will be handled.
return;
}

$quizobj = \quiz::create($cm->instance);
$quiz = $quizobj->get_quiz();

$userids = $userlist->get_userids();

// Handle the 'quizaccess' quizaccess.
manager::plugintype_class_callback(
'quizaccess',
quizaccess_user_provider::class,
'delete_quizaccess_data_for_users',
[$userlist]
);

foreach ($userids as $userid) {
// Remove overrides for this user.
$overrides = $DB->get_records('quiz_overrides' , [
'quiz' => $quizobj->get_quizid(),
'userid' => $userid,
]);

foreach ($overrides as $override) {
quiz_delete_override($quiz, $override->id, false);
}

// This will delete all question attempts, quiz attempts, and quiz grades for this user in the given quiz.
quiz_delete_user_attempts($quizobj, (object)['id' => $userid]);
}
}

/**
* Store all quiz attempts for the contextlist.
*
Expand Down
95 changes: 95 additions & 0 deletions mod/quiz/tests/privacy_provider_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -457,4 +457,99 @@ protected function attempt_quiz($quiz, $user) {

return [$quizobj, $quba, $attemptobj];
}

/**
* Test for provider::get_users_in_context().
*/
public function test_get_users_in_context() {
global $DB;
$this->resetAfterTest(true);

$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_user();
$anotheruser = $this->getDataGenerator()->create_user();
$extrauser = $this->getDataGenerator()->create_user();

// Make a quiz.
$this->setUser();
$quiz = $this->create_test_quiz($course);

// Create an override for user1.
$DB->insert_record('quiz_overrides', [
'quiz' => $quiz->id,
'userid' => $user->id,
'timeclose' => 1300,
'timelimit' => null,
]);

// Make an attempt on the quiz as user2.
list($quizobj, $quba, $attemptobj) = $this->attempt_quiz($quiz, $anotheruser);
$context = $quizobj->get_context();

// Fetch users - user1 and user2 should be returned.
$userlist = new \core_privacy\local\request\userlist($context, 'mod_quiz');
\mod_quiz\privacy\provider::get_users_in_context($userlist);
$this->assertEquals(
[$user->id, $anotheruser->id],
$userlist->get_userids(),
'', 0.0, 10, true);
}

/**
* Test for provider::delete_data_for_users().
*/
public function test_delete_data_for_users() {
global $DB;
$this->resetAfterTest(true);

$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();

$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();

// Make a quiz in each course.
$quiz1 = $this->create_test_quiz($course1);
$quiz2 = $this->create_test_quiz($course2);

// Attempt quiz1 as user1 and user2.
list($quiz1obj) = $this->attempt_quiz($quiz1, $user1);
$this->attempt_quiz($quiz1, $user2);

// Create an override in quiz1 for user3.
$DB->insert_record('quiz_overrides', [
'quiz' => $quiz1->id,
'userid' => $user3->id,
'timeclose' => 1300,
'timelimit' => null,
]);

// Attempt quiz2 as user1.
$this->attempt_quiz($quiz2, $user1);

// Delete the data for user1 and user3 in course1 and check it is removed.
$quiz1context = $quiz1obj->get_context();
$approveduserlist = new \core_privacy\local\request\approved_userlist($quiz1context, 'mod_quiz',
[$user1->id, $user3->id]);
provider::delete_data_for_users($approveduserlist);

// Only the attempt of user2 should be remained in quiz1.
$this->assertEquals(
[$user2->id],
$DB->get_fieldset_select('quiz_attempts', 'userid', 'quiz = ?', [$quiz1->id])
);

// The attempt that user1 made in quiz2 should be remained.
$this->assertEquals(
[$user1->id],
$DB->get_fieldset_select('quiz_attempts', 'userid', 'quiz = ?', [$quiz2->id])
);

// The quiz override in quiz1 that we had for user3 should be deleted.
$this->assertEquals(
[],
$DB->get_fieldset_select('quiz_overrides', 'userid', 'quiz = ?', [$quiz1->id])
);
}
}

0 comments on commit 0db66dc

Please sign in to comment.