Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge branch 'MDL-63564-35' of git://github.com/mihailges/moodle into…
… MOODLE_35_STABLE
  • Loading branch information
andrewnicols committed Nov 5, 2018
2 parents 748409d + 46782ec commit ea321f3
Show file tree
Hide file tree
Showing 7 changed files with 488 additions and 16 deletions.
11 changes: 11 additions & 0 deletions mod/quiz/classes/privacy/legacy_quizaccess_polyfill.php
Expand Up @@ -23,6 +23,8 @@
*/
namespace mod_quiz\privacy;

use core_privacy\local\request\approved_userlist;

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

/**
Expand Down Expand Up @@ -63,4 +65,13 @@ public static function delete_quizaccess_data_for_all_users_in_context(\quiz $qu
public static function delete_quizaccess_data_for_user(\quiz $quiz, \stdClass $user) {
static::_delete_quizaccess_data_for_user($quiz, $user);
}

/**
* Delete all user data for the specified users, in the specified context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_quizaccess_data_for_users(approved_userlist $userlist) {
static::_delete_quizaccess_data_for_users($userlist);
}
}
118 changes: 110 additions & 8 deletions mod/quiz/classes/privacy/provider.php
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
41 changes: 41 additions & 0 deletions mod/quiz/classes/privacy/quizaccess_user_provider.php
@@ -0,0 +1,41 @@
<?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/>.

/**
* The quizaccess_user_provider interface provides the expected interface for all 'quizaccess' quizaccesss.
*
* Quiz sub plugins should implement this if they store personal information and can retrieve a userid.
*
* @package mod_quiz
* @copyright 2018 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace mod_quiz\privacy;

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

use core_privacy\local\request\approved_userlist;

interface quizaccess_user_provider extends \core_privacy\local\request\plugin\subplugin_provider {

/**
* Delete multiple users data within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_quizaccess_data_for_users(approved_userlist $userlist);
}
31 changes: 29 additions & 2 deletions mod/quiz/tests/privacy_legacy_quizaccess_polyfill_test.php
Expand Up @@ -73,7 +73,7 @@ public function test_delete_quizaccess_for_context() {
}

/**
* Test the _delete_quizaccess_for_context shim.
* Test the _delete_quizaccess_for_user shim.
*/
public function test_delete_quizaccess_for_user() {
$context = context_system::instance();
Expand All @@ -89,6 +89,23 @@ public function test_delete_quizaccess_for_user() {
test_privacy_legacy_quizaccess_polyfill_provider::$mock = $mock;
test_privacy_legacy_quizaccess_polyfill_provider::delete_quizaccess_data_for_user($quiz, $user);
}

/**
* Test the _delete_quizaccess_for_users shim.
*/
public function test_delete_quizaccess_for_users() {
$context = $this->createMock(context_module::class);
$user = (object) [];
$approveduserlist = new \core_privacy\local\request\approved_userlist($context, 'mod_quiz', [$user]);

$mock = $this->createMock(test_privacy_legacy_quizaccess_polyfill_mock_wrapper::class);
$mock->expects($this->once())
->method('get_return_value')
->with('_delete_quizaccess_data_for_users', [$approveduserlist]);

test_privacy_legacy_quizaccess_polyfill_provider::$mock = $mock;
test_privacy_legacy_quizaccess_polyfill_provider::delete_quizaccess_data_for_users($approveduserlist);
}
}

/**
Expand All @@ -99,7 +116,8 @@ public function test_delete_quizaccess_for_user() {
*/
class test_privacy_legacy_quizaccess_polyfill_provider implements
\core_privacy\local\metadata\provider,
\mod_quiz\privacy\quizaccess_provider {
\mod_quiz\privacy\quizaccess_provider,
\mod_quiz\privacy\quizaccess_user_provider {

use \mod_quiz\privacy\legacy_quizaccess_polyfill;
use \core_privacy\local\legacy_polyfill;
Expand Down Expand Up @@ -138,6 +156,15 @@ protected static function _delete_quizaccess_data_for_user($quiz, $user) {
static::$mock->get_return_value(__FUNCTION__, func_get_args());
}

/**
* Delete all user data for the specified users, in the specified context.
*
* @param \core_privacy\local\request\approved_userlist $userlist
*/
protected static function _delete_quizaccess_data_for_users($userlist) {
static::$mock->get_return_value(__FUNCTION__, func_get_args());
}

/**
* Returns metadata about this plugin.
*
Expand Down
95 changes: 95 additions & 0 deletions mod/quiz/tests/privacy_provider_test.php
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 ea321f3

Please sign in to comment.