Compare commits
289 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae82333cf2 | |||
| 0c5e1ca0d2 | |||
| 1ef6a4c080 | |||
| c2b73b54eb | |||
| 1f47c7fc7b | |||
| 4da970984c | |||
| d3b93323c4 | |||
| b68e858798 | |||
| d02dc6a763 | |||
| 12cd92baa6 | |||
| d99ea30311 | |||
| 472746d97e | |||
| d23d50025e | |||
| 43f7bb1815 | |||
| c041f919f0 | |||
| 32b1faf8b7 | |||
| d2a2855501 | |||
| f66db7a52b | |||
| a6e6765bf1 | |||
| a00ea08b58 | |||
| 1fcfd1b90e | |||
| 8324204f6d | |||
| 00282f1c56 | |||
| 5c5ee48bf6 | |||
| 040430abff | |||
| 2582943f92 | |||
| 97e8c0abce | |||
| 51894a816f | |||
| c0ac0bd256 | |||
| a5c5651a0f | |||
| 01e64f89c7 | |||
| a505a4abd7 | |||
| c587194dee | |||
| 76c9202996 | |||
| 752b63d574 | |||
| 4cd4b14df2 | |||
| a49f5519e2 | |||
| d494848985 | |||
| 950da9db66 | |||
| d3c9d9306c | |||
| ff7941f00e | |||
| a02c72d8bb | |||
| 3d1ee2568e | |||
| bb560c7012 | |||
| 0e8ebb0ead | |||
| 0375eb8488 | |||
| 1f258e679a | |||
| 13d2621e23 | |||
| 2709ae4dcc | |||
| 9aa05ccfdd | |||
| 6855ebb1eb | |||
| 3d1949af87 | |||
| 3cda2cab0e | |||
| e899bccd8e | |||
| a94a0c565b | |||
| 1171be0c0a | |||
| 12963c1460 | |||
| d5fea5ba37 | |||
| 76a361a821 | |||
| fb1740d897 | |||
| 95433e3763 | |||
| af7362c323 | |||
| 07d283e56e | |||
| f1608e1184 | |||
| 958dfb534d | |||
| aa561d76e8 | |||
| fd497585a8 | |||
| 62c1be5b19 | |||
| 258057604b | |||
| e21e6d3d5b | |||
| dbf4da68de | |||
| 20f17d1ee3 | |||
| a0d09bb67e | |||
| fc77529235 | |||
| de2c1990eb | |||
| f8191ba10f | |||
| 3ea8d9d89a | |||
| ceb8d8dc60 | |||
| 4f232817c2 | |||
| dabce49529 | |||
| 67cc6f70c1 | |||
| 33adcf9926 | |||
| 7d3536fb48 | |||
| 0d7f464d4b | |||
| 629a97d5c1 | |||
| 16f8828e9f | |||
| fbfa3a0bd2 | |||
| bb9bb21546 | |||
| b1da31a63e | |||
| c165ee4890 | |||
| 0446552496 | |||
| 7e954344d2 | |||
| efd6748031 | |||
| 493f79cc6a | |||
| 0231ce53b1 | |||
| 222c900018 | |||
| e4d181c4de | |||
| 56b96699a6 | |||
| 5bc61c9686 | |||
| 62165df6dd | |||
| 0cd43cccbf | |||
| 0b9cf6ba5e | |||
| 6ef7759473 | |||
| 6816976c3b | |||
| 6e7a73c16d | |||
| 6fc275038c | |||
| 41548fb26e | |||
| 082ef8ef3c | |||
| 371168e848 | |||
| 982839422f | |||
| cc22614602 | |||
| 094fa8ce8a | |||
| eca5403915 | |||
| 5924e71021 | |||
| b9706f8cf3 | |||
| b17a7d65a7 | |||
| 558126aee6 | |||
| 1cec2b9041 | |||
| 5279b38c8b | |||
| 286e9ef666 | |||
| 026b1ffe51 | |||
| 6eddcd1310 | |||
| 4bb3e3fccf | |||
| be23442e17 | |||
| e75470d531 | |||
| c9a28c645b | |||
| df84f822bf | |||
| 4ca488c639 | |||
| 745f6e7f61 | |||
| f5b95df245 | |||
| 12131e4806 | |||
| 2dfd3b9839 | |||
| f68fe6bc00 | |||
| ea8b23542a | |||
| 1a228ad894 | |||
| 324e9a2800 | |||
| c37329dc10 | |||
| c3dbe66477 | |||
| 3bfd32be9b | |||
| 85a42937b9 | |||
| c3db28fe90 | |||
| 2f1c8a398b | |||
| e7c034a8d8 | |||
| 5a6185300a | |||
| 9ab746af27 | |||
| a21f18e947 | |||
| 711d42778a | |||
| d2a497ee03 | |||
| 3f8dfe2ab7 | |||
| 09cbca8566 | |||
| 1c2efd6f1b | |||
| 751bdfd50d | |||
| 19c1e9845b | |||
| 64a736ee72 | |||
| 1f319dda3d | |||
| 5790e87939 | |||
| 40260a258f | |||
| 86f3c074e9 | |||
| cfac8bb5ab | |||
| 772afa0f43 | |||
| 111ec01643 | |||
| cf21130cc2 | |||
| a454fd5f57 | |||
| 300e220dbc | |||
| 5432075187 | |||
| a51fa84f50 | |||
| 052d30081e | |||
| ab883ad6b0 | |||
| 6069d11046 | |||
| 1875af2207 | |||
| 65b8d2ab7d | |||
| b39379cb98 | |||
| c840b579cb | |||
| 784c21b116 | |||
| f423dd8ccf | |||
| f5d57e74bf | |||
| e67669e638 | |||
| aa881b7717 | |||
| 19a94ef923 | |||
| 866d7a064a | |||
| 1d5b6e94ee | |||
| 014628c4c4 | |||
| 61b0e103b7 | |||
| 2a0c44b56d | |||
| ab79c7bf95 | |||
| 4415985085 | |||
| 83c83473f8 | |||
| 680b9186c7 | |||
| 3717d20a33 | |||
| e29b668d7b | |||
| 8987e225da | |||
| b96151e864 | |||
| 1c6f6505b9 | |||
| 884aa7b9f7 | |||
| 082f2f7dd8 | |||
| 9bec7f417f | |||
| b3865f159b | |||
| f03a28c520 | |||
| 68d882a927 | |||
| 41b628707c | |||
| 2d287b27e3 | |||
| b76d708315 | |||
| 1f2972764b | |||
| 2161e3294d | |||
| 898584944b | |||
| 642ad98e28 | |||
| 6892904225 | |||
| 5f7519ba19 | |||
| 2c904a6c87 | |||
| 91b38b5c30 | |||
| e60b5bd04d | |||
| d54b8bb7ee | |||
| a300c3479d | |||
| 38174e03de | |||
| 0d21b5a2dc | |||
| a36a6e4491 | |||
| 27621e4bfa | |||
| 939ba8562b | |||
| d94a35a5a4 | |||
| 1f44dfad42 | |||
| 62ba30c11f | |||
| 663c41bf76 | |||
| ded79d63d8 | |||
| d6dcfee9f5 | |||
| bb478e8ddd | |||
| be6fc5b122 | |||
| cad1e4f427 | |||
| 858cb072b1 | |||
| 5ededd52c0 | |||
| cf31c73791 | |||
| 265894ee59 | |||
| f1a57a3181 | |||
| ce48eff24e | |||
| 008b92bd0a | |||
| fe8ca8b78d | |||
| e5fe5da40c | |||
| d8d35d7c1f | |||
| 680160dca4 | |||
| 78315808b1 | |||
| b67c98ca83 | |||
| d8eb85604f | |||
| 027b757f20 | |||
| a21351f43f | |||
| 573fe725a4 | |||
| 32bfc786df | |||
| 3504a2a99f | |||
| eca3395a70 | |||
| d5a8d310e0 | |||
| af89e5a896 | |||
| 31aa968f43 | |||
| 560ddebb05 | |||
| 45654c4813 | |||
| ff3187ba50 | |||
| f5d105b4e0 | |||
| 6f2c8d1ca6 | |||
| 10c6f9c88f | |||
| 33e3839fdc | |||
| f60980615c | |||
| 153f6546bb | |||
| 6acb8efc76 | |||
| 73e51962df | |||
| e56de91383 | |||
| 68aabce476 | |||
| 8d53360c70 | |||
| 833d4aa42c | |||
| 6e043dd5ec | |||
| f1c8d8a374 | |||
| 8e31b3241a | |||
| 86b5d678bc | |||
| 1b9f20fdf9 | |||
| 5204184a9b | |||
| a2650c9a7d | |||
| 83832b98a3 | |||
| 149552615c | |||
| 33ef4d941d | |||
| 5aa514982d | |||
| 2ed6a293df | |||
| 7c84c195e9 | |||
| 5db5c6dcc3 | |||
| 8fb6a1a4f3 | |||
| 08c753034f | |||
| c54ed68179 | |||
| 485c8e1f77 | |||
| 62994208a1 | |||
| 74b5f26b3b | |||
| 6bdc80eaf5 | |||
| 6a5d9d0705 | |||
| 23c1612fe1 | |||
| 7da1a29e3b |
@@ -3,6 +3,7 @@
|
||||
*/**/build/
|
||||
node_modules/
|
||||
vendor/
|
||||
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
|
||||
admin/tool/usertours/amd/src/tour.js
|
||||
admin/tool/usertours/amd/src/popper.js
|
||||
auth/cas/CAS/
|
||||
|
||||
@@ -4,6 +4,7 @@ theme/clean/style/custom.css
|
||||
theme/more/style/custom.css
|
||||
node_modules/
|
||||
vendor/
|
||||
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
|
||||
admin/tool/usertours/amd/src/tour.js
|
||||
admin/tool/usertours/amd/src/popper.js
|
||||
auth/cas/CAS/
|
||||
|
||||
+3
-1
@@ -55,7 +55,7 @@ matrix:
|
||||
include:
|
||||
# Run grunt/npm install on highest version ('node' is an alias for the latest node.js version.)
|
||||
- php: 7.1
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='8.9'
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
|
||||
|
||||
exclude:
|
||||
# MySQL - it's just too slow.
|
||||
@@ -264,7 +264,9 @@ script:
|
||||
grunt ;
|
||||
# Add all files to the git index and then run diff --cached to see all changes.
|
||||
# This ensures that we get the status of all files, including new files.
|
||||
# We ignore npm-shrinkwrap.json to make the tasks immune to npm changes.
|
||||
git add . ;
|
||||
git reset -- npm-shrinkwrap.json ;
|
||||
git diff --cached --exit-code ;
|
||||
fi
|
||||
|
||||
|
||||
@@ -2097,6 +2097,7 @@
|
||||
<VENDOR name="oracle" version="10.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.0.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_73" message="unsupportedphpversion73" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -2273,6 +2274,7 @@
|
||||
<VENDOR name="oracle" version="10.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.0.0" level="required">
|
||||
<RESTRICT function="restrict_php_version_73" message="unsupportedphpversion73" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -2435,4 +2437,185 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.6" requires="3.1">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="5.5.31" />
|
||||
<VENDOR name="mysql" version="5.6" />
|
||||
<VENDOR name="postgres" version="9.4" />
|
||||
<VENDOR name="mssql" version="10.0" />
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.0.0" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="mbstringrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlrpc" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="xmlrpcrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/str","core/ajax","core/notification","core/modal_factory","core/modal_events","core/fragment"],function(a,b,c,d,e,f,g){var h={CATEGORY_LINK:'[data-add-element="category"]'},i=function(a){this.contextId=a;var c=[{key:"addcategory",component:"tool_dataprivacy"},{key:"save",component:"admin"}];this.strings=b.get_strings(c),this.registerEventListeners()};return i.prototype.contextId=0,i.prototype.strings=0,i.prototype.registerEventListeners=function(){var b=a(h.CATEGORY_LINK);b.on("click",function(){return this.strings.then(function(a){e.create({type:e.types.SAVE_CANCEL,title:a[0],body:""},b).done(function(b){this.setupFormModal(b,a[1])}.bind(this))}.bind(this)).fail(d.exception)}.bind(this))},i.prototype.getBody=function(a){var b=null;return"undefined"!=typeof a&&(b={jsonformdata:JSON.stringify(a)}),g.loadFragment("tool_dataprivacy","addcategory_form",this.contextId,b)},i.prototype.setupFormModal=function(a,b){a.setLarge(),a.setSaveButtonText(b),a.getRoot().on(f.hidden,this.destroy.bind(this)),a.setBody(this.getBody()),a.getRoot().on(f.save,this.submitForm.bind(this)),a.getRoot().on("submit","form",this.submitFormAjax.bind(this)),this.modal=a,a.show()},i.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},i.prototype.submitFormAjax=function(a){a.preventDefault();var b=this.modal.getRoot().find("form").serialize();c.call([{methodname:"tool_dataprivacy_create_category_form",args:{jsonformdata:JSON.stringify(b)},done:function(a){a.validationerrors?this.modal.setBody(this.getBody(b)):this.close()}.bind(this),fail:d.exception}])},i.prototype.close=function(){this.destroy(),document.location.reload()},i.prototype.destroy=function(){Y.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()}),this.modal.destroy()},i.prototype.removeListeners=function(){a(h.CATEGORY_LINK).off("click")},{getInstance:function(a){return new i(a)}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/str","core/ajax","core/notification","core/modal_factory","core/modal_events","core/fragment"],function(a,b,c,d,e,f,g){var h={PURPOSE_LINK:'[data-add-element="purpose"]'},i=function(a){this.contextId=a;var c=[{key:"addpurpose",component:"tool_dataprivacy"},{key:"save",component:"admin"}];this.strings=b.get_strings(c),this.registerEventListeners()};return i.prototype.contextId=0,i.prototype.strings=0,i.prototype.registerEventListeners=function(){var b=a(h.PURPOSE_LINK);b.on("click",function(){return this.strings.then(function(a){e.create({type:e.types.SAVE_CANCEL,title:a[0],body:""},b).done(function(b){this.setupFormModal(b,a[1])}.bind(this))}.bind(this)).fail(d.exception)}.bind(this))},i.prototype.getBody=function(a){var b=null;return"undefined"!=typeof a&&(b={jsonformdata:JSON.stringify(a)}),g.loadFragment("tool_dataprivacy","addpurpose_form",this.contextId,b)},i.prototype.setupFormModal=function(a,b){a.setLarge(),a.setSaveButtonText(b),a.getRoot().on(f.hidden,this.destroy.bind(this)),a.setBody(this.getBody()),a.getRoot().on(f.save,this.submitForm.bind(this)),a.getRoot().on("submit","form",this.submitFormAjax.bind(this)),this.modal=a,a.show()},i.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},i.prototype.submitFormAjax=function(a){a.preventDefault();var b=this.modal.getRoot().find("form").serialize();c.call([{methodname:"tool_dataprivacy_create_purpose_form",args:{jsonformdata:JSON.stringify(b)},done:function(a){a.validationerrors?this.modal.setBody(this.getBody(b)):this.close()}.bind(this),fail:d.exception}])},i.prototype.close=function(){this.destroy(),document.location.reload()},i.prototype.destroy=function(){Y.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()}),this.modal.destroy()},i.prototype.removeListeners=function(){a(h.PURPOSE_LINK).off("click")},{getInstance:function(a){return new i(a)}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletecategory"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletecategory",component:"tool_dataprivacy"},{key:"deletecategorytext",component:"tool_dataprivacy",param:i},{key:"delete"}];d.get_strings(j).then(function(d){var g=d[0],i=d[1],j=d[2];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(j),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_category",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-categoryid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){function g(a){var g=[{key:"confirm",component:"moodle"},{key:"confirmcontextdeletion",component:"tool_dataprivacy"}],h="tool_dataprivacy_confirm_contexts_for_deletion",i="";d.get_strings(g).then(function(a){i=a[0];var b=a[1];return e.create({title:i,body:b,type:e.types.SAVE_CANCEL})}).then(function(d){return d.setSaveButtonText(i),d.getRoot().on(f.save,function(){var d={ids:a},e={methodname:h,args:d};b.call([e])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d}).done(function(a){a.show()}).fail(c.exception)}var h={MARK_FOR_DELETION:'[data-action="markfordeletion"]',SELECT_ALL:'[data-action="selectall"]'},i={SELECTCONTEXT:".selectcontext"},j=function(){this.registerEvents()};return j.prototype.registerEvents=function(){a(h.MARK_FOR_DELETION).click(function(b){b.preventDefault();var c=[];a(i.SELECTCONTEXT).each(function(){var b=a(this);b.is(":checked")&&c.push(b.val())}),g(c)}),a(h.SELECT_ALL).change(function(b){b.preventDefault();var c=a(this);c.is(":checked")?a(i.SELECTCONTEXT).attr("checked","checked"):a(i.SELECTCONTEXT).removeAttr("checked")})},j});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/str","core/ajax","core/notification","core/templates","core/modal_factory","core/modal_events","core/fragment","tool_dataprivacy/add_purpose","tool_dataprivacy/add_category"],function(a,b,c,d,e,f,g,h,i,j){var k={TREE_NODES:"[data-context-tree-node=1]",FORM_CONTAINER:"#context-form-container"},l=function(a,b,c){this.systemContextId=a,this.currentContextLevel=b,this.currentContextId=c,this.init()};return l.prototype.systemContextId=0,l.prototype.currentContextLevel=0,l.prototype.currentContextId=0,l.prototype.addpurpose=null,l.prototype.addcategory=null,l.prototype.init=function(){this.addpurpose=i.getInstance(this.systemContextId),this.addcategory=j.getInstance(this.systemContextId);var a=[{key:"changessaved",component:"moodle"},{key:"contextpurposecategorysaved",component:"tool_dataprivacy"},{key:"noblockstoload",component:"tool_dataprivacy"},{key:"noactivitiestoload",component:"tool_dataprivacy"},{key:"nocoursestoload",component:"tool_dataprivacy"}];this.strings=b.get_strings(a),this.registerEventListeners(),this.currentContextId?this.loadForm("context_form",[this.currentContextId],this.submitContextFormAjax.bind(this)):this.loadForm("contextlevel_form",[this.currentContextLevel],this.submitContextLevelFormAjax.bind(this))},l.prototype.registerEventListeners=function(){a(k.TREE_NODES).on("click",function(b){b.preventDefault();var c=a(b.currentTarget);a(k.TREE_NODES).removeClass("active"),c.addClass("active");var d=c.data("contextlevel"),e=c.data("contextid");if(d)window.history.pushState({},null,"?contextlevel="+d),this.addpurpose.removeListeners(),this.addcategory.removeListeners(),this.currentContextLevel=d,this.loadForm("contextlevel_form",[this.currentContextLevel],this.submitContextLevelFormAjax.bind(this));else if(e)window.history.pushState({},null,"?contextid="+e),this.addpurpose.removeListeners(),this.addcategory.removeListeners(),this.currentContextId=e,this.loadForm("context_form",[this.currentContextId],this.submitContextFormAjax.bind(this));else{var f=c.data("expandcontextid"),g=c.data("expandelement"),h=c.data("expanded");g&&(h?this.collapse(c):!c.data("loaded")&&f&&g?(c.find("> i").removeClass("fa-plus"),c.find("> i").addClass("fa-circle-o-notch fa-spin"),this.loadExtra(c,f,g)):this.expand(c))}}.bind(this))},l.prototype.removeListeners=function(){a(k.TREE_NODES).off("click")},l.prototype.loadForm=function(b,c,f){this.clearForm();var g=h.loadFragment("tool_dataprivacy",b,this.systemContextId,c);g.done(function(b,c){a(k.FORM_CONTAINER).html(b),e.runTemplateJS(c),this.addpurpose.registerEventListeners(),this.addcategory.registerEventListeners(),a(k.FORM_CONTAINER).on("submit","form",f)}.bind(this)).fail(d.exception)},l.prototype.clearForm=function(){Y.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()}),a(k.FORM_CONTAINER).off("submit","form")},l.prototype.submitForm=function(b){b.preventDefault(),a(k.FORM_CONTAINER).find("form").submit()},l.prototype.submitContextLevelFormAjax=function(a){this.submitFormAjax(a,"tool_dataprivacy_set_contextlevel_form")},l.prototype.submitContextFormAjax=function(a){this.submitFormAjax(a,"tool_dataprivacy_set_context_form")},l.prototype.submitFormAjax=function(b,e){b.preventDefault();var f=a(k.FORM_CONTAINER).find("form").serialize();return this.strings.then(function(a){c.call([{methodname:e,args:{jsonformdata:JSON.stringify(f)},done:function(){d.alert(a[0],a[1])},fail:d.exception}])})["catch"](d.exception)},l.prototype.loadExtra=function(a,b,f){c.call([{methodname:"tool_dataprivacy_tree_extra_branches",args:{contextid:b,element:f},done:function(b){return 0==b.branches.length?void this.noElements(a,f):void e.render("tool_dataprivacy/context_tree_branches",b).then(function(b){a.after(b),this.removeListeners(),this.registerEventListeners(),this.expand(a),a.data("loaded",1)}.bind(this)).fail(d.exception)}.bind(this),fail:d.exception}])},l.prototype.noElements=function(a,b){a.data("expandcontextid",""),a.data("expandelement",""),this.strings.then(function(c){var d=2;"module"==b?d=3:"course"==b&&(d=4),a.text(c[d])}).fail(d.exception)},l.prototype.collapse=function(a){a.data("expanded",0),a.siblings("nav").addClass("hidden"),a.find("> i").removeClass("fa-minus"),a.find("> i").addClass("fa-plus")},l.prototype.expand=function(a){a.data("expanded",1),a.siblings("nav").removeClass("hidden"),a.find("> i").removeClass("fa-plus"),a.find("> i").removeClass("fa-circle-o-notch fa-spin"),a.find("> i").addClass("fa-minus")},{init:function(a,b,c){return new l(a,b,c)}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","tool_dataprivacy/events"],function(a,b,c,d,e,f){var g=!1,h={APPROVE_BUTTON:'[data-action="approve"]',DENY_BUTTON:'[data-action="deny"]',COMPLETE_BUTTON:'[data-action="complete"]'},i=function(a){d.call(this,a)};return i.TYPE="tool_dataprivacy-data_request",i.TYPE_ENQUIRY="tool_dataprivacy-complete_request",i.prototype=Object.create(d.prototype),i.prototype.constructor=i,i.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this),this.getModal().on(c.events.activate,h.APPROVE_BUTTON,function(b,c){var d=a.Event(f.approve);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.DENY_BUTTON,function(b,c){var d=a.Event(f.deny);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.COMPLETE_BUTTON,function(b,c){var d=a.Event(f.complete);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this))},g||(e.register(i.TYPE,i,"tool_dataprivacy/data_request_modal"),e.register(i.TYPE_ENQUIRY,i,"tool_dataprivacy/mark_request_complete_modal"),g=!0),i});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates"],function(a,b,c,d,e,f,g){function h(b,d,h,j,k,l,m,n){null!==h&&l.forEach(function(a){a.id===h&&(a.selected=!0)}),null!==j&&m.forEach(function(a){a.id===j&&(a.selected=!0)});var o={contextlevel:d,categoryoptions:l,purposeoptions:m};null!==n&&n.length&&(null===k?o.newactivitydefaults=!0:n.forEach(function(a){k===a.name&&(a.selected=!0)}),o.modemodule=!0,o.activityoptions=n),e.create({title:b,body:g.render("tool_dataprivacy/category_purpose_form",o),type:e.types.SAVE_CANCEL,large:!0}).then(function(b){return b.getRoot().on(f.save,function(){var b=a("#activity"),c="undefined"!=typeof b?b.val():null,d=a("#override"),e="undefined"!=typeof d&&d.is(":checked");i(a("#contextlevel").val(),a("#category").val(),a("#purpose").val(),c,e)}),b.getRoot().on(f.hidden,function(){b.destroy()}),b.show(),b})["catch"](c.exception)}function i(a,c,d,e,f){var g={methodname:"tool_dataprivacy_set_context_defaults",args:{contextlevel:a,category:c,purpose:d,override:f,activity:e}};b.call([g])[0].done(function(a){a.result&&window.location.reload()})}var j={EDIT_LEVEL_DEFAULTS:'[data-action="edit-level-defaults"]',NEW_ACTIVITY_DEFAULTS:'[data-action="new-activity-defaults"]',EDIT_ACTIVITY_DEFAULTS:'[data-action="edit-activity-defaults"]',DELETE_ACTIVITY_DEFAULTS:'[data-action="delete-activity-defaults"]'},k=-1,l=function(){this.registerEvents()};return l.prototype.registerEvents=function(){a(j.EDIT_LEVEL_DEFAULTS).click(function(e){e.preventDefault();var f=a(this),g=f.data("contextlevel"),i=f.data("category"),j=f.data("purpose"),k=[{methodname:"tool_dataprivacy_get_category_options",args:{}},{methodname:"tool_dataprivacy_get_purpose_options",args:{}}],l=b.call(k),m=d.get_string("editdefaults","tool_dataprivacy",a("#defaults-header").text());a.when(l[0],l[1],m).then(function(a,b,c){var d=a.options,e=b.options;return h(c,g,i,j,null,d,e,null),!0})["catch"](c.exception)}),a(j.NEW_ACTIVITY_DEFAULTS).click(function(e){e.preventDefault();var f=a(this),g=f.data("contextlevel"),i=[{methodname:"tool_dataprivacy_get_category_options",args:{}},{methodname:"tool_dataprivacy_get_purpose_options",args:{}},{methodname:"tool_dataprivacy_get_activity_options",args:{nodefaults:!0}}],j=b.call(i),k=d.get_string("addnewdefaults","tool_dataprivacy");a.when(j[0],j[1],j[2],k).then(function(a,b,c,d){var e=a.options,f=b.options,i=c.options;return h(d,g,null,null,null,e,f,i),!0})["catch"](c.exception)}),a(j.EDIT_ACTIVITY_DEFAULTS).click(function(e){e.preventDefault();var f=a(this),g=f.data("contextlevel"),i=f.data("category"),j=f.data("purpose"),k=f.data("activityname"),l=[{methodname:"tool_dataprivacy_get_category_options",args:{}},{methodname:"tool_dataprivacy_get_purpose_options",args:{}},{methodname:"tool_dataprivacy_get_activity_options",args:{}}],m=b.call(l),n=d.get_string("editmoduledefaults","tool_dataprivacy");a.when(m[0],m[1],m[2],n).then(function(a,b,c,d){var e=a.options,f=b.options,l=c.options;return h(d,g,i,j,k,e,f,l),!0})["catch"](c.exception)}),a(j.DELETE_ACTIVITY_DEFAULTS).click(function(b){b.preventDefault();var h=a(this),j=h.data("contextlevel"),l=h.data("activityname"),m=h.data("activitydisplayname"),n=k,o=k;e.create({title:d.get_string("deletedefaults","tool_dataprivacy",m),body:g.render("tool_dataprivacy/delete_activity_defaults",{activityname:m}),type:e.types.SAVE_CANCEL,large:!0}).then(function(a){return a.setSaveButtonText(d.get_string("delete")),a.getRoot().on(f.save,function(){i(j,n,o,l,!1)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show(),!0})["catch"](c.exception)})},{init:function(){return new l}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery"],function(a){var b={PURPOSE_SELECT:"#id_purposeid",RETENTION_FIELD_BOOST:"#id_error_retention_current",RETENTION_FIELD_CLEAN:"#fitem_id_retention_current [data-fieldtype=static]"},c=function(a){this.purposeRetentionPeriods=a,this.registerEventListeners()},d=function(){a(b.PURPOSE_SELECT).off("change")};return c.prototype.purposeRetentionPeriods=[],c.prototype.registerEventListeners=function(){a(b.PURPOSE_SELECT).on("change",function(c){var d=a(c.currentTarget).val(),e=this.purposeRetentionPeriods[d],f=a(b.RETENTION_FIELD_CLEAN);if(f.length>0)f.text(e);else{var g=a(b.RETENTION_FIELD_BOOST),h=g.siblings();h.length>0&&h.text(e)}}.bind(this))},{init:function(a){return d(),new c(a)}}});
|
||||
@@ -0,0 +1 @@
|
||||
define([],function(){return{approve:"tool_dataprivacy-data_request:approve",bulkApprove:"tool_dataprivacy-data_request:bulk_approve",deny:"tool_dataprivacy-data_request:deny",bulkDeny:"tool_dataprivacy-data_request:bulk_deny",complete:"tool_dataprivacy-data_request:complete"}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/url","core/str"],function(a,b,c){var d=a('<img alt="" src="'+b.imageUrl("t/expanded")+'"/>'),e=a('<img alt="" src="'+b.imageUrl("t/collapsed")+'"/>');return{expandCollapse:function(a,b){a.hasClass("hide")?(a.removeClass("hide"),a.addClass("visible"),a.attr("aria-expanded",!0),b.find(":header i.fa").removeClass("fa-plus-square"),b.find(":header i.fa").addClass("fa-minus-square"),b.find(":header img.icon").attr("src",d.attr("src"))):(a.removeClass("visible"),a.addClass("hide"),a.attr("aria-expanded",!1),b.find(":header i.fa").removeClass("fa-minus-square"),b.find(":header i.fa").addClass("fa-plus-square"),b.find(":header img.icon").attr("src",e.attr("src")))},expandCollapseAll:function(b){var f="visible"==b?"hide":"visible",g="visible"==b,h="visible"==b?"fa-plus-square":"fa-minus-square",i="visible"==b?"fa-minus-square":"fa-plus-square",j="visible"==b?d.attr("src"):e.attr("src");a("."+f).each(function(){a(this).removeClass(f),a(this).addClass(b),a(this).attr("aria-expanded",g)}),a(".tool_dataprivacy-expand-all").data("visibilityState",f),c.get_string(f,"tool_dataprivacy").then(function(b){a(".tool_dataprivacy-expand-all").html(b)})["catch"](Notification.exception),a(":header i.fa").each(function(){a(this).removeClass(h),a(this).addClass(i)}),a(":header img.icon").each(function(){a(this).attr("src",j)})}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/ajax","core/templates"],function(a,b,c){return{processResults:function(b,c){var d=[];return a.each(c,function(a,b){d.push({value:b.id,label:b._label})}),d},transport:function(d,e,f,g){var h;h=b.call([{methodname:"tool_dataprivacy_get_users",args:{query:e}}]),h[0].then(function(b){var d=[],e=0;return a.each(b,function(a,b){d.push(c.render("tool_dataprivacy/form-user-selector-suggestion",b))}),a.when.apply(a.when,d).then(function(){var c=arguments;a.each(b,function(a,b){b._label=c[e],e++}),f(b)})}).fail(g)}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates"],function(a,b,c,d,e,f,g){function h(a){var e={methodname:"tool_dataprivacy_contact_dpo",args:{message:a}},f="success";b.call([e])[0].then(function(a){return a.result?d.get_string("requestsubmitted","tool_dataprivacy"):(f="error",a.warnings.join("<br>"))}).done(function(a){c.addNotification({message:a,type:f})}).fail(c.exception)}var i={CANCEL_REQUEST:'[data-action="cancel"]',CONTACT_DPO:'[data-action="contactdpo"]'},j=function(){this.registerEvents()};return j.prototype.registerEvents=function(){a(i.CANCEL_REQUEST).click(function(g){g.preventDefault();var h=a(this).data("requestid"),i=[{key:"cancelrequest",component:"tool_dataprivacy"},{key:"cancelrequestconfirmation",component:"tool_dataprivacy"}];d.get_strings(i).then(function(a){var d=a[0],g=a[1];return e.create({title:d,body:g,type:e.types.SAVE_CANCEL}).then(function(a){return a.setSaveButtonText(d),a.getRoot().on(f.save,function(){var a={requestid:h},d={methodname:"tool_dataprivacy_cancel_data_request",args:a};b.call([d])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a})}).done(function(a){a.show()}).fail(c.exception)}),a(i.CONTACT_DPO).click(function(b){b.preventDefault();var i=a(this).data("replytoemail"),j=[{key:"contactdataprotectionofficer",component:"tool_dataprivacy"},{key:"send",component:"tool_dataprivacy"}],k="";d.get_strings(j).then(function(a){var b=a[0];k=a[1];var c={replytoemail:i};return e.create({title:b,body:g.render("tool_dataprivacy/contact_dpo",c),type:e.types.SAVE_CANCEL,large:!0})}).done(function(b){b.setSaveButtonText(k),b.getRoot().on(f.save,function(b){var c=a("#message").val().trim();0===c.length?(b.preventDefault(),a('[data-region="messageinput"]').addClass("has-danger notifyproblem"),a("#id_error_message").removeAttr("hidden")):h(c)}),b.getRoot().on(f.hidden,function(){b.destroy()}),b.show()}).fail(c.exception)})},{init:function(){return new j}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f){var g={DELETE:'[data-action="deletepurpose"]'},h=function(){this.registerEvents()};return h.prototype.registerEvents=function(){a(g.DELETE).click(function(g){g.preventDefault();var h=a(this).data("id"),i=a(this).data("name"),j=[{key:"deletepurpose",component:"tool_dataprivacy"},{key:"deletepurposetext",component:"tool_dataprivacy",param:i},{key:"delete"}];d.get_strings(j).then(function(d){var g=d[0],i=d[1],j=d[2];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(j),d.getRoot().on(f.save,function(){var d={methodname:"tool_dataprivacy_delete_purpose",args:{id:h}};b.call([d])[0].done(function(b){b.result?a('tr[data-purposeid="'+h+'"]').remove():c.addNotification({message:b.warnings[0].message,type:"error"})}).fail(c.exception)}),d.getRoot().on(f.hidden,function(){d.destroy()}),d})}).done(function(a){a.show()}).fail(c.exception)})},{init:function(){return new h}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/form-autocomplete","core/str","core/notification"],function(a,b,c,d){var e={REQUEST_FILTERS:"#request-filters"},f=function(){var f=[{key:"filter",component:"moodle"},{key:"nofiltersapplied",component:"tool_dataprivacy"}];c.get_strings(f).then(function(a){var c=a[0],d=a[1];return b.enhance(e.REQUEST_FILTERS,!1,"",c,!1,!0,d,!0)}).fail(d.exception);var g=a(e.REQUEST_FILTERS).val();a(e.REQUEST_FILTERS).on("change",function(){var b=a(this).val();g.join(",")!==b.join(",")&&(0===b.length&&a("#filters-cleared").val(1),a(this.form).submit())})};return{init:function(){f()}}});
|
||||
@@ -0,0 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","tool_dataprivacy/data_request_modal","tool_dataprivacy/events"],function(a,b,c,d,e,f,g,h,i){function j(a){return{wsfunction:"tool_dataprivacy_approve_data_request",wsparams:{requestid:a}}}function k(a){return{wsfunction:"tool_dataprivacy_bulk_approve_data_requests",wsparams:{requestids:a}}}function l(a){return{wsfunction:"tool_dataprivacy_deny_data_request",wsparams:{requestid:a}}}function m(a){return{wsfunction:"tool_dataprivacy_bulk_deny_data_requests",wsparams:{requestids:a}}}function n(a){return{wsfunction:"tool_dataprivacy_mark_complete",wsparams:{requestid:a}}}function o(a,b){var g=[];switch(a){case i.approve:g=[{key:"approverequest",component:"tool_dataprivacy"},{key:"confirmapproval",component:"tool_dataprivacy"}];break;case i.bulkApprove:g=[{key:"bulkapproverequests",component:"tool_dataprivacy"},{key:"confirmbulkapproval",component:"tool_dataprivacy"}];break;case i.deny:g=[{key:"denyrequest",component:"tool_dataprivacy"},{key:"confirmdenial",component:"tool_dataprivacy"}];break;case i.bulkDeny:g=[{key:"bulkdenyrequests",component:"tool_dataprivacy"},{key:"confirmbulkdenial",component:"tool_dataprivacy"}];break;case i.complete:g=[{key:"markcomplete",component:"tool_dataprivacy"},{key:"confirmcompletion",component:"tool_dataprivacy"}]}var h="";d.get_strings(g).then(function(a){h=a[0];var b=a[1];return e.create({title:h,body:b,type:e.types.SAVE_CANCEL})}).then(function(a){a.setSaveButtonText(h),a.getRoot().on(f.save,function(){p(b.wsfunction,b.wsparams)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show()})["catch"](c.exception)}function p(a,d){var e={methodname:a,args:d};b.call([e])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}var q={APPROVE_REQUEST:'[data-action="approve"]',DENY_REQUEST:'[data-action="deny"]',VIEW_REQUEST:'[data-action="view"]',MARK_COMPLETE:'[data-action="complete"]',CHANGE_BULK_ACTION:'[id="bulk-action"]',CONFIRM_BULK_ACTION:'[id="confirm-bulk-action"]',SELECT_ALL:'[data-action="selectall"]'},r={APPROVE:1,DENY:2},s={SELECT_REQUEST:".selectrequests"},t=function(){this.registerEvents()};return t.prototype.registerEvents=function(){a(q.VIEW_REQUEST).click(function(d){d.preventDefault();var k=a(this).data("requestid"),m={requestid:k},n={methodname:"tool_dataprivacy_get_data_request",args:m},q=b.call([n]);a.when(q[0]).then(function(a){return a.result?a.result:(c.addNotification({message:a.warnings[0].message,type:"error"}),!1)}).then(function(a){var b=e.types.DEFAULT;a.approvedeny?b=h.TYPE:a.canmarkcomplete&&(b=h.TYPE_ENQUIRY);var c=g.render("tool_dataprivacy/request_details",a);return e.create({title:a.typename,body:c,type:b,large:!0})}).then(function(a){a.getRoot().on(i.approve,function(){o(i.approve,j(k))}),a.getRoot().on(i.deny,function(){o(i.deny,l(k))}),a.getRoot().on(i.complete,function(){var a={requestid:k};p("tool_dataprivacy_mark_complete",a)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show()})["catch"](c.exception)}),a(q.APPROVE_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");o(i.approve,j(c))}),a(q.DENY_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");o(i.deny,l(c))}),a(q.MARK_COMPLETE).click(function(b){b.preventDefault();var c=a(this).data("requestid");o(i.complete,n(c))}),a(q.CONFIRM_BULK_ACTION).click(function(){var b=[],e="",f={},g=[{key:"selectbulkaction",component:"tool_dataprivacy"},{key:"selectdatarequests",component:"tool_dataprivacy"},{key:"ok"}],h=parseInt(a("#bulk-action").val());if(h!=r.APPROVE&&h!=r.DENY)return void d.get_strings(g).done(function(a){c.alert("",a[0],a[2])}).fail(c.exception);if(a(".selectrequests:checked").each(function(){b.push(a(this).val())}),b.length<1)return void d.get_strings(g).done(function(a){c.alert("",a[1],a[2])}).fail(c.exception);switch(h){case r.APPROVE:e=i.bulkApprove,f=k(b);break;case r.DENY:e=i.bulkDeny,f=m(b)}o(e,f)}),a(q.SELECT_ALL).change(function(b){b.preventDefault();var c=a(this).is(":checked");a(s.SELECT_REQUEST).prop("checked",c)})},t});
|
||||
@@ -0,0 +1,172 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Module to add categories.
|
||||
*
|
||||
* @module tool_dataprivacy/add_category
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/str', 'core/ajax', 'core/notification', 'core/modal_factory', 'core/modal_events', 'core/fragment'],
|
||||
function($, Str, Ajax, Notification, ModalFactory, ModalEvents, Fragment) {
|
||||
|
||||
var SELECTORS = {
|
||||
CATEGORY_LINK: '[data-add-element="category"]',
|
||||
};
|
||||
|
||||
var AddCategory = function(contextId) {
|
||||
this.contextId = contextId;
|
||||
|
||||
var stringKeys = [
|
||||
{
|
||||
key: 'addcategory',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'save',
|
||||
component: 'admin'
|
||||
}
|
||||
];
|
||||
this.strings = Str.get_strings(stringKeys);
|
||||
|
||||
this.registerEventListeners();
|
||||
};
|
||||
|
||||
/**
|
||||
* @var {int} contextId
|
||||
* @private
|
||||
*/
|
||||
AddCategory.prototype.contextId = 0;
|
||||
|
||||
/**
|
||||
* @var {Promise}
|
||||
* @private
|
||||
*/
|
||||
AddCategory.prototype.strings = 0;
|
||||
|
||||
AddCategory.prototype.registerEventListeners = function() {
|
||||
|
||||
var trigger = $(SELECTORS.CATEGORY_LINK);
|
||||
trigger.on('click', function() {
|
||||
return this.strings.then(function(strings) {
|
||||
ModalFactory.create({
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
title: strings[0],
|
||||
body: '',
|
||||
}, trigger).done(function(modal) {
|
||||
this.setupFormModal(modal, strings[1]);
|
||||
}.bind(this));
|
||||
}.bind(this))
|
||||
.fail(Notification.exception);
|
||||
}.bind(this));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @method getBody
|
||||
* @param {Object} formdata
|
||||
* @private
|
||||
* @return {Promise}
|
||||
*/
|
||||
AddCategory.prototype.getBody = function(formdata) {
|
||||
|
||||
var params = null;
|
||||
if (typeof formdata !== "undefined") {
|
||||
params = {jsonformdata: JSON.stringify(formdata)};
|
||||
}
|
||||
// Get the content of the modal.
|
||||
return Fragment.loadFragment('tool_dataprivacy', 'addcategory_form', this.contextId, params);
|
||||
};
|
||||
|
||||
AddCategory.prototype.setupFormModal = function(modal, saveText) {
|
||||
modal.setLarge();
|
||||
|
||||
modal.setSaveButtonText(saveText);
|
||||
|
||||
// We want to reset the form every time it is opened.
|
||||
modal.getRoot().on(ModalEvents.hidden, this.destroy.bind(this));
|
||||
|
||||
modal.setBody(this.getBody());
|
||||
|
||||
// We catch the modal save event, and use it to submit the form inside the modal.
|
||||
// Triggering a form submission will give JS validation scripts a chance to check for errors.
|
||||
modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));
|
||||
// We also catch the form submit event and use it to submit the form with ajax.
|
||||
modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
|
||||
|
||||
this.modal = modal;
|
||||
|
||||
modal.show();
|
||||
};
|
||||
|
||||
/**
|
||||
* This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
|
||||
*
|
||||
* @method submitForm
|
||||
* @param {Event} e Form submission event.
|
||||
* @private
|
||||
*/
|
||||
AddCategory.prototype.submitForm = function(e) {
|
||||
e.preventDefault();
|
||||
this.modal.getRoot().find('form').submit();
|
||||
};
|
||||
|
||||
AddCategory.prototype.submitFormAjax = function(e) {
|
||||
// We don't want to do a real form submission.
|
||||
e.preventDefault();
|
||||
|
||||
// Convert all the form elements values to a serialised string.
|
||||
var formData = this.modal.getRoot().find('form').serialize();
|
||||
|
||||
Ajax.call([{
|
||||
methodname: 'tool_dataprivacy_create_category_form',
|
||||
args: {jsonformdata: JSON.stringify(formData)},
|
||||
done: function(data) {
|
||||
if (data.validationerrors) {
|
||||
this.modal.setBody(this.getBody(formData));
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
}.bind(this),
|
||||
fail: Notification.exception
|
||||
}]);
|
||||
};
|
||||
|
||||
AddCategory.prototype.close = function() {
|
||||
this.destroy();
|
||||
document.location.reload();
|
||||
};
|
||||
|
||||
AddCategory.prototype.destroy = function() {
|
||||
Y.use('moodle-core-formchangechecker', function() {
|
||||
M.core_formchangechecker.reset_form_dirty_state();
|
||||
});
|
||||
this.modal.destroy();
|
||||
};
|
||||
|
||||
AddCategory.prototype.removeListeners = function() {
|
||||
$(SELECTORS.CATEGORY_LINK).off('click');
|
||||
};
|
||||
|
||||
return /** @alias module:tool_dataprivacy/add_category */ {
|
||||
getInstance: function(contextId) {
|
||||
return new AddCategory(contextId);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Module to add purposes.
|
||||
*
|
||||
* @module tool_dataprivacy/add_purpose
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/str', 'core/ajax', 'core/notification', 'core/modal_factory', 'core/modal_events', 'core/fragment'],
|
||||
function($, Str, Ajax, Notification, ModalFactory, ModalEvents, Fragment) {
|
||||
|
||||
var SELECTORS = {
|
||||
PURPOSE_LINK: '[data-add-element="purpose"]',
|
||||
};
|
||||
|
||||
var AddPurpose = function(contextId) {
|
||||
this.contextId = contextId;
|
||||
|
||||
var stringKeys = [
|
||||
{
|
||||
key: 'addpurpose',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'save',
|
||||
component: 'admin'
|
||||
}
|
||||
];
|
||||
this.strings = Str.get_strings(stringKeys);
|
||||
|
||||
this.registerEventListeners();
|
||||
};
|
||||
|
||||
/**
|
||||
* @var {int} contextId
|
||||
* @private
|
||||
*/
|
||||
AddPurpose.prototype.contextId = 0;
|
||||
|
||||
/**
|
||||
* @var {Promise}
|
||||
* @private
|
||||
*/
|
||||
AddPurpose.prototype.strings = 0;
|
||||
|
||||
AddPurpose.prototype.registerEventListeners = function() {
|
||||
|
||||
var trigger = $(SELECTORS.PURPOSE_LINK);
|
||||
trigger.on('click', function() {
|
||||
return this.strings.then(function(strings) {
|
||||
ModalFactory.create({
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
title: strings[0],
|
||||
body: '',
|
||||
}, trigger).done(function(modal) {
|
||||
this.setupFormModal(modal, strings[1]);
|
||||
}.bind(this));
|
||||
}.bind(this))
|
||||
.fail(Notification.exception);
|
||||
}.bind(this));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @method getBody
|
||||
* @param {Object} formdata
|
||||
* @private
|
||||
* @return {Promise}
|
||||
*/
|
||||
AddPurpose.prototype.getBody = function(formdata) {
|
||||
|
||||
var params = null;
|
||||
if (typeof formdata !== "undefined") {
|
||||
params = {jsonformdata: JSON.stringify(formdata)};
|
||||
}
|
||||
// Get the content of the modal.
|
||||
return Fragment.loadFragment('tool_dataprivacy', 'addpurpose_form', this.contextId, params);
|
||||
};
|
||||
|
||||
AddPurpose.prototype.setupFormModal = function(modal, saveText) {
|
||||
modal.setLarge();
|
||||
|
||||
modal.setSaveButtonText(saveText);
|
||||
|
||||
// We want to reset the form every time it is opened.
|
||||
modal.getRoot().on(ModalEvents.hidden, this.destroy.bind(this));
|
||||
|
||||
modal.setBody(this.getBody());
|
||||
|
||||
// We catch the modal save event, and use it to submit the form inside the modal.
|
||||
// Triggering a form submission will give JS validation scripts a chance to check for errors.
|
||||
modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));
|
||||
// We also catch the form submit event and use it to submit the form with ajax.
|
||||
modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
|
||||
|
||||
this.modal = modal;
|
||||
|
||||
modal.show();
|
||||
};
|
||||
|
||||
/**
|
||||
* This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
|
||||
*
|
||||
* @method submitForm
|
||||
* @param {Event} e Form submission event.
|
||||
* @private
|
||||
*/
|
||||
AddPurpose.prototype.submitForm = function(e) {
|
||||
e.preventDefault();
|
||||
this.modal.getRoot().find('form').submit();
|
||||
};
|
||||
|
||||
AddPurpose.prototype.submitFormAjax = function(e) {
|
||||
// We don't want to do a real form submission.
|
||||
e.preventDefault();
|
||||
|
||||
// Convert all the form elements values to a serialised string.
|
||||
var formData = this.modal.getRoot().find('form').serialize();
|
||||
|
||||
Ajax.call([{
|
||||
methodname: 'tool_dataprivacy_create_purpose_form',
|
||||
args: {jsonformdata: JSON.stringify(formData)},
|
||||
done: function(data) {
|
||||
if (data.validationerrors) {
|
||||
this.modal.setBody(this.getBody(formData));
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
}.bind(this),
|
||||
|
||||
fail: Notification.exception
|
||||
}]);
|
||||
};
|
||||
|
||||
AddPurpose.prototype.close = function() {
|
||||
this.destroy();
|
||||
document.location.reload();
|
||||
};
|
||||
|
||||
AddPurpose.prototype.destroy = function() {
|
||||
Y.use('moodle-core-formchangechecker', function() {
|
||||
M.core_formchangechecker.reset_form_dirty_state();
|
||||
});
|
||||
this.modal.destroy();
|
||||
};
|
||||
|
||||
AddPurpose.prototype.removeListeners = function() {
|
||||
$(SELECTORS.PURPOSE_LINK).off('click');
|
||||
};
|
||||
|
||||
return /** @alias module:tool_dataprivacy/add_purpose */ {
|
||||
getInstance: function(contextId) {
|
||||
return new AddPurpose(contextId);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* AMD module for categories actions.
|
||||
*
|
||||
* @module tool_dataprivacy/categoriesactions
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/ajax',
|
||||
'core/notification',
|
||||
'core/str',
|
||||
'core/modal_factory',
|
||||
'core/modal_events'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
*
|
||||
* @type {{DELETE: string}}
|
||||
*/
|
||||
var ACTIONS = {
|
||||
DELETE: '[data-action="deletecategory"]',
|
||||
};
|
||||
|
||||
/**
|
||||
* CategoriesActions class.
|
||||
*/
|
||||
var CategoriesActions = function() {
|
||||
this.registerEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
CategoriesActions.prototype.registerEvents = function() {
|
||||
$(ACTIONS.DELETE).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var id = $(this).data('id');
|
||||
var categoryname = $(this).data('name');
|
||||
var stringkeys = [
|
||||
{
|
||||
key: 'deletecategory',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'deletecategorytext',
|
||||
component: 'tool_dataprivacy',
|
||||
param: categoryname
|
||||
},
|
||||
{
|
||||
key: 'delete'
|
||||
}
|
||||
];
|
||||
|
||||
Str.get_strings(stringkeys).then(function(langStrings) {
|
||||
var title = langStrings[0];
|
||||
var confirmMessage = langStrings[1];
|
||||
var buttonText = langStrings[2];
|
||||
return ModalFactory.create({
|
||||
title: title,
|
||||
body: confirmMessage,
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(buttonText);
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
|
||||
var request = {
|
||||
methodname: 'tool_dataprivacy_delete_category',
|
||||
args: {'id': id}
|
||||
};
|
||||
|
||||
Ajax.call([request])[0].done(function(data) {
|
||||
if (data.result) {
|
||||
$('tr[data-categoryid="' + id + '"]').remove();
|
||||
} else {
|
||||
Notification.addNotification({
|
||||
message: data.warnings[0].message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}).fail(Notification.exception);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
return modal;
|
||||
});
|
||||
}).done(function(modal) {
|
||||
modal.show();
|
||||
|
||||
}).fail(Notification.exception);
|
||||
});
|
||||
};
|
||||
|
||||
return /** @alias module:tool_dataprivacy/categoriesactions */ {
|
||||
// Public variables and functions.
|
||||
|
||||
/**
|
||||
* Initialise the module.
|
||||
*
|
||||
* @method init
|
||||
* @return {CategoriesActions}
|
||||
*/
|
||||
'init': function() {
|
||||
return new CategoriesActions();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Request actions.
|
||||
*
|
||||
* @module tool_dataprivacy/data_deletion
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/ajax',
|
||||
'core/notification',
|
||||
'core/str',
|
||||
'core/modal_factory',
|
||||
'core/modal_events'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
*
|
||||
* @type {{MARK_FOR_DELETION: string}}
|
||||
* @type {{SELECT_ALL: string}}
|
||||
*/
|
||||
var ACTIONS = {
|
||||
MARK_FOR_DELETION: '[data-action="markfordeletion"]',
|
||||
SELECT_ALL: '[data-action="selectall"]',
|
||||
};
|
||||
|
||||
/**
|
||||
* List of selectors.
|
||||
*
|
||||
* @type {{SELECTCONTEXT: string}}
|
||||
*/
|
||||
var SELECTORS = {
|
||||
SELECTCONTEXT: '.selectcontext',
|
||||
};
|
||||
|
||||
/**
|
||||
* DataDeletionActions class.
|
||||
*/
|
||||
var DataDeletionActions = function() {
|
||||
this.registerEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
DataDeletionActions.prototype.registerEvents = function() {
|
||||
$(ACTIONS.MARK_FOR_DELETION).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var selectedIds = [];
|
||||
$(SELECTORS.SELECTCONTEXT).each(function() {
|
||||
var checkbox = $(this);
|
||||
if (checkbox.is(':checked')) {
|
||||
selectedIds.push(checkbox.val());
|
||||
}
|
||||
});
|
||||
showConfirmation(selectedIds);
|
||||
});
|
||||
|
||||
$(ACTIONS.SELECT_ALL).change(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var selectallnone = $(this);
|
||||
if (selectallnone.is(':checked')) {
|
||||
$(SELECTORS.SELECTCONTEXT).attr('checked', 'checked');
|
||||
} else {
|
||||
$(SELECTORS.SELECTCONTEXT).removeAttr('checked');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the confirmation dialogue.
|
||||
*
|
||||
* @param {Array} ids The array of expired context record IDs.
|
||||
*/
|
||||
function showConfirmation(ids) {
|
||||
var keys = [
|
||||
{
|
||||
key: 'confirm',
|
||||
component: 'moodle'
|
||||
},
|
||||
{
|
||||
key: 'confirmcontextdeletion',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
var wsfunction = 'tool_dataprivacy_confirm_contexts_for_deletion';
|
||||
|
||||
var modalTitle = '';
|
||||
Str.get_strings(keys).then(function(langStrings) {
|
||||
modalTitle = langStrings[0];
|
||||
var confirmMessage = langStrings[1];
|
||||
return ModalFactory.create({
|
||||
title: modalTitle,
|
||||
body: confirmMessage,
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
});
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(modalTitle);
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
// Confirm the request.
|
||||
var params = {
|
||||
'ids': ids
|
||||
};
|
||||
|
||||
var request = {
|
||||
methodname: wsfunction,
|
||||
args: params
|
||||
};
|
||||
|
||||
Ajax.call([request])[0].done(function(data) {
|
||||
if (data.result) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
Notification.addNotification({
|
||||
message: data.warnings[0].message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}).fail(Notification.exception);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
return modal;
|
||||
}).done(function(modal) {
|
||||
modal.show();
|
||||
}).fail(Notification.exception);
|
||||
}
|
||||
|
||||
return DataDeletionActions;
|
||||
});
|
||||
@@ -0,0 +1,318 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Request actions.
|
||||
*
|
||||
* @module tool_dataprivacy/data_registry
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/str', 'core/ajax', 'core/notification', 'core/templates', 'core/modal_factory',
|
||||
'core/modal_events', 'core/fragment', 'tool_dataprivacy/add_purpose', 'tool_dataprivacy/add_category'],
|
||||
function($, Str, Ajax, Notification, Templates, ModalFactory, ModalEvents, Fragment, AddPurpose, AddCategory) {
|
||||
|
||||
var SELECTORS = {
|
||||
TREE_NODES: '[data-context-tree-node=1]',
|
||||
FORM_CONTAINER: '#context-form-container',
|
||||
};
|
||||
|
||||
var DataRegistry = function(systemContextId, initContextLevel, initContextId) {
|
||||
this.systemContextId = systemContextId;
|
||||
this.currentContextLevel = initContextLevel;
|
||||
this.currentContextId = initContextId;
|
||||
this.init();
|
||||
};
|
||||
|
||||
/**
|
||||
* @var {int} systemContextId
|
||||
* @private
|
||||
*/
|
||||
DataRegistry.prototype.systemContextId = 0;
|
||||
|
||||
/**
|
||||
* @var {int} currentContextLevel
|
||||
* @private
|
||||
*/
|
||||
DataRegistry.prototype.currentContextLevel = 0;
|
||||
|
||||
/**
|
||||
* @var {int} currentContextId
|
||||
* @private
|
||||
*/
|
||||
DataRegistry.prototype.currentContextId = 0;
|
||||
|
||||
/**
|
||||
* @var {AddPurpose} addpurpose
|
||||
* @private
|
||||
*/
|
||||
DataRegistry.prototype.addpurpose = null;
|
||||
|
||||
/**
|
||||
* @var {AddCategory} addcategory
|
||||
* @private
|
||||
*/
|
||||
DataRegistry.prototype.addcategory = null;
|
||||
|
||||
DataRegistry.prototype.init = function() {
|
||||
// Add purpose and category modals always at system context.
|
||||
this.addpurpose = AddPurpose.getInstance(this.systemContextId);
|
||||
this.addcategory = AddCategory.getInstance(this.systemContextId);
|
||||
|
||||
var stringKeys = [
|
||||
{
|
||||
key: 'changessaved',
|
||||
component: 'moodle'
|
||||
}, {
|
||||
key: 'contextpurposecategorysaved',
|
||||
component: 'tool_dataprivacy'
|
||||
}, {
|
||||
key: 'noblockstoload',
|
||||
component: 'tool_dataprivacy'
|
||||
}, {
|
||||
key: 'noactivitiestoload',
|
||||
component: 'tool_dataprivacy'
|
||||
}, {
|
||||
key: 'nocoursestoload',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
this.strings = Str.get_strings(stringKeys);
|
||||
|
||||
this.registerEventListeners();
|
||||
|
||||
// Load the default context level form.
|
||||
if (this.currentContextId) {
|
||||
this.loadForm('context_form', [this.currentContextId], this.submitContextFormAjax.bind(this));
|
||||
} else {
|
||||
this.loadForm('contextlevel_form', [this.currentContextLevel], this.submitContextLevelFormAjax.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
DataRegistry.prototype.registerEventListeners = function() {
|
||||
$(SELECTORS.TREE_NODES).on('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
var trigger = $(ev.currentTarget);
|
||||
|
||||
// Active node.
|
||||
$(SELECTORS.TREE_NODES).removeClass('active');
|
||||
trigger.addClass('active');
|
||||
|
||||
var contextLevel = trigger.data('contextlevel');
|
||||
var contextId = trigger.data('contextid');
|
||||
if (contextLevel) {
|
||||
// Context level level.
|
||||
|
||||
window.history.pushState({}, null, '?contextlevel=' + contextLevel);
|
||||
|
||||
// Remove previous add purpose and category listeners to avoid memory leaks.
|
||||
this.addpurpose.removeListeners();
|
||||
this.addcategory.removeListeners();
|
||||
|
||||
// Load the context level form.
|
||||
this.currentContextLevel = contextLevel;
|
||||
this.loadForm('contextlevel_form', [this.currentContextLevel], this.submitContextLevelFormAjax.bind(this));
|
||||
} else if (contextId) {
|
||||
// Context instance level.
|
||||
|
||||
window.history.pushState({}, null, '?contextid=' + contextId);
|
||||
|
||||
// Remove previous add purpose and category listeners to avoid memory leaks.
|
||||
this.addpurpose.removeListeners();
|
||||
this.addcategory.removeListeners();
|
||||
|
||||
// Load the context level form.
|
||||
this.currentContextId = contextId;
|
||||
this.loadForm('context_form', [this.currentContextId], this.submitContextFormAjax.bind(this));
|
||||
} else {
|
||||
// Expandable nodes.
|
||||
|
||||
var expandContextId = trigger.data('expandcontextid');
|
||||
var expandElement = trigger.data('expandelement');
|
||||
var expanded = trigger.data('expanded');
|
||||
|
||||
// Extra checking that there is an expandElement because we remove it after loading 0 branches.
|
||||
if (expandElement) {
|
||||
|
||||
if (!expanded) {
|
||||
if (trigger.data('loaded') || !expandContextId || !expandElement) {
|
||||
this.expand(trigger);
|
||||
} else {
|
||||
|
||||
trigger.find('> i').removeClass('fa-plus');
|
||||
trigger.find('> i').addClass('fa-circle-o-notch fa-spin');
|
||||
this.loadExtra(trigger, expandContextId, expandElement);
|
||||
}
|
||||
} else {
|
||||
this.collapse(trigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
DataRegistry.prototype.removeListeners = function() {
|
||||
$(SELECTORS.TREE_NODES).off('click');
|
||||
};
|
||||
|
||||
DataRegistry.prototype.loadForm = function(fragmentName, fragmentArgs, formSubmitCallback) {
|
||||
|
||||
this.clearForm();
|
||||
|
||||
var fragment = Fragment.loadFragment('tool_dataprivacy', fragmentName, this.systemContextId, fragmentArgs);
|
||||
fragment.done(function(html, js) {
|
||||
|
||||
$(SELECTORS.FORM_CONTAINER).html(html);
|
||||
Templates.runTemplateJS(js);
|
||||
|
||||
this.addpurpose.registerEventListeners();
|
||||
this.addcategory.registerEventListeners();
|
||||
|
||||
// We also catch the form submit event and use it to submit the form with ajax.
|
||||
$(SELECTORS.FORM_CONTAINER).on('submit', 'form', formSubmitCallback);
|
||||
|
||||
}.bind(this)).fail(Notification.exception);
|
||||
};
|
||||
|
||||
DataRegistry.prototype.clearForm = function() {
|
||||
// For the previously loaded form.
|
||||
Y.use('moodle-core-formchangechecker', function() {
|
||||
M.core_formchangechecker.reset_form_dirty_state();
|
||||
});
|
||||
|
||||
// Remove previous listeners.
|
||||
$(SELECTORS.FORM_CONTAINER).off('submit', 'form');
|
||||
};
|
||||
|
||||
/**
|
||||
* This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
|
||||
*
|
||||
* @method submitForm
|
||||
* @param {Event} e Form submission event.
|
||||
* @private
|
||||
*/
|
||||
DataRegistry.prototype.submitForm = function(e) {
|
||||
e.preventDefault();
|
||||
$(SELECTORS.FORM_CONTAINER).find('form').submit();
|
||||
};
|
||||
|
||||
DataRegistry.prototype.submitContextLevelFormAjax = function(e) {
|
||||
this.submitFormAjax(e, 'tool_dataprivacy_set_contextlevel_form');
|
||||
};
|
||||
|
||||
DataRegistry.prototype.submitContextFormAjax = function(e) {
|
||||
this.submitFormAjax(e, 'tool_dataprivacy_set_context_form');
|
||||
};
|
||||
|
||||
DataRegistry.prototype.submitFormAjax = function(e, saveMethodName) {
|
||||
// We don't want to do a real form submission.
|
||||
e.preventDefault();
|
||||
|
||||
// Convert all the form elements values to a serialised string.
|
||||
var formData = $(SELECTORS.FORM_CONTAINER).find('form').serialize();
|
||||
return this.strings.then(function(strings) {
|
||||
Ajax.call([{
|
||||
methodname: saveMethodName,
|
||||
args: {jsonformdata: JSON.stringify(formData)},
|
||||
done: function() {
|
||||
Notification.alert(strings[0], strings[1]);
|
||||
},
|
||||
fail: Notification.exception
|
||||
}]);
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
|
||||
};
|
||||
|
||||
DataRegistry.prototype.loadExtra = function(parentNode, expandContextId, expandElement) {
|
||||
|
||||
Ajax.call([{
|
||||
methodname: 'tool_dataprivacy_tree_extra_branches',
|
||||
args: {
|
||||
contextid: expandContextId,
|
||||
element: expandElement,
|
||||
},
|
||||
done: function(data) {
|
||||
if (data.branches.length == 0) {
|
||||
this.noElements(parentNode, expandElement);
|
||||
return;
|
||||
}
|
||||
Templates.render('tool_dataprivacy/context_tree_branches', data)
|
||||
.then(function(html) {
|
||||
parentNode.after(html);
|
||||
this.removeListeners();
|
||||
this.registerEventListeners();
|
||||
this.expand(parentNode);
|
||||
parentNode.data('loaded', 1);
|
||||
return;
|
||||
}.bind(this))
|
||||
.fail(Notification.exception);
|
||||
}.bind(this),
|
||||
fail: Notification.exception
|
||||
}]);
|
||||
};
|
||||
|
||||
DataRegistry.prototype.noElements = function(node, expandElement) {
|
||||
node.data('expandcontextid', '');
|
||||
node.data('expandelement', '');
|
||||
this.strings.then(function(strings) {
|
||||
|
||||
// 2 = blocks, 3 = activities, 4 = courses (although courses is not likely really).
|
||||
var key = 2;
|
||||
if (expandElement == 'module') {
|
||||
key = 3;
|
||||
} else if (expandElement == 'course') {
|
||||
key = 4;
|
||||
}
|
||||
node.text(strings[key]);
|
||||
return;
|
||||
}).fail(Notification.exception);
|
||||
};
|
||||
|
||||
DataRegistry.prototype.collapse = function(node) {
|
||||
node.data('expanded', 0);
|
||||
node.siblings('nav').addClass('hidden');
|
||||
node.find('> i').removeClass('fa-minus');
|
||||
node.find('> i').addClass('fa-plus');
|
||||
};
|
||||
|
||||
DataRegistry.prototype.expand = function(node) {
|
||||
node.data('expanded', 1);
|
||||
node.siblings('nav').removeClass('hidden');
|
||||
node.find('> i').removeClass('fa-plus');
|
||||
// Also remove the spinning one if data was just loaded.
|
||||
node.find('> i').removeClass('fa-circle-o-notch fa-spin');
|
||||
node.find('> i').addClass('fa-minus');
|
||||
};
|
||||
return /** @alias module:tool_dataprivacy/data_registry */ {
|
||||
|
||||
/**
|
||||
* Initialise the page.
|
||||
*
|
||||
* @param {Number} systemContextId
|
||||
* @param {Number} initContextLevel
|
||||
* @param {Number} initContextId
|
||||
* @return {DataRegistry}
|
||||
*/
|
||||
init: function(systemContextId, initContextLevel, initContextId) {
|
||||
return new DataRegistry(systemContextId, initContextLevel, initContextId);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Request actions.
|
||||
*
|
||||
* @module tool_dataprivacy/data_request_modal
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_registry',
|
||||
'tool_dataprivacy/events'],
|
||||
function($, Notification, CustomEvents, Modal, ModalRegistry, DataPrivacyEvents) {
|
||||
|
||||
var registered = false;
|
||||
var SELECTORS = {
|
||||
APPROVE_BUTTON: '[data-action="approve"]',
|
||||
DENY_BUTTON: '[data-action="deny"]',
|
||||
COMPLETE_BUTTON: '[data-action="complete"]'
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for the Modal.
|
||||
*
|
||||
* @param {object} root The root jQuery element for the modal
|
||||
*/
|
||||
var ModalDataRequest = function(root) {
|
||||
Modal.call(this, root);
|
||||
};
|
||||
|
||||
ModalDataRequest.TYPE = 'tool_dataprivacy-data_request';
|
||||
ModalDataRequest.TYPE_ENQUIRY = 'tool_dataprivacy-complete_request';
|
||||
ModalDataRequest.prototype = Object.create(Modal.prototype);
|
||||
ModalDataRequest.prototype.constructor = ModalDataRequest;
|
||||
|
||||
/**
|
||||
* Set up all of the event handling for the modal.
|
||||
*
|
||||
* @method registerEventListeners
|
||||
*/
|
||||
ModalDataRequest.prototype.registerEventListeners = function() {
|
||||
// Apply parent event listeners.
|
||||
Modal.prototype.registerEventListeners.call(this);
|
||||
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.APPROVE_BUTTON, function(e, data) {
|
||||
var approveEvent = $.Event(DataPrivacyEvents.approve);
|
||||
this.getRoot().trigger(approveEvent, this);
|
||||
|
||||
if (!approveEvent.isDefaultPrevented()) {
|
||||
this.hide();
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.DENY_BUTTON, function(e, data) {
|
||||
var denyEvent = $.Event(DataPrivacyEvents.deny);
|
||||
this.getRoot().trigger(denyEvent, this);
|
||||
|
||||
if (!denyEvent.isDefaultPrevented()) {
|
||||
this.hide();
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.COMPLETE_BUTTON, function(e, data) {
|
||||
var completeEvent = $.Event(DataPrivacyEvents.complete);
|
||||
this.getRoot().trigger(completeEvent, this);
|
||||
|
||||
if (!completeEvent.isDefaultPrevented()) {
|
||||
this.hide();
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
// Automatically register with the modal registry the first time this module is imported so that you can create modals
|
||||
// of this type using the modal factory.
|
||||
if (!registered) {
|
||||
ModalRegistry.register(ModalDataRequest.TYPE, ModalDataRequest, 'tool_dataprivacy/data_request_modal');
|
||||
ModalRegistry.register(ModalDataRequest.TYPE_ENQUIRY, ModalDataRequest, 'tool_dataprivacy/mark_request_complete_modal');
|
||||
registered = true;
|
||||
}
|
||||
|
||||
return ModalDataRequest;
|
||||
});
|
||||
@@ -0,0 +1,315 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* AMD module for data registry defaults actions.
|
||||
*
|
||||
* @module tool_dataprivacy/defaultsactions
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/ajax',
|
||||
'core/notification',
|
||||
'core/str',
|
||||
'core/modal_factory',
|
||||
'core/modal_events',
|
||||
'core/templates'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
*
|
||||
* @type {{EDIT_LEVEL_DEFAULTS: string}}
|
||||
* @type {{NEW_ACTIVITY_DEFAULTS: string}}
|
||||
* @type {{EDIT_ACTIVITY_DEFAULTS: string}}
|
||||
* @type {{DELETE_ACTIVITY_DEFAULTS: string}}
|
||||
*/
|
||||
var ACTIONS = {
|
||||
EDIT_LEVEL_DEFAULTS: '[data-action="edit-level-defaults"]',
|
||||
NEW_ACTIVITY_DEFAULTS: '[data-action="new-activity-defaults"]',
|
||||
EDIT_ACTIVITY_DEFAULTS: '[data-action="edit-activity-defaults"]',
|
||||
DELETE_ACTIVITY_DEFAULTS: '[data-action="delete-activity-defaults"]'
|
||||
};
|
||||
|
||||
/** @type {{INHERIT: Number}} **/
|
||||
var INHERIT = -1;
|
||||
|
||||
/**
|
||||
* DefaultsActions class.
|
||||
*/
|
||||
var DefaultsActions = function() {
|
||||
this.registerEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
DefaultsActions.prototype.registerEvents = function() {
|
||||
$(ACTIONS.EDIT_LEVEL_DEFAULTS).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var button = $(this);
|
||||
var contextLevel = button.data('contextlevel');
|
||||
var category = button.data('category');
|
||||
var purpose = button.data('purpose');
|
||||
|
||||
// Get options.
|
||||
var requests = [
|
||||
{methodname: 'tool_dataprivacy_get_category_options', args: {}},
|
||||
{methodname: 'tool_dataprivacy_get_purpose_options', args: {}}
|
||||
];
|
||||
|
||||
var promises = Ajax.call(requests);
|
||||
var titlePromise = Str.get_string('editdefaults', 'tool_dataprivacy', $('#defaults-header').text());
|
||||
$.when(promises[0], promises[1], titlePromise).then(function(categoryResponse, purposeResponse, title) {
|
||||
var categories = categoryResponse.options;
|
||||
var purposes = purposeResponse.options;
|
||||
showDefaultsFormModal(title, contextLevel, category, purpose, null, categories, purposes, null);
|
||||
|
||||
return true;
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
|
||||
$(ACTIONS.NEW_ACTIVITY_DEFAULTS).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var button = $(this);
|
||||
var contextLevel = button.data('contextlevel');
|
||||
|
||||
// Get options.
|
||||
var requests = [
|
||||
{methodname: 'tool_dataprivacy_get_category_options', args: {}},
|
||||
{methodname: 'tool_dataprivacy_get_purpose_options', args: {}},
|
||||
{methodname: 'tool_dataprivacy_get_activity_options', args: {'nodefaults': true}}
|
||||
];
|
||||
|
||||
var promises = Ajax.call(requests);
|
||||
var titlePromise = Str.get_string('addnewdefaults', 'tool_dataprivacy');
|
||||
|
||||
$.when(promises[0], promises[1], promises[2], titlePromise).then(
|
||||
function(categoryResponse, purposeResponse, activityResponse, title) {
|
||||
var categories = categoryResponse.options;
|
||||
var purposes = purposeResponse.options;
|
||||
var activities = activityResponse.options;
|
||||
|
||||
showDefaultsFormModal(title, contextLevel, null, null, null, categories, purposes, activities);
|
||||
|
||||
return true;
|
||||
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
);
|
||||
|
||||
$(ACTIONS.EDIT_ACTIVITY_DEFAULTS).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var button = $(this);
|
||||
var contextLevel = button.data('contextlevel');
|
||||
var category = button.data('category');
|
||||
var purpose = button.data('purpose');
|
||||
var activity = button.data('activityname');
|
||||
|
||||
// Get options.
|
||||
var requests = [
|
||||
{methodname: 'tool_dataprivacy_get_category_options', args: {}},
|
||||
{methodname: 'tool_dataprivacy_get_purpose_options', args: {}},
|
||||
{methodname: 'tool_dataprivacy_get_activity_options', args: {}}
|
||||
];
|
||||
|
||||
var promises = Ajax.call(requests);
|
||||
var titlePromise = Str.get_string('editmoduledefaults', 'tool_dataprivacy');
|
||||
|
||||
$.when(promises[0], promises[1], promises[2], titlePromise).then(
|
||||
function(categoryResponse, purposeResponse, activityResponse, title) {
|
||||
var categories = categoryResponse.options;
|
||||
var purposes = purposeResponse.options;
|
||||
var activities = activityResponse.options;
|
||||
|
||||
showDefaultsFormModal(title, contextLevel, category, purpose, activity, categories, purposes, activities);
|
||||
|
||||
return true;
|
||||
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
);
|
||||
|
||||
$(ACTIONS.DELETE_ACTIVITY_DEFAULTS).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var button = $(this);
|
||||
var contextLevel = button.data('contextlevel');
|
||||
var activity = button.data('activityname');
|
||||
var activityDisplayName = button.data('activitydisplayname');
|
||||
// Set category and purpose to inherit (-1).
|
||||
var category = INHERIT;
|
||||
var purpose = INHERIT;
|
||||
|
||||
ModalFactory.create({
|
||||
title: Str.get_string('deletedefaults', 'tool_dataprivacy', activityDisplayName),
|
||||
body: Templates.render('tool_dataprivacy/delete_activity_defaults', {"activityname": activityDisplayName}),
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
large: true
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(Str.get_string('delete'));
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
setContextDefaults(contextLevel, category, purpose, activity, false);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
modal.show();
|
||||
|
||||
return true;
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares and renders the modal for setting the defaults for the given context level/plugin.
|
||||
*
|
||||
* @param {String} title The modal's title.
|
||||
* @param {Number} contextLevel The context level to set defaults for.
|
||||
* @param {Number} category The current category ID.
|
||||
* @param {Number} purpose The current purpose ID.
|
||||
* @param {String} activity The plugin name of the activity. Optional.
|
||||
* @param {Array} categoryOptions The list of category options.
|
||||
* @param {Array} purposeOptions The list of purpose options.
|
||||
* @param {Array} activityOptions The list of activity options. Optional.
|
||||
*/
|
||||
function showDefaultsFormModal(title, contextLevel, category, purpose, activity,
|
||||
categoryOptions, purposeOptions, activityOptions) {
|
||||
|
||||
if (category !== null) {
|
||||
categoryOptions.forEach(function(currentValue) {
|
||||
if (currentValue.id === category) {
|
||||
currentValue.selected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (purpose !== null) {
|
||||
purposeOptions.forEach(function(currentValue) {
|
||||
if (currentValue.id === purpose) {
|
||||
currentValue.selected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var templateContext = {
|
||||
"contextlevel": contextLevel,
|
||||
"categoryoptions": categoryOptions,
|
||||
"purposeoptions": purposeOptions
|
||||
};
|
||||
|
||||
// Check the activityOptions parameter that was passed.
|
||||
if (activityOptions !== null && activityOptions.length) {
|
||||
// Check the activity parameter that was passed.
|
||||
if (activity === null) {
|
||||
// We're setting a new defaults for a module.
|
||||
templateContext.newactivitydefaults = true;
|
||||
|
||||
} else {
|
||||
// Edit mode. Set selection.
|
||||
activityOptions.forEach(function(currentValue) {
|
||||
if (activity === currentValue.name) {
|
||||
currentValue.selected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
templateContext.modemodule = true;
|
||||
templateContext.activityoptions = activityOptions;
|
||||
}
|
||||
|
||||
ModalFactory.create({
|
||||
title: title,
|
||||
body: Templates.render('tool_dataprivacy/category_purpose_form', templateContext),
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
large: true
|
||||
}).then(function(modal) {
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
var activity = $('#activity');
|
||||
var activityVal = typeof activity !== 'undefined' ? activity.val() : null;
|
||||
var override = $('#override');
|
||||
var overrideVal = typeof override !== 'undefined' ? override.is(':checked') : false;
|
||||
|
||||
setContextDefaults($('#contextlevel').val(), $('#category').val(), $('#purpose').val(), activityVal, overrideVal);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
modal.show();
|
||||
|
||||
return modal;
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a the tool_dataprivacy_set_context_defaults WS function.
|
||||
*
|
||||
* @param {Number} contextLevel The context level.
|
||||
* @param {Number} category The category ID.
|
||||
* @param {Number} purpose The purpose ID.
|
||||
* @param {String} activity The plugin name of the activity module.
|
||||
* @param {Boolean} override Whether to override custom instances.
|
||||
*/
|
||||
function setContextDefaults(contextLevel, category, purpose, activity, override) {
|
||||
var request = {
|
||||
methodname: 'tool_dataprivacy_set_context_defaults',
|
||||
args: {
|
||||
'contextlevel': contextLevel,
|
||||
'category': category,
|
||||
'purpose': purpose,
|
||||
'override': override,
|
||||
'activity': activity
|
||||
}
|
||||
};
|
||||
|
||||
Ajax.call([request])[0].done(function(data) {
|
||||
if (data.result) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return /** @alias module:tool_dataprivacy/defaultsactions */ {
|
||||
// Public variables and functions.
|
||||
|
||||
/**
|
||||
* Initialise the module.
|
||||
*
|
||||
* @method init
|
||||
* @return {DefaultsActions}
|
||||
*/
|
||||
'init': function() {
|
||||
return new DefaultsActions();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Module to update the displayed retention period.
|
||||
*
|
||||
* @module tool_dataprivacy/effective_retention_period
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery'],
|
||||
function($) {
|
||||
|
||||
var SELECTORS = {
|
||||
PURPOSE_SELECT: '#id_purposeid',
|
||||
RETENTION_FIELD_BOOST: '#id_error_retention_current',
|
||||
RETENTION_FIELD_CLEAN: '#fitem_id_retention_current [data-fieldtype=static]',
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for the retention period display.
|
||||
*
|
||||
* @param {Array} purposeRetentionPeriods Associative array of purposeids with effective retention period at this context
|
||||
*/
|
||||
var EffectiveRetentionPeriod = function(purposeRetentionPeriods) {
|
||||
this.purposeRetentionPeriods = purposeRetentionPeriods;
|
||||
this.registerEventListeners();
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the current 'change' listeners.
|
||||
*
|
||||
* Useful when a new form is loaded.
|
||||
*/
|
||||
var removeListeners = function() {
|
||||
$(SELECTORS.PURPOSE_SELECT).off('change');
|
||||
};
|
||||
|
||||
/**
|
||||
* @var {Array} purposeRetentionPeriods
|
||||
* @private
|
||||
*/
|
||||
EffectiveRetentionPeriod.prototype.purposeRetentionPeriods = [];
|
||||
|
||||
/**
|
||||
* Add purpose change listeners.
|
||||
*
|
||||
* @method registerEventListeners
|
||||
*/
|
||||
EffectiveRetentionPeriod.prototype.registerEventListeners = function() {
|
||||
|
||||
$(SELECTORS.PURPOSE_SELECT).on('change', function(ev) {
|
||||
var selected = $(ev.currentTarget).val();
|
||||
var selectedPurpose = this.purposeRetentionPeriods[selected];
|
||||
|
||||
var cleanSelector = $(SELECTORS.RETENTION_FIELD_CLEAN);
|
||||
if (cleanSelector.length > 0) {
|
||||
cleanSelector.text(selectedPurpose);
|
||||
} else {
|
||||
var boostSelector = $(SELECTORS.RETENTION_FIELD_BOOST);
|
||||
var retentionField = boostSelector.siblings();
|
||||
if (retentionField.length > 0) {
|
||||
retentionField.text(selectedPurpose);
|
||||
}
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
return /** @alias module:tool_dataprivacy/effective_retention_period */ {
|
||||
init: function(purposeRetentionPeriods) {
|
||||
// Remove previously attached listeners.
|
||||
removeListeners();
|
||||
return new EffectiveRetentionPeriod(purposeRetentionPeriods);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Contain the events the data privacy tool can fire.
|
||||
*
|
||||
* @module tool_dataprivacy/events
|
||||
* @class events
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([], function() {
|
||||
return {
|
||||
approve: 'tool_dataprivacy-data_request:approve',
|
||||
bulkApprove: 'tool_dataprivacy-data_request:bulk_approve',
|
||||
deny: 'tool_dataprivacy-data_request:deny',
|
||||
bulkDeny: 'tool_dataprivacy-data_request:bulk_deny',
|
||||
complete: 'tool_dataprivacy-data_request:complete'
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Potential user selector module.
|
||||
*
|
||||
* @module tool_dataprivacy/expand_contract
|
||||
* @class page-expand-contract
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Adrian Greeve
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define(['jquery', 'core/url', 'core/str'], function($, url, str) {
|
||||
|
||||
var expandedImage = $('<img alt="" src="' + url.imageUrl('t/expanded') + '"/>');
|
||||
var collapsedImage = $('<img alt="" src="' + url.imageUrl('t/collapsed') + '"/>');
|
||||
|
||||
return /** @alias module:tool_dataprivacy/expand-collapse */ {
|
||||
/**
|
||||
* Expand or collapse a selected node.
|
||||
*
|
||||
* @param {object} targetnode The node that we want to expand / collapse
|
||||
* @param {object} thisnode The node that was clicked.
|
||||
*/
|
||||
expandCollapse: function(targetnode, thisnode) {
|
||||
if (targetnode.hasClass('hide')) {
|
||||
targetnode.removeClass('hide');
|
||||
targetnode.addClass('visible');
|
||||
targetnode.attr('aria-expanded', true);
|
||||
thisnode.find(':header i.fa').removeClass('fa-plus-square');
|
||||
thisnode.find(':header i.fa').addClass('fa-minus-square');
|
||||
thisnode.find(':header img.icon').attr('src', expandedImage.attr('src'));
|
||||
} else {
|
||||
targetnode.removeClass('visible');
|
||||
targetnode.addClass('hide');
|
||||
targetnode.attr('aria-expanded', false);
|
||||
thisnode.find(':header i.fa').removeClass('fa-minus-square');
|
||||
thisnode.find(':header i.fa').addClass('fa-plus-square');
|
||||
thisnode.find(':header img.icon').attr('src', collapsedImage.attr('src'));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Expand or collapse all nodes on this page.
|
||||
*
|
||||
* @param {string} nextstate The next state to change to.
|
||||
*/
|
||||
expandCollapseAll: function(nextstate) {
|
||||
var currentstate = (nextstate == 'visible') ? 'hide' : 'visible';
|
||||
var ariaexpandedstate = (nextstate == 'visible') ? true : false;
|
||||
var iconclassnow = (nextstate == 'visible') ? 'fa-plus-square' : 'fa-minus-square';
|
||||
var iconclassnext = (nextstate == 'visible') ? 'fa-minus-square' : 'fa-plus-square';
|
||||
var imagenow = (nextstate == 'visible') ? expandedImage.attr('src') : collapsedImage.attr('src');
|
||||
$('.' + currentstate).each(function() {
|
||||
$(this).removeClass(currentstate);
|
||||
$(this).addClass(nextstate);
|
||||
$(this).attr('aria-expanded', ariaexpandedstate);
|
||||
});
|
||||
$('.tool_dataprivacy-expand-all').data('visibilityState', currentstate);
|
||||
|
||||
str.get_string(currentstate, 'tool_dataprivacy').then(function(langString) {
|
||||
$('.tool_dataprivacy-expand-all').html(langString);
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
|
||||
$(':header i.fa').each(function() {
|
||||
$(this).removeClass(iconclassnow);
|
||||
$(this).addClass(iconclassnext);
|
||||
});
|
||||
$(':header img.icon').each(function() {
|
||||
$(this).attr('src', imagenow);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Potential user selector module.
|
||||
*
|
||||
* @module tool_dataprivacy/form-user-selector
|
||||
* @class form-user-selector
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
|
||||
|
||||
return /** @alias module:tool_dataprivacy/form-user-selector */ {
|
||||
|
||||
processResults: function(selector, results) {
|
||||
var users = [];
|
||||
$.each(results, function(index, user) {
|
||||
users.push({
|
||||
value: user.id,
|
||||
label: user._label
|
||||
});
|
||||
});
|
||||
return users;
|
||||
},
|
||||
|
||||
transport: function(selector, query, success, failure) {
|
||||
var promise;
|
||||
|
||||
promise = Ajax.call([{
|
||||
methodname: 'tool_dataprivacy_get_users',
|
||||
args: {
|
||||
query: query
|
||||
}
|
||||
}]);
|
||||
|
||||
promise[0].then(function(results) {
|
||||
var promises = [],
|
||||
i = 0;
|
||||
|
||||
// Render the label.
|
||||
$.each(results, function(index, user) {
|
||||
promises.push(Templates.render('tool_dataprivacy/form-user-selector-suggestion', user));
|
||||
});
|
||||
|
||||
// Apply the label to the results.
|
||||
return $.when.apply($.when, promises).then(function() {
|
||||
var args = arguments;
|
||||
$.each(results, function(index, user) {
|
||||
user._label = args[i];
|
||||
i++;
|
||||
});
|
||||
success(results);
|
||||
return;
|
||||
});
|
||||
|
||||
}).fail(failure);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,221 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* AMD module to enable users to manage their own data requests.
|
||||
*
|
||||
* @module tool_dataprivacy/myrequestactions
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/ajax',
|
||||
'core/notification',
|
||||
'core/str',
|
||||
'core/modal_factory',
|
||||
'core/modal_events',
|
||||
'core/templates'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
*
|
||||
* @type {{CANCEL_REQUEST: string}}
|
||||
* @type {{CONTACT_DPO: string}}
|
||||
*/
|
||||
var ACTIONS = {
|
||||
CANCEL_REQUEST: '[data-action="cancel"]',
|
||||
CONTACT_DPO: '[data-action="contactdpo"]',
|
||||
};
|
||||
|
||||
/**
|
||||
* MyRequestActions class.
|
||||
*/
|
||||
var MyRequestActions = function() {
|
||||
this.registerEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
MyRequestActions.prototype.registerEvents = function() {
|
||||
$(ACTIONS.CANCEL_REQUEST).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var requestId = $(this).data('requestid');
|
||||
var stringkeys = [
|
||||
{
|
||||
key: 'cancelrequest',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'cancelrequestconfirmation',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
|
||||
Str.get_strings(stringkeys).then(function(langStrings) {
|
||||
var title = langStrings[0];
|
||||
var confirmMessage = langStrings[1];
|
||||
return ModalFactory.create({
|
||||
title: title,
|
||||
body: confirmMessage,
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(title);
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
// Cancel the request.
|
||||
var params = {
|
||||
'requestid': requestId
|
||||
};
|
||||
|
||||
var request = {
|
||||
methodname: 'tool_dataprivacy_cancel_data_request',
|
||||
args: params
|
||||
};
|
||||
|
||||
Ajax.call([request])[0].done(function(data) {
|
||||
if (data.result) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
Notification.addNotification({
|
||||
message: data.warnings[0].message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}).fail(Notification.exception);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
return modal;
|
||||
});
|
||||
}).done(function(modal) {
|
||||
// Show the modal!
|
||||
modal.show();
|
||||
|
||||
}).fail(Notification.exception);
|
||||
});
|
||||
|
||||
$(ACTIONS.CONTACT_DPO).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var replyToEmail = $(this).data('replytoemail');
|
||||
|
||||
var keys = [
|
||||
{
|
||||
key: 'contactdataprotectionofficer',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'send',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
];
|
||||
|
||||
var sendButtonText = '';
|
||||
Str.get_strings(keys).then(function(langStrings) {
|
||||
var modalTitle = langStrings[0];
|
||||
sendButtonText = langStrings[1];
|
||||
var context = {
|
||||
'replytoemail': replyToEmail
|
||||
};
|
||||
return ModalFactory.create({
|
||||
title: modalTitle,
|
||||
body: Templates.render('tool_dataprivacy/contact_dpo', context),
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
large: true
|
||||
});
|
||||
}).done(function(modal) {
|
||||
modal.setSaveButtonText(sendButtonText);
|
||||
|
||||
// Handle send event.
|
||||
modal.getRoot().on(ModalEvents.save, function(e) {
|
||||
var message = $('#message').val().trim();
|
||||
if (message.length === 0) {
|
||||
e.preventDefault();
|
||||
// Show validation error when the message is empty.
|
||||
$('[data-region="messageinput"]').addClass('has-danger notifyproblem');
|
||||
$('#id_error_message').removeAttr('hidden');
|
||||
} else {
|
||||
// Send the message.
|
||||
sendMessageToDPO(message);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
// Show the modal!
|
||||
modal.show();
|
||||
}).fail(Notification.exception);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send message to the Data Protection Officer.
|
||||
*
|
||||
* @param {String} message The message to send.
|
||||
*/
|
||||
function sendMessageToDPO(message) {
|
||||
var request = {
|
||||
methodname: 'tool_dataprivacy_contact_dpo',
|
||||
args: {
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
var requestType = 'success';
|
||||
Ajax.call([request])[0].then(function(data) {
|
||||
if (data.result) {
|
||||
return Str.get_string('requestsubmitted', 'tool_dataprivacy');
|
||||
}
|
||||
requestType = 'error';
|
||||
return data.warnings.join('<br>');
|
||||
|
||||
}).done(function(message) {
|
||||
Notification.addNotification({
|
||||
message: message,
|
||||
type: requestType
|
||||
});
|
||||
|
||||
}).fail(Notification.exception);
|
||||
}
|
||||
|
||||
return /** @alias module:tool_dataprivacy/myrequestactions */ {
|
||||
// Public variables and functions.
|
||||
|
||||
/**
|
||||
* Initialise the unified user filter.
|
||||
*
|
||||
* @method init
|
||||
* @return {MyRequestActions}
|
||||
*/
|
||||
'init': function() {
|
||||
return new MyRequestActions();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* AMD module for purposes actions.
|
||||
*
|
||||
* @module tool_dataprivacy/purposesactions
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/ajax',
|
||||
'core/notification',
|
||||
'core/str',
|
||||
'core/modal_factory',
|
||||
'core/modal_events'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
*
|
||||
* @type {{DELETE: string}}
|
||||
*/
|
||||
var ACTIONS = {
|
||||
DELETE: '[data-action="deletepurpose"]',
|
||||
};
|
||||
|
||||
/**
|
||||
* PurposesActions class.
|
||||
*/
|
||||
var PurposesActions = function() {
|
||||
this.registerEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
PurposesActions.prototype.registerEvents = function() {
|
||||
$(ACTIONS.DELETE).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var id = $(this).data('id');
|
||||
var purposename = $(this).data('name');
|
||||
var stringkeys = [
|
||||
{
|
||||
key: 'deletepurpose',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'deletepurposetext',
|
||||
component: 'tool_dataprivacy',
|
||||
param: purposename
|
||||
},
|
||||
{
|
||||
key: 'delete'
|
||||
}
|
||||
];
|
||||
|
||||
Str.get_strings(stringkeys).then(function(langStrings) {
|
||||
var title = langStrings[0];
|
||||
var confirmMessage = langStrings[1];
|
||||
var buttonText = langStrings[2];
|
||||
return ModalFactory.create({
|
||||
title: title,
|
||||
body: confirmMessage,
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(buttonText);
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
|
||||
var request = {
|
||||
methodname: 'tool_dataprivacy_delete_purpose',
|
||||
args: {'id': id}
|
||||
};
|
||||
|
||||
Ajax.call([request])[0].done(function(data) {
|
||||
if (data.result) {
|
||||
$('tr[data-purposeid="' + id + '"]').remove();
|
||||
} else {
|
||||
Notification.addNotification({
|
||||
message: data.warnings[0].message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}).fail(Notification.exception);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
return modal;
|
||||
});
|
||||
}).done(function(modal) {
|
||||
modal.show();
|
||||
|
||||
}).fail(Notification.exception);
|
||||
});
|
||||
};
|
||||
|
||||
return /** @alias module:tool_dataprivacy/purposesactions */ {
|
||||
// Public variables and functions.
|
||||
|
||||
/**
|
||||
* Initialise the module.
|
||||
*
|
||||
* @method init
|
||||
* @return {PurposesActions}
|
||||
*/
|
||||
'init': function() {
|
||||
return new PurposesActions();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* JS module for the data requests filter.
|
||||
*
|
||||
* @module tool_dataprivacy/request_filter
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/form-autocomplete', 'core/str', 'core/notification'], function($, Autocomplete, Str, Notification) {
|
||||
|
||||
/**
|
||||
* Selectors.
|
||||
*
|
||||
* @access private
|
||||
* @type {{REQUEST_FILTERS: string}}
|
||||
*/
|
||||
var SELECTORS = {
|
||||
REQUEST_FILTERS: '#request-filters'
|
||||
};
|
||||
|
||||
/**
|
||||
* Init function.
|
||||
*
|
||||
* @method init
|
||||
* @private
|
||||
*/
|
||||
var init = function() {
|
||||
var stringkeys = [
|
||||
{
|
||||
key: 'filter',
|
||||
component: 'moodle'
|
||||
},
|
||||
{
|
||||
key: 'nofiltersapplied',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
|
||||
Str.get_strings(stringkeys).then(function(langstrings) {
|
||||
var placeholder = langstrings[0];
|
||||
var noSelectionString = langstrings[1];
|
||||
return Autocomplete.enhance(SELECTORS.REQUEST_FILTERS, false, '', placeholder, false, true, noSelectionString, true);
|
||||
}).fail(Notification.exception);
|
||||
|
||||
var last = $(SELECTORS.REQUEST_FILTERS).val();
|
||||
$(SELECTORS.REQUEST_FILTERS).on('change', function() {
|
||||
var current = $(this).val();
|
||||
// Prevent form from submitting unnecessarily, eg. on blur when no filter is selected.
|
||||
if (last.join(',') !== current.join(',')) {
|
||||
// If we're submitting without filters, set the hidden input 'filters-cleared' to 1.
|
||||
if (current.length === 0) {
|
||||
$('#filters-cleared').val(1);
|
||||
}
|
||||
$(this.form).submit();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return /** @alias module:core/form-autocomplete */ {
|
||||
/**
|
||||
* Initialise the unified user filter.
|
||||
*
|
||||
* @method init
|
||||
*/
|
||||
init: function() {
|
||||
init();
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,443 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Request actions.
|
||||
*
|
||||
* @module tool_dataprivacy/requestactions
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/ajax',
|
||||
'core/notification',
|
||||
'core/str',
|
||||
'core/modal_factory',
|
||||
'core/modal_events',
|
||||
'core/templates',
|
||||
'tool_dataprivacy/data_request_modal',
|
||||
'tool_dataprivacy/events'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, ModalDataRequest, DataPrivacyEvents) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
*
|
||||
* @type {{APPROVE_REQUEST: string}}
|
||||
* @type {{DENY_REQUEST: string}}
|
||||
* @type {{VIEW_REQUEST: string}}
|
||||
* @type {{MARK_COMPLETE: string}}
|
||||
* @type {{CHANGE_BULK_ACTION: string}}
|
||||
* @type {{CONFIRM_BULK_ACTION: string}}
|
||||
* @type {{SELECT_ALL: string}}
|
||||
*/
|
||||
var ACTIONS = {
|
||||
APPROVE_REQUEST: '[data-action="approve"]',
|
||||
DENY_REQUEST: '[data-action="deny"]',
|
||||
VIEW_REQUEST: '[data-action="view"]',
|
||||
MARK_COMPLETE: '[data-action="complete"]',
|
||||
CHANGE_BULK_ACTION: '[id="bulk-action"]',
|
||||
CONFIRM_BULK_ACTION: '[id="confirm-bulk-action"]',
|
||||
SELECT_ALL: '[data-action="selectall"]'
|
||||
};
|
||||
|
||||
/**
|
||||
* List of available bulk actions.
|
||||
*
|
||||
* @type {{APPROVE: number}}
|
||||
* @type {{DENY: number}}
|
||||
*/
|
||||
var BULK_ACTIONS = {
|
||||
APPROVE: 1,
|
||||
DENY: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* List of selectors.
|
||||
*
|
||||
* @type {{SELECT_REQUEST: string}}
|
||||
*/
|
||||
var SELECTORS = {
|
||||
SELECT_REQUEST: '.selectrequests'
|
||||
};
|
||||
|
||||
/**
|
||||
* RequestActions class.
|
||||
*/
|
||||
var RequestActions = function() {
|
||||
this.registerEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
RequestActions.prototype.registerEvents = function() {
|
||||
$(ACTIONS.VIEW_REQUEST).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var requestId = $(this).data('requestid');
|
||||
|
||||
// Cancel the request.
|
||||
var params = {
|
||||
'requestid': requestId
|
||||
};
|
||||
|
||||
var request = {
|
||||
methodname: 'tool_dataprivacy_get_data_request',
|
||||
args: params
|
||||
};
|
||||
|
||||
var promises = Ajax.call([request]);
|
||||
$.when(promises[0]).then(function(data) {
|
||||
if (data.result) {
|
||||
return data.result;
|
||||
}
|
||||
// Fail.
|
||||
Notification.addNotification({
|
||||
message: data.warnings[0].message,
|
||||
type: 'error'
|
||||
});
|
||||
return false;
|
||||
|
||||
}).then(function(data) {
|
||||
// Determine the type of modal to show.
|
||||
var modalType = ModalFactory.types.DEFAULT;
|
||||
if (data.approvedeny) {
|
||||
modalType = ModalDataRequest.TYPE;
|
||||
} else if (data.canmarkcomplete) {
|
||||
modalType = ModalDataRequest.TYPE_ENQUIRY;
|
||||
}
|
||||
var body = Templates.render('tool_dataprivacy/request_details', data);
|
||||
return ModalFactory.create({
|
||||
title: data.typename,
|
||||
body: body,
|
||||
type: modalType,
|
||||
large: true
|
||||
});
|
||||
|
||||
}).then(function(modal) {
|
||||
// Handle approve event.
|
||||
modal.getRoot().on(DataPrivacyEvents.approve, function() {
|
||||
showConfirmation(DataPrivacyEvents.approve, approveEventWsData(requestId));
|
||||
});
|
||||
|
||||
// Handle deny event.
|
||||
modal.getRoot().on(DataPrivacyEvents.deny, function() {
|
||||
showConfirmation(DataPrivacyEvents.deny, denyEventWsData(requestId));
|
||||
});
|
||||
|
||||
// Handle send event.
|
||||
modal.getRoot().on(DataPrivacyEvents.complete, function() {
|
||||
var params = {
|
||||
'requestid': requestId
|
||||
};
|
||||
handleSave('tool_dataprivacy_mark_complete', params);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
// Show the modal!
|
||||
modal.show();
|
||||
|
||||
return;
|
||||
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
|
||||
$(ACTIONS.APPROVE_REQUEST).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var requestId = $(this).data('requestid');
|
||||
showConfirmation(DataPrivacyEvents.approve, approveEventWsData(requestId));
|
||||
});
|
||||
|
||||
$(ACTIONS.DENY_REQUEST).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var requestId = $(this).data('requestid');
|
||||
showConfirmation(DataPrivacyEvents.deny, denyEventWsData(requestId));
|
||||
});
|
||||
|
||||
$(ACTIONS.MARK_COMPLETE).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var requestId = $(this).data('requestid');
|
||||
showConfirmation(DataPrivacyEvents.complete, completeEventWsData(requestId));
|
||||
});
|
||||
|
||||
$(ACTIONS.CONFIRM_BULK_ACTION).click(function() {
|
||||
var requestIds = [];
|
||||
var actionEvent = '';
|
||||
var wsdata = {};
|
||||
var bulkActionKeys = [
|
||||
{
|
||||
key: 'selectbulkaction',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'selectdatarequests',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'ok'
|
||||
}
|
||||
];
|
||||
|
||||
var bulkaction = parseInt($('#bulk-action').val());
|
||||
|
||||
if (bulkaction != BULK_ACTIONS.APPROVE && bulkaction != BULK_ACTIONS.DENY) {
|
||||
Str.get_strings(bulkActionKeys).done(function(langStrings) {
|
||||
Notification.alert('', langStrings[0], langStrings[2]);
|
||||
}).fail(Notification.exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$(".selectrequests:checked").each(function() {
|
||||
requestIds.push($(this).val());
|
||||
});
|
||||
|
||||
if (requestIds.length < 1) {
|
||||
Str.get_strings(bulkActionKeys).done(function(langStrings) {
|
||||
Notification.alert('', langStrings[1], langStrings[2]);
|
||||
}).fail(Notification.exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (bulkaction) {
|
||||
case BULK_ACTIONS.APPROVE:
|
||||
actionEvent = DataPrivacyEvents.bulkApprove;
|
||||
wsdata = bulkApproveEventWsData(requestIds);
|
||||
break;
|
||||
case BULK_ACTIONS.DENY:
|
||||
actionEvent = DataPrivacyEvents.bulkDeny;
|
||||
wsdata = bulkDenyEventWsData(requestIds);
|
||||
}
|
||||
|
||||
showConfirmation(actionEvent, wsdata);
|
||||
});
|
||||
|
||||
$(ACTIONS.SELECT_ALL).change(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var selectAll = $(this).is(':checked');
|
||||
$(SELECTORS.SELECT_REQUEST).prop('checked', selectAll);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the webservice data for the approve request action.
|
||||
*
|
||||
* @param {Number} requestId The ID of the request.
|
||||
* @return {Object}
|
||||
*/
|
||||
function approveEventWsData(requestId) {
|
||||
return {
|
||||
'wsfunction': 'tool_dataprivacy_approve_data_request',
|
||||
'wsparams': {'requestid': requestId}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the webservice data for the bulk approve request action.
|
||||
*
|
||||
* @param {Array} requestIds The array of request ID's.
|
||||
* @return {Object}
|
||||
*/
|
||||
function bulkApproveEventWsData(requestIds) {
|
||||
return {
|
||||
'wsfunction': 'tool_dataprivacy_bulk_approve_data_requests',
|
||||
'wsparams': {'requestids': requestIds}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the webservice data for the deny request action.
|
||||
*
|
||||
* @param {Number} requestId The ID of the request.
|
||||
* @return {Object}
|
||||
*/
|
||||
function denyEventWsData(requestId) {
|
||||
return {
|
||||
'wsfunction': 'tool_dataprivacy_deny_data_request',
|
||||
'wsparams': {'requestid': requestId}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the webservice data for the bulk deny request action.
|
||||
*
|
||||
* @param {Array} requestIds The array of request ID's.
|
||||
* @return {Object}
|
||||
*/
|
||||
function bulkDenyEventWsData(requestIds) {
|
||||
return {
|
||||
'wsfunction': 'tool_dataprivacy_bulk_deny_data_requests',
|
||||
'wsparams': {'requestids': requestIds}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the webservice data for the complete request action.
|
||||
*
|
||||
* @param {Number} requestId The ID of the request.
|
||||
* @return {Object}
|
||||
*/
|
||||
function completeEventWsData(requestId) {
|
||||
return {
|
||||
'wsfunction': 'tool_dataprivacy_mark_complete',
|
||||
'wsparams': {'requestid': requestId}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the confirmation dialogue.
|
||||
*
|
||||
* @param {String} action The action name.
|
||||
* @param {Object} wsdata Object containing ws data.
|
||||
*/
|
||||
function showConfirmation(action, wsdata) {
|
||||
var keys = [];
|
||||
|
||||
switch (action) {
|
||||
case DataPrivacyEvents.approve:
|
||||
keys = [
|
||||
{
|
||||
key: 'approverequest',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'confirmapproval',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
break;
|
||||
case DataPrivacyEvents.bulkApprove:
|
||||
keys = [
|
||||
{
|
||||
key: 'bulkapproverequests',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'confirmbulkapproval',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
break;
|
||||
case DataPrivacyEvents.deny:
|
||||
keys = [
|
||||
{
|
||||
key: 'denyrequest',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'confirmdenial',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
break;
|
||||
case DataPrivacyEvents.bulkDeny:
|
||||
keys = [
|
||||
{
|
||||
key: 'bulkdenyrequests',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'confirmbulkdenial',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
break;
|
||||
case DataPrivacyEvents.complete:
|
||||
keys = [
|
||||
{
|
||||
key: 'markcomplete',
|
||||
component: 'tool_dataprivacy'
|
||||
},
|
||||
{
|
||||
key: 'confirmcompletion',
|
||||
component: 'tool_dataprivacy'
|
||||
}
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
var modalTitle = '';
|
||||
Str.get_strings(keys).then(function(langStrings) {
|
||||
modalTitle = langStrings[0];
|
||||
var confirmMessage = langStrings[1];
|
||||
return ModalFactory.create({
|
||||
title: modalTitle,
|
||||
body: confirmMessage,
|
||||
type: ModalFactory.types.SAVE_CANCEL
|
||||
});
|
||||
}).then(function(modal) {
|
||||
modal.setSaveButtonText(modalTitle);
|
||||
|
||||
// Handle save event.
|
||||
modal.getRoot().on(ModalEvents.save, function() {
|
||||
handleSave(wsdata.wsfunction, wsdata.wsparams);
|
||||
});
|
||||
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
modal.show();
|
||||
|
||||
return;
|
||||
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a web service function and reloads the page on success and shows a notification.
|
||||
* Displays an error notification, otherwise.
|
||||
*
|
||||
* @param {String} wsfunction The web service function to call.
|
||||
* @param {Object} params The parameters for the web service functoon.
|
||||
*/
|
||||
function handleSave(wsfunction, params) {
|
||||
// Confirm the request.
|
||||
var request = {
|
||||
methodname: wsfunction,
|
||||
args: params
|
||||
};
|
||||
|
||||
Ajax.call([request])[0].done(function(data) {
|
||||
if (data.result) {
|
||||
// On success, reload the page so that the data request table will be updated.
|
||||
// TODO: Probably in the future, better to reload the table or the target data request via AJAX.
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Add the notification.
|
||||
Notification.addNotification({
|
||||
message: data.warnings[0].message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}).fail(Notification.exception);
|
||||
}
|
||||
|
||||
return RequestActions;
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This page lets users manage categories.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$url = new moodle_url("/admin/tool/dataprivacy/categories.php");
|
||||
$title = get_string('editcategories', 'tool_dataprivacy');
|
||||
|
||||
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
|
||||
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->header();
|
||||
echo $output->heading($title);
|
||||
|
||||
$categories = \tool_dataprivacy\api::get_categories();
|
||||
$renderable = new \tool_dataprivacy\output\categories($categories);
|
||||
|
||||
echo $output->render($renderable);
|
||||
echo $output->footer();
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
<?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 for loading/storing data categories from the DB.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
/**
|
||||
* Class for loading/storing data categories from the DB.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class category extends \core\persistent {
|
||||
|
||||
/**
|
||||
* Database table.
|
||||
*/
|
||||
const TABLE = 'tool_dataprivacy_category';
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return array(
|
||||
'name' => array(
|
||||
'type' => PARAM_TEXT,
|
||||
'description' => 'The category name.',
|
||||
),
|
||||
'description' => array(
|
||||
'type' => PARAM_RAW,
|
||||
'description' => 'The category description.',
|
||||
'null' => NULL_ALLOWED,
|
||||
'default' => '',
|
||||
),
|
||||
'descriptionformat' => array(
|
||||
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
|
||||
'type' => PARAM_INT,
|
||||
'default' => FORMAT_HTML
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this category used?.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function is_used() {
|
||||
|
||||
if (\tool_dataprivacy\contextlevel::is_category_used($this->get('id')) ||
|
||||
\tool_dataprivacy\context_instance::is_category_used($this->get('id'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$pluginconfig = get_config('tool_dataprivacy');
|
||||
$levels = \context_helper::get_all_levels();
|
||||
foreach ($levels as $level => $classname) {
|
||||
|
||||
list($unused, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
|
||||
if (!empty($pluginconfig->{$categoryvar}) && $pluginconfig->{$categoryvar} == $this->get('id')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?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 for loading/storing context instances data from the DB.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class for loading/storing context instances data from the DB.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class context_instance extends \core\persistent {
|
||||
|
||||
/**
|
||||
* Database table.
|
||||
*/
|
||||
const TABLE = 'tool_dataprivacy_ctxinstance';
|
||||
|
||||
/**
|
||||
* Not set value.
|
||||
*/
|
||||
const NOTSET = 0;
|
||||
|
||||
/**
|
||||
* Inherit value.
|
||||
*/
|
||||
const INHERIT = -1;
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return array(
|
||||
'contextid' => array(
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The context id.',
|
||||
),
|
||||
'purposeid' => array(
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The purpose id.',
|
||||
'null' => NULL_ALLOWED,
|
||||
),
|
||||
'categoryid' => array(
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The category id.',
|
||||
'null' => NULL_ALLOWED,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance by contextid.
|
||||
*
|
||||
* @param mixed $contextid
|
||||
* @param mixed $exception
|
||||
* @return null
|
||||
*/
|
||||
public static function get_record_by_contextid($contextid, $exception = true) {
|
||||
global $DB;
|
||||
|
||||
if (!$record = $DB->get_record(self::TABLE, array('contextid' => $contextid))) {
|
||||
if (!$exception) {
|
||||
return false;
|
||||
} else {
|
||||
throw new \dml_missing_record_exception(self::TABLE);
|
||||
}
|
||||
}
|
||||
|
||||
return new static(0, $record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided purpose used by any context instance?
|
||||
*
|
||||
* @param int $purposeid
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_purpose_used($purposeid) {
|
||||
global $DB;
|
||||
return $DB->record_exists(self::TABLE, array('purposeid' => $purposeid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided category used by any context instance?
|
||||
*
|
||||
* @param int $categoryid
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_category_used($categoryid) {
|
||||
global $DB;
|
||||
return $DB->record_exists(self::TABLE, array('categoryid' => $categoryid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?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 for loading/storing context level data from the DB.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class for loading/storing context level data from the DB.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class contextlevel extends \core\persistent {
|
||||
|
||||
/**
|
||||
* Database table.
|
||||
*/
|
||||
const TABLE = 'tool_dataprivacy_ctxlevel';
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return array(
|
||||
'contextlevel' => array(
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The context level.',
|
||||
),
|
||||
'purposeid' => array(
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The purpose id.',
|
||||
),
|
||||
'categoryid' => array(
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The category id.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance by contextlevel.
|
||||
*
|
||||
* @param mixed $contextlevel
|
||||
* @param mixed $exception
|
||||
* @return null
|
||||
*/
|
||||
public static function get_record_by_contextlevel($contextlevel, $exception = true) {
|
||||
global $DB;
|
||||
|
||||
$cache = \cache::make('tool_dataprivacy', 'contextlevel');
|
||||
if ($data = $cache->get($contextlevel)) {
|
||||
return new static(0, $data);
|
||||
}
|
||||
|
||||
if (!$record = $DB->get_record(self::TABLE, array('contextlevel' => $contextlevel))) {
|
||||
if (!$exception) {
|
||||
return false;
|
||||
} else {
|
||||
throw new \dml_missing_record_exception(self::TABLE);
|
||||
}
|
||||
}
|
||||
|
||||
return new static(0, $record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided purpose used by any contextlevel?
|
||||
*
|
||||
* @param int $purposeid
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_purpose_used($purposeid) {
|
||||
global $DB;
|
||||
return $DB->record_exists(self::TABLE, array('purposeid' => $purposeid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the provided category used by any contextlevel?
|
||||
*
|
||||
* @param int $categoryid
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_category_used($categoryid) {
|
||||
global $DB;
|
||||
return $DB->record_exists(self::TABLE, array('categoryid' => $categoryid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the new record to the cache.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function after_create() {
|
||||
$cache = \cache::make('tool_dataprivacy', 'contextlevel');
|
||||
$cache->set($this->get('contextlevel'), $this->to_record());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cache record.
|
||||
*
|
||||
* @param bool $result
|
||||
* @return null
|
||||
*/
|
||||
protected function after_update($result) {
|
||||
$cache = \cache::make('tool_dataprivacy', 'contextlevel');
|
||||
$cache->set($this->get('contextlevel'), $this->to_record());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unnecessary stuff from db.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function before_delete() {
|
||||
$cache = \cache::make('tool_dataprivacy', 'contextlevel');
|
||||
$cache->delete($this->get('contextlevel'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the contextlist persistent.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\persistent;
|
||||
|
||||
/**
|
||||
* The contextlist persistent.
|
||||
*
|
||||
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class contextlist extends persistent {
|
||||
|
||||
/** The table name this persistent object maps to. */
|
||||
const TABLE = 'tool_dataprivacy_contextlist';
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return [
|
||||
'component' => [
|
||||
'type' => PARAM_TEXT
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new contextlist persistent from an instance of \core_privacy\local\request\contextlist.
|
||||
*
|
||||
* @param \core_privacy\local\request\contextlist $contextlist the core privacy contextlist.
|
||||
* @return contextlist a contextlist persistent.
|
||||
*/
|
||||
public static function from_contextlist(\core_privacy\local\request\contextlist $contextlist) {
|
||||
$contextlistpersistent = new contextlist();
|
||||
return $contextlistpersistent->set('component', $contextlist->get_component());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the contextlist_context persistent.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\persistent;
|
||||
|
||||
/**
|
||||
* The contextlist_context persistent.
|
||||
*
|
||||
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class contextlist_context extends persistent {
|
||||
|
||||
/** The table name this persistent object maps to. */
|
||||
const TABLE = 'tool_dataprivacy_ctxlst_ctx';
|
||||
|
||||
/** This context is pending approval. */
|
||||
const STATUS_PENDING = 0;
|
||||
|
||||
/** This context has been approved. */
|
||||
const STATUS_APPROVED = 1;
|
||||
|
||||
/** This context has been rejected. */
|
||||
const STATUS_REJECTED = 2;
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return [
|
||||
'contextid' => [
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'contextlistid' => [
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'status' => [
|
||||
'choices' => [
|
||||
self::STATUS_PENDING,
|
||||
self::STATUS_APPROVED,
|
||||
self::STATUS_REJECTED,
|
||||
],
|
||||
'default' => self::STATUS_PENDING,
|
||||
'type' => PARAM_INT
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
<?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 registry business logic methods. Mostly internal stuff.
|
||||
*
|
||||
* All methods should be considered part of the internal tool_dataprivacy API
|
||||
* unless something different is specified.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use coding_exception;
|
||||
use core\persistent;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/coursecatlib.php');
|
||||
|
||||
/**
|
||||
* Data registry business logic methods. Mostly internal stuff.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_registry {
|
||||
/**
|
||||
* Returns purpose and category var names from a context class name
|
||||
*
|
||||
* @param string $classname The context level's class.
|
||||
* @param string $pluginname The name of the plugin associated with the context level.
|
||||
* @return string[]
|
||||
*/
|
||||
public static function var_names_from_context($classname, $pluginname = '') {
|
||||
$pluginname = trim($pluginname);
|
||||
if (!empty($pluginname)) {
|
||||
$categoryvar = $classname . '_' . $pluginname . '_category';
|
||||
$purposevar = $classname . '_' . $pluginname . '_purpose';
|
||||
} else {
|
||||
$categoryvar = $classname . '_category';
|
||||
$purposevar = $classname . '_purpose';
|
||||
}
|
||||
return [
|
||||
$purposevar,
|
||||
$categoryvar
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default purpose id and category id for the provided context level.
|
||||
*
|
||||
* The caller code is responsible of checking that $contextlevel is an integer.
|
||||
*
|
||||
* @param int $contextlevel The context level.
|
||||
* @param string $pluginname The name of the plugin associated with the context level.
|
||||
* @return int[]|false[]
|
||||
*/
|
||||
public static function get_defaults($contextlevel, $pluginname = '') {
|
||||
$classname = \context_helper::get_class_for_level($contextlevel);
|
||||
list($purposevar, $categoryvar) = self::var_names_from_context($classname, $pluginname);
|
||||
|
||||
$purposeid = get_config('tool_dataprivacy', $purposevar);
|
||||
$categoryid = get_config('tool_dataprivacy', $categoryvar);
|
||||
|
||||
if (!empty($pluginname)) {
|
||||
list($purposevar, $categoryvar) = self::var_names_from_context($classname);
|
||||
// If the plugin-level doesn't have a default purpose set, try the context level.
|
||||
if ($purposeid == false) {
|
||||
$purposeid = get_config('tool_dataprivacy', $purposevar);
|
||||
}
|
||||
|
||||
// If the plugin-level doesn't have a default category set, try the context level.
|
||||
if ($categoryid == false) {
|
||||
$categoryid = get_config('tool_dataprivacy', $categoryvar);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($purposeid)) {
|
||||
$purposeid = context_instance::NOTSET;
|
||||
}
|
||||
if (empty($categoryid)) {
|
||||
$categoryid = context_instance::NOTSET;
|
||||
}
|
||||
|
||||
return [$purposeid, $categoryid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Are data registry defaults set?
|
||||
*
|
||||
* At least the system defaults need to be set.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function defaults_set() {
|
||||
list($purposeid, $categoryid) = self::get_defaults(CONTEXT_SYSTEM);
|
||||
if (empty($purposeid) || empty($categoryid)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all site categories that are visible to the current user.
|
||||
*
|
||||
* @return \coursecat[]
|
||||
*/
|
||||
public static function get_site_categories() {
|
||||
global $DB;
|
||||
|
||||
if (method_exists('\coursecat', 'get_all')) {
|
||||
$categories = \coursecat::get_all(['returnhidden' => true]);
|
||||
} else {
|
||||
// Fallback (to be removed once this gets integrated into master).
|
||||
$ids = $DB->get_fieldset_select('course_categories', 'id', '');
|
||||
$categories = \coursecat::get_many($ids);
|
||||
}
|
||||
|
||||
foreach ($categories as $key => $category) {
|
||||
if (!$category->is_uservisible()) {
|
||||
unset($categories[$key]);
|
||||
}
|
||||
}
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the roles assigned to the provided level.
|
||||
*
|
||||
* Important to note that it returns course-level assigned roles
|
||||
* if the provided context level is below course.
|
||||
*
|
||||
* @param \context $context
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subject_scope(\context $context) {
|
||||
|
||||
if ($contextcourse = $context->get_course_context(false)) {
|
||||
// Below course level we look at module or block level roles + course-assigned roles.
|
||||
$courseroles = get_roles_with_assignment_on_context($contextcourse);
|
||||
$roles = $courseroles + get_roles_with_assignment_on_context($context);
|
||||
} else {
|
||||
// We list category + system for others (we don't work with user instances so no need to work about them).
|
||||
$roles = get_roles_used_in_context($context);
|
||||
}
|
||||
|
||||
return array_map(function($role) {
|
||||
if ($role->name) {
|
||||
return $role->name;
|
||||
} else {
|
||||
return $role->shortname;
|
||||
}
|
||||
}, $roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective value given a context instance
|
||||
*
|
||||
* @param \context $context
|
||||
* @param string $element 'category' or 'purpose'
|
||||
* @param int|false $forcedvalue Use this value as if this was this context instance value.
|
||||
* @return persistent|false It return a 'purpose' instance or a 'category' instance, depending on $element
|
||||
*/
|
||||
public static function get_effective_context_value(\context $context, $element, $forcedvalue = false) {
|
||||
global $DB;
|
||||
|
||||
if ($element !== 'purpose' && $element !== 'category') {
|
||||
throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
|
||||
}
|
||||
$fieldname = $element . 'id';
|
||||
|
||||
if (!empty($forcedvalue) && ($forcedvalue == context_instance::INHERIT)) {
|
||||
// Do not include the current context when calculating the value.
|
||||
// This has the effect that an inheritted value is calculated.
|
||||
$parentcontextids = $context->get_parent_context_ids(false);
|
||||
} else if (!empty($forcedvalue) && ($forcedvalue != context_instance::NOTSET)) {
|
||||
return self::get_element_instance($element, $forcedvalue);
|
||||
} else {
|
||||
// Fetch all parent contexts, including self.
|
||||
$parentcontextids = $context->get_parent_context_ids(true);
|
||||
}
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED);
|
||||
$inparams['contextmodule'] = CONTEXT_MODULE;
|
||||
|
||||
if ('purpose' === $element) {
|
||||
$elementjoin = 'LEFT JOIN {tool_dataprivacy_purpose} ele ON ctxins.purposeid = ele.id';
|
||||
$elementfields = purpose::get_sql_fields('ele', 'ele');
|
||||
} else {
|
||||
$elementjoin = 'LEFT JOIN {tool_dataprivacy_category} ele ON ctxins.categoryid = ele.id';
|
||||
$elementfields = category::get_sql_fields('ele', 'ele');
|
||||
}
|
||||
$contextfields = \context_helper::get_preload_record_columns_sql('ctx');
|
||||
$fields = implode(', ', ['ctx.id', 'm.name AS modname', $contextfields, $elementfields]);
|
||||
|
||||
$sql = "SELECT $fields
|
||||
FROM {context} ctx
|
||||
LEFT JOIN {tool_dataprivacy_ctxinstance} ctxins ON ctx.id = ctxins.contextid
|
||||
LEFT JOIN {course_modules} cm ON ctx.contextlevel = :contextmodule AND ctx.instanceid = cm.id
|
||||
LEFT JOIN {modules} m ON m.id = cm.module
|
||||
{$elementjoin}
|
||||
WHERE ctx.id {$insql}
|
||||
ORDER BY ctx.path DESC";
|
||||
$contextinstances = $DB->get_records_sql($sql, $inparams);
|
||||
|
||||
// Check whether this context is a user context, or a child of a user context.
|
||||
// All children of a User context share the same context and cannot be set individually.
|
||||
foreach ($contextinstances as $record) {
|
||||
\context_helper::preload_from_record($record);
|
||||
$parent = \context::instance_by_id($record->id, false);
|
||||
|
||||
if ($parent->contextlevel == CONTEXT_USER) {
|
||||
// Use the context level value for the user.
|
||||
return self::get_effective_contextlevel_value(CONTEXT_USER, $element);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($contextinstances as $record) {
|
||||
$parent = \context::instance_by_id($record->id, false);
|
||||
|
||||
$checkcontextlevel = false;
|
||||
if (empty($record->eleid)) {
|
||||
$checkcontextlevel = true;
|
||||
}
|
||||
|
||||
if (!empty($forcedvalue) && context_instance::NOTSET == $forcedvalue) {
|
||||
$checkcontextlevel = true;
|
||||
}
|
||||
|
||||
if ($checkcontextlevel) {
|
||||
// Check for a value at the contextlevel
|
||||
$forplugin = empty($record->modname) ? '' : $record->modname;
|
||||
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
|
||||
$parent->contextlevel, false, false, $forplugin);
|
||||
|
||||
$instancevalue = $$fieldname;
|
||||
|
||||
if (context_instance::NOTSET != $instancevalue && context_instance::INHERIT != $instancevalue) {
|
||||
// There is an actual value. Return it.
|
||||
return self::get_element_instance($element, $instancevalue);
|
||||
}
|
||||
} else {
|
||||
$elementclass = "\\tool_dataprivacy\\{$element}";
|
||||
$instance = new $elementclass(null, $elementclass::extract_record($record, 'ele'));
|
||||
$instance->validate();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
||||
throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective value for a context level.
|
||||
*
|
||||
* Note that this is different from the effective default context level
|
||||
* (see get_effective_default_contextlevel_purpose_and_category) as this is returning
|
||||
* the value set in the data registry, not in the defaults page.
|
||||
*
|
||||
* @param int $contextlevel
|
||||
* @param string $element 'category' or 'purpose'
|
||||
* @return \tool_dataprivacy\purpose|false
|
||||
*/
|
||||
public static function get_effective_contextlevel_value($contextlevel, $element) {
|
||||
if ($element !== 'purpose' && $element !== 'category') {
|
||||
throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
|
||||
}
|
||||
$fieldname = $element . 'id';
|
||||
|
||||
if ($contextlevel != CONTEXT_SYSTEM && $contextlevel != CONTEXT_USER) {
|
||||
throw new \coding_exception('Only context_system and context_user values can be retrieved, no other context levels ' .
|
||||
'have a purpose or a category.');
|
||||
}
|
||||
|
||||
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
|
||||
|
||||
// Note: The $$fieldname points to either $purposeid, or $categoryid.
|
||||
if (context_instance::NOTSET != $$fieldname && context_instance::INHERIT != $$fieldname) {
|
||||
// There is a specific value set.
|
||||
return self::get_element_instance($element, $$fieldname);
|
||||
}
|
||||
|
||||
throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective default purpose and category for a context level.
|
||||
*
|
||||
* @param int $contextlevel
|
||||
* @param int|bool $forcedpurposevalue Use this value as if this was this context level purpose.
|
||||
* @param int|bool $forcedcategoryvalue Use this value as if this was this context level category.
|
||||
* @param string $component The name of the component to check.
|
||||
* @return int[]
|
||||
*/
|
||||
public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false,
|
||||
$forcedcategoryvalue = false, $component = '') {
|
||||
// Get the defaults for this context level.
|
||||
list($purposeid, $categoryid) = self::get_defaults($contextlevel, $component);
|
||||
|
||||
// Honour forced values.
|
||||
if ($forcedpurposevalue) {
|
||||
$purposeid = $forcedpurposevalue;
|
||||
}
|
||||
if ($forcedcategoryvalue) {
|
||||
$categoryid = $forcedcategoryvalue;
|
||||
}
|
||||
|
||||
if ($contextlevel == CONTEXT_USER) {
|
||||
// Only user context levels inherit from a parent context level.
|
||||
list($parentpurposeid, $parentcategoryid) = self::get_defaults(CONTEXT_SYSTEM);
|
||||
|
||||
if (context_instance::INHERIT == $purposeid || context_instance::NOTSET == $purposeid) {
|
||||
$purposeid = (int)$parentpurposeid;
|
||||
}
|
||||
|
||||
if (context_instance::INHERIT == $categoryid || context_instance::NOTSET == $categoryid) {
|
||||
$categoryid = $parentcategoryid;
|
||||
}
|
||||
}
|
||||
|
||||
return [$purposeid, $categoryid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the provided element.
|
||||
*
|
||||
* @throws \coding_exception
|
||||
* @param string $element The element name 'purpose' or 'category'
|
||||
* @param int $id The element id
|
||||
* @return \core\persistent
|
||||
*/
|
||||
private static function get_element_instance($element, $id) {
|
||||
if ($element !== 'purpose' && $element !== 'category') {
|
||||
throw new coding_exception('No other elements than purpose and category are allowed');
|
||||
}
|
||||
|
||||
$classname = '\tool_dataprivacy\\' . $element;
|
||||
return new $classname($id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?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 for loading/storing data requests from the DB.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\persistent;
|
||||
|
||||
/**
|
||||
* Class for loading/storing competencies from the DB.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_request extends persistent {
|
||||
|
||||
/** The table name this persistent object maps to. */
|
||||
const TABLE = 'tool_dataprivacy_request';
|
||||
|
||||
/** Data request created manually. */
|
||||
const DATAREQUEST_CREATION_MANUAL = 0;
|
||||
|
||||
/** Data request created automatically. */
|
||||
const DATAREQUEST_CREATION_AUTO = 1;
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return [
|
||||
'type' => [
|
||||
'choices' => [
|
||||
api::DATAREQUEST_TYPE_EXPORT,
|
||||
api::DATAREQUEST_TYPE_DELETE,
|
||||
api::DATAREQUEST_TYPE_OTHERS,
|
||||
],
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'comments' => [
|
||||
'type' => PARAM_TEXT,
|
||||
'default' => ''
|
||||
],
|
||||
'commentsformat' => [
|
||||
'choices' => [
|
||||
FORMAT_HTML,
|
||||
FORMAT_MOODLE,
|
||||
FORMAT_PLAIN,
|
||||
FORMAT_MARKDOWN
|
||||
],
|
||||
'type' => PARAM_INT,
|
||||
'default' => FORMAT_PLAIN
|
||||
],
|
||||
'userid' => [
|
||||
'default' => 0,
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'requestedby' => [
|
||||
'default' => 0,
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'status' => [
|
||||
'default' => api::DATAREQUEST_STATUS_PENDING,
|
||||
'choices' => [
|
||||
api::DATAREQUEST_STATUS_PENDING,
|
||||
api::DATAREQUEST_STATUS_PREPROCESSING,
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL,
|
||||
api::DATAREQUEST_STATUS_APPROVED,
|
||||
api::DATAREQUEST_STATUS_PROCESSING,
|
||||
api::DATAREQUEST_STATUS_COMPLETE,
|
||||
api::DATAREQUEST_STATUS_CANCELLED,
|
||||
api::DATAREQUEST_STATUS_REJECTED,
|
||||
api::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
api::DATAREQUEST_STATUS_EXPIRED,
|
||||
api::DATAREQUEST_STATUS_DELETED,
|
||||
],
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'dpo' => [
|
||||
'default' => 0,
|
||||
'type' => PARAM_INT,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'dpocomment' => [
|
||||
'default' => '',
|
||||
'type' => PARAM_TEXT,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'dpocommentformat' => [
|
||||
'choices' => [
|
||||
FORMAT_HTML,
|
||||
FORMAT_MOODLE,
|
||||
FORMAT_PLAIN,
|
||||
FORMAT_MARKDOWN
|
||||
],
|
||||
'type' => PARAM_INT,
|
||||
'default' => FORMAT_PLAIN
|
||||
],
|
||||
'creationmethod' => [
|
||||
'default' => self::DATAREQUEST_CREATION_MANUAL,
|
||||
'choices' => [
|
||||
self::DATAREQUEST_CREATION_MANUAL,
|
||||
self::DATAREQUEST_CREATION_AUTO
|
||||
],
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a completed data export request has expired.
|
||||
* The response will be valid regardless of the expiry scheduled task having run.
|
||||
*
|
||||
* @param data_request $request the data request object whose expiry will be checked.
|
||||
* @return bool true if the request has expired.
|
||||
*/
|
||||
public static function is_expired(data_request $request) {
|
||||
$result = false;
|
||||
|
||||
// Only export requests expire.
|
||||
if ($request->get('type') == api::DATAREQUEST_TYPE_EXPORT) {
|
||||
switch ($request->get('status')) {
|
||||
// Expired requests are obviously expired.
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$result = true;
|
||||
break;
|
||||
// Complete requests are expired if the expiry time has elapsed.
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
if ($expiryseconds > 0 && time() >= ($request->get('timemodified') + $expiryseconds)) {
|
||||
$result = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Fetch completed data requests which are due to expire.
|
||||
*
|
||||
* @param int $userid Optional user ID to filter by.
|
||||
*
|
||||
* @return array Details of completed requests which are due to expire.
|
||||
*/
|
||||
public static function get_expired_requests($userid = 0) {
|
||||
global $DB;
|
||||
|
||||
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
$expirytime = strtotime("-{$expiryseconds} second");
|
||||
$table = self::TABLE;
|
||||
$sqlwhere = 'type = :export_type AND status = :completestatus AND timemodified <= :expirytime';
|
||||
$params = array(
|
||||
'export_type' => api::DATAREQUEST_TYPE_EXPORT,
|
||||
'completestatus' => api::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
'expirytime' => $expirytime,
|
||||
);
|
||||
$sort = 'id';
|
||||
$fields = 'id, userid';
|
||||
|
||||
// Filter by user ID if specified.
|
||||
if ($userid > 0) {
|
||||
$sqlwhere .= ' AND (userid = :userid OR requestedby = :requestedby)';
|
||||
$params['userid'] = $userid;
|
||||
$params['requestedby'] = $userid;
|
||||
}
|
||||
|
||||
return $DB->get_records_select_menu($table, $sqlwhere, $params, $sort, $fields, 0, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a given set of data requests.
|
||||
* Update request status and delete the files.
|
||||
*
|
||||
* @param array $expiredrequests [requestid => userid]
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function expire($expiredrequests) {
|
||||
global $DB;
|
||||
|
||||
$ids = array_keys($expiredrequests);
|
||||
|
||||
if (count($ids) > 0) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($ids);
|
||||
$initialparams = array(api::DATAREQUEST_STATUS_EXPIRED, time());
|
||||
$params = array_merge($initialparams, $inparams);
|
||||
|
||||
$update = "UPDATE {" . self::TABLE . "}
|
||||
SET status = ?, timemodified = ?
|
||||
WHERE id $insql";
|
||||
|
||||
if ($DB->execute($update, $params)) {
|
||||
$fs = get_file_storage();
|
||||
|
||||
foreach ($expiredrequests as $id => $userid) {
|
||||
$usercontext = \context_user::instance($userid);
|
||||
$fs->delete_area_files($usercontext->id, 'tool_dataprivacy', 'export', $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Event observers supported by this module.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\event;
|
||||
|
||||
use \tool_dataprivacy\api;
|
||||
use \tool_dataprivacy\data_request;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Event observers supported by this module.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_deleted_observer {
|
||||
|
||||
/**
|
||||
* Create user data deletion request when the user is deleted.
|
||||
*
|
||||
* @param \core\event\user_deleted $event
|
||||
*/
|
||||
public static function create_delete_data_request(\core\event\user_deleted $event) {
|
||||
// Automatic creation of deletion requests must be enabled.
|
||||
if (get_config('tool_dataprivacy', 'automaticdeletionrequests')) {
|
||||
$requesttypes = [api::DATAREQUEST_TYPE_DELETE];
|
||||
$requeststatuses = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DELETED];
|
||||
|
||||
$hasongoingdeleterequests = api::has_ongoing_request($event->objectid, $requesttypes[0]);
|
||||
$hascompleteddeleterequest = (api::get_data_requests_count($event->objectid, $requeststatuses,
|
||||
$requesttypes) > 0) ? true : false;
|
||||
|
||||
if (!$hasongoingdeleterequests && !$hascompleteddeleterequest) {
|
||||
api::create_data_request($event->objectid, $requesttypes[0],
|
||||
get_string('datarequestcreatedupondelete', 'tool_dataprivacy'),
|
||||
data_request::DATAREQUEST_CREATION_AUTO);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?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 that represents an expired context.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
use dml_exception;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class that represents an expired context.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class expired_context extends \core\persistent {
|
||||
|
||||
/**
|
||||
* Database table.
|
||||
*/
|
||||
const TABLE = 'tool_dataprivacy_ctxexpired';
|
||||
|
||||
/**
|
||||
* Expired contexts with no delete action scheduled.
|
||||
*/
|
||||
const STATUS_EXPIRED = 0;
|
||||
|
||||
/**
|
||||
* Expired contexts approved for deletion.
|
||||
*/
|
||||
const STATUS_APPROVED = 1;
|
||||
|
||||
/**
|
||||
* Already processed expired contexts.
|
||||
*/
|
||||
const STATUS_CLEANED = 2;
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return array(
|
||||
'contextid' => array(
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The context id.',
|
||||
),
|
||||
'status' => array(
|
||||
'choices' => [
|
||||
self::STATUS_EXPIRED,
|
||||
self::STATUS_APPROVED,
|
||||
self::STATUS_CLEANED,
|
||||
],
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'The deletion status of the context.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns expired_contexts instances that match the provided level and status.
|
||||
*
|
||||
* @param int $contextlevel The context level filter criterion.
|
||||
* @param bool $status The expired context record's status.
|
||||
* @param string $sort The sort column. Must match the column name in {tool_dataprivacy_ctxexpired} table
|
||||
* @param int $offset The query offset.
|
||||
* @param int $limit The query limit.
|
||||
* @return expired_context[]
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function get_records_by_contextlevel($contextlevel = null, $status = false, $sort = 'timecreated',
|
||||
$offset = 0, $limit = 0) {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT expiredctx.*
|
||||
FROM {" . self::TABLE . "} expiredctx
|
||||
JOIN {context} ctx
|
||||
ON ctx.id = expiredctx.contextid";
|
||||
$params = [];
|
||||
$conditions = [];
|
||||
|
||||
if (!empty($contextlevel)) {
|
||||
$conditions[] = "ctx.contextlevel = :contextlevel";
|
||||
$params['contextlevel'] = intval($contextlevel);
|
||||
}
|
||||
|
||||
if ($status !== false) {
|
||||
$conditions[] = "expiredctx.status = :status";
|
||||
$params['status'] = intval($status);
|
||||
}
|
||||
|
||||
if (!empty($conditions)) {
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
}
|
||||
$sql .= " ORDER BY expiredctx.{$sort}";
|
||||
|
||||
$records = $DB->get_records_sql($sql, $params, $offset, $limit);
|
||||
|
||||
// We return class instances.
|
||||
$instances = array();
|
||||
foreach ($records as $key => $record) {
|
||||
$instances[$key] = new static(0, $record);
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of expired_contexts instances that match the provided level and status.
|
||||
*
|
||||
* @param int $contextlevel
|
||||
* @param bool $status
|
||||
* @return int
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function get_record_count_by_contextlevel($contextlevel = null, $status = false) {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT COUNT(1)
|
||||
FROM {" . self::TABLE . "} expiredctx
|
||||
JOIN {context} ctx
|
||||
ON ctx.id = expiredctx.contextid";
|
||||
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($contextlevel)) {
|
||||
$conditions[] = "ctx.contextlevel = :contextlevel";
|
||||
$params['contextlevel'] = intval($contextlevel);
|
||||
}
|
||||
|
||||
if ($status !== false) {
|
||||
$sql .= " AND expiredctx.status = :status";
|
||||
$params['status'] = intval($status);
|
||||
}
|
||||
if (!empty($conditions)) {
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
}
|
||||
|
||||
return $DB->count_records_sql($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new expired_context based on the context, and expiry_info object.
|
||||
*
|
||||
* @param \context $context
|
||||
* @param expiry_info $info
|
||||
* @return expired_context
|
||||
*/
|
||||
public static function create_from_expiry_info(\context $context, expiry_info $info) {
|
||||
$record = (object) [
|
||||
'contextid' => $context->id,
|
||||
'status' => self::STATUS_EXPIRED,
|
||||
];
|
||||
|
||||
$expiredcontext = new static(0, $record);
|
||||
$expiredcontext->save();
|
||||
|
||||
return $expiredcontext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the expired_context from an expiry_info object which relates to this context.
|
||||
*
|
||||
* @param expiry_info $info
|
||||
* @return $this
|
||||
*/
|
||||
public function update_from_expiry_info(expiry_info $info) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this expired_context record is in a state ready for deletion to actually take place.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_process_deletion() {
|
||||
return ($this->get('status') == self::STATUS_APPROVED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this expired_context record has already been cleaned.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return ($this->get('status') == self::STATUS_CLEANED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Expired contexts manager.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use core_privacy\manager;
|
||||
use tool_dataprivacy\expired_context;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Expired contexts manager.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class expired_contexts_manager {
|
||||
|
||||
/**
|
||||
* Number of deleted contexts for each scheduled task run.
|
||||
*/
|
||||
const DELETE_LIMIT = 200;
|
||||
|
||||
/** @var progress_trace The log progress tracer */
|
||||
protected $progresstracer = null;
|
||||
|
||||
/** @var manager The privacy manager */
|
||||
protected $manager = null;
|
||||
|
||||
/**
|
||||
* Flag expired contexts as expired.
|
||||
*
|
||||
* @return int[] The number of contexts flagged as expired for courses, and users.
|
||||
*/
|
||||
public function flag_expired_contexts() {
|
||||
if (!$this->check_requirements()) {
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
// Clear old and stale records first.
|
||||
static::clear_old_records();
|
||||
|
||||
$data = static::get_nested_expiry_info_for_courses();
|
||||
$coursecount = 0;
|
||||
foreach ($data as $expiryrecord) {
|
||||
if ($this->update_from_expiry_info($expiryrecord)) {
|
||||
$coursecount++;
|
||||
}
|
||||
}
|
||||
|
||||
$data = static::get_nested_expiry_info_for_user();
|
||||
$usercount = 0;
|
||||
foreach ($data as $expiryrecord) {
|
||||
if ($this->update_from_expiry_info($expiryrecord)) {
|
||||
$usercount++;
|
||||
}
|
||||
}
|
||||
|
||||
return [$coursecount, $usercount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear old and stale records.
|
||||
*/
|
||||
protected static function clear_old_records() {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT dpctx.*
|
||||
FROM {tool_dataprivacy_ctxexpired} dpctx
|
||||
LEFT JOIN {context} ctx ON ctx.id = dpctx.contextid
|
||||
WHERE ctx.id IS NULL";
|
||||
|
||||
$orphaned = $DB->get_recordset_sql($sql);
|
||||
foreach ($orphaned as $orphan) {
|
||||
$expiredcontext = new expired_context(0, $orphan);
|
||||
$expiredcontext->delete();
|
||||
}
|
||||
|
||||
// Delete any child of a user context.
|
||||
$parentpath = $DB->sql_concat('ctxuser.path', "'/%'");
|
||||
$params = [
|
||||
'contextuser' => CONTEXT_USER,
|
||||
];
|
||||
|
||||
$sql = "SELECT dpctx.*
|
||||
FROM {tool_dataprivacy_ctxexpired} dpctx
|
||||
WHERE dpctx.contextid IN (
|
||||
SELECT ctx.id
|
||||
FROM {context} ctxuser
|
||||
JOIN {context} ctx ON ctx.path LIKE {$parentpath}
|
||||
WHERE ctxuser.contextlevel = :contextuser
|
||||
)";
|
||||
$userchildren = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($userchildren as $child) {
|
||||
$expiredcontext = new expired_context(0, $child);
|
||||
$expiredcontext->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full nested set of expiry data relating to all contexts.
|
||||
*
|
||||
* @param string $contextpath A contexpath to restrict results to
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
protected static function get_nested_expiry_info($contextpath = '') {
|
||||
$coursepaths = self::get_nested_expiry_info_for_courses($contextpath);
|
||||
$userpaths = self::get_nested_expiry_info_for_user($contextpath);
|
||||
|
||||
return array_merge($coursepaths, $userpaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full nested set of expiry data relating to course-related contexts.
|
||||
*
|
||||
* @param string $contextpath A contexpath to restrict results to
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
protected static function get_nested_expiry_info_for_courses($contextpath = '') {
|
||||
global $DB;
|
||||
|
||||
$contextfields = \context_helper::get_preload_record_columns_sql('ctx');
|
||||
$expiredfields = expired_context::get_sql_fields('expiredctx', 'expiredctx');
|
||||
$purposefields = 'dpctx.purposeid';
|
||||
$coursefields = 'ctxcourse.expirydate AS expirydate';
|
||||
$fields = implode(', ', ['ctx.id', $contextfields, $expiredfields, $coursefields, $purposefields]);
|
||||
|
||||
// We want all contexts at course-dependant levels.
|
||||
$parentpath = $DB->sql_concat('ctxcourse.path', "'/%'");
|
||||
|
||||
// This SQL query returns all course-dependant contexts (including the course context)
|
||||
// which course end date already passed.
|
||||
// This is ordered by the context path in reverse order, which will give the child nodes before any parent node.
|
||||
$params = [
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
];
|
||||
$where = '';
|
||||
|
||||
if (!empty($contextpath)) {
|
||||
$where = "WHERE (ctx.path = :pathmatchexact OR ctx.path LIKE :pathmatchchildren)";
|
||||
$params['pathmatchexact'] = $contextpath;
|
||||
$params['pathmatchchildren'] = "{$contextpath}/%";
|
||||
}
|
||||
|
||||
$sql = "SELECT $fields
|
||||
FROM {context} ctx
|
||||
JOIN (
|
||||
SELECT c.enddate AS expirydate, subctx.path
|
||||
FROM {context} subctx
|
||||
JOIN {course} c
|
||||
ON subctx.contextlevel = :contextlevel
|
||||
AND subctx.instanceid = c.id
|
||||
AND c.format != 'site'
|
||||
) ctxcourse
|
||||
ON ctx.path LIKE {$parentpath} OR ctx.path = ctxcourse.path
|
||||
LEFT JOIN {tool_dataprivacy_ctxinstance} dpctx
|
||||
ON dpctx.contextid = ctx.id
|
||||
LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx
|
||||
ON ctx.id = expiredctx.contextid
|
||||
{$where}
|
||||
ORDER BY ctx.path DESC";
|
||||
|
||||
return self::get_nested_expiry_info_from_sql($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full nested set of expiry data.
|
||||
*
|
||||
* @param string $contextpath A contexpath to restrict results to
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
protected static function get_nested_expiry_info_for_user($contextpath = '') {
|
||||
global $DB;
|
||||
|
||||
$contextfields = \context_helper::get_preload_record_columns_sql('ctx');
|
||||
$expiredfields = expired_context::get_sql_fields('expiredctx', 'expiredctx');
|
||||
$purposefields = 'dpctx.purposeid';
|
||||
$userfields = 'u.lastaccess AS expirydate';
|
||||
$fields = implode(', ', ['ctx.id', $contextfields, $expiredfields, $userfields, $purposefields]);
|
||||
|
||||
// We want all contexts at user-dependant levels.
|
||||
$parentpath = $DB->sql_concat('ctxuser.path', "'/%'");
|
||||
|
||||
// This SQL query returns all user-dependant contexts (including the user context)
|
||||
// This is ordered by the context path in reverse order, which will give the child nodes before any parent node.
|
||||
$params = [
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
];
|
||||
$where = '';
|
||||
|
||||
if (!empty($contextpath)) {
|
||||
$where = "AND ctx.path = :pathmatchexact";
|
||||
$params['pathmatchexact'] = $contextpath;
|
||||
}
|
||||
|
||||
$sql = "SELECT $fields, u.deleted AS userdeleted
|
||||
FROM {context} ctx
|
||||
JOIN {user} u ON ctx.instanceid = u.id
|
||||
LEFT JOIN {tool_dataprivacy_ctxinstance} dpctx
|
||||
ON dpctx.contextid = ctx.id
|
||||
LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx
|
||||
ON ctx.id = expiredctx.contextid
|
||||
WHERE ctx.contextlevel = :contextlevel {$where}
|
||||
ORDER BY ctx.path DESC";
|
||||
|
||||
return self::get_nested_expiry_info_from_sql($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full nested set of expiry data given appropriate SQL.
|
||||
* Only contexts which have expired will be included.
|
||||
*
|
||||
* @param string $sql The SQL used to select the nested information.
|
||||
* @param array $params The params required by the SQL.
|
||||
* @return \stdClass[]
|
||||
*/
|
||||
protected static function get_nested_expiry_info_from_sql($sql, array $params) {
|
||||
global $DB;
|
||||
|
||||
$fulllist = $DB->get_recordset_sql($sql, $params);
|
||||
$datalist = [];
|
||||
$expiredcontents = [];
|
||||
$pathstoskip = [];
|
||||
foreach ($fulllist as $record) {
|
||||
\context_helper::preload_from_record($record);
|
||||
$context = \context::instance_by_id($record->id, false);
|
||||
|
||||
if (!self::is_eligible_for_deletion($pathstoskip, $context)) {
|
||||
// We should skip this context, and therefore all of it's children.
|
||||
$datalist = array_filter($datalist, function($data, $path) use ($context) {
|
||||
// Remove any child of this context.
|
||||
// Technically this should never be fulfilled because the query is ordered in path DESC, but is kept
|
||||
// in to be certain.
|
||||
return (false === strpos($path, "{$context->path}/"));
|
||||
}, ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
if ($record->expiredctxid) {
|
||||
// There was previously an expired context record.
|
||||
// Delete it to be on the safe side.
|
||||
$expiredcontext = new expired_context(null, expired_context::extract_record($record, 'expiredctx'));
|
||||
$expiredcontext->delete();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$purposevalue = $record->purposeid !== null ? $record->purposeid : context_instance::NOTSET;
|
||||
$purpose = api::get_effective_context_purpose($context, $purposevalue);
|
||||
|
||||
if ($context instanceof \context_user && !empty($record->userdeleted)) {
|
||||
$expiryinfo = static::get_expiry_info($purpose, $record->userdeleted);
|
||||
} else {
|
||||
$expiryinfo = static::get_expiry_info($purpose, $record->expirydate);
|
||||
}
|
||||
foreach ($datalist as $path => $data) {
|
||||
// Merge with already-processed children.
|
||||
if (strpos($path, $context->path) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expiryinfo->merge_with_child($data->info);
|
||||
}
|
||||
$datalist[$context->path] = (object) [
|
||||
'context' => $context,
|
||||
'record' => $record,
|
||||
'purpose' => $purpose,
|
||||
'info' => $expiryinfo,
|
||||
];
|
||||
}
|
||||
$fulllist->close();
|
||||
|
||||
return $datalist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the supplied context would be elible for deletion.
|
||||
*
|
||||
* @param array $pathstoskip A set of paths which should be skipped
|
||||
* @param \context $context
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_eligible_for_deletion(array &$pathstoskip, \context $context) {
|
||||
$shouldskip = false;
|
||||
// Check whether any of the child contexts are ineligble.
|
||||
$shouldskip = !empty(array_filter($pathstoskip, function($path) use ($context) {
|
||||
// If any child context has already been skipped then it will appear in this list.
|
||||
// Since paths include parents, test if the context under test appears as the haystack in the skipped
|
||||
// context's needle.
|
||||
return false !== (strpos($context->path, $path));
|
||||
}));
|
||||
|
||||
if (!$shouldskip && $context instanceof \context_user) {
|
||||
// The context instanceid is the user's ID.
|
||||
if (isguestuser($context->instanceid) || is_siteadmin($context->instanceid)) {
|
||||
// This is an admin, or the guest and cannot be deleted.
|
||||
$shouldskip = true;
|
||||
}
|
||||
|
||||
if (!$shouldskip) {
|
||||
$courses = enrol_get_users_courses($context->instanceid, false, ['enddate']);
|
||||
$requireenddate = self::require_all_end_dates_for_user_deletion();
|
||||
|
||||
foreach ($courses as $course) {
|
||||
if (empty($course->enddate)) {
|
||||
// This course has no end date.
|
||||
if ($requireenddate) {
|
||||
// Course end dates are required, and this course has no end date.
|
||||
$shouldskip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Course end dates are not required. The subsequent checks are pointless at this time so just
|
||||
// skip them.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($course->enddate >= time()) {
|
||||
// This course is still in the future.
|
||||
$shouldskip = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// This course has an end date which is in the past.
|
||||
if (!self::is_course_expired($course)) {
|
||||
// This course has not expired yet.
|
||||
$shouldskip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldskip) {
|
||||
// Add this to the list of contexts to skip for parentage checks.
|
||||
$pathstoskip[] = $context->path;
|
||||
}
|
||||
|
||||
return !$shouldskip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the expired contexts.
|
||||
*
|
||||
* @return int[] The number of deleted contexts.
|
||||
*/
|
||||
public function process_approved_deletions() {
|
||||
if (!$this->check_requirements()) {
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
$expiredcontexts = expired_context::get_records(['status' => expired_context::STATUS_APPROVED]);
|
||||
$totalprocessed = 0;
|
||||
$usercount = 0;
|
||||
$coursecount = 0;
|
||||
foreach ($expiredcontexts as $expiredctx) {
|
||||
$context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
|
||||
if (empty($context)) {
|
||||
// Unable to process this request further.
|
||||
// We have no context to delete.
|
||||
$expiredctx->delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->delete_expired_context($expiredctx)) {
|
||||
if ($context instanceof \context_user) {
|
||||
$usercount++;
|
||||
} else {
|
||||
$coursecount++;
|
||||
}
|
||||
|
||||
$totalprocessed++;
|
||||
if ($totalprocessed >= $this->get_delete_limit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$coursecount, $usercount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes user data from the provided context.
|
||||
*
|
||||
* @param expired_context $expiredctx
|
||||
* @return \context|false
|
||||
*/
|
||||
protected function delete_expired_context(expired_context $expiredctx) {
|
||||
$context = \context::instance_by_id($expiredctx->get('contextid'));
|
||||
|
||||
$this->get_progress()->output("Deleting context {$context->id} - " . $context->get_context_name(true, true));
|
||||
|
||||
// Update the expired_context and verify that it is still ready for deletion.
|
||||
$expiredctx = $this->update_expired_context($expiredctx);
|
||||
if (empty($expiredctx)) {
|
||||
$this->get_progress()->output("Context has changed since approval and is no longer pending approval. Skipping", 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$expiredctx->can_process_deletion()) {
|
||||
// This only happens if the record was updated after being first fetched.
|
||||
$this->get_progress()->output("Context has changed since approval and must be re-approved. Skipping", 1);
|
||||
$expiredctx->set('status', expired_context::STATUS_EXPIRED);
|
||||
$expiredctx->save();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$privacymanager = $this->get_privacy_manager();
|
||||
if ($context instanceof \context_user) {
|
||||
$this->delete_expired_user_context($expiredctx);
|
||||
} else {
|
||||
// This context is fully expired - that is that the default retention period has been reached.
|
||||
$privacymanager->delete_data_for_all_users_in_context($context);
|
||||
}
|
||||
|
||||
// Mark the record as cleaned.
|
||||
$expiredctx->set('status', expired_context::STATUS_CLEANED);
|
||||
$expiredctx->save();
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes user data from the provided user context.
|
||||
*
|
||||
* @param expired_context $expiredctx
|
||||
*/
|
||||
protected function delete_expired_user_context(expired_context $expiredctx) {
|
||||
global $DB;
|
||||
|
||||
$contextid = $expiredctx->get('contextid');
|
||||
$context = \context::instance_by_id($contextid);
|
||||
$user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
|
||||
|
||||
$privacymanager = $this->get_privacy_manager();
|
||||
|
||||
// Delete all child contexts of the user context.
|
||||
$parentpath = $DB->sql_concat('ctxuser.path', "'/%'");
|
||||
|
||||
$params = [
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'contextid' => $expiredctx->get('contextid'),
|
||||
];
|
||||
|
||||
$fields = \context_helper::get_preload_record_columns_sql('ctx');
|
||||
$sql = "SELECT ctx.id, $fields
|
||||
FROM {context} ctxuser
|
||||
JOIN {context} ctx ON ctx.path LIKE {$parentpath}
|
||||
WHERE ctxuser.contextlevel = :contextlevel AND ctxuser.id = :contextid
|
||||
ORDER BY ctx.path DESC";
|
||||
|
||||
$children = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($children as $child) {
|
||||
\context_helper::preload_from_record($child);
|
||||
$context = \context::instance_by_id($child->id);
|
||||
|
||||
$privacymanager->delete_data_for_all_users_in_context($context);
|
||||
}
|
||||
$children->close();
|
||||
|
||||
// Delete all unprotected data that the user holds.
|
||||
$approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
|
||||
$contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
|
||||
|
||||
foreach ($contextlistcollection as $contextlist) {
|
||||
$contextids = [];
|
||||
$approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
|
||||
$user,
|
||||
$contextlist->get_component(),
|
||||
$contextlist->get_contextids()
|
||||
));
|
||||
}
|
||||
$privacymanager->delete_data_for_user($approvedlistcollection, $this->get_progress());
|
||||
|
||||
// Delete the user context.
|
||||
$context = \context::instance_by_id($expiredctx->get('contextid'));
|
||||
$privacymanager->delete_data_for_all_users_in_context($context);
|
||||
|
||||
// This user is now fully expired - finish by deleting the user.
|
||||
delete_user($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether end dates are required on all courses in order for a user to be expired from them.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function require_all_end_dates_for_user_deletion() {
|
||||
$requireenddate = get_config('tool_dataprivacy', 'requireallenddatesforuserdeletion');
|
||||
|
||||
return !empty($requireenddate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the requirements to start deleting contexts are satisified.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function check_requirements() {
|
||||
if (!data_registry::defaults_set()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a date is beyond the specified period.
|
||||
*
|
||||
* @param string $period The Expiry Period
|
||||
* @param int $comparisondate The date for comparison
|
||||
* @return bool
|
||||
*/
|
||||
protected static function has_expired($period, $comparisondate) {
|
||||
$dt = new \DateTime();
|
||||
$dt->setTimestamp($comparisondate);
|
||||
$dt->add(new \DateInterval($period));
|
||||
|
||||
return (time() >= $dt->getTimestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expiry info object for the specified purpose and comparison date.
|
||||
*
|
||||
* @param purpose $purpose The purpose of this context
|
||||
* @param int $comparisondate The date for comparison
|
||||
* @return expiry_info
|
||||
*/
|
||||
protected static function get_expiry_info(purpose $purpose, $comparisondate = 0) {
|
||||
if (empty($comparisondate)) {
|
||||
// The date is empty, therefore this context cannot be considered for automatic expiry.
|
||||
$defaultexpired = false;
|
||||
} else {
|
||||
$defaultexpired = static::has_expired($purpose->get('retentionperiod'), $comparisondate);
|
||||
}
|
||||
|
||||
return new expiry_info($defaultexpired);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update or delete the expired_context from the expiry_info object.
|
||||
* This function depends upon the data structure returned from get_nested_expiry_info.
|
||||
*
|
||||
* If the context is expired in any way, then an expired_context will be returned, otherwise null will be returned.
|
||||
*
|
||||
* @param \stdClass $expiryrecord
|
||||
* @return expired_context|null
|
||||
*/
|
||||
protected function update_from_expiry_info(\stdClass $expiryrecord) {
|
||||
if ($expiryrecord->info->is_any_expired()) {
|
||||
// The context is expired in some fashion.
|
||||
// Create or update as required.
|
||||
if ($expiryrecord->record->expiredctxid) {
|
||||
$expiredcontext = new expired_context(null, expired_context::extract_record($expiryrecord->record, 'expiredctx'));
|
||||
$expiredcontext->update_from_expiry_info($expiryrecord->info);
|
||||
|
||||
if ($expiredcontext->is_complete()) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
$expiredcontext = expired_context::create_from_expiry_info($expiryrecord->context, $expiryrecord->info);
|
||||
}
|
||||
|
||||
return $expiredcontext;
|
||||
} else {
|
||||
// The context is not expired.
|
||||
if ($expiryrecord->record->expiredctxid) {
|
||||
// There was previously an expired context record, but it is no longer relevant.
|
||||
// Delete it to be on the safe side.
|
||||
$expiredcontext = new expired_context(null, expired_context::extract_record($expiryrecord->record, 'expiredctx'));
|
||||
$expiredcontext->delete();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the expired context record.
|
||||
*
|
||||
* Note: You should use the return value as the provided value will be used to fetch data only.
|
||||
*
|
||||
* @param expired_context $expiredctx The record to update
|
||||
* @return expired_context|null
|
||||
*/
|
||||
protected function update_expired_context(expired_context $expiredctx) {
|
||||
// Fetch the context from the expired_context record.
|
||||
$context = \context::instance_by_id($expiredctx->get('contextid'));
|
||||
|
||||
// Fetch the current nested expiry data.
|
||||
$expiryrecords = self::get_nested_expiry_info($context->path);
|
||||
|
||||
// Find the current record.
|
||||
if (empty($expiryrecords[$context->path])) {
|
||||
$expiredctx->delete();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Refresh the record.
|
||||
// Note: Use the returned expiredctx.
|
||||
$expiredctx = $this->update_from_expiry_info($expiryrecords[$context->path]);
|
||||
if (empty($expiredctx)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$context instanceof \context_user) {
|
||||
// Where the target context is not a user, we check all children of the context.
|
||||
// The expiryrecords array only contains children, fetched from the get_nested_expiry_info call above.
|
||||
// No need to check that these _are_ children.
|
||||
foreach ($expiryrecords as $expiryrecord) {
|
||||
if ($expiryrecord->context->id === $context->id) {
|
||||
// This is record for the context being tested that we checked earlier.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($expiryrecord->record->expiredctxid)) {
|
||||
// There is no expired context record for this context.
|
||||
// If there is no record, then this context cannot have been approved for removal.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch the expired_context object for this record.
|
||||
// This needs to be updated from the expiry_info data too as there may be child changes to consider.
|
||||
$expiredcontext = new expired_context(null, expired_context::extract_record($expiryrecord->record, 'expiredctx'));
|
||||
$expiredcontext->update_from_expiry_info($expiryrecord->info);
|
||||
if (!$expiredcontext->is_complete()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $expiredctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the course has expired.
|
||||
*
|
||||
* @param \stdClass $course
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_course_expired(\stdClass $course) {
|
||||
$context = \context_course::instance($course->id);
|
||||
$expiryrecords = self::get_nested_expiry_info_for_courses($context->path);
|
||||
|
||||
return !empty($expiryrecords[$context->path]) && $expiryrecords[$context->path]->info->is_fully_expired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the privacy manager.
|
||||
*
|
||||
* @return manager
|
||||
*/
|
||||
protected function get_privacy_manager() {
|
||||
if (null === $this->manager) {
|
||||
$this->manager = new manager();
|
||||
$this->manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
}
|
||||
|
||||
return $this->manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the limit for the maximum number of contexts to delete in one session.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_delete_limit() {
|
||||
return self::DELETE_LIMIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the progress tracer.
|
||||
*
|
||||
* @return \progress_trace
|
||||
*/
|
||||
protected function get_progress() {
|
||||
if (null === $this->progresstracer) {
|
||||
$this->set_progress(new \text_progress_trace());
|
||||
}
|
||||
|
||||
return $this->progresstracer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific tracer for the task.
|
||||
*
|
||||
* @param \progress_trace $trace
|
||||
* @return $this
|
||||
*/
|
||||
public function set_progress(\progress_trace $trace) {
|
||||
$this->progresstracer = $trace;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Expiry Data.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use core_privacy\manager;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Expiry Data.
|
||||
*
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class expiry_info {
|
||||
|
||||
/** @var bool Whether this context is fully expired */
|
||||
protected $isexpired = false;
|
||||
|
||||
/**
|
||||
* Constructor for the expiry_info class.
|
||||
*
|
||||
* @param bool $isexpired Whether the retention period for this context has expired yet.
|
||||
*/
|
||||
public function __construct($isexpired) {
|
||||
$this->isexpired = $isexpired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this context has 'fully' expired.
|
||||
* That is to say that the default retention period has been reached, and that there are no unexpired roles.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_fully_expired() {
|
||||
return $this->isexpired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any part of this context has expired.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_any_expired() {
|
||||
if ($this->is_fully_expired()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this expiry_info object with another belonging to a child context in order to set the 'safest' heritage.
|
||||
*
|
||||
* It is not possible to delete any part of a context that is not deleted by a parent.
|
||||
* So if a course's retention policy has been reached, then only parts where the children have also expired can be
|
||||
* deleted.
|
||||
*
|
||||
* @param expiry_info $child The child record to merge with.
|
||||
* @return $this
|
||||
*/
|
||||
public function merge_with_child(expiry_info $child) {
|
||||
if ($child->is_fully_expired()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// If the child is not fully expired, then none of the parents can be either.
|
||||
$this->isexpired = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
<?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 for exporting data category.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\external;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\external\persistent_exporter;
|
||||
use tool_dataprivacy\category;
|
||||
use tool_dataprivacy\context_instance;
|
||||
|
||||
/**
|
||||
* Class for exporting field data.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class category_exporter extends persistent_exporter {
|
||||
|
||||
/**
|
||||
* Defines the persistent class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function define_class() {
|
||||
return \tool_dataprivacy\category::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return array(
|
||||
'context' => 'context',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that fetches a category name from the given ID.
|
||||
*
|
||||
* @param int $categoryid The category ID. Could be INHERIT (false, -1), NOT_SET (0), or the actual ID.
|
||||
* @return string The purpose name.
|
||||
*/
|
||||
public static function get_name($categoryid) {
|
||||
global $PAGE;
|
||||
if ($categoryid === false || $categoryid == context_instance::INHERIT) {
|
||||
return get_string('inherit', 'tool_dataprivacy');
|
||||
} else if ($categoryid == context_instance::NOTSET) {
|
||||
return get_string('notset', 'tool_dataprivacy');
|
||||
} else {
|
||||
$purpose = new category($categoryid);
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
$exporter = new self($purpose, ['context' => \context_system::instance()]);
|
||||
$data = $exporter->export($output);
|
||||
return $data->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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 for exporting context instance.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\external;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\external\persistent_exporter;
|
||||
|
||||
/**
|
||||
* Class for exporting context instance.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class context_instance_exporter extends persistent_exporter {
|
||||
|
||||
/**
|
||||
* Defines the persistent class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function define_class() {
|
||||
return \tool_dataprivacy\context_instance::class;
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
/**
|
||||
* Class for exporting user evidence with all competencies.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\external;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use core\external\persistent_exporter;
|
||||
use core_user;
|
||||
use core_user\external\user_summary_exporter;
|
||||
use dml_exception;
|
||||
use moodle_exception;
|
||||
use renderer_base;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
/**
|
||||
* Class for exporting user evidence with all competencies.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_request_exporter extends persistent_exporter {
|
||||
|
||||
/**
|
||||
* Class definition.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function define_class() {
|
||||
return data_request::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Related objects definition.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'context' => 'context',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Other properties definition.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'foruser' => [
|
||||
'type' => user_summary_exporter::read_properties_definition(),
|
||||
],
|
||||
'requestedbyuser' => [
|
||||
'type' => user_summary_exporter::read_properties_definition(),
|
||||
'optional' => true
|
||||
],
|
||||
'dpouser' => [
|
||||
'type' => user_summary_exporter::read_properties_definition(),
|
||||
'optional' => true
|
||||
],
|
||||
'messagehtml' => [
|
||||
'type' => PARAM_RAW,
|
||||
'optional' => true
|
||||
],
|
||||
'typename' => [
|
||||
'type' => PARAM_TEXT,
|
||||
],
|
||||
'typenameshort' => [
|
||||
'type' => PARAM_TEXT,
|
||||
],
|
||||
'statuslabel' => [
|
||||
'type' => PARAM_TEXT,
|
||||
],
|
||||
'statuslabelclass' => [
|
||||
'type' => PARAM_TEXT,
|
||||
],
|
||||
'canreview' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
'default' => false
|
||||
],
|
||||
'approvedeny' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
'default' => false
|
||||
],
|
||||
'canmarkcomplete' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
'default' => false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign values to the defined other properties.
|
||||
*
|
||||
* @param renderer_base $output The output renderer object.
|
||||
* @return array
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$values = [];
|
||||
|
||||
$foruserid = $this->persistent->get('userid');
|
||||
$user = core_user::get_user($foruserid, '*', MUST_EXIST);
|
||||
$userexporter = new user_summary_exporter($user);
|
||||
$values['foruser'] = $userexporter->export($output);
|
||||
|
||||
$requestedbyid = $this->persistent->get('requestedby');
|
||||
if ($requestedbyid != $foruserid) {
|
||||
$user = core_user::get_user($requestedbyid, '*', MUST_EXIST);
|
||||
$userexporter = new user_summary_exporter($user);
|
||||
$values['requestedbyuser'] = $userexporter->export($output);
|
||||
} else {
|
||||
$values['requestedbyuser'] = $values['foruser'];
|
||||
}
|
||||
|
||||
if (!empty($this->persistent->get('dpo'))) {
|
||||
$dpoid = $this->persistent->get('dpo');
|
||||
$user = core_user::get_user($dpoid, '*', MUST_EXIST);
|
||||
$userexporter = new user_summary_exporter($user);
|
||||
$values['dpouser'] = $userexporter->export($output);
|
||||
}
|
||||
|
||||
$values['messagehtml'] = text_to_html($this->persistent->get('comments'));
|
||||
|
||||
$requesttype = $this->persistent->get('type');
|
||||
$values['typename'] = helper::get_request_type_string($requesttype);
|
||||
$values['typenameshort'] = helper::get_shortened_request_type_string($requesttype);
|
||||
|
||||
$values['canreview'] = false;
|
||||
$values['approvedeny'] = false;
|
||||
$values['statuslabel'] = helper::get_request_status_string($this->persistent->get('status'));
|
||||
|
||||
switch ($this->persistent->get('status')) {
|
||||
case api::DATAREQUEST_STATUS_PENDING:
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
// Request can be manually completed for general enquiry requests.
|
||||
$values['canmarkcomplete'] = $requesttype == api::DATAREQUEST_TYPE_OTHERS;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_PREPROCESSING:
|
||||
$values['statuslabelclass'] = 'label-default';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
// DPO can review the request once it's ready.
|
||||
$values['canreview'] = true;
|
||||
// Whether the DPO can approve or deny the request.
|
||||
$values['approvedeny'] = in_array($requesttype, [api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_TYPE_DELETE]);
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_APPROVED:
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_PROCESSING:
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_COMPLETE:
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
case api::DATAREQUEST_STATUS_DELETED:
|
||||
$values['statuslabelclass'] = 'label-success';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_CANCELLED:
|
||||
$values['statuslabelclass'] = 'label-warning';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_REJECTED:
|
||||
$values['statuslabelclass'] = 'label-important';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$values['statuslabelclass'] = 'label-default';
|
||||
break;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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 for exporting an object containing a name and a description.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\external;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\external\exporter;
|
||||
|
||||
/**
|
||||
* Class that exports an object containing a name and a description.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class name_description_exporter extends exporter {
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return array(
|
||||
'context' => 'context',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return [
|
||||
'name' => [
|
||||
'type' => PARAM_TEXT,
|
||||
],
|
||||
'description' => [
|
||||
'type' => PARAM_TEXT,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?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 for exporting data purpose.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\external;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use core\external\persistent_exporter;
|
||||
use DateInterval;
|
||||
use Exception;
|
||||
use renderer_base;
|
||||
use tool_dataprivacy\context_instance;
|
||||
use tool_dataprivacy\purpose;
|
||||
|
||||
/**
|
||||
* Class for exporting field data.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class purpose_exporter extends persistent_exporter {
|
||||
|
||||
/**
|
||||
* Defines the persistent class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function define_class() {
|
||||
return purpose::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of objects that are related.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_related() {
|
||||
return array(
|
||||
'context' => 'context',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of additional properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
return [
|
||||
'formattedretentionperiod' => [
|
||||
'type' => PARAM_TEXT
|
||||
],
|
||||
'formattedlawfulbases' => [
|
||||
'type' => name_description_exporter::read_properties_definition(),
|
||||
'multiple' => true
|
||||
],
|
||||
'formattedsensitivedatareasons' => [
|
||||
'type' => name_description_exporter::read_properties_definition(),
|
||||
'multiple' => true,
|
||||
'optional' => true
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return other properties.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return array
|
||||
* @throws coding_exception
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$values = [];
|
||||
|
||||
$formattedbases = [];
|
||||
$lawfulbases = explode(',', $this->persistent->get('lawfulbases'));
|
||||
if (!empty($lawfulbases)) {
|
||||
foreach ($lawfulbases as $basis) {
|
||||
if (empty(trim($basis))) {
|
||||
continue;
|
||||
}
|
||||
$formattedbases[] = (object)[
|
||||
'name' => get_string($basis . '_name', 'tool_dataprivacy'),
|
||||
'description' => get_string($basis . '_description', 'tool_dataprivacy')
|
||||
];
|
||||
}
|
||||
}
|
||||
$values['formattedlawfulbases'] = $formattedbases;
|
||||
|
||||
$formattedsensitivereasons = [];
|
||||
$sensitivereasons = explode(',', $this->persistent->get('sensitivedatareasons'));
|
||||
if (!empty($sensitivereasons)) {
|
||||
foreach ($sensitivereasons as $reason) {
|
||||
if (empty(trim($reason))) {
|
||||
continue;
|
||||
}
|
||||
$formattedsensitivereasons[] = (object)[
|
||||
'name' => get_string($reason . '_name', 'tool_dataprivacy'),
|
||||
'description' => get_string($reason . '_description', 'tool_dataprivacy')
|
||||
];
|
||||
}
|
||||
}
|
||||
$values['formattedsensitivedatareasons'] = $formattedsensitivereasons;
|
||||
|
||||
$retentionperiod = $this->persistent->get('retentionperiod');
|
||||
if ($retentionperiod) {
|
||||
$interval = new DateInterval($retentionperiod);
|
||||
|
||||
// It is one or another.
|
||||
if ($interval->y) {
|
||||
$formattedtime = get_string('numyears', 'moodle', $interval->format('%y'));
|
||||
} else if ($interval->m) {
|
||||
$formattedtime = get_string('nummonths', 'moodle', $interval->format('%m'));
|
||||
} else if ($interval->d) {
|
||||
$formattedtime = get_string('numdays', 'moodle', $interval->format('%d'));
|
||||
} else {
|
||||
$formattedtime = get_string('retentionperiodzero', 'tool_dataprivacy');
|
||||
}
|
||||
} else {
|
||||
$formattedtime = get_string('retentionperiodnotdefined', 'tool_dataprivacy');
|
||||
}
|
||||
$values['formattedretentionperiod'] = $formattedtime;
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that fetches a purpose name from the given ID.
|
||||
*
|
||||
* @param int $purposeid The purpose ID. Could be INHERIT (false, -1), NOT_SET (0), or the actual ID.
|
||||
* @return string The purpose name.
|
||||
*/
|
||||
public static function get_name($purposeid) {
|
||||
global $PAGE;
|
||||
if ($purposeid === false || $purposeid == context_instance::INHERIT) {
|
||||
return get_string('inherit', 'tool_dataprivacy');
|
||||
} else if ($purposeid == context_instance::NOTSET) {
|
||||
return get_string('notset', 'tool_dataprivacy');
|
||||
} else {
|
||||
$purpose = new purpose($purposeid);
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
$exporter = new self($purpose, ['context' => \context_system::instance()]);
|
||||
$data = $exporter->export($output);
|
||||
return $data->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This file contains the form add/update a data category.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\form;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\form\persistent;
|
||||
|
||||
/**
|
||||
* Data category form.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class category extends persistent {
|
||||
|
||||
/**
|
||||
* @var The persistent class.
|
||||
*/
|
||||
protected static $persistentclass = 'tool_dataprivacy\\category';
|
||||
|
||||
/**
|
||||
* Define the form - called by parent constructor
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$mform->addElement('text', 'name', get_string('name'), 'maxlength="100"');
|
||||
$mform->setType('name', PARAM_TEXT);
|
||||
$mform->addRule('name', get_string('required'), 'required', null, 'server');
|
||||
$mform->addRule('name', get_string('maximumchars', '', 100), 'maxlength', 100, 'server');
|
||||
|
||||
$mform->addElement('editor', 'description', get_string('description'), null, ['autosave' => false]);
|
||||
$mform->setType('description', PARAM_CLEANHTML);
|
||||
|
||||
if (!empty($this->_customdata['showbuttons'])) {
|
||||
if (!$this->get_persistent()->get('id')) {
|
||||
$savetext = get_string('add');
|
||||
} else {
|
||||
$savetext = get_string('savechanges');
|
||||
}
|
||||
$this->add_action_buttons(true, $savetext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This file contains the form add/update context instance data.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\form;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_registry;
|
||||
use tool_dataprivacy\purpose;
|
||||
|
||||
/**
|
||||
* Context instance data form.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class context_instance extends \core\form\persistent {
|
||||
|
||||
/**
|
||||
* @var The persistent class.
|
||||
*/
|
||||
protected static $persistentclass = 'tool_dataprivacy\\context_instance';
|
||||
|
||||
/**
|
||||
* Define the form - called by parent constructor
|
||||
*/
|
||||
public function definition() {
|
||||
$this->_form->setDisableShortforms();
|
||||
|
||||
$this->_form->addElement('header', 'contextname', $this->_customdata['contextname']);
|
||||
|
||||
$subjectscope = implode(', ', $this->_customdata['subjectscope']);
|
||||
if (empty($subjectscope)) {
|
||||
$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);
|
||||
|
||||
$this->_form->addElement('hidden', 'contextid');
|
||||
$this->_form->setType('contextid', PARAM_INT);
|
||||
|
||||
parent::add_action_buttons(false, get_string('savechanges'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds purpose and category selectors.
|
||||
*
|
||||
* @param int $contextlevel Apply this context level defaults. False for no defaults.
|
||||
* @return null
|
||||
*/
|
||||
protected function add_purpose_category($contextlevel = false) {
|
||||
|
||||
$mform = $this->_form;
|
||||
|
||||
$addcategorytext = $this->get_add_element_content(get_string('addcategory', 'tool_dataprivacy'));
|
||||
$categoryselect = $mform->createElement('select', 'categoryid', null, $this->_customdata['categories']);
|
||||
$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);
|
||||
|
||||
$addpurposetext = $this->get_add_element_content(get_string('addpurpose', 'tool_dataprivacy'));
|
||||
$purposeselect = $mform->createElement('select', 'purposeid', null, $this->_customdata['purposes']);
|
||||
$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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 'add' label.
|
||||
*
|
||||
* It depends on the theme in use.
|
||||
*
|
||||
* @param string $label
|
||||
* @return \renderable|string
|
||||
*/
|
||||
private function get_add_element_content($label) {
|
||||
global $PAGE, $OUTPUT;
|
||||
|
||||
$bs4 = false;
|
||||
|
||||
$theme = $PAGE->theme;
|
||||
if ($theme->name === 'boost') {
|
||||
$bs4 = true;
|
||||
} else {
|
||||
foreach ($theme->parents as $basetheme) {
|
||||
if ($basetheme === 'boost') {
|
||||
$bs4 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bs4) {
|
||||
return $label;
|
||||
}
|
||||
return $OUTPUT->pix_icon('e/insert', $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the customdata array for the provided context instance.
|
||||
*
|
||||
* @param \context $context
|
||||
* @return array
|
||||
*/
|
||||
public static function get_context_instance_customdata(\context $context) {
|
||||
|
||||
$persistent = \tool_dataprivacy\context_instance::get_record_by_contextid($context->id, false);
|
||||
if (!$persistent) {
|
||||
$persistent = new \tool_dataprivacy\context_instance();
|
||||
$persistent->set('contextid', $context->id);
|
||||
}
|
||||
|
||||
$purposes = [];
|
||||
foreach (api::get_purposes() as $purpose) {
|
||||
$purposes[$purpose->get('id')] = $purpose;
|
||||
}
|
||||
$purposeoptions = \tool_dataprivacy\output\data_registry_page::purpose_options($purposes);
|
||||
$categoryoptions = \tool_dataprivacy\output\data_registry_page::category_options(api::get_categories());
|
||||
|
||||
$customdata = [
|
||||
'context' => $context,
|
||||
'subjectscope' => data_registry::get_subject_scope($context),
|
||||
'contextname' => $context->get_context_name(),
|
||||
'persistent' => $persistent,
|
||||
'purposes' => $purposeoptions,
|
||||
'categories' => $categoryoptions,
|
||||
];
|
||||
|
||||
$effectivepurpose = api::get_effective_context_purpose($context);
|
||||
if ($effectivepurpose) {
|
||||
|
||||
$customdata['currentretentionperiod'] = self::get_retention_display_text($effectivepurpose, $context->contextlevel,
|
||||
$context);
|
||||
|
||||
$customdata['purposeretentionperiods'] = [];
|
||||
foreach (array_keys($purposeoptions) as $optionvalue) {
|
||||
|
||||
if (isset($purposes[$optionvalue])) {
|
||||
$purpose = $purposes[$optionvalue];
|
||||
} else {
|
||||
// Get the effective purpose if $optionvalue would be the selected value.
|
||||
$purpose = api::get_effective_context_purpose($context, $optionvalue);
|
||||
}
|
||||
|
||||
$retentionperiod = self::get_retention_display_text(
|
||||
$purpose,
|
||||
$context->contextlevel,
|
||||
$context
|
||||
);
|
||||
$customdata['purposeretentionperiods'][$optionvalue] = $retentionperiod;
|
||||
}
|
||||
}
|
||||
|
||||
return $customdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the purpose display text.
|
||||
*
|
||||
* @param purpose $effectivepurpose
|
||||
* @param int $retentioncontextlevel
|
||||
* @param \context $context The context, just for displaying (filters) purposes.
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_retention_display_text(purpose $effectivepurpose, $retentioncontextlevel, \context $context) {
|
||||
global $PAGE;
|
||||
|
||||
$renderer = $PAGE->get_renderer('tool_dataprivacy');
|
||||
|
||||
$exporter = new \tool_dataprivacy\external\purpose_exporter($effectivepurpose, ['context' => $context]);
|
||||
$exportedpurpose = $exporter->export($renderer);
|
||||
|
||||
switch ($retentioncontextlevel) {
|
||||
case CONTEXT_COURSE:
|
||||
case CONTEXT_MODULE:
|
||||
case CONTEXT_BLOCK:
|
||||
$str = get_string('effectiveretentionperiodcourse', 'tool_dataprivacy',
|
||||
$exportedpurpose->formattedretentionperiod);
|
||||
break;
|
||||
case CONTEXT_USER:
|
||||
$str = get_string('effectiveretentionperioduser', 'tool_dataprivacy',
|
||||
$exportedpurpose->formattedretentionperiod);
|
||||
break;
|
||||
default:
|
||||
$str = $exportedpurpose->formattedretentionperiod;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This file contains the form add/update context level data.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\form;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\form\persistent;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_registry;
|
||||
|
||||
/**
|
||||
* Context level data form.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class contextlevel extends context_instance {
|
||||
|
||||
/**
|
||||
* @var The persistent class.
|
||||
*/
|
||||
protected static $persistentclass = 'tool_dataprivacy\\contextlevel';
|
||||
|
||||
/**
|
||||
* Define the form - called by parent constructor
|
||||
*/
|
||||
public function definition() {
|
||||
$this->_form->setDisableShortforms();
|
||||
|
||||
$this->_form->addElement('header', 'contextlevelname', $this->_customdata['contextlevelname']);
|
||||
|
||||
$this->add_purpose_category();
|
||||
|
||||
$this->_form->addElement('hidden', 'contextlevel');
|
||||
$this->_form->setType('contextlevel', PARAM_INT);
|
||||
|
||||
parent::add_action_buttons(false, get_string('savechanges'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the customdata array for the provided context level.
|
||||
*
|
||||
* @param int $contextlevel
|
||||
* @return array
|
||||
*/
|
||||
public static function get_contextlevel_customdata($contextlevel) {
|
||||
|
||||
$persistent = \tool_dataprivacy\contextlevel::get_record_by_contextlevel($contextlevel, false);
|
||||
if (!$persistent) {
|
||||
$persistent = new \tool_dataprivacy\contextlevel();
|
||||
$persistent->set('contextlevel', $contextlevel);
|
||||
}
|
||||
|
||||
$includeinherit = true;
|
||||
if ($contextlevel == CONTEXT_SYSTEM) {
|
||||
// Nothing to inherit from Site level.
|
||||
$includeinherit = false;
|
||||
}
|
||||
$includenotset = true;
|
||||
if ($contextlevel == CONTEXT_SYSTEM || $contextlevel == CONTEXT_USER) {
|
||||
// No 'not set' value for system and user because we do not have defaults for them.
|
||||
$includenotset = false;
|
||||
}
|
||||
$purposeoptions = \tool_dataprivacy\output\data_registry_page::purpose_options(
|
||||
api::get_purposes(), $includenotset, $includeinherit);
|
||||
$categoryoptions = \tool_dataprivacy\output\data_registry_page::category_options(
|
||||
api::get_categories(), $includenotset, $includeinherit);
|
||||
|
||||
$customdata = [
|
||||
'contextlevel' => $contextlevel,
|
||||
'contextlevelname' => get_string('contextlevelname' . $contextlevel, 'tool_dataprivacy'),
|
||||
'persistent' => $persistent,
|
||||
'purposes' => $purposeoptions,
|
||||
'categories' => $categoryoptions,
|
||||
];
|
||||
|
||||
$effectivepurpose = api::get_effective_contextlevel_purpose($contextlevel);
|
||||
if ($effectivepurpose) {
|
||||
|
||||
$customdata['currentretentionperiod'] = self::get_retention_display_text($effectivepurpose, $contextlevel,
|
||||
\context_system::instance());
|
||||
|
||||
$customdata['purposeretentionperiods'] = [];
|
||||
foreach ($purposeoptions as $optionvalue => $unused) {
|
||||
|
||||
// Get the effective purpose if $optionvalue would be the selected value.
|
||||
list($purposeid, $unused) = data_registry::get_effective_default_contextlevel_purpose_and_category($contextlevel,
|
||||
$optionvalue);
|
||||
$purpose = new \tool_dataprivacy\purpose($purposeid);
|
||||
|
||||
$retentionperiod = self::get_retention_display_text(
|
||||
$purpose,
|
||||
$contextlevel,
|
||||
\context_system::instance()
|
||||
);
|
||||
$customdata['purposeretentionperiods'][$optionvalue] = $retentionperiod;
|
||||
}
|
||||
}
|
||||
|
||||
return $customdata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This file contains the form add/update a data purpose.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\form;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\form\persistent;
|
||||
|
||||
/**
|
||||
* Data purpose form.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class purpose extends persistent {
|
||||
|
||||
/**
|
||||
* @var string The persistent class.
|
||||
*/
|
||||
protected static $persistentclass = 'tool_dataprivacy\\purpose';
|
||||
|
||||
/**
|
||||
* Define the form - called by parent constructor
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
|
||||
$mform->addElement('text', 'name', get_string('name'), 'maxlength="100"');
|
||||
$mform->setType('name', PARAM_TEXT);
|
||||
$mform->addRule('name', get_string('required'), 'required', null, 'server');
|
||||
$mform->addRule('name', get_string('maximumchars', '', 100), 'maxlength', 100, 'server');
|
||||
|
||||
$mform->addElement('editor', 'description', get_string('description'), null, ['autosave' => false]);
|
||||
$mform->setType('description', PARAM_CLEANHTML);
|
||||
|
||||
// Field for selecting lawful bases (from GDPR Article 6.1).
|
||||
$lawfulbases = [];
|
||||
foreach (\tool_dataprivacy\purpose::GDPR_ART_6_1_ITEMS as $article) {
|
||||
$key = 'gdpr_art_6_1_' . $article;
|
||||
$lawfulbases[$key] = get_string($key . '_name', 'tool_dataprivacy');
|
||||
}
|
||||
$options = array(
|
||||
'multiple' => true,
|
||||
);
|
||||
$mform->addElement('autocomplete', 'lawfulbases', get_string('lawfulbases', 'tool_dataprivacy'), $lawfulbases, $options);
|
||||
$mform->addRule('lawfulbases', get_string('required'), 'required', null, 'server');
|
||||
$mform->addHelpButton('lawfulbases', 'lawfulbases', 'tool_dataprivacy');
|
||||
|
||||
// Optional field for selecting reasons for collecting sensitive personal data (from GDPR Article 9.2).
|
||||
$sensitivereasons = [];
|
||||
foreach (\tool_dataprivacy\purpose::GDPR_ART_9_2_ITEMS as $article) {
|
||||
$key = 'gdpr_art_9_2_' . $article;
|
||||
$sensitivereasons[$key] = get_string($key . '_name', 'tool_dataprivacy');
|
||||
}
|
||||
$mform->addElement('autocomplete', 'sensitivedatareasons', get_string('sensitivedatareasons', 'tool_dataprivacy'),
|
||||
$sensitivereasons, $options);
|
||||
$mform->addHelpButton('sensitivedatareasons', 'sensitivedatareasons', 'tool_dataprivacy');
|
||||
|
||||
$number = $mform->createElement('text', 'retentionperiodnumber', null, ['size' => 8]);
|
||||
$unitoptions = [
|
||||
'Y' => get_string('years'),
|
||||
'M' => strtolower(get_string('months')),
|
||||
'D' => strtolower(get_string('days'))
|
||||
];
|
||||
$unit = $mform->createElement('select', 'retentionperiodunit', '', $unitoptions);
|
||||
$mform->addGroup(['number' => $number, 'unit' => $unit], 'retentionperiod',
|
||||
get_string('retentionperiod', 'tool_dataprivacy'), null, false);
|
||||
$mform->setType('retentionperiodnumber', PARAM_INT);
|
||||
|
||||
$this->_form->addElement('advcheckbox', 'protected', get_string('protected', 'tool_dataprivacy'),
|
||||
get_string('protectedlabel', 'tool_dataprivacy'));
|
||||
|
||||
if (!empty($this->_customdata['showbuttons'])) {
|
||||
if (!$this->get_persistent()->get('id')) {
|
||||
$savetext = get_string('add');
|
||||
} else {
|
||||
$savetext = get_string('savechanges');
|
||||
}
|
||||
$this->add_action_buttons(true, $savetext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts fields.
|
||||
*
|
||||
* @param \stdClass $data
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected static function convert_fields(\stdClass $data) {
|
||||
$data = parent::convert_fields($data);
|
||||
|
||||
if (is_array($data->lawfulbases)) {
|
||||
$data->lawfulbases = implode(',', $data->lawfulbases);
|
||||
}
|
||||
if (!empty($data->sensitivedatareasons) && is_array($data->sensitivedatareasons)) {
|
||||
$data->sensitivedatareasons = implode(',', $data->sensitivedatareasons);
|
||||
}
|
||||
|
||||
// A single value.
|
||||
$data->retentionperiod = 'P' . $data->retentionperiodnumber . $data->retentionperiodunit;
|
||||
unset($data->retentionperiodnumber);
|
||||
unset($data->retentionperiodunit);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default data.
|
||||
*
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function get_default_data() {
|
||||
$data = parent::get_default_data();
|
||||
|
||||
$data->lawfulbases = explode(',', $data->lawfulbases);
|
||||
if (!empty($data->sensitivedatareasons)) {
|
||||
$data->sensitivedatareasons = explode(',', $data->sensitivedatareasons);
|
||||
}
|
||||
|
||||
// Convert the single properties into number and unit.
|
||||
$strlen = strlen($data->retentionperiod);
|
||||
$data->retentionperiodnumber = substr($data->retentionperiod, 1, $strlen - 2);
|
||||
$data->retentionperiodunit = substr($data->retentionperiod, $strlen - 1);
|
||||
unset($data->retentionperiod);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?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/>.
|
||||
|
||||
|
||||
/**
|
||||
* Filter selector field.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use MoodleQuickForm_autocomplete;
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/form/autocomplete.php');
|
||||
|
||||
/**
|
||||
* Form field type for choosing a user on the data request creation form.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class request_user_autocomplete extends MoodleQuickForm_autocomplete {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $elementname Element name
|
||||
* @param mixed $elementlabel Label(s) for an element
|
||||
* @param array $options Options to control the element's display
|
||||
* Valid options are:
|
||||
* - multiple bool Whether or not the field accepts more than one values.
|
||||
*/
|
||||
public function __construct($elementname = null, $elementlabel = null, $options = array()) {
|
||||
$validattributes = array(
|
||||
'ajax' => 'tool_dataprivacy/form-user-selector',
|
||||
);
|
||||
if (!empty($options['multiple'])) {
|
||||
$validattributes['multiple'] = 'multiple';
|
||||
}
|
||||
|
||||
parent::__construct($elementname, $elementlabel, array(), $validattributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of this element.
|
||||
*
|
||||
* @param string|array $value The value to set.
|
||||
* @return boolean
|
||||
*/
|
||||
public function setValue($value) {
|
||||
global $DB;
|
||||
$values = (array) $value;
|
||||
$ids = [];
|
||||
foreach ($values as $onevalue) {
|
||||
if (!empty($onevalue) && (!$this->optionExists($onevalue)) &&
|
||||
($onevalue !== '_qf__force_multiselect_submission')) {
|
||||
array_push($ids, $onevalue);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($ids)) {
|
||||
return $this->setSelected([]);
|
||||
}
|
||||
|
||||
$toselect = [];
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
|
||||
$allusernames = get_all_user_name_fields(true);
|
||||
// Exclude admins and guest user.
|
||||
$excludedusers = array_keys(get_admins()) + [guest_user()->id];
|
||||
$sort = 'firstname ASC';
|
||||
$fields = 'id, email, ' . $allusernames;
|
||||
// Fetch the selected users, exclude the admins and guest users.
|
||||
$users = get_users(true, '', true, $excludedusers, $sort, '', '', 0, 30, $fields, "id $insql", $inparams);
|
||||
foreach ($users as $user) {
|
||||
$userdata = (object)[
|
||||
'name' => fullname($user),
|
||||
'email' => $user->email
|
||||
];
|
||||
$this->addOption(get_string('nameemail', 'tool_dataprivacy', $userdata), $user->id);
|
||||
array_push($toselect, $user->id);
|
||||
}
|
||||
|
||||
return $this->setSelected($toselect);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Collection of helper functions for the data privacy tool.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\local;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use moodle_exception;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
|
||||
/**
|
||||
* Class containing helper functions for the data privacy tool.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
/** The default number of results to be shown per page. */
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
/** Filter constant associated with the request type filter. */
|
||||
const FILTER_TYPE = 1;
|
||||
|
||||
/** Filter constant associated with the request status filter. */
|
||||
const FILTER_STATUS = 2;
|
||||
|
||||
/** Filter constant associated with the request creation filter. */
|
||||
const FILTER_CREATION = 3;
|
||||
|
||||
/** The request filters preference key. */
|
||||
const PREF_REQUEST_FILTERS = 'tool_dataprivacy_request-filters';
|
||||
|
||||
/** The number of data request records per page preference key. */
|
||||
const PREF_REQUEST_PERPAGE = 'tool_dataprivacy_request-perpage';
|
||||
|
||||
/**
|
||||
* Retrieves the human-readable text value of a data request type.
|
||||
*
|
||||
* @param int $requesttype The request type.
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_request_type_string($requesttype) {
|
||||
$types = self::get_request_types();
|
||||
if (!isset($types[$requesttype])) {
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
return $types[$requesttype];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the human-readable shortened text value of a data request type.
|
||||
*
|
||||
* @param int $requesttype The request type.
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_shortened_request_type_string($requesttype) {
|
||||
$types = self::get_request_types_short();
|
||||
if (!isset($types[$requesttype])) {
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
return $types[$requesttype];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request type code and their string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_types() {
|
||||
return [
|
||||
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexport', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedelete', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothers', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request type code and their shortened string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_types_short() {
|
||||
return [
|
||||
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexportshort', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedeleteshort', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_OTHERS => get_string('requesttypeothersshort', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the human-readable value of a data request status.
|
||||
*
|
||||
* @param int $status The request status.
|
||||
* @return string
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_request_status_string($status) {
|
||||
$statuses = self::get_request_statuses();
|
||||
if (!isset($statuses[$status])) {
|
||||
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
return $statuses[$status];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request status code and string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_statuses() {
|
||||
return [
|
||||
api::DATAREQUEST_STATUS_PENDING => get_string('statuspending', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_PREPROCESSING => get_string('statuspreprocessing', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL => get_string('statusawaitingapproval', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_APPROVED => get_string('statusapproved', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_PROCESSING => get_string('statusprocessing', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_COMPLETE => get_string('statuscomplete', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_DOWNLOAD_READY => get_string('statusready', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_EXPIRED => get_string('statusexpired', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_CANCELLED => get_string('statuscancelled', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_REJECTED => get_string('statusrejected', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_DELETED => get_string('statusdeleted', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the human-readable value of a data request creation method.
|
||||
*
|
||||
* @param int $creation The request creation method.
|
||||
* @return string
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_request_creation_method_string($creation) {
|
||||
$creationmethods = self::get_request_creation_methods();
|
||||
if (!isset($creationmethods[$creation])) {
|
||||
throw new moodle_exception('errorinvalidrequestcreationmethod', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
return $creationmethods[$creation];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key value-pairs of request creation method code and string value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_request_creation_methods() {
|
||||
return [
|
||||
data_request::DATAREQUEST_CREATION_MANUAL => get_string('creationmanual', 'tool_dataprivacy'),
|
||||
data_request::DATAREQUEST_CREATION_AUTO => get_string('creationauto', '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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get options for the data requests filter.
|
||||
*
|
||||
* @return array
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function get_request_filter_options() {
|
||||
$filters = [
|
||||
self::FILTER_TYPE => (object)[
|
||||
'name' => get_string('requesttype', 'tool_dataprivacy'),
|
||||
'options' => self::get_request_types_short()
|
||||
],
|
||||
self::FILTER_STATUS => (object)[
|
||||
'name' => get_string('requeststatus', 'tool_dataprivacy'),
|
||||
'options' => self::get_request_statuses()
|
||||
],
|
||||
self::FILTER_CREATION => (object)[
|
||||
'name' => get_string('requestcreation', 'tool_dataprivacy'),
|
||||
'options' => self::get_request_creation_methods()
|
||||
],
|
||||
];
|
||||
$options = [];
|
||||
foreach ($filters as $category => $filtercategory) {
|
||||
foreach ($filtercategory->options as $key => $name) {
|
||||
$option = (object)[
|
||||
'category' => $filtercategory->name,
|
||||
'name' => $name
|
||||
];
|
||||
$options["{$category}:{$key}"] = get_string('filteroption', 'tool_dataprivacy', $option);
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?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 containing helper methods for processing data requests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Adrian Greeve
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class containing helper methods for processing data requests.
|
||||
*
|
||||
* @copyright 2018 Adrian Greeve
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class metadata_registry {
|
||||
|
||||
/**
|
||||
* Returns plugin types / plugins and the user data that it stores in a format that can be sent to a template.
|
||||
*
|
||||
* @return array An array with all of the plugin types / plugins and the user data they store.
|
||||
*/
|
||||
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();
|
||||
$fullyrichtree = $this->get_full_component_list();
|
||||
foreach ($fullyrichtree as $branch => $leaves) {
|
||||
$plugintype = $leaves['plugin_type'];
|
||||
$plugins = array_map(function($component) use ($manager, $metadata, $contributedplugins, $plugintype, $pluginman) {
|
||||
// Use the plugin name for the plugins, ignore for core subsystems.
|
||||
$internaldata = ($plugintype == 'core') ? ['component' => $component] :
|
||||
['component' => $pluginman->plugin_name($component)];
|
||||
$internaldata['raw_component'] = $component;
|
||||
if ($manager->component_is_compliant($component)) {
|
||||
$internaldata['compliant'] = true;
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
$internaldata['compliant'] = false;
|
||||
}
|
||||
// Check to see if we are an external plugin.
|
||||
// Plugin names can contain _ characters, limit to 2 to just remove initial plugintype.
|
||||
$componentshortname = explode('_', $component, 2);
|
||||
$shortname = array_pop($componentshortname);
|
||||
if (isset($contributedplugins[$plugintype][$shortname])) {
|
||||
$internaldata['external'] = true;
|
||||
}
|
||||
|
||||
// Check if the interface is deprecated.
|
||||
if (!$manager->is_empty_subsystem($component)) {
|
||||
$classname = $manager->get_provider_classname_for_component($component);
|
||||
if (class_exists($classname)) {
|
||||
$componentclass = new $classname();
|
||||
if ($componentclass instanceof \core_privacy\local\deprecated) {
|
||||
$internaldata['deprecated'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $internaldata;
|
||||
}, $leaves['plugins']);
|
||||
$fullyrichtree[$branch]['plugin_type_raw'] = $plugintype;
|
||||
// We're done using the plugin type. Convert it to a readable string.
|
||||
$fullyrichtree[$branch]['plugin_type'] = $pluginman->plugintype_name($plugintype);
|
||||
$fullyrichtree[$branch]['plugins'] = $plugins;
|
||||
}
|
||||
return $fullyrichtree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the metadata for use with a template.
|
||||
*
|
||||
* @param array $collection The collection associated with the component that we want to expand and format.
|
||||
* @param string $component The component that we are dealing in
|
||||
* @param array $internaldata The array to add the formatted metadata to.
|
||||
* @return array The internal data array with the formatted metadata.
|
||||
*/
|
||||
protected function format_metadata($collection, $component, $internaldata) {
|
||||
foreach ($collection as $collectioninfo) {
|
||||
$privacyfields = $collectioninfo->get_privacy_fields();
|
||||
$fields = '';
|
||||
if (!empty($privacyfields)) {
|
||||
$fields = array_map(function($key, $field) use ($component) {
|
||||
return [
|
||||
'field_name' => $key,
|
||||
'field_summary' => get_string($field, $component)
|
||||
];
|
||||
}, array_keys($privacyfields), $privacyfields);
|
||||
}
|
||||
// Can the metadata types be located somewhere else besides core?
|
||||
$items = explode('\\', get_class($collectioninfo));
|
||||
$type = array_pop($items);
|
||||
$typedata = [
|
||||
'name' => $collectioninfo->get_name(),
|
||||
'type' => $type,
|
||||
'fields' => $fields,
|
||||
'summary' => get_string($collectioninfo->get_summary(), $component)
|
||||
];
|
||||
if (strpos($type, 'subsystem_link') === 0 || strpos($type, 'plugintype_link') === 0) {
|
||||
$typedata['link'] = true;
|
||||
}
|
||||
$internaldata['metadata'][] = $typedata;
|
||||
}
|
||||
return $internaldata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full list of components.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of contributed plugins installed on the system.
|
||||
*
|
||||
* @return array A list of contributed plugins installed.
|
||||
*/
|
||||
protected function get_contrib_list() {
|
||||
return array_map(function($plugins) {
|
||||
return array_filter($plugins, function($plugindata) {
|
||||
return !$plugindata->is_standard();
|
||||
});
|
||||
}, \core_plugin_manager::instance()->get_plugins());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Categories renderable.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\external\category_exporter;
|
||||
|
||||
/**
|
||||
* Class containing the categories page renderable.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class categories extends crud_element implements renderable, templatable {
|
||||
|
||||
/** @var array $categories All system categories. */
|
||||
protected $categories = [];
|
||||
|
||||
/**
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param \tool_dataprivacy\category[] $categories
|
||||
*/
|
||||
public function __construct($categories) {
|
||||
$this->categories = $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
global $PAGE;
|
||||
|
||||
$context = \context_system::instance();
|
||||
|
||||
$PAGE->requires->js_call_amd('tool_dataprivacy/categoriesactions', 'init');
|
||||
$PAGE->requires->js_call_amd('tool_dataprivacy/add_category', 'getInstance', [$context->id]);
|
||||
|
||||
$data = new stdClass();
|
||||
|
||||
// Navigation links.
|
||||
$data->navigation = [];
|
||||
$navigationlinks = $this->get_navigation();
|
||||
foreach ($navigationlinks as $navlink) {
|
||||
$data->navigation[] = $navlink->export_for_template($output);
|
||||
}
|
||||
|
||||
$data->categories = [];
|
||||
foreach ($this->categories as $category) {
|
||||
$exporter = new category_exporter($category, ['context' => \context_system::instance()]);
|
||||
$exportedcategory = $exporter->export($output);
|
||||
|
||||
$actionmenu = $this->action_menu('category', $exportedcategory, $category);
|
||||
$exportedcategory->actions = $actionmenu->export_for_template($output);
|
||||
$data->categories[] = $exportedcategory;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Abstract renderer for independent renderable elements.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\external\purpose_exporter;
|
||||
use tool_dataprivacy\external\category_exporter;
|
||||
|
||||
/**
|
||||
* Abstract renderer for independent renderable elements.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class crud_element {
|
||||
|
||||
/**
|
||||
* Returns the top navigation buttons.
|
||||
*
|
||||
* @return \action_link[]
|
||||
*/
|
||||
protected final function get_navigation() {
|
||||
$back = new \action_link(
|
||||
new \moodle_url('/admin/tool/dataprivacy/dataregistry.php'),
|
||||
get_string('back'),
|
||||
null,
|
||||
['class' => 'btn btn-default']
|
||||
);
|
||||
return [$back];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an action menu for the provided element
|
||||
*
|
||||
* @param string $elementname 'purpose' or 'category'.
|
||||
* @param \stdClass $exported
|
||||
* @param \core\persistent $persistent
|
||||
* @return \action_menu
|
||||
*/
|
||||
protected final function action_menu($elementname, $exported, $persistent) {
|
||||
|
||||
// Just in case, we are doing funny stuff below.
|
||||
$elementname = clean_param($elementname, PARAM_ALPHA);
|
||||
|
||||
// Actions.
|
||||
$actionmenu = new \action_menu();
|
||||
$actionmenu->set_menu_trigger(get_string('actions'));
|
||||
$actionmenu->set_owner_selector($elementname . '-' . $exported->id . '-actions');
|
||||
$actionmenu->set_alignment(\action_menu::TL, \action_menu::BL);
|
||||
|
||||
$url = new \moodle_url('/admin/tool/dataprivacy/edit' . $elementname . '.php',
|
||||
['id' => $exported->id]);
|
||||
$link = new \action_menu_link_secondary($url, new \pix_icon('t/edit',
|
||||
get_string('edit')), get_string('edit'));
|
||||
$actionmenu->add($link);
|
||||
|
||||
if (!$persistent->is_used()) {
|
||||
$url = new \moodle_url('#');
|
||||
$attrs = ['data-id' => $exported->id, 'data-action' => 'delete' . $elementname, 'data-name' => $exported->name];
|
||||
$link = new \action_menu_link_secondary($url, new \pix_icon('t/delete',
|
||||
get_string('delete')), get_string('delete'), $attrs);
|
||||
$actionmenu->add($link);
|
||||
}
|
||||
|
||||
return $actionmenu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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 containing data for a user's data requests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use single_select;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
/**
|
||||
* Class containing data for a user's data requests.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_deletion_page implements renderable, templatable {
|
||||
|
||||
/** @var data_request[] $requests List of data requests. */
|
||||
protected $filter = null;
|
||||
|
||||
/** @var data_request[] $requests List of data requests. */
|
||||
protected $expiredcontextstable = [];
|
||||
|
||||
/**
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param \tool_dataprivacy\data_request[] $filter
|
||||
* @param expired_contexts_table $expiredcontextstable
|
||||
*/
|
||||
public function __construct($filter, expired_contexts_table $expiredcontextstable) {
|
||||
$this->filter = $filter;
|
||||
$this->expiredcontextstable = $expiredcontextstable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
$data = new stdClass();
|
||||
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/datadeletion.php');
|
||||
$options = [
|
||||
CONTEXT_USER => get_string('user'),
|
||||
CONTEXT_COURSE => get_string('course'),
|
||||
CONTEXT_MODULE => get_string('activitiesandresources', 'tool_dataprivacy'),
|
||||
CONTEXT_BLOCK => get_string('blocks'),
|
||||
];
|
||||
$filterselector = new single_select($url, 'filter', $options, $this->filter, null);
|
||||
$data->filter = $filterselector->export_for_template($output);
|
||||
|
||||
ob_start();
|
||||
$this->expiredcontextstable->out(helper::DEFAULT_PAGE_SIZE, true);
|
||||
$expiredcontexts = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$data->expiredcontexts = $expiredcontexts;
|
||||
|
||||
$data->existingcontexts = $this->expiredcontextstable->rawdata ? true : false;
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the data registry compliance renderable.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Adrian Greeve
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use templatable;
|
||||
|
||||
/**
|
||||
* Class containing the data registry compliance renderable
|
||||
*
|
||||
* @copyright 2018 Adrian Greeve
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_registry_compliance_page implements renderable, templatable {
|
||||
|
||||
/** @var array meta-data to be displayed about the system. */
|
||||
protected $metadata;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $metadata
|
||||
*/
|
||||
public function __construct($metadata) {
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
return ['types' => $this->metadata];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
<?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 registry renderable.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\data_registry;
|
||||
|
||||
require_once($CFG->libdir . '/coursecatlib.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
require_once($CFG->libdir . '/blocklib.php');
|
||||
|
||||
/**
|
||||
* Class containing the data registry renderable
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_registry_page implements renderable, templatable {
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $defaultcontextlevel;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $defaultcontextid;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $defaultcontextlevel
|
||||
* @param int $defaultcontextid
|
||||
* @return null
|
||||
*/
|
||||
public function __construct($defaultcontextlevel = false, $defaultcontextid = false) {
|
||||
$this->defaultcontextlevel = $defaultcontextlevel;
|
||||
$this->defaultcontextid = $defaultcontextid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
global $PAGE;
|
||||
|
||||
$params = [\context_system::instance()->id, $this->defaultcontextlevel, $this->defaultcontextid];
|
||||
$PAGE->requires->js_call_amd('tool_dataprivacy/data_registry', 'init', $params);
|
||||
|
||||
$data = new stdClass();
|
||||
$defaultsbutton = new \action_link(
|
||||
new \moodle_url('/admin/tool/dataprivacy/defaults.php'),
|
||||
get_string('setdefaults', 'tool_dataprivacy'),
|
||||
null,
|
||||
['class' => 'btn btn-default']
|
||||
);
|
||||
$data->defaultsbutton = $defaultsbutton->export_for_template($output);
|
||||
|
||||
$actionmenu = new \action_menu();
|
||||
$actionmenu->set_menu_trigger(get_string('edit'), 'btn btn-default');
|
||||
$actionmenu->set_owner_selector('dataregistry-actions');
|
||||
$actionmenu->set_alignment(\action_menu::TL, \action_menu::BL);
|
||||
|
||||
$url = new \moodle_url('/admin/tool/dataprivacy/categories.php');
|
||||
$categories = new \action_menu_link_secondary($url, null, get_string('categories', 'tool_dataprivacy'));
|
||||
$actionmenu->add($categories);
|
||||
|
||||
$url = new \moodle_url('/admin/tool/dataprivacy/purposes.php');
|
||||
$purposes = new \action_menu_link_secondary($url, null, get_string('purposes', 'tool_dataprivacy'));
|
||||
$actionmenu->add($purposes);
|
||||
|
||||
$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
|
||||
];
|
||||
}
|
||||
|
||||
$data->tree = $this->get_default_tree_structure();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tree default structure.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_default_tree_structure() {
|
||||
|
||||
$frontpage = \context_course::instance(SITEID);
|
||||
|
||||
$categorybranches = $this->get_all_category_branches();
|
||||
|
||||
$elements = [
|
||||
'text' => get_string('contextlevelname' . CONTEXT_SYSTEM, 'tool_dataprivacy'),
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'branches' => [
|
||||
[
|
||||
'text' => get_string('user'),
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
], [
|
||||
'text' => get_string('categories'),
|
||||
'branches' => $categorybranches,
|
||||
'expandelement' => 'category',
|
||||
], [
|
||||
'text' => get_string('frontpagecourse', 'tool_dataprivacy'),
|
||||
'contextid' => $frontpage->id,
|
||||
'branches' => [
|
||||
[
|
||||
'text' => get_string('activitiesandresources', 'tool_dataprivacy'),
|
||||
'expandcontextid' => $frontpage->id,
|
||||
'expandelement' => 'module',
|
||||
'expanded' => 0,
|
||||
], [
|
||||
'text' => get_string('blocks'),
|
||||
'expandcontextid' => $frontpage->id,
|
||||
'expandelement' => 'block',
|
||||
'expanded' => 0,
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// Returned as an array to follow a common array format.
|
||||
return [self::complete($elements, $this->defaultcontextlevel, $this->defaultcontextid)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hierarchy of system course categories.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_all_category_branches() {
|
||||
|
||||
$categories = data_registry::get_site_categories();
|
||||
|
||||
$categoriesbranch = [];
|
||||
while (count($categories) > 0) {
|
||||
foreach ($categories as $key => $category) {
|
||||
|
||||
$context = \context_coursecat::instance($category->id);
|
||||
$newnode = [
|
||||
'text' => shorten_text(format_string($category->name, true, ['context' => $context])),
|
||||
'categoryid' => $category->id,
|
||||
'contextid' => $context->id,
|
||||
];
|
||||
if ($category->coursecount > 0) {
|
||||
$newnode['branches'] = [
|
||||
[
|
||||
'text' => get_string('courses'),
|
||||
'expandcontextid' => $context->id,
|
||||
'expandelement' => 'course',
|
||||
'expanded' => 0,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$added = false;
|
||||
if ($category->parent == 0) {
|
||||
// New categories root-level node.
|
||||
$categoriesbranch[] = $newnode;
|
||||
$added = true;
|
||||
|
||||
} else {
|
||||
// Add the new node under the appropriate parent.
|
||||
if ($this->add_to_parent_category_branch($category, $newnode, $categoriesbranch)) {
|
||||
$added = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($added) {
|
||||
unset($categories[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $categoriesbranch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the courses branch for the provided category.
|
||||
*
|
||||
* @param \context $catcontext
|
||||
* @return array
|
||||
*/
|
||||
public static function get_courses_branch(\context $catcontext) {
|
||||
|
||||
if ($catcontext->contextlevel !== CONTEXT_COURSECAT) {
|
||||
throw new \coding_exception('A course category context should be provided');
|
||||
}
|
||||
|
||||
$coursecat = \coursecat::get($catcontext->instanceid);
|
||||
$courses = $coursecat->get_courses();
|
||||
|
||||
$branches = [];
|
||||
|
||||
foreach ($courses as $course) {
|
||||
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
|
||||
if (!$course->visible && !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$coursenode = [
|
||||
'text' => shorten_text(format_string($course->shortname, true, ['context' => $coursecontext])),
|
||||
'contextid' => $coursecontext->id,
|
||||
'branches' => [
|
||||
[
|
||||
'text' => get_string('activitiesandresources', 'tool_dataprivacy'),
|
||||
'expandcontextid' => $coursecontext->id,
|
||||
'expandelement' => 'module',
|
||||
'expanded' => 0,
|
||||
], [
|
||||
'text' => get_string('blocks'),
|
||||
'expandcontextid' => $coursecontext->id,
|
||||
'expandelement' => 'block',
|
||||
'expanded' => 0,
|
||||
],
|
||||
]
|
||||
];
|
||||
$branches[] = self::complete($coursenode);
|
||||
}
|
||||
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the modules branch for the provided course.
|
||||
*
|
||||
* @param \context $coursecontext
|
||||
* @return array
|
||||
*/
|
||||
public static function get_modules_branch(\context $coursecontext) {
|
||||
|
||||
if ($coursecontext->contextlevel !== CONTEXT_COURSE) {
|
||||
throw new \coding_exception('A course context should be provided');
|
||||
}
|
||||
|
||||
$branches = [];
|
||||
|
||||
// Using the current user.
|
||||
$modinfo = get_fast_modinfo($coursecontext->instanceid);
|
||||
foreach ($modinfo->get_instances() as $moduletype => $instances) {
|
||||
foreach ($instances as $cm) {
|
||||
|
||||
if (!$cm->uservisible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$a = (object)[
|
||||
'instancename' => shorten_text($cm->get_formatted_name()),
|
||||
'modulename' => get_string('pluginname', 'mod_' . $moduletype),
|
||||
];
|
||||
|
||||
$text = get_string('moduleinstancename', 'tool_dataprivacy', $a);
|
||||
$branches[] = self::complete([
|
||||
'text' => $text,
|
||||
'contextid' => $cm->context->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blocks branch for the provided course.
|
||||
*
|
||||
* @param \context $coursecontext
|
||||
* @return null
|
||||
*/
|
||||
public static function get_blocks_branch(\context $coursecontext) {
|
||||
global $DB;
|
||||
|
||||
if ($coursecontext->contextlevel !== CONTEXT_COURSE) {
|
||||
throw new \coding_exception('A course context should be provided');
|
||||
}
|
||||
|
||||
$branches = [];
|
||||
|
||||
$children = $coursecontext->get_child_contexts();
|
||||
foreach ($children as $childcontext) {
|
||||
|
||||
if ($childcontext->contextlevel !== CONTEXT_BLOCK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$blockinstance = block_instance_by_id($childcontext->instanceid);
|
||||
$displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $childcontext]));
|
||||
$branches[] = self::complete([
|
||||
'text' => $displayname,
|
||||
'contextid' => $childcontext->id,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
return $branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided category to the categories branch.
|
||||
*
|
||||
* @param stdClass $category
|
||||
* @param array $newnode
|
||||
* @param array $categoriesbranch
|
||||
* @return bool
|
||||
*/
|
||||
private function add_to_parent_category_branch($category, $newnode, &$categoriesbranch) {
|
||||
|
||||
foreach ($categoriesbranch as $key => $branch) {
|
||||
if (!empty($branch['categoryid']) && $branch['categoryid'] == $category->parent) {
|
||||
// It may be empty (if it does not contain courses and this is the first child cat).
|
||||
if (!isset($categoriesbranch[$key]['branches'])) {
|
||||
$categoriesbranch[$key]['branches'] = [];
|
||||
}
|
||||
$categoriesbranch[$key]['branches'][] = $newnode;
|
||||
return true;
|
||||
}
|
||||
if (!empty($branch['branches'])) {
|
||||
$parent = $this->add_to_parent_category_branch($category, $newnode, $categoriesbranch[$key]['branches']);
|
||||
if ($parent) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes tree nodes with default values.
|
||||
*
|
||||
* @param array $node
|
||||
* @param int|false $currentcontextlevel
|
||||
* @param int|false $currentcontextid
|
||||
* @return array
|
||||
*/
|
||||
private static function complete($node, $currentcontextlevel = false, $currentcontextid = false) {
|
||||
if (!isset($node['active'])) {
|
||||
if ($currentcontextlevel && !empty($node['contextlevel']) &&
|
||||
$currentcontextlevel == $node['contextlevel'] &&
|
||||
empty($currentcontextid)) {
|
||||
// This is the active context level, we also checked that there
|
||||
// is no default contextid set.
|
||||
$node['active'] = true;
|
||||
} else if ($currentcontextid && !empty($node['contextid']) &&
|
||||
$currentcontextid == $node['contextid']) {
|
||||
$node['active'] = true;
|
||||
} else {
|
||||
$node['active'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($node['branches'])) {
|
||||
$node['branches'] = [];
|
||||
} else {
|
||||
foreach ($node['branches'] as $key => $childnode) {
|
||||
$node['branches'][$key] = self::complete($childnode, $currentcontextlevel, $currentcontextid);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($node['expandelement'])) {
|
||||
$node['expandelement'] = null;
|
||||
}
|
||||
|
||||
if (!isset($node['expandcontextid'])) {
|
||||
$node['expandcontextid'] = null;
|
||||
}
|
||||
|
||||
if (!isset($node['contextid'])) {
|
||||
$node['contextid'] = null;
|
||||
}
|
||||
|
||||
if (!isset($node['contextlevel'])) {
|
||||
$node['contextlevel'] = null;
|
||||
}
|
||||
|
||||
if (!isset($node['expanded'])) {
|
||||
if (!empty($node['branches'])) {
|
||||
$node['expanded'] = 1;
|
||||
} else {
|
||||
$node['expanded'] = 0;
|
||||
}
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* From a list of purpose persistents to a list of id => name purposes.
|
||||
*
|
||||
* @param \tool_dataprivacy\purpose[] $purposes
|
||||
* @param bool $includenotset
|
||||
* @param bool $includeinherit
|
||||
* @return string[]
|
||||
*/
|
||||
public static function purpose_options($purposes, $includenotset = true, $includeinherit = true) {
|
||||
$options = self::base_options($includenotset, $includeinherit);
|
||||
foreach ($purposes as $purpose) {
|
||||
$options[$purpose->get('id')] = $purpose->get('name');
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* From a list of category persistents to a list of id => name categories.
|
||||
*
|
||||
* @param \tool_dataprivacy\category[] $categories
|
||||
* @param bool $includenotset
|
||||
* @param bool $includeinherit
|
||||
* @return string[]
|
||||
*/
|
||||
public static function category_options($categories, $includenotset = true, $includeinherit = true) {
|
||||
$options = self::base_options($includenotset, $includeinherit);
|
||||
foreach ($categories as $category) {
|
||||
$options[$category->get('id')] = $category->get('name');
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base not set and inherit options.
|
||||
*
|
||||
* @param bool $includenotset
|
||||
* @param bool $includeinherit
|
||||
* @return array
|
||||
*/
|
||||
private static function base_options($includenotset = true, $includeinherit = true) {
|
||||
|
||||
$options = [];
|
||||
|
||||
if ($includenotset) {
|
||||
$options[\tool_dataprivacy\context_instance::NOTSET] = get_string('notset', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
if ($includeinherit) {
|
||||
$options[\tool_dataprivacy\context_instance::INHERIT] = get_string('inherit', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?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 containing data for a user's data requests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use dml_exception;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use single_select;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
/**
|
||||
* Class containing data for a user's data requests.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_requests_page implements renderable, templatable {
|
||||
|
||||
/** @var data_requests_table $table The data requests table. */
|
||||
protected $table;
|
||||
|
||||
/** @var int[] $filters The applied filters. */
|
||||
protected $filters = [];
|
||||
|
||||
/**
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param data_requests_table $table The data requests table.
|
||||
* @param int[] $filters The applied filters.
|
||||
*/
|
||||
public function __construct($table, $filters) {
|
||||
$this->table = $table;
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
$data = new stdClass();
|
||||
$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);
|
||||
}
|
||||
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
|
||||
$filteroptions = helper::get_request_filter_options();
|
||||
$filter = new request_filter($filteroptions, $this->filters, $url);
|
||||
$data->filter = $filter->export_for_template($output);
|
||||
|
||||
ob_start();
|
||||
$this->table->out($this->table->get_requests_per_page(), true);
|
||||
$requests = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$data->datarequests = $requests;
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the class used for the displaying the data requests table.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/tablelib.php');
|
||||
|
||||
use action_menu;
|
||||
use action_menu_link_secondary;
|
||||
use coding_exception;
|
||||
use dml_exception;
|
||||
use html_writer;
|
||||
use moodle_url;
|
||||
use stdClass;
|
||||
use table_sql;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
/**
|
||||
* The class for displaying the data requests table.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class data_requests_table extends table_sql {
|
||||
|
||||
/** @var int The user ID. */
|
||||
protected $userid = 0;
|
||||
|
||||
/** @var int[] The status filters. */
|
||||
protected $statuses = [];
|
||||
|
||||
/** @var int[] The request type filters. */
|
||||
protected $types = [];
|
||||
|
||||
/** @var bool Whether this table is being rendered for managing data requests. */
|
||||
protected $manage = false;
|
||||
|
||||
/** @var \tool_dataprivacy\data_request[] Array of data request persistents. */
|
||||
protected $datarequests = [];
|
||||
|
||||
/** @var int The number of data request to be displayed per page. */
|
||||
protected $perpage;
|
||||
|
||||
/** @var int[] The available options for the number of data request to be displayed per page. */
|
||||
protected $perpageoptions = [25, 50, 100, 250];
|
||||
|
||||
/**
|
||||
* data_requests_table constructor.
|
||||
*
|
||||
* @param int $userid The user ID
|
||||
* @param int[] $statuses
|
||||
* @param int[] $types
|
||||
* @param int[] $creationmethods
|
||||
* @param bool $manage
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function __construct($userid = 0, $statuses = [], $types = [], $creationmethods = [], $manage = false) {
|
||||
parent::__construct('data-requests-table');
|
||||
|
||||
$this->userid = $userid;
|
||||
$this->statuses = $statuses;
|
||||
$this->types = $types;
|
||||
$this->creationmethods = $creationmethods;
|
||||
$this->manage = $manage;
|
||||
|
||||
$checkboxattrs = [
|
||||
'title' => get_string('selectall'),
|
||||
'data-action' => 'selectall'
|
||||
];
|
||||
|
||||
$columnheaders = [
|
||||
'select' => html_writer::checkbox('selectall', 1, false, null, $checkboxattrs),
|
||||
'type' => get_string('requesttype', 'tool_dataprivacy'),
|
||||
'userid' => get_string('user', 'tool_dataprivacy'),
|
||||
'timecreated' => get_string('daterequested', 'tool_dataprivacy'),
|
||||
'requestedby' => get_string('requestby', 'tool_dataprivacy'),
|
||||
'status' => get_string('requeststatus', 'tool_dataprivacy'),
|
||||
'comments' => get_string('message', 'tool_dataprivacy'),
|
||||
'actions' => '',
|
||||
];
|
||||
|
||||
$this->define_columns(array_keys($columnheaders));
|
||||
$this->define_headers(array_values($columnheaders));
|
||||
$this->no_sorting('select', 'actions');
|
||||
}
|
||||
|
||||
/**
|
||||
* The select column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_select($data) {
|
||||
if ($data->status == \tool_dataprivacy\api::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
|
||||
$stringdata = [
|
||||
'username' => $data->foruser->fullname,
|
||||
'requesttype' => \core_text::strtolower($data->typenameshort)
|
||||
];
|
||||
|
||||
return \html_writer::checkbox('requestids[]', $data->id, false, '',
|
||||
['class' => 'selectrequests', 'title' => get_string('selectuserdatarequest',
|
||||
'tool_dataprivacy', $stringdata)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_type($data) {
|
||||
if ($this->manage) {
|
||||
return $data->typenameshort;
|
||||
}
|
||||
return $data->typename;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
*/
|
||||
public function col_userid($data) {
|
||||
$user = $data->foruser;
|
||||
return html_writer::link($user->profileurl, $user->fullname, ['title' => get_string('viewprofile')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The context information column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_timecreated($data) {
|
||||
return userdate($data->timecreated);
|
||||
}
|
||||
|
||||
/**
|
||||
* The requesting user's column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
*/
|
||||
public function col_requestedby($data) {
|
||||
$user = $data->requestedbyuser;
|
||||
return html_writer::link($user->profileurl, $user->fullname, ['title' => get_string('viewprofile')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The status column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
*/
|
||||
public function col_status($data) {
|
||||
return html_writer::span($data->statuslabel, 'label ' . $data->statuslabelclass);
|
||||
}
|
||||
|
||||
/**
|
||||
* The comments column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_comments($data) {
|
||||
return shorten_text($data->comments, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actions column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_actions($data) {
|
||||
global $OUTPUT;
|
||||
|
||||
$requestid = $data->id;
|
||||
$status = $data->status;
|
||||
|
||||
// Prepare actions.
|
||||
$actions = [];
|
||||
|
||||
// View action.
|
||||
$actionurl = new moodle_url('#');
|
||||
$actiondata = ['data-action' => 'view', 'data-requestid' => $requestid];
|
||||
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
switch ($status) {
|
||||
case api::DATAREQUEST_STATUS_PENDING:
|
||||
// Add action to mark a general enquiry request as complete.
|
||||
if ($data->type == api::DATAREQUEST_TYPE_OTHERS) {
|
||||
$actiondata['data-action'] = 'complete';
|
||||
$nameemail = (object)[
|
||||
'name' => $data->foruser->fullname,
|
||||
'email' => $data->foruser->email
|
||||
];
|
||||
$actiondata['data-requestid'] = $data->id;
|
||||
$actiondata['data-replytoemail'] = get_string('nameemail', 'tool_dataprivacy', $nameemail);
|
||||
$actiontext = get_string('markcomplete', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
}
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
|
||||
// Approve.
|
||||
$actiondata['data-action'] = 'approve';
|
||||
$actiontext = get_string('approverequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
// Deny.
|
||||
$actiondata['data-action'] = 'deny';
|
||||
$actiontext = get_string('denyrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$userid = $data->foruser->id;
|
||||
$usercontext = \context_user::instance($userid, IGNORE_MISSING);
|
||||
// If user has permission to view download link, show relevant action item.
|
||||
if ($usercontext && api::can_download_data_request_for_user($userid, $data->requestedbyuser->id)) {
|
||||
$actions[] = api::get_download_link($usercontext, $requestid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$actionsmenu = new action_menu($actions);
|
||||
$actionsmenu->set_menu_trigger(get_string('actions'));
|
||||
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
|
||||
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
|
||||
$actionsmenu->set_constraint('[data-region=data-requests-table] > .no-overflow');
|
||||
|
||||
return $OUTPUT->render($actionsmenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database for results to display in the table.
|
||||
*
|
||||
* @param int $pagesize size of page for paginated displayed table.
|
||||
* @param bool $useinitialsbar do you want to use the initials bar.
|
||||
* @throws dml_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function query_db($pagesize, $useinitialsbar = true) {
|
||||
global $PAGE;
|
||||
|
||||
// Set dummy page total until we fetch full result set.
|
||||
$this->pagesize($pagesize, $pagesize + 1);
|
||||
|
||||
$sort = $this->get_sql_sort();
|
||||
|
||||
// Get data requests from the given conditions.
|
||||
$datarequests = api::get_data_requests($this->userid, $this->statuses, $this->types,
|
||||
$this->creationmethods, $sort, $this->get_page_start(), $this->get_page_size());
|
||||
|
||||
// Count data requests from the given conditions.
|
||||
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types,
|
||||
$this->creationmethods);
|
||||
$this->pagesize($pagesize, $total);
|
||||
|
||||
$this->rawdata = [];
|
||||
$context = \context_system::instance();
|
||||
$renderer = $PAGE->get_renderer('tool_dataprivacy');
|
||||
|
||||
foreach ($datarequests as $persistent) {
|
||||
$this->datarequests[$persistent->get('id')] = $persistent;
|
||||
$exporter = new data_request_exporter($persistent, ['context' => $context]);
|
||||
$this->rawdata[] = $exporter->export($renderer);
|
||||
}
|
||||
|
||||
// Set initial bars.
|
||||
if ($useinitialsbar) {
|
||||
$this->initialbars($total > $pagesize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override default implementation to display a more meaningful information to the user.
|
||||
*/
|
||||
public function print_nothing_to_display() {
|
||||
global $OUTPUT;
|
||||
echo $this->render_reset_button();
|
||||
$this->print_initials_bar();
|
||||
if (!empty($this->statuses) || !empty($this->types)) {
|
||||
$message = get_string('nodatarequestsmatchingfilter', 'tool_dataprivacy');
|
||||
} else {
|
||||
$message = get_string('nodatarequests', 'tool_dataprivacy');
|
||||
}
|
||||
echo $OUTPUT->notification($message, 'warning');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the table's show_hide_link method to prevent the show/hide links from rendering.
|
||||
*
|
||||
* @param string $column the column name, index into various names.
|
||||
* @param int $index numerical index of the column.
|
||||
* @return string HTML fragment.
|
||||
*/
|
||||
protected function show_hide_link($column, $index) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the table's wrap_html_finish method in order to render the bulk actions and
|
||||
* records per page options.
|
||||
*/
|
||||
public function wrap_html_finish() {
|
||||
global $OUTPUT;
|
||||
|
||||
$data = new stdClass();
|
||||
$data->options = [
|
||||
[
|
||||
'value' => 0,
|
||||
'name' => ''
|
||||
],
|
||||
[
|
||||
'value' => \tool_dataprivacy\api::DATAREQUEST_ACTION_APPROVE,
|
||||
'name' => get_string('approve', 'tool_dataprivacy')
|
||||
],
|
||||
[
|
||||
'value' => \tool_dataprivacy\api::DATAREQUEST_ACTION_REJECT,
|
||||
'name' => get_string('deny', 'tool_dataprivacy')
|
||||
]
|
||||
];
|
||||
|
||||
$perpageoptions = array_combine($this->perpageoptions, $this->perpageoptions);
|
||||
$perpageselect = new \single_select(new moodle_url(''), 'perpage',
|
||||
$perpageoptions, get_user_preferences('tool_dataprivacy_request-perpage'), null, 'selectgroup');
|
||||
$perpageselect->label = get_string('perpage', 'moodle');
|
||||
$data->perpage = $OUTPUT->render($perpageselect);
|
||||
|
||||
echo $OUTPUT->render_from_template('tool_dataprivacy/data_requests_bulk_actions', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of data request records to be displayed per page.
|
||||
*
|
||||
* @param int $perpage The number of data request records.
|
||||
*/
|
||||
public function set_requests_per_page($perpage) {
|
||||
$this->perpage = $perpage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of data request records to be displayed per page.
|
||||
*
|
||||
* @return int The number of data request records.
|
||||
*/
|
||||
public function get_requests_per_page() {
|
||||
return $this->perpage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the available options for the number of data request to be displayed per page.
|
||||
*
|
||||
* @param array $perpageoptions The available options for the number of data request to be displayed per page.
|
||||
*/
|
||||
public function set_requests_per_page_options($perpageoptions) {
|
||||
$this->$perpageoptions = $perpageoptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available options for the number of data request to be displayed per page.
|
||||
*
|
||||
* @return array The available options for the number of data request to be displayed per page.
|
||||
*/
|
||||
public function get_requests_per_page_options() {
|
||||
return $this->perpageoptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?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 containing data for the data registry defaults.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use action_menu_link_primary;
|
||||
use coding_exception;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\data_registry;
|
||||
use tool_dataprivacy\external\category_exporter;
|
||||
use tool_dataprivacy\external\purpose_exporter;
|
||||
|
||||
/**
|
||||
* Class containing data for the data registry defaults.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class defaults_page implements renderable, templatable {
|
||||
|
||||
/** @var int $mode The display mode. */
|
||||
protected $mode = null;
|
||||
|
||||
/** @var int $category The default category for the given mode. */
|
||||
protected $category = null;
|
||||
|
||||
/** @var int $purpose The default purpose for the given mode. */
|
||||
protected $purpose = null;
|
||||
|
||||
/** @var stdClass[] $otherdefaults Other defaults for the given mode. */
|
||||
protected $otherdefaults = [];
|
||||
|
||||
/** @var bool $canedit Whether editing is allowed. */
|
||||
protected $canedit = false;
|
||||
|
||||
/**
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param int $mode The display mode.
|
||||
* @param int $category The default category for the given mode.
|
||||
* @param int $purpose The default purpose for the given mode.
|
||||
* @param stdClass[] $otherdefaults Other defaults for the given mode.
|
||||
* @param bool $canedit Whether editing is allowed.
|
||||
*/
|
||||
public function __construct($mode, $category, $purpose, $otherdefaults = [], $canedit = false) {
|
||||
$this->mode = $mode;
|
||||
$this->category = $category;
|
||||
$this->purpose = $purpose;
|
||||
$this->otherdefaults = $otherdefaults;
|
||||
$this->canedit = $canedit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
$data = new stdClass();
|
||||
|
||||
// Set tab URLs.
|
||||
$coursecaturl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_COURSECAT]);
|
||||
$courseurl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_COURSE]);
|
||||
$moduleurl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_MODULE]);
|
||||
$blockurl = new moodle_url('/admin/tool/dataprivacy/defaults.php', ['mode' => CONTEXT_BLOCK]);
|
||||
$data->coursecaturl = $coursecaturl;
|
||||
$data->courseurl = $courseurl;
|
||||
$data->moduleurl = $moduleurl;
|
||||
$data->blockurl = $blockurl;
|
||||
|
||||
// Set display mode.
|
||||
switch ($this->mode) {
|
||||
case CONTEXT_COURSECAT:
|
||||
$data->modecoursecat = true;
|
||||
break;
|
||||
case CONTEXT_COURSE:
|
||||
$data->modecourse = true;
|
||||
break;
|
||||
case CONTEXT_MODULE:
|
||||
$data->modemodule = true;
|
||||
break;
|
||||
case CONTEXT_BLOCK:
|
||||
$data->modeblock = true;
|
||||
break;
|
||||
default:
|
||||
$data->modecoursecat = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set config variables.
|
||||
$configname = \context_helper::get_class_for_level($this->mode);
|
||||
list($purposevar, $categoryvar) = data_registry::var_names_from_context($configname);
|
||||
$data->categoryvar = $categoryvar;
|
||||
$data->purposevar = $purposevar;
|
||||
|
||||
// Set default category.
|
||||
$data->categoryid = $this->category;
|
||||
$data->category = category_exporter::get_name($this->category);
|
||||
|
||||
// Set default purpose.
|
||||
$data->purposeid = $this->purpose;
|
||||
$data->purpose = purpose_exporter::get_name($this->purpose);
|
||||
|
||||
// Set other defaults.
|
||||
$otherdefaults = [];
|
||||
$url = new moodle_url('#');
|
||||
foreach ($this->otherdefaults as $pluginname => $values) {
|
||||
$defaults = [
|
||||
'name' => $values->name,
|
||||
'category' => category_exporter::get_name($values->category),
|
||||
'purpose' => purpose_exporter::get_name($values->purpose),
|
||||
];
|
||||
if ($this->canedit) {
|
||||
$actions = [];
|
||||
// Edit link.
|
||||
$editattrs = [
|
||||
'data-action' => 'edit-activity-defaults',
|
||||
'data-contextlevel' => $this->mode,
|
||||
'data-activityname' => $pluginname,
|
||||
'data-category' => $values->category,
|
||||
'data-purpose' => $values->purpose,
|
||||
];
|
||||
$editlink = new action_menu_link_primary($url, new \pix_icon('t/edit', get_string('edit')),
|
||||
get_string('edit'), $editattrs);
|
||||
$actions[] = $editlink->export_for_template($output);
|
||||
|
||||
// Delete link.
|
||||
$deleteattrs = [
|
||||
'data-action' => 'delete-activity-defaults',
|
||||
'data-contextlevel' => $this->mode,
|
||||
'data-activityname' => $pluginname,
|
||||
'data-activitydisplayname' => $values->name,
|
||||
];
|
||||
$deletelink = new action_menu_link_primary($url, new \pix_icon('t/delete', get_string('delete')),
|
||||
get_string('delete'), $deleteattrs);
|
||||
$actions[] = $deletelink->export_for_template($output);
|
||||
|
||||
$defaults['actions'] = $actions;
|
||||
}
|
||||
$otherdefaults[] = (object)$defaults;
|
||||
}
|
||||
$data->otherdefaults = $otherdefaults;
|
||||
|
||||
$data->canedit = $this->canedit;
|
||||
$data->contextlevel = $this->mode;
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the class used for the displaying the expired contexts table.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/tablelib.php');
|
||||
|
||||
use coding_exception;
|
||||
use context_helper;
|
||||
use dml_exception;
|
||||
use Exception;
|
||||
use html_writer;
|
||||
use pix_icon;
|
||||
use stdClass;
|
||||
use table_sql;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\expired_context;
|
||||
use tool_dataprivacy\external\purpose_exporter;
|
||||
use tool_dataprivacy\purpose;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
/**
|
||||
* The class for displaying the expired contexts table.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class expired_contexts_table extends table_sql {
|
||||
|
||||
/** @var int The context level acting as a filter for this table. */
|
||||
protected $contextlevel = null;
|
||||
|
||||
/**
|
||||
* @var bool $selectall Has the user selected all users on the page? True by default.
|
||||
*/
|
||||
protected $selectall = true;
|
||||
|
||||
/** @var purpose[] Array of purposes mapped to the contexts. */
|
||||
protected $purposes = [];
|
||||
|
||||
/**
|
||||
* expired_contexts_table constructor.
|
||||
*
|
||||
* @param int|null $contextlevel
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function __construct($contextlevel = null) {
|
||||
parent::__construct('expired-contexts-table');
|
||||
|
||||
$this->contextlevel = $contextlevel;
|
||||
|
||||
$columnheaders = [
|
||||
'name' => get_string('name'),
|
||||
'info' => get_string('info'),
|
||||
'purpose' => get_string('purpose', 'tool_dataprivacy'),
|
||||
'category' => get_string('category', 'tool_dataprivacy'),
|
||||
'retentionperiod' => get_string('retentionperiod', 'tool_dataprivacy'),
|
||||
'timecreated' => get_string('expiry', 'tool_dataprivacy'),
|
||||
];
|
||||
$checkboxattrs = [
|
||||
'title' => get_string('selectall'),
|
||||
'data-action' => 'selectall'
|
||||
];
|
||||
$columnheaders['select'] = html_writer::checkbox('selectall', 1, true, null, $checkboxattrs);
|
||||
|
||||
$this->define_columns(array_keys($columnheaders));
|
||||
$this->define_headers(array_values($columnheaders));
|
||||
$this->no_sorting('name');
|
||||
$this->no_sorting('select');
|
||||
$this->no_sorting('info');
|
||||
$this->no_sorting('purpose');
|
||||
$this->no_sorting('category');
|
||||
$this->no_sorting('retentionperiod');
|
||||
|
||||
// Make this table sorted by first name by default.
|
||||
$this->sortable(true, 'timecreated');
|
||||
}
|
||||
|
||||
/**
|
||||
* The context name column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function col_name($data) {
|
||||
global $OUTPUT;
|
||||
$context = context_helper::instance_by_id($data->contextid);
|
||||
$parent = $context->get_parent_context();
|
||||
$contextdata = (object)[
|
||||
'name' => $context->get_context_name(false, true),
|
||||
'parent' => $parent->get_context_name(false, true),
|
||||
];
|
||||
$fullcontexts = $context->get_parent_contexts(true);
|
||||
$contextsinpath = [];
|
||||
foreach ($fullcontexts as $contextinpath) {
|
||||
$contextsinpath[] = $contextinpath->get_context_name(false, true);
|
||||
}
|
||||
$infoicon = new pix_icon('i/info', implode(' / ', array_reverse($contextsinpath)));
|
||||
$infoiconhtml = $OUTPUT->render($infoicon);
|
||||
$name = html_writer::span(get_string('nameandparent', 'tool_dataprivacy', $contextdata), 'm-r-1');
|
||||
|
||||
return $name . $infoiconhtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* The context information column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function col_info($data) {
|
||||
global $OUTPUT;
|
||||
|
||||
$context = context_helper::instance_by_id($data->contextid);
|
||||
|
||||
$children = $context->get_child_contexts();
|
||||
if (empty($children)) {
|
||||
return get_string('none');
|
||||
} else {
|
||||
$childnames = [];
|
||||
foreach ($children as $child) {
|
||||
$childnames[] = $child->get_context_name(false, true);
|
||||
}
|
||||
$infoicon = new pix_icon('i/info', implode(', ', $childnames));
|
||||
$infoiconhtml = $OUTPUT->render($infoicon);
|
||||
$name = html_writer::span(get_string('nchildren', 'tool_dataprivacy', count($children)), 'm-r-1');
|
||||
|
||||
return $name . $infoiconhtml;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The category name column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return mixed
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public function col_category($data) {
|
||||
$context = context_helper::instance_by_id($data->contextid);
|
||||
$category = api::get_effective_context_category($context);
|
||||
|
||||
return s($category->get('name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The purpose column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function col_purpose($data) {
|
||||
$purpose = $this->purposes[$data->contextid];
|
||||
|
||||
return s($purpose->get('name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The retention period column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function col_retentionperiod($data) {
|
||||
global $PAGE;
|
||||
|
||||
$purpose = $this->purposes[$data->contextid];
|
||||
|
||||
$exporter = new purpose_exporter($purpose, ['context' => \context_system::instance()]);
|
||||
$exportedpurpose = $exporter->export($PAGE->get_renderer('core'));
|
||||
|
||||
return $exportedpurpose->formattedretentionperiod;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timecreated a.k.a. the context expiry date column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_timecreated($data) {
|
||||
return userdate($data->timecreated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the select column.
|
||||
*
|
||||
* @param stdClass $data The row data.
|
||||
* @return string
|
||||
*/
|
||||
public function col_select($data) {
|
||||
$id = $data->id;
|
||||
return html_writer::checkbox('expiredcontext_' . $id, $id, $this->selectall, '', ['class' => 'selectcontext']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the database for results to display in the table.
|
||||
*
|
||||
* @param int $pagesize size of page for paginated displayed table.
|
||||
* @param bool $useinitialsbar do you want to use the initials bar.
|
||||
* @throws dml_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function query_db($pagesize, $useinitialsbar = true) {
|
||||
// Only count expired contexts that are awaiting confirmation.
|
||||
$total = expired_context::get_record_count_by_contextlevel($this->contextlevel, expired_context::STATUS_EXPIRED);
|
||||
$this->pagesize($pagesize, $total);
|
||||
|
||||
$sort = $this->get_sql_sort();
|
||||
if (empty($sort)) {
|
||||
$sort = 'timecreated';
|
||||
}
|
||||
|
||||
// Only load expired contexts that are awaiting confirmation.
|
||||
$expiredcontexts = expired_context::get_records_by_contextlevel($this->contextlevel, expired_context::STATUS_EXPIRED,
|
||||
$sort, $this->get_page_start(), $this->get_page_size());
|
||||
$this->rawdata = [];
|
||||
foreach ($expiredcontexts as $persistent) {
|
||||
$data = $persistent->to_record();
|
||||
|
||||
$context = context_helper::instance_by_id($data->contextid);
|
||||
|
||||
$purpose = api::get_effective_context_purpose($context);
|
||||
$this->purposes[$data->contextid] = $purpose;
|
||||
$this->rawdata[] = $data;
|
||||
}
|
||||
|
||||
// Set initial bars.
|
||||
if ($useinitialsbar) {
|
||||
$this->initialbars($total > $pagesize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override default implementation to display a more meaningful information to the user.
|
||||
*/
|
||||
public function print_nothing_to_display() {
|
||||
global $OUTPUT;
|
||||
echo $this->render_reset_button();
|
||||
$this->print_initials_bar();
|
||||
echo $OUTPUT->notification(get_string('noexpiredcontexts', 'tool_dataprivacy'), 'warning');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the table's show_hide_link method to prevent the show/hide link for the select column from rendering.
|
||||
*
|
||||
* @param string $column the column name, index into various names.
|
||||
* @param int $index numerical index of the column.
|
||||
* @return string HTML fragment.
|
||||
*/
|
||||
protected function show_hide_link($column, $index) {
|
||||
if ($index < 6) {
|
||||
return parent::show_hide_link($column, $index);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?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 containing data for a user's data requests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use action_menu;
|
||||
use action_menu_link_secondary;
|
||||
use coding_exception;
|
||||
use context_user;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
|
||||
/**
|
||||
* Class containing data for a user's data requests.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class my_data_requests_page implements renderable, templatable {
|
||||
|
||||
/** @var array $requests List of data requests. */
|
||||
protected $requests = [];
|
||||
|
||||
/**
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param data_request[] $requests
|
||||
*/
|
||||
public function __construct($requests) {
|
||||
$this->requests = $requests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
* @throws coding_exception
|
||||
* @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');
|
||||
$type = $request->get('type');
|
||||
|
||||
$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);
|
||||
|
||||
$self = $request->get('userid') == $USER->id;
|
||||
if (!$self) {
|
||||
// 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) {
|
||||
case api::DATAREQUEST_STATUS_COMPLETE:
|
||||
$item->statuslabelclass = 'label-success';
|
||||
$item->statuslabel = get_string('statuscomplete', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$item->statuslabelclass = 'label-success';
|
||||
$item->statuslabel = get_string('statusready', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
$candownload = true;
|
||||
|
||||
if ($usercontext) {
|
||||
$candownload = api::can_download_data_request_for_user(
|
||||
$request->get('userid'), $request->get('requestedby'));
|
||||
}
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_DELETED:
|
||||
$item->statuslabelclass = 'label-success';
|
||||
$item->statuslabel = get_string('statusdeleted', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$item->statuslabelclass = 'label-default';
|
||||
$item->statuslabel = get_string('statusexpired', 'tool_dataprivacy');
|
||||
$item->statuslabeltitle = get_string('downloadexpireduser', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_CANCELLED:
|
||||
case api::DATAREQUEST_STATUS_REJECTED:
|
||||
$cancancel = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Prepare actions.
|
||||
$actions = [];
|
||||
if ($cancancel) {
|
||||
$cancelurl = new moodle_url('#');
|
||||
$canceldata = ['data-action' => 'cancel', 'data-requestid' => $requestid];
|
||||
$canceltext = get_string('cancelrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($cancelurl, null, $canceltext, $canceldata);
|
||||
}
|
||||
if ($candownload && $usercontext) {
|
||||
$actions[] = api::get_download_link($usercontext, $requestid);
|
||||
}
|
||||
if (!empty($actions)) {
|
||||
$actionsmenu = new action_menu($actions);
|
||||
$actionsmenu->set_menu_trigger(get_string('actions'));
|
||||
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
|
||||
$actionsmenu->set_alignment(\action_menu::TL, \action_menu::BL);
|
||||
$item->actions = $actionsmenu->export_for_template($output);
|
||||
}
|
||||
|
||||
$requests[] = $item;
|
||||
}
|
||||
$data->requests = $requests;
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Purposes renderable.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use tool_dataprivacy\external\purpose_exporter;
|
||||
|
||||
/**
|
||||
* Class containing the purposes page renderable.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class purposes extends crud_element implements renderable, templatable {
|
||||
|
||||
/** @var array $purposes All system purposes. */
|
||||
protected $purposes = [];
|
||||
|
||||
/**
|
||||
* Construct this renderable.
|
||||
*
|
||||
* @param \tool_dataprivacy\purpose[] $purposes
|
||||
*/
|
||||
public function __construct($purposes) {
|
||||
$this->purposes = $purposes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
global $PAGE;
|
||||
|
||||
$context = \context_system::instance();
|
||||
|
||||
$PAGE->requires->js_call_amd('tool_dataprivacy/purposesactions', 'init');
|
||||
$PAGE->requires->js_call_amd('tool_dataprivacy/add_purpose', 'getInstance', [$context->id]);
|
||||
|
||||
$data = new stdClass();
|
||||
|
||||
// Navigation links.
|
||||
$data->navigation = [];
|
||||
$navigationlinks = $this->get_navigation();
|
||||
foreach ($navigationlinks as $navlink) {
|
||||
$data->navigation[] = $navlink->export_for_template($output);
|
||||
}
|
||||
|
||||
$data->purposes = [];
|
||||
foreach ($this->purposes as $purpose) {
|
||||
$exporter = new purpose_exporter($purpose, ['context' => \context_system::instance()]);
|
||||
$exportedpurpose = $exporter->export($output);
|
||||
|
||||
$actionmenu = $this->action_menu('purpose', $exportedpurpose, $purpose);
|
||||
$exportedpurpose->actions = $actionmenu->export_for_template($output);
|
||||
$data->purposes[] = $exportedpurpose;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
/**
|
||||
* Renderer class for tool_dataprivacy
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\output;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use html_writer;
|
||||
use moodle_exception;
|
||||
use plugin_renderer_base;
|
||||
|
||||
/**
|
||||
* Renderer class for tool_dataprivacy.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class renderer extends plugin_renderer_base {
|
||||
|
||||
/**
|
||||
* Render the user's data requests page.
|
||||
*
|
||||
* @param my_data_requests_page $page
|
||||
* @return string html for the page
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function render_my_data_requests_page(my_data_requests_page $page) {
|
||||
$data = $page->export_for_template($this);
|
||||
return parent::render_from_template('tool_dataprivacy/my_data_requests', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the contact DPO link.
|
||||
*
|
||||
* @param string $replytoemail The Reply-to email address
|
||||
* @return string The HTML for the link.
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function render_contact_dpo_link($replytoemail) {
|
||||
$params = [
|
||||
'data-action' => 'contactdpo',
|
||||
'data-replytoemail' => $replytoemail,
|
||||
'class' => 'contactdpo'
|
||||
];
|
||||
return html_writer::link('#', get_string('contactdataprotectionofficer', 'tool_dataprivacy'), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the data requests page for the DPO.
|
||||
*
|
||||
* @param data_requests_page $page
|
||||
* @return string html for the page
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function render_data_requests_page(data_requests_page $page) {
|
||||
$data = $page->export_for_template($this);
|
||||
return parent::render_from_template('tool_dataprivacy/data_requests', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the data registry.
|
||||
*
|
||||
* @param data_registry_page $page
|
||||
* @return string html for the page
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function render_data_registry_page(data_registry_page $page) {
|
||||
$data = $page->export_for_template($this);
|
||||
return parent::render_from_template('tool_dataprivacy/data_registry', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the data compliance registry.
|
||||
*
|
||||
* @param data_registry_compliance_page $page
|
||||
* @return string html for the page
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function render_data_registry_compliance_page(data_registry_compliance_page $page) {
|
||||
$data = $page->export_for_template($this);
|
||||
return parent::render_from_template('tool_dataprivacy/data_registry_compliance', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the purposes management page.
|
||||
*
|
||||
* @param purposes $page
|
||||
* @return string html for the page
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function render_purposes(purposes $page) {
|
||||
$data = $page->export_for_template($this);
|
||||
return parent::render_from_template('tool_dataprivacy/purposes', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the categories management page.
|
||||
*
|
||||
* @param categories $page
|
||||
* @return string html for the page
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function render_categories(categories $page) {
|
||||
$data = $page->export_for_template($this);
|
||||
return parent::render_from_template('tool_dataprivacy/categories', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the review page for the deletion of expired contexts.
|
||||
*
|
||||
* @param data_deletion_page $page
|
||||
* @return string html for the page
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function render_data_deletion_page(data_deletion_page $page) {
|
||||
$data = $page->export_for_template($this);
|
||||
return parent::render_from_template('tool_dataprivacy/data_deletion', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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 containing the filter options data for rendering the autocomplete element for the data requests page.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\output;
|
||||
|
||||
use moodle_url;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class containing the filter options data for rendering the autocomplete element for the data requests page.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class request_filter implements renderable, templatable {
|
||||
|
||||
/** @var array $filteroptions The filter options. */
|
||||
protected $filteroptions;
|
||||
|
||||
/** @var array $selectedoptions The list of selected filter option values. */
|
||||
protected $selectedoptions;
|
||||
|
||||
/** @var moodle_url|string $baseurl The url with params needed to call up this page. */
|
||||
protected $baseurl;
|
||||
|
||||
/**
|
||||
* request_filter constructor.
|
||||
*
|
||||
* @param array $filteroptions The filter options.
|
||||
* @param array $selectedoptions The list of selected filter option values.
|
||||
* @param string|moodle_url $baseurl The url with params needed to call up this page.
|
||||
*/
|
||||
public function __construct($filteroptions, $selectedoptions, $baseurl = null) {
|
||||
$this->filteroptions = $filteroptions;
|
||||
$this->selectedoptions = $selectedoptions;
|
||||
if (!empty($baseurl)) {
|
||||
$this->baseurl = new moodle_url($baseurl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to export the renderer data in a format that is suitable for a mustache template.
|
||||
*
|
||||
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
|
||||
* @return stdClass|array
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
global $PAGE;
|
||||
$data = new stdClass();
|
||||
if (empty($this->baseurl)) {
|
||||
$this->baseurl = $PAGE->url;
|
||||
}
|
||||
$data->action = $this->baseurl->out(false);
|
||||
|
||||
foreach ($this->selectedoptions as $option) {
|
||||
if (!isset($this->filteroptions[$option])) {
|
||||
$this->filteroptions[$option] = $option;
|
||||
}
|
||||
}
|
||||
|
||||
$data->filteroptions = [];
|
||||
foreach ($this->filteroptions as $value => $label) {
|
||||
$selected = in_array($value, $this->selectedoptions);
|
||||
$filteroption = (object)[
|
||||
'value' => $value,
|
||||
'label' => $label
|
||||
];
|
||||
$filteroption->selected = $selected;
|
||||
$data->filteroptions[] = $filteroption;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Page helper.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy;
|
||||
use context_system;
|
||||
use moodle_url;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Page helper.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class page_helper {
|
||||
|
||||
/**
|
||||
* Sets up $PAGE for data privacy admin pages.
|
||||
*
|
||||
* @param moodle_url $url The page URL.
|
||||
* @param string $title The page's title.
|
||||
* @param string $attachtoparentnode The parent navigation node where this page can be accessed from.
|
||||
* @param string $requiredcapability The required capability to view this page.
|
||||
*/
|
||||
public static function setup(moodle_url $url, $title, $attachtoparentnode = '',
|
||||
$requiredcapability = 'tool/dataprivacy:managedataregistry') {
|
||||
global $PAGE, $SITE;
|
||||
|
||||
$context = context_system::instance();
|
||||
|
||||
require_login();
|
||||
if (isguestuser()) {
|
||||
print_error('noguest');
|
||||
}
|
||||
|
||||
// TODO Check that data privacy is enabled.
|
||||
require_capability($requiredcapability, $context);
|
||||
|
||||
$PAGE->navigation->override_active_url($url);
|
||||
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_pagelayout('admin');
|
||||
$PAGE->set_title($title);
|
||||
$PAGE->set_heading($SITE->fullname);
|
||||
|
||||
// If necessary, override the settings navigation to add this page into the breadcrumb navigation.
|
||||
if ($attachtoparentnode) {
|
||||
if ($siteadmin = $PAGE->settingsnav->find('root', \navigation_node::TYPE_SITE_ADMIN)) {
|
||||
$PAGE->navbar->add($siteadmin->get_content(), $siteadmin->action());
|
||||
}
|
||||
if ($dataprivacy = $PAGE->settingsnav->find('privacy', \navigation_node::TYPE_SETTING)) {
|
||||
$PAGE->navbar->add($dataprivacy->get_content(), $dataprivacy->action());
|
||||
}
|
||||
if ($dataregistry = $PAGE->settingsnav->find($attachtoparentnode, \navigation_node::TYPE_SETTING)) {
|
||||
$PAGE->navbar->add($dataregistry->get_content(), $dataregistry->action());
|
||||
}
|
||||
|
||||
$PAGE->navbar->add($title, $url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
<?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 class for requesting user data.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\privacy;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use coding_exception;
|
||||
use context;
|
||||
use context_user;
|
||||
use core_privacy\local\metadata\collection;
|
||||
use core_privacy\local\request\approved_contextlist;
|
||||
use core_privacy\local\request\contextlist;
|
||||
use core_privacy\local\request\helper;
|
||||
use core_privacy\local\request\transform;
|
||||
use core_privacy\local\request\writer;
|
||||
use dml_exception;
|
||||
use stdClass;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\local\helper as tool_helper;
|
||||
|
||||
/**
|
||||
* Privacy class for requesting user data.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements
|
||||
// This tool stores user data.
|
||||
\core_privacy\local\metadata\provider,
|
||||
|
||||
// This tool may provide access to and deletion of user data.
|
||||
\core_privacy\local\request\plugin\provider,
|
||||
|
||||
// This plugin has some sitewide user preferences to export.
|
||||
\core_privacy\local\request\user_preference_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->add_database_table(
|
||||
'tool_dataprivacy_request',
|
||||
[
|
||||
'comments' => 'privacy:metadata:request:comments',
|
||||
'userid' => 'privacy:metadata:request:userid',
|
||||
'requestedby' => 'privacy:metadata:request:requestedby',
|
||||
'dpocomment' => 'privacy:metadata:request:dpocomment',
|
||||
'timecreated' => 'privacy:metadata:request:timecreated'
|
||||
],
|
||||
'privacy:metadata:request'
|
||||
);
|
||||
|
||||
$collection->add_user_preference(tool_helper::PREF_REQUEST_FILTERS,
|
||||
'privacy:metadata:preference:tool_dataprivacy_request-filters');
|
||||
$collection->add_user_preference(tool_helper::PREF_REQUEST_PERPAGE,
|
||||
'privacy:metadata:preference:tool_dataprivacy_request-perpage');
|
||||
|
||||
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($userid) {
|
||||
$sql = "SELECT id
|
||||
FROM {context}
|
||||
WHERE instanceid = :userid
|
||||
AND contextlevel = :contextlevel";
|
||||
|
||||
$contextlist = new contextlist();
|
||||
$contextlist->set_component('tool_dataprivacy');
|
||||
$contextlist->add_from_sql($sql, ['userid' => $userid, 'contextlevel' => CONTEXT_USER]);
|
||||
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.
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
* @throws \moodle_exception
|
||||
*/
|
||||
public static function export_user_data(approved_contextlist $contextlist) {
|
||||
if (empty($contextlist->count())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $contextlist->get_user();
|
||||
$datarequests = api::get_data_requests($user->id);
|
||||
$context = context_user::instance($user->id);
|
||||
$contextdatatowrite = [];
|
||||
foreach ($datarequests as $request) {
|
||||
$record = $request->to_record();
|
||||
$data = new stdClass();
|
||||
|
||||
// The user ID that made the request/the request is made for.
|
||||
if ($record->requestedby != $record->userid) {
|
||||
if ($user->id != $record->requestedby) {
|
||||
// This request is done by this user for another user.
|
||||
$data->userid = fullname($user);
|
||||
} else if ($user->id != $record->userid) {
|
||||
// This request was done by another user on behalf of this user.
|
||||
$data->requestedby = fullname($user);
|
||||
}
|
||||
}
|
||||
|
||||
// Request type.
|
||||
$data->type = tool_helper::get_shortened_request_type_string($record->type);
|
||||
// Status.
|
||||
$data->status = tool_helper::get_request_status_string($record->status);
|
||||
// Creation method.
|
||||
$data->creationmethod = tool_helper::get_request_creation_method_string($record->creationmethod);
|
||||
// Comments.
|
||||
$data->comments = $record->comments;
|
||||
// The DPO's comment about this request.
|
||||
$data->dpocomment = $record->dpocomment;
|
||||
// The date and time this request was lodged.
|
||||
$data->timecreated = transform::datetime($record->timecreated);
|
||||
$contextdatatowrite[] = $data;
|
||||
}
|
||||
|
||||
// ...{User context} / Privacy and policies / Data requests.
|
||||
$subcontext = [
|
||||
get_string('privacyandpolicies', 'admin'),
|
||||
get_string('datarequests', 'tool_dataprivacy'),
|
||||
];
|
||||
writer::with_context($context)->export_data($subcontext, (object)$contextdatatowrite);
|
||||
|
||||
// Write generic module intro files.
|
||||
helper::export_context_files($context, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($userid) {
|
||||
$preffilter = get_user_preferences(tool_helper::PREF_REQUEST_FILTERS, null, $userid);
|
||||
if ($preffilter !== null) {
|
||||
$filters = json_decode($preffilter);
|
||||
$descriptions = [];
|
||||
foreach ($filters as $filter) {
|
||||
list($category, $value) = explode(':', $filter);
|
||||
$option = new stdClass();
|
||||
switch($category) {
|
||||
case tool_helper::FILTER_TYPE:
|
||||
$option->category = get_string('requesttype', 'tool_dataprivacy');
|
||||
$option->name = tool_helper::get_shortened_request_type_string($value);
|
||||
break;
|
||||
case tool_helper::FILTER_STATUS:
|
||||
$option->category = get_string('requeststatus', 'tool_dataprivacy');
|
||||
$option->name = tool_helper::get_request_status_string($value);
|
||||
break;
|
||||
case tool_helper::FILTER_CREATION:
|
||||
$option->category = get_string('requestcreation', 'tool_dataprivacy');
|
||||
$option->name = tool_helper::get_request_creation_method_string($value);
|
||||
break;
|
||||
}
|
||||
$descriptions[] = get_string('filteroption', 'tool_dataprivacy', $option);
|
||||
}
|
||||
// Export the filter preference as comma-separated values and text descriptions.
|
||||
$values = implode(', ', $filters);
|
||||
$descriptionstext = implode(', ', $descriptions);
|
||||
writer::export_user_preference('tool_dataprivacy', tool_helper::PREF_REQUEST_FILTERS, $values, $descriptionstext);
|
||||
}
|
||||
|
||||
$prefperpage = get_user_preferences(tool_helper::PREF_REQUEST_PERPAGE, null, $userid);
|
||||
if ($prefperpage !== null) {
|
||||
writer::export_user_preference('tool_dataprivacy', tool_helper::PREF_REQUEST_PERPAGE, $prefperpage,
|
||||
get_string('privacy:metadata:preference:tool_dataprivacy_request-perpage', 'tool_dataprivacy'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?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 for loading/storing data purposes from the DB.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use stdClass;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
/**
|
||||
* Class for loading/storing data purposes from the DB.
|
||||
*
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class purpose extends \core\persistent {
|
||||
|
||||
/**
|
||||
* Database table.
|
||||
*/
|
||||
const TABLE = 'tool_dataprivacy_purpose';
|
||||
|
||||
/** Items under GDPR Article 6.1. */
|
||||
const GDPR_ART_6_1_ITEMS = ['a', 'b', 'c', 'd', 'e', 'f'];
|
||||
|
||||
/** Items under GDPR Article 9.2. */
|
||||
const GDPR_ART_9_2_ITEMS = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
|
||||
|
||||
/**
|
||||
* Extended constructor to fetch from the cache if available.
|
||||
*
|
||||
* @param int $id If set, this is the id of an existing record, used to load the data.
|
||||
* @param stdClass $record If set will be passed to {@link self::from_record()}.
|
||||
*/
|
||||
public function __construct($id = 0, stdClass $record = null) {
|
||||
global $CFG;
|
||||
|
||||
if ($id) {
|
||||
$cache = \cache::make('tool_dataprivacy', 'purpose');
|
||||
if ($data = $cache->get($id)) {
|
||||
|
||||
// Replicate self::read.
|
||||
$this->from_record($data);
|
||||
|
||||
// Validate the purpose record.
|
||||
$this->validate();
|
||||
|
||||
// Now replicate the parent constructor.
|
||||
if (!empty($record)) {
|
||||
$this->from_record($record);
|
||||
}
|
||||
if ($CFG->debugdeveloper) {
|
||||
$this->verify_protected_methods();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
parent::__construct($id, $record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return array(
|
||||
'name' => array(
|
||||
'type' => PARAM_TEXT,
|
||||
'description' => 'The purpose name.',
|
||||
),
|
||||
'description' => array(
|
||||
'type' => PARAM_RAW,
|
||||
'description' => 'The purpose description.',
|
||||
'null' => NULL_ALLOWED,
|
||||
'default' => '',
|
||||
),
|
||||
'descriptionformat' => array(
|
||||
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
|
||||
'type' => PARAM_INT,
|
||||
'default' => FORMAT_HTML
|
||||
),
|
||||
'lawfulbases' => array(
|
||||
'type' => PARAM_TEXT,
|
||||
'description' => 'Comma-separated IDs matching records in tool_dataprivacy_lawfulbasis.',
|
||||
),
|
||||
'sensitivedatareasons' => array(
|
||||
'type' => PARAM_TEXT,
|
||||
'description' => 'Comma-separated IDs matching records in tool_dataprivacy_sensitive',
|
||||
'null' => NULL_ALLOWED,
|
||||
'default' => ''
|
||||
),
|
||||
'retentionperiod' => array(
|
||||
'type' => PARAM_ALPHANUM,
|
||||
'description' => 'Retention period. ISO_8601 durations format (as in DateInterval format).',
|
||||
'default' => '',
|
||||
),
|
||||
'protected' => array(
|
||||
'type' => PARAM_INT,
|
||||
'description' => 'Data retention with higher precedent over user\'s request to be forgotten.',
|
||||
'default' => '0',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the new record to the cache.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function after_create() {
|
||||
$cache = \cache::make('tool_dataprivacy', 'purpose');
|
||||
$cache->set($this->get('id'), $this->to_record());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cache record.
|
||||
*
|
||||
* @param bool $result
|
||||
* @return null
|
||||
*/
|
||||
protected function after_update($result) {
|
||||
$cache = \cache::make('tool_dataprivacy', 'purpose');
|
||||
$cache->set($this->get('id'), $this->to_record());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unnecessary stuff from db.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function before_delete() {
|
||||
$cache = \cache::make('tool_dataprivacy', 'purpose');
|
||||
$cache->delete($this->get('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this purpose used?.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function is_used() {
|
||||
|
||||
if (\tool_dataprivacy\contextlevel::is_purpose_used($this->get('id')) ||
|
||||
\tool_dataprivacy\context_instance::is_purpose_used($this->get('id'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$pluginconfig = get_config('tool_dataprivacy');
|
||||
$levels = \context_helper::get_all_levels();
|
||||
foreach ($levels as $level => $classname) {
|
||||
|
||||
list($purposevar, $unused) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
|
||||
if (!empty($pluginconfig->{$purposevar}) && $pluginconfig->{$purposevar} == $this->get('id')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Contains the request_contextlist persistent.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core\persistent;
|
||||
|
||||
/**
|
||||
* The request_contextlist persistent.
|
||||
*
|
||||
* @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class request_contextlist extends persistent {
|
||||
|
||||
/** The table name this persistent object maps to. */
|
||||
const TABLE = 'tool_dataprivacy_rqst_ctxlst';
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties() {
|
||||
return [
|
||||
'requestid' => [
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'contextlistid' => [
|
||||
'type' => PARAM_INT
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new relation, but does not persist it.
|
||||
*
|
||||
* @param int $requestid
|
||||
* @param int $contextlistid
|
||||
* @return $this
|
||||
* @throws \coding_exception
|
||||
*/
|
||||
public static function create_relation($requestid, $contextlistid) {
|
||||
$requestcontextlist = new request_contextlist();
|
||||
return $requestcontextlist->set('requestid', $requestid)
|
||||
->set('contextlistid', $contextlistid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Scheduled task to create delete data request for pre-existing deleted users.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Mihail Geshoski
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\task;
|
||||
|
||||
use core\task\scheduled_task;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
/**
|
||||
* Scheduled task to create delete data request for pre-existing deleted users.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Mihail Geshoski
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class delete_existing_deleted_users extends scheduled_task {
|
||||
|
||||
/**
|
||||
* Returns the task name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('deleteexistingdeleteduserstask', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the task to delete expired data request files and update request statuses.
|
||||
*
|
||||
*/
|
||||
public function execute() {
|
||||
global $DB;
|
||||
|
||||
// Automatic creation of deletion requests must be enabled.
|
||||
if (get_config('tool_dataprivacy', 'automaticdeletionrequests')) {
|
||||
// Select all deleted users that do not have any delete data requests created for them.
|
||||
$sql = "SELECT DISTINCT(u.id)
|
||||
FROM {user} u
|
||||
LEFT JOIN {tool_dataprivacy_request} r
|
||||
ON u.id = r.userid
|
||||
WHERE u.deleted = ?
|
||||
AND (r.id IS NULL
|
||||
OR r.type != ?)";
|
||||
|
||||
$params = [
|
||||
1,
|
||||
api::DATAREQUEST_TYPE_DELETE
|
||||
];
|
||||
|
||||
$deletedusers = $DB->get_records_sql($sql, $params);
|
||||
$createdrequests = 0;
|
||||
|
||||
foreach ($deletedusers as $user) {
|
||||
api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE,
|
||||
get_string('datarequestcreatedfromscheduledtask', 'tool_dataprivacy'),
|
||||
data_request::DATAREQUEST_CREATION_AUTO);
|
||||
$createdrequests++;
|
||||
}
|
||||
|
||||
if ($createdrequests > 0) {
|
||||
mtrace($createdrequests . ' delete data request(s) created for existing deleted users');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Scheduled task to delete expired context instances once they are approved for deletion.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\task;
|
||||
|
||||
use coding_exception;
|
||||
use core\task\scheduled_task;
|
||||
use tool_dataprivacy\api;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
/**
|
||||
* Scheduled task to delete expired context instances once they are approved for deletion.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class delete_expired_contexts extends scheduled_task {
|
||||
|
||||
/**
|
||||
* Returns the task name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('deleteexpiredcontextstask', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the task to delete context instances based on their retention periods.
|
||||
*/
|
||||
public function execute() {
|
||||
$manager = new \tool_dataprivacy\expired_contexts_manager();
|
||||
list($courses, $users) = $manager->process_approved_deletions();
|
||||
mtrace("Processed deletions for {$courses} course contexts, and {$users} user contexts as expired");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Scheduled task to delete files and update statuses of expired data requests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\task;
|
||||
|
||||
use coding_exception;
|
||||
use core\task\scheduled_task;
|
||||
use tool_dataprivacy\api;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
/**
|
||||
* Scheduled task to delete files and update request statuses once they expire.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class delete_expired_requests extends scheduled_task {
|
||||
|
||||
/**
|
||||
* Returns the task name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('deleteexpireddatarequeststask', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the task to delete expired data request files and update request statuses.
|
||||
*
|
||||
*/
|
||||
public function execute() {
|
||||
$expiredrequests = \tool_dataprivacy\data_request::get_expired_requests();
|
||||
$deletecount = count($expiredrequests);
|
||||
|
||||
if ($deletecount > 0) {
|
||||
\tool_dataprivacy\data_request::expire($expiredrequests);
|
||||
|
||||
mtrace($deletecount . ' expired completed data requests have been deleted');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Scheduled task to flag contexts as expired.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\task;
|
||||
|
||||
use coding_exception;
|
||||
use core\task\scheduled_task;
|
||||
use tool_dataprivacy\api;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
/**
|
||||
* Scheduled task to flag contexts as expired.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class expired_retention_period extends scheduled_task {
|
||||
|
||||
/**
|
||||
* Returns the task name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('expiredretentionperiodtask', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the task to flag context instances as expired.
|
||||
*/
|
||||
public function execute() {
|
||||
$manager = new \tool_dataprivacy\expired_contexts_manager();
|
||||
list($courses, $users) = $manager->flag_expired_contexts();
|
||||
mtrace("Flagged {$courses} course contexts, and {$users} user contexts as expired");
|
||||
}
|
||||
}
|
||||
@@ -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/>.
|
||||
|
||||
/**
|
||||
* Adhoc task that processes a data request and prepares the user's relevant contexts for review.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\task;
|
||||
|
||||
use coding_exception;
|
||||
use core\task\adhoc_task;
|
||||
use moodle_exception;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\contextlist_context;
|
||||
use tool_dataprivacy\data_request;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class that processes a data request and prepares the user's relevant contexts for review.
|
||||
*
|
||||
* Custom data accepted:
|
||||
* - requestid -> The ID of the data request to be processed.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class initiate_data_request_task extends adhoc_task {
|
||||
|
||||
/**
|
||||
* Run the task to initiate the data request process.
|
||||
*
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function execute() {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php");
|
||||
|
||||
if (!isset($this->get_custom_data()->requestid)) {
|
||||
throw new coding_exception('The custom data \'requestid\' is required.');
|
||||
}
|
||||
$requestid = $this->get_custom_data()->requestid;
|
||||
|
||||
$datarequest = new data_request($requestid);
|
||||
|
||||
// Check if this request still needs to be processed. e.g. The user might have cancelled it before this task has run.
|
||||
$status = $datarequest->get('status');
|
||||
if (!api::is_active($status)) {
|
||||
mtrace('Request ' . $requestid . ' with status ' . $status . ' doesn\'t need to be processed. Skipping...');
|
||||
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);
|
||||
|
||||
// When the preparation of the contexts finishes, update the request status to awaiting approval.
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
|
||||
mtrace('Context generation complete...');
|
||||
|
||||
// Get the list of the site Data Protection Officers.
|
||||
$dpos = api::get_site_dpos();
|
||||
|
||||
// We should prevent DPO(s)/Admin(s) being flooded with notifications for each request when bulk delete
|
||||
// data requests are being created.
|
||||
// NOTE: This should be improved, we should not totally disable the notifications for automatically
|
||||
// created requests. Possibly, we should create one notification for these such cases.
|
||||
if ($datarequest->get('creationmethod') != data_request::DATAREQUEST_CREATION_AUTO) {
|
||||
// Email the data request to the Data Protection Officer(s)/Admin(s).
|
||||
foreach ($dpos as $dpo) {
|
||||
$dponame = fullname($dpo);
|
||||
if (api::notify_dpo($dpo, $datarequest)) {
|
||||
mtrace('Message sent to DPO: ' . $dponame);
|
||||
} else {
|
||||
mtrace('A problem was encountered while sending the message to the DPO: ' . $dponame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Adhoc task that processes an approved data request and prepares/deletes the user's data.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\task;
|
||||
|
||||
use action_link;
|
||||
use coding_exception;
|
||||
use core\message\message;
|
||||
use core\task\adhoc_task;
|
||||
use core_user;
|
||||
use moodle_exception;
|
||||
use moodle_url;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class that processes an approved data request and prepares/deletes the user's data.
|
||||
*
|
||||
* Custom data accepted:
|
||||
* - requestid -> The ID of the data request to be processed.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class process_data_request_task extends adhoc_task {
|
||||
|
||||
/**
|
||||
* Run the task to initiate the data request process.
|
||||
*
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function execute() {
|
||||
global $CFG, $PAGE, $SITE;
|
||||
|
||||
require_once($CFG->dirroot . "/{$CFG->admin}/tool/dataprivacy/lib.php");
|
||||
|
||||
if (!isset($this->get_custom_data()->requestid)) {
|
||||
throw new coding_exception('The custom data \'requestid\' is required.');
|
||||
}
|
||||
$requestid = $this->get_custom_data()->requestid;
|
||||
|
||||
$requestpersistent = new data_request($requestid);
|
||||
$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.
|
||||
$status = $requestpersistent->get('status');
|
||||
if (!api::is_active($status)) {
|
||||
mtrace("Request {$requestid} with status {$status} doesn't need to be processed. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the user details now. We might not be able to retrieve it later if it's a deletion processing.
|
||||
$foruser = core_user::get_user($request->userid);
|
||||
|
||||
// Update the status of this request as pre-processing.
|
||||
mtrace('Processing request...');
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PROCESSING);
|
||||
$completestatus = api::DATAREQUEST_STATUS_COMPLETE;
|
||||
$deleteuser = false;
|
||||
|
||||
if ($request->type == api::DATAREQUEST_TYPE_EXPORT) {
|
||||
// Run as the user performing the export.
|
||||
cron_setup_user($foruser);
|
||||
|
||||
// Get the user context.
|
||||
$usercontext = \context_user::instance($foruser->id, IGNORE_MISSING);
|
||||
if (!$usercontext) {
|
||||
mtrace("Request {$requestid} cannot be processed due to a missing user context instance for the user
|
||||
with ID {$foruser->id}. Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the collection of approved_contextlist objects needed for core_privacy data export.
|
||||
$approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
|
||||
|
||||
// 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();
|
||||
$filerecord = new \stdClass;
|
||||
$filerecord->component = 'tool_dataprivacy';
|
||||
$filerecord->contextid = $usercontext->id;
|
||||
$filerecord->userid = $foruser->id;
|
||||
$filerecord->filearea = 'export';
|
||||
$filerecord->filename = 'export.zip';
|
||||
$filerecord->filepath = '/';
|
||||
$filerecord->itemid = $requestid;
|
||||
$filerecord->license = $CFG->sitedefaultlicense;
|
||||
$filerecord->author = fullname($foruser);
|
||||
// Save somewhere.
|
||||
$thing = $fs->create_file_from_pathname($filerecord, $exportedcontent);
|
||||
$completestatus = api::DATAREQUEST_STATUS_DOWNLOAD_READY;
|
||||
} else if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
|
||||
// Get the collection of approved_contextlist objects needed for core_privacy data deletion.
|
||||
$approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
|
||||
|
||||
// Delete the data.
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$manager->delete_data_for_user($approvedclcollection);
|
||||
$completestatus = api::DATAREQUEST_STATUS_DELETED;
|
||||
$deleteuser = !$foruser->deleted;
|
||||
}
|
||||
|
||||
// When the preparation of the metadata finishes, update the request status to awaiting approval.
|
||||
api::update_request_status($requestid, $completestatus);
|
||||
mtrace('The processing of the user data request has been completed...');
|
||||
|
||||
// Create message to notify the user regarding the processing results.
|
||||
$dpo = core_user::get_user($request->dpo);
|
||||
$message = new message();
|
||||
$message->courseid = $SITE->id;
|
||||
$message->component = 'tool_dataprivacy';
|
||||
$message->name = 'datarequestprocessingresults';
|
||||
$message->userfrom = $dpo;
|
||||
$message->replyto = $dpo->email;
|
||||
$message->replytoname = fullname($dpo->email);
|
||||
|
||||
$typetext = null;
|
||||
// Prepare the context data for the email message body.
|
||||
$messagetextdata = [
|
||||
'username' => fullname($foruser)
|
||||
];
|
||||
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
$emailonly = false;
|
||||
$notifyuser = true;
|
||||
switch ($request->type) {
|
||||
case api::DATAREQUEST_TYPE_EXPORT:
|
||||
// Check if the user is allowed to download their own export. (This is for
|
||||
// institutions which centrally co-ordinate subject access request across many
|
||||
// systems, not just one Moodle instance, so we don't want every instance emailing
|
||||
// the user.)
|
||||
if (!api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->userid)) {
|
||||
$notifyuser = false;
|
||||
}
|
||||
|
||||
$typetext = get_string('requesttypeexport', 'tool_dataprivacy');
|
||||
// We want to notify the user in Moodle about the processing results.
|
||||
$message->notification = 1;
|
||||
$datarequestsurl = new moodle_url('/admin/tool/dataprivacy/mydatarequests.php');
|
||||
$message->contexturl = $datarequestsurl;
|
||||
$message->contexturlname = get_string('datarequests', 'tool_dataprivacy');
|
||||
// Message to the recipient.
|
||||
$messagetextdata['message'] = get_string('resultdownloadready', 'tool_dataprivacy', $SITE->fullname);
|
||||
// Prepare download link.
|
||||
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $thing->get_itemid(),
|
||||
$thing->get_filepath(), $thing->get_filename(), true);
|
||||
$downloadlink = new action_link($downloadurl, get_string('download', 'tool_dataprivacy'));
|
||||
$messagetextdata['downloadlink'] = $downloadlink->export_for_template($output);
|
||||
break;
|
||||
case api::DATAREQUEST_TYPE_DELETE:
|
||||
$typetext = get_string('requesttypedelete', 'tool_dataprivacy');
|
||||
// No point notifying a deleted user in Moodle.
|
||||
$message->notification = 0;
|
||||
// Message to the recipient.
|
||||
$messagetextdata['message'] = get_string('resultdeleted', 'tool_dataprivacy', $SITE->fullname);
|
||||
// Message will be sent to the deleted user via email only.
|
||||
$emailonly = true;
|
||||
break;
|
||||
default:
|
||||
cron_setup_user();
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
$subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typetext);
|
||||
$message->subject = $subject;
|
||||
$message->fullmessageformat = FORMAT_HTML;
|
||||
$message->userto = $foruser;
|
||||
|
||||
// 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 to the user involved.
|
||||
if ($notifyuser) {
|
||||
$messagesent = false;
|
||||
if ($emailonly) {
|
||||
// Do not sent an email if the user has been deleted. The user email has been previously deleted.
|
||||
if (!$foruser->deleted) {
|
||||
$messagesent = email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
|
||||
}
|
||||
} else {
|
||||
$messagesent = message_send($message);
|
||||
}
|
||||
|
||||
if ($messagesent) {
|
||||
mtrace('Message sent to user: ' . $messagetextdata['username']);
|
||||
}
|
||||
}
|
||||
|
||||
// Send to requester as well in some circumstances.
|
||||
if ($foruser->id != $request->requestedby) {
|
||||
$sendtorequester = false;
|
||||
switch ($request->type) {
|
||||
case api::DATAREQUEST_TYPE_EXPORT:
|
||||
// Send to the requester as well if they can download it, unless they are the
|
||||
// DPO. If we didn't notify the user themselves (because they can't download)
|
||||
// then send to requester even if it is the DPO, as in that case the requester
|
||||
// needs to take some action.
|
||||
if (api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->requestedby)) {
|
||||
$sendtorequester = !$notifyuser || !api::is_site_dpo($request->requestedby);
|
||||
}
|
||||
break;
|
||||
case api::DATAREQUEST_TYPE_DELETE:
|
||||
// Send to the requester if they are not the DPO and if they are allowed to
|
||||
// create data requests for the user (e.g. Parent).
|
||||
$sendtorequester = !api::is_site_dpo($request->requestedby) &&
|
||||
api::can_create_data_request_for_user($request->userid, $request->requestedby);
|
||||
break;
|
||||
default:
|
||||
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
// Ensure the requester has the capability to make data requests for this user.
|
||||
if ($sendtorequester) {
|
||||
$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);
|
||||
}
|
||||
mtrace('Message sent to requester: ' . $messagetextdata['username']);
|
||||
}
|
||||
}
|
||||
|
||||
cron_setup_user();
|
||||
|
||||
if ($deleteuser) {
|
||||
// Delete the user.
|
||||
delete_user($foruser);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Prints the contact form to the site's Data Protection Officer
|
||||
*
|
||||
* @copyright 2018 onwards Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package tool_dataprivacy
|
||||
*/
|
||||
|
||||
require_once('../../../config.php');
|
||||
require_once('lib.php');
|
||||
require_once('createdatarequest_form.php');
|
||||
|
||||
$manage = optional_param('manage', 0, PARAM_INT);
|
||||
$requesttype = optional_param('type', \tool_dataprivacy\api::DATAREQUEST_TYPE_EXPORT, PARAM_INT);
|
||||
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php', ['manage' => $manage, 'type' => $requesttype]);
|
||||
|
||||
$PAGE->set_url($url);
|
||||
|
||||
require_login();
|
||||
if (isguestuser()) {
|
||||
print_error('noguest');
|
||||
}
|
||||
|
||||
// Return URL and context.
|
||||
if ($manage) {
|
||||
// For the case where DPO creates data requests on behalf of another user.
|
||||
$returnurl = new moodle_url($CFG->wwwroot . '/admin/tool/dataprivacy/datarequests.php');
|
||||
$context = context_system::instance();
|
||||
// Make sure the user has the proper capability.
|
||||
require_capability('tool/dataprivacy:managedatarequests', $context);
|
||||
} else {
|
||||
// For the case where a user makes request for themselves (or for their children if they are the parent).
|
||||
$returnurl = new moodle_url($CFG->wwwroot . '/admin/tool/dataprivacy/mydatarequests.php');
|
||||
$context = context_user::instance($USER->id);
|
||||
}
|
||||
$PAGE->set_context($context);
|
||||
|
||||
// If contactdataprotectionofficer is disabled, send the user back to the profile page, or the privacy policy page.
|
||||
// That is, unless you have sufficient capabilities to perform this on behalf of a user.
|
||||
if (!$manage && !\tool_dataprivacy\api::can_contact_dpo()) {
|
||||
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), 0, \core\output\notification::NOTIFY_ERROR);
|
||||
}
|
||||
|
||||
$mform = new tool_dataprivacy_data_request_form($url->out(false), ['manage' => !empty($manage)]);
|
||||
$mform->set_data(['type' => $requesttype]);
|
||||
|
||||
// Data request cancelled.
|
||||
if ($mform->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
// Data request submitted.
|
||||
if ($data = $mform->get_data()) {
|
||||
if ($data->userid != $USER->id) {
|
||||
if (!\tool_dataprivacy\api::can_manage_data_requests($USER->id)) {
|
||||
// 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).
|
||||
\tool_dataprivacy\api::require_can_create_data_request_for_user($data->userid);
|
||||
}
|
||||
}
|
||||
|
||||
\tool_dataprivacy\api::create_data_request($data->userid, $data->type, $data->comments);
|
||||
|
||||
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('createnewdatarequest', 'tool_dataprivacy');
|
||||
$PAGE->set_heading($SITE->fullname);
|
||||
$PAGE->set_title($title);
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading($title);
|
||||
|
||||
echo $OUTPUT->box_start();
|
||||
$mform->display();
|
||||
echo $OUTPUT->box_end();
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* The contact form to the site's Data Protection Officer
|
||||
*
|
||||
* @copyright 2018 onwards Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package tool_dataprivacy
|
||||
*/
|
||||
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/formslib.php');
|
||||
\MoodleQuickForm::registerElementType('request_user_autocomplete',
|
||||
$CFG->dirroot . '/admin/tool/dataprivacy/classes/form/request_user_autocomplete.php',
|
||||
'\\tool_dataprivacy\\form\\request_user_autocomplete');
|
||||
/**
|
||||
* The contact form to the site's Data Protection Officer
|
||||
*
|
||||
* @copyright 2018 onwards Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package tool_dataprivacy
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function definition() {
|
||||
global $USER;
|
||||
$mform =& $this->_form;
|
||||
|
||||
$this->manage = $this->_customdata['manage'];
|
||||
if ($this->manage) {
|
||||
$options = [
|
||||
'ajax' => 'tool_dataprivacy/form-user-selector',
|
||||
];
|
||||
$mform->addElement('request_user_autocomplete', 'userid', get_string('requestfor', 'tool_dataprivacy'), [], $options);
|
||||
$mform->addRule('userid', null, 'required', null, 'client');
|
||||
|
||||
} else {
|
||||
// Get users whom you are being a guardian to if your role has the capability to make data requests for children.
|
||||
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');
|
||||
|
||||
} else {
|
||||
// Requesting for self.
|
||||
$mform->addElement('hidden', 'userid', $USER->id);
|
||||
}
|
||||
}
|
||||
|
||||
$mform->setType('userid', PARAM_INT);
|
||||
|
||||
// Subject access request type.
|
||||
$options = [
|
||||
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexport', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedelete', 'tool_dataprivacy')
|
||||
];
|
||||
$mform->addElement('select', 'type', get_string('requesttype', 'tool_dataprivacy'), $options);
|
||||
$mform->setType('type', PARAM_INT);
|
||||
$mform->addHelpButton('type', 'requesttype', 'tool_dataprivacy');
|
||||
|
||||
// Request comments text area.
|
||||
$textareaoptions = ['cols' => 60, 'rows' => 10];
|
||||
$mform->addElement('textarea', 'comments', get_string('requestcomments', 'tool_dataprivacy'), $textareaoptions);
|
||||
$mform->setType('type', PARAM_ALPHANUM);
|
||||
$mform->addHelpButton('comments', 'requestcomments', 'tool_dataprivacy');
|
||||
|
||||
// Action buttons.
|
||||
$this->add_action_buttons();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Form validation.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @return array
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
$errors = [];
|
||||
|
||||
$validrequesttypes = [
|
||||
api::DATAREQUEST_TYPE_EXPORT,
|
||||
api::DATAREQUEST_TYPE_DELETE
|
||||
];
|
||||
if (!in_array($data['type'], $validrequesttypes)) {
|
||||
$errors['type'] = get_string('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
if (api::has_ongoing_request($data['userid'], $data['type'])) {
|
||||
$errors['type'] = get_string('errorrequestalreadyexists', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Prints the data deletion main page.
|
||||
*
|
||||
* @copyright 2018 onwards Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package tool_dataprivacy
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$filter = optional_param('filter', CONTEXT_COURSE, PARAM_INT);
|
||||
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/datadeletion.php');
|
||||
|
||||
$title = get_string('datadeletion', 'tool_dataprivacy');
|
||||
|
||||
\tool_dataprivacy\page_helper::setup($url, $title);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading($title);
|
||||
|
||||
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
|
||||
$table = new \tool_dataprivacy\output\expired_contexts_table($filter);
|
||||
$table->baseurl = $url;
|
||||
$table->baseurl->param('filter', $filter);
|
||||
|
||||
$datadeletionpage = new \tool_dataprivacy\output\data_deletion_page($filter, $table);
|
||||
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->render($datadeletionpage);
|
||||
} else {
|
||||
$dponamestring = implode (',', tool_dataprivacy\api::get_dpo_role_names());
|
||||
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
|
||||
echo $OUTPUT->notification($message, 'error');
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Prints the data registry main page.
|
||||
*
|
||||
* @copyright 2018 onwards David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package tool_dataprivacy
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$contextlevel = optional_param('contextlevel', CONTEXT_SYSTEM, PARAM_INT);
|
||||
$contextid = optional_param('contextid', 0, PARAM_INT);
|
||||
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/dataregistry.php');
|
||||
$title = get_string('dataregistry', 'tool_dataprivacy');
|
||||
|
||||
\tool_dataprivacy\page_helper::setup($url, $title);
|
||||
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->header();
|
||||
echo $OUTPUT->heading($title);
|
||||
|
||||
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
|
||||
$dataregistry = new tool_dataprivacy\output\data_registry_page($contextlevel, $contextid);
|
||||
echo $output->render($dataregistry);
|
||||
} else {
|
||||
$dponamestring = implode (', ', tool_dataprivacy\api::get_dpo_role_names());
|
||||
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
|
||||
echo $OUTPUT->notification($message, 'error');
|
||||
}
|
||||
echo $OUTPUT->footer();
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Prints the contact form to the site's Data Protection Officer
|
||||
*
|
||||
* @copyright 2018 onwards Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package tool_dataprivacy
|
||||
*/
|
||||
|
||||
require_once("../../../config.php");
|
||||
require_once('lib.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$perpage = optional_param('perpage', 0, PARAM_INT);
|
||||
|
||||
$url = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
|
||||
|
||||
$title = get_string('datarequests', 'tool_dataprivacy');
|
||||
|
||||
\tool_dataprivacy\page_helper::setup($url, $title, '', 'tool/dataprivacy:managedatarequests');
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading($title);
|
||||
|
||||
if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
|
||||
$filtersapplied = optional_param_array('request-filters', [-1], PARAM_NOTAGS);
|
||||
$filterscleared = optional_param('filters-cleared', 0, PARAM_INT);
|
||||
if ($filtersapplied === [-1]) {
|
||||
// If there are no filters submitted, check if there is a saved filters from the user preferences.
|
||||
$filterprefs = get_user_preferences(\tool_dataprivacy\local\helper::PREF_REQUEST_FILTERS, null);
|
||||
if ($filterprefs && empty($filterscleared)) {
|
||||
$filtersapplied = json_decode($filterprefs);
|
||||
} else {
|
||||
$filtersapplied = [];
|
||||
}
|
||||
}
|
||||
// Save the current applied filters to the user preferences.
|
||||
set_user_preference(\tool_dataprivacy\local\helper::PREF_REQUEST_FILTERS, json_encode($filtersapplied));
|
||||
|
||||
$types = [];
|
||||
$statuses = [];
|
||||
$creationmethods = [];
|
||||
foreach ($filtersapplied as $filter) {
|
||||
list($category, $value) = explode(':', $filter);
|
||||
switch($category) {
|
||||
case \tool_dataprivacy\local\helper::FILTER_TYPE:
|
||||
$types[] = $value;
|
||||
break;
|
||||
case \tool_dataprivacy\local\helper::FILTER_STATUS:
|
||||
$statuses[] = $value;
|
||||
break;
|
||||
case \tool_dataprivacy\local\helper::FILTER_CREATION:
|
||||
$creationmethods[] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$table = new \tool_dataprivacy\output\data_requests_table(0, $statuses, $types, $creationmethods, true);
|
||||
if (!empty($perpage)) {
|
||||
set_user_preference(\tool_dataprivacy\local\helper::PREF_REQUEST_PERPAGE, $perpage);
|
||||
} else {
|
||||
$prefperpage = get_user_preferences(\tool_dataprivacy\local\helper::PREF_REQUEST_PERPAGE);
|
||||
$perpage = ($prefperpage) ? $prefperpage : $table->get_requests_per_page_options()[0];
|
||||
}
|
||||
$table->set_requests_per_page($perpage);
|
||||
$table->baseurl = $url;
|
||||
|
||||
$requestlist = new tool_dataprivacy\output\data_requests_page($table, $filtersapplied);
|
||||
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $requestlistoutput->render($requestlist);
|
||||
} else {
|
||||
$dponamestring = implode (', ', tool_dataprivacy\api::get_dpo_role_names());
|
||||
$message = get_string('privacyofficeronly', 'tool_dataprivacy', $dponamestring);
|
||||
echo $OUTPUT->notification($message, 'error');
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Capability definitions for this module.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 onwards Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$capabilities = [
|
||||
|
||||
// Capability for managing data requests. Usually given to the site's Data Protection Officer.
|
||||
'tool/dataprivacy:managedatarequests' => [
|
||||
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS | RISK_DATALOSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => []
|
||||
],
|
||||
|
||||
// Capability for managing the data registry. Usually given to the site's Data Protection Officer.
|
||||
'tool/dataprivacy:managedataregistry' => [
|
||||
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS | RISK_DATALOSS,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => []
|
||||
],
|
||||
|
||||
// Capability for parents/guardians to make data requests on behalf of their children.
|
||||
'tool/dataprivacy:makedatarequestsforchildren' => [
|
||||
'riskbitmask' => RISK_SPAM | RISK_PERSONAL,
|
||||
'captype' => 'write',
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'archetypes' => []
|
||||
],
|
||||
|
||||
// Capability for users to download the results of their own data request.
|
||||
'tool/dataprivacy:downloadownrequest' => [
|
||||
'riskbitmask' => 0,
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'archetypes' => [
|
||||
'user' => CAP_ALLOW
|
||||
]
|
||||
],
|
||||
|
||||
// Capability for administrators to download other people's data requests.
|
||||
'tool/dataprivacy:downloadallrequests' => [
|
||||
'riskbitmask' => RISK_PERSONAL,
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_USER,
|
||||
'archetypes' => []
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* tool_dataprivacy cache definitions.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @category cache
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$definitions = array(
|
||||
'purpose' => array(
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'simplekeys' => true,
|
||||
'simpledata' => true,
|
||||
'staticacceleration' => true,
|
||||
'staticaccelerationsize' => 30,
|
||||
),
|
||||
'contextlevel' => array(
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'simplekeys' => true,
|
||||
'simpledata' => true,
|
||||
'staticacceleration' => true,
|
||||
'staticaccelerationsize' => 10,
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This file defines observers needed by the plugin.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$observers = [
|
||||
[
|
||||
'eventname' => '\core\event\user_deleted',
|
||||
'callback' => '\tool_dataprivacy\event\user_deleted_observer::create_delete_data_request',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="admin/tool/dataprivacy/db" VERSION="20180821" COMMENT="XMLDB file for Moodle tool/dataprivacy"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
<TABLES>
|
||||
<TABLE NAME="tool_dataprivacy_request" COMMENT="Table for data requests">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="type" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Data request type"/>
|
||||
<FIELD NAME="comments" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="More details about the request"/>
|
||||
<FIELD NAME="commentsformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID the request is being made for"/>
|
||||
<FIELD NAME="requestedby" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID of the one making the request"/>
|
||||
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The current status of the data request"/>
|
||||
<FIELD NAME="dpo" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="The user ID of the Data Protection Officer who is reviewing th request"/>
|
||||
<FIELD NAME="dpocomment" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="DPO's comments (e.g. reason for rejecting the request, etc.)"/>
|
||||
<FIELD NAME="dpocommentformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user who created/modified this request object"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time this data request was created"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The last time this data request was updated"/>
|
||||
<FIELD NAME="creationmethod" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The type of the creation method of the data request"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
|
||||
<KEY NAME="requestedby" TYPE="foreign" FIELDS="requestedby" REFTABLE="user" REFFIELDS="id"/>
|
||||
<KEY NAME="dpo" TYPE="foreign" FIELDS="dpo" REFTABLE="user" REFFIELDS="id"/>
|
||||
<KEY NAME="usermodified" TYPE="foreign" FIELDS="usermodified" REFTABLE="user" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_purpose" COMMENT="Data purposes">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="name" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="lawfulbases" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Comma-separated IDs matching records in tool_dataprivacy_lawfulbasis"/>
|
||||
<FIELD NAME="sensitivedatareasons" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Comma-separated IDs matching records in tool_dataprivacy_sensitive"/>
|
||||
<FIELD NAME="retentionperiod" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="protected" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_category" COMMENT="Data categories">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="name" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="descriptionformat" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_ctxinstance" COMMENT="Default comment for the table, please edit me">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="purposeid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="categoryid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="contextid" TYPE="foreign-unique" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
|
||||
<KEY NAME="purposeid" TYPE="foreign" FIELDS="purposeid" REFTABLE="tool_dataprivacy_purpose" REFFIELDS="id"/>
|
||||
<KEY NAME="categoryid" TYPE="foreign" FIELDS="categoryid" REFTABLE="tool_dataprivacy_category" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_ctxlevel" COMMENT="Default comment for the table, please edit me">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="contextlevel" TYPE="int" LENGTH="3" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="purposeid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="categoryid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="contextlevel" TYPE="unique" FIELDS="contextlevel"/>
|
||||
<KEY NAME="categoryid" TYPE="foreign" FIELDS="categoryid" REFTABLE="tool_dataprivacy_category" REFFIELDS="id"/>
|
||||
<KEY NAME="purposeid" TYPE="foreign" FIELDS="purposeid" REFTABLE="tool_dataprivacy_purpose" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_ctxexpired" COMMENT="Default comment for the table, please edit me">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="contextid" TYPE="foreign-unique" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_contextlist" COMMENT="List of contexts for a component">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="component" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Frankenstyle component name"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_ctxlst_ctx" COMMENT="A contextlist context item">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="contextlistid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Approval status of the context item"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="contextlistid" TYPE="foreign" FIELDS="contextlistid" REFTABLE="tool_dataprivacy_contextlist" REFFIELDS="id" COMMENT="Reference to the contextlist containing this context item"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_rqst_ctxlst" COMMENT="Association table joining requests and contextlists">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="requestid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="contextlistid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="requestid" TYPE="foreign" FIELDS="requestid" REFTABLE="tool_dataprivacy_request" REFFIELDS="id" COMMENT="Reference to the request"/>
|
||||
<KEY NAME="contextlistid" TYPE="foreign" FIELDS="contextlistid" REFTABLE="tool_dataprivacy_contextlist" REFFIELDS="id" COMMENT="Reference to the contextlist"/>
|
||||
<KEY NAME="request_contextlist" TYPE="unique" FIELDS="requestid, contextlistid" COMMENT="Uniqueness constraint on request and contextlist"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Defines message providers (types of messages being sent)
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 onwards Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$messageproviders = [
|
||||
// Notify Data Protection Officer about incoming data requests.
|
||||
'contactdataprotectionofficer' => [
|
||||
'defaults' => [
|
||||
'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
],
|
||||
'capability' => 'tool/dataprivacy:managedatarequests'
|
||||
],
|
||||
|
||||
// Notify user about the processing results of their data request.
|
||||
'datarequestprocessingresults' => [
|
||||
'defaults' => [
|
||||
'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
|
||||
'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'
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,236 @@
|
||||
<?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/>.
|
||||
/**
|
||||
* Chat external functions and service definitions.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @category external
|
||||
* @copyright 2018 Jun Pataleta <jun@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
$functions = [
|
||||
'tool_dataprivacy_cancel_data_request' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'cancel_data_request',
|
||||
'classpath' => '',
|
||||
'description' => 'Cancel the data request made by the user',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_contact_dpo' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'contact_dpo',
|
||||
'classpath' => '',
|
||||
'description' => 'Contact the site Data Protection Officer(s)',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_mark_complete' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'mark_complete',
|
||||
'classpath' => '',
|
||||
'description' => 'Mark a user\'s general enquiry as complete',
|
||||
'type' => 'write',
|
||||
'capabilities' => 'tool/dataprivacy:managedatarequests',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_get_data_request' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'get_data_request',
|
||||
'classpath' => '',
|
||||
'description' => 'Fetch the details of a user\'s data request',
|
||||
'type' => 'read',
|
||||
'capabilities' => 'tool/dataprivacy:managedatarequests',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_approve_data_request' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'approve_data_request',
|
||||
'classpath' => '',
|
||||
'description' => 'Approve a data request',
|
||||
'type' => 'write',
|
||||
'capabilities' => 'tool/dataprivacy:managedatarequests',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_bulk_approve_data_requests' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'bulk_approve_data_requests',
|
||||
'classpath' => '',
|
||||
'description' => 'Bulk approve data requests',
|
||||
'type' => 'write',
|
||||
'capabilities' => 'tool/dataprivacy:managedatarequests',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_deny_data_request' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'deny_data_request',
|
||||
'classpath' => '',
|
||||
'description' => 'Deny a data request',
|
||||
'type' => 'write',
|
||||
'capabilities' => 'tool/dataprivacy:managedatarequests',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_bulk_deny_data_requests' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'bulk_deny_data_requests',
|
||||
'classpath' => '',
|
||||
'description' => 'Bulk deny data requests',
|
||||
'type' => 'write',
|
||||
'capabilities' => 'tool/dataprivacy:managedatarequests',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_get_users' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'get_users',
|
||||
'classpath' => '',
|
||||
'description' => 'Fetches a list of users',
|
||||
'type' => 'read',
|
||||
'capabilities' => 'tool/dataprivacy:managedatarequests',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_create_purpose_form' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'create_purpose_form',
|
||||
'classpath' => '',
|
||||
'description' => 'Adds a data purpose',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_create_category_form' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'create_category_form',
|
||||
'classpath' => '',
|
||||
'description' => 'Adds a data category',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_delete_purpose' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'delete_purpose',
|
||||
'classpath' => '',
|
||||
'description' => 'Deletes an existing data purpose',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_delete_category' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'delete_category',
|
||||
'classpath' => '',
|
||||
'description' => 'Deletes an existing data category',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_set_contextlevel_form' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'set_contextlevel_form',
|
||||
'classpath' => '',
|
||||
'description' => 'Sets purpose and category across a context level',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_set_context_form' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'set_context_form',
|
||||
'classpath' => '',
|
||||
'description' => 'Sets purpose and category for a specific context',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_tree_extra_branches' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'tree_extra_branches',
|
||||
'classpath' => '',
|
||||
'description' => 'Return branches for the context tree',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_confirm_contexts_for_deletion' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'confirm_contexts_for_deletion',
|
||||
'classpath' => '',
|
||||
'description' => 'Mark the selected expired contexts as confirmed for deletion',
|
||||
'type' => 'write',
|
||||
'capabilities' => '',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_set_context_defaults' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'set_context_defaults',
|
||||
'classpath' => '',
|
||||
'description' => 'Updates the default category and purpose for a given context level (and optionally, a plugin)',
|
||||
'type' => 'write',
|
||||
'capabilities' => 'tool/dataprivacy:managedataregistry',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_get_category_options' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'get_category_options',
|
||||
'classpath' => '',
|
||||
'description' => 'Fetches a list of data category options',
|
||||
'type' => 'read',
|
||||
'capabilities' => 'tool/dataprivacy:managedataregistry',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_get_purpose_options' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'get_purpose_options',
|
||||
'classpath' => '',
|
||||
'description' => 'Fetches a list of data storage purpose options',
|
||||
'type' => 'read',
|
||||
'capabilities' => 'tool/dataprivacy:managedataregistry',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_get_activity_options' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'get_activity_options',
|
||||
'classpath' => '',
|
||||
'description' => 'Fetches a list of activity options',
|
||||
'type' => 'read',
|
||||
'capabilities' => 'tool/dataprivacy:managedataregistry',
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This file defines tasks performed by the tool.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
// List of tasks.
|
||||
$tasks = array(
|
||||
array(
|
||||
'classname' => 'tool_dataprivacy\task\expired_retention_period',
|
||||
'blocking' => 0,
|
||||
'minute' => '0',
|
||||
'hour' => 'R',
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
), array(
|
||||
'classname' => 'tool_dataprivacy\task\delete_expired_contexts',
|
||||
'blocking' => 0,
|
||||
'minute' => '0',
|
||||
'hour' => 'R',
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
), array(
|
||||
'classname' => 'tool_dataprivacy\task\delete_expired_requests',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => 'R',
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
), array(
|
||||
'classname' => 'tool_dataprivacy\task\delete_existing_deleted_users',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => 'R',
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*',
|
||||
'disabled' => true,
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,233 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* tool_dataprivacy plugin upgrade code
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Function to upgrade auth_cas.
|
||||
* @param int $oldversion the version we are upgrading from
|
||||
* @return bool result
|
||||
*/
|
||||
function xmldb_tool_dataprivacy_upgrade($oldversion) {
|
||||
global $CFG, $DB;
|
||||
|
||||
$dbman = $DB->get_manager();
|
||||
|
||||
if ($oldversion < 2017051500) {
|
||||
// Nothing to do here. Moodle Plugins site's just complaining about missing upgrade.php.
|
||||
// Savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2017051500, 'error', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2017051503) {
|
||||
|
||||
// Define table tool_dataprivacy_ctxexpired to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_ctxexpired');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_ctxexpired.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table tool_dataprivacy_ctxexpired.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('contextid', XMLDB_KEY_FOREIGN_UNIQUE, array('contextid'), 'context', array('id'));
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_ctxexpired.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Dataprivacy savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2017051503, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2017051504) {
|
||||
|
||||
// Define table tool_dataprivacy_contextlist to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_contextlist');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_contextlist.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('component', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
|
||||
// Adding keys to table tool_dataprivacy_contextlist.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_contextlist.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table tool_dataprivacy_ctxlst_ctx to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_ctxlst_ctx');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_ctxlst_ctx.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
|
||||
// Adding keys to table tool_dataprivacy_ctxlst_ctx.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_ctxlst_ctx.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table tool_dataprivacy_rqst_ctxlst to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_rqst_ctxlst');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_rqst_ctxlst.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('requestid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table tool_dataprivacy_rqst_ctxlst.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('requestid', XMLDB_KEY_FOREIGN, array('requestid'), 'tool_dataprivacy_request', array('id'));
|
||||
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
|
||||
$table->add_key('request_contextlist', XMLDB_KEY_UNIQUE, array('requestid', 'contextlistid'));
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_rqst_ctxlst.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Dataprivacy savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2017051504, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2017051506) {
|
||||
// Define field lawfulbases to be added to tool_dataprivacy_purpose.
|
||||
$table = new xmldb_table('tool_dataprivacy_purpose');
|
||||
|
||||
// It is a required field. We initially define and add it as null and later update it to XMLDB_NOTNULL.
|
||||
$field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, null, null, null, 'descriptionformat');
|
||||
|
||||
// Conditionally launch add field lawfulbases.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
|
||||
// Set a kind-of-random value to lawfulbasis field.
|
||||
$DB->set_field('tool_dataprivacy_purpose', 'lawfulbases', 'gdpr_art_6_1_a');
|
||||
|
||||
// We redefine it now as not null.
|
||||
$field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null, 'descriptionformat');
|
||||
|
||||
// Launch change of nullability for field lawfulbases.
|
||||
$dbman->change_field_notnull($table, $field);
|
||||
}
|
||||
|
||||
// Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
|
||||
$table = new xmldb_table('tool_dataprivacy_purpose');
|
||||
$field = new xmldb_field('sensitivedatareasons', XMLDB_TYPE_TEXT, null, null, null, null, null, 'lawfulbases');
|
||||
|
||||
// Conditionally launch add field sensitivedatareasons.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Dataprivacy savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2017051506, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2017051516) {
|
||||
// Update completed delete requests to new delete status.
|
||||
$query = "UPDATE {tool_dataprivacy_request}
|
||||
SET status = :setstatus
|
||||
WHERE type = :type
|
||||
AND status = :wherestatus";
|
||||
$params = array(
|
||||
'setstatus' => 10, // Request deleted.
|
||||
'type' => 2, // Delete type.
|
||||
'wherestatus' => 5, // Request completed.
|
||||
);
|
||||
|
||||
$DB->execute($query, $params);
|
||||
|
||||
// Update completed data export requests to new download ready status.
|
||||
$params = array(
|
||||
'setstatus' => 8, // Request download ready.
|
||||
'type' => 1, // export type.
|
||||
'wherestatus' => 5, // Request completed.
|
||||
);
|
||||
|
||||
$DB->execute($query, $params);
|
||||
|
||||
upgrade_plugin_savepoint(true, 2017051516, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2017051517) {
|
||||
|
||||
// Changing precision of field status on table tool_dataprivacy_request to (2).
|
||||
$table = new xmldb_table('tool_dataprivacy_request');
|
||||
$field = new xmldb_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'requestedby');
|
||||
|
||||
// Launch change of precision for field status.
|
||||
$dbman->change_field_precision($table, $field);
|
||||
|
||||
// Dataprivacy savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2017051517, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2017051554) {
|
||||
// Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
|
||||
$table = new xmldb_table('tool_dataprivacy_request');
|
||||
$field = new xmldb_field('creationmethod', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, 0, 'timemodified');
|
||||
|
||||
// Conditionally launch add field sensitivedatareasons.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Dataprivacy savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2017051554, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2017051556) {
|
||||
// Delete orphaned data privacy requests.
|
||||
$sql = "SELECT r.id
|
||||
FROM {tool_dataprivacy_request} r LEFT JOIN {user} u ON r.userid = u.id
|
||||
WHERE u.id IS NULL";
|
||||
$orphaned = $DB->get_fieldset_sql($sql);
|
||||
|
||||
if ($orphaned) {
|
||||
$DB->delete_records_list('tool_dataprivacy_request', 'id', $orphaned);
|
||||
}
|
||||
|
||||
upgrade_plugin_savepoint(true, 2017051556, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This page lets users manage default purposes and categories.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$url = new \moodle_url('/admin/tool/dataprivacy/defaults.php');
|
||||
$title = get_string('setdefaults', 'tool_dataprivacy');
|
||||
|
||||
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
|
||||
|
||||
$mode = optional_param('mode', CONTEXT_COURSECAT, PARAM_INT);
|
||||
$classname = context_helper::get_class_for_level($mode);
|
||||
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
|
||||
$purpose = get_config('tool_dataprivacy', $purposevar);
|
||||
$category = get_config('tool_dataprivacy', $categoryvar);
|
||||
|
||||
$otherdefaults = [];
|
||||
if ($mode == CONTEXT_MODULE) {
|
||||
// Get activity module plugin info.
|
||||
$pluginmanager = core_plugin_manager::instance();
|
||||
$modplugins = $pluginmanager->get_enabled_plugins('mod');
|
||||
|
||||
foreach ($modplugins as $name) {
|
||||
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname, $name);
|
||||
$plugincategory = get_config('tool_dataprivacy', $categoryvar);
|
||||
$pluginpurpose = get_config('tool_dataprivacy', $purposevar);
|
||||
if ($plugincategory === false && $pluginpurpose === false) {
|
||||
// If no purpose and category has been set for this plugin, then there's no need to show this on the list.
|
||||
continue;
|
||||
}
|
||||
|
||||
$displayname = $pluginmanager->plugin_name('mod_' . $name);
|
||||
$otherdefaults[$name] = (object)[
|
||||
'name' => $displayname,
|
||||
'category' => $plugincategory,
|
||||
'purpose' => $pluginpurpose,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$defaultspage = new \tool_dataprivacy\output\defaults_page($mode, $category, $purpose, $otherdefaults, true);
|
||||
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->header();
|
||||
echo $output->heading($title);
|
||||
echo $output->render_from_template('tool_dataprivacy/defaults_page', $defaultspage->export_for_template($output));
|
||||
echo $output->footer();
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This page lets users manage categories.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$id = optional_param('id', 0, PARAM_INT);
|
||||
|
||||
$url = new \moodle_url('/admin/tool/dataprivacy/editcategory.php', array('id' => $id));
|
||||
if ($id) {
|
||||
$title = get_string('editcategory', 'tool_dataprivacy');
|
||||
} else {
|
||||
$title = get_string('addcategory', 'tool_dataprivacy');
|
||||
}
|
||||
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
|
||||
|
||||
$category = new \tool_dataprivacy\category($id);
|
||||
$form = new \tool_dataprivacy\form\category($PAGE->url->out(false),
|
||||
array('persistent' => $category, 'showbuttons' => true));
|
||||
|
||||
$returnurl = new \moodle_url('/admin/tool/dataprivacy/categories.php');
|
||||
if ($form->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
} else if ($data = $form->get_data()) {
|
||||
if (empty($data->id)) {
|
||||
\tool_dataprivacy\api::create_category($data);
|
||||
$messagesuccess = get_string('categorycreated', 'tool_dataprivacy');
|
||||
} else {
|
||||
\tool_dataprivacy\api::update_category($data);
|
||||
$messagesuccess = get_string('categoryupdated', 'tool_dataprivacy');
|
||||
}
|
||||
redirect($returnurl, $messagesuccess, 0, \core\output\notification::NOTIFY_SUCCESS);
|
||||
}
|
||||
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->header();
|
||||
$form->display();
|
||||
echo $output->footer();
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* This page lets users manage purposes.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 David Monllao
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../../../config.php');
|
||||
|
||||
require_login(null, false);
|
||||
|
||||
$id = optional_param('id', 0, PARAM_INT);
|
||||
|
||||
$url = new \moodle_url('/admin/tool/dataprivacy/editpurpose.php', array('id' => $id));
|
||||
if ($id) {
|
||||
$title = get_string('editpurpose', 'tool_dataprivacy');
|
||||
} else {
|
||||
$title = get_string('addpurpose', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
|
||||
|
||||
$purpose = new \tool_dataprivacy\purpose($id);
|
||||
$form = new \tool_dataprivacy\form\purpose($PAGE->url->out(false),
|
||||
array('persistent' => $purpose, 'showbuttons' => true));
|
||||
|
||||
$returnurl = new \moodle_url('/admin/tool/dataprivacy/purposes.php');
|
||||
if ($form->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
} else if ($data = $form->get_data()) {
|
||||
if (empty($data->id)) {
|
||||
\tool_dataprivacy\api::create_purpose($data);
|
||||
$messagesuccess = get_string('purposecreated', 'tool_dataprivacy');
|
||||
} else {
|
||||
\tool_dataprivacy\api::update_purpose($data);
|
||||
$messagesuccess = get_string('purposeupdated', 'tool_dataprivacy');
|
||||
}
|
||||
redirect($returnurl, $messagesuccess, 0, \core\output\notification::NOTIFY_SUCCESS);
|
||||
}
|
||||
|
||||
$output = $PAGE->get_renderer('tool_dataprivacy');
|
||||
echo $output->header();
|
||||
$form->display();
|
||||
echo $output->footer();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user