Compare commits
552 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 46574904af | |||
| 4c8005a0ff | |||
| 57cc5dc750 | |||
| 897a492729 | |||
| a6c45af354 | |||
| 36960958ad | |||
| 798f0f1da9 | |||
| 6c32ea4631 | |||
| 554e6a6562 | |||
| 372ea5502c | |||
| 14f7296436 | |||
| 004fd4259e | |||
| 39da8c1a35 | |||
| d1620c57f9 | |||
| dc69734963 | |||
| 8da25ca04c | |||
| ad7f143897 | |||
| 0e156326cc | |||
| 8760b7335b | |||
| 22c0a30888 | |||
| 3e3e5cdbb4 | |||
| 72580c0832 | |||
| 37e1069289 | |||
| 3f18d2af9a | |||
| 65abf2a37c | |||
| 51c17c381d | |||
| 3811659bc7 | |||
| cd0f1aa403 | |||
| 12ef4a1219 | |||
| 9d594541b6 | |||
| 68ed91b7af | |||
| 5e84f521fa | |||
| f63745eabd | |||
| 827c8e98ac | |||
| bcd677bc6a | |||
| 85218c28e9 | |||
| 7f9b187994 | |||
| b188407bdc | |||
| dc71a8b50e | |||
| 096b3723bb | |||
| 76578cf3f6 | |||
| 7831f2785d | |||
| 8880510d40 | |||
| 2f48e08c46 | |||
| 0061a6ae67 | |||
| d060e0b80f | |||
| 63b0f6276c | |||
| 05c87e7bb4 | |||
| 3a959aa800 | |||
| 641d878d08 | |||
| 014335fb2b | |||
| 0668ba779a | |||
| a61cd7efef | |||
| 502344a904 | |||
| df1714f25a | |||
| 3960ad53a7 | |||
| 4557b4e065 | |||
| 05dcf35796 | |||
| 267effaaa2 | |||
| 30f3ea7fe1 | |||
| 3558897fc3 | |||
| b5f402ad73 | |||
| 62b94b486e | |||
| ef0f37ca16 | |||
| b5b86c298c | |||
| 66f2da2e0a | |||
| 04b1bea112 | |||
| 0bffcfccdd | |||
| 8e24041287 | |||
| f8630a8170 | |||
| cea21312f9 | |||
| c7a2ed73b7 | |||
| 882d2787ba | |||
| 54ef15dac8 | |||
| e995166863 | |||
| a890f2ecfa | |||
| af09390773 | |||
| 24bd0bc3b3 | |||
| 5bf7f59a19 | |||
| 15ed7d938e | |||
| 28ec28e026 | |||
| 7bdb9d877d | |||
| 4023f84b29 | |||
| 0f7fb98747 | |||
| 563852791d | |||
| a2d976dd03 | |||
| 823e08bf9d | |||
| ab65b87f3d | |||
| 8b15175347 | |||
| f946d87564 | |||
| 2389ce39ac | |||
| 1906a73574 | |||
| 5aa93d1b1f | |||
| 9fa74e9374 | |||
| 526491f2f7 | |||
| 9eb19cdf94 | |||
| 4f510bea17 | |||
| 614ebf05ea | |||
| 28e06c2a17 | |||
| e63a4ae540 | |||
| 76fc9a4f5a | |||
| 5da250d027 | |||
| ffc148ae39 | |||
| 61496b764a | |||
| 776b4fef19 | |||
| 7b4fa14f36 | |||
| 3d8c0fb776 | |||
| aaa1140d9a | |||
| 5e08a28f9b | |||
| b96a064f14 | |||
| ee66104098 | |||
| cef2c013d2 | |||
| 868fc2aba1 | |||
| b513b36cfd | |||
| 0ab6c0d546 | |||
| 0697a52218 | |||
| 20f3b33b24 | |||
| 3e642074ee | |||
| 44d89dfa7a | |||
| ebe7417f94 | |||
| ddb1a13573 | |||
| 61b1cc4dd7 | |||
| 28f3776451 | |||
| b3252ecdc1 | |||
| 6f213a2fa2 | |||
| 195f6fe37e | |||
| b1add54344 | |||
| 239c6202e2 | |||
| 932ee52ba7 | |||
| 38e6852140 | |||
| a339908497 | |||
| f6021ae0af | |||
| 09f9af4729 | |||
| 037027230a | |||
| d04b8d74a7 | |||
| 212d7fef05 | |||
| 5a329d81f9 | |||
| d8eec7ee38 | |||
| 8a5b84cf8b | |||
| e4ad3ee714 | |||
| 75aa674b99 | |||
| 575b2eaa2d | |||
| a7a5d10319 | |||
| 00293f90a4 | |||
| 1f000f071e | |||
| 4033c2a11b | |||
| 74e82e4684 | |||
| 7b6c74431b | |||
| a319808cfb | |||
| f01734122a | |||
| c459409c1b | |||
| 8614257135 | |||
| 07890336ab | |||
| 44efefcbeb | |||
| c2a38d17a4 | |||
| bdc43d7729 | |||
| 7feac02414 | |||
| 51e53b9f40 | |||
| 4dfafed5d9 | |||
| 8e8ae80550 | |||
| 4f41ae2763 | |||
| d2d150b21d | |||
| da3c76158e | |||
| b1f1bd2e54 | |||
| eac66ff63c | |||
| 7258bf0bcf | |||
| f181a24a0b | |||
| 91037dfb29 | |||
| 782b242ccb | |||
| f662082974 | |||
| 4a70fd9973 | |||
| f5b667f1c5 | |||
| efc508c39c | |||
| 1be5d6f4fe | |||
| 84bc2245f7 | |||
| 6c0c9d5d08 | |||
| dfa16c06a6 | |||
| 34dd6c69ab | |||
| 24a12d96e5 | |||
| 3f02b87df0 | |||
| c3da16f13d | |||
| 51b3c29b92 | |||
| a83b43a118 | |||
| 9d56e7925b | |||
| 7140f67a98 | |||
| 337673949b | |||
| 06ede94f07 | |||
| 1b764a2fde | |||
| 99a080f12a | |||
| 0139b8828f | |||
| 0f125a45f4 | |||
| 270cefc4b0 | |||
| a49a438536 | |||
| 6fe7510ae0 | |||
| 8a4b725f6d | |||
| aea028ff29 | |||
| f60b216de0 | |||
| d8609cb545 | |||
| 5b89ff626d | |||
| 4191032993 | |||
| 178bb8c22e | |||
| 9f358de241 | |||
| f7ca19afaa | |||
| 820c234934 | |||
| 5b98200ff4 | |||
| 2d83a76ed7 | |||
| db4754e98c | |||
| 6416e87f26 | |||
| 14e0ab3536 | |||
| a5591614d5 | |||
| 5b399ba107 | |||
| b197ab3566 | |||
| 0f41e8a2a1 | |||
| 468c8d39c3 | |||
| b6b7c2636f | |||
| 7cc21cb7bd | |||
| 5d5881f0f8 | |||
| 7e4bffd836 | |||
| 94522f8dd0 | |||
| 74fc7d3120 | |||
| 59a8a5c0b8 | |||
| 4b8d462ee6 | |||
| fe745a0a84 | |||
| 84a57322c2 | |||
| e4a37fbef1 | |||
| fd7174d03f | |||
| 3e95e09b67 | |||
| caef6489ae | |||
| 2de389aa05 | |||
| be5cc0ba03 | |||
| 5c87353833 | |||
| 55c1865638 | |||
| fe80a5ab66 | |||
| a92bbd867d | |||
| c8e4922194 | |||
| 9faf51a7ca | |||
| fd9c70c43f | |||
| ce3f804683 | |||
| 12ed3e2716 | |||
| 57ea6753d3 | |||
| 4864a25baf | |||
| 451f4a284c | |||
| 93ba31b27c | |||
| 40cd9b66a6 | |||
| 5d2dd881e3 | |||
| 95d7c77b28 | |||
| 02c7769422 | |||
| 69a0a8edef | |||
| 127f9c8274 | |||
| a69de9ba05 | |||
| 7c7ace009a | |||
| ea6dfd78ce | |||
| 800c81dc38 | |||
| 9f7aeac5e5 | |||
| ae3b1c0048 | |||
| b781f58e91 | |||
| ab78499bbd | |||
| 4f2bc5bc8f | |||
| 1144ae255f | |||
| e3a1b34376 | |||
| e151fdad92 | |||
| 2d9d4a1d88 | |||
| 8dd8486355 | |||
| c49f309204 | |||
| a9be2f18bd | |||
| 44d8324247 | |||
| 2207b0fa9f | |||
| 31fd863bd9 | |||
| 630a72f19f | |||
| b043073143 | |||
| fc8cc24c85 | |||
| e2ede0b5c3 | |||
| d0ce3439fe | |||
| 3560cf5f82 | |||
| 29587cc9da | |||
| a22f7bbb3a | |||
| 775db1409e | |||
| 54cbfbca8a | |||
| b175d43408 | |||
| ea083b0a5e | |||
| 966dd82d90 | |||
| 461b1931c0 | |||
| 53a58dda1c | |||
| 9846d710fe | |||
| aad32d11f5 | |||
| 6d3b250b4f | |||
| ebab932f71 | |||
| e9e2ef7da7 | |||
| 0fed66ad0f | |||
| c9b29e6b98 | |||
| 2963a540ef | |||
| 8852faea91 | |||
| 910b62a3d0 | |||
| b03c67e978 | |||
| 23fbd252db | |||
| 97f1c1f424 | |||
| fbd7daf50e | |||
| ea80f967b8 | |||
| a5c2308443 | |||
| fc5c5306c9 | |||
| 70e8515ea8 | |||
| bfe00e67a3 | |||
| fcb7459569 | |||
| 62c9d0877a | |||
| 9967731256 | |||
| 7529031368 | |||
| bdf271df80 | |||
| 209f6e1eda | |||
| 9b3159246f | |||
| cb596d823f | |||
| ab803d697c | |||
| 1c4b87cb97 | |||
| e5ec530a44 | |||
| 70703d4c51 | |||
| a7a6be90c8 | |||
| ed54f4e11e | |||
| 2cae92c6c3 | |||
| 28b870b4de | |||
| 66156ad245 | |||
| 1c73a11a20 | |||
| 6ef8a82599 | |||
| e46c60db2a | |||
| 2566b18a31 | |||
| 4fb76c0fac | |||
| 67e2ee0b65 | |||
| 6c0b4929c6 | |||
| 3149bea2c4 | |||
| 6a23fb2171 | |||
| fa7297aff0 | |||
| bf694d4724 | |||
| 6474044464 | |||
| 97b0a6cbfc | |||
| 04cd1b3f31 | |||
| 3d34aa5c29 | |||
| 545e2b4ec7 | |||
| 9ab7a315d1 | |||
| ae0629d284 | |||
| 46cb408e30 | |||
| c166b700d8 | |||
| 23fe1e2bed | |||
| fd4c7d6461 | |||
| 3128529784 | |||
| 50f5a097a2 | |||
| 09a9b8f1ac | |||
| 2e85118fb3 | |||
| 05014132b6 | |||
| 80b0c733f2 | |||
| fa9eba7d84 | |||
| 4c729982c7 | |||
| d44064cdd6 | |||
| 4e7232b82e | |||
| 205994bafe | |||
| cf6a77fbbb | |||
| a15712ef6e | |||
| f2cb3952af | |||
| 5e92cf33c5 | |||
| 8ddb08c9d9 | |||
| 7a3707797d | |||
| 76b61aa064 | |||
| 2bb0f8923c | |||
| 76cb5a56ea | |||
| 2891577f61 | |||
| 198c797a73 | |||
| d28429859e | |||
| bacaac3a12 | |||
| bb35eb7da7 | |||
| 3127fe10b8 | |||
| d16be43c05 | |||
| 27013d397c | |||
| 159f057751 | |||
| 350fb7829a | |||
| 3cfbce12a6 | |||
| ede6125a08 | |||
| 54659fd14d | |||
| cc98914e85 | |||
| 04507546ec | |||
| 7ee1f3dede | |||
| b3359e2a3b | |||
| 000355f223 | |||
| 028fb5c1f6 | |||
| 61547a4d68 | |||
| fc6cf1802e | |||
| 0f3bcbb92e | |||
| ab01e8a970 | |||
| 2bc753db41 | |||
| cee06411b5 | |||
| ea2ede2cf6 | |||
| 0188d5b5b4 | |||
| a97af76c0b | |||
| 1b35627570 | |||
| c39c3edb8e | |||
| fa43449aad | |||
| f7bac7ac8d | |||
| d1430a1e83 | |||
| 475fddc99a | |||
| f43d811e96 | |||
| 88ab001b25 | |||
| db65b09188 | |||
| 030c58dcb9 | |||
| 3041fe6faa | |||
| cd305dc26b | |||
| 499f2ee9ce | |||
| e1ceb10ee8 | |||
| f4524f8da3 | |||
| 850dfdeadb | |||
| 2a17a616a3 | |||
| 0fc5b4e17a | |||
| 446d8483a3 | |||
| 40938ae2d8 | |||
| 048b266f14 | |||
| 91161a6325 | |||
| a3893e1b4d | |||
| 16f18cb984 | |||
| bfb57d2c16 | |||
| 7085e286c0 | |||
| d42139ace6 | |||
| 1de2a1598e | |||
| 39b17e9933 | |||
| cb3cd93b39 | |||
| 3dee4ca6d9 | |||
| c9591cdde6 | |||
| 06cab84176 | |||
| 3dd3ed3af2 | |||
| 3fcfc19743 | |||
| 1891ed7bb0 | |||
| c5d720ab51 | |||
| d35d17a14c | |||
| 98a2f802c4 | |||
| 1582d54bb0 | |||
| 15729d9ffa | |||
| 1e096d96a2 | |||
| 3b1d98ba73 | |||
| 732ebbe0fe | |||
| 9b7527defc | |||
| 264b7dffb4 | |||
| 3467e8ce3d | |||
| 7816cac9fe | |||
| edbee4c4b6 | |||
| 13fadc8767 | |||
| 897a6e824f | |||
| 583f84d7cb | |||
| 03d6e06400 | |||
| 2b3c4538f1 | |||
| 8f0f0a5903 | |||
| 31b76f4f59 | |||
| 81fa241788 | |||
| 970be4496e | |||
| 3f86772ae2 | |||
| 6c88a36f8f | |||
| cd7baa11c9 | |||
| 97b05c8f1e | |||
| c287f0fdd9 | |||
| 26bb44e5f4 | |||
| 2203ef4f74 | |||
| c9f95d296a | |||
| 7092ba71de | |||
| a88287c67b | |||
| e054f43f5b | |||
| 52f9b15d64 | |||
| a924b2616a | |||
| bebff35401 | |||
| 11dc37dacc | |||
| 11446e8dfc | |||
| 915fd9e6c3 | |||
| 3d110a5f6f | |||
| 1c42b1d6ed | |||
| 2750523534 | |||
| d21c5e384d | |||
| ebdfc3c935 | |||
| 15ce5b7e9b | |||
| af099b484c | |||
| c4a5c8440a | |||
| 739b59610a | |||
| f8a6a533c2 | |||
| d498287fd7 | |||
| d8844b3f8c | |||
| 9827740b01 | |||
| 33a258b573 | |||
| a52770f010 | |||
| a76032ce8d | |||
| 06f49a5eb2 | |||
| e6c1e22197 | |||
| e754bce399 | |||
| df7cc2cc7f | |||
| b078358fa7 | |||
| 3127efef0c | |||
| 353f1bfb37 | |||
| 0b01f6f391 | |||
| bb874345cf | |||
| 75bf06128f | |||
| 540ea1bc7f | |||
| c3f2290d11 | |||
| e2afdc9c21 | |||
| 9fd2b9ac5f | |||
| 5726577fd4 | |||
| c7522f838c | |||
| 7dc966479c | |||
| 49d6fb0379 | |||
| 78d77c4223 | |||
| 97fcf562ba | |||
| 716b5ffa09 | |||
| 07b203db88 | |||
| 5f29d191d9 | |||
| 8c9a196409 | |||
| 258bf5c4e8 | |||
| 10b67ef9f1 | |||
| 46e7b6eecf | |||
| aad67f481a | |||
| f8650bb7a3 | |||
| 5843543338 | |||
| 5c41cd77f8 | |||
| 697bed6bb7 | |||
| 790c89b34d | |||
| 25e213ad8f | |||
| 38ffa48cf5 | |||
| 6ef65b8a85 | |||
| 8e82081176 | |||
| e55b7082be | |||
| 079114d70b | |||
| 1b4983fc90 | |||
| 7bff0ee145 | |||
| c8fc9c61a2 | |||
| e971596371 | |||
| 969d649b79 | |||
| e06a28b539 | |||
| 6fb429dd79 | |||
| 0d1707f053 | |||
| 30e44bfcc4 | |||
| ed0e21c0ef | |||
| a24bf64ffd | |||
| d24fab1182 | |||
| e58362ed1e | |||
| b6ebda707f | |||
| 60ecb39fb6 | |||
| 970c1a4bf1 | |||
| 31c04fec7c | |||
| 04df75ceeb | |||
| acee831165 | |||
| 323ce3952b | |||
| 10acff4ada | |||
| e709853ee6 | |||
| 8e0bebd1cd | |||
| f9a7a98a4a | |||
| 7d5667ee57 | |||
| c706699a0e | |||
| 5a86bf1448 | |||
| dde3d58eb1 | |||
| 83ef2b2cf8 | |||
| 9f9d7bbe87 | |||
| bcc08d3b5e | |||
| c182f060fa | |||
| b197da8770 |
+1
-1
@@ -207,7 +207,7 @@ before_script:
|
||||
# We need the official upstream for comparison
|
||||
git remote add upstream https://github.com/moodle/moodle.git;
|
||||
|
||||
git fetch upstream master;
|
||||
git fetch upstream MOODLE_35_STABLE;
|
||||
export GIT_PREVIOUS_COMMIT="`git merge-base FETCH_HEAD $TRAVIS_COMMIT`";
|
||||
export GIT_COMMIT="$TRAVIS_COMMIT";
|
||||
export UPSTREAM_FETCH_HEAD=`git rev-parse FETCH_HEAD`
|
||||
|
||||
+5
-5
@@ -14,16 +14,16 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
/**
|
||||
* Privacy Subsystem implementation for core_register.
|
||||
* Privacy Subsystem implementation for core_admin.
|
||||
*
|
||||
* @package core_register
|
||||
* @package core_admin
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_register\privacy;
|
||||
namespace core_admin\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
/**
|
||||
* Privacy Subsystem for core_register implementing null_provider.
|
||||
* Privacy Subsystem for core_admin implementing null_provider.
|
||||
*
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
@@ -38,4 +38,4 @@ class provider implements \core_privacy\local\metadata\null_provider {
|
||||
public static function get_reason() : string {
|
||||
return 'privacy:metadata';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ if (\core\hub\registration::is_registered()) {
|
||||
echo $OUTPUT->heading(get_string('registerwithmoodleorg', 'admin'));
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('core', 'register');
|
||||
$renderer = $PAGE->get_renderer('core', 'admin');
|
||||
echo $renderer->moodleorg_registration_message();
|
||||
|
||||
$siteregistrationform->display();
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// This file is part of Moodle - http://moodle.org/ //
|
||||
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
|
||||
// //
|
||||
// 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/>. //
|
||||
// //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Registration renderer.
|
||||
* @package moodle
|
||||
* @subpackage registration
|
||||
* @copyright 2010 Moodle Pty Ltd (http://moodle.com)
|
||||
* @author Jerome Mouneyrac
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_register_renderer extends plugin_renderer_base {
|
||||
|
||||
/**
|
||||
* Display message about the benefits of registering on Moodle.org
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function moodleorg_registration_message() {
|
||||
return format_text(get_string('registermoodlenet', 'admin'), FORMAT_HTML, ['noclean' => true]);
|
||||
}
|
||||
}
|
||||
@@ -2084,4 +2084,13 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
return $this->warning(get_string('legacylogginginuse'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display message about the benefits of registering on Moodle.org
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function moodleorg_registration_message() {
|
||||
return format_text(get_string('registermoodlenet', 'admin'), FORMAT_HTML, ['noclean' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,9 +528,11 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
|
||||
if (!$this->disabled) {
|
||||
$output .= '<input type="hidden" name="contextlevel' . $cl . '" value="0" />';
|
||||
}
|
||||
$output .= '<input type="checkbox" id="cl' . $cl . '" name="contextlevel' . $cl .
|
||||
$output .= '<div class="form-check justify-content-start w-100">';
|
||||
$output .= '<input class="form-check-input" type="checkbox" id="cl' . $cl . '" name="contextlevel' . $cl .
|
||||
'" value="1" ' . $extraarguments . '/> ';
|
||||
$output .= '<label for="cl' . $cl . '">' . $clname . "</label><br />\n";
|
||||
$output .= '<label class="form-check-label" for="cl' . $cl . '">' . $clname . "</label>\n";
|
||||
$output .= '</div>';
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
<?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/>.
|
||||
/**
|
||||
* Privacy Subsystem implementation for core_role.
|
||||
*
|
||||
* @package core_role
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_role\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use \core_privacy\local\metadata\collection;
|
||||
use \core_privacy\local\request\contextlist;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\transform;
|
||||
use \core_privacy\local\request\writer;
|
||||
|
||||
/**
|
||||
* Privacy provider for core_role.
|
||||
*
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @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\provider,
|
||||
\core_privacy\local\request\subsystem\plugin_provider,
|
||||
\core_privacy\local\request\user_preference_provider {
|
||||
|
||||
/**
|
||||
* Get information about the user data stored by this plugin.
|
||||
*
|
||||
* @param collection $collection An object for storing metadata.
|
||||
* @return collection The metadata.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$rolecapabilities = [
|
||||
'roleid' => 'privacy:metadata:role_capabilities:roleid',
|
||||
'capability' => 'privacy:metadata:role_capabilities:capability',
|
||||
'permission' => 'privacy:metadata:role_capabilities:permission',
|
||||
'timemodified' => 'privacy:metadata:role_capabilities:timemodified',
|
||||
'modifierid' => 'privacy:metadata:role_capabilities:modifierid'
|
||||
];
|
||||
$roleassignments = [
|
||||
'roleid' => 'privacy:metadata:role_assignments:roleid',
|
||||
'userid' => 'privacy:metadata:role_assignments:userid',
|
||||
'timemodified' => 'privacy:metadata:role_assignments:timemodified',
|
||||
'modifierid' => 'privacy:metadata:role_assignments:modifierid',
|
||||
'component' => 'privacy:metadata:role_assignments:component',
|
||||
'itemid' => 'privacy:metadata:role_assignments:itemid'
|
||||
];
|
||||
$collection->add_database_table('role_capabilities', $rolecapabilities,
|
||||
'privacy:metadata:role_capabilities:tableexplanation');
|
||||
$collection->add_database_table('role_assignments', $roleassignments,
|
||||
'privacy:metadata:role_assignments:tableexplanation');
|
||||
|
||||
$collection->add_user_preference('definerole_showadvanced',
|
||||
'privacy:metadata:preference:showadvanced');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
/**
|
||||
* Export all user preferences for the plugin.
|
||||
*
|
||||
* @param int $userid The userid of the user whose data is to be exported.
|
||||
*/
|
||||
public static function export_user_preferences(int $userid) {
|
||||
$showadvanced = get_user_preferences('definerole_showadvanced', null, $userid);
|
||||
if ($showadvanced !== null) {
|
||||
writer::export_user_preference('core_role',
|
||||
'definerole_showadvanced',
|
||||
transform::yesno($showadvanced),
|
||||
get_string('privacy:metadata:preference:showadvanced', 'core_role')
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return all contexts for this userid.
|
||||
*
|
||||
* @param int $userid The user ID.
|
||||
* @return contextlist The list of context IDs.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
global $DB;
|
||||
|
||||
$contextlist = new contextlist();
|
||||
|
||||
// The role_capabilities table contains user data.
|
||||
$contexts = [
|
||||
CONTEXT_SYSTEM,
|
||||
CONTEXT_USER,
|
||||
CONTEXT_COURSECAT,
|
||||
CONTEXT_COURSE,
|
||||
CONTEXT_MODULE,
|
||||
CONTEXT_BLOCK
|
||||
];
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {context} ctx
|
||||
JOIN {role_capabilities} rc
|
||||
ON rc.contextid = ctx.id
|
||||
AND ((ctx.contextlevel {$insql} AND rc.modifierid = :modifierid)
|
||||
OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))";
|
||||
$params = [
|
||||
'modifierid' => $userid,
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'userid' => $userid
|
||||
];
|
||||
$params += $inparams;
|
||||
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
// The role_assignments table contains user data.
|
||||
$contexts = [
|
||||
CONTEXT_SYSTEM,
|
||||
CONTEXT_USER,
|
||||
CONTEXT_COURSECAT,
|
||||
CONTEXT_COURSE,
|
||||
CONTEXT_MODULE,
|
||||
CONTEXT_BLOCK
|
||||
];
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'modifierid' => $userid
|
||||
];
|
||||
$params += $inparams;
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx
|
||||
ON ctx.id = ra.contextid
|
||||
AND ctx.contextlevel {$insql}
|
||||
WHERE (ra.userid = :userid
|
||||
OR ra.modifierid = :modifierid)
|
||||
AND ra.component != 'tool_cohortroles'";
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
return $contextlist;
|
||||
}
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The list of approved contexts for a user.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
if (empty($contextlist)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rolesnames = self::get_roles_name();
|
||||
$userid = $contextlist->get_user()->id;
|
||||
$ctxfields = \context_helper::get_preload_record_columns_sql('ctx');
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
|
||||
// Role Assignments export data.
|
||||
$contexts = [
|
||||
CONTEXT_SYSTEM,
|
||||
CONTEXT_USER,
|
||||
CONTEXT_COURSECAT,
|
||||
CONTEXT_COURSE,
|
||||
CONTEXT_MODULE,
|
||||
CONTEXT_BLOCK
|
||||
];
|
||||
list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
|
||||
$sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, $ctxfields
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx
|
||||
ON ctx.id = ra.contextid
|
||||
AND ctx.contextlevel {$inctxsql}
|
||||
AND (ra.userid = :userid OR ra.modifierid = :modifierid)
|
||||
AND ra.component != 'tool_cohortroles'
|
||||
JOIN {role} r
|
||||
ON r.id = ra.roleid
|
||||
WHERE ctx.id {$insql}";
|
||||
$params = ['userid' => $userid, 'modifierid' => $userid];
|
||||
$params += $inparams;
|
||||
$params += $ctxparams;
|
||||
$assignments = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($assignments as $assignment) {
|
||||
\context_helper::preload_from_record($assignment);
|
||||
$alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
|
||||
'timemodified' => transform::datetime($assignment->timemodified),
|
||||
'userid' => transform::user($assignment->userid),
|
||||
'modifierid' => transform::user($assignment->modifierid)
|
||||
];
|
||||
}
|
||||
$assignments->close();
|
||||
if (!empty($alldata)) {
|
||||
array_walk($alldata, function($roledata, $contextid) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
array_walk($roledata, function($data, $rolename) use ($context) {
|
||||
writer::with_context($context)->export_data(
|
||||
[get_string('privacy:metadata:role_assignments', 'core_role'), $rolename],
|
||||
(object)$data);
|
||||
});
|
||||
});
|
||||
unset($alldata);
|
||||
}
|
||||
|
||||
// Role Capabilities export data.
|
||||
$strpermissions = self::get_permissions_name();
|
||||
$contexts = [
|
||||
CONTEXT_SYSTEM,
|
||||
CONTEXT_USER,
|
||||
CONTEXT_COURSECAT,
|
||||
CONTEXT_COURSE,
|
||||
CONTEXT_MODULE,
|
||||
CONTEXT_BLOCK
|
||||
];
|
||||
list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
|
||||
$sql = "SELECT rc.id, rc.contextid, rc.capability, rc.permission, rc.timemodified, rc.roleid, $ctxfields
|
||||
FROM {context} ctx
|
||||
JOIN {role_capabilities} rc
|
||||
ON rc.contextid = ctx.id
|
||||
AND ((ctx.contextlevel {$inctxsql} AND rc.modifierid = :modifierid)
|
||||
OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))
|
||||
WHERE ctx.id {$insql}";
|
||||
$params = [
|
||||
'modifierid' => $userid,
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'userid' => $userid
|
||||
];
|
||||
$params += $inparams;
|
||||
$params += $ctxparams;
|
||||
$capabilities = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($capabilities as $capability) {
|
||||
\context_helper::preload_from_record($capability);
|
||||
$alldata[$capability->contextid][$rolesnames[$capability->roleid]][] = (object)[
|
||||
'timemodified' => transform::datetime($capability->timemodified),
|
||||
'capability' => $capability->capability,
|
||||
'permission' => $strpermissions[$capability->permission]
|
||||
];
|
||||
}
|
||||
$capabilities->close();
|
||||
if (!empty($alldata)) {
|
||||
array_walk($alldata, function($capdata, $contextid) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
array_walk($capdata, function($data, $rolename) use ($context) {
|
||||
writer::with_context($context)->export_data(
|
||||
[get_string('privacy:metadata:role_capabilities', 'core_role'), $rolename],
|
||||
(object)$data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Exports the data relating to tool_cohortroles component on role assignments by
|
||||
* Assign user roles to cohort feature.
|
||||
*
|
||||
* @param int $userid The user ID.
|
||||
*/
|
||||
public static function export_user_role_to_cohort(int $userid) {
|
||||
global $DB;
|
||||
|
||||
$rolesnames = self::get_roles_name();
|
||||
$sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, r.id as roleid
|
||||
FROM {role_assignments} ra
|
||||
JOIN {context} ctx
|
||||
ON ctx.id = ra.contextid
|
||||
AND ctx.contextlevel = :contextlevel
|
||||
AND ra.component = 'tool_cohortroles'
|
||||
JOIN {role} r
|
||||
ON r.id = ra.roleid
|
||||
WHERE ctx.instanceid = :instanceid
|
||||
OR ra.userid = :userid";
|
||||
$params = ['userid' => $userid, 'instanceid' => $userid, 'contextlevel' => CONTEXT_USER];
|
||||
$assignments = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($assignments as $assignment) {
|
||||
$alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
|
||||
'timemodified' => transform::datetime($assignment->timemodified),
|
||||
'userid' => transform::user($assignment->userid),
|
||||
'modifierid' => transform::user($assignment->modifierid)
|
||||
];
|
||||
}
|
||||
$assignments->close();
|
||||
if (!empty($alldata)) {
|
||||
array_walk($alldata, function($roledata, $contextid) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
array_walk($roledata, function($data, $rolename) use ($context) {
|
||||
writer::with_context($context)->export_related_data(
|
||||
[get_string('privacy:metadata:role_cohortroles', 'core_role'), $rolename], 'cohortroles',
|
||||
(object)$data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Delete all user data for this context.
|
||||
*
|
||||
* @param \context $context The context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
global $DB;
|
||||
|
||||
// Don't remove data from role_capabilities.
|
||||
// Because this data affects the whole Moodle, there are override capabilities.
|
||||
// Don't belong to the modifier user.
|
||||
|
||||
// Remove data from role_assignments.
|
||||
$DB->delete_records('role_assignments', ['contextid' => $context->id]);
|
||||
}
|
||||
/**
|
||||
* Delete all user data for this user only.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The list of approved contexts for a user.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
// Don't remove data from role_capabilities.
|
||||
// Because this data affects the whole Moodle, there are override capabilities.
|
||||
// Don't belong to the modifier user.
|
||||
|
||||
// Remove data from role_assignments.
|
||||
if (empty($contextlist->count())) {
|
||||
return;
|
||||
}
|
||||
$userid = $contextlist->get_user()->id;
|
||||
foreach ($contextlist->get_contexts() as $context) {
|
||||
// Only delete the roles assignments where the user is assigned in all contexts.
|
||||
$DB->delete_records('role_assignments', ['userid' => $userid, 'contextid' => $context->id]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Delete user entries in role_assignments related to the feature
|
||||
* Assign user roles to cohort feature.
|
||||
*
|
||||
* @param int $userid The user ID.
|
||||
*/
|
||||
public static function delete_user_role_to_cohort(int $userid) {
|
||||
global $DB;
|
||||
|
||||
// Delete entries where userid is a mentor by tool_cohortroles.
|
||||
$DB->delete_records('role_assignments', ['userid' => $userid, 'component' => 'tool_cohortroles']);
|
||||
}
|
||||
/**
|
||||
* Get all the localised roles name in a simple array.
|
||||
*
|
||||
* @return array Array of name of the roles by roleid.
|
||||
*/
|
||||
protected static function get_roles_name() {
|
||||
$roles = role_fix_names(get_all_roles(), \context_system::instance(), ROLENAME_ORIGINAL);
|
||||
$rolesnames = array();
|
||||
foreach ($roles as $role) {
|
||||
$rolesnames[$role->id] = $role->localname;
|
||||
}
|
||||
return $rolesnames;
|
||||
}
|
||||
/**
|
||||
* Get all the permissions name in a simple array.
|
||||
*
|
||||
* @return array Array of permissions name.
|
||||
*/
|
||||
protected static function get_permissions_name() {
|
||||
$strpermissions = array(
|
||||
CAP_INHERIT => get_string('inherit', 'role'),
|
||||
CAP_ALLOW => get_string('allow', 'role'),
|
||||
CAP_PREVENT => get_string('prevent', 'role'),
|
||||
CAP_PROHIBIT => get_string('prohibit', 'role')
|
||||
);
|
||||
return $strpermissions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
<?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/>.
|
||||
/**
|
||||
* Privacy test for core_role
|
||||
*
|
||||
* @package core_role
|
||||
* @category test
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
use \core_role\privacy\provider;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\writer;
|
||||
use \core_privacy\tests\provider_testcase;
|
||||
use \core_privacy\local\request\transform;
|
||||
use \tool_cohortroles\api;
|
||||
|
||||
/**
|
||||
* Privacy test for core_role
|
||||
*
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_role_privacy_testcase extends provider_testcase {
|
||||
/**
|
||||
* Test to check export_user_preferences.
|
||||
* returns user preferences data.
|
||||
*/
|
||||
public function test_export_user_preferences() {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$showadvanced = 1;
|
||||
set_user_preference('definerole_showadvanced', $showadvanced);
|
||||
provider::export_user_preferences($user->id);
|
||||
$writer = writer::with_context(\context_system::instance());
|
||||
$prefs = $writer->get_user_preferences('core_role');
|
||||
$this->assertEquals(transform::yesno($showadvanced), transform::yesno($prefs->definerole_showadvanced->value));
|
||||
$this->assertEquals(get_string('privacy:metadata:preference:showadvanced', 'core_role'),
|
||||
$prefs->definerole_showadvanced->description);
|
||||
}
|
||||
/**
|
||||
* Check all contexts are returned if there is any user data for this user.
|
||||
*/
|
||||
public function test_get_contexts_for_userid() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->assertEmpty(provider::get_contexts_for_userid($user->id));
|
||||
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$usercontext2 = \context_user::instance($user2->id);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$coursecat = $this->getDataGenerator()->create_category();
|
||||
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
|
||||
$cmcontext = \context_module::instance($cm->cmid);
|
||||
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
|
||||
$cmcontext2 = \context_module::instance($page->cmid);
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$coursecontext2 = \context_course::instance($course2->id);
|
||||
$coursecatcontext = \context_coursecat::instance($coursecat->id);
|
||||
$systemcontext = \context_system::instance();
|
||||
$block = $this->getDataGenerator()->create_block('online_users');
|
||||
$blockcontext = \context_block::instance($block->id);
|
||||
|
||||
$student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
|
||||
$manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
|
||||
|
||||
// Role assignments, where the user is assigned.
|
||||
role_assign($student->id, $user->id, $cmcontext2->id);
|
||||
role_assign($student->id, $user->id, $coursecontext2->id);
|
||||
role_assign($student->id, $user->id, $blockcontext->id);
|
||||
role_assign($manager->id, $user->id, $usercontext2->id);
|
||||
// Role assignments, where the user makes assignments.
|
||||
$this->setUser($user);
|
||||
role_assign($student->id, $user2->id, $coursecontext->id);
|
||||
role_assign($manager->id, $user2->id, $coursecatcontext->id);
|
||||
role_assign($manager->id, $user2->id, $systemcontext->id);
|
||||
|
||||
// Role capabilities.
|
||||
$this->setUser($user);
|
||||
$result = assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $cmcontext->id);
|
||||
|
||||
$contextlist = provider::get_contexts_for_userid($user->id)->get_contextids();
|
||||
$this->assertCount(8, $contextlist);
|
||||
$this->assertTrue(in_array($cmcontext->id, $contextlist));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that user data is exported correctly.
|
||||
*/
|
||||
public function test_export_user_data() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$usercontext2 = \context_user::instance($user2->id);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$coursecat = $this->getDataGenerator()->create_category();
|
||||
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
|
||||
$cmcontext = \context_module::instance($cm->cmid);
|
||||
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
|
||||
$cmcontext2 = \context_module::instance($page->cmid);
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$coursecontext2 = \context_course::instance($course2->id);
|
||||
$coursecatcontext = \context_coursecat::instance($coursecat->id);
|
||||
$systemcontext = \context_system::instance();
|
||||
$block = $this->getDataGenerator()->create_block('online_users');
|
||||
$blockcontext = \context_block::instance($block->id);
|
||||
|
||||
$student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
|
||||
$manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
|
||||
$rolesnames = self::get_roles_name();
|
||||
|
||||
$subcontextstudent = [
|
||||
get_string('privacy:metadata:role_assignments', 'core_role'),
|
||||
$rolesnames[$student->id]
|
||||
];
|
||||
$subcontextmanager = [
|
||||
get_string('privacy:metadata:role_assignments', 'core_role'),
|
||||
$rolesnames[$manager->id]
|
||||
];
|
||||
$subcontextrc = [
|
||||
get_string('privacy:metadata:role_capabilities', 'core_role'),
|
||||
$rolesnames[$student->id]
|
||||
];
|
||||
|
||||
// Test over role assignments.
|
||||
// Where the user is assigned.
|
||||
role_assign($student->id, $user->id, $cmcontext2->id);
|
||||
role_assign($student->id, $user->id, $coursecontext2->id);
|
||||
role_assign($student->id, $user->id, $blockcontext->id);
|
||||
role_assign($manager->id, $user->id, $usercontext2->id);
|
||||
// Where the user makes assignments.
|
||||
$this->setUser($user);
|
||||
role_assign($manager->id, $user2->id, $coursecatcontext->id);
|
||||
role_assign($manager->id, $user2->id, $systemcontext->id);
|
||||
|
||||
// Test overridable roles in module, course, category, user, system and block.
|
||||
assign_capability('moodle/backup:backupactivity', CAP_ALLOW, $student->id, $cmcontext->id, true);
|
||||
assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $coursecontext->id, true);
|
||||
assign_capability('moodle/category:manage', CAP_ALLOW, $student->id, $coursecatcontext->id, true);
|
||||
assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $systemcontext->id, true);
|
||||
assign_capability('moodle/block:edit', CAP_ALLOW, $student->id, $blockcontext->id, true);
|
||||
assign_capability('moodle/competency:evidencedelete', CAP_ALLOW, $student->id, $usercontext2->id, true);
|
||||
|
||||
// Retrieve the user's context ids.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$approvedcontextlist = new approved_contextlist($user, 'core_role', $contextlist->get_contextids());
|
||||
|
||||
$strpermissions = array(
|
||||
CAP_INHERIT => get_string('inherit', 'role'),
|
||||
CAP_ALLOW => get_string('allow', 'role'),
|
||||
CAP_PREVENT => get_string('prevent', 'role'),
|
||||
CAP_PROHIBIT => get_string('prohibit', 'role')
|
||||
);
|
||||
// Retrieve role capabilities and role assignments.
|
||||
provider::export_user_data($approvedcontextlist);
|
||||
foreach ($contextlist as $context) {
|
||||
$writer = writer::with_context($context);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
if ($context->contextlevel == CONTEXT_MODULE) {
|
||||
if ($data = $writer->get_data($subcontextstudent)) {
|
||||
$this->assertEquals($user->id, reset($data)->userid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/backup:backupactivity', reset($data)->capability);
|
||||
$this->assertEquals($strpermissions[CAP_ALLOW], reset($data)->permission);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_COURSE) {
|
||||
if ($data = $writer->get_data($subcontextstudent)) {
|
||||
$this->assertEquals($user->id, reset($data)->userid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/backup:backupcourse', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_COURSECAT) {
|
||||
if ($data = $writer->get_data($subcontextmanager)) {
|
||||
$this->assertEquals($user->id, reset($data)->modifierid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/category:manage', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
if ($data = $writer->get_data($subcontextmanager)) {
|
||||
$this->assertEquals($user->id, reset($data)->modifierid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/backup:backupcourse', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_BLOCK) {
|
||||
if ($data = $writer->get_data($subcontextstudent)) {
|
||||
$this->assertEquals($user->id, reset($data)->userid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/block:edit', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
if ($context->contextlevel == CONTEXT_USER) {
|
||||
if ($data = $writer->get_data($subcontextmanager)) {
|
||||
$this->assertEquals($user->id, reset($data)->userid);
|
||||
}
|
||||
if ($data = $writer->get_data($subcontextrc)) {
|
||||
$this->assertEquals('moodle/competency:evidencedelete', reset($data)->capability);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Test for provider::delete_data_for_all_users_in_context().
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$usercontext2 = \context_user::instance($user2->id);
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$coursecat = $this->getDataGenerator()->create_category();
|
||||
$coursecatcontext = \context_coursecat::instance($coursecat->id);
|
||||
$systemcontext = \context_system::instance();
|
||||
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
|
||||
$cmcontext = \context_module::instance($cm->cmid);
|
||||
$student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
|
||||
$manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
|
||||
$block = $this->getDataGenerator()->create_block('online_users');
|
||||
$blockcontext = \context_block::instance($block->id);
|
||||
|
||||
// Role assignments CONTEXT_COURSE.
|
||||
role_assign($student->id, $user->id, $coursecontext->id);
|
||||
role_assign($student->id, $user2->id, $coursecontext->id);
|
||||
role_assign($student->id, $user3->id, $coursecontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecontext->id]);
|
||||
$this->assertEquals(3, $count);
|
||||
// Role assignments CONTEXT_COURSECAT.
|
||||
role_assign($student->id, $user2->id, $coursecatcontext->id);
|
||||
role_assign($student->id, $user3->id, $coursecatcontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
|
||||
$this->assertEquals(2, $count);
|
||||
// Role assignments CONTEXT_SYSTEM.
|
||||
role_assign($student->id, $user->id, $systemcontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
// Role assignments CONTEXT_MODULE.
|
||||
role_assign($student->id, $user->id, $cmcontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
// Role assigments CONTEXT_BLOCK.
|
||||
role_assign($student->id, $user->id, $blockcontext->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $blockcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
// Role assigments CONTEXT_USER.
|
||||
role_assign($manager->id, $user->id, $usercontext2->id);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $usercontext2->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
|
||||
// Delete data based on CONTEXT_COURSE context.
|
||||
provider::delete_data_for_all_users_in_context($coursecontext);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecontext->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// Check it is not removing data on other contexts.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
|
||||
$this->assertEquals(2, $count);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
|
||||
$this->assertEquals(1, $count);
|
||||
// Delete data based on CONTEXT_COURSECAT context.
|
||||
provider::delete_data_for_all_users_in_context($coursecatcontext);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// Delete data based on CONTEXT_SYSTEM context.
|
||||
provider::delete_data_for_all_users_in_context($systemcontext);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// Delete data based on CONTEXT_MODULE context.
|
||||
provider::delete_data_for_all_users_in_context($cmcontext);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// Delete data based on CONTEXT_BLOCK context.
|
||||
provider::delete_data_for_all_users_in_context($usercontext2);
|
||||
// After deletion, the role_assignments entries for this context should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['contextid' => $usercontext2->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
}
|
||||
/**
|
||||
* Test for provider::delete_data_for_user().
|
||||
*/
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$usercontext2 = \context_user::instance($user2->id);
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
$usercontext3 = \context_user::instance($user3->id);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$course3 = $this->getDataGenerator()->create_course();
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$coursecontext2 = \context_course::instance($course2->id);
|
||||
$coursecontext3 = \context_course::instance($course3->id);
|
||||
$coursecat = $this->getDataGenerator()->create_category();
|
||||
$coursecatcontext = \context_coursecat::instance($coursecat->id);
|
||||
$systemcontext = \context_system::instance();
|
||||
$cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
|
||||
$cmcontext = \context_module::instance($cm->cmid);
|
||||
$student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
|
||||
$manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
|
||||
$block = $this->getDataGenerator()->create_block('online_users');
|
||||
$blockcontext = \context_block::instance($block->id);
|
||||
|
||||
// Role assignments, Where the user is assigned.
|
||||
role_assign($student->id, $user->id, $coursecontext->id);
|
||||
role_assign($student->id, $user->id, $coursecontext2->id);
|
||||
role_assign($student->id, $user->id, $coursecatcontext->id);
|
||||
role_assign($student->id, $user->id, $cmcontext->id);
|
||||
role_assign($student->id, $user->id, $systemcontext->id);
|
||||
role_assign($student->id, $user->id, $blockcontext->id);
|
||||
role_assign($manager->id, $user->id, $usercontext2->id);
|
||||
role_assign($manager->id, $user->id, $usercontext3->id);
|
||||
$count = $DB->count_records('role_assignments', ['userid' => $user->id]);
|
||||
$this->assertEquals(8, $count);
|
||||
// Role assignments, where the user makes assignments.
|
||||
$this->setUser($user);
|
||||
role_assign($student->id, $user2->id, $coursecontext3->id);
|
||||
role_assign($student->id, $user3->id, $coursecontext3->id);
|
||||
$count = $DB->count_records('role_assignments', ['modifierid' => $user->id]);
|
||||
$this->assertEquals(2, $count);
|
||||
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$approvedcontextlist = new approved_contextlist($user, 'core_role', $contextlist->get_contextids());
|
||||
provider::delete_data_for_user($approvedcontextlist);
|
||||
// After deletion, the role_assignments assigned to the user should have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['userid' => $user->id]);
|
||||
$this->assertEquals(0, $count);
|
||||
// After deletion, the role_assignments assigned by the user should not have been deleted.
|
||||
$count = $DB->count_records('role_assignments', ['modifierid' => $user->id]);
|
||||
$this->assertEquals(2, $count);
|
||||
}
|
||||
/**
|
||||
* Export for a user with a key against a script where no instance is specified.
|
||||
*/
|
||||
public function test_export_user_role_to_cohort() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
// Assign user roles to cohort.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$contextuser = \context_user::instance($user->id);
|
||||
$teacher = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
|
||||
$cohort = $this->getDataGenerator()->create_cohort();
|
||||
$userassignover = $this->getDataGenerator()->create_user();
|
||||
$contextuserassignover = \context_user::instance($userassignover->id);
|
||||
cohort_add_member($cohort->id, $userassignover->id);
|
||||
$this->setAdminUser();
|
||||
$params = (object) array(
|
||||
'userid' => $user->id,
|
||||
'roleid' => $teacher->id,
|
||||
'cohortid' => $cohort->id
|
||||
);
|
||||
api::create_cohort_role_assignment($params);
|
||||
api::sync_all_cohort_roles();
|
||||
$rolesnames = self::get_roles_name();
|
||||
$subcontextteacher = [
|
||||
get_string('privacy:metadata:role_cohortroles', 'core_role'),
|
||||
$rolesnames[$teacher->id]
|
||||
];
|
||||
// Test User is assigned role teacher to cohort.
|
||||
provider::export_user_role_to_cohort($user->id);
|
||||
$writer = writer::with_context($contextuserassignover);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
$exported = $writer->get_related_data($subcontextteacher, 'cohortroles');
|
||||
$this->assertEquals($user->id, reset($exported)->userid);
|
||||
|
||||
// Test User is member of a cohort which User2 is assigned to role to this cohort.
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$cohort2 = $this->getDataGenerator()->create_cohort();
|
||||
cohort_add_member($cohort2->id, $user->id);
|
||||
$params = (object) array(
|
||||
'userid' => $user2->id,
|
||||
'roleid' => $teacher->id,
|
||||
'cohortid' => $cohort2->id
|
||||
);
|
||||
api::create_cohort_role_assignment($params);
|
||||
api::sync_all_cohort_roles();
|
||||
provider::export_user_role_to_cohort($user->id);
|
||||
$writer = writer::with_context($contextuser);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
$exported = $writer->get_related_data($subcontextteacher, 'cohortroles');
|
||||
$this->assertEquals($user2->id, reset($exported)->userid);
|
||||
}
|
||||
/**
|
||||
* Test for provider::delete_user_role_to_cohort().
|
||||
*/
|
||||
public function test_delete_user_role_to_cohort() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
// Assign user roles to cohort.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$user3 = $this->getDataGenerator()->create_user();
|
||||
$user4 = $this->getDataGenerator()->create_user();
|
||||
$teacher = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
|
||||
$cohort = $this->getDataGenerator()->create_cohort();
|
||||
cohort_add_member($cohort->id, $user2->id);
|
||||
cohort_add_member($cohort->id, $user3->id);
|
||||
cohort_add_member($cohort->id, $user4->id);
|
||||
$this->setAdminUser();
|
||||
$params = (object) array(
|
||||
'userid' => $user->id,
|
||||
'roleid' => $teacher->id,
|
||||
'cohortid' => $cohort->id
|
||||
);
|
||||
api::create_cohort_role_assignment($params);
|
||||
api::sync_all_cohort_roles();
|
||||
|
||||
$count = $DB->count_records('role_assignments', ['userid' => $user->id, 'component' => 'tool_cohortroles']);
|
||||
$this->assertEquals(3, $count);
|
||||
|
||||
provider::delete_user_role_to_cohort($user->id);
|
||||
$count = $DB->count_records('role_assignments', ['userid' => $user->id, 'component' => 'tool_cohortroles']);
|
||||
$this->assertEquals(0, $count);
|
||||
}
|
||||
/**
|
||||
* Supoort function to get all the localised roles name
|
||||
* in a simple array for testing.
|
||||
*
|
||||
* @return array Array of name of the roles by roleid.
|
||||
*/
|
||||
protected static function get_roles_name() {
|
||||
$roles = role_fix_names(get_all_roles(), \context_system::instance(), ROLENAME_ORIGINAL);
|
||||
$rolesnames = array();
|
||||
foreach ($roles as $role) {
|
||||
$rolesnames[$role->id] = $role->localname;
|
||||
}
|
||||
return $rolesnames;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
|
||||
$temp->add(new admin_setting_configexecutable('pathtosassc', new lang_string('pathtosassc', 'admin'), new lang_string('pathtosassc_help', 'admin'), ''));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('forceclean', new lang_string('forceclean', 'core_admin'),
|
||||
new lang_string('forceclean_desc', 'core_admin'), 0));
|
||||
|
||||
$ADMIN->add('experimental', $temp);
|
||||
|
||||
// "debugging" settingpage
|
||||
@@ -30,7 +33,9 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$ADMIN->add('development', $temp);
|
||||
|
||||
// "Profiling" settingpage (conditionally if the 'xhprof' extension is available only).
|
||||
$xhprofenabled = extension_loaded('xhprof') || extension_loaded('tideways');
|
||||
$xhprofenabled = extension_loaded('tideways_xhprof');
|
||||
$xhprofenabled = $xhprofenabled || extension_loaded('tideways');
|
||||
$xhprofenabled = $xhprofenabled || extension_loaded('xhprof');
|
||||
$temp = new admin_settingpage('profiling', new lang_string('profiling', 'admin'), 'moodle/site:config', !$xhprofenabled);
|
||||
// Main profiling switch.
|
||||
$temp->add(new admin_setting_configcheckbox('profilingenabled', new lang_string('profilingenabled', 'admin'), new lang_string('profilingenabled_help', 'admin'), false));
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Adds privacy and policies links to admin tree.
|
||||
*
|
||||
* @package core_privacy
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($hassiteconfig) {
|
||||
// Privacy settings.
|
||||
$temp = new admin_settingpage('privacysettings', new lang_string('privacysettings', 'admin'));
|
||||
|
||||
$options = array(
|
||||
0 => get_string('no'),
|
||||
1 => get_string('yes')
|
||||
);
|
||||
$url = new moodle_url('/admin/settings.php?section=supportcontact');
|
||||
$url = $url->out();
|
||||
$setting = new admin_setting_configselect('agedigitalconsentverification',
|
||||
new lang_string('agedigitalconsentverification', 'admin'),
|
||||
new lang_string('agedigitalconsentverification_desc', 'admin', $url), 0, $options);
|
||||
$setting->set_force_ltr(true);
|
||||
$temp->add($setting);
|
||||
|
||||
$setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
|
||||
new lang_string('ageofdigitalconsentmap', 'admin'),
|
||||
new lang_string('ageofdigitalconsentmap_desc', 'admin'),
|
||||
// See {@link https://gdpr-info.eu/art-8-gdpr/}.
|
||||
implode(PHP_EOL, [
|
||||
'*, 16',
|
||||
'AT, 14',
|
||||
'CZ, 13',
|
||||
'DE, 14',
|
||||
'DK, 13',
|
||||
'ES, 13',
|
||||
'FI, 15',
|
||||
'GB, 13',
|
||||
'HU, 14',
|
||||
'IE, 13',
|
||||
'LT, 16',
|
||||
'LU, 16',
|
||||
'NL, 16',
|
||||
'PL, 13',
|
||||
'SE, 13',
|
||||
]),
|
||||
PARAM_RAW
|
||||
);
|
||||
$temp->add($setting);
|
||||
|
||||
$ADMIN->add('privacy', $temp);
|
||||
|
||||
// Policy settings.
|
||||
$temp = new admin_settingpage('policysettings', new lang_string('policysettings', 'admin'));
|
||||
$temp->add(new admin_settings_sitepolicy_handler_select('sitepolicyhandler', new lang_string('sitepolicyhandler', 'core_admin'),
|
||||
new lang_string('sitepolicyhandler_desc', 'core_admin')));
|
||||
$temp->add(new admin_setting_configtext('sitepolicy', new lang_string('sitepolicy', 'core_admin'),
|
||||
new lang_string('sitepolicy_help', 'core_admin'), '', PARAM_RAW));
|
||||
$temp->add(new admin_setting_configtext('sitepolicyguest', new lang_string('sitepolicyguest', 'core_admin'),
|
||||
new lang_string('sitepolicyguest_help', 'core_admin'), (isset($CFG->sitepolicy) ? $CFG->sitepolicy : ''), PARAM_RAW));
|
||||
|
||||
$ADMIN->add('privacy', $temp);
|
||||
}
|
||||
@@ -43,9 +43,6 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
|
||||
new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('forceclean', new lang_string('forceclean', 'core_admin'),
|
||||
new lang_string('forceclean_desc', 'core_admin'), 0));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configselect('maxeditingtime', new lang_string('maxeditingtime','admin'), new lang_string('configmaxeditingtime','admin'), 1800,
|
||||
|
||||
@@ -29,7 +29,6 @@ $ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empt
|
||||
$ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
|
||||
$ADMIN->add('root', new admin_category('language', new lang_string('language')));
|
||||
$ADMIN->add('root', new admin_category('modules', new lang_string('plugins', 'admin')));
|
||||
$ADMIN->add('root', new admin_category('privacy', new lang_string('privacyandpolicies', 'admin')));
|
||||
$ADMIN->add('root', new admin_category('security', new lang_string('security','admin')));
|
||||
$ADMIN->add('root', new admin_category('appearance', new lang_string('appearance','admin')));
|
||||
$ADMIN->add('root', new admin_category('frontpage', new lang_string('frontpage','admin')));
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
$ADMIN->add('users', new admin_category('accounts', new lang_string('accounts', 'admin')));
|
||||
$ADMIN->add('users', new admin_category('roles', new lang_string('permissions', 'role')));
|
||||
$ADMIN->add('users', new admin_category('privacy', new lang_string('privacyandpolicies', 'admin')));
|
||||
|
||||
if ($hassiteconfig
|
||||
or has_capability('moodle/user:create', $systemcontext)
|
||||
@@ -210,3 +211,48 @@ if ($hassiteconfig
|
||||
$ADMIN->add('roles', new admin_externalpage('checkpermissions', new lang_string('checkglobalpermissions', 'role'), "$CFG->wwwroot/$CFG->admin/roles/check.php?contextid=".$systemcontext->id, array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:manage')));
|
||||
|
||||
} // end of speedup
|
||||
|
||||
// Privacy settings.
|
||||
if ($hassiteconfig) {
|
||||
$temp = new admin_settingpage('privacysettings', new lang_string('privacysettings', 'admin'));
|
||||
|
||||
$options = array(
|
||||
0 => get_string('no'),
|
||||
1 => get_string('yes')
|
||||
);
|
||||
$url = new moodle_url('/admin/settings.php?section=supportcontact');
|
||||
$url = $url->out();
|
||||
$setting = new admin_setting_configselect('agedigitalconsentverification',
|
||||
new lang_string('agedigitalconsentverification', 'admin'),
|
||||
new lang_string('agedigitalconsentverification_desc', 'admin', $url), 0, $options);
|
||||
$setting->set_force_ltr(true);
|
||||
$temp->add($setting);
|
||||
|
||||
// See {@link https://gdpr-info.eu/art-8-gdpr/}.
|
||||
$ageofdigitalconsentmap = implode(PHP_EOL, [
|
||||
'*, 16',
|
||||
'AT, 14',
|
||||
'ES, 14',
|
||||
'US, 13'
|
||||
]);
|
||||
$setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
|
||||
new lang_string('ageofdigitalconsentmap', 'admin'),
|
||||
new lang_string('ageofdigitalconsentmap_desc', 'admin'),
|
||||
$ageofdigitalconsentmap,
|
||||
PARAM_RAW
|
||||
);
|
||||
$temp->add($setting);
|
||||
|
||||
$ADMIN->add('privacy', $temp);
|
||||
|
||||
// Policy settings.
|
||||
$temp = new admin_settingpage('policysettings', new lang_string('policysettings', 'admin'));
|
||||
$temp->add(new admin_settings_sitepolicy_handler_select('sitepolicyhandler', new lang_string('sitepolicyhandler', 'core_admin'),
|
||||
new lang_string('sitepolicyhandler_desc', 'core_admin')));
|
||||
$temp->add(new admin_setting_configtext('sitepolicy', new lang_string('sitepolicy', 'core_admin'),
|
||||
new lang_string('sitepolicy_help', 'core_admin'), '', PARAM_RAW));
|
||||
$temp->add(new admin_setting_configtext('sitepolicyguest', new lang_string('sitepolicyguest', 'core_admin'),
|
||||
new lang_string('sitepolicyguest_help', 'core_admin'), (isset($CFG->sitepolicy) ? $CFG->sitepolicy : ''), PARAM_RAW));
|
||||
|
||||
$ADMIN->add('privacy', $temp);
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_analytics'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->component = 'tool_assignmentupgrade';
|
||||
$plugin->dependencies = array('mod_assign' => 2017110800);
|
||||
$plugin->dependencies = array('mod_assign' => 2018050800);
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->component = 'tool_availabilityconditions';
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version
|
||||
$plugin->component = 'tool_behat'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_capability'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Privacy Subsystem implementation for tool_cohortroles.
|
||||
*
|
||||
* @package tool_cohortroles
|
||||
* @copyright 2018 Zig Tan <zig@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_cohortroles\privacy;
|
||||
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\context;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Privacy Subsystem for tool_cohortroles implementing metadata and plugin providers.
|
||||
*
|
||||
* @copyright 2018 Zig Tan <zig@moodle.com>
|
||||
* @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\plugin\provider {
|
||||
|
||||
/**
|
||||
* Returns meta data about this system.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
// The tool_cohortroles plugin utilises the mdl_tool_cohortroles table.
|
||||
$collection->add_database_table(
|
||||
'tool_cohortroles',
|
||||
[
|
||||
'id' => 'privacy:metadata:tool_cohortroles:id',
|
||||
'cohortid' => 'privacy:metadata:tool_cohortroles:cohortid',
|
||||
'roleid' => 'privacy:metadata:tool_cohortroles:roleid',
|
||||
'userid' => 'privacy:metadata:tool_cohortroles:userid',
|
||||
'timecreated' => 'privacy:metadata:tool_cohortroles:timecreated',
|
||||
'timemodified' => 'privacy:metadata:tool_cohortroles:timemodified',
|
||||
'usermodified' => 'privacy:metadata:tool_cohortroles:usermodified'
|
||||
],
|
||||
'privacy:metadata:tool_cohortroles'
|
||||
);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
$contextlist = new contextlist();
|
||||
|
||||
// Retrieve the User context associated with tool_cohortroles records.
|
||||
$sql = "SELECT DISTINCT c.id
|
||||
FROM {context} c
|
||||
JOIN {tool_cohortroles} cr ON cr.userid = c.instanceid AND c.contextlevel = :contextuser
|
||||
WHERE cr.userid = :userid";
|
||||
|
||||
$params = [
|
||||
'contextuser' => CONTEXT_USER,
|
||||
'userid' => $userid
|
||||
];
|
||||
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
return $contextlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
// If the user has tool_cohortroles data, then only the User context should be present so get the first context.
|
||||
$contexts = $contextlist->get_contexts();
|
||||
if (count($contexts) == 0) {
|
||||
return;
|
||||
}
|
||||
$context = reset($contexts);
|
||||
|
||||
// Sanity check that context is at the User context level, then get the userid.
|
||||
if ($context->contextlevel !== CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
$userid = $context->instanceid;
|
||||
|
||||
// Retrieve the tool_cohortroles records created for the user.
|
||||
$sql = 'SELECT cr.id as cohortroleid,
|
||||
c.name as cohortname,
|
||||
c.idnumber as cohortidnumber,
|
||||
c.description as cohortdescription,
|
||||
r.shortname as roleshortname,
|
||||
cr.userid as userid,
|
||||
cr.timecreated as timecreated,
|
||||
cr.timemodified as timemodified
|
||||
FROM {tool_cohortroles} cr
|
||||
JOIN {cohort} c ON c.id = cr.cohortid
|
||||
JOIN {role} r ON r.id = cr.roleid
|
||||
WHERE cr.userid = :userid';
|
||||
|
||||
$params = [
|
||||
'userid' => $userid
|
||||
];
|
||||
|
||||
$cohortroles = $DB->get_records_sql($sql, $params);
|
||||
foreach ($cohortroles as $cohortrole) {
|
||||
// The tool_cohortroles data export is organised in:
|
||||
// {User Context}/Cohort roles management/{cohort name}/{role shortname}/data.json.
|
||||
$subcontext = [
|
||||
get_string('pluginname', 'tool_cohortroles'),
|
||||
$cohortrole->cohortname,
|
||||
$cohortrole->roleshortname
|
||||
];
|
||||
|
||||
$data = (object) [
|
||||
'cohortname' => $cohortrole->cohortname,
|
||||
'cohortidnumber' => $cohortrole->cohortidnumber,
|
||||
'cohortdescription' => $cohortrole->cohortdescription,
|
||||
'roleshortname' => $cohortrole->roleshortname,
|
||||
'userid' => transform::user($cohortrole->userid),
|
||||
'timecreated' => transform::datetime($cohortrole->timecreated),
|
||||
'timemodified' => transform::datetime($cohortrole->timemodified)
|
||||
];
|
||||
|
||||
writer::with_context($context)->export_data($subcontext, $data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
global $DB;
|
||||
|
||||
// Sanity check that context is at the User context level, then get the userid.
|
||||
if ($context->contextlevel !== CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
$userid = $context->instanceid;
|
||||
|
||||
// Delete the tool_cohortroles records created for the userid.
|
||||
$DB->delete_records('tool_cohortroles', ['userid' => $userid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
// If the user has tool_cohortroles data, then only the User context should be present so get the first context.
|
||||
$contexts = $contextlist->get_contexts();
|
||||
if (count($contexts) == 0) {
|
||||
return;
|
||||
}
|
||||
$context = reset($contexts);
|
||||
|
||||
// Sanity check that context is at the User context level, then get the userid.
|
||||
if ($context->contextlevel !== CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
$userid = $context->instanceid;
|
||||
|
||||
// Delete the tool_cohortroles records created for the userid.
|
||||
$DB->delete_records('tool_cohortroles', ['userid' => $userid]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,3 +42,11 @@ $string['selectrole'] = 'Select role';
|
||||
$string['selectusers'] = 'Select users to assign role';
|
||||
$string['taskname'] = 'Sync cohort role assignments';
|
||||
$string['thisuserroles'] = 'Roles assigned relative to this user';
|
||||
$string['privacy:metadata:tool_cohortroles'] = 'The Cohort roles management plugin stores user cohort role mappings.';
|
||||
$string['privacy:metadata:tool_cohortroles:id'] = 'The ID of the cohort role mapping record.';
|
||||
$string['privacy:metadata:tool_cohortroles:cohortid'] = 'The ID of the cohort.';
|
||||
$string['privacy:metadata:tool_cohortroles:roleid'] = 'The ID of the role.';
|
||||
$string['privacy:metadata:tool_cohortroles:userid'] = 'The ID of the user.';
|
||||
$string['privacy:metadata:tool_cohortroles:timecreated'] = 'The date/time of when the cohort role mapping was created.';
|
||||
$string['privacy:metadata:tool_cohortroles:timemodified'] = 'The date/time of when the cohort role mapping was modified.';
|
||||
$string['privacy:metadata:tool_cohortroles:usermodified'] = 'The ID of the user who last modified the cohort role mapping.';
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Unit tests for the tool_cohortroles implementation of the privacy API.
|
||||
*
|
||||
* @package tool_cohortroles
|
||||
* @category test
|
||||
* @copyright 2018 Zig Tan <zig@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use \core_privacy\local\request\writer;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \tool_cohortroles\api;
|
||||
use \tool_cohortroles\privacy\provider;
|
||||
|
||||
/**
|
||||
* Unit tests for the tool_cohortroles implementation of the privacy API.
|
||||
*
|
||||
* @copyright 2018 Zig Tan <zig@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_cohortroles_privacy_testcase extends \core_privacy\tests\provider_testcase {
|
||||
|
||||
/**
|
||||
* Overriding setUp() function to always reset after tests.
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->resetAfterTest(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for provider::get_contexts_for_userid().
|
||||
*/
|
||||
public function test_get_contexts_for_userid() {
|
||||
global $DB;
|
||||
|
||||
// Test setup.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$this->setAdminUser();
|
||||
|
||||
$nocohortroles = 3;
|
||||
$this->setup_test_scenario_data($user->id, $nocohortroles);
|
||||
|
||||
// Test the User's assigned cohortroles matches 3.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount($nocohortroles, $cohortroles);
|
||||
|
||||
// Test the User's retrieved contextlist contains only one context.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$contexts = $contextlist->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
|
||||
// Test the User's contexts equal the User's own context.
|
||||
$context = reset($contexts);
|
||||
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
|
||||
$this->assertEquals($user->id, $context->instanceid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for provider::export_user_data().
|
||||
*/
|
||||
public function test_export_user_data() {
|
||||
// Test setup.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$this->setAdminUser();
|
||||
|
||||
$nocohortroles = 3;
|
||||
$this->setup_test_scenario_data($user->id, $nocohortroles);
|
||||
|
||||
// Test the User's retrieved contextlist contains only one context.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$contexts = $contextlist->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
|
||||
// Test the User's contexts equal the User's own context.
|
||||
$context = reset($contexts);
|
||||
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
|
||||
$this->assertEquals($user->id, $context->instanceid);
|
||||
|
||||
// Retrieve the User's tool_cohortroles data.
|
||||
$approvedcontextlist = new approved_contextlist($user, 'tool_cohortroles', $contextlist->get_contextids());
|
||||
provider::export_user_data($approvedcontextlist);
|
||||
|
||||
// Test the tool_cohortroles data is exported at the User context level.
|
||||
$writer = writer::with_context($context);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for provider::delete_data_for_all_users_in_context().
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
// Test setup.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$this->setAdminUser();
|
||||
|
||||
$nocohortroles = 4;
|
||||
$this->setup_test_scenario_data($user->id, $nocohortroles);
|
||||
|
||||
// Test the User's assigned cohortroles matches 4.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount($nocohortroles, $cohortroles);
|
||||
|
||||
// Test the User's retrieved contextlist contains only one context.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$contexts = $contextlist->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
|
||||
// Test the User's contexts equal the User's own context.
|
||||
$context = reset($contexts);
|
||||
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
|
||||
$this->assertEquals($user->id, $context->instanceid);
|
||||
|
||||
// Delete all the User's records in mdl_tool_cohortroles table by the specified User context.
|
||||
provider::delete_data_for_all_users_in_context($context);
|
||||
|
||||
// Test the cohort roles records in mdl_tool_cohortroles table is equals zero.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount(0, $cohortroles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for provider::delete_data_for_user().
|
||||
*/
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
// Test setup.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->setUser($user);
|
||||
$this->setAdminUser();
|
||||
|
||||
$nocohortroles = 4;
|
||||
$this->setup_test_scenario_data($user->id, $nocohortroles);
|
||||
|
||||
// Test the User's assigned cohortroles matches 4.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount($nocohortroles, $cohortroles);
|
||||
|
||||
// Test the User's retrieved contextlist contains only one context.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$contexts = $contextlist->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
|
||||
// Test the User's contexts equal the User's own context.
|
||||
$context = reset($contexts);
|
||||
$this->assertEquals(CONTEXT_USER, $context->contextlevel);
|
||||
$this->assertEquals($user->id, $context->instanceid);
|
||||
|
||||
// Delete all the User's records in mdl_tool_cohortroles table by the specified User approved context list.
|
||||
$approvedcontextlist = new approved_contextlist($user, 'tool_cohortroles', $contextlist->get_contextids());
|
||||
provider::delete_data_for_user($approvedcontextlist);
|
||||
|
||||
// Test the records in mdl_tool_cohortroles table is equals zero.
|
||||
$cohortroles = $DB->get_records('tool_cohortroles', ['userid' => $user->id]);
|
||||
$this->assertCount(0, $cohortroles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to setup tool_cohortroles records for testing a specific user.
|
||||
*
|
||||
* @param int $userid The ID of the user used for testing.
|
||||
* @param int $nocohortroles The number of tool_cohortroles to create for the user.
|
||||
* @throws \core_competency\invalid_persistent_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
protected function setup_test_scenario_data($userid, $nocohortroles) {
|
||||
$roleid = create_role('Sausage Roll', 'sausageroll', 'mmmm');
|
||||
|
||||
for ($c = 0; $c < $nocohortroles; $c++) {
|
||||
$cohort = $this->getDataGenerator()->create_cohort();
|
||||
|
||||
$params = (object)array(
|
||||
'userid' => $userid,
|
||||
'roleid' => $roleid,
|
||||
'cohortid' => $cohort->id
|
||||
);
|
||||
|
||||
api::create_cohort_role_assignment($params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,8 +25,8 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_cohortroles'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
$plugin->dependencies = array(
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->component = 'tool_customlang'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -37,6 +37,7 @@ use moodle_url;
|
||||
use required_capability_exception;
|
||||
use stdClass;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
use tool_dataprivacy\local\helper;
|
||||
use tool_dataprivacy\task\initiate_data_request_task;
|
||||
use tool_dataprivacy\task\process_data_request_task;
|
||||
|
||||
@@ -186,8 +187,25 @@ class api {
|
||||
$datarequest = new data_request();
|
||||
// The user the request is being made for.
|
||||
$datarequest->set('userid', $foruser);
|
||||
|
||||
$requestinguser = $USER->id;
|
||||
// Check when the user is making a request on behalf of another.
|
||||
if ($requestinguser != $foruser) {
|
||||
if (self::is_site_dpo($requestinguser)) {
|
||||
// The user making the request is a DPO. Should be fine.
|
||||
$datarequest->set('dpo', $requestinguser);
|
||||
} else {
|
||||
// If not a DPO, only users with the capability to make data requests for the user should be allowed.
|
||||
// (e.g. users with the Parent role, etc).
|
||||
if (!api::can_create_data_request_for_user($foruser)) {
|
||||
$forusercontext = \context_user::instance($foruser);
|
||||
throw new required_capability_exception($forusercontext,
|
||||
'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
// The user making the request.
|
||||
$datarequest->set('requestedby', $USER->id);
|
||||
$datarequest->set('requestedby', $requestinguser);
|
||||
// Set status.
|
||||
$datarequest->set('status', self::DATAREQUEST_STATUS_PENDING);
|
||||
// Set request type.
|
||||
@@ -218,20 +236,34 @@ class api {
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function get_data_requests($userid = 0) {
|
||||
global $USER;
|
||||
global $DB, $USER;
|
||||
$results = [];
|
||||
$sort = 'status ASC, timemodified ASC';
|
||||
if ($userid) {
|
||||
// Get the data requests for the user or data requests made by the user.
|
||||
$select = "userid = :userid OR requestedby = :requestedby";
|
||||
$select = "(userid = :userid OR requestedby = :requestedby)";
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'requestedby' => $userid
|
||||
];
|
||||
$results = data_request::get_records_select($select, $params, 'status DESC, timemodified DESC');
|
||||
|
||||
// Build a list of user IDs that the user is allowed to make data requests for.
|
||||
// Of course, the user should be included in this list.
|
||||
$alloweduserids = [$userid];
|
||||
// Get any users that the user can make data requests for.
|
||||
if ($children = helper::get_children_of_user($userid)) {
|
||||
// Get the list of user IDs of the children and merge to the allowed user IDs.
|
||||
$alloweduserids = array_merge($alloweduserids, array_keys($children));
|
||||
}
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($alloweduserids, SQL_PARAMS_NAMED);
|
||||
$select .= " AND userid $insql";
|
||||
$params = array_merge($params, $inparams);
|
||||
|
||||
$results = data_request::get_records_select($select, $params, $sort);
|
||||
} else {
|
||||
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
|
||||
if (self::is_site_dpo($USER->id)) {
|
||||
$results = data_request::get_records(null, 'status DESC, timemodified DESC', '');
|
||||
$results = data_request::get_records(null, $sort, '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,17 +321,19 @@ class api {
|
||||
* @param int $requestid The request identifier.
|
||||
* @param int $status The request status.
|
||||
* @param int $dpoid The user ID of the Data Protection Officer
|
||||
* @param string $comment The comment about the status update.
|
||||
* @return bool
|
||||
* @throws invalid_persistent_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function update_request_status($requestid, $status, $dpoid = 0) {
|
||||
public static function update_request_status($requestid, $status, $dpoid = 0, $comment = '') {
|
||||
// Update the request.
|
||||
$datarequest = new data_request($requestid);
|
||||
$datarequest->set('status', $status);
|
||||
if ($dpoid) {
|
||||
$datarequest->set('dpo', $dpoid);
|
||||
}
|
||||
$datarequest->set('dpocomment', $comment);
|
||||
return $datarequest->update();
|
||||
}
|
||||
|
||||
@@ -417,7 +451,7 @@ class api {
|
||||
$message->courseid = $SITE->id;
|
||||
$message->component = 'tool_dataprivacy';
|
||||
$message->name = 'contactdataprotectionofficer';
|
||||
$message->userfrom = $requestedby;
|
||||
$message->userfrom = $requestedby->id;
|
||||
$message->replyto = $requestedby->email;
|
||||
$message->replytoname = $requestedby->fullname;
|
||||
$message->subject = $subject;
|
||||
@@ -453,6 +487,19 @@ class api {
|
||||
return message_send($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a non-DPO user can make a data request for another user.
|
||||
*
|
||||
* @param int $user The user ID of the target user.
|
||||
* @param int $requester The user ID of the user making the request.
|
||||
* @return bool
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function can_create_data_request_for_user($user, $requester = null) {
|
||||
$usercontext = \context_user::instance($user);
|
||||
return has_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new data purpose.
|
||||
*
|
||||
@@ -477,6 +524,10 @@ class api {
|
||||
public static function update_purpose(stdClass $record) {
|
||||
self::check_can_manage_data_registry();
|
||||
|
||||
if (!isset($record->sensitivedatareasons)) {
|
||||
$record->sensitivedatareasons = '';
|
||||
}
|
||||
|
||||
$purpose = new purpose($record->id);
|
||||
$purpose->from_record($record);
|
||||
|
||||
@@ -764,6 +815,7 @@ class api {
|
||||
* @param int $status the status to set the contexts to.
|
||||
*/
|
||||
public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
|
||||
$request = new data_request($requestid);
|
||||
foreach ($clcollection as $contextlist) {
|
||||
// Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it.
|
||||
$clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist);
|
||||
@@ -772,6 +824,12 @@ class api {
|
||||
|
||||
// Store the associated contexts in the contextlist.
|
||||
foreach ($contextlist->get_contextids() as $contextid) {
|
||||
if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
|
||||
$context = \context::instance_by_id($contextid);
|
||||
if (($purpose = static::get_effective_context_purpose($context)) && !empty($purpose->get('protected'))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$context = new contextlist_context();
|
||||
$context->set('contextid', $contextid)
|
||||
->set('contextlistid', $contextlistid)
|
||||
@@ -868,6 +926,7 @@ class api {
|
||||
}
|
||||
$contexts = [];
|
||||
}
|
||||
|
||||
$contexts[] = $record->contextid;
|
||||
$lastcomponent = $record->component;
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ abstract class expired_contexts_manager {
|
||||
}
|
||||
|
||||
$privacymanager = new \core_privacy\manager();
|
||||
$privacymanager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
foreach ($this->get_context_levels() as $level) {
|
||||
|
||||
|
||||
@@ -115,12 +115,35 @@ class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
|
||||
* @return \context|false
|
||||
*/
|
||||
protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
|
||||
if (!$context = parent::delete_expired_context($privacymanager, $expiredctx)) {
|
||||
$context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
|
||||
if (!$context) {
|
||||
api::delete_expired_context($expiredctx->get('contextid'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the user.
|
||||
if (!PHPUNIT_TEST) {
|
||||
mtrace('Deleting context ' . $context->id . ' - ' .
|
||||
shorten_text($context->get_context_name(true, true)));
|
||||
}
|
||||
|
||||
// To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray
|
||||
// contexts for the user that may still exist and call delete_data_for_user().
|
||||
$user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
|
||||
$approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
|
||||
$contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
|
||||
|
||||
foreach ($contextlistcollection as $contextlist) {
|
||||
$approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
|
||||
$user,
|
||||
$contextlist->get_component(),
|
||||
$contextlist->get_contextids()
|
||||
));
|
||||
}
|
||||
|
||||
$privacymanager->delete_data_for_user($approvedlistcollection);
|
||||
api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
|
||||
|
||||
// Delete the user.
|
||||
delete_user($user);
|
||||
|
||||
return $context;
|
||||
|
||||
@@ -96,8 +96,8 @@ class external extends external_api {
|
||||
self::validate_context($context);
|
||||
|
||||
// Ensure the request exists.
|
||||
$select = 'id = :id AND requestedby = :requestedby';
|
||||
$params = ['id' => $requestid, 'requestedby' => $USER->id];
|
||||
$select = 'id = :id AND (userid = :userid OR requestedby = :requestedby)';
|
||||
$params = ['id' => $requestid, 'userid' => $USER->id, 'requestedby' => $USER->id];
|
||||
$requestexists = data_request::record_exists_select($select, $params);
|
||||
|
||||
$result = false;
|
||||
|
||||
@@ -55,6 +55,7 @@ class context_instance extends \core\form\persistent {
|
||||
$subjectscope = get_string('noassignedroles', 'tool_dataprivacy');
|
||||
}
|
||||
$this->_form->addElement('static', 'subjectscope', get_string('subjectscope', 'tool_dataprivacy'), $subjectscope);
|
||||
$this->_form->addHelpButton('subjectscope', 'subjectscope', 'tool_dataprivacy');
|
||||
|
||||
$this->add_purpose_category($this->_customdata['context']->contextlevel);
|
||||
|
||||
@@ -79,6 +80,7 @@ class context_instance extends \core\form\persistent {
|
||||
$addcategory = $mform->createElement('button', 'addcategory', $addcategorytext, ['data-add-element' => 'category']);
|
||||
$mform->addElement('group', 'categorygroup', get_string('category', 'tool_dataprivacy'),
|
||||
[$categoryselect, $addcategory], null, false);
|
||||
$mform->addHelpButton('categorygroup', 'category', 'tool_dataprivacy');
|
||||
$mform->setType('categoryid', PARAM_INT);
|
||||
$mform->setDefault('categoryid', 0);
|
||||
|
||||
@@ -87,12 +89,14 @@ class context_instance extends \core\form\persistent {
|
||||
$addpurpose = $mform->createElement('button', 'addpurpose', $addpurposetext, ['data-add-element' => 'purpose']);
|
||||
$mform->addElement('group', 'purposegroup', get_string('purpose', 'tool_dataprivacy'),
|
||||
[$purposeselect, $addpurpose], null, false);
|
||||
$mform->addHelpButton('purposegroup', 'purpose', 'tool_dataprivacy');
|
||||
$mform->setType('purposeid', PARAM_INT);
|
||||
$mform->setDefault('purposeid', 0);
|
||||
|
||||
if (!empty($this->_customdata['currentretentionperiod'])) {
|
||||
$mform->addElement('static', 'retention_current', get_string('retentionperiod', 'tool_dataprivacy'),
|
||||
$this->_customdata['currentretentionperiod']);
|
||||
$mform->addHelpButton('retention_current', 'retentionperiod', 'tool_dataprivacy');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,9 +68,11 @@ class defaults extends \moodleform {
|
||||
$purposeoptions = data_registry_page::category_options($this->_customdata['purposes'], false, $includeinherit);
|
||||
|
||||
$mform->addElement('select', $categoryvar, get_string('category', 'tool_dataprivacy'), $categoryoptions);
|
||||
$mform->addHelpButton($categoryvar, 'categorydefault', 'tool_dataprivacy');
|
||||
$mform->setType($categoryvar, PARAM_INT);
|
||||
|
||||
$mform->addElement('select', $purposevar, get_string('purpose', 'tool_dataprivacy'), $purposeoptions);
|
||||
$mform->addHelpButton($purposevar, 'purposedefault', 'tool_dataprivacy');
|
||||
$mform->setType($purposevar, PARAM_INT);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,4 +108,42 @@ class helper {
|
||||
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the users that a user can make data request for.
|
||||
*
|
||||
* E.g. User having a parent role and has the 'tool/dataprivacy:makedatarequestsforchildren' capability.
|
||||
* @param int $userid The user's ID.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_children_of_user($userid) {
|
||||
global $DB;
|
||||
|
||||
// Get users that the user has role assignments to.
|
||||
$allusernames = get_all_user_name_fields(true, 'u');
|
||||
$sql = "SELECT u.id, $allusernames
|
||||
FROM {role_assignments} ra, {context} c, {user} u
|
||||
WHERE ra.userid = :userid
|
||||
AND ra.contextid = c.id
|
||||
AND c.instanceid = u.id
|
||||
AND c.contextlevel = :contextlevel";
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'contextlevel' => CONTEXT_USER
|
||||
];
|
||||
|
||||
// The final list of users that we will return;
|
||||
$finalresults = [];
|
||||
|
||||
// Our prospective list of users.
|
||||
if ($candidates = $DB->get_records_sql($sql, $params)) {
|
||||
foreach ($candidates as $key => $child) {
|
||||
$childcontext = \context_user::instance($child->id);
|
||||
if (has_capability('tool/dataprivacy:makedatarequestsforchildren', $childcontext, $userid)) {
|
||||
$finalresults[$key] = $child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $finalresults;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Class \tool_dataprivacy\manager_observer.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* A failure observer for the \core_privacy\manager.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class manager_observer implements \core_privacy\manager_observer {
|
||||
/**
|
||||
* Notifies all DPOs that an exception occurred.
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @param string $component
|
||||
* @param string $interface
|
||||
* @param string $methodname
|
||||
* @param array $params
|
||||
*/
|
||||
public function handle_component_failure($e, $component, $interface, $methodname, array $params) {
|
||||
// Get the list of the site Data Protection Officers.
|
||||
$dpos = api::get_site_dpos();
|
||||
|
||||
$messagesubject = get_string('exceptionnotificationsubject', 'tool_dataprivacy');
|
||||
$a = (object)[
|
||||
'fullmethodname' => \core_privacy\manager::get_provider_classname_for_component($component) . '::' . $methodname,
|
||||
'component' => $component,
|
||||
'message' => $e->getMessage(),
|
||||
'backtrace' => $e->getTraceAsString()
|
||||
];
|
||||
$messagebody = get_string('exceptionnotificationbody', 'tool_dataprivacy', $a);
|
||||
|
||||
// Email the data request to the Data Protection Officer(s)/Admin(s).
|
||||
foreach ($dpos as $dpo) {
|
||||
$message = new \core\message\message();
|
||||
$message->courseid = SITEID;
|
||||
$message->component = 'tool_dataprivacy';
|
||||
$message->name = 'notifyexceptions';
|
||||
$message->userfrom = \core_user::get_noreply_user();
|
||||
$message->subject = $messagesubject;
|
||||
$message->fullmessageformat = FORMAT_HTML;
|
||||
$message->notification = 1;
|
||||
$message->userto = $dpo;
|
||||
$message->fullmessagehtml = $messagebody;
|
||||
$message->fullmessage = html_to_text($messagebody);
|
||||
|
||||
// Send message.
|
||||
message_send($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ class metadata_registry {
|
||||
*/
|
||||
public function get_registry_metadata() {
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$pluginman = \core_plugin_manager::instance();
|
||||
$contributedplugins = $this->get_contrib_list();
|
||||
$metadata = $manager->get_metadata_for_components();
|
||||
@@ -56,6 +58,10 @@ class metadata_registry {
|
||||
if (isset($metadata[$component])) {
|
||||
$collection = $metadata[$component]->get_collection();
|
||||
$internaldata = $this->format_metadata($collection, $component, $internaldata);
|
||||
} else if ($manager->is_empty_subsystem($component)) {
|
||||
// This is an unused subsystem.
|
||||
// Use the generic string.
|
||||
$internaldata['nullprovider'] = get_string('privacy:subsystem:empty', 'core_privacy');
|
||||
} else {
|
||||
// Call get_reason for null provider.
|
||||
$internaldata['nullprovider'] = get_string($manager->get_null_provider_reason($component), $component);
|
||||
@@ -122,11 +128,15 @@ class metadata_registry {
|
||||
* @return array An array of plugin types which contain plugin data.
|
||||
*/
|
||||
protected function get_full_component_list() {
|
||||
global $CFG;
|
||||
|
||||
$list = \core_component::get_component_list();
|
||||
$list['core']['core'] = "{$CFG->dirroot}/lib";
|
||||
$formattedlist = [];
|
||||
foreach ($list as $plugintype => $plugin) {
|
||||
$formattedlist[] = ['plugin_type' => $plugintype, 'plugins' => array_keys($plugin)];
|
||||
}
|
||||
|
||||
return $formattedlist;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,10 @@ class data_registry_page implements renderable, templatable {
|
||||
$data->actions = $actionmenu->export_for_template($output);
|
||||
|
||||
if (!data_registry::defaults_set()) {
|
||||
$data->info = (object)[
|
||||
'message' => get_string('dataregistryinfo', 'tool_dataprivacy'),
|
||||
'announce' => 1
|
||||
];
|
||||
$data->nosystemdefaults = (object)[
|
||||
'message' => get_string('nosystemdefaults', 'tool_dataprivacy'),
|
||||
'announce' => 1
|
||||
@@ -131,7 +135,7 @@ class data_registry_page implements renderable, templatable {
|
||||
'text' => get_string('user'),
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
], [
|
||||
'text' => get_string('categories', 'tool_dataprivacy'),
|
||||
'text' => get_string('categories'),
|
||||
'branches' => $categorybranches,
|
||||
'expandelement' => 'category',
|
||||
], [
|
||||
@@ -312,29 +316,23 @@ class data_registry_page implements renderable, templatable {
|
||||
|
||||
$branches = [];
|
||||
|
||||
$blockinstances = \core_block_external::get_course_blocks($coursecontext->instanceid);
|
||||
if (empty($blockinstances['blocks'])) {
|
||||
return $branches;
|
||||
}
|
||||
$children = $coursecontext->get_child_contexts();
|
||||
foreach ($children as $childcontext) {
|
||||
|
||||
foreach ($blockinstances['blocks'] as $bi) {
|
||||
if (function_exists('block_instance_by_id')) {
|
||||
$blockinstance = block_instance_by_id($bi['instanceid']);
|
||||
} else {
|
||||
// TODO To be removed when MDL-61621 gets integrated.
|
||||
$blockinstance = $DB->get_record('block_instances', ['id' => $bi['instanceid']]);
|
||||
$blockinstance = block_instance($blockinstance->blockname, $blockinstance);
|
||||
if ($childcontext->contextlevel !== CONTEXT_BLOCK) {
|
||||
continue;
|
||||
}
|
||||
$blockcontext = \context_block::instance($bi['instanceid']);
|
||||
$displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $blockcontext->id]));
|
||||
|
||||
$blockinstance = block_instance_by_id($childcontext->instanceid);
|
||||
$displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $childcontext]));
|
||||
$branches[] = self::complete([
|
||||
'text' => $displayname,
|
||||
'contextid' => $blockcontext->id,
|
||||
'contextid' => $childcontext->id,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
return $branches;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -73,6 +73,11 @@ class data_requests_page implements renderable, templatable {
|
||||
$data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
|
||||
$data->newdatarequesturl->param('manage', true);
|
||||
|
||||
if (!is_https()) {
|
||||
$httpwarningmessage = get_string('httpwarning', 'tool_dataprivacy');
|
||||
$data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
|
||||
}
|
||||
|
||||
$requests = [];
|
||||
foreach ($this->requests as $request) {
|
||||
$requestid = $request->get('id');
|
||||
|
||||
@@ -67,18 +67,39 @@ class my_data_requests_page implements renderable, templatable {
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
global $USER;
|
||||
|
||||
$data = new stdClass();
|
||||
$data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
|
||||
|
||||
if (!is_https()) {
|
||||
$httpwarningmessage = get_string('httpwarning', 'tool_dataprivacy');
|
||||
$data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
|
||||
}
|
||||
|
||||
$requests = [];
|
||||
foreach ($this->requests as $request) {
|
||||
$requestid = $request->get('id');
|
||||
$status = $request->get('status');
|
||||
$userid = $request->get('userid');
|
||||
$usercontext = context_user::instance($userid);
|
||||
$requestexporter = new data_request_exporter($request, ['context' => $usercontext]);
|
||||
|
||||
$usercontext = context_user::instance($userid, IGNORE_MISSING);
|
||||
if (!$usercontext) {
|
||||
// Use the context system.
|
||||
$outputcontext = \context_system::instance();
|
||||
} else {
|
||||
$outputcontext = $usercontext;
|
||||
}
|
||||
|
||||
$requestexporter = new data_request_exporter($request, ['context' => $outputcontext]);
|
||||
$item = $requestexporter->export($output);
|
||||
|
||||
if ($request->get('userid') != $USER->id) {
|
||||
// Append user name if it differs from $USER.
|
||||
$a = (object)['typename' => $item->typename, 'user' => $item->foruser->fullname];
|
||||
$item->typename = get_string('requesttypeuser', 'tool_dataprivacy', $a);
|
||||
}
|
||||
|
||||
$candownload = false;
|
||||
$cancancel = true;
|
||||
switch ($status) {
|
||||
@@ -102,7 +123,7 @@ class my_data_requests_page implements renderable, templatable {
|
||||
$canceltext = get_string('cancelrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($cancelurl, null, $canceltext, $canceldata);
|
||||
}
|
||||
if ($candownload) {
|
||||
if ($candownload && $usercontext) {
|
||||
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $requestid, '/',
|
||||
'export.zip', true);
|
||||
$downloadtext = get_string('download', 'tool_dataprivacy');
|
||||
|
||||
@@ -62,7 +62,8 @@ class renderer extends plugin_renderer_base {
|
||||
public function render_contact_dpo_link($replytoemail) {
|
||||
$params = [
|
||||
'data-action' => 'contactdpo',
|
||||
'data-replytoemail' => $replytoemail
|
||||
'data-replytoemail' => $replytoemail,
|
||||
'class' => 'contactdpo'
|
||||
];
|
||||
return html_writer::link('#', get_string('contactdataprotectionofficer', 'tool_dataprivacy'), $params);
|
||||
}
|
||||
|
||||
@@ -70,12 +70,35 @@ class initiate_data_request_task extends adhoc_task {
|
||||
return;
|
||||
}
|
||||
|
||||
$requestedby = $datarequest->get('requestedby');
|
||||
$valid = true;
|
||||
$comment = '';
|
||||
$foruser = $datarequest->get('userid');
|
||||
if ($foruser != $requestedby) {
|
||||
if (!$valid = api::can_create_data_request_for_user($foruser, $requestedby)) {
|
||||
$params = (object)[
|
||||
'requestedby' => $requestedby,
|
||||
'userid' => $foruser
|
||||
];
|
||||
$comment = get_string('errornocapabilitytorequestforothers', 'tool_dataprivacy', $params);
|
||||
mtrace($comment);
|
||||
}
|
||||
}
|
||||
// Reject the request outright if it's invalid.
|
||||
if (!$valid) {
|
||||
$dpo = $datarequest->get('dpo');
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_REJECTED, $dpo, $comment);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the status of this request as pre-processing.
|
||||
mtrace('Generating the contexts containing personal data for the user...');
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PREPROCESSING);
|
||||
|
||||
// Add the list of relevant contexts to the request, and mark all as pending approval.
|
||||
$privacymanager = new \core_privacy\manager();
|
||||
$privacymanager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$contextlistcollection = $privacymanager->get_contexts_for_userid($datarequest->get('userid'));
|
||||
api::add_request_contexts_with_status($contextlistcollection, $requestid, contextlist_context::STATUS_PENDING);
|
||||
|
||||
|
||||
@@ -68,8 +68,9 @@ class process_data_request_task extends adhoc_task {
|
||||
$request = $requestpersistent->to_record();
|
||||
|
||||
// Check if this request still needs to be processed. e.g. The user might have cancelled it before this task has run.
|
||||
if ($request->status != api::DATAREQUEST_STATUS_APPROVED) {
|
||||
mtrace("Request {$request->id} hasn\'t been approved yet or it has already been processed. Skipping...");
|
||||
$status = $requestpersistent->get('status');
|
||||
if (!api::is_active($status)) {
|
||||
mtrace("Request {$requestid} with status {$status} doesn't need to be processed. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,6 +88,8 @@ class process_data_request_task extends adhoc_task {
|
||||
|
||||
// Export the data.
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$exportedcontent = $manager->export_user_data($approvedclcollection);
|
||||
|
||||
$fs = get_file_storage();
|
||||
@@ -109,6 +112,8 @@ class process_data_request_task extends adhoc_task {
|
||||
|
||||
// Delete the data.
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$manager->delete_data_for_user($approvedclcollection);
|
||||
}
|
||||
|
||||
@@ -181,23 +186,27 @@ class process_data_request_task extends adhoc_task {
|
||||
}
|
||||
mtrace('Message sent to user: ' . $messagetextdata['username']);
|
||||
|
||||
// Send to the requester as well. requestedby is 0 if the request was made on behalf of the user by a DPO.
|
||||
if (!empty($request->requestedby) && $foruser->id != $request->requestedby) {
|
||||
$requestedby = core_user::get_user($request->requestedby);
|
||||
$message->userto = $requestedby;
|
||||
$messagetextdata['username'] = fullname($requestedby);
|
||||
// Render message email body.
|
||||
$messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata);
|
||||
$message->fullmessage = html_to_text($messagehtml);
|
||||
$message->fullmessagehtml = $messagehtml;
|
||||
// Send to requester as well if this request was made on behalf of another user who's not a DPO,
|
||||
// and has the capability to make data requests for the user (e.g. Parent).
|
||||
if (!api::is_site_dpo($request->requestedby) && $foruser->id != $request->requestedby) {
|
||||
// Ensure the requester has the capability to make data requests for this user.
|
||||
if (api::can_create_data_request_for_user($request->userid, $request->requestedby)) {
|
||||
$requestedby = core_user::get_user($request->requestedby);
|
||||
$message->userto = $requestedby;
|
||||
$messagetextdata['username'] = fullname($requestedby);
|
||||
// Render message email body.
|
||||
$messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata);
|
||||
$message->fullmessage = html_to_text($messagehtml);
|
||||
$message->fullmessagehtml = $messagehtml;
|
||||
|
||||
// Send message.
|
||||
if ($emailonly) {
|
||||
email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
|
||||
} else {
|
||||
message_send($message);
|
||||
// Send message.
|
||||
if ($emailonly) {
|
||||
email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
|
||||
} else {
|
||||
message_send($message);
|
||||
}
|
||||
mtrace('Message sent to requester: ' . $messagetextdata['username']);
|
||||
}
|
||||
mtrace('Message sent to requester: ' . $messagetextdata['username']);
|
||||
}
|
||||
|
||||
if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
|
||||
|
||||
@@ -57,7 +57,7 @@ if (!\tool_dataprivacy\api::can_contact_dpo()) {
|
||||
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), \core\output\notification::NOTIFY_ERROR);
|
||||
}
|
||||
|
||||
$mform = new tool_dataprivacy_data_request_form($url->out(false));
|
||||
$mform = new tool_dataprivacy_data_request_form($url->out(false), ['manage' => !empty($manage)]);
|
||||
|
||||
// Data request cancelled.
|
||||
if ($mform->is_cancelled()) {
|
||||
@@ -68,7 +68,13 @@ if ($mform->is_cancelled()) {
|
||||
if ($data = $mform->get_data()) {
|
||||
\tool_dataprivacy\api::create_data_request($data->userid, $data->type, $data->comments);
|
||||
|
||||
redirect($returnurl, get_string('requestsubmitted', 'tool_dataprivacy'));
|
||||
if ($manage) {
|
||||
$foruser = core_user::get_user($data->userid);
|
||||
$redirectmessage = get_string('datarequestcreatedforuser', 'tool_dataprivacy', fullname($foruser));
|
||||
} else {
|
||||
$redirectmessage = get_string('requestsubmitted', 'tool_dataprivacy');
|
||||
}
|
||||
redirect($returnurl, $redirectmessage);
|
||||
}
|
||||
|
||||
$title = get_string('contactdataprotectionofficer', 'tool_dataprivacy');
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -37,6 +38,9 @@ require_once($CFG->libdir.'/formslib.php');
|
||||
*/
|
||||
class tool_dataprivacy_data_request_form extends moodleform {
|
||||
|
||||
/** @var bool Flag to indicate whether this form is being rendered for managing data requests or for regular requests. */
|
||||
protected $manage = false;
|
||||
|
||||
/**
|
||||
* Form definition.
|
||||
*
|
||||
@@ -48,7 +52,8 @@ class tool_dataprivacy_data_request_form extends moodleform {
|
||||
global $DB, $USER;
|
||||
$mform =& $this->_form;
|
||||
|
||||
if (api::is_site_dpo($USER->id)) {
|
||||
$this->manage = $this->_customdata['manage'];
|
||||
if ($this->manage) {
|
||||
$options = [
|
||||
'ajax' => 'tool_dataprivacy/form-user-selector',
|
||||
'multiple' => false
|
||||
@@ -58,27 +63,12 @@ class tool_dataprivacy_data_request_form extends moodleform {
|
||||
|
||||
} else {
|
||||
// Get users whom you are being a guardian to if your role has the capability to make data requests for children.
|
||||
$allusernames = get_all_user_name_fields(true, 'u');
|
||||
$sql = "SELECT u.id, $allusernames
|
||||
FROM {role_assignments} ra, {context} c, {user} u
|
||||
WHERE ra.userid = :userid
|
||||
AND ra.contextid = c.id
|
||||
AND c.instanceid = u.id
|
||||
AND c.contextlevel = :contextlevel";
|
||||
$params = [
|
||||
'userid' => $USER->id,
|
||||
'contextlevel' => CONTEXT_USER
|
||||
];
|
||||
$children = $DB->get_records_sql($sql, $params);
|
||||
|
||||
if ($children) {
|
||||
$useroptions = [];
|
||||
$useroptions[$USER->id] = fullname($USER);
|
||||
foreach ($children as $child) {
|
||||
$childcontext = context_user::instance($child->id);
|
||||
if (has_capability('tool/dataprivacy:makedatarequestsforchildren', $childcontext)) {
|
||||
$useroptions[$child->id] = fullname($child);
|
||||
}
|
||||
if ($children = helper::get_children_of_user($USER->id)) {
|
||||
$useroptions = [
|
||||
$USER->id => fullname($USER)
|
||||
];
|
||||
foreach ($children as $key => $child) {
|
||||
$useroptions[$key] = fullname($child);
|
||||
}
|
||||
$mform->addElement('autocomplete', 'userid', get_string('requestfor', 'tool_dataprivacy'), $useroptions);
|
||||
$mform->addRule('userid', null, 'required', null, 'client');
|
||||
|
||||
@@ -41,4 +41,12 @@ $messageproviders = [
|
||||
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
]
|
||||
],
|
||||
|
||||
// Notify Data Protection Officer about exceptions.
|
||||
'notifyexceptions' => [
|
||||
'defaults' => [
|
||||
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
],
|
||||
'capability' => 'tool/dataprivacy:managedatarequests'
|
||||
],
|
||||
];
|
||||
|
||||
@@ -37,7 +37,10 @@ $string['cancelrequest'] = 'Cancel request';
|
||||
$string['cancelrequestconfirmation'] = 'Do you really want cancel this data request?';
|
||||
$string['categories'] = 'Categories';
|
||||
$string['category'] = 'Category';
|
||||
$string['category_help'] = 'A category in the data registry describes a type of data. A new category may be added, or if Inherit is selected, the data category from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > Site.';
|
||||
$string['categorycreated'] = 'Category created';
|
||||
$string['categorydefault'] = 'Default category';
|
||||
$string['categorydefault_help'] = 'The default category is the data category applied to any new instances. If Inherit is selected, the data category from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > User > Site.';
|
||||
$string['categorieslist'] = 'List of data categories';
|
||||
$string['categoryupdated'] = 'Category updated';
|
||||
$string['close'] = 'Close';
|
||||
@@ -46,7 +49,7 @@ $string['confirmapproval'] = 'Do you really want to approve this data request?';
|
||||
$string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
|
||||
$string['confirmdenial'] = 'Do you really want deny this data request?';
|
||||
$string['contactdataprotectionofficer'] = 'Contact Data Protection Officer';
|
||||
$string['contactdataprotectionofficer_desc'] = 'Enabling this feature will provide a link for users to contact the site\'s Data Protection Officer through this site. This link will be shown on their profile page, and on the site\'s privacy policy page, as well. The link leads to a form in which the user can make a data request to the Data Protection Officer.';
|
||||
$string['contactdataprotectionofficer_desc'] = 'If enabled, users will be able to contact the Data Protection Officer and make a data request via a link on their profile page.';
|
||||
$string['contextlevelname10'] = 'Site';
|
||||
$string['contextlevelname30'] = 'Users';
|
||||
$string['contextlevelname40'] = 'Course categories';
|
||||
@@ -54,15 +57,17 @@ $string['contextlevelname50'] = 'Courses';
|
||||
$string['contextlevelname70'] = 'Activity modules';
|
||||
$string['contextlevelname80'] = 'Blocks';
|
||||
$string['contextpurposecategorysaved'] = 'Purpose and category saved.';
|
||||
$string['contactdpoviaprivacypolicy'] = 'Please contact the site\'s Data Protection Officer as described in the Privacy Policy';
|
||||
$string['contactdpoviaprivacypolicy'] = 'Please contact the Data Protection Officer as described in the privacy policy.';
|
||||
$string['createcategory'] = 'Create data category';
|
||||
$string['createpurpose'] = 'Create data purpose';
|
||||
$string['datadeletion'] = 'Data deletion';
|
||||
$string['datadeletionpagehelp'] = 'This page lists the contexts that are already past their retention period and need to be confirmed for user data deletion. Once the selected contexts have been confirmed for deletion, the user data related to these contexts will be deleted on the next execution of the "Delete expired contexts" scheduled task.';
|
||||
$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for children';
|
||||
$string['datadeletionpagehelp'] = 'Data for which the retention period has expired are listed here. Please review and confirm data deletion, which will then be executed by the "Delete expired contexts" scheduled task.';
|
||||
$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for minors';
|
||||
$string['dataprivacy:managedatarequests'] = 'Manage data requests';
|
||||
$string['dataprivacy:managedataregistry'] = 'Manage data registry';
|
||||
$string['dataregistry'] = 'Data registry';
|
||||
$string['dataregistryinfo'] = 'The data registry enables categories (types of data) and purposes (the reasons for processing data) to be set for all content on the site - from users and courses down to activities and blocks. For each purpose, a retention period may be set. When a retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
|
||||
$string['datarequestcreatedforuser'] = 'Data request created for {$a}';
|
||||
$string['datarequestemailsubject'] = 'Data request: {$a}';
|
||||
$string['datarequests'] = 'Data requests';
|
||||
$string['daterequested'] = 'Date requested';
|
||||
@@ -78,7 +83,7 @@ $string['deny'] = 'Deny';
|
||||
$string['denyrequest'] = 'Deny request';
|
||||
$string['download'] = 'Download';
|
||||
$string['dporolemapping'] = 'Data Protection Officer role mapping';
|
||||
$string['dporolemapping_desc'] = 'Select one or more roles that map to the Data Protection Officer role. Users with these roles will be able to manage data requests. This requires the selected role(s) to have the capability \'tool/dataprivacy:managedatarequests\'';
|
||||
$string['dporolemapping_desc'] = 'The Data Protection Officer can manage data requests. The capability tool/dataprivacy:managedatarequests must be allowed for a role to be listed as a Data Protection Officer role mapping option.';
|
||||
$string['editcategories'] = 'Edit categories';
|
||||
$string['editcategory'] = 'Edit category';
|
||||
$string['editcategories'] = 'Edit categories';
|
||||
@@ -89,18 +94,21 @@ $string['effectiveretentionperioduser'] = '{$a} (since the last time the user ac
|
||||
$string['emailsalutation'] = 'Dear {$a},';
|
||||
$string['errorinvalidrequeststatus'] = 'Invalid request status!';
|
||||
$string['errorinvalidrequesttype'] = 'Invalid request type!';
|
||||
$string['errornocapabilitytorequestforothers'] = 'User {$a->requestedby} doesn\'t have the capability to make a data request on behalf of user {$a->userid}';
|
||||
$string['errornoexpiredcontexts'] = 'There are no expired contexts to process';
|
||||
$string['errorcontexthasunexpiredchildren'] = 'The context "{$a}" still has sub-contexts that have not yet expired. No contexts have been flagged for deletion.';
|
||||
$string['errorrequestalreadyexists'] = 'You already have an ongoing request.';
|
||||
$string['errorrequestnotfound'] = 'Request not found';
|
||||
$string['errorrequestnotwaitingforapproval'] = 'The request is not awaiting approval. Either it is not yet ready or it has already been processed.';
|
||||
$string['errorsendingmessagetodpo'] = 'An error was encountered while trying to send a message to {$a}.';
|
||||
$string['exceptionnotificationsubject'] = "Exception occured while processing privacy data";
|
||||
$string['exceptionnotificationbody'] = "<p>Exception occured while calling <b>{\$a->fullmethodname}</b>.<br>This means that plugin <b>{\$a->component}</b> did not complete processing data. Below you can find exception information that can be passed to the plugin developer.</p><pre>{\$a->message}<br>\n\n{\$a->backtrace}</pre>";
|
||||
$string['expiredretentionperiodtask'] = 'Expired retention period';
|
||||
$string['expiry'] = 'Expiry';
|
||||
$string['expandplugin'] = 'Expand and collapse plugin.';
|
||||
$string['expandplugintype'] = 'Expand and collapse plugin type.';
|
||||
$string['explanationtitle'] = 'Icons used on this page and what they mean.';
|
||||
$string['external'] = 'External';
|
||||
$string['external'] = 'Additional';
|
||||
$string['externalexplanation'] = 'An additional plugin installed on this site.';
|
||||
$string['frontpagecourse'] = 'Front page course';
|
||||
$string['gdpr_art_6_1_a_description'] = 'The data subject has given consent to the processing of his or her personal data for one or more specific purposes';
|
||||
@@ -136,11 +144,13 @@ $string['gdpr_art_9_2_i_name'] = 'Public health (GDPR Art. 9.2(i))';
|
||||
$string['gdpr_art_9_2_j_description'] = 'Processing is necessary for archiving purposes in the public interest, scientific or historical research purposes or statistical purposes in accordance with Article 89(1) based on Union or Member State law which shall be proportionate to the aim pursued, respect the essence of the right to data protection and provide for suitable and specific measures to safeguard the fundamental rights and the interests of the data subject';
|
||||
$string['gdpr_art_9_2_j_name'] = 'Public interest, or scientific/historical/statistical research (GDPR Art. 9.2(j))';
|
||||
$string['hide'] = 'Collapse all';
|
||||
$string['httpwarning'] = 'Any data downloaded from this site may not be encrypted. Please contact your system administrator and request that they install SSL on this site.';
|
||||
$string['inherit'] = 'Inherit';
|
||||
$string['lawfulbases'] = 'Lawful bases';
|
||||
$string['lawfulbases_help'] = 'Select at least one option that will serve as the lawful basis for processing personal data. For details on these lawful bases, please see <a href="https://gdpr-info.eu/art-6-gdpr/" target="_blank">GDPR Art. 6.1</a>';
|
||||
$string['messageprovider:contactdataprotectionofficer'] = 'Data requests';
|
||||
$string['messageprovider:datarequestprocessingresults'] = 'Data request processing results';
|
||||
$string['messageprovider:notifyexceptions'] = 'Data requests exceptions notifications';
|
||||
$string['message'] = 'Message';
|
||||
$string['messagelabel'] = 'Message:';
|
||||
$string['moduleinstancename'] = '{$a->instancename} ({$a->modulename})';
|
||||
@@ -155,7 +165,7 @@ $string['noassignedroles'] = 'No assigned roles in this context';
|
||||
$string['noblockstoload'] = 'No blocks';
|
||||
$string['nocategories'] = 'There are no categories yet';
|
||||
$string['nocoursestoload'] = 'No activities';
|
||||
$string['noexpiredcontexts'] = 'There are no expired contexts in this level that need to be confirmed for deletion.';
|
||||
$string['noexpiredcontexts'] = 'This context level has no data for which the retention period has expired.';
|
||||
$string['nopersonaldatarequests'] = 'You don\'t have any personal data requests';
|
||||
$string['nopurposes'] = 'There are no purposes yet';
|
||||
$string['nosubjectaccessrequests'] = 'There are no data requests that you need to act on';
|
||||
@@ -168,12 +178,15 @@ $string['privacy:metadata:request'] = 'Information from personal data requests (
|
||||
$string['privacy:metadata:request:comments'] = 'Any user comments accompanying the request.';
|
||||
$string['privacy:metadata:request:userid'] = 'The ID of the user to whom the request belongs';
|
||||
$string['privacy:metadata:request:requestedby'] = 'The ID of the user making the request, if made on behalf of another user.';
|
||||
$string['privacy:metadata:request:dpocomment'] = 'Any comments made by the site\'s respective privacy officer regarding the request.';
|
||||
$string['privacy:metadata:request:dpocomment'] = 'Any comments made by the site\'s Data Protection Officer regarding the request.';
|
||||
$string['privacy:metadata:request:timecreated'] = 'The timestamp indicating when the request was made by the user.';
|
||||
$string['protected'] = 'Protected';
|
||||
$string['protectedlabel'] = 'The retention of this data has a higher legal precedent over a user\'s request to be forgotten. This data will only be deleted after the retention period has expired.';
|
||||
$string['purpose'] = 'Purpose';
|
||||
$string['purpose_help'] = 'The purpose describes the reason for processing the data. A new purpose may be added, or if Inherit is selected, the purpose from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > User > Site.';
|
||||
$string['purposecreated'] = 'Purpose created';
|
||||
$string['purposedefault'] = 'Default purpose';
|
||||
$string['purposedefault_help'] = 'The default purpose is the purpose which is applied to any new instances. If Inherit is selected, the purpose from a higher context is applied. Contexts are (from low to high): Blocks > Activity modules > Courses > Course categories > User > Site.';
|
||||
$string['purposes'] = 'Purposes';
|
||||
$string['purposeslist'] = 'List of data purposes';
|
||||
$string['purposeupdated'] = 'Purpose updated';
|
||||
@@ -181,12 +194,13 @@ $string['replyto'] = 'Reply to';
|
||||
$string['requestactions'] = 'Actions';
|
||||
$string['requestby'] = 'Requested by';
|
||||
$string['requestcomments'] = 'Comments';
|
||||
$string['requestcomments_help'] = 'Please feel free to provide more details about your request';
|
||||
$string['requestcomments_help'] = 'This box enables you to enter any further details about your data request.';
|
||||
$string['requestemailintro'] = 'You have received a data request:';
|
||||
$string['requestfor'] = 'Requesting for';
|
||||
$string['requeststatus'] = 'Status';
|
||||
$string['requestsubmitted'] = 'Your request has been submitted to the site\'s Data Protection Officer';
|
||||
$string['requesttype'] = 'Type';
|
||||
$string['requesttypeuser'] = '{$a->typename} ({$a->user})';
|
||||
$string['requesttype_help'] = 'Select the reason why you would like to contact the site\'s Data Protection Officer';
|
||||
$string['requesttypedelete'] = 'Delete all of my personal data';
|
||||
$string['requesttypedeleteshort'] = 'Delete';
|
||||
@@ -200,6 +214,7 @@ $string['resultdeleted'] = 'You recently requested to have your account and pers
|
||||
$string['resultdownloadready'] = 'Your copy of your personal data in {$a} that you recently requested is now available for download. Please click on the link below to go to the download page.';
|
||||
$string['reviewdata'] = 'Review data';
|
||||
$string['retentionperiod'] = 'Retention period';
|
||||
$string['retentionperiod_help'] = 'The retention period specifies the length of time that data should be kept for. When the retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
|
||||
$string['retentionperiodnotdefined'] = 'No retention period was defined';
|
||||
$string['retentionperiodzero'] = 'No retention period';
|
||||
$string['send'] = 'Send';
|
||||
@@ -216,6 +231,7 @@ $string['statusprocessing'] = 'Processing';
|
||||
$string['statuspending'] = 'Pending';
|
||||
$string['statusrejected'] = 'Rejected';
|
||||
$string['subjectscope'] = 'Subject scope';
|
||||
$string['subjectscope_help'] = 'The subject scope lists the roles which may be assigned in this context.';
|
||||
$string['user'] = 'User';
|
||||
$string['viewrequest'] = 'View the request';
|
||||
$string['visible'] = 'Expand all';
|
||||
|
||||
@@ -49,7 +49,7 @@ if ($hassiteconfig) {
|
||||
}
|
||||
}
|
||||
if (!empty($roles)) {
|
||||
$privacysettings->add(new admin_setting_configmultiselect('tool_dataprivacy/dporoles',
|
||||
$privacysettings->add(new admin_setting_configmulticheckbox('tool_dataprivacy/dporoles',
|
||||
new lang_string('dporolemapping', 'tool_dataprivacy'),
|
||||
new lang_string('dporolemapping_desc', 'tool_dataprivacy'), null, $roles)
|
||||
);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.nav-pills .nav-pills {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.data-registry > .top-nav > * {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
/*Extra attribute selection to have preference over bs2's .moodle-actionmenu[data-enhance] */
|
||||
.data-registry > .top-nav > .singlebutton,
|
||||
.data-registry > .top-nav > .moodle-actionmenu[data-owner='dataregistry-actions'] {
|
||||
@@ -12,3 +14,13 @@
|
||||
height: 70vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
dd a.contactdpo {
|
||||
/* Reverting dd's left margin */
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.card dd a.contactdpo {
|
||||
/* Reverting dd's left margin */
|
||||
margin-left: inherit;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
}
|
||||
}}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="row m-b-2">
|
||||
<label class="col-md-3 span3 col-form-label">{{#str}}replyto, tool_dataprivacy{{/str}}</label>
|
||||
<div class="col-md-9 span9 col-form-label">{{replytoemail}}</div>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
}}
|
||||
<div class="data-registry">
|
||||
<div class="top-nav">
|
||||
<div class="top-nav d-flex">
|
||||
{{#defaultsbutton}}
|
||||
{{> core/action_link}}
|
||||
{{/defaultsbutton}}
|
||||
@@ -41,6 +41,11 @@
|
||||
{{> core/action_menu}}
|
||||
{{/actions}}
|
||||
</div>
|
||||
{{#info}}
|
||||
<div class="m-t-1">
|
||||
{{> core/notification_info}}
|
||||
</div>
|
||||
{{/info}}
|
||||
{{#nosystemdefaults}}
|
||||
<div class="m-t-1">
|
||||
{{> core/notification_warning}}
|
||||
|
||||
@@ -95,6 +95,10 @@
|
||||
}
|
||||
}}
|
||||
|
||||
{{#httpsite}}
|
||||
{{> core/notification_warning}}
|
||||
{{/httpsite}}
|
||||
|
||||
<div data-region="datarequests">
|
||||
<div class="m-t-1 m-b-1">
|
||||
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
|
||||
|
||||
@@ -75,6 +75,10 @@
|
||||
}
|
||||
}}
|
||||
|
||||
{{#httpsite}}
|
||||
{{> core/notification_warning}}
|
||||
{{/httpsite}}
|
||||
|
||||
<div data-region="datarequests">
|
||||
<div class="m-t-1 m-b-1">
|
||||
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<table class="generaltable fullwidth">
|
||||
<caption class="accesshide">{{#str}}purposeslist, tool_dataprivacy{{/str}}</caption>
|
||||
<thead>
|
||||
<tr style="display: flex;">
|
||||
<tr>
|
||||
<th scope="col" class="col-md-2">{{#str}}name{{/str}}</th>
|
||||
<th scope="col" class="col-md-3">{{#str}}description{{/str}}</th>
|
||||
<th scope="col" class="col-md-2">{{#str}}lawfulbases, tool_dataprivacy{{/str}}</th>
|
||||
|
||||
@@ -57,6 +57,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
public function test_update_request_status() {
|
||||
$generator = new testing_data_generator();
|
||||
$s1 = $generator->create_user();
|
||||
$this->setUser($s1);
|
||||
|
||||
// Create the sample data request.
|
||||
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
@@ -145,6 +146,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
||||
|
||||
// Create the sample data request.
|
||||
$this->setUser($s1);
|
||||
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
@@ -186,6 +188,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
||||
|
||||
// Create the sample data request.
|
||||
$this->setUser($s1);
|
||||
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
@@ -203,6 +206,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
$teacher = $generator->create_user();
|
||||
|
||||
// Create the sample data request.
|
||||
$this->setUser($student);
|
||||
$datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
|
||||
$requestid = $datarequest->get('id');
|
||||
@@ -286,6 +290,71 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
|
||||
$this->assertEquals($user->id, $datarequest->get('userid'));
|
||||
$this->assertEquals($user->id, $datarequest->get('requestedby'));
|
||||
$this->assertEquals(0, $datarequest->get('dpo'));
|
||||
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
|
||||
$this->assertEquals($comment, $datarequest->get('comments'));
|
||||
|
||||
// Test adhoc task creation.
|
||||
$adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
|
||||
$this->assertCount(1, $adhoctasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for api::create_data_request() made by DPO.
|
||||
*/
|
||||
public function test_create_data_request_by_dpo() {
|
||||
global $USER;
|
||||
|
||||
$generator = new testing_data_generator();
|
||||
$user = $generator->create_user();
|
||||
$comment = 'sample comment';
|
||||
|
||||
// Login as DPO (Admin is DPO by default).
|
||||
$this->setAdminUser();
|
||||
|
||||
// Test data request creation.
|
||||
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
|
||||
$this->assertEquals($user->id, $datarequest->get('userid'));
|
||||
$this->assertEquals($USER->id, $datarequest->get('requestedby'));
|
||||
$this->assertEquals($USER->id, $datarequest->get('dpo'));
|
||||
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
|
||||
$this->assertEquals($comment, $datarequest->get('comments'));
|
||||
|
||||
// Test adhoc task creation.
|
||||
$adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
|
||||
$this->assertCount(1, $adhoctasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for api::create_data_request() made by a parent.
|
||||
*/
|
||||
public function test_create_data_request_by_parent() {
|
||||
global $DB;
|
||||
|
||||
$generator = new testing_data_generator();
|
||||
$user = $generator->create_user();
|
||||
$parent = $generator->create_user();
|
||||
$comment = 'sample comment';
|
||||
|
||||
// Get the teacher role pretend it's the parent roles ;).
|
||||
$systemcontext = context_system::instance();
|
||||
$usercontext = context_user::instance($user->id);
|
||||
$parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
|
||||
// Give the manager role with the capability to manage data requests.
|
||||
assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
|
||||
// Assign the parent to user.
|
||||
role_assign($parentroleid, $parent->id, $usercontext->id);
|
||||
|
||||
// Login as the user's parent.
|
||||
$this->setUser($parent);
|
||||
|
||||
// Test data request creation.
|
||||
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
|
||||
$this->assertEquals($user->id, $datarequest->get('userid'));
|
||||
$this->assertEquals($parent->id, $datarequest->get('requestedby'));
|
||||
$this->assertEquals(0, $datarequest->get('dpo'));
|
||||
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
|
||||
$this->assertEquals($comment, $datarequest->get('comments'));
|
||||
@@ -351,8 +420,10 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
$comment = 'sample comment';
|
||||
|
||||
// Make a data request as user 1.
|
||||
$this->setUser($user1);
|
||||
$d1 = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
|
||||
// Make a data request as user 2.
|
||||
$this->setUser($user2);
|
||||
$d2 = api::create_data_request($user2->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
|
||||
|
||||
// Fetching data requests of specific users.
|
||||
@@ -406,6 +477,7 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
$user1 = $generator->create_user();
|
||||
|
||||
// Make a data request as user 1.
|
||||
$this->setUser($user1);
|
||||
$request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
// Set the status.
|
||||
api::update_request_status($request->get('id'), $status);
|
||||
@@ -1073,4 +1145,126 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
[$module1, $module2]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that delete requests filter out protected purpose contexts.
|
||||
*/
|
||||
public function test_add_request_contexts_with_status_delete() {
|
||||
$data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
|
||||
$contextids = $data->list->get_contextids();
|
||||
|
||||
$this->assertCount(1, $contextids);
|
||||
$this->assertEquals($data->contexts->unprotected, $contextids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that export requests don't filter out protected purpose contexts.
|
||||
*/
|
||||
public function test_add_request_contexts_with_status_export() {
|
||||
$data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
|
||||
$contextids = $data->list->get_contextids();
|
||||
|
||||
$this->assertCount(2, $contextids);
|
||||
$this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
|
||||
*
|
||||
* @param int $type The type of request to create
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function setup_test_add_request_contexts_with_status($type) {
|
||||
$this->setAdminUser();
|
||||
|
||||
// User under test.
|
||||
$s1 = $this->getDataGenerator()->create_user();
|
||||
|
||||
// Create three sample contexts.
|
||||
// 1 which should not be returned; and
|
||||
// 1 which will be returned and is not protected; and
|
||||
// 1 which will be returned and is protected.
|
||||
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$c3 = $this->getDataGenerator()->create_course();
|
||||
|
||||
$ctx1 = \context_course::instance($c1->id);
|
||||
$ctx2 = \context_course::instance($c2->id);
|
||||
$ctx3 = \context_course::instance($c3->id);
|
||||
|
||||
$unprotected = api::create_purpose((object)[
|
||||
'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
|
||||
$protected = api::create_purpose((object) [
|
||||
'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
|
||||
|
||||
$cat1 = api::create_category((object)['name' => 'a']);
|
||||
|
||||
// Set the defaults.
|
||||
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
|
||||
\context_helper::get_class_for_level(CONTEXT_SYSTEM)
|
||||
);
|
||||
set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
|
||||
set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
|
||||
|
||||
$contextinstance1 = api::set_context_instance((object) [
|
||||
'contextid' => $ctx1->id,
|
||||
'purposeid' => $unprotected->get('id'),
|
||||
'categoryid' => $cat1->get('id'),
|
||||
]);
|
||||
|
||||
$contextinstance2 = api::set_context_instance((object) [
|
||||
'contextid' => $ctx2->id,
|
||||
'purposeid' => $unprotected->get('id'),
|
||||
'categoryid' => $cat1->get('id'),
|
||||
]);
|
||||
|
||||
$contextinstance3 = api::set_context_instance((object) [
|
||||
'contextid' => $ctx3->id,
|
||||
'purposeid' => $protected->get('id'),
|
||||
'categoryid' => $cat1->get('id'),
|
||||
]);
|
||||
|
||||
$collection = new \core_privacy\local\request\contextlist_collection($s1->id);
|
||||
$contextlist = new \core_privacy\local\request\contextlist();
|
||||
$contextlist->set_component('tool_dataprivacy');
|
||||
$contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
|
||||
'ctx2' => $ctx2->id,
|
||||
'ctx3' => $ctx3->id,
|
||||
]);
|
||||
|
||||
$collection->add_contextlist($contextlist);
|
||||
|
||||
// Create the sample data request.
|
||||
$datarequest = api::create_data_request($s1->id, $type);
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
// Add the full collection with contexts 2, and 3.
|
||||
api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
|
||||
|
||||
// Mark it as approved.
|
||||
api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
|
||||
|
||||
// Fetch the list.
|
||||
$approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
|
||||
|
||||
return (object) [
|
||||
'contexts' => (object) [
|
||||
'unused' => [
|
||||
$ctx1->id,
|
||||
],
|
||||
'used' => [
|
||||
$ctx2->id,
|
||||
$ctx3->id,
|
||||
],
|
||||
'unprotected' => [
|
||||
$ctx2->id,
|
||||
],
|
||||
'protected' => [
|
||||
$ctx3->id,
|
||||
],
|
||||
],
|
||||
'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,22 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
|
||||
$this->getDataGenerator()->enrol_user($user3->id, $course2->id, 'student');
|
||||
$this->getDataGenerator()->enrol_user($user4->id, $course3->id, 'student');
|
||||
|
||||
// Add an activity and some data for user 2.
|
||||
$assignmod = $this->getDataGenerator()->create_module('assign', ['course' => $course2->id]);
|
||||
$data = (object) [
|
||||
'assignment' => $assignmod->id,
|
||||
'userid' => $user2->id,
|
||||
'timecreated' => time(),
|
||||
'timemodified' => time(),
|
||||
'status' => 'new',
|
||||
'groupid' => 0,
|
||||
'attemptnumber' => 0,
|
||||
'latest' => 1,
|
||||
];
|
||||
$DB->insert_record('assign_submission', $data);
|
||||
// We should have one record in the assign submission table.
|
||||
$this->assertEquals(1, $DB->count_records('assign_submission'));
|
||||
|
||||
// Users without lastaccess are skipped as well as users enroled in courses with no end date.
|
||||
$expired = new \tool_dataprivacy\expired_user_contexts();
|
||||
$numexpired = $expired->flag_expired();
|
||||
@@ -109,6 +125,9 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
|
||||
$deleted = $expired->delete();
|
||||
$this->assertEquals(0, $deleted);
|
||||
|
||||
// No user data left in mod_assign.
|
||||
$this->assertEquals(0, $DB->count_records('assign_submission'));
|
||||
|
||||
// The user is deleted.
|
||||
$deleteduser = \core_user::get_user($user2->id, 'id, deleted', IGNORE_MISSING);
|
||||
$this->assertEquals(1, $deleteduser->deleted);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Tests for the manager observer.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* API tests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_dataprivacy_manager_observer_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Helper to set andn return two users who are DPOs.
|
||||
*/
|
||||
protected function setup_site_dpos() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = new testing_data_generator();
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
|
||||
$context = context_system::instance();
|
||||
|
||||
// Give the manager role with the capability to manage data requests.
|
||||
$managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
|
||||
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
|
||||
|
||||
// Assign both users as manager.
|
||||
role_assign($managerroleid, $u1->id, $context->id);
|
||||
role_assign($managerroleid, $u2->id, $context->id);
|
||||
|
||||
// Only map the manager role to the DPO role.
|
||||
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
||||
|
||||
return \tool_dataprivacy\api::get_site_dpos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that when users are configured as DPO, they are sent an message upon failure.
|
||||
*/
|
||||
public function test_handle_component_failure() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create another user who is not a DPO.
|
||||
$this->getDataGenerator()->create_user();
|
||||
|
||||
// Create the DPOs.
|
||||
$dpos = $this->setup_site_dpos();
|
||||
|
||||
$observer = new \tool_dataprivacy\manager_observer();
|
||||
|
||||
// Handle the failure, catching messages.
|
||||
$mailsink = $this->redirectMessages();
|
||||
$mailsink->clear();
|
||||
$observer->handle_component_failure(new \Exception('error'), 'foo', 'bar', 'baz', ['foobarbaz', 'bum']);
|
||||
|
||||
// Messages should be sent to both DPOs only.
|
||||
$this->assertEquals(2, $mailsink->count());
|
||||
|
||||
$messages = $mailsink->get_messages();
|
||||
$messageusers = array_map(function($message) {
|
||||
return $message->useridto;
|
||||
}, $messages);
|
||||
|
||||
$this->assertEquals(array_keys($dpos), $messageusers, '', 0.0, 0, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that when no user is configured as DPO, the message is sent to admin instead.
|
||||
*/
|
||||
public function test_handle_component_failure_no_dpo() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create another user who is not a DPO or admin.
|
||||
$this->getDataGenerator()->create_user();
|
||||
|
||||
$observer = new \tool_dataprivacy\manager_observer();
|
||||
|
||||
$mailsink = $this->redirectMessages();
|
||||
$mailsink->clear();
|
||||
$observer->handle_component_failure(new \Exception('error'), 'foo', 'bar', 'baz', ['foobarbaz', 'bum']);
|
||||
|
||||
// Messages should have been sent only to the admin.
|
||||
$this->assertEquals(1, $mailsink->count());
|
||||
|
||||
$messages = $mailsink->get_messages();
|
||||
$message = reset($messages);
|
||||
|
||||
$admin = \core_user::get_user_by_username('admin');
|
||||
$this->assertEquals($admin->id, $message->useridto);
|
||||
}
|
||||
}
|
||||
@@ -72,8 +72,9 @@ class tool_dataprivacy_metadata_registry_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
// Let's check core subsystems.
|
||||
// The Privacy API adds an extra component in the form of 'core'.
|
||||
$coresubsystems = \core_component::get_core_subsystems();
|
||||
$this->assertEquals(count($coresubsystems), count($data['core']));
|
||||
$this->assertEquals(count($coresubsystems) + 1, count($data['core']));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$plugin->version = 2018040500;
|
||||
$plugin->requires = 2018040500; // Moodle 3.5dev (Build 2018031600) and upwards.
|
||||
$plugin->version = 2018051401;
|
||||
$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
|
||||
$plugin->component = 'tool_dataprivacy';
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_dbtransfer'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->component = 'tool_filetypes';
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->component = 'tool_generator';
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version
|
||||
$plugin->component = 'tool_health'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
$plugin->maturity = MATURITY_ALPHA; // this version's maturity level
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_httpsreplace'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version
|
||||
$plugin->component = 'tool_innodb'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'tool_installaddon';
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version
|
||||
$plugin->component = 'tool_langimport'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Privacy helper.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_log\local\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_privacy\local\request\transform;
|
||||
|
||||
/**
|
||||
* Privacy helper class.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
|
||||
/**
|
||||
* Returns an event from a standard record.
|
||||
*
|
||||
* @see \logstore_standard\log\store::get_log_event()
|
||||
* @param object $data Log data.
|
||||
* @return \core\event\base
|
||||
*/
|
||||
protected static function restore_event_from_standard_record($data) {
|
||||
$extra = ['origin' => $data->origin, 'ip' => $data->ip, 'realuserid' => $data->realuserid];
|
||||
$data = (array) $data;
|
||||
$id = $data['id'];
|
||||
$data['other'] = unserialize($data['other']);
|
||||
if ($data['other'] === false) {
|
||||
$data['other'] = [];
|
||||
}
|
||||
unset($data['origin']);
|
||||
unset($data['ip']);
|
||||
unset($data['realuserid']);
|
||||
unset($data['id']);
|
||||
|
||||
if (!$event = \core\event\base::restore($data, $extra)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a standard log record for a user.
|
||||
*
|
||||
* @param object $record The record.
|
||||
* @param int $userid The user ID.
|
||||
* @return array
|
||||
*/
|
||||
public static function transform_standard_log_record_for_userid($record, $userid) {
|
||||
|
||||
// Restore the event to try to get the name, description and other field.
|
||||
$restoredevent = static::restore_event_from_standard_record($record);
|
||||
if ($restoredevent) {
|
||||
$name = $restoredevent->get_name();
|
||||
$description = $restoredevent->get_description();
|
||||
$other = $restoredevent->other;
|
||||
|
||||
} else {
|
||||
$name = $record->eventname;
|
||||
$description = "Unknown event ({$name})";
|
||||
$other = unserialize($record->other);
|
||||
}
|
||||
|
||||
$realuserid = $record->realuserid;
|
||||
$isauthor = $record->userid == $userid;
|
||||
$isrelated = $record->relateduserid == $userid;
|
||||
$isrealuser = $realuserid == $userid;
|
||||
$ismasqueraded = $realuserid !== null && $record->userid != $realuserid;
|
||||
$ismasquerading = $isrealuser && !$isauthor;
|
||||
$isanonymous = $record->anonymous;
|
||||
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'timecreated' => transform::datetime($record->timecreated),
|
||||
'ip' => $record->ip,
|
||||
'origin' => static::transform_origin($record->origin),
|
||||
'other' => $other ? $other : []
|
||||
];
|
||||
|
||||
if ($isanonymous) {
|
||||
$data['action_was_done_anonymously'] = transform::yesno($isanonymous);
|
||||
}
|
||||
if ($isauthor || !$isanonymous) {
|
||||
$data['authorid'] = transform::user($record->userid);
|
||||
$data['author_of_the_action_was_you'] = transform::yesno($isauthor);
|
||||
}
|
||||
|
||||
if ($record->relateduserid) {
|
||||
$data['relateduserid'] = transform::user($record->relateduserid);
|
||||
$data['related_user_was_you'] = transform::yesno($isrelated);
|
||||
}
|
||||
|
||||
if ($ismasqueraded) {
|
||||
$data['author_of_the_action_was_masqueraded'] = transform::yesno(true);
|
||||
if ($ismasquerading || !$isanonymous) {
|
||||
$data['masqueradinguserid'] = transform::user($realuserid);
|
||||
$data['masquerading_user_was_you'] = transform::yesno($ismasquerading);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform origin.
|
||||
*
|
||||
* @param string $origin The page request origin.
|
||||
* @return string
|
||||
*/
|
||||
public static function transform_origin($origin) {
|
||||
switch ($origin) {
|
||||
case 'cli':
|
||||
case 'restore':
|
||||
case 'web':
|
||||
case 'ws':
|
||||
return get_string('privacy:request:origin:' . $origin, 'tool_log');
|
||||
break;
|
||||
}
|
||||
return $origin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Logstore provider interface.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_log\local\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
|
||||
/**
|
||||
* Logstore provider interface.
|
||||
*
|
||||
* Logstore subplugins providers must implement this interface.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
interface logstore_provider extends \core_privacy\local\request\plugin\subplugin_provider {
|
||||
|
||||
/**
|
||||
* Add contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param contextlist $contextlist The contextlist to add the contexts to.
|
||||
* @param int $userid The user to find the contexts for.
|
||||
* @return void
|
||||
*/
|
||||
public static function add_contexts_for_userid(contextlist $contextlist, $userid);
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
* @return void
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist);
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context);
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Moodle database: export and delete.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_log\local\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\writer;
|
||||
|
||||
/**
|
||||
* Moodle database: export and delete trait.
|
||||
*
|
||||
* This is to be used with logstores which use a database and table with the same columns
|
||||
* as the core plugin 'logstore_standard'.
|
||||
*
|
||||
* This trait expects the following methods to be present in the object:
|
||||
*
|
||||
* - public static function get_database_and_table(): [moodle_database|null, string|null]
|
||||
* - public static function get_export_subcontext(): []
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
trait moodle_database_export_and_delete {
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
list($db, $table) = static::get_database_and_table();
|
||||
if (!$db || !$table) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userid = $contextlist->get_user()->id;
|
||||
list($insql, $inparams) = $db->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
|
||||
$sql = "(userid = :userid1 OR relateduserid = :userid2 OR realuserid = :userid3) AND contextid $insql";
|
||||
$params = array_merge($inparams, [
|
||||
'userid1' => $userid,
|
||||
'userid2' => $userid,
|
||||
'userid3' => $userid,
|
||||
]);
|
||||
|
||||
$path = static::get_export_subcontext();
|
||||
$flush = function($lastcontextid, $data) use ($path) {
|
||||
$context = context::instance_by_id($lastcontextid);
|
||||
writer::with_context($context)->export_data($path, (object) ['logs' => $data]);
|
||||
};
|
||||
|
||||
$lastcontextid = null;
|
||||
$data = [];
|
||||
$recordset = $db->get_recordset_select($table, $sql, $params, 'contextid, timecreated, id');
|
||||
foreach ($recordset as $record) {
|
||||
if ($lastcontextid && $lastcontextid != $record->contextid) {
|
||||
$flush($lastcontextid, $data);
|
||||
$data = [];
|
||||
}
|
||||
$data[] = helper::transform_standard_log_record_for_userid($record, $userid);
|
||||
$lastcontextid = $record->contextid;
|
||||
}
|
||||
if ($lastcontextid) {
|
||||
$flush($lastcontextid, $data);
|
||||
}
|
||||
$recordset->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context) {
|
||||
list($db, $table) = static::get_database_and_table();
|
||||
if (!$db || !$table) {
|
||||
return;
|
||||
}
|
||||
$db->delete_records($table, ['contextid' => $context->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
list($db, $table) = static::get_database_and_table();
|
||||
if (!$db || !$table) {
|
||||
return;
|
||||
}
|
||||
list($insql, $inparams) = $db->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
|
||||
$params = array_merge($inparams, ['userid' => $contextlist->get_user()->id]);
|
||||
$db->delete_records_select($table, "userid = :userid AND contextid $insql", $params);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_log\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use tool_log\log\manager;
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package tool_log
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @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\provider {
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_plugintype_link('logstore', [], 'privacy:metadata:logstore');
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist {
|
||||
$contextlist = new \core_privacy\local\request\contextlist();
|
||||
static::call_subplugins_method_with_args('add_contexts_for_userid', [$contextlist, $userid]);
|
||||
return $contextlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
static::call_subplugins_method_with_args('export_user_data', [$contextlist]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context) {
|
||||
static::call_subplugins_method_with_args('delete_data_for_all_users_in_context', [$context]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
static::call_subplugins_method_with_args('delete_data_for_user', [$contextlist]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the subplugins method with arguments.
|
||||
*
|
||||
* @param string $method The method name.
|
||||
* @param array $args The arguments.
|
||||
* @return void
|
||||
*/
|
||||
protected static function call_subplugins_method_with_args($method, array $args = []) {
|
||||
$interface = \tool_log\local\privacy\logstore_provider::class;
|
||||
\core_privacy\manager::plugintype_class_callback('logstore', $interface, $method, $args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,13 @@ $string['actlogshdr'] = 'Available log stores';
|
||||
$string['configlogplugins'] = 'Please enable all required plugins and arrange them in appropriate order.';
|
||||
$string['logging'] = 'Logging';
|
||||
$string['managelogging'] = 'Manage log stores';
|
||||
$string['reportssupported'] = 'Reports supported';
|
||||
$string['pluginname'] = 'Log store manager';
|
||||
$string['privacy:metadata:logstore'] = 'The log stores';
|
||||
$string['privacy:path:logs'] = 'Logs';
|
||||
$string['privacy:request:origin:cli'] = 'Command line tool';
|
||||
$string['privacy:request:origin:restore'] = 'Backup being restored';
|
||||
$string['privacy:request:origin:web'] = 'Standard web request';
|
||||
$string['privacy:request:origin:ws'] = 'Mobile app or web service';
|
||||
$string['reportssupported'] = 'Reports supported';
|
||||
$string['subplugintype_logstore'] = 'Log store';
|
||||
$string['subplugintype_logstore_plural'] = 'Log stores';
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package logstore_database
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace logstore_database\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\contextlist;
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package logstore_database
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\tool_log\local\privacy\logstore_provider {
|
||||
|
||||
use \tool_log\local\privacy\moodle_database_export_and_delete;
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_external_location_link('log', [
|
||||
'eventname' => 'privacy:metadata:log:eventname',
|
||||
'userid' => 'privacy:metadata:log:userid',
|
||||
'relateduserid' => 'privacy:metadata:log:relateduserid',
|
||||
'anonymous' => 'privacy:metadata:log:anonymous',
|
||||
'other' => 'privacy:metadata:log:other',
|
||||
'timecreated' => 'privacy:metadata:log:timecreated',
|
||||
'origin' => 'privacy:metadata:log:origin',
|
||||
'ip' => 'privacy:metadata:log:ip',
|
||||
'realuserid' => 'privacy:metadata:log:realuserid',
|
||||
], 'privacy:metadata:log');
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param contextlist $contextlist The contextlist to add the contexts to.
|
||||
* @param int $userid The user to find the contexts for.
|
||||
* @return void
|
||||
*/
|
||||
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
|
||||
list($db, $table) = static::get_database_and_table();
|
||||
if (!$db || !$table) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = 'userid = :userid1 OR relateduserid = :userid2 OR realuserid = :userid3';
|
||||
$params = ['userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid];
|
||||
$contextids = $db->get_fieldset_select($table, 'DISTINCT contextid', $sql, $params);
|
||||
if (empty($contextids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = implode(' UNION ', array_map(function($id) use ($db) {
|
||||
return 'SELECT ' . $id . $db->sql_null_from_clause();
|
||||
}, $contextids));
|
||||
$contextlist->add_from_sql($sql, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database object.
|
||||
*
|
||||
* @return array Containing moodle_database, string, or null values.
|
||||
*/
|
||||
protected static function get_database_and_table() {
|
||||
$manager = get_log_manager();
|
||||
$store = new \logstore_database\log\store($manager);
|
||||
$db = $store->get_extdb();
|
||||
return $db ? [$db, $store->get_config_value('dbtable')] : [null, null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to export the logs to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_export_subcontext() {
|
||||
return [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_database')];
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,16 @@ $string['other'] = 'Other';
|
||||
$string['participating'] = 'Participating';
|
||||
$string['pluginname'] = 'External database log';
|
||||
$string['pluginname_desc'] = 'A log plugin that stores log entries in an external database table.';
|
||||
$string['privacy:metadata:log'] = 'A collection of past events';
|
||||
$string['privacy:metadata:log:anonymous'] = 'Whether the event was flagged as anonymous';
|
||||
$string['privacy:metadata:log:eventname'] = 'The event name';
|
||||
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
|
||||
$string['privacy:metadata:log:origin'] = 'The origin of the event';
|
||||
$string['privacy:metadata:log:other'] = 'Additional information about the event';
|
||||
$string['privacy:metadata:log:realuserid'] = 'The ID of the real user behind the event, when masquerading a user.';
|
||||
$string['privacy:metadata:log:relateduserid'] = 'The ID of a user related to this event';
|
||||
$string['privacy:metadata:log:timecreated'] = 'The time at which the event occurred';
|
||||
$string['privacy:metadata:log:userid'] = 'The ID of the user who triggered this event';
|
||||
$string['read'] = 'Read';
|
||||
$string['tablenotfound'] = 'Specified table was not found';
|
||||
$string['teaching'] = 'Teaching';
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package logstore_database
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use logstore_database\privacy\provider;
|
||||
|
||||
require_once(__DIR__ . '/fixtures/event.php');
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* This testcase is almost identical to the logstore_standard testcase, aside from the
|
||||
* initialisation of the relevant logstore obviously.
|
||||
*
|
||||
* @package logstore_database
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class logstore_database_privacy_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
$this->resetAfterTest();
|
||||
$this->preventResetByRollback(); // Logging waits till the transaction gets committed.
|
||||
|
||||
// Fake the settings, we will abuse the standard plugin table here...
|
||||
set_config('dbdriver', $CFG->dblibrary . '/' . $CFG->dbtype, 'logstore_database');
|
||||
set_config('dbhost', $CFG->dbhost, 'logstore_database');
|
||||
set_config('dbuser', $CFG->dbuser, 'logstore_database');
|
||||
set_config('dbpass', $CFG->dbpass, 'logstore_database');
|
||||
set_config('dbname', $CFG->dbname, 'logstore_database');
|
||||
set_config('dbtable', $CFG->prefix . 'logstore_standard_log', 'logstore_database');
|
||||
if (!empty($CFG->dboptions['dbpersist'])) {
|
||||
set_config('dbpersist', 1, 'logstore_database');
|
||||
} else {
|
||||
set_config('dbpersist', 0, 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbsocket'])) {
|
||||
set_config('dbsocket', $CFG->dboptions['dbsocket'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbsocket', '', 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbport'])) {
|
||||
set_config('dbport', $CFG->dboptions['dbport'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbport', '', 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbschema'])) {
|
||||
set_config('dbschema', $CFG->dboptions['dbschema'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbschema', '', 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbcollation'])) {
|
||||
set_config('dbcollation', $CFG->dboptions['dbcollation'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbcollation', '', 'logstore_database');
|
||||
}
|
||||
if (!empty($CFG->dboptions['dbhandlesoptions'])) {
|
||||
set_config('dbhandlesoptions', $CFG->dboptions['dbhandlesoptions'], 'logstore_database');
|
||||
} else {
|
||||
set_config('dbhandlesoptions', false, 'logstore_database');
|
||||
}
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
|
||||
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
$cm2ctx = context_module::instance($cm2->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $cm1ctx]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
|
||||
// User 2 is the related user.
|
||||
$this->setUser(0);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $cm2ctx, 'relateduserid' => $u2->id]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm2ctx]);
|
||||
|
||||
// Admin user is the real user.
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), []);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []);
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
|
||||
|
||||
// By admin user masquerading u1 related to u3.
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u1->id, context_system::instance());
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u3->id]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$sysctx, $cm1ctx, $c2ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx, $c2ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]);
|
||||
}
|
||||
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_database', [$c1ctx->id]));
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_all_users_in_context($c1ctx);
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_export_data_for_user() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$u4 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
$cm2ctx = context_module::instance($cm2->cmid);
|
||||
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_database')];
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 0]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is related.
|
||||
$this->setUser(0);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u2->id,
|
||||
'other' => ['i' => 1]]);
|
||||
$e->trigger();
|
||||
|
||||
// Admin user masquerades u3, which is related to u4.
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u4->id,
|
||||
'other' => ['i' => 2]]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertSame(0, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u2.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u2, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertSame(1, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u3.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u3, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
|
||||
$this->assertSame(2, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u4.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u4, 'logstore_database', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
|
||||
$this->assertSame(2, $data->logs[0]['other']['i']);
|
||||
|
||||
// Add anonymous events.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u2->id,
|
||||
'anonymous' => true]);
|
||||
$e->trigger();
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_database\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u4->id,
|
||||
'anonymous' => true]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_database', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
|
||||
// Confirm data present for u2.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u2, 'logstore_database', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
|
||||
// Confirm data present for u3.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u3, 'logstore_database', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
|
||||
|
||||
// Confirm data present for u4.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u4, 'logstore_database', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the content of a context list.
|
||||
*
|
||||
* @param contextlist $contextlist The collection.
|
||||
* @param array $expected List of expected contexts or IDs.
|
||||
* @return void
|
||||
*/
|
||||
protected function assert_contextlist_equals($contextlist, array $expected) {
|
||||
$expectedids = array_map(function($context) {
|
||||
if (is_object($context)) {
|
||||
return $context->id;
|
||||
}
|
||||
return $context;
|
||||
}, $expected);
|
||||
$contextids = array_map('intval', $contextlist->get_contextids());
|
||||
sort($contextids);
|
||||
sort($expectedids);
|
||||
$this->assertEquals($expectedids, $contextids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enable_logging() {
|
||||
set_config('enabled_stores', 'logstore_database', 'tool_log');
|
||||
set_config('buffersize', 0, 'logstore_database');
|
||||
set_config('logguests', 1, 'logstore_database');
|
||||
get_log_manager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contextlist for a user.
|
||||
*
|
||||
* @param object $user The user.
|
||||
* @return contextlist
|
||||
*/
|
||||
protected function get_contextlist_for_user($user) {
|
||||
$contextlist = new contextlist();
|
||||
provider::add_contexts_for_userid($contextlist, $user->id);
|
||||
return $contextlist;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'logstore_database'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package logstore_legacy
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace logstore_legacy\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use tool_log\local\privacy\helper;
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package logstore_legacy
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\tool_log\local\privacy\logstore_provider {
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_external_location_link('log', [
|
||||
'time' => 'privacy:metadata:log:time',
|
||||
'userid' => 'privacy:metadata:log:userid',
|
||||
'ip' => 'privacy:metadata:log:ip',
|
||||
'action' => 'privacy:metadata:log:action',
|
||||
'url' => 'privacy:metadata:log:url',
|
||||
'info' => 'privacy:metadata:log:info',
|
||||
], 'privacy:metadata:log');
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param contextlist $contextlist The contextlist to add the contexts to.
|
||||
* @param int $userid The user to find the contexts for.
|
||||
* @return void
|
||||
*/
|
||||
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
|
||||
$sql = "
|
||||
SELECT ctx.id
|
||||
FROM {context} ctx
|
||||
JOIN {log} l
|
||||
ON (l.cmid = 0 AND l.course = ctx.instanceid AND ctx.contextlevel = :courselevel)
|
||||
OR (l.cmid > 0 AND l.cmid = ctx.instanceid AND ctx.contextlevel = :modulelevel)
|
||||
OR (l.course <= 0 AND ctx.id = :syscontextid)
|
||||
WHERE l.userid = :userid";
|
||||
$params = [
|
||||
'courselevel' => CONTEXT_COURSE,
|
||||
'modulelevel' => CONTEXT_MODULE,
|
||||
'syscontextid' => SYSCONTEXTID,
|
||||
'userid' => $userid,
|
||||
];
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
|
||||
$userid = $contextlist->get_user()->id;
|
||||
list($insql, $inparams) = static::get_sql_where_from_contexts($contextlist->get_contexts());
|
||||
if (empty($insql)) {
|
||||
return;
|
||||
}
|
||||
$sql = "userid = :userid AND $insql";
|
||||
$params = array_merge($inparams, ['userid' => $userid]);
|
||||
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_legacy')];
|
||||
$flush = function($lastcontextid, $data) use ($path) {
|
||||
$context = context::instance_by_id($lastcontextid);
|
||||
writer::with_context($context)->export_data($path, (object) ['logs' => $data]);
|
||||
};
|
||||
|
||||
$lastcontextid = null;
|
||||
$data = [];
|
||||
$recordset = $DB->get_recordset_select('log', $sql, $params, 'course, cmid, time, id');
|
||||
foreach ($recordset as $record) {
|
||||
$event = \logstore_legacy\event\legacy_logged::restore_legacy($record);
|
||||
$context = $event->get_context();
|
||||
if ($lastcontextid && $lastcontextid != $context->id) {
|
||||
$flush($lastcontextid, $data);
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$extra = $event->get_logextra();
|
||||
$data[] = [
|
||||
'name' => $event->get_name(),
|
||||
'description' => $event->get_description(),
|
||||
'timecreated' => transform::datetime($event->timecreated),
|
||||
'ip' => $extra['ip'],
|
||||
'origin' => helper::transform_origin($extra['origin']),
|
||||
];
|
||||
|
||||
$lastcontextid = $context->id;
|
||||
}
|
||||
if ($lastcontextid) {
|
||||
$flush($lastcontextid, $data);
|
||||
}
|
||||
$recordset->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context) {
|
||||
global $DB;
|
||||
list($sql, $params) = static::get_sql_where_from_contexts([$context]);
|
||||
if (empty($sql)) {
|
||||
return;
|
||||
}
|
||||
$DB->delete_records_select('log', $sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
list($sql, $params) = static::get_sql_where_from_contexts($contextlist->get_contexts());
|
||||
if (empty($sql)) {
|
||||
return;
|
||||
}
|
||||
$userid = $contextlist->get_user()->id;
|
||||
$DB->delete_records_select('log', "$sql AND userid = :userid", array_merge($params, ['userid' => $userid]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an SQL where statement from a list of contexts.
|
||||
*
|
||||
* @param array $contexts The contexts.
|
||||
* @return array [$sql, $params]
|
||||
*/
|
||||
protected static function get_sql_where_from_contexts(array $contexts) {
|
||||
global $DB;
|
||||
|
||||
$sorted = array_reduce($contexts, function ($carry, $context) {
|
||||
$level = $context->contextlevel;
|
||||
if ($level == CONTEXT_MODULE || $level == CONTEXT_COURSE) {
|
||||
$carry[$level][] = $context->instanceid;
|
||||
} else if ($level == CONTEXT_SYSTEM) {
|
||||
$carry[$level] = $context->id;
|
||||
}
|
||||
return $carry;
|
||||
}, [
|
||||
CONTEXT_COURSE => [],
|
||||
CONTEXT_MODULE => [],
|
||||
CONTEXT_SYSTEM => null,
|
||||
]);
|
||||
|
||||
$sqls = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($sorted[CONTEXT_MODULE])) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_MODULE], SQL_PARAMS_NAMED);
|
||||
$sqls[] = "cmid $insql";
|
||||
$params = array_merge($params, $inparams);
|
||||
}
|
||||
|
||||
if (!empty($sorted[CONTEXT_COURSE])) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_COURSE], SQL_PARAMS_NAMED);
|
||||
|
||||
$sqls[] = "cmid = 0 AND course $insql";
|
||||
$params = array_merge($params, $inparams);
|
||||
}
|
||||
|
||||
if (!empty($sorted[CONTEXT_SYSTEM])) {
|
||||
$sqls[] = "course <= 0";
|
||||
}
|
||||
|
||||
if (empty($sqls)) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
return ['((' . implode(') OR (', $sqls) . '))', $params];
|
||||
}
|
||||
}
|
||||
@@ -27,4 +27,11 @@ $string['loglegacy'] = 'Log legacy data';
|
||||
$string['loglegacy_help'] = 'This plugin records log data to the legacy log table (mdl_log). This functionality has been replaced by newer, richer and more efficient logging plugins, so you should only run this plugin if you have old custom reports that directly query the old log table. Writing to the legacy logs will increase load, so it is recommended that you disable this plugin for performance reasons when it is not needed.';
|
||||
$string['pluginname'] = 'Legacy log';
|
||||
$string['pluginname_desc'] = 'A log plugin that stores log entries in the legacy log table.';
|
||||
$string['privacy:metadata:log'] = 'A collection of past events';
|
||||
$string['privacy:metadata:log:action'] = 'A description of the action';
|
||||
$string['privacy:metadata:log:info'] = 'Additional information';
|
||||
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
|
||||
$string['privacy:metadata:log:time'] = 'The date at wich the action took place';
|
||||
$string['privacy:metadata:log:url'] = 'The URL related to the event';
|
||||
$string['privacy:metadata:log:userid'] = 'The ID of the user who performed the action';
|
||||
$string['taskcleanup'] = 'Legacy log table cleanup';
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package logstore_legacy
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use logstore_legacy\privacy\provider;
|
||||
use logstore_legacy\event\unittest_executed;
|
||||
|
||||
require_once(__DIR__ . '/fixtures/event.php');
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* @package logstore_legacy
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class logstore_legacy_privacy_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->resetAfterTest();
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid() {
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm1ctx]);
|
||||
|
||||
// User 3 is the author.
|
||||
$this->setUser($u3);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []);
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 3]]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx]);
|
||||
}
|
||||
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]);
|
||||
$e->trigger();
|
||||
|
||||
// Assert what we have.
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete other context.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$c2ctx->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete system.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$sysctx->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete course.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$c1ctx->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(2, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete course.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_legacy', [$cm1ctx->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['userid' => $u1->id, 'cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]);
|
||||
$e->trigger();
|
||||
|
||||
// Assert what we have.
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete other context.
|
||||
provider::delete_data_for_all_users_in_context($c2ctx);
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(4, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete system.
|
||||
provider::delete_data_for_all_users_in_context($sysctx);
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(3, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete course.
|
||||
provider::delete_data_for_all_users_in_context($c1ctx);
|
||||
$this->assertTrue($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(2, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete course.
|
||||
provider::delete_data_for_all_users_in_context($cm1ctx);
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => $cm1->cmid, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => $c1->id]));
|
||||
$this->assertFalse($DB->record_exists('log', ['cmid' => 0, 'course' => 0]));
|
||||
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(0, $DB->count_records('log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_export_data_for_user() {
|
||||
global $DB;
|
||||
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_legacy')];
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 1]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 2]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 3]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 4]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = unittest_executed::create(['context' => $cm1ctx, 'other' => ['sample' => 5]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $c1ctx, 'other' => ['sample' => 6]]);
|
||||
$e->trigger();
|
||||
$e = unittest_executed::create(['context' => $sysctx, 'other' => ['sample' => 7]]);
|
||||
$e->trigger();
|
||||
|
||||
// Test export.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$cm1ctx->id]));
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($cm1ctx)->get_data($path);
|
||||
$this->assertCount(2, $data->logs);
|
||||
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$c1ctx->id]));
|
||||
$data = writer::with_context($cm1ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_legacy', [$sysctx->id]));
|
||||
$data = writer::with_context($sysctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the content of a context list.
|
||||
*
|
||||
* @param contextlist $contextlist The collection.
|
||||
* @param array $expected List of expected contexts or IDs.
|
||||
* @return void
|
||||
*/
|
||||
protected function assert_contextlist_equals($contextlist, array $expected) {
|
||||
$expectedids = array_map(function($context) {
|
||||
if (is_object($context)) {
|
||||
return $context->id;
|
||||
}
|
||||
return $context;
|
||||
}, $expected);
|
||||
$contextids = array_map('intval', $contextlist->get_contextids());
|
||||
sort($contextids);
|
||||
sort($expectedids);
|
||||
$this->assertEquals($expectedids, $contextids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enable_logging() {
|
||||
set_config('enabled_stores', 'logstore_legacy', 'tool_log');
|
||||
set_config('loglegacy', 1, 'logstore_legacy');
|
||||
get_log_manager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contextlist for a user.
|
||||
*
|
||||
* @param object $user The user.
|
||||
* @return contextlist
|
||||
*/
|
||||
protected function get_contextlist_for_user($user) {
|
||||
$contextlist = new contextlist();
|
||||
provider::add_contexts_for_userid($contextlist, $user->id);
|
||||
return $contextlist;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'logstore_legacy'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package logstore_standard
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace logstore_standard\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\contextlist;
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package logstore_standard
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
\core_privacy\local\metadata\provider,
|
||||
\tool_log\local\privacy\logstore_provider {
|
||||
|
||||
use \tool_log\local\privacy\moodle_database_export_and_delete;
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_database_table('logstore_standard_log', [
|
||||
'eventname' => 'privacy:metadata:log:eventname',
|
||||
'userid' => 'privacy:metadata:log:userid',
|
||||
'relateduserid' => 'privacy:metadata:log:relateduserid',
|
||||
'anonymous' => 'privacy:metadata:log:anonymous',
|
||||
'other' => 'privacy:metadata:log:other',
|
||||
'timecreated' => 'privacy:metadata:log:timecreated',
|
||||
'origin' => 'privacy:metadata:log:origin',
|
||||
'ip' => 'privacy:metadata:log:ip',
|
||||
'realuserid' => 'privacy:metadata:log:realuserid',
|
||||
], 'privacy:metadata:log');
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param contextlist $contextlist The contextlist to add the contexts to.
|
||||
* @param int $userid The user to find the contexts for.
|
||||
* @return void
|
||||
*/
|
||||
public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
|
||||
$sql = "
|
||||
SELECT l.contextid
|
||||
FROM {logstore_standard_log} l
|
||||
WHERE l.userid = :userid1
|
||||
OR l.relateduserid = :userid2
|
||||
OR l.realuserid = :userid3";
|
||||
$contextlist->add_from_sql($sql, [
|
||||
'userid1' => $userid,
|
||||
'userid2' => $userid,
|
||||
'userid3' => $userid,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database object.
|
||||
*
|
||||
* @return array Containing moodle_database, string, or null values.
|
||||
*/
|
||||
protected static function get_database_and_table() {
|
||||
global $DB;
|
||||
return [$DB, 'logstore_standard_log'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to export the logs to.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_export_subcontext() {
|
||||
return [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')];
|
||||
}
|
||||
}
|
||||
@@ -25,4 +25,14 @@
|
||||
$string['buffersize'] = 'Write buffer size';
|
||||
$string['pluginname'] = 'Standard log';
|
||||
$string['pluginname_desc'] = 'A log plugin stores log entries in a Moodle database table.';
|
||||
$string['privacy:metadata:log'] = 'A collection of past events';
|
||||
$string['privacy:metadata:log:anonymous'] = 'Whether the event was flagged as anonymous';
|
||||
$string['privacy:metadata:log:eventname'] = 'The event name';
|
||||
$string['privacy:metadata:log:ip'] = 'The IP address used at the time of the event';
|
||||
$string['privacy:metadata:log:origin'] = 'The origin of the event';
|
||||
$string['privacy:metadata:log:other'] = 'Additional information about the event';
|
||||
$string['privacy:metadata:log:realuserid'] = 'The ID of the real user behind the event, when masquerading a user.';
|
||||
$string['privacy:metadata:log:relateduserid'] = 'The ID of a user related to this event';
|
||||
$string['privacy:metadata:log:timecreated'] = 'The time at which the event occurred';
|
||||
$string['privacy:metadata:log:userid'] = 'The ID of the user who triggered this event';
|
||||
$string['taskcleanup'] = 'Log table cleanup';
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package logstore_standard
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use logstore_standard\privacy\provider;
|
||||
|
||||
require_once(__DIR__ . '/fixtures/event.php');
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* @package logstore_standard
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class logstore_standard_privacy_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->resetAfterTest();
|
||||
$this->preventResetByRollback(); // Logging waits till the transaction gets committed.
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
|
||||
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
$cm2ctx = context_module::instance($cm2->cmid);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), []);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $cm1ctx]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
|
||||
// User 2 is the related user.
|
||||
$this->setUser(0);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), []);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $cm2ctx, 'relateduserid' => $u2->id]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u2), [$cm2ctx]);
|
||||
|
||||
// Admin user is the real user.
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), []);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), []);
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
|
||||
|
||||
// By admin user masquerading u1 related to u3.
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$cm1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx]);
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u1->id, context_system::instance());
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u3->id]);
|
||||
$e->trigger();
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u1), [$sysctx, $cm1ctx, $c2ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($u3), [$sysctx, $c1ctx, $c2ctx]);
|
||||
$this->assert_contextlist_equals($this->get_contextlist_for_user($admin), [$sysctx, $c1ctx, $c2ctx]);
|
||||
}
|
||||
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_standard', [$c1ctx->id]));
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(3, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_all_users_in_context($c1ctx);
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_export_data_for_user() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$u3 = $this->getDataGenerator()->create_user();
|
||||
$u4 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$cm1 = $this->getDataGenerator()->create_module('url', ['course' => $c1]);
|
||||
$c2 = $this->getDataGenerator()->create_course();
|
||||
$cm2 = $this->getDataGenerator()->create_module('url', ['course' => $c2]);
|
||||
$sysctx = context_system::instance();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
$c2ctx = context_course::instance($c2->id);
|
||||
$cm1ctx = context_module::instance($cm1->cmid);
|
||||
$cm2ctx = context_module::instance($cm2->cmid);
|
||||
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')];
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 0]]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is related.
|
||||
$this->setUser(0);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u2->id,
|
||||
'other' => ['i' => 1]]);
|
||||
$e->trigger();
|
||||
|
||||
// Admin user masquerades u3, which is related to u4.
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'relateduserid' => $u4->id,
|
||||
'other' => ['i' => 2]]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertSame(0, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u2.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u2, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertSame(1, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u3.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u3, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
|
||||
$this->assertSame(2, $data->logs[0]['other']['i']);
|
||||
|
||||
// Confirm data present for u4.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u4, 'logstore_standard', [$c2ctx->id, $c1ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertEquals(transform::yesno(false), $data->logs[0]['masquerading_user_was_you']);
|
||||
$this->assertSame(2, $data->logs[0]['other']['i']);
|
||||
|
||||
// Add anonymous events.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u2->id,
|
||||
'anonymous' => true]);
|
||||
$e->trigger();
|
||||
$this->setAdminUser();
|
||||
\core\session\manager::loginas($u3->id, $sysctx);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c2ctx, 'relateduserid' => $u4->id,
|
||||
'anonymous' => true]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'logstore_standard', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
|
||||
// Confirm data present for u2.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u2, 'logstore_standard', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
|
||||
// Confirm data present for u3.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u3, 'logstore_standard', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
|
||||
|
||||
// Confirm data present for u4.
|
||||
writer::reset();
|
||||
provider::export_user_data(new approved_contextlist($u4, 'logstore_standard', [$c2ctx->id]));
|
||||
$data = writer::with_context($c2ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['action_was_done_anonymously']);
|
||||
$this->assertArrayNotHasKey('author_of_the_action_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('authorid', $data->logs[0]);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['related_user_was_you']);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_masqueraded']);
|
||||
$this->assertArrayNotHasKey('masquerading_user_was_you', $data->logs[0]);
|
||||
$this->assertArrayNotHasKey('masqueradinguserid', $data->logs[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the content of a context list.
|
||||
*
|
||||
* @param contextlist $contextlist The collection.
|
||||
* @param array $expected List of expected contexts or IDs.
|
||||
* @return void
|
||||
*/
|
||||
protected function assert_contextlist_equals($contextlist, array $expected) {
|
||||
$expectedids = array_map(function($context) {
|
||||
if (is_object($context)) {
|
||||
return $context->id;
|
||||
}
|
||||
return $context;
|
||||
}, $expected);
|
||||
$contextids = array_map('intval', $contextlist->get_contextids());
|
||||
sort($contextids);
|
||||
sort($expectedids);
|
||||
$this->assertEquals($expectedids, $contextids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enable_logging() {
|
||||
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
||||
set_config('buffersize', 0, 'logstore_standard');
|
||||
set_config('logguests', 1, 'logstore_standard');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contextlist for a user.
|
||||
*
|
||||
* @param object $user The user.
|
||||
* @return contextlist
|
||||
*/
|
||||
protected function get_contextlist_for_user($user) {
|
||||
$contextlist = new contextlist();
|
||||
provider::add_contexts_for_userid($contextlist, $user->id);
|
||||
return $contextlist;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'logstore_standard'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package tool_log
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use tool_log\privacy\provider;
|
||||
|
||||
require_once($CFG->dirroot . '/admin/tool/log/store/standard/tests/fixtures/event.php');
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* We're not testing the full functionality, just that the provider passes the requests
|
||||
* down to at least one of its subplugin. Each subplugin should have tests to cover the
|
||||
* different provider methods in depth.
|
||||
*
|
||||
* @package tool_log
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_log_privacy_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
$this->resetAfterTest();
|
||||
$this->preventResetByRollback(); // Logging waits till the transaction gets committed.
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
$this->setUser($u1);
|
||||
$this->assertEmpty(provider::get_contexts_for_userid($u1->id)->get_contextids(), []);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$this->assertEquals($c1ctx->id, provider::get_contexts_for_userid($u1->id)->get_contextids()[0]);
|
||||
}
|
||||
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'logstore_standard', [$c1ctx->id]));
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['userid' => $u1->id, 'contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(0, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$u2 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// User 2 is the author.
|
||||
$this->setUser($u2);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present.
|
||||
$this->assertTrue($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(2, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(1, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
|
||||
// Delete all the things!
|
||||
provider::delete_data_for_all_users_in_context($c1ctx);
|
||||
$this->assertFalse($DB->record_exists('logstore_standard_log', ['contextid' => $c1ctx->id]));
|
||||
$this->assertEquals(0, $DB->count_records('logstore_standard_log', ['userid' => $u1->id]));
|
||||
$this->assertEquals(0, $DB->count_records('logstore_standard_log', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_export_data_for_user() {
|
||||
$admin = \core_user::get_user(2);
|
||||
$u1 = $this->getDataGenerator()->create_user();
|
||||
$c1 = $this->getDataGenerator()->create_course();
|
||||
$c1ctx = context_course::instance($c1->id);
|
||||
|
||||
$path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_standard')];
|
||||
$this->enable_logging();
|
||||
$manager = get_log_manager(true);
|
||||
|
||||
// User 1 is the author.
|
||||
$this->setUser($u1);
|
||||
$e = \logstore_standard\event\unittest_executed::create(['context' => $c1ctx, 'other' => ['i' => 123]]);
|
||||
$e->trigger();
|
||||
|
||||
// Confirm data present for u1.
|
||||
provider::export_user_data(new approved_contextlist($u1, 'tool_log', [$c1ctx->id]));
|
||||
$data = writer::with_context($c1ctx)->get_data($path);
|
||||
$this->assertCount(1, $data->logs);
|
||||
$this->assertEquals(transform::yesno(true), $data->logs[0]['author_of_the_action_was_you']);
|
||||
$this->assertSame(123, $data->logs[0]['other']['i']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable logging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function enable_logging() {
|
||||
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
||||
set_config('buffersize', 0, 'logstore_standard');
|
||||
set_config('logguests', 1, 'logstore_standard');
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_log'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -130,6 +130,11 @@ define(['jquery',
|
||||
* @return {Promise}
|
||||
*/
|
||||
PlanActions.prototype._callAndRefresh = function(calls, planData) {
|
||||
// Because this function causes a refresh, we must track the JS completion from start to finish to prevent
|
||||
// stale reference issues in Behat.
|
||||
var callKey = 'tool_lp/planactions:_callAndRefresh-' + Math.floor(Math.random() * Math.floor(1000));
|
||||
M.util.js_pending(callKey);
|
||||
|
||||
var self = this;
|
||||
calls.push({
|
||||
methodname: self._contextMethod,
|
||||
@@ -141,7 +146,10 @@ define(['jquery',
|
||||
.then(function() {
|
||||
return self._renderView(arguments[arguments.length - 1]);
|
||||
})
|
||||
.fail(notification.exception);
|
||||
.fail(notification.exception)
|
||||
.always(function() {
|
||||
return M.util.js_complete(callKey);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_lp'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_lpimportcsv'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->dependencies = array('tool_lp' => 2017110800);
|
||||
$plugin->dependencies = array('tool_lp' => 2018050800);
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_lpmigrate'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->dependencies = array(
|
||||
'tool_lp' => ANY_VERSION
|
||||
|
||||
@@ -310,6 +310,16 @@ class manager {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove older verification failures.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function tidy_old_verification_failures() {
|
||||
global $DB;
|
||||
$DB->delete_records_select('messageinbound_messagelist', 'timecreated < :time', ['time' => time() - DAYSECS]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a message and pass it through the Inbound Message handling systems.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @package tool_messageinbound
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_messageinbound\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use context;
|
||||
use context_user;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
|
||||
/**
|
||||
* Data provider class.
|
||||
*
|
||||
* @package tool_messageinbound
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @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\plugin\provider {
|
||||
|
||||
/**
|
||||
* Returns metadata.
|
||||
*
|
||||
* @param collection $collection The initialised collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
|
||||
$collection->add_database_table('messageinbound_messagelist', [
|
||||
'messageid' => 'privacy:metadata:messagelist:messageid',
|
||||
'userid' => 'privacy:metadata:messagelist:userid',
|
||||
'address' => 'privacy:metadata:messagelist:address',
|
||||
'timecreated' => 'privacy:metadata:messagelist:timecreated',
|
||||
], 'privacy:metadata:messagelist');
|
||||
|
||||
// Arguably the keys are handled by \core\message\inbound\address_manager and thus could/should be handled by core.
|
||||
$collection->add_subsystem_link('core_userkey', [], 'privacy:metadata:coreuserkey');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param int $userid The user to search.
|
||||
* @return \contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist {
|
||||
$contextlist = new \core_privacy\local\request\contextlist();
|
||||
|
||||
// Always add the user context so we're sure we're not dodging user keys, besides it's not costly to do so.
|
||||
$contextlist->add_user_context($userid);
|
||||
|
||||
return $contextlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
if (!static::approved_contextlist_contains_my_context($contextlist)) {
|
||||
// We only care about the user's user context.
|
||||
return;
|
||||
}
|
||||
|
||||
$userid = $contextlist->get_user()->id;
|
||||
$context = context_user::instance($userid);
|
||||
$path = [get_string('messageinbound', 'tool_messageinbound')];
|
||||
|
||||
// Export user keys.
|
||||
\core_userkey\privacy\provider::export_userkeys($context, $path, 'messageinbound_handler');
|
||||
|
||||
// Export the message list.
|
||||
$data = [];
|
||||
$recordset = $DB->get_recordset('messageinbound_messagelist', ['userid' => $userid], 'timecreated, id');
|
||||
foreach ($recordset as $record) {
|
||||
$data[] = [
|
||||
'received_at' => $record->address,
|
||||
'timecreated' => transform::datetime($record->timecreated),
|
||||
];
|
||||
}
|
||||
$recordset->close();
|
||||
writer::with_context($context)->export_data($path, (object) ['messages_pending_validation' => $data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all data for all users in the specified context.
|
||||
*
|
||||
* @param context $context The specific context to delete data for.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(context $context) {
|
||||
global $DB;
|
||||
if ($context->contextlevel != CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
|
||||
static::delete_user_data($context->instanceid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
global $DB;
|
||||
if (!static::approved_contextlist_contains_my_context($contextlist)) {
|
||||
// We only care about the user's user context.
|
||||
return;
|
||||
}
|
||||
|
||||
static::delete_user_data($contextlist->get_user()->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user's data.
|
||||
*
|
||||
* @param int $userid The user ID.
|
||||
* @return void
|
||||
*/
|
||||
protected static function delete_user_data($userid) {
|
||||
global $DB;
|
||||
$DB->delete_records_select('messageinbound_messagelist', 'userid = :userid', ['userid' => $userid]);
|
||||
\core_userkey\privacy\provider::delete_userkeys('messageinbound_handler', $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the contextlist contains our own context.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The contextlist
|
||||
* @return bool
|
||||
*/
|
||||
protected static function approved_contextlist_contains_my_context(approved_contextlist $contextlist) {
|
||||
$userid = $contextlist->get_user()->id;
|
||||
foreach ($contextlist->get_contexts() as $context) {
|
||||
if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,6 +49,7 @@ class cleanup_task extends \core\task\scheduled_task {
|
||||
*/
|
||||
public function execute() {
|
||||
$manager = new \tool_messageinbound\manager();
|
||||
return $manager->tidy_old_messages();
|
||||
$manager->tidy_old_messages();
|
||||
$manager->tidy_old_verification_failures();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
$string['classname'] = 'Class name';
|
||||
$string['component'] = 'Component';
|
||||
$string['configmessageinboundhost'] = 'The address of the server that Moodle should check mail against. To specify a non-default port, you can use the [server]:[port], for example mail.example.com:587 format. If you leave this field blank, Moodle will use the default port for the type of mail server you specify.';
|
||||
$string['configmessageinboundhost'] = 'The address of the server that Moodle should check mail against. To specify a non-default port, use [server]:[port], for example mail.example.com:587. If a port isn\'t specified, the default port for the type of mail server will be used.';
|
||||
$string['defaultexpiration'] = 'Default address expiry period';
|
||||
$string['defaultexpiration_help'] = 'When an email address is generated by the handler, it can be set to automatically expire after a period of time, so that it can no longer be used. It is advisable to set an expiry period.';
|
||||
$string['description'] = 'Description';
|
||||
@@ -45,7 +45,7 @@ $string['invalid_recipient_handler'] = 'If a valid message is received but the s
|
||||
This handler processes those replies.
|
||||
|
||||
It is not possible to disable sender verification of this handler because the user may reply from an incorrect email address if their email client configuration is incorrect.';
|
||||
$string['invalid_recipient_handler_name'] = 'Invalid recipient handler';
|
||||
$string['invalid_recipient_handler_name'] = 'Invalid sender handler';
|
||||
$string['invalidrecipientdescription'] = 'The message "{$a->subject}" could not be authenticated, since it was sent from a different email address than in your user profile. For the message to be authenticated, you need to reply to this message.';
|
||||
$string['invalidrecipientdescriptionhtml'] = 'The message "{$a->subject}" could not be authenticated, since it was sent from a different email address than in your user profile. For the message to be authenticated, you need to reply to this message.';
|
||||
$string['invalidrecipientfinal'] = 'The message "{$a->subject}" could not be authenticated. Please check that you are sending your message from the same email address as in your profile.';
|
||||
@@ -94,6 +94,12 @@ $string['onehour'] = 'One hour';
|
||||
$string['oneweek'] = 'One week';
|
||||
$string['oneyear'] = 'One year';
|
||||
$string['pluginname'] = 'Inbound message configuration';
|
||||
$string['privacy:metadata:coreuserkey'] = 'User\'s keys to validate the email received';
|
||||
$string['privacy:metadata:messagelist'] = 'A list of message identifiers which failed validation and requires further authorisation';
|
||||
$string['privacy:metadata:messagelist:address'] = 'The address at which the email was sent';
|
||||
$string['privacy:metadata:messagelist:messageid'] = 'The message ID';
|
||||
$string['privacy:metadata:messagelist:timecreated'] = 'The time at which the record was made';
|
||||
$string['privacy:metadata:messagelist:userid'] = 'The ID of user who need to approve the message';
|
||||
$string['replysubjectprefix'] = 'Re:';
|
||||
$string['requirevalidation'] = 'Validate sender address';
|
||||
$string['name'] = 'Name';
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Manager tests.
|
||||
*
|
||||
* @package tool_messageinbound
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use tool_messageinbound\privacy\provider;
|
||||
|
||||
/**
|
||||
* Manager testcase class.
|
||||
*
|
||||
* @package tool_messageinbound
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_messageinbound_manager_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Pretend the system is enabled.
|
||||
$CFG->messageinbound_enabled = true;
|
||||
$CFG->messageinbound_mailbox = 'mailbox';
|
||||
$CFG->messageinbound_domain = 'example.com';
|
||||
}
|
||||
|
||||
public function test_tidy_old_verification_failures() {
|
||||
global $DB;
|
||||
|
||||
$now = time();
|
||||
$stale = $now - DAYSECS - 1; // Make a second older because PHP Unit is too damn fast!!
|
||||
|
||||
$this->create_messagelist(['timecreated' => $now]);
|
||||
$this->create_messagelist(['timecreated' => $now - HOURSECS]);
|
||||
$this->create_messagelist(['timecreated' => $stale]);
|
||||
$this->create_messagelist(['timecreated' => $stale - HOURSECS]);
|
||||
$this->create_messagelist(['timecreated' => $stale - YEARSECS]);
|
||||
|
||||
$this->assertEquals(5, $DB->count_records('messageinbound_messagelist', []));
|
||||
$this->assertEquals(3, $DB->count_records_select('messageinbound_messagelist', 'timecreated < :t', ['t' => $stale + 1]));
|
||||
|
||||
$manager = new \tool_messageinbound\manager();
|
||||
$manager->tidy_old_verification_failures();
|
||||
|
||||
$this->assertEquals(2, $DB->count_records('messageinbound_messagelist', []));
|
||||
$this->assertEquals(0, $DB->count_records_select('messageinbound_messagelist', 'timecreated < :t', ['t' => $stale + 1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message to validate.
|
||||
*
|
||||
* @param array $params The params.
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function create_messagelist(array $params) {
|
||||
global $DB, $USER;
|
||||
$record = (object) array_merge([
|
||||
'messageid' => 'abc',
|
||||
'userid' => $USER->id,
|
||||
'address' => 'text@example.com',
|
||||
'timecreated' => time(),
|
||||
], $params);
|
||||
$record->id = $DB->insert_record('messageinbound_messagelist', $record);
|
||||
return $record;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Data provider tests.
|
||||
*
|
||||
* @package tool_messageinbound
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_privacy\tests\provider_testcase;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use tool_messageinbound\privacy\provider;
|
||||
|
||||
/**
|
||||
* Data provider testcase class.
|
||||
*
|
||||
* @package tool_messageinbound
|
||||
* @category test
|
||||
* @copyright 2018 Frédéric Massart
|
||||
* @author Frédéric Massart <fred@branchup.tech>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_messageinbound_privacy_testcase extends provider_testcase {
|
||||
|
||||
public function setUp() {
|
||||
global $CFG;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Pretend the system is enabled.
|
||||
$CFG->messageinbound_enabled = true;
|
||||
$CFG->messageinbound_mailbox = 'mailbox';
|
||||
$CFG->messageinbound_domain = 'example.com';
|
||||
}
|
||||
|
||||
public function test_get_contexts_for_userid() {
|
||||
$dg = $this->getDataGenerator();
|
||||
$u1 = $dg->create_user();
|
||||
$u2 = $dg->create_user();
|
||||
$u1ctx = context_user::instance($u1->id);
|
||||
$u2ctx = context_user::instance($u2->id);
|
||||
|
||||
$contexts = provider::get_contexts_for_userid($u1->id)->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
$this->assertEquals($u1ctx->id, $contexts[0]->id);
|
||||
|
||||
$contexts = provider::get_contexts_for_userid($u2->id)->get_contexts();
|
||||
$this->assertCount(1, $contexts);
|
||||
$this->assertEquals($u2ctx->id, $contexts[0]->id);
|
||||
}
|
||||
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
$dg = $this->getDataGenerator();
|
||||
$u1 = $dg->create_user();
|
||||
$u2 = $dg->create_user();
|
||||
$u1ctx = context_user::instance($u1->id);
|
||||
$u2ctx = context_user::instance($u2->id);
|
||||
|
||||
$addressmanager = new \core\message\inbound\address_manager();
|
||||
$addressmanager->set_handler('\tool_messageinbound\message\inbound\invalid_recipient_handler');
|
||||
$addressmanager->set_data(123);
|
||||
|
||||
// Create a user key for both users.
|
||||
$addressmanager->generate($u1->id);
|
||||
$addressmanager->generate($u2->id);
|
||||
|
||||
// Create a messagelist for both users.
|
||||
$this->create_messagelist(['userid' => $u1->id]);
|
||||
$this->create_messagelist(['userid' => $u2->id]);
|
||||
|
||||
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
|
||||
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
|
||||
|
||||
// Passing another user's context does not do anything.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'tool_messageinbound', [$u2ctx->id]));
|
||||
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
|
||||
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
|
||||
|
||||
// Deleting user 1.
|
||||
provider::delete_data_for_user(new approved_contextlist($u1, 'tool_messageinbound', [$u1ctx->id]));
|
||||
$this->assertFalse($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertFalse($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
|
||||
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
$dg = $this->getDataGenerator();
|
||||
$u1 = $dg->create_user();
|
||||
$u2 = $dg->create_user();
|
||||
$u1ctx = context_user::instance($u1->id);
|
||||
$u2ctx = context_user::instance($u2->id);
|
||||
|
||||
$addressmanager = new \core\message\inbound\address_manager();
|
||||
$addressmanager->set_handler('\tool_messageinbound\message\inbound\invalid_recipient_handler');
|
||||
$addressmanager->set_data(123);
|
||||
|
||||
// Create a user key for both users.
|
||||
$addressmanager->generate($u1->id);
|
||||
$addressmanager->generate($u2->id);
|
||||
|
||||
// Create a messagelist for both users.
|
||||
$this->create_messagelist(['userid' => $u1->id]);
|
||||
$this->create_messagelist(['userid' => $u2->id]);
|
||||
|
||||
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
|
||||
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
|
||||
|
||||
// Deleting user 1.
|
||||
provider::delete_data_for_all_users_in_context($u1ctx);
|
||||
$this->assertFalse($DB->record_exists('user_private_key', ['userid' => $u1->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertTrue($DB->record_exists('user_private_key', ['userid' => $u2->id, 'script' => 'messageinbound_handler']));
|
||||
$this->assertFalse($DB->record_exists('messageinbound_messagelist', ['userid' => $u1->id]));
|
||||
$this->assertTrue($DB->record_exists('messageinbound_messagelist', ['userid' => $u2->id]));
|
||||
}
|
||||
|
||||
public function test_export_data_for_user() {
|
||||
$dg = $this->getDataGenerator();
|
||||
$u1 = $dg->create_user();
|
||||
$u2 = $dg->create_user();
|
||||
$u1ctx = context_user::instance($u1->id);
|
||||
$u2ctx = context_user::instance($u2->id);
|
||||
|
||||
$addressmanager = new \core\message\inbound\address_manager();
|
||||
$addressmanager->set_handler('\tool_messageinbound\message\inbound\invalid_recipient_handler');
|
||||
$addressmanager->set_data(123);
|
||||
|
||||
// Create a user key for both users.
|
||||
$addressmanager->generate($u1->id);
|
||||
$addressmanager->generate($u2->id);
|
||||
|
||||
// Create a messagelist for both users.
|
||||
$this->create_messagelist(['userid' => $u1->id, 'address' => 'u1@example1.com']);
|
||||
$this->create_messagelist(['userid' => $u1->id, 'address' => 'u1@example2.com']);
|
||||
$this->create_messagelist(['userid' => $u2->id, 'address' => 'u2@example1.com']);
|
||||
|
||||
// Export for user.
|
||||
$this->setUser($u1);
|
||||
provider::export_user_data(new approved_contextlist($u1, 'tool_messageinbound', [$u1ctx->id, $u2ctx->id]));
|
||||
$data = writer::with_context($u2ctx)->get_data([get_string('messageinbound', 'tool_messageinbound')]);
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($u1ctx)->get_data([get_string('messageinbound', 'tool_messageinbound')]);
|
||||
$this->assertCount(2, $data->messages_pending_validation);
|
||||
$this->assertEquals('u1@example1.com', $data->messages_pending_validation[0]['received_at']);
|
||||
$this->assertEquals('u1@example2.com', $data->messages_pending_validation[1]['received_at']);
|
||||
|
||||
$data = writer::with_context($u2ctx)->get_related_data([get_string('messageinbound', 'tool_messageinbound')], 'userkeys');
|
||||
$this->assertEmpty($data);
|
||||
$data = writer::with_context($u1ctx)->get_related_data([get_string('messageinbound', 'tool_messageinbound')], 'userkeys');
|
||||
$this->assertCount(1, $data->keys);
|
||||
$this->assertEquals('messageinbound_handler', $data->keys[0]->script);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a message to validate.
|
||||
*
|
||||
* @param array $params The params.
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function create_messagelist(array $params) {
|
||||
global $DB, $USER;
|
||||
$record = (object) array_merge([
|
||||
'messageid' => 'abc',
|
||||
'userid' => $USER->id,
|
||||
'address' => 'text@example.com',
|
||||
'timecreated' => time(),
|
||||
], $params);
|
||||
$record->id = $DB->insert_record('messageinbound_messagelist', $record);
|
||||
return $record;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017111300;
|
||||
$plugin->requires = 2017110800;
|
||||
$plugin->version = 2018051400;
|
||||
$plugin->requires = 2018050800;
|
||||
$plugin->component = 'tool_messageinbound';
|
||||
|
||||
@@ -277,6 +277,7 @@ class api {
|
||||
$settings->tool_mobile_customlangstrings = get_config('tool_mobile', 'customlangstrings');
|
||||
$settings->tool_mobile_disabledfeatures = get_config('tool_mobile', 'disabledfeatures');
|
||||
$settings->tool_mobile_custommenuitems = get_config('tool_mobile', 'custommenuitems');
|
||||
$settings->tool_mobile_apppolicy = get_config('tool_mobile', 'apppolicy');
|
||||
}
|
||||
|
||||
return $settings;
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
<?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/>.
|
||||
/**
|
||||
* Privacy Subsystem implementation for tool_mobile.
|
||||
*
|
||||
* @package tool_mobile
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_mobile\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
use \core_privacy\local\request\writer;
|
||||
use \core_privacy\local\metadata\collection;
|
||||
use \core_privacy\local\request\contextlist;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \core_privacy\local\request\transform;
|
||||
|
||||
/**
|
||||
* Privacy provider for tool_mobile.
|
||||
*
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @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\user_preference_provider,
|
||||
\core_privacy\local\request\plugin\provider {
|
||||
/**
|
||||
* Returns meta data about this system.
|
||||
*
|
||||
* @param collection $collection The initialised item collection to add items to.
|
||||
* @return collection A listing of user data stored through this system.
|
||||
*/
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
// There is a one user preference.
|
||||
$collection->add_user_preference('tool_mobile_autologin_request_last',
|
||||
'privacy:metadata:preference:tool_mobile_autologin_request_last');
|
||||
$collection->add_subsystem_link('core_userkey', [], 'privacy:metadata:core_userkey');
|
||||
|
||||
return $collection;
|
||||
}
|
||||
/**
|
||||
* Get the list of contexts that contain user information for the specified user.
|
||||
*
|
||||
* @param int $userid The user to search.
|
||||
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
|
||||
*/
|
||||
public static function get_contexts_for_userid(int $userid) : contextlist {
|
||||
$sql = "SELECT ctx.id
|
||||
FROM {user_private_key} k
|
||||
JOIN {user} u ON k.userid = u.id
|
||||
JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel
|
||||
WHERE k.userid = :userid AND k.script = 'tool_mobile'";
|
||||
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
|
||||
$contextlist = new contextlist();
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
|
||||
return $contextlist;
|
||||
}
|
||||
/**
|
||||
* Export all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts to export information for.
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
// If the user has data, then only the CONTEXT_USER should be present so get the first context.
|
||||
$contexts = $contextlist->get_contexts();
|
||||
if (count($contexts) == 0) {
|
||||
return;
|
||||
}
|
||||
$context = reset($contexts);
|
||||
// Sanity check that context is at the user context level, then get the userid.
|
||||
if ($context->contextlevel !== CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
// Export associated userkeys.
|
||||
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile');
|
||||
}
|
||||
/**
|
||||
* Export all user preferences for the plugin.
|
||||
*
|
||||
* @param int $userid The userid of the user whose data is to be exported.
|
||||
*/
|
||||
public static function export_user_preferences(int $userid) {
|
||||
$autologinrequestlast = get_user_preferences('tool_mobile_autologin_request_last', null, $userid);
|
||||
if ($autologinrequestlast !== null) {
|
||||
$time = transform::datetime($autologinrequestlast);
|
||||
writer::export_user_preference('tool_mobile',
|
||||
'tool_mobile_autologin_request_last',
|
||||
$time,
|
||||
get_string('privacy:metadata:preference:tool_mobile_autologin_request_last', 'tool_mobile')
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Delete all use data which matches the specified deletion_criteria.
|
||||
*
|
||||
* @param context $context A user context.
|
||||
*/
|
||||
public static function delete_data_for_all_users_in_context(\context $context) {
|
||||
// Sanity check that context is at the user context level, then get the userid.
|
||||
if ($context->contextlevel !== CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
$userid = $context->instanceid;
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
}
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
*
|
||||
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
|
||||
*/
|
||||
public static function delete_data_for_user(approved_contextlist $contextlist) {
|
||||
// If the user has data, then only the user context should be present so get the first context.
|
||||
$contexts = $contextlist->get_contexts();
|
||||
if (count($contexts) == 0) {
|
||||
return;
|
||||
}
|
||||
$context = reset($contexts);
|
||||
// Sanity check that context is at the user context level, then get the userid.
|
||||
if ($context->contextlevel !== CONTEXT_USER) {
|
||||
return;
|
||||
}
|
||||
$userid = $context->instanceid;
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@
|
||||
$string['adodbdebugwarning'] = 'ADOdb debugging is enabled. It should be disabled in the external database authentication or external database enrolment plugin settings.';
|
||||
$string['androidappid'] = 'Android app\'s unique identifier';
|
||||
$string['androidappid_desc'] = 'This setting may be left as default unless you have a custom Android app.';
|
||||
$string['apppolicy'] = 'App policy URL';
|
||||
$string['apppolicy_help'] = 'The URL of a policy for app users which is listed on the About page in the app. If the field is left empty, the site policy URL will be used instead.';
|
||||
$string['autologinkeygenerationlockout'] = 'Auto-login key generation is blocked. You need to wait 6 minutes between requests.';
|
||||
$string['autologinnotallowedtoadmins'] = 'Auto-login is not allowed for site admins.';
|
||||
$string['cachedef_plugininfo'] = 'This stores the list of plugins with mobile addons';
|
||||
@@ -65,7 +67,7 @@ $string['invalidcertificatechainwarning'] = 'It seems that the certificate chain
|
||||
$string['invalidcertificateexpiredatewarning'] = 'It seems that the HTTPS certificate for the site has expired.';
|
||||
$string['invalidcertificatestartdatewarning'] = 'It seems that the HTTPS certificate for the site is not yet valid (with a start date in the future).';
|
||||
$string['invalidprivatetoken'] = 'Invalid private token. Token should not be empty or passed via GET parameter.';
|
||||
$string['invaliduserquotawarning'] = 'The user quota (userquota) is set to an invalid number. It should be set to a valid number (an integer value) in Site policies.';
|
||||
$string['invaliduserquotawarning'] = 'The user quota (userquota) is set to an invalid number. It should be set to a valid number (an integer value) in Site security settings.';
|
||||
$string['iosappid'] = 'iOS app\'s unique identifier';
|
||||
$string['iosappid_desc'] = 'This setting may be left as default unless you have a custom iOS app.';
|
||||
$string['loginintheapp'] = 'Via the app';
|
||||
@@ -92,3 +94,5 @@ $string['smartappbanners'] = 'App Banners';
|
||||
$string['typeoflogin'] = 'Type of login';
|
||||
$string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins.';
|
||||
$string['getmoodleonyourmobile'] = 'Get the mobile app';
|
||||
$string['privacy:metadata:preference:tool_mobile_autologin_request_last'] = 'The date of the last auto-login key request. Between each request 6 minutes are required.';
|
||||
$string['privacy:metadata:core_userkey'] = 'User\'s keys used to create auto-login key for the current user.';
|
||||
|
||||
@@ -43,6 +43,9 @@ if ($hassiteconfig) {
|
||||
new lang_string('configenablemobilewebservice', 'admin', $enablemobiledoclink), $default));
|
||||
}
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/apppolicy', new lang_string('apppolicy', 'tool_mobile'),
|
||||
new lang_string('apppolicy_help', 'tool_mobile'), '', PARAM_URL));
|
||||
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
|
||||
// Show only mobile settings if the mobile service is enabled.
|
||||
|
||||
@@ -173,6 +173,7 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
|
||||
array('name' => 'tool_mobile_customlangstrings', 'value' => ''),
|
||||
array('name' => 'tool_mobile_disabledfeatures', 'value' => ''),
|
||||
array('name' => 'tool_mobile_custommenuitems', 'value' => ''),
|
||||
array('name' => 'tool_mobile_apppolicy', 'value' => ''),
|
||||
);
|
||||
$this->assertCount(0, $result['warnings']);
|
||||
$this->assertEquals($expected, $result['settings']);
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<?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/>.
|
||||
/**
|
||||
* Base class for unit tests for tool_mobile.
|
||||
*
|
||||
* @package tool_mobile
|
||||
* @category test
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use \core_privacy\local\request\writer;
|
||||
use \core_privacy\local\request\transform;
|
||||
use \core_privacy\local\request\approved_contextlist;
|
||||
use \tool_mobile\privacy\provider;
|
||||
|
||||
/**
|
||||
* Unit tests for the tool_mobile implementation of the privacy API.
|
||||
*
|
||||
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_mobile_privacy_testcase extends \core_privacy\tests\provider_testcase {
|
||||
|
||||
/**
|
||||
* Basic setup for these tests.
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->resetAfterTest(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to check export_user_preferences.
|
||||
* returns user preferences data.
|
||||
*/
|
||||
public function test_export_user_preferences() {
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$expectedtime = time();
|
||||
set_user_preference('tool_mobile_autologin_request_last', time(), $user);
|
||||
provider::export_user_preferences($user->id);
|
||||
$writer = writer::with_context(\context_system::instance());
|
||||
$prefs = $writer->get_user_preferences('tool_mobile');
|
||||
$time = transform::datetime($expectedtime);
|
||||
$this->assertEquals($time, $prefs->tool_mobile_autologin_request_last->value);
|
||||
$this->assertEquals(get_string('privacy:metadata:preference:tool_mobile_autologin_request_last', 'tool_mobile'),
|
||||
$prefs->tool_mobile_autologin_request_last->description);
|
||||
}
|
||||
/**
|
||||
* Test getting the context for the user ID related to this plugin.
|
||||
*/
|
||||
public function test_get_contexts_for_userid() {
|
||||
// Create user and Mobile user keys.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$context = \context_user::instance($user->id);
|
||||
$key = get_user_key('tool_mobile', $user->id);
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$this->assertEquals($context->id, $contextlist->current()->id);
|
||||
}
|
||||
/**
|
||||
* Test that data is exported correctly for this plugin.
|
||||
*/
|
||||
public function test_export_user_data() {
|
||||
global $DB;
|
||||
// Create user and Mobile user keys.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$context = \context_user::instance($user->id);
|
||||
$keyvalue = get_user_key('tool_mobile', $user->id);
|
||||
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
|
||||
// Validate exported data.
|
||||
$this->setUser($user);
|
||||
$writer = writer::with_context($context);
|
||||
$this->assertFalse($writer->has_any_data());
|
||||
$this->export_context_data_for_user($user->id, $context, 'tool_mobile');
|
||||
$userkeydata = $writer->get_related_data([], 'userkeys');
|
||||
$this->assertCount(1, $userkeydata->keys);
|
||||
$this->assertEquals($key->script, reset($userkeydata->keys)->script);
|
||||
}
|
||||
/**
|
||||
* Test for provider::delete_data_for_all_users_in_context().
|
||||
*/
|
||||
public function test_delete_data_for_all_users_in_context() {
|
||||
global $DB;
|
||||
// Create user and Mobile user keys.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$context = \context_user::instance($user->id);
|
||||
$keyvalue = get_user_key('tool_mobile', $user->id);
|
||||
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
|
||||
// Before deletion, we should have 1 user_private_key.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(1, $count);
|
||||
// Delete data.
|
||||
provider::delete_data_for_all_users_in_context($context);
|
||||
// After deletion, the user_private_key entries should have been deleted.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(0, $count);
|
||||
}
|
||||
/**
|
||||
* Test for provider::delete_data_for_user().
|
||||
*/
|
||||
public function test_delete_data_for_user() {
|
||||
global $DB;
|
||||
// Create user and Mobile user keys.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$context = \context_user::instance($user->id);
|
||||
$keyvalue = get_user_key('tool_mobile', $user->id);
|
||||
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
|
||||
// Before deletion, we should have 1 user_private_key.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(1, $count);
|
||||
// Delete data.
|
||||
$contextlist = provider::get_contexts_for_userid($user->id);
|
||||
$approvedcontextlist = new approved_contextlist($user, 'tool_mobile', $contextlist->get_contextids());
|
||||
provider::delete_data_for_user($approvedcontextlist);
|
||||
// After deletion, the user_private_key entries should have been deleted.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(0, $count);
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,9 @@
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
$plugin->version = 2017111301; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2017110800; // Requires this Moodle version.
|
||||
$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018050800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->dependencies = array(
|
||||
'webservice_rest' => 2017110800
|
||||
'webservice_rest' => 2018050800
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user