From 61fafe8127bf19c1911c994a909e7939b820396b Mon Sep 17 00:00:00 2001 From: Mihail Geshoski Date: Fri, 16 Nov 2018 12:18:22 +0800 Subject: [PATCH] MDL-63969 core_favourites: Add support for removal of context users --- favourites/classes/privacy/provider.php | 110 ++++++++++++- favourites/tests/privacy_test.php | 199 ++++++++++++++++++++++-- 2 files changed, 291 insertions(+), 18 deletions(-) diff --git a/favourites/classes/privacy/provider.php b/favourites/classes/privacy/provider.php index 0d947394566..44a076f6862 100644 --- a/favourites/classes/privacy/provider.php +++ b/favourites/classes/privacy/provider.php @@ -29,6 +29,7 @@ defined('MOODLE_INTERNAL') || die(); use \core_privacy\local\metadata\collection; use \core_privacy\local\request\context; use \core_privacy\local\request\approved_contextlist; +use \core_privacy\local\request\transform; /** * Privacy class for requesting user data. @@ -36,7 +37,10 @@ use \core_privacy\local\request\approved_contextlist; * @copyright 2018 Jake Dallimore * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\subsystem\plugin_provider { +class provider implements + \core_privacy\local\metadata\provider, + \core_privacy\local\request\subsystem\plugin_provider, + \core_privacy\local\request\shared_userlist_provider { /** * Returns metadata about this system. @@ -86,13 +90,82 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l FROM {favourite} f WHERE userid = :userid AND component = :component"; + + $params = ['userid' => $userid, 'component' => $component]; + if (!is_null($itemtype)) { $sql .= " AND itemtype = :itemtype"; + $params['itemtype'] = $itemtype; } - $params = ['userid' => $userid, 'component' => $component, 'itemtype' => $itemtype]; + $contextlist->add_from_sql($sql, $params); } + /** + * Add users to a userlist who have favourites within the specified context. + * + * @param \core_privacy\local\request\userlist $userlist The userlist to add the users to. + * @param string $itemtype the type of the favourited items. + * @return void + */ + public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist, + string $itemtype = null) { + if (empty($userlist)) { + return; + } + + $params = [ + 'contextid' => $userlist->get_context()->id, + 'component' => $userlist->get_component() + ]; + + $sql = "SELECT userid + FROM {favourite} + WHERE contextid = :contextid + AND component = :component"; + + if (!is_null($itemtype)) { + $sql .= " AND itemtype = :itemtype"; + $params['itemtype'] = $itemtype; + } + + $userlist->add_from_sql('userid', $sql, $params); + } + + /** + * Get favourites data for the specified user in the specified component, item type and item ID. + * + * @param int $userid The id of the user in scope. + * @param \context $context The context to which data is scoped. + * @param string $component The favourite's component name. + * @param string $itemtype The favourite's item type. + * @param int $itemid The favourite's item ID. + * @return array|null + */ + public static function get_favourites_info_for_user(int $userid, \context $context, + string $component, string $itemtype, int $itemid) { + global $DB; + + $params = [ + 'userid' => $userid, + 'component' => $component, + 'itemtype' => $itemtype, + 'itemid' => $itemid, + 'contextid' => $context->id + ]; + + if (!$favourited = $DB->get_record('favourite', $params)) { + return; + } + + return [ + 'starred' => transform::yesno(true), + 'ordering' => $favourited->ordering, + 'timecreated' => transform::datetime($favourited->timecreated), + 'timemodified' => transform::datetime($favourited->timemodified) + ]; + } + /** * Delete all favourites for all users in the specified contexts, and component area. * @@ -114,6 +187,39 @@ class provider implements \core_privacy\local\metadata\provider, \core_privacy\l $DB->delete_records_select('favourite', $select, $params); } + /** + * Delete all favourites for the specified users in the specified context, component area and item type. + * + * @param \core_privacy\local\request\approved_userlist $userlist The approved contexts and user information + * to delete information for. + * @param string $itemtype The favourite's itemtype. + * @throws \dml_exception if any errors are encountered during deletion. + */ + public static function delete_favourites_for_userlist(\core_privacy\local\request\approved_userlist $userlist, + string $itemtype) { + global $DB; + + $userids = $userlist->get_userids(); + + if (empty($userids)) { + return; + } + + $context = $userlist->get_context(); + list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); + + $params = [ + 'component' => $userlist->get_component(), + 'itemtype' => $itemtype, + 'contextid' => $context->id + ]; + + $params += $userparams; + $select = "component = :component AND itemtype = :itemtype AND contextid = :contextid AND userid $usersql"; + + $DB->delete_records_select('favourite', $select, $params); + } + /** * Delete all favourites for the specified user, in the specified contexts. * diff --git a/favourites/tests/privacy_test.php b/favourites/tests/privacy_test.php index 409282bae80..75076890935 100644 --- a/favourites/tests/privacy_test.php +++ b/favourites/tests/privacy_test.php @@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die(); use \core_privacy\tests\provider_testcase; use \core_favourites\privacy\provider; +use \core_privacy\local\request\transform; /** * Unit tests for favourites/classes/privacy/provider @@ -65,25 +66,25 @@ class privacy_test extends provider_testcase { $ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context); $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context); $systemcontext = context_system::instance(); - $ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $systemcontext); - $ufservice1->create_favourite('core_course', 'course', $course2context->instanceid, $systemcontext); - $ufservice2->create_favourite('core_course', 'course', $course2context->instanceid, $systemcontext); - $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'course')); - $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'course')); + $ufservice1->create_favourite('core_course', 'courses', $course1context->instanceid, $systemcontext); + $ufservice1->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext); + $ufservice2->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext); + $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses')); // Now, just for variety, let's assume you can favourite a course at user context, and do so for user1. - $ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $user1context); + $ufservice1->create_favourite('core_course', 'courses', $course1context->instanceid, $user1context); // Now, ask the favourites privacy api to export contexts for favourites of the type we just created, for user1. $contextlist = new \core_privacy\local\request\contextlist(); - \core_favourites\privacy\provider::add_contexts_for_userid($contextlist, $user1->id, 'core_course', 'course'); + \core_favourites\privacy\provider::add_contexts_for_userid($contextlist, $user1->id, 'core_course', 'courses'); // Verify we have two contexts in the list for user1. $this->assertCount(2, $contextlist->get_contextids()); // And verify we only have the system context returned for user2. $contextlist = new \core_privacy\local\request\contextlist(); - \core_favourites\privacy\provider::add_contexts_for_userid($contextlist, $user2->id, 'core_course', 'course'); + \core_favourites\privacy\provider::add_contexts_for_userid($contextlist, $user2->id, 'core_course', 'courses'); $this->assertCount(1, $contextlist->get_contextids()); } @@ -96,19 +97,19 @@ class privacy_test extends provider_testcase { // Favourite 2 courses for user1 and 1 course for user2, all at the user context. $ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context); $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context); - $ufservice1->create_favourite('core_course', 'course', $course1context->instanceid, $user1context); - $ufservice1->create_favourite('core_course', 'course', $course2context->instanceid, $user1context); - $ufservice2->create_favourite('core_course', 'course', $course2context->instanceid, $user2context); - $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'course')); - $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'course')); + $ufservice1->create_favourite('core_course', 'courses', $course1context->instanceid, $user1context); + $ufservice1->create_favourite('core_course', 'courses', $course2context->instanceid, $user1context); + $ufservice2->create_favourite('core_course', 'courses', $course2context->instanceid, $user2context); + $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses')); // Now, delete the favourites for user1 only. $approvedcontextlist = new \core_privacy\local\request\approved_contextlist($user1, 'core_course', [$user1context->id]); - provider::delete_favourites_for_user($approvedcontextlist, 'core_course', 'course'); + provider::delete_favourites_for_user($approvedcontextlist, 'core_course', 'courses'); // Verify that we have no favourite courses for user1 but that the records are in tact for user2. - $this->assertCount(0, $ufservice1->find_favourites_by_type('core_course', 'course')); - $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'course')); + $this->assertCount(0, $ufservice1->find_favourites_by_type('core_course', 'courses')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses')); } public function test_delete_favourites_for_all_users() { @@ -134,4 +135,170 @@ class privacy_test extends provider_testcase { $this->assertCount(0, $ufservice1->find_favourites_by_type('core_course', 'modules')); $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'modules')); } + + /** + * Test confirming that user ID's of favourited items can be added to the userlist. + */ + public function test_add_userids_for_context() { + list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users(); + + // Favourite 2 courses for user1 and 1 course for user2, all at the site context. + $ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context); + $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context); + $systemcontext = context_system::instance(); + $ufservice1->create_favourite('core_course', 'courses', $course1context->instanceid, $systemcontext); + $ufservice1->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext); + $ufservice2->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext); + $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses')); + + // Now, just for variety, let's assume you can favourite a course at user context, and do so for user1. + $ufservice1->create_favourite('core_course', 'courses', $course1context->instanceid, $user1context); + + // Now, ask the favourites privacy api to export userids for favourites of the type we just created, in the system context. + $userlist = new \core_privacy\local\request\userlist($systemcontext, 'core_course'); + provider::add_userids_for_context($userlist, 'courses'); + // Verify we have two userids in the list for system context. + $this->assertCount(2, $userlist->get_userids()); + $expected = [ + $user1->id, + $user2->id + ]; + $this->assertEquals($expected, $userlist->get_userids(), '', 0.0, 10, true); + + // Ask the favourites privacy api to export userids for favourites of the type we just created, in the user1 context. + $userlist = new \core_privacy\local\request\userlist($user1context, 'core_course'); + provider::add_userids_for_context($userlist, 'courses'); + // Verify we have one userid in the list for user1 context. + $this->assertCount(1, $userlist->get_userids()); + $expected = [$user1->id]; + $this->assertEquals($expected, $userlist->get_userids()); + + // Ask the favourites privacy api to export userids for favourites of the type we just created, in the user2 context. + $userlist = new \core_privacy\local\request\userlist($user2context, 'core_favourites'); + provider::add_userids_for_context($userlist, 'core_course', 'courses'); + // Verify we do not have any userids in the list for user2 context. + $this->assertCount(0, $userlist->get_userids()); + } + + /** + * Test deletion of user favourites based on an approved_userlist, component area and item type. + */ + public function test_delete_favourites_for_userlist() { + list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users(); + + // Favourite 2 courses for user1 and 1 course for user2. + $systemcontext = context_system::instance(); + $ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context); + $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context); + $ufservice1->create_favourite('core_course', 'courses', $course1context->instanceid, $systemcontext); + $ufservice1->create_favourite('core_course', 'courses', $course2context->instanceid, $user1context); + $ufservice2->create_favourite('core_course', 'courses', $course2context->instanceid, $systemcontext); + $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses')); + + // Ask the favourites privacy api to export userids for favourites of the type we just created, in the system context. + $userlist1 = new \core_privacy\local\request\userlist($systemcontext, 'core_course'); + provider::add_userids_for_context($userlist1, 'courses'); + // Verify we have two userids in the list for system context. + $this->assertCount(2, $userlist1->get_userids()); + + // Ask the favourites privacy api to export userids for favourites of the type we just created, in the user1 context. + $userlist2 = new \core_privacy\local\request\userlist($user1context, 'core_course'); + provider::add_userids_for_context($userlist2, 'courses'); + // Verify we have one userid in the list for user1 context. + $this->assertCount(1, $userlist2->get_userids()); + + // Now, delete the favourites for user1 only in the system context. + $approveduserlist = new \core_privacy\local\request\approved_userlist($systemcontext, 'core_course', + [$user1->id]); + provider::delete_favourites_for_userlist($approveduserlist, 'courses'); + // Ensure user1's data was deleted and user2 is still returned for system context. + $userlist1 = new \core_privacy\local\request\userlist($systemcontext, 'core_course'); + provider::add_userids_for_context($userlist1, 'courses'); + $this->assertCount(1, $userlist1->get_userids()); + // Verify that user2 is still in the list for system context. + $expected = [$user2->id]; + $this->assertEquals($expected, $userlist1->get_userids()); + // Verify that the data of user1 was not deleted in the user1context. + $userlist2 = new \core_privacy\local\request\userlist($user1context, 'core_course'); + provider::add_userids_for_context($userlist2, 'courses'); + $expected = [$user1->id]; + $this->assertEquals($expected, $userlist2->get_userids()); + + // Now, delete the favourites for user2 only in the user1 context. + // Make sure favourites are only being deleted in the right context. + $approveduserlist = new \core_privacy\local\request\approved_userlist($user1context, 'core_course', + [$user2->id]); + provider::delete_favourites_for_userlist($approveduserlist, 'courses'); + // Verify we have one userid in the list for system context. + $userlist2 = new \core_privacy\local\request\userlist($systemcontext, 'core_course'); + provider::add_userids_for_context($userlist2, 'courses'); + $this->assertCount(1, $userlist2->get_userids()); + // Verify that user2 is still in the list for system context. + $expected = [$user2->id]; + $this->assertEquals($expected, $userlist2->get_userids()); + + // Verify that user1 is still present in the list for user1 context. + $userlist3 = new \core_privacy\local\request\userlist($user1context, 'core_course'); + provider::add_userids_for_context($userlist3, 'courses'); + $this->assertCount(1, $userlist3->get_userids()); + // Verify that user1 is still in the list for user1 context. + $expected = [$user1->id]; + $this->assertEquals($expected, $userlist3->get_userids()); + } + + /** + * Test fetching the favourites data for a specified user in a specified component, item type and item ID. + */ + public function test_get_favourites_info_for_user() { + list($user1, $user2, $user1context, $user2context, $course1context, $course2context) = $this->set_up_courses_and_users(); + + // Favourite 2 courses for user1 and 1 course for user2. + $ufservice1 = \core_favourites\service_factory::get_service_for_user_context($user1context); + $ufservice2 = \core_favourites\service_factory::get_service_for_user_context($user2context); + $coursefavourite1 = $ufservice1->create_favourite('core_course', 'courses', + $course1context->instanceid, $course1context); + $this->waitForSecond(); + $coursefavourite2 = $ufservice1->create_favourite('core_course', 'courses', + $course2context->instanceid, $course2context); + $this->waitForSecond(); + $coursefavourite3 = $ufservice2->create_favourite('core_course', 'courses', + $course2context->instanceid, $course2context); + $this->assertCount(2, $ufservice1->find_favourites_by_type('core_course', 'courses')); + $this->assertCount(1, $ufservice2->find_favourites_by_type('core_course', 'courses')); + + // Get the favourites info for user1 in the course1 context. + $favouriteinfo1 = (object) provider::get_favourites_info_for_user($user1->id, $course1context, + 'core_course', 'courses', $course1context->instanceid); + // Ensure the correct data has been returned. + $this->assertEquals(transform::yesno(true), $favouriteinfo1->starred); + $this->assertEquals('', $favouriteinfo1->ordering); + $this->assertEquals(transform::datetime($coursefavourite1->timecreated), $favouriteinfo1->timecreated); + $this->assertEquals(transform::datetime($coursefavourite1->timemodified), $favouriteinfo1->timemodified); + + // Get the favourites info for user1 in the course2 context. + $favouriteinfo2 = (object) provider::get_favourites_info_for_user($user1->id, $course2context, + 'core_course', 'courses', $course2context->instanceid); + // Ensure the correct data has been returned. + $this->assertEquals(transform::yesno(true), $favouriteinfo2->starred); + $this->assertEquals('', $favouriteinfo2->ordering); + $this->assertEquals(transform::datetime($coursefavourite2->timecreated), $favouriteinfo2->timecreated); + $this->assertEquals(transform::datetime($coursefavourite2->timemodified), $favouriteinfo2->timemodified); + + // Get the favourites info for user2 in the course2 context. + $favouriteinfo3 = (object) provider::get_favourites_info_for_user($user2->id, $course2context, + 'core_course', 'courses', $course2context->instanceid); + // Ensure the correct data has been returned. + $this->assertEquals(transform::yesno(true), $favouriteinfo3->starred); + $this->assertEquals('', $favouriteinfo3->ordering); + $this->assertEquals(transform::datetime($coursefavourite3->timecreated), $favouriteinfo3->timecreated); + $this->assertEquals(transform::datetime($coursefavourite3->timemodified), $favouriteinfo3->timemodified); + + // Get the favourites info for user2 in the course1 context (user2 has not favourited course1). + $favouriteinfo4 = provider::get_favourites_info_for_user($user2->id, $course1context, + 'core_course', 'courses', $course1context->instanceid); + // Ensure that data has not been returned. + $this->assertEmpty($favouriteinfo4); + } }