MDL-61899 tool_dataprivacy: Subject access requests tool

This commit is composed of different issues, most of them under
MDL-61306:
- MDL-61888, MDL-61785, MDL-61830, MDL-61874, MDL-59720,
  MDL-61486, MDL-61697, MDL-61798, MDL-61685, MDL-61682,
  MDL-61684, MDL-61618, MDL-61681, MDL-61680, MDL-61487,
  MDL-61499, MDL-61500, MDL-61362 and MDL-59718

Contributions to this patch by:
- Jun Pataleta
- David Monllao
- Andrew Nicols
- Adrian B. Greeve
- Jake Dallimore
- Sara Arjona
This commit is contained in:
David Monllao
2018-04-18 18:14:23 +02:00
committed by Eloy Lafuente (stronk7)
parent b5cca5ae5a
commit 5efc1f9e64
101 changed files with 12569 additions and 0 deletions
+1
View File
@@ -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)}}});
+1
View File
@@ -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",param:i},{key:"deletecategorytext",component:"tool_dataprivacy",param:i}];d.get_strings(j).then(function(d){var g=d[0],i=d[1];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(g),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}}});
+1
View File
@@ -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});
+1
View File
@@ -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}])}).fail(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"]'},i=function(a){d.call(this,a),this.getFooter().find(h.APPROVE_BUTTON).length||b.exception({message:"No approve button found"}),this.getFooter().find(h.DENY_BUTTON).length||b.exception({message:"No deny button found"})};return i.TYPE="tool_dataprivacy-data_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))},g||(e.register(i.TYPE,i,"tool_dataprivacy/data_request_modal"),g=!0),i});
@@ -0,0 +1 @@
define(["jquery"],function(a){var b={PURPOSE_SELECT:"#id_purposeid",RETENTION_FIELD:".form-control-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];a(b.RETENTION_FIELD).each(function(){var b=a(this),c=b.siblings("#id_error_retention_current");c.length>0&&b.text(e)})}.bind(this))},{init:function(a){return d(),new c(a)}}});
+1
View File
@@ -0,0 +1 @@
define([],function(){return{approve:"tool_dataprivacy-data_request:approve",deny:"tool_dataprivacy-data_request:deny"}});
@@ -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",param:i},{key:"deletepurposetext",component:"tool_dataprivacy",param:i}];d.get_strings(j).then(function(d){var g=d[0],i=d[1];return e.create({title:g,body:i,type:e.types.SAVE_CANCEL}).then(function(d){return d.setSaveButtonText(g),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/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,g){var h=[],j="";switch(a){case i.approve:h=[{key:"approverequest",component:"tool_dataprivacy"},{key:"confirmapproval",component:"tool_dataprivacy"}],j="tool_dataprivacy_approve_data_request";break;case i.deny:h=[{key:"denyrequest",component:"tool_dataprivacy"},{key:"confirmdenial",component:"tool_dataprivacy"}],j="tool_dataprivacy_deny_data_request"}var k="";d.get_strings(h).then(function(a){k=a[0];var b=a[1];return e.create({title:k,body:b,type:e.types.SAVE_CANCEL})}).then(function(a){return a.setSaveButtonText(k),a.getRoot().on(f.save,function(){var a={requestid:g},d={methodname:j,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)}var k={APPROVE_REQUEST:'[data-action="approve"]',DENY_REQUEST:'[data-action="deny"]',VIEW_REQUEST:'[data-action="view"]'},l=function(){this.registerEvents()};return l.prototype.registerEvents=function(){a(k.VIEW_REQUEST).click(function(d){d.preventDefault();var k=a(this).data("requestid"),l={requestid:k},m={methodname:"tool_dataprivacy_get_data_request",args:l},n=b.call([m]),o="",p=e.types.DEFAULT;a.when(n[0]).then(function(a){return a.result?(2==a.result.status&&(p=h.TYPE),o=a.result.typename,g.render("tool_dataprivacy/request_details",a.result)):(c.addNotification({message:a.warnings[0].message,type:"error"}),!1)}).then(function(a){return e.create({title:o,body:a,type:p,large:!0}).then(function(a){return a.getRoot().on(i.approve,function(){j(i.approve,k)}),a.getRoot().on(i.deny,function(){j(i.deny,k)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a})}).done(function(a){a.show()}).fail(c.exception)}),a(k.APPROVE_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.approve,c)}),a(k.DENY_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.deny,c)})},l});
@@ -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,129 @@
// 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',
param: categoryname
},
{
key: 'deletecategorytext',
component: 'tool_dataprivacy',
param: categoryname
}
];
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() {
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,317 @@
// 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
}]);
}).fail(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,93 @@
// 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"]',
};
/**
* Constructor for the Modal.
*
* @param {object} root The root jQuery element for the modal
*/
var ModalDataRequest = function(root) {
Modal.call(this, root);
if (!this.getFooter().find(SELECTORS.APPROVE_BUTTON).length) {
Notification.exception({message: 'No approve button found'});
}
if (!this.getFooter().find(SELECTORS.DENY_BUTTON).length) {
Notification.exception({message: 'No deny button found'});
}
};
ModalDataRequest.TYPE = 'tool_dataprivacy-data_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));
};
// 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');
registered = true;
}
return ModalDataRequest;
});
@@ -0,0 +1,89 @@
// 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: '.form-control-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];
$(SELECTORS.RETENTION_FIELD).each(function() {
var node = $(this);
// Sucky way to select the text, but no id for static fields :(.
var retentionField = node.siblings('#id_error_retention_current');
if (retentionField.length > 0) {
node.text(selectedPurpose);
}
});
}.bind(this));
};
return /** @alias module:tool_dataprivacy/effective_retention_period */ {
init: function(purposeRetentionPeriods) {
// Remove previously attached listeners.
removeListeners();
return new EffectiveRetentionPeriod(purposeRetentionPeriods);
}
};
}
);
+30
View File
@@ -0,0 +1,30 @@
// 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',
deny: 'tool_dataprivacy-data_request:deny',
};
});
@@ -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,129 @@
// 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',
param: purposename
},
{
key: 'deletepurposetext',
component: 'tool_dataprivacy',
param: purposename
}
];
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() {
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,227 @@
// 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}}
*/
var ACTIONS = {
APPROVE_REQUEST: '[data-action="approve"]',
DENY_REQUEST: '[data-action="deny"]',
VIEW_REQUEST: '[data-action="view"]'
};
/**
* 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]);
var modalTitle = '';
var modalType = ModalFactory.types.DEFAULT;
$.when(promises[0]).then(function(data) {
if (data.result) {
// Check if the status is awaiting approval.
if (data.result.status == 2) {
modalType = ModalDataRequest.TYPE;
}
modalTitle = data.result.typename;
return Templates.render('tool_dataprivacy/request_details', data.result);
}
// Fail.
Notification.addNotification({
message: data.warnings[0].message,
type: 'error'
});
return false;
}).then(function(html) {
return ModalFactory.create({
title: modalTitle,
body: html,
type: modalType,
large: true
}).then(function(modal) {
// Handle approve event.
modal.getRoot().on(DataPrivacyEvents.approve, function() {
showConfirmation(DataPrivacyEvents.approve, requestId);
});
// Handle deny event.
modal.getRoot().on(DataPrivacyEvents.deny, function() {
showConfirmation(DataPrivacyEvents.deny, requestId);
});
// 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.APPROVE_REQUEST).click(function(e) {
e.preventDefault();
var requestId = $(this).data('requestid');
showConfirmation(DataPrivacyEvents.approve, requestId);
});
$(ACTIONS.DENY_REQUEST).click(function(e) {
e.preventDefault();
var requestId = $(this).data('requestid');
showConfirmation(DataPrivacyEvents.deny, requestId);
});
};
/**
* Show the confirmation dialogue.
*
* @param {String} action The action name.
* @param {Number} requestId The request ID.
*/
function showConfirmation(action, requestId) {
var keys = [];
var wsfunction = '';
switch (action) {
case DataPrivacyEvents.approve:
keys = [
{
key: 'approverequest',
component: 'tool_dataprivacy'
},
{
key: 'confirmapproval',
component: 'tool_dataprivacy'
}
];
wsfunction = 'tool_dataprivacy_approve_data_request';
break;
case DataPrivacyEvents.deny:
keys = [
{
key: 'denyrequest',
component: 'tool_dataprivacy'
},
{
key: 'confirmdenial',
component: 'tool_dataprivacy'
}
];
wsfunction = 'tool_dataprivacy_deny_data_request';
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() {
// Confirm the request.
var params = {
'requestid': requestId
};
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 RequestActions;
});
+39
View File
@@ -0,0 +1,39 @@
<?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');
$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();
$categories = \tool_dataprivacy\api::get_categories();
$renderable = new \tool_dataprivacy\output\categories($categories);
echo $output->render($renderable);
echo $output->footer();
+756
View File
@@ -0,0 +1,756 @@
<?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 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use coding_exception;
use context_system;
use core\invalid_persistent_exception;
use core\message\message;
use core\task\manager;
use core_user;
use dml_exception;
use moodle_exception;
use moodle_url;
use required_capability_exception;
use stdClass;
use tool_dataprivacy\task\initiate_data_request_task;
use tool_dataprivacy\task\process_data_request_task;
use tool_dataprivacy\purpose;
use tool_dataprivacy\category;
use tool_dataprivacy\contextlevel;
use tool_dataprivacy\context_instance;
use tool_dataprivacy\data_registry;
use tool_dataprivacy\expired_context;
defined('MOODLE_INTERNAL') || die();
/**
* Class containing helper methods for processing data requests.
*
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api {
/** Data export request type. */
const DATAREQUEST_TYPE_EXPORT = 1;
/** Data deletion request type. */
const DATAREQUEST_TYPE_DELETE = 2;
/** Other request type. Usually of enquiries to the DPO. */
const DATAREQUEST_TYPE_OTHERS = 3;
/** Newly submitted and we haven't yet started finding out where they have data. */
const DATAREQUEST_STATUS_PENDING = 0;
/** Newly submitted and we have started to find the location of data. */
const DATAREQUEST_STATUS_PREPROCESSING = 1;
/** Metadata ready and awaiting review and approval by the Data Protection officer. */
const DATAREQUEST_STATUS_AWAITING_APPROVAL = 2;
/** Request approved and will be processed soon. */
const DATAREQUEST_STATUS_APPROVED = 3;
/** The request is now being processed. */
const DATAREQUEST_STATUS_PROCESSING = 4;
/** Data request completed. */
const DATAREQUEST_STATUS_COMPLETE = 5;
/** Data request cancelled by the user. */
const DATAREQUEST_STATUS_CANCELLED = 6;
/** Data request rejected by the DPO. */
const DATAREQUEST_STATUS_REJECTED = 7;
/**
* Determines whether the user can contact the site's Data Protection Officer via Moodle.
*
* @return boolean True when tool_dataprivacy|contactdataprotectionofficer is enabled.
* @throws dml_exception
*/
public static function can_contact_dpo() {
return get_config('tool_dataprivacy', 'contactdataprotectionofficer') == 1;
}
/**
* Check's whether the current user has the capability to manage data requests.
*
* @param int $userid The user ID.
* @return bool
* @throws coding_exception
* @throws dml_exception
*/
public static function can_manage_data_requests($userid) {
$context = context_system::instance();
// A user can manage data requests if he/she has the site DPO role and has the capability to manage data requests.
return self::is_site_dpo($userid) && has_capability('tool/dataprivacy:managedatarequests', $context, $userid);
}
/**
* Checks if the current user can manage the data registry at the provided id.
*
* @param int $contextid Fallback to system context id.
* @throws \required_capability_exception
* @return null
*/
public static function check_can_manage_data_registry($contextid = false) {
if ($contextid) {
$context = \context_helper::instance_by_id($contextid);
} else {
$context = \context_system::instance();
}
require_capability('tool/dataprivacy:managedataregistry', $context);
}
/**
* Fetches the list of users with the Data Protection Officer role.
*
* @throws dml_exception
*/
public static function get_site_dpos() {
// Get role(s) that can manage data requests.
$dporoles = explode(',', get_config('tool_dataprivacy', 'dporoles'));
$dpos = [];
$context = context_system::instance();
foreach ($dporoles as $roleid) {
if (empty($roleid)) {
continue;
}
// Fetch users that can manage data requests.
$dpos += get_role_users($roleid, $context, false, 'u.*');
}
// If the site has no data protection officer, defer to site admin(s).
if (empty($dpos)) {
$dpos = get_admins();
}
return $dpos;
}
/**
* Checks whether a given user is a site DPO.
*
* @param int $userid The user ID.
* @return bool
* @throws dml_exception
*/
public static function is_site_dpo($userid) {
$dpos = self::get_site_dpos();
return array_key_exists($userid, $dpos);
}
/**
* Lodges a data request and sends the request details to the site Data Protection Officer(s).
*
* @param int $foruser The user whom the request is being made for.
* @param int $type The request type.
* @param string $comments Request comments.
* @return data_request
* @throws invalid_persistent_exception
* @throws coding_exception
*/
public static function create_data_request($foruser, $type, $comments = '') {
global $USER;
$datarequest = new data_request();
// The user the request is being made for.
$datarequest->set('userid', $foruser);
// The user making the request.
$datarequest->set('requestedby', $USER->id);
// Set status.
$datarequest->set('status', self::DATAREQUEST_STATUS_PENDING);
// Set request type.
$datarequest->set('type', $type);
// Set request comments.
$datarequest->set('comments', $comments);
// Store subject access request.
$datarequest->create();
// Fire an ad hoc task to initiate the data request process.
$task = new initiate_data_request_task();
$task->set_custom_data(['requestid' => $datarequest->get('id')]);
manager::queue_adhoc_task($task, true);
return $datarequest;
}
/**
* Fetches the list of the data requests.
*
* If user ID is provided, it fetches the data requests for the user.
* Otherwise, it fetches all of the data requests, provided that the user has the capability to manage data requests.
* (e.g. Users with the Data Protection Officer roles)
*
* @param int $userid The User ID.
* @return data_request[]
* @throws dml_exception
*/
public static function get_data_requests($userid = 0) {
global $USER;
$results = [];
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$select = "userid = :userid OR requestedby = :requestedby";
$params = [
'userid' => $userid,
'requestedby' => $userid
];
$results = data_request::get_records_select($select, $params, 'status DESC, timemodified DESC');
} else {
// If the current user is one of the site's Data Protection Officers, then fetch all data requests.
if (self::is_site_dpo($USER->id)) {
$results = data_request::get_records(null, 'status DESC, timemodified DESC', '');
}
}
return $results;
}
/**
* Checks whether there is already an existing pending/in-progress data request for a user for a given request type.
*
* @param int $userid The user ID.
* @param int $type The request type.
* @return bool
* @throws coding_exception
* @throws dml_exception
*/
public static function has_ongoing_request($userid, $type) {
global $DB;
// Check if the user already has an incomplete data request of the same type.
$nonpendingstatuses = [
self::DATAREQUEST_STATUS_COMPLETE,
self::DATAREQUEST_STATUS_CANCELLED,
self::DATAREQUEST_STATUS_REJECTED,
];
list($insql, $inparams) = $DB->get_in_or_equal($nonpendingstatuses, SQL_PARAMS_NAMED);
$select = 'type = :type AND userid = :userid AND status NOT ' . $insql;
$params = array_merge([
'type' => $type,
'userid' => $userid
], $inparams);
return data_request::record_exists_select($select, $params);
}
/**
* Determines whether a request is active or not based on its status.
*
* @param int $status The request status.
* @return bool
*/
public static function is_active($status) {
// List of statuses which doesn't require any further processing.
$finalstatuses = [
self::DATAREQUEST_STATUS_COMPLETE,
self::DATAREQUEST_STATUS_CANCELLED,
self::DATAREQUEST_STATUS_REJECTED,
];
return !in_array($status, $finalstatuses);
}
/**
* Cancels the data request for a given request ID.
*
* @param int $requestid The request identifier.
* @param int $status The request status.
* @param int $dpoid The user ID of the Data Protection Officer
* @return bool
* @throws invalid_persistent_exception
* @throws coding_exception
*/
public static function update_request_status($requestid, $status, $dpoid = 0) {
// Update the request.
$datarequest = new data_request($requestid);
$datarequest->set('status', $status);
if ($dpoid) {
$datarequest->set('dpo', $dpoid);
}
return $datarequest->update();
}
/**
* Fetches a request based on the request ID.
*
* @param int $requestid The request identifier
* @return data_request
*/
public static function get_request($requestid) {
return new data_request($requestid);
}
/**
* Approves a data request based on the request ID.
*
* @param int $requestid The request identifier
* @return bool
* @throws coding_exception
* @throws dml_exception
* @throws invalid_persistent_exception
* @throws required_capability_exception
* @throws moodle_exception
*/
public static function approve_data_request($requestid) {
global $USER;
// Check first whether the user can manage data requests.
if (!self::can_manage_data_requests($USER->id)) {
$context = context_system::instance();
throw new required_capability_exception($context, 'tool/dataprivacy:managedatarequests', 'nopermissions', '');
}
// Check if request is already awaiting for approval.
$request = new data_request($requestid);
if ($request->get('status') != self::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
throw new moodle_exception('errorrequestnotwaitingforapproval', 'tool_dataprivacy');
}
// Update the status and the DPO.
$result = self::update_request_status($requestid, self::DATAREQUEST_STATUS_APPROVED, $USER->id);
// Fire an ad hoc task to initiate the data request process.
$task = new process_data_request_task();
$task->set_custom_data(['requestid' => $requestid]);
if ($request->get('type') == self::DATAREQUEST_TYPE_EXPORT) {
$task->set_userid($request->get('userid'));
}
manager::queue_adhoc_task($task, true);
return $result;
}
/**
* Rejects a data request based on the request ID.
*
* @param int $requestid The request identifier
* @return bool
* @throws coding_exception
* @throws dml_exception
* @throws invalid_persistent_exception
* @throws required_capability_exception
* @throws moodle_exception
*/
public static function deny_data_request($requestid) {
global $USER;
if (!self::can_manage_data_requests($USER->id)) {
$context = context_system::instance();
throw new required_capability_exception($context, 'tool/dataprivacy:managedatarequests', 'nopermissions', '');
}
// Check if request is already awaiting for approval.
$request = new data_request($requestid);
if ($request->get('status') != self::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
throw new moodle_exception('errorrequestnotwaitingforapproval', 'tool_dataprivacy');
}
// Update the status and the DPO.
return self::update_request_status($requestid, self::DATAREQUEST_STATUS_REJECTED, $USER->id);
}
/**
* Sends a message to the site's Data Protection Officer about a request.
*
* @param stdClass $dpo The DPO user record
* @param data_request $request The data request
* @return int|false
* @throws coding_exception
* @throws dml_exception
* @throws moodle_exception
*/
public static function notify_dpo($dpo, data_request $request) {
global $PAGE, $SITE;
// Create message to send to the Data Protection Officer(s).
$typetext = null;
switch ($request->get('type')) {
case self::DATAREQUEST_TYPE_EXPORT:
$typetext = get_string('requesttypeexport', 'tool_dataprivacy');
break;
case self::DATAREQUEST_TYPE_DELETE:
$typetext = get_string('requesttypedelete', 'tool_dataprivacy');
break;
case self::DATAREQUEST_TYPE_OTHERS:
$typetext = get_string('requesttypeothers', 'tool_dataprivacy');
break;
default:
throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
}
$subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typetext);
$requestedby = core_user::get_user($request->get('requestedby'));
$datarequestsurl = new moodle_url('/admin/tool/dataprivacy/datarequests.php');
$message = new message();
$message->courseid = $SITE->id;
$message->component = 'tool_dataprivacy';
$message->name = 'contactdataprotectionofficer';
$message->userfrom = $requestedby;
$message->replyto = $requestedby->email;
$message->replytoname = fullname($requestedby->email);
$message->subject = $subject;
$message->fullmessageformat = FORMAT_HTML;
$message->notification = 1;
$message->contexturl = $datarequestsurl;
$message->contexturlname = get_string('datarequests', 'tool_dataprivacy');
// Prepare the context data for the email message body.
$messagetextdata = [
'requestedby' => fullname($requestedby),
'requesttype' => $typetext,
'requestdate' => userdate($request->get('timecreated')),
'requestcomments' => text_to_html($request->get('comments')),
'datarequestsurl' => $datarequestsurl
];
$requestingfor = core_user::get_user($request->get('userid'));
if ($requestedby->id == $requestingfor->id) {
$messagetextdata['requestfor'] = $messagetextdata['requestedby'];
} else {
$messagetextdata['requestfor'] = fullname($requestingfor);
}
$output = $PAGE->get_renderer('tool_dataprivacy');
// Email the data request to the Data Protection Officer(s)/Admin(s).
$messagetextdata['dponame'] = fullname($dpo);
// Render message email body.
$messagehtml = $output->render_from_template('tool_dataprivacy/data_request_email', $messagetextdata);
$message->userto = $dpo;
$message->fullmessage = html_to_text($messagehtml);
$message->fullmessagehtml = $messagehtml;
// Send message.
return message_send($message);
}
/**
* Creates a new data purpose.
*
* @param stdClass $record
* @return \tool_dataprivacy\purpose.
*/
public static function create_purpose(stdClass $record) {
self::check_can_manage_data_registry();
$purpose = new purpose(0, $record);
$purpose->create();
return $purpose;
}
/**
* Updates an existing data purpose.
*
* @param stdClass $record
* @return \tool_dataprivacy\purpose.
*/
public static function update_purpose(stdClass $record) {
self::check_can_manage_data_registry();
$purpose = new purpose($record->id);
$purpose->from_record($record);
$result = $purpose->update();
return $purpose;
}
/**
* Deletes a data purpose.
*
* @param int $id
* @return bool
*/
public static function delete_purpose($id) {
self::check_can_manage_data_registry();
$purpose = new purpose($id);
if ($purpose->is_used()) {
throw new \moodle_exception('Purpose with id ' . $id . ' can not be deleted because it is used.');
}
return $purpose->delete();
}
/**
* Get all system data purposes.
*
* @return \tool_dataprivacy\purpose[]
*/
public static function get_purposes() {
self::check_can_manage_data_registry();
return purpose::get_records([], 'name', 'ASC');
}
/**
* Creates a new data category.
*
* @param stdClass $record
* @return \tool_dataprivacy\category.
*/
public static function create_category(stdClass $record) {
self::check_can_manage_data_registry();
$category = new category(0, $record);
$category->create();
return $category;
}
/**
* Updates an existing data category.
*
* @param stdClass $record
* @return \tool_dataprivacy\category.
*/
public static function update_category(stdClass $record) {
self::check_can_manage_data_registry();
$category = new category($record->id);
$category->from_record($record);
$result = $category->update();
return $category;
}
/**
* Deletes a data category.
*
* @param int $id
* @return bool
*/
public static function delete_category($id) {
self::check_can_manage_data_registry();
$category = new category($id);
if ($category->is_used()) {
throw new \moodle_exception('Category with id ' . $id . ' can not be deleted because it is used.');
}
return $category->delete();
}
/**
* Get all system data categories.
*
* @return \tool_dataprivacy\category[]
*/
public static function get_categories() {
self::check_can_manage_data_registry();
return category::get_records([], 'name', 'ASC');
}
/**
* Sets the context instance purpose and category.
*
* @param \stdClass $record
* @return \tool_dataprivacy\context_instance
*/
public static function set_context_instance($record) {
self::check_can_manage_data_registry($record->contextid);
if ($instance = context_instance::get_record_by_contextid($record->contextid, false)) {
// Update.
$instance->from_record($record);
if (empty($record->purposeid) && empty($record->categoryid)) {
// We accept one of them to be null but we delete it if both are null.
self::unset_context_instance($instance);
return;
}
} else {
// Add.
$instance = new context_instance(0, $record);
}
$instance->save();
return $instance;
}
/**
* Unsets the context instance record.
*
* @param \tool_dataprivacy\context_instance $instance
* @return null
*/
public static function unset_context_instance(context_instance $instance) {
self::check_can_manage_data_registry($instance->get('contextid'));
$instance->delete();
}
/**
* Sets the context level purpose and category.
*
* @throws \coding_exception
* @param \stdClass $record
* @return contextlevel
*/
public static function set_contextlevel($record) {
global $DB;
// Only manager at system level can set this.
self::check_can_manage_data_registry();
if ($record->contextlevel != CONTEXT_SYSTEM && $record->contextlevel != CONTEXT_USER) {
throw new \coding_exception('Only context system and context user can set a contextlevel ' .
'purpose and retention');
}
if ($contextlevel = contextlevel::get_record_by_contextlevel($record->contextlevel, false)) {
// Update.
$contextlevel->from_record($record);
} else {
// Add.
$contextlevel = new contextlevel(0, $record);
}
$contextlevel->save();
// We sync with their defaults as we removed these options from the defaults page.
$classname = \context_helper::get_class_for_level($record->contextlevel);
list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
set_config($purposevar, $record->purposeid, 'tool_dataprivacy');
set_config($categoryvar, $record->categoryid, 'tool_dataprivacy');
return $contextlevel;
}
/**
* Returns the effective category given a context instance.
*
* @param \context $context
* @param int $forcedvalue Use this categoryid value as if this was this context instance category.
* @return category|false
*/
public static function get_effective_context_category(\context $context, $forcedvalue=false) {
self::check_can_manage_data_registry($context->id);
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_context_value($context, 'category', $forcedvalue);
}
/**
* Returns the effective purpose given a context instance.
*
* @param \context $context
* @param int $forcedvalue Use this purposeid value as if this was this context instance purpose.
* @return purpose|false
*/
public static function get_effective_context_purpose(\context $context, $forcedvalue=false) {
self::check_can_manage_data_registry($context->id);
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_context_value($context, 'purpose', $forcedvalue);
}
/**
* Returns the effective category given a context level.
*
* @param int $contextlevel
* @param int $forcedvalue Use this categoryid value as if this was this context level category.
* @return category|false
*/
public static function get_effective_contextlevel_category($contextlevel, $forcedvalue=false) {
self::check_can_manage_data_registry(\context_system::instance()->id);
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_contextlevel_value($contextlevel, 'category', $forcedvalue);
}
/**
* Returns the effective purpose given a context level.
*
* @param int $contextlevel
* @param int $forcedvalue Use this purposeid value as if this was this context level purpose.
* @return purpose|false
*/
public static function get_effective_contextlevel_purpose($contextlevel, $forcedvalue=false) {
self::check_can_manage_data_registry(\context_system::instance()->id);
if (!data_registry::defaults_set()) {
return false;
}
return data_registry::get_effective_contextlevel_value($contextlevel, 'purpose', $forcedvalue);
}
/**
* Creates an expired context record for the provided context id.
*
* @param int $contextid
* @return \tool_dataprivacy\expired_context
*/
public static function create_expired_context($contextid) {
self::check_can_manage_data_registry();
$record = (object)[
'contextid' => $contextid,
'status' => expired_context::STATUS_EXPIRED,
];
$expiredctx = new expired_context(0, $record);
$expiredctx->save();
return $expiredctx;
}
/**
* Deletes an expired context record.
*
* @param int $id The tool_dataprivacy_ctxexpire id.
* @return bool True on success.
*/
public static function delete_expired_context($id) {
self::check_can_manage_data_registry();
$expiredcontext = new expired_context($id);
return $expiredcontext->delete();
}
/**
* Updates the status of an expired context.
*
* @param \tool_dataprivacy\expired_context $expiredctx
* @param int $status
* @return null
*/
public static function set_expired_context_status(expired_context $expiredctx, $status) {
self::check_can_manage_data_registry();
$expiredctx->set('status', $status);
$expiredctx->save();
}
}
@@ -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 $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,356 @@
<?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 tool_dataprivacy\purpose;
use tool_dataprivacy\category;
use tool_dataprivacy\contextlevel;
use tool_dataprivacy\context_instance;
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 {
/**
* @var array Inheritance between context levels.
*/
private static $contextlevelinheritance = [
CONTEXT_USER => [CONTEXT_SYSTEM],
CONTEXT_COURSECAT => [CONTEXT_SYSTEM],
CONTEXT_COURSE => [CONTEXT_COURSECAT, CONTEXT_SYSTEM],
CONTEXT_MODULE => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
CONTEXT_BLOCK => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
];
/**
* Returns purpose and category var names from a context class name
*
* @access private
* @param string $classname
* @return string[]
*/
public static function var_names_from_context($classname) {
return [
$classname . '_purpose',
$classname . '_category',
];
}
/**
* 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.
*
* @access private
* @param int $contextlevel
* @return int|false[]
*/
public static function get_defaults($contextlevel) {
$classname = \context_helper::get_class_for_level($contextlevel);
list($purposevar, $categoryvar) = self::var_names_from_context($classname);
$purposeid = get_config('tool_dataprivacy', $purposevar);
$categoryid = get_config('tool_dataprivacy', $categoryvar);
if (empty($purposeid)) {
$purposeid = false;
}
if (empty($categoryid)) {
$categoryid = false;
}
return [$purposeid, $categoryid];
}
/**
* Are data registry defaults set?
*
* At least the system defaults need to be set.
*
* @access private
* @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.
*
* @access private
* @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.
*
* @access private
* @param \context $context
* @return array
*/
public static function get_subject_scope(\context $context) {
if ($contextcourse = $context->get_course_context(false)) {
// Below course level we only look at course-assigned roles.
$roles = get_user_roles($contextcourse, 0, false);
} else {
$roles = get_user_roles($context, 0, false);
}
return array_map(function($role) {
if ($role->name) {
return $role->name;
} else {
return $role->shortname;
}
}, $roles);
}
/**
* Returns the effective value given a context instance
*
* @access private
* @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) {
if ($element !== 'purpose' && $element !== 'category') {
throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
}
$fieldname = $element . 'id';
if ($forcedvalue === false) {
$instance = context_instance::get_record_by_contextid($context->id, false);
if (!$instance) {
// If the instance does not have a value defaults to not set, so we grab the context level default as its value.
$instancevalue = context_instance::NOTSET;
} else {
$instancevalue = $instance->get($fieldname);
}
} else {
$instancevalue = $forcedvalue;
}
// Not set.
if ($instancevalue == context_instance::NOTSET) {
// The effective value varies depending on the context level.
if ($context->contextlevel == CONTEXT_USER) {
// Use the context level value as we don't allow people to set specific instances values.
return self::get_effective_contextlevel_value($context->contextlevel, $element);
} else {
// Use the default context level value.
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($context->contextlevel);
return self::get_element_instance($element, $$fieldname);
}
}
// Specific value for this context instance.
if ($instancevalue != context_instance::INHERIT) {
return self::get_element_instance($element, $instancevalue);
}
// This context is using inherited so let's return the parent effective value.
$parentcontext = $context->get_parent_context();
if (!$parentcontext) {
return false;
}
// The forced value should not be transmitted to parent contexts.
return self::get_effective_context_value($parentcontext, $element);
}
/**
* 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'
* @param int $forcedvalue Use this value as if this was this context level purpose.
* @return \tool_dataprivacy\purpose|false
*/
public static function get_effective_contextlevel_value($contextlevel, $element, $forcedvalue = false) {
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.');
}
if ($forcedvalue === false) {
$instance = contextlevel::get_record_by_contextlevel($contextlevel, false);
if (!$instance) {
// If the context level does not have a value defaults to not set, so we grab the context level default as its value.
$instancevalue = context_instance::NOTSET;
} else {
$instancevalue = $instance->get($fieldname);
}
} else {
$instancevalue = $forcedvalue;
}
// Not set -> Use the default context level value.
if ($instancevalue == context_instance::NOTSET) {
list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
return self::get_element_instance($element, $$fieldname);
}
// Specific value for this context instance.
if ($instancevalue != context_instance::INHERIT) {
return self::get_element_instance($element, $instancevalue);
}
if ($contextlevel == CONTEXT_SYSTEM) {
throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
}
// If we reach this point is that we are inheriting so get the parent context level and repeat.
$parentcontextlevel = reset(self::$contextlevelinheritance[$contextlevel]);
// Forced value are intentionally not passed as the force value should only affect the immediate context level.
return self::get_effective_contextlevel_value($parentcontextlevel, $element);
}
/**
* Returns the effective default purpose and category for a context level.
*
* @param int $contextlevel
* @param int $forcedpurposevalue Use this value as if this was this context level purpose.
* @param int $forcedcategoryvalue Use this value as if this was this context level category.
* @return int[]
*/
public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false, $forcedcategoryvalue = false) {
list($purposeid, $categoryid) = self::get_defaults($contextlevel);
// Honour forced values.
if ($forcedpurposevalue) {
$purposeid = $forcedpurposevalue;
}
if ($forcedcategoryvalue) {
$categoryid = $forcedcategoryvalue;
}
// Not set == INHERIT for defaults.
if ($purposeid == context_instance::INHERIT || $purposeid == context_instance::NOTSET) {
$purposeid = false;
}
if ($categoryid == context_instance::INHERIT || $categoryid == context_instance::NOTSET) {
$categoryid = false;
}
if ($contextlevel != CONTEXT_SYSTEM && ($purposeid === false || $categoryid === false)) {
foreach (self::$contextlevelinheritance[$contextlevel] as $parent) {
list($parentpurposeid, $parentcategoryid) = self::get_defaults($parent);
// Not set == INHERIT for defaults.
if ($parentpurposeid == context_instance::INHERIT || $parentpurposeid == context_instance::NOTSET) {
$parentpurposeid = false;
}
if ($parentcategoryid == context_instance::INHERIT || $parentcategoryid == context_instance::NOTSET) {
$parentcategoryid = false;
}
if ($purposeid === false && $parentpurposeid) {
$purposeid = $parentpurposeid;
}
if ($categoryid === false && $parentcategoryid) {
$categoryid = $parentcategoryid;
}
}
}
// They may still be false, but we return anyway.
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,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/>.
/**
* 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';
/**
* 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' => ''
],
'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,
],
'type' => PARAM_INT
],
'dpo' => [
'default' => 0,
'type' => PARAM_INT,
'null' => NULL_ALLOWED
],
'dpocomment' => [
'default' => '',
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED
],
];
}
}
@@ -0,0 +1,162 @@
<?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);
}
}
@@ -0,0 +1,132 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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 tool_dataprivacy\api;
use tool_dataprivacy\purpose;
use tool_dataprivacy\context_instance;
use tool_dataprivacy\data_registry;
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
*/
abstract class expired_contexts_manager {
const DELETE_LIMIT = 200;
/**
* Returns the list of expired context instances.
*
* @return \stdClass[]
*/
abstract protected function get_expired_contexts();
/**
* Flag expired contexts as expired.
*
* @return int The number of contexts flagged as expired.
*/
public function flag_expired() {
if (!$this->check_requirements()) {
return 0;
}
$contexts = $this->get_expired_contexts();
foreach ($contexts as $context) {
api::create_expired_context($context->id);
}
return count($contexts);
}
/**
* Deletes the expired contexts.
*
* @return int The number of deleted contexts.
*/
public function delete() {
$numprocessed = 0;
if (!$this->check_requirements()) {
return $numprocessed;
}
$privacymanager = new \core_privacy\manager();
$levels = [CONTEXT_USER, CONTEXT_MODULE, CONTEXT_BLOCK, CONTEXT_COURSE];
foreach ($levels as $level) {
$expiredcontexts = expired_context::get_records_by_contextlevel($level, expired_context::STATUS_APPROVED);
foreach ($expiredcontexts as $expiredctx) {
$context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
if (!$context) {
api::delete_expired_context($expiredctx->get('contextid'));
continue;
}
if (!PHPUNIT_TEST && !BEHAT_TEST) {
mtrace('Deleting context ' . $context->id . ' - ' .
shorten_text($context->get_context_name(true, true)));
}
$privacymanager->delete_data_for_all_users_in_context($context);
api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
$numprocessed += 1;
if ($numprocessed == self::DELETE_LIMIT) {
// Close the recordset.
$expiredcontexts->close();
break 2;
}
}
}
return $numprocessed;
}
/**
* Check that the requirements to start deleting contexts are satisified.
*
* @return bool
*/
protected function check_requirements() {
api::check_can_manage_data_registry(\context_system::instance()->id);
if (!data_registry::defaults_set()) {
return false;
}
return true;
}
}
@@ -0,0 +1,126 @@
<?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 for CONTEXT_COURSE, CONTEXT_MODULE and CONTEXT_BLOCK.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use tool_dataprivacy\purpose;
defined('MOODLE_INTERNAL') || die();
/**
* Expired contexts manager for CONTEXT_COURSE, CONTEXT_MODULE and CONTEXT_BLOCK.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_course_related_contexts extends \tool_dataprivacy\expired_contexts_manager {
/**
* Returns a recordset with user context instances that are possibly expired (to be confirmed by get_recordset_callback).
*
* @return \stdClass[]
*/
protected function get_expired_contexts() {
global $DB;
// Including context info + course end date + purposeid (this last one only if defined).
$fields = 'ctx.id AS id, ctxcourse.enddate AS courseenddate, dpctx.purposeid AS purposeid, ' .
\context_helper::get_preload_record_columns_sql('ctx');
// 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.
$sql = "SELECT $fields
FROM {context} ctx
JOIN (
SELECT c.enddate, subctx.path
FROM {context} subctx
JOIN {course} c
ON subctx.contextlevel = ? AND subctx.instanceid = c.id
WHERE c.enddate < ? AND c.enddate > 0
) 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 expiredctx.id IS NULL
ORDER BY ctx.contextlevel DESC, ctx.path";
$possiblyexpired = $DB->get_recordset_sql($sql, [CONTEXT_COURSE, time()]);
$expiredcontexts = [];
$excludedcontextids = [];
foreach ($possiblyexpired as $record) {
\context_helper::preload_from_record($record);
// No strict checking as the context may already be deleted (e.g. we just deleted a course,
// module contexts below it will not exist).
$context = \context::instance_by_id($record->id, false);
if (!$context) {
continue;
}
// We pass the value we just got from SQL so get_effective_context_purpose don't need to query
// the db again to retrieve it. If there is no tool_dataprovider_ctxinstance record
// $record->purposeid will be null which is ok as it would force get_effective_context_purpose
// to return the default purpose for the context context level (no db queries involved).
$purposevalue = $record->purposeid !== null ? $record->purposeid : context_instance::NOTSET;
// It should be cheap as system purposes and context level purposes will be retrieved from a cache most of the time.
$purpose = api::get_effective_context_purpose($context, $purposevalue);
$dt = new \DateTime();
$dt->setTimestamp($record->courseenddate);
$di = new \DateInterval($purpose->get('retentionperiod'));
$dt->add($di);
if (time() < $dt->getTimestamp()) {
// Exclude this context ID as it has not reached the retention period yet.
$excludedcontextids[] = $context->id;
continue;
}
// Check if this context has children that have not yet expired.
$hasunexpiredchildren = false;
$children = $context->get_child_contexts();
foreach ($children as $child) {
if (in_array($child->id, $excludedcontextids)) {
$hasunexpiredchildren = true;
break;
}
}
if ($hasunexpiredchildren) {
// Exclude this context ID as it has children that have not yet expired.
$excludedcontextids[] = $context->id;
continue;
}
$expiredcontexts[$context->id] = $context;
}
return $expiredcontexts;
}
}
@@ -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/>.
/**
* Expired contexts manager for CONTEXT_USER.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use tool_dataprivacy\purpose;
use tool_dataprivacy\context_instance;
defined('MOODLE_INTERNAL') || die();
/**
* Expired contexts manager for CONTEXT_USER.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
/**
* Returns the user context instances that are expired.
*
* @return \stdClass[]
*/
protected function get_expired_contexts() {
global $DB;
// Including context info + last login timestamp.
$fields = 'ctx.id AS id, ' . \context_helper::get_preload_record_columns_sql('ctx');
$purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
// Calculate what is considered expired according to the context level effective purpose (= now + retention period).
$expiredtime = new \DateTime();
$retention = new \DateInterval($purpose->get('retentionperiod'));
$expiredtime->sub($retention);
$sql = "SELECT $fields FROM {context} ctx
JOIN {user} u ON ctx.contextlevel = ? AND ctx.instanceid = u.id
LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx ON ctx.id = expiredctx.contextid
WHERE u.lastaccess <= ? AND u.lastaccess > 0 AND expiredctx.id IS NULL
ORDER BY ctx.path, ctx.contextlevel ASC";
$possiblyexpired = $DB->get_recordset_sql($sql, [CONTEXT_USER, $expiredtime->getTimestamp()]);
$expiredcontexts = [];
foreach ($possiblyexpired as $record) {
\context_helper::preload_from_record($record);
// No strict checking as the context may already be deleted (e.g. we just deleted a course,
// module contexts below it will not exist).
$context = \context::instance_by_id($record->id, false);
if (!$context) {
continue;
}
if (is_siteadmin($context->instanceid)) {
continue;
}
$courses = enrol_get_users_courses($context->instanceid, false, ['enddate']);
foreach ($courses as $course) {
if (!$course->enddate) {
// We can not know it what is going on here, so we prefer to be conservative.
continue 2;
}
if ($course->enddate >= time()) {
// Future or ongoing course.
continue 2;
}
}
$expiredcontexts[$context->id] = $context;
}
return $expiredcontexts;
}
}
File diff suppressed because it is too large Load Diff
@@ -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/>.
/**
* 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;
/**
* 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',
);
}
}
@@ -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,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.
*
* @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.
*
* @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\context::class;
}
}
@@ -0,0 +1,189 @@
<?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 renderer_base;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
/**
* 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
],
];
}
/**
* Assign values to the defined other properties.
*
* @param renderer_base $output The output renderer object.
* @return array
* @throws coding_exception
* @throws dml_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);
}
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'));
if ($this->persistent->get('type') == api::DATAREQUEST_TYPE_EXPORT) {
$values['typename'] = get_string('requesttypeexport', 'tool_dataprivacy');
$values['typenameshort'] = get_string('requesttypeexportshort', 'tool_dataprivacy');
} else if ($this->persistent->get('type') == api::DATAREQUEST_TYPE_DELETE) {
$values['typename'] = get_string('requesttypedelete', 'tool_dataprivacy');
$values['typenameshort'] = get_string('requesttypedeleteshort', 'tool_dataprivacy');
} else {
$values['typename'] = get_string('requesttypeothers', 'tool_dataprivacy');
$values['typenameshort'] = get_string('requesttypeothersshort', 'tool_dataprivacy');
}
$values['canreview'] = false;
switch ($this->persistent->get('status')) {
case api::DATAREQUEST_STATUS_PENDING:
$values['statuslabelclass'] = 'label-default';
$values['statuslabel'] = get_string('statuspending', 'tool_dataprivacy');
break;
case api::DATAREQUEST_STATUS_PREPROCESSING:
$values['statuslabelclass'] = 'label-default';
$values['statuslabel'] = get_string('statuspreprocessing', 'tool_dataprivacy');
break;
case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
$values['statuslabelclass'] = 'label-info';
$values['statuslabel'] = get_string('statusawaitingapproval', 'tool_dataprivacy');
// DPO can review the request once it's ready.
$values['canreview'] = true;
break;
case api::DATAREQUEST_STATUS_APPROVED:
$values['statuslabelclass'] = 'label-info';
$values['statuslabel'] = get_string('statusapproved', 'tool_dataprivacy');
break;
case api::DATAREQUEST_STATUS_PROCESSING:
$values['statuslabelclass'] = 'label-info';
$values['statuslabel'] = get_string('statusprocessing', 'tool_dataprivacy');
break;
case api::DATAREQUEST_STATUS_COMPLETE:
$values['statuslabelclass'] = 'label-success';
$values['statuslabel'] = get_string('statuscomplete', 'tool_dataprivacy');
break;
case api::DATAREQUEST_STATUS_CANCELLED:
$values['statuslabelclass'] = 'label-warning';
$values['statuslabel'] = get_string('statuscancelled', 'tool_dataprivacy');
break;
case api::DATAREQUEST_STATUS_REJECTED:
$values['statuslabelclass'] = 'label-important';
$values['statuslabel'] = get_string('statusrejected', 'tool_dataprivacy');
break;
}
return $values;
}
}
@@ -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 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 core\external\persistent_exporter;
/**
* 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 \tool_dataprivacy\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 array(
'formattedretentionperiod' => array(
'type' => PARAM_TEXT
),
);
}
/**
* Return other properties.
*
* @param \renderer_base $output
* @throws coding_exception
* @throws dml_exception
* @return array
*/
protected function get_other_values(\renderer_base $output) {
$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');
}
return ['formattedretentionperiod' => $formattedtime];
}
}
@@ -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,215 @@
<?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;
/**
* 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->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->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->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']);
}
}
/**
* 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);
}
$purposeoptions = \tool_dataprivacy\output\data_registry_page::purpose_options(
api::get_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 ($purposeoptions as $optionvalue => $unused) {
// 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 \tool_dataprivacy\purpose $effectivepurpose
* @param int $retentioncontextlevel
* @param \context $context The context, just for displaying (filters) purposes.
* @return string
*/
protected static function get_retention_display_text(\tool_dataprivacy\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,78 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains the defaults form.
*
* @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\output\data_registry_page;
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
/**
* Context levels defaults form.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class defaults extends \moodleform {
/**
* Define the form.
*/
public function definition() {
global $OUTPUT;
$mform = $this->_form;
$mform->setDisableShortforms();
$notification = $OUTPUT->notification(get_string('defaultsinfo', 'tool_dataprivacy'), \core\output\notification::NOTIFY_INFO);
$mform->addElement('html', $notification);
foreach ($this->_customdata['levels'] as $level => $classname) {
$mform->addElement('header', $classname . '-header',
get_string('contextlevelname' . $level, 'tool_dataprivacy'));
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
$includeinherit = true;
if ($level == CONTEXT_SYSTEM) {
$includeinherit = false;
}
$categoryoptions = data_registry_page::category_options($this->_customdata['categories'], false, $includeinherit);
$purposeoptions = data_registry_page::category_options($this->_customdata['purposes'], false, $includeinherit);
$mform->addElement('select', $categoryvar, get_string('category', 'tool_dataprivacy'), $categoryoptions);
$mform->setType($categoryvar, PARAM_INT);
$mform->addElement('select', $purposevar, get_string('purpose', 'tool_dataprivacy'), $purposeoptions);
$mform->setType($purposevar, PARAM_INT);
}
$this->add_action_buttons(true, get_string('savechanges'));
}
}
@@ -0,0 +1,113 @@
<?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 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);
$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);
// 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();
// 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,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,97 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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\data_request;
/**
* 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 {
/** The default number of results to be shown per page. */
const DEFAULT_PAGE_SIZE = 20;
/** @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 expired_contexts_table $expiredcontextstable
*/
public function __construct($filter, $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 dml_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(self::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,483 @@
<?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->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', 'tool_dataprivacy'),
'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 = [];
$blockinstances = \core_block_external::get_course_blocks($coursecontext->instanceid);
if (empty($blockinstances['blocks'])) {
return $branches;
}
foreach ($blockinstances['blocks'] as $bi) {
if (function_exists('block_instance_by_id')) {
$blockinstance = block_instance_by_id($bi['instanceid']);
} else {
// TODO To be removed when MDL-61621 gets integrated.
$blockinstance = $DB->get_record('block_instances', ['id' => $bi['instanceid']]);
$blockinstance = block_instance($blockinstance->blockname, $blockinstance);
}
$blockcontext = \context_block::instance($bi['instanceid']);
$displayname = shorten_text(format_string($blockinstance->get_title(), true, ['context' => $blockcontext->id]));
$branches[] = self::complete([
'text' => $displayname,
'contextid' => $blockcontext->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,115 @@
<?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_system;
use dml_exception;
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 data_requests_page implements renderable, templatable {
/** @var data_request[] $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 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);
$requests = [];
foreach ($this->requests as $request) {
$requestid = $request->get('id');
$status = $request->get('status');
$requestexporter = new data_request_exporter($request, ['context' => context_system::instance()]);
$item = $requestexporter->export($output);
// 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);
if ($status == 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);
}
$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,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 <[email protected]>
* @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 <[email protected]>
* @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 $filter The filter type.
* @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,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/>.
/**
* 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) {
$data = new stdClass();
$data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
$requests = [];
foreach ($this->requests as $request) {
$requestid = $request->get('id');
$status = $request->get('status');
$userid = $request->get('userid');
$usercontext = context_user::instance($userid);
$requestexporter = new data_request_exporter($request, ['context' => $usercontext]);
$item = $requestexporter->export($output);
$candownload = false;
$cancancel = true;
switch ($status) {
case api::DATAREQUEST_STATUS_COMPLETE:
$item->statuslabelclass = 'label-success';
$item->statuslabel = get_string('statuscomplete', 'tool_dataprivacy');
$cancancel = false;
$candownload = true;
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) {
$downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $requestid, '/',
'export.zip', true);
$downloadtext = get_string('download', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($downloadurl, null, $downloadtext);
}
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,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/>.
/**
* 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
];
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 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,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;
$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($title);
// 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,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/>.
/**
* Privacy class for requesting user data.
*
* @package tool_dataprivacy
* @copyright 2018 Jake Dallimore <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\context;
use core_privacy\local\request\contextlist;
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 {
/**
* 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 {
$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'
);
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
return new contextlist();
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
}
/**
* 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) {
}
}
+167
View File
@@ -0,0 +1,167 @@
<?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';
/**
* 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);
// Using validate() as self::$validated is private.
$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
),
'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 $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,69 @@
<?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_course_related_contexts();
$deleted = $manager->delete();
if ($deleted > 0) {
mtrace($deleted . ' course-related contexts have been deleted');
}
$manager = new \tool_dataprivacy\expired_user_contexts();
$deleted = $manager->delete();
if ($deleted > 0) {
mtrace($deleted . ' user contexts have been deleted');
}
}
}
@@ -0,0 +1,68 @@
<?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_course_related_contexts();
$flagged = $manager->flag_expired();
if ($flagged > 0) {
mtrace($flagged . ' course-related contexts have been flagged as expired');
}
$manager = new \tool_dataprivacy\expired_user_contexts();
$flagged = $manager->flag_expired();
if ($flagged > 0) {
mtrace($flagged . ' user contexts have been flagged as expired');
}
}
}
@@ -0,0 +1,95 @@
<?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 metadata 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\data_request;
defined('MOODLE_INTERNAL') || die();
/**
* Class that processes a data request and prepares the user's metadata 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 . '/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 user metadata...');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PREPROCESSING);
// TODO: Add code here to process the request and prepare the metadata to for review.
// When the preparation of the metadata finishes, update the request status to awaiting approval.
api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
mtrace('User metadata generation complete...');
// Get the list of the site Data Protection Officers.
$dpos = api::get_site_dpos();
// 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,207 @@
<?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 . '/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.
if ($request->status != api::DATAREQUEST_STATUS_APPROVED) {
mtrace("Request {$request->id} hasn\'t been approved yet or it has already been 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);
$usercontext = \context_user::instance($foruser->id);
// Update the status of this request as pre-processing.
mtrace('Processing request...');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PROCESSING);
if ($request->type == api::DATAREQUEST_TYPE_EXPORT) {
// TODO: Update this code to retrieve the approved_contextlist properly.
$manager = new \core_privacy\manager();
$contextcollection = $manager->get_contexts_for_userid($foruser->id);
$approvedcollection = new \core_privacy\local\request\contextlist_collection($foruser->id);
foreach ($contextcollection as $contextlist) {
$approvedcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist($foruser,
$contextlist->get_component(), $contextlist->get_contextids()));
}
$exportedcontent = $manager->export_user_data($approvedcollection);
$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);
} else if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
// TODO: Update this code to retrieve the approved_contextlist properly.
$manager = new \core_privacy\manager();
$contextcollection = $manager->get_contexts_for_userid($foruser->id);
$approvedcollection = new \core_privacy\local\request\contextlist_collection($foruser->id);
foreach ($contextcollection as $contextlist) {
$approvedcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist($foruser,
$contextlist->get_component(), $contextlist->get_contextids()));
}
$manager->delete_user_data($approvedcollection);
}
// When the preparation of the metadata finishes, update the request status to awaiting approval.
api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE);
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;
switch ($request->type) {
case api::DATAREQUEST_TYPE_EXPORT:
$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 = new moodle_url('#'); // TODO: Replace with the proper download URL.
$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:
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 ($emailonly) {
email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
} else {
message_send($message);
}
mtrace('Message sent to user: ' . $messagetextdata['username']);
// Send to the requester as well. requestedby is 0 if the request was made on behalf of the user by a DPO.
if (!empty($request->requestedby) && $foruser->id != $request->requestedby) {
$requestedby = core_user::get_user($request->requestedby);
$message->userto = $requestedby;
$messagetextdata['username'] = fullname($requestedby);
// Render message email body.
$messagehtml = $output->render_from_template('tool_dataprivacy/data_request_results_email', $messagetextdata);
$message->fullmessage = html_to_text($messagehtml);
$message->fullmessagehtml = $messagehtml;
// Send message.
if ($emailonly) {
email_to_user($requestedby, $dpo, $subject, $message->fullmessage, $messagehtml);
} else {
message_send($message);
}
mtrace('Message sent to requester: ' . $messagetextdata['username']);
}
}
}
@@ -0,0 +1,84 @@
<?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('classes/api.php');
require_once('createdatarequest_form.php');
$manage = optional_param('manage', 0, PARAM_INT);
$url = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php', ['manage' => $manage]);
$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.
if (!\tool_dataprivacy\api::can_contact_dpo()) {
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), \core\output\notification::NOTIFY_ERROR);
}
$mform = new tool_dataprivacy_data_request_form($url->out(false));
// Data request cancelled.
if ($mform->is_cancelled()) {
redirect($returnurl);
}
// Data request submitted.
if ($data = $mform->get_data()) {
\tool_dataprivacy\api::create_data_request($data->userid, $data->type, $data->comments);
redirect($returnurl, get_string('requestsubmitted', 'tool_dataprivacy'));
}
$title = get_string('contactdataprotectionofficer', 'tool_dataprivacy');
$PAGE->set_heading($title);
$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,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/>.
/**
* 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;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* 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 {
/**
* Form definition.
*
* @throws HTML_QuickForm_Error
* @throws coding_exception
* @throws dml_exception
*/
public function definition() {
global $DB, $USER;
$mform =& $this->_form;
if (api::is_site_dpo($USER->id)) {
$options = [
'ajax' => 'tool_dataprivacy/form-user-selector',
'multiple' => false
];
$mform->addElement('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.
$allusernames = get_all_user_name_fields(true, 'u');
$sql = "SELECT u.id, $allusernames
FROM {role_assignments} ra, {context} c, {user} u
WHERE ra.userid = :userid
AND ra.contextid = c.id
AND c.instanceid = u.id
AND c.contextlevel = :contextlevel";
$params = [
'userid' => $USER->id,
'contextlevel' => CONTEXT_USER
];
$children = $DB->get_records_sql($sql, $params);
if ($children) {
$useroptions = [];
$useroptions[$USER->id] = fullname($USER);
foreach ($children as $child) {
$childcontext = context_user::instance($child->id);
if (has_capability('tool/dataprivacy:makedatarequestsforchildren', $childcontext)) {
$useroptions[$child->id] = fullname($child);
}
}
$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;
}
}
+47
View File
@@ -0,0 +1,47 @@
<?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');
$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();
$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);
echo $OUTPUT->footer();
+42
View File
@@ -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/>.
/**
* 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');
$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();
$dataregistry = new tool_dataprivacy\output\data_registry_page($contextlevel, $contextid);
echo $output->render($dataregistry);
echo $OUTPUT->footer();
+42
View File
@@ -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/>.
/**
* 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');
$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);
$requests = tool_dataprivacy\api::get_data_requests();
$requestlist = new tool_dataprivacy\output\data_requests_page($requests);
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
echo $requestlistoutput->render($requestlist);
echo $OUTPUT->footer();
+52
View File
@@ -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/>.
/**
* 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' => []
],
];
+43
View File
@@ -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,
),
);
+108
View File
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="admin/tool/dataprivacy/db" VERSION="20180313" 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="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="1" 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="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"/>
</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="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" DEFAULT="0" NOTNULL="true" 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>
</TABLES>
</XMLDB>
+44
View File
@@ -0,0 +1,44 @@
<?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,
]
],
];
+166
View File
@@ -0,0 +1,166 @@
<?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 <[email protected]>
* @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_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_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_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,
],
];
+46
View File
@@ -0,0 +1,46 @@
<?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' => '*'
),
);
+44
View File
@@ -0,0 +1,44 @@
<?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 < 2018040500) {
// Nothing to do here. Moodle Plugins site's just complaining about missing upgrade.php.
// Savepoint reached.
upgrade_plugin_savepoint(true, 2018040500, 'tool', 'dataprivacy');
}
return true;
}
+76
View File
@@ -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/>.
/**
* 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');
$url = new \moodle_url('/admin/tool/dataprivacy/defaults.php');
$title = get_string('setdefaults', 'tool_dataprivacy');
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
$levels = \context_helper::get_all_levels();
// They are set through the context level site and user.
unset($levels[CONTEXT_SYSTEM]);
unset($levels[CONTEXT_USER]);
$customdata = [
'levels' => $levels,
'purposes' => \tool_dataprivacy\api::get_purposes(),
'categories' => \tool_dataprivacy\api::get_categories(),
];
$form = new \tool_dataprivacy\form\defaults($PAGE->url->out(false), $customdata);
$toform = new stdClass();
foreach ($levels as $level => $classname) {
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
$toform->{$purposevar} = get_config('tool_dataprivacy', $purposevar);
$toform->{$categoryvar} = get_config('tool_dataprivacy', $categoryvar);
}
$form->set_data($toform);
$returnurl = new \moodle_url('/admin/tool/dataprivacy/dataregistry.php');
if ($form->is_cancelled()) {
redirect($returnurl);
} else if ($data = $form->get_data()) {
foreach ($levels as $level => $classname) {
list($purposevar, $categoryvar) = \tool_dataprivacy\data_registry::var_names_from_context($classname);
if (isset($data->{$purposevar})) {
set_config($purposevar, $data->{$purposevar}, 'tool_dataprivacy');
}
if (isset($data->{$categoryvar})) {
set_config($categoryvar, $data->{$categoryvar}, 'tool_dataprivacy');
}
}
redirect($returnurl, get_string('defaultssaved', 'tool_dataprivacy'),
0, \core\output\notification::NOTIFY_SUCCESS);
}
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
$form->display();
echo $output->footer();
+58
View File
@@ -0,0 +1,58 @@
<?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');
$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();
+59
View File
@@ -0,0 +1,59 @@
<?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');
$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();
@@ -0,0 +1,171 @@
<?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/>.
/**
* Strings for component 'tool_dataprivacy'
*
* @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();
$string['pluginname'] = 'Data privacy';
$string['pluginname_help'] = 'Data privacy plugin';
$string['activitiesandresources'] = 'Activities and resources';
$string['addcategory'] = 'Add category';
$string['addpurpose'] = 'Add purpose';
$string['approve'] = 'Approve';
$string['approverequest'] = 'Approve request';
$string['cancelrequest'] = 'Cancel request';
$string['cancelrequestconfirmation'] = 'Do you really want cancel this data request?';
$string['categories'] = 'Categories';
$string['category'] = 'Category';
$string['categorycreated'] = 'Category created';
$string['categorieslist'] = 'List of data categories';
$string['categoryupdated'] = 'Category updated';
$string['close'] = 'Close';
$string['confirmapproval'] = 'Do you really want to approve this data request?';
$string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
$string['confirmdenial'] = 'Do you really want deny this data request?';
$string['contactdataprotectionofficer'] = 'Contact Data Protection Officer';
$string['contactdataprotectionofficer_desc'] = 'Enabling this feature will provide a link for users to contact the site\'s Data Protection Officer through this site. This link will be shown on their profile page, and on the site\'s privacy policy page, as well. The link leads to a form in which the user can make a data request to the Data Protection Officer.';
$string['contextlevelname10'] = 'Site';
$string['contextlevelname30'] = 'Users';
$string['contextlevelname40'] = 'Course categories';
$string['contextlevelname50'] = 'Courses';
$string['contextlevelname70'] = 'Activity modules';
$string['contextlevelname80'] = 'Blocks';
$string['contextpurposecategorysaved'] = 'Purpose and category saved.';
$string['contactdpoviaprivacypolicy'] = 'Please contact the site\'s Data Protection Officer as described in the Privacy Policy';
$string['createcategory'] = 'Create data category';
$string['createpurpose'] = 'Create data purpose';
$string['datadeletion'] = 'Data deletion';
$string['datadeletionpagehelp'] = 'This page lists the contexts that are already past their retention period and need to be confirmed for user data deletion. Once the selected contexts have been confirmed for deletion, the user data related to these contexts will be deleted on the next execution of the "Delete expired contexts" scheduled task.';
$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for children';
$string['dataprivacy:managedatarequests'] = 'Manage data requests';
$string['dataprivacy:managedataregistry'] = 'Manage data registry';
$string['dataregistry'] = 'Data registry';
$string['datarequestemailsubject'] = 'Data request: {$a}';
$string['datarequests'] = 'Data requests';
$string['daterequested'] = 'Date requested';
$string['daterequesteddetail'] = 'Date requested:';
$string['defaultsinfo'] = 'Default categories and purposes are applied to all newly created instances.';
$string['deletecategory'] = 'Delete "{$a}" category';
$string['deletecategorytext'] = 'Are you sure you want to delete "{$a}" category?';
$string['deleteexpiredcontextstask'] = 'Delete expired contexts';
$string['deletepurpose'] = 'Delete "{$a}" purpose';
$string['deletepurposetext'] = 'Are you sure you want to delete "{$a}" purpose?';
$string['defaultssaved'] = 'Defaults saved';
$string['deny'] = 'Deny';
$string['denyrequest'] = 'Deny request';
$string['download'] = 'Download';
$string['dporolemapping'] = 'Data Protection Officer role mapping';
$string['dporolemapping_desc'] = 'Select one or more roles that map to the Data Protection Officer role. Users with these roles will be able to manage data requests. This requires the selected role(s) to have the capability \'tool/dataprivacy:managedatarequests\'';
$string['editcategories'] = 'Edit categories';
$string['editcategory'] = 'Edit category';
$string['editcategories'] = 'Edit categories';
$string['editpurpose'] = 'Edit purpose';
$string['editpurposes'] = 'Edit purposes';
$string['effectiveretentionperiodcourse'] = '{$a} (after the course end date)';
$string['effectiveretentionperioduser'] = '{$a} (since the last time the user accessed the site)';
$string['emailsalutation'] = 'Dear {$a},';
$string['errorinvalidrequeststatus'] = 'Invalid request status!';
$string['errorinvalidrequesttype'] = 'Invalid request type!';
$string['errornoexpiredcontexts'] = 'There are no expired contexts to process';
$string['errorcontexthasunexpiredchildren'] = 'The context "{$a}" still has sub-contexts that have not yet expired. No contexts have been flagged for deletion.';
$string['errorrequestalreadyexists'] = 'You already have an ongoing request.';
$string['errorrequestnotfound'] = 'Request not found';
$string['errorrequestnotwaitingforapproval'] = 'The request is not awaiting approval. Either it is not yet ready or it has already been processed.';
$string['errorsendingmessagetodpo'] = 'An error was encountered while trying to send a message to {$a}.';
$string['expiredretentionperiodtask'] = 'Expired retention period';
$string['expiry'] = 'Expiry';
$string['frontpagecourse'] = 'Front page course';
$string['inherit'] = 'Inherit';
$string['messageprovider:contactdataprotectionofficer'] = 'Data requests';
$string['messageprovider:datarequestprocessingresults'] = 'Data request processing results';
$string['message'] = 'Message';
$string['messagelabel'] = 'Message:';
$string['moduleinstancename'] = '{$a->instancename} ({$a->modulename})';
$string['mypersonaldatarequests'] = 'My personal data requests';
$string['nameandparent'] = '{$a->parent} / {$a->name}';
$string['nameemail'] = '{$a->name} ({$a->email})';
$string['nchildren'] = '{$a} children';
$string['newrequest'] = 'New request';
$string['nodatarequests'] = 'There are no data requests';
$string['noactivitiestoload'] = 'No activities';
$string['noassignedroles'] = 'No assigned roles in this context';
$string['noblockstoload'] = 'No blocks';
$string['nocategories'] = 'There are no categories yet';
$string['nocoursestoload'] = 'No activities';
$string['noexpiredcontexts'] = 'There are no expired contexts in this level that need to be confirmed for deletion.';
$string['nopersonaldatarequests'] = 'You don\'t have any personal data requests';
$string['nopurposes'] = 'There are no purposes yet';
$string['nosubjectaccessrequests'] = 'There are no data requests that you need to act on';
$string['nosystemdefaults'] = 'Site purpose and category have not yet been defined.';
$string['notset'] = 'Not set (use the default value)';
$string['privacy'] = 'Privacy';
$string['privacy:metadata:request'] = 'Information from personal data requests (subject access and deletion requests) made for this site.';
$string['privacy:metadata:request:comments'] = 'Any user comments accompanying the request.';
$string['privacy:metadata:request:userid'] = 'The ID of the user to whom the request belongs';
$string['privacy:metadata:request:requestedby'] = 'The ID of the user making the request, if made on behalf of another user.';
$string['privacy:metadata:request:dpocomment'] = 'Any comments made by the site\'s respective privacy officer regarding the request.';
$string['privacy:metadata:request:timecreated'] = 'The timestamp indicating when the request was made by the user.';
$string['protected'] = 'Protected';
$string['protectedlabel'] = 'The retention of this data has a higher legal precedent over a user\'s request to be forgotten. This data will only be deleted after the retention period has expired.';
$string['purpose'] = 'Purpose';
$string['purposecreated'] = 'Purpose created';
$string['purposes'] = 'Purposes';
$string['purposeslist'] = 'List of data purposes';
$string['purposeupdated'] = 'Purpose updated';
$string['replyto'] = 'Reply to';
$string['requestactions'] = 'Actions';
$string['requestby'] = 'Requested by';
$string['requestcomments'] = 'Comments';
$string['requestcomments_help'] = 'Please feel free to provide more details about your request';
$string['requestemailintro'] = 'You have received a data request:';
$string['requestfor'] = 'Requesting for';
$string['requeststatus'] = 'Status';
$string['requestsubmitted'] = 'Your request has been submitted to the site\'s Data Protection Officer';
$string['requesttype'] = 'Type';
$string['requesttype_help'] = 'Select the reason why you would like to contact the site\'s Data Protection Officer';
$string['requesttypedelete'] = 'Delete all of my personal data';
$string['requesttypedeleteshort'] = 'Delete';
$string['requesttypeexport'] = 'Export all of my personal data';
$string['requesttypeexportshort'] = 'Export';
$string['requesttypeothers'] = 'General inquiry';
$string['requesttypeothersshort'] = 'Others';
$string['resultdeleted'] = 'You recently requested to have your account and personal data in {$a} to be deleted. This process has been completed and you will no longer be able to log in.';
$string['resultdownloadready'] = 'Your copy of your personal data in {$a} that you recently requested is now available for download. Please click on the link below to go to the download page.';
$string['reviewdata'] = 'Review data';
$string['retentionperiod'] = 'Retention period';
$string['retentionperiodnotdefined'] = 'No retention period was defined';
$string['retentionperiodzero'] = 'No retention period';
$string['send'] = 'Send';
$string['setdefaults'] = 'Set defaults';
$string['statusapproved'] = 'Approved';
$string['statusawaitingapproval'] = 'Awaiting approval';
$string['statuscancelled'] = 'Cancelled';
$string['statuscomplete'] = 'Complete';
$string['statusdetail'] = 'Status:';
$string['statuspreprocessing'] = 'Pre-processing';
$string['statusprocessing'] = 'Processing';
$string['statuspending'] = 'Pending';
$string['statusrejected'] = 'Rejected';
$string['subjectscope'] = 'Subject scope';
$string['user'] = 'User';
$string['viewrequest'] = 'View the request';
+197
View File
@@ -0,0 +1,197 @@
<?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 privacy plugin library
* @package tool_dataprivacy
* @copyright 2018 onwards Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_user\output\myprofile\tree;
defined('MOODLE_INTERNAL') || die();
/**
* Add nodes to myprofile page.
*
* @param tree $tree Tree object
* @param stdClass $user User object
* @param bool $iscurrentuser
* @param stdClass $course Course object
* @return bool
* @throws coding_exception
* @throws dml_exception
* @throws moodle_exception
*/
function tool_dataprivacy_myprofile_navigation(tree $tree, $user, $iscurrentuser, $course) {
global $PAGE, $USER;
// Get the Privacy and policies category.
if (!array_key_exists('privacyandpolicies', $tree->__get('categories'))) {
// Create the category.
$categoryname = get_string('privacyandpolicies', 'admin');
$category = new core_user\output\myprofile\category('privacyandpolicies', $categoryname, 'contact');
$tree->add_category($category);
} else {
// Get the existing category.
$category = $tree->__get('categories')['privacyandpolicies'];
}
// Contact data protection officer link.
if (\tool_dataprivacy\api::can_contact_dpo() && $iscurrentuser) {
$renderer = $PAGE->get_renderer('tool_dataprivacy');
$content = $renderer->render_contact_dpo_link($USER->email);
$node = new core_user\output\myprofile\node('privacyandpolicies', 'contactdpo', null, null, null, $content);
$category->add_node($node);
$PAGE->requires->js_call_amd('tool_dataprivacy/myrequestactions', 'init');
$url = new moodle_url('/admin/tool/dataprivacy/mydatarequests.php');
$node = new core_user\output\myprofile\node('privacyandpolicies', 'datarequests',
get_string('datarequests', 'tool_dataprivacy'), null, $url);
$category->add_node($node);
}
// Add the Privacy category to the tree if it's not empty and it doesn't exist.
$nodes = $category->nodes;
if (!empty($nodes)) {
if (!array_key_exists('privacyandpolicies', $tree->__get('categories'))) {
$tree->add_category($category);
}
return true;
}
return false;
}
/**
* Fragment to add a new purpose.
*
* @param array $args The fragment arguments.
* @return string The rendered mform fragment.
*/
function tool_dataprivacy_output_fragment_addpurpose_form($args) {
$formdata = [];
if (!empty($args['jsonformdata'])) {
$serialiseddata = json_decode($args['jsonformdata']);
parse_str($serialiseddata, $formdata);
}
$persistent = new \tool_dataprivacy\purpose();
$mform = new \tool_dataprivacy\form\purpose(null, ['persistent' => $persistent],
'post', '', null, true, $formdata);
if (!empty($args['jsonformdata'])) {
// Show errors if data was received.
$mform->is_validated();
}
return $mform->render();
}
/**
* Fragment to add a new category.
*
* @param array $args The fragment arguments.
* @return string The rendered mform fragment.
*/
function tool_dataprivacy_output_fragment_addcategory_form($args) {
$formdata = [];
if (!empty($args['jsonformdata'])) {
$serialiseddata = json_decode($args['jsonformdata']);
parse_str($serialiseddata, $formdata);
}
$persistent = new \tool_dataprivacy\category();
$mform = new \tool_dataprivacy\form\category(null, ['persistent' => $persistent],
'post', '', null, true, $formdata);
if (!empty($args['jsonformdata'])) {
// Show errors if data was received.
$mform->is_validated();
}
return $mform->render();
}
/**
* Fragment to edit a context purpose and category.
*
* @param array $args The fragment arguments.
* @return string The rendered mform fragment.
*/
function tool_dataprivacy_output_fragment_context_form($args) {
global $PAGE;
$contextid = $args[0];
$context = \context_helper::instance_by_id($contextid);
$customdata = \tool_dataprivacy\form\context_instance::get_context_instance_customdata($context);
if (!empty($customdata['purposeretentionperiods'])) {
$PAGE->requires->js_call_amd('tool_dataprivacy/effective_retention_period', 'init', [$customdata['purposeretentionperiods']]);
}
$mform = new \tool_dataprivacy\form\context_instance(null, $customdata);
return $mform->render();
}
/**
* Fragment to edit a contextlevel purpose and category.
*
* @param array $args The fragment arguments.
* @return string The rendered mform fragment.
*/
function tool_dataprivacy_output_fragment_contextlevel_form($args) {
global $PAGE;
$contextlevel = $args[0];
$customdata = \tool_dataprivacy\form\contextlevel::get_contextlevel_customdata($contextlevel);
if (!empty($customdata['purposeretentionperiods'])) {
$PAGE->requires->js_call_amd('tool_dataprivacy/effective_retention_period', 'init', [$customdata['purposeretentionperiods']]);
}
$mform = new \tool_dataprivacy\form\contextlevel(null, $customdata);
return $mform->render();
}
/**
* Serves any files associated with the data privacy settings.
*
* @param stdClass $course Course object
* @param stdClass $cm Course module object
* @param context $context Context
* @param string $filearea File area for data privacy
* @param array $args Arguments
* @param bool $forcedownload If we are forcing the download
* @param array $options More options
* @return bool Returns false if we don't find a file.
*/
function tool_dataprivacy_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) {
if ($context->contextlevel == CONTEXT_USER) {
$fs = get_file_storage();
$relativepath = implode('/', $args);
$fullpath = "/$context->id/tool_dataprivacy/$filearea/$relativepath";
if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
return false;
}
send_stored_file($file, 0, 0, $forcedownload, $options);
} else {
send_file_not_found();
}
}
+63
View File
@@ -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/>.
/**
* 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');
$courseid = optional_param('course', 0, PARAM_INT);
$url = new moodle_url('/admin/tool/dataprivacy/mydatarequests.php');
if ($courseid) {
$url->param('course', $courseid);
}
$PAGE->set_url($url);
require_login();
if (isguestuser()) {
print_error('noguest');
}
$usercontext = context_user::instance($USER->id);
$PAGE->set_context($usercontext);
// Return URL.
$params = ['id' => $USER->id];
if ($courseid) {
$params['course'] = $courseid;
}
$returnurl = new moodle_url('/user/profile.php', $params);
$title = get_string('datarequests', 'tool_dataprivacy');
$PAGE->set_heading($title);
$PAGE->set_title($title);
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
$requests = tool_dataprivacy\api::get_data_requests($USER->id);
$requestlist = new tool_dataprivacy\output\my_data_requests_page($requests);
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
echo $requestlistoutput->render($requestlist);
echo $OUTPUT->footer();
+39
View File
@@ -0,0 +1,39 @@
<?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');
$url = new moodle_url("/admin/tool/dataprivacy/purposes.php");
$title = get_string('editpurposes', 'tool_dataprivacy');
\tool_dataprivacy\page_helper::setup($url, $title, 'dataregistry');
$output = $PAGE->get_renderer('tool_dataprivacy');
echo $output->header();
$purposes = \tool_dataprivacy\api::get_purposes();
$renderable = new \tool_dataprivacy\output\purposes($purposes);
echo $output->render($renderable);
echo $output->footer();
+72
View File
@@ -0,0 +1,72 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Adds Data privacy-related settings.
*
* @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;
if ($hassiteconfig) {
$privacysettings = $ADMIN->locate('privacysettings');
if ($ADMIN->fulltree) {
// Contact data protection officer.
$privacysettings->add(new admin_setting_configcheckbox('tool_dataprivacy/contactdataprotectionofficer',
new lang_string('contactdataprotectionofficer', 'tool_dataprivacy'),
new lang_string('contactdataprotectionofficer_desc', 'tool_dataprivacy'), 1)
);
// Fetch roles that are assignable.
$assignableroles = get_assignable_roles(context_system::instance());
// Fetch roles that have the capability to manage data requests.
$capableroles = get_roles_with_capability('tool/dataprivacy:managedatarequests');
// Role(s) that map to the Data Protection Officer role. These are assignable roles with the capability to manage data requests.
$roles = [];
foreach ($capableroles as $key => $role) {
if (array_key_exists($key, $assignableroles)) {
$roles[$key] = $assignableroles[$key];
}
}
if (!empty($roles)) {
$privacysettings->add(new admin_setting_configmultiselect('tool_dataprivacy/dporoles',
new lang_string('dporolemapping', 'tool_dataprivacy'),
new lang_string('dporolemapping_desc', 'tool_dataprivacy'), null, $roles)
);
}
}
}
// Link that leads to the data requests management page.
$ADMIN->add('privacy', new admin_externalpage('datarequests', get_string('datarequests', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/datarequests.php'), 'tool/dataprivacy:managedatarequests')
);
// Link that leads to the data registry management page.
$ADMIN->add('privacy', new admin_externalpage('dataregistry', get_string('dataregistry', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/dataregistry.php'), 'tool/dataprivacy:managedataregistry')
);
// Link that leads to the review page of expired contexts that are up for deletion.
$ADMIN->add('privacy', new admin_externalpage('datadeletion', get_string('datadeletion', 'tool_dataprivacy'),
new moodle_url('/admin/tool/dataprivacy/datadeletion.php'), 'tool/dataprivacy:managedataregistry')
);
+14
View File
@@ -0,0 +1,14 @@
.nav-pills .nav-pills {
margin-left: 1rem;
}
/*Extra attribute selection to have preference over bs2's .moodle-actionmenu[data-enhance] */
.data-registry > .top-nav > .singlebutton,
.data-registry > .top-nav > .moodle-actionmenu[data-owner='dataregistry-actions'] {
display: inline-block;
}
.data-registry .context-tree {
height: 70vh;
overflow-y: scroll;
}
@@ -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/>.
}}
{{!
@template tool_dataprivacy/categories
Manage categories.
Classes required for JS:
Data attributes required for JS:
Context variables required for this template:
* categories - array of objects
* actions - array of actions (already in HTML).
Example context (json):
{
"categoriesexist": 1,
"categories": [
{
"name" : "Category 1",
"description": "<strong>Description 1</strong>",
"actions": [
]
}, {
"name" : "Category 2",
"description": "<strong>Description 2</strong>",
"actions": [
]
}
]
}
}}
{{#navigation}}
{{> core/action_link}}
{{/navigation}}
<div data-region="categories" class="m-t-3 m-b-1">
<h3>{{#str}}categories, tool_dataprivacy{{/str}}</h3>
<div class="m-y-1">
<button class="btn btn-secondary" data-add-element="category">
{{#pix}}t/add, moodle, {{#str}}addcategory, tool_dataprivacy{{/str}}{{/pix}}
</button>
</div>
<table class="generaltable fullwidth">
<caption class="accesshide">{{#str}}categorieslist, tool_dataprivacy{{/str}}</caption>
<thead>
<tr>
<th scope="col">{{#str}}name{{/str}}</th>
<th scope="col">{{#str}}description{{/str}}</th>
<th scope="col">{{#str}}actions{{/str}}</th>
</tr>
</thead>
<tbody>
{{#categories}}
<tr data-categoryid="{{id}}">
<td>{{{name}}}</td>
<td>{{{description}}}</td>
<td>
{{#actions}}
{{> core/action_menu}}
{{/actions}}
</td>
</tr>
{{/categories}}
</tbody>
</table>
{{^categories}}
<p>
{{#str}}nocategories, tool_dataprivacy{{/str}}
</p>
{{/categories}}
</div>
@@ -0,0 +1,58 @@
{{!
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/>.
}}
{{!
@template tool_dataprivacy/contact_dpo
The purpose of this template is to enable the user to contact the site's DPO via email.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* userid int The user's ID.
* email string The user's email address.
Example context (json):
{
"userid": 1,
"replytoemail": "[email protected]"
}
}}
<div class="container">
<div class="row">
<label class="col-md-3 span3 col-form-label">{{#str}}replyto, tool_dataprivacy{{/str}}</label>
<div class="col-md-9 span9 col-form-label">{{replytoemail}}</div>
</div>
<div class="row" data-region="messageinput">
<label for="message" class="col-md-3 span3 col-form-label">
{{#str}}message, tool_dataprivacy{{/str}}
<span class="pull-xs-right text-nowrap">
<abbr class="initialism text-danger" title="{{#str}}required{{/str}}">{{#pix}}req, core, {{#str}}required{{/str}}{{/pix}}</abbr>
</span>
</label>
<div class="col-md-9 span9">
<textarea class="form-control" id="message" cols="60" rows="8"></textarea>
<div class="form-control-feedback" id="id_error_message" hidden="hidden">
{{#str}}required, moodle{{/str}}
</div>
</div>
</div>
</div>
@@ -0,0 +1,38 @@
{{!
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/>.
}}
{{!
@template tool_dataprivacy/context_tree
Context tree.
Classes required for JS:
Data attributes required for JS:
Context variables required for this template:
Example context (json):
{
}
}}
{{#branches}}
<nav class="nav-pills flex-column">
{{> tool_dataprivacy/context_tree_node}}
</nav>
{{/branches}}
@@ -0,0 +1,50 @@
{{!
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/>.
}}
{{!
@template tool_dataprivacy/context_tree_branch
A tree branch.
Classes required for JS:
Data attributes required for JS:
Context variables required for this template:
Example context (json):
{
}
}}
<a class="nav-link {{#active}}active{{/active}}" href="#" data-context-tree-node="1"
{{#contextlevel}} data-contextlevel="{{.}}" {{/contextlevel}}
{{#contextid}} data-contextid="{{.}}" {{/contextid}}
{{#expandcontextid}} data-expandcontextid="{{.}}" {{/expandcontextid}}
{{#expandelement}} data-expandelement="{{.}}" {{/expandelement}}
data-expanded="{{expanded}}">
<i class="fa
{{#expandelement}}{{#expanded}}fa-minus{{/expanded}}{{/expandelement}}
{{#expandelement}}{{^expanded}}fa-plus{{/expanded}}{{/expandelement}}
{{^expandelement}}{{#expanded}}fa-folder-open{{/expanded}}{{/expandelement}}
{{^expandelement}}{{^expanded}}fa-file{{/expanded}}{{/expandelement}}"
>
</i>
{{text}}
</a>
{{> tool_dataprivacy/context_tree_branches}}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,61 @@
{{!
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 comments.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_dataprivacy/data_registry
Data registry main page.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{
}
}}
<div class="data-registry">
<div class="top-nav">
{{#defaultsbutton}}
{{> core/action_link}}
{{/defaultsbutton}}
{{#actions}}
{{> core/action_menu}}
{{/actions}}
</div>
{{#nosystemdefaults}}
<div class="m-t-1">
{{> core/notification_warning}}
</div>
{{/nosystemdefaults}}
<div class="container-fluid m-t-2">
<div class="row">
<div class="span3 col-md-4 p-l-0 nav-pills context-tree">
{{#tree}}
{{> tool_dataprivacy/context_tree_node}}
{{/tree}}
</div>
<div class="span9 col-md-8" id="context-form-container">
</div>
</div>
</div>
</div>
@@ -0,0 +1,105 @@
{{!
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/>.
}}
{{!
@template tool_dataprivacy/data_request_email
Email template for the data request.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* string dponame The name of the Data Protection Officer
* string requestfor The user the request is being made for.
* string requestedby The one making the request.
* string requesttype The request type.
* string requestdate The date the request was made.
* string requestcomments Additional details regarding the request.
* bool forself Whether the request has been made on behalf of another user or not.
* string datarequestsurl The URL to the data requests page.
Example context (json):
{
"dponame": "Eva Ferrer",
"requestfor": "Oscar Olsen",
"requestedby": "Angus Zhang",
"requesttype": "Export user data",
"requestdate": "31 January 2018",
"requestcomments": "Dear admin,<br/> I would like to request a copy of my son's user data. Thanks!",
"forself": true,
"datarequestsurl": "#"
}
}}
<style>
table, th, td {
border: 1px solid black;
padding: 0.5em;
}
</style>
<div>
<p>{{#str}}emailsalutation, tool_dataprivacy, {{dponame}}{{/str}}</p>
<p>{{#str}}requestemailintro, tool_dataprivacy{{/str}}</p>
<table>
<tr>
<th scope="row">
{{#str}}requesttype, tool_dataprivacy{{/str}}
</th>
<td>
{{requesttype}}
</td>
</tr>
<tr>
<th scope="row">
{{#str}}requestfor, tool_dataprivacy{{/str}}
</th>
<td>
{{requestfor}}
</td>
</tr>
{{^forself}}
<tr>
<th scope="row">
{{#str}}requestby, tool_dataprivacy{{/str}}
</th>
<td>
{{requestedby}}
</td>
</tr>
{{/forself}}
<tr>
<th scope="row">
{{#str}}requestcomments, tool_dataprivacy{{/str}}
</th>
<td>
{{{requestcomments}}}
</td>
</tr>
<tr>
<th scope="row">
{{#str}}daterequested, tool_dataprivacy{{/str}}
</th>
<td>
{{requestdate}}
</td>
</tr>
</table>
<hr>
<a href="{{datarequestsurl}}">{{#str}}viewrequest, tool_dataprivacy{{/str}}</a>
</div>
@@ -0,0 +1,40 @@
{{!
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 comments.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_dataprivacy/data_request_modal
Data request details
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* none
Example context (json):
{
}
}}
{{< core/modal }}
{{$footer}}
<button type="button" class="btn btn-primary" data-action="approve">{{#str}} approve, tool_dataprivacy {{/str}}</button>
<button type="button" class="btn btn-secondary" data-action="deny">{{#str}} deny, tool_dataprivacy {{/str}}</button>
{{/footer}}
{{/ core/modal }}
@@ -0,0 +1,54 @@
{{!
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/>.
}}
{{!
@template tool_dataprivacy/data_request_results_email
Email template for the data request processing results.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* string username The user's name.
* string message The message to the user.
* object downloadlink The action link context data for the download link (e.g. in case of user data export).
Example context (json):
{
"username": "Eva Ferrer",
"message": "Your data is ready for download. Enjoy!",
"downloadlink": {
"url": "#",
"id": "test-id",
"attributes": [
{
"name": "title",
"value": "Download here!"
}
],
"text": "Download here!"
}
}
}}
<div>
<p>{{#str}}emailsalutation, tool_dataprivacy, {{username}}{{/str}}</p>
<p>{{message}}</p>
<p>{{#downloadlink}}{{> core/action_link}}{{/downloadlink}}</p>
</div>
@@ -0,0 +1,152 @@
{{!
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 comments.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_dataprivacy/data_requests
Data requests page.
Classes required for JS:
* requestactions
Data attributes required for JS:
* none
Context variables required for this template:
* requests - Array of data requests.
Example context (json):
{
"requests": [
{
"id": 1,
"foruser" : {
"fullname": "Oscar Olsen",
"profileurl": "#"
},
"typenameshort" : "Export",
"comments": "I would like to download all of my daughter's personal data",
"statuslabelclass": "label-default",
"statuslabel": "Pending",
"timecreated" : 1517902435
},
{
"id": 2,
"foruser" : {
"fullname": "Alexandre Denys",
"profileurl": "#"
},
"typenameshort" : "Export",
"comments": "Please give me all of the information you have about me...",
"statuslabelclass": "label-warning",
"statuslabel": "Awaiting completion",
"timecreated" : 1517902435
},
{
"id": 3,
"foruser" : {
"fullname": "Hirondino Moura",
"profileurl": "#"
},
"typenameshort" : "Delete",
"comments": "Please delete all of my son's personal data.",
"statuslabelclass": "label-success",
"statuslabel": "Complete",
"timecreated" : 1517902435
},
{
"id": 4,
"foruser" : {
"fullname": "Florian Krause",
"profileurl": "#"
},
"typenameshort" : "Delete",
"comments": "I would like to request for my personal data to be deleted from your site. Thanks!",
"statuslabelclass": "label-danger",
"statuslabel": "Rejected",
"timecreated" : 1517902435
},
{
"id": 5,
"foruser" : {
"fullname": "Nicklas Sørensen",
"profileurl": "#"
},
"typenameshort" : "Export",
"comments": "Please let me download my data",
"statuslabelclass": "label-info",
"statuslabel": "Processing",
"timecreated" : 1517902435
}
]
}
}}
<div data-region="datarequests">
<div class="m-t-1 m-b-1">
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
{{#str}}newrequest, tool_dataprivacy{{/str}}
</a>
</div>
<table class="generaltable fullwidth">
<thead>
<tr>
<th scope="col">{{#str}}requesttype, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}user, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}daterequested, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}requeststatus, tool_dataprivacy{{/str}}</th>
<th scope="col" colspan="2">{{#str}}message, tool_dataprivacy{{/str}}</th>
</tr>
</thead>
<tbody>
{{#requests}}
<tr {{!
}} data-region="request-node"{{!
}} data-id="{{id}}"{{!
}} data-type="{{type}}"{{!
}} data-status="{{status}}"{{!
}}>
<td>{{typenameshort}}</td>
<td><a href="{{foruser.profileurl}}" title="{{#str}}viewprofile{{/str}}">{{foruser.fullname}}</a></td>
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedate {{/str}} {{/userdate}}</td>
<td>
<span class="label {{statuslabelclass}}">{{statuslabel}}</span>
</td>
<td>{{#shortentext}}60, {{comments}}{{/shortentext}}</td>
<td>
{{#actions}}
{{> core/action_menu}}
{{/actions}}
</td>
</tr>
{{/requests}}
{{^requests}}
<tr>
<td class="text-muted" colspan="6">
{{#str}}nodatarequests, tool_dataprivacy{{/str}}
</td>
</tr>
{{/requests}}
</tbody>
</table>
</div>
{{#js}}
// Initialise the JS.
require(['tool_dataprivacy/requestactions'], function(ActionsMod) {
new ActionsMod();
});
{{/js}}
@@ -0,0 +1,41 @@
{{!
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/>.
}}
{{!
@template tool_dataprivacy/form-user-selector-suggestion
Moodle template for the list of valid options in an autocomplate form element.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* fullname string Users full name
* email string user email field
Example context (json):
{
"fullname": "Admin User",
"email": "[email protected]"
}
}}
<span>
<span>{{fullname}}</span>
<span><small>{{email}}</small></span>
</span>
@@ -0,0 +1,130 @@
{{!
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 comments.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_dataprivacy/my_data_requests
A user's data requests page.
Classes required for JS:
* requestactions
Data attributes required for JS:
* none
Context variables required for this template:
* requests - Array of data requests.
Example context (json):
{
"requests": [
{
"id": 1,
"typename" : "Data export",
"comments": "I would like to download all of my daughter's personal data",
"statuslabelclass": "label-default",
"statuslabel": "Pending",
"timecreated" : 1517902087
},
{
"id": 2,
"typename" : "Data export",
"comments": "Give me all of the information you have about me, or else...",
"statuslabelclass": "label-warning",
"statuslabel": "Awaiting completion",
"timecreated" : 1517902087
},
{
"id": 3,
"typename" : "Data deletion",
"comments": "Please delete all of my son's personal data.",
"statuslabelclass": "label-success",
"statuslabel": "Complete",
"timecreated" : 1517902087
},
{
"id": 4,
"typename" : "Data deletion",
"comments": "Delete my data or I'm coming for you...",
"statuslabelclass": "label-danger",
"statuslabel": "Rejected",
"timecreated" : 1517902087
},
{
"id": 5,
"typename" : "Data export",
"comments": "Please let me download my data",
"statuslabelclass": "label-info",
"statuslabel": "Processing",
"timecreated" : 1517902087
}
]
}
}}
<div data-region="datarequests">
<div class="m-t-1 m-b-1">
<a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
{{#str}}newrequest, tool_dataprivacy{{/str}}
</a>
</div>
<table class="generaltable fullwidth">
<thead>
<tr>
<th scope="col">{{#str}}requesttype, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}daterequested, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}requeststatus, tool_dataprivacy{{/str}}</th>
<th scope="col" colspan="2">{{#str}}message, tool_dataprivacy{{/str}}</th>
</tr>
</thead>
<tbody>
{{#requests}}
<tr {{!
}} data-region="request-node"{{!
}} data-id="{{id}}"{{!
}} data-type="{{type}}"{{!
}} data-status="{{status}}"{{!
}}>
<td>{{typename}}</td>
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedate {{/str}} {{/userdate}}</td>
<td>
<span class="label {{statuslabelclass}}">{{statuslabel}}</span>
</td>
<td>{{comments}}</td>
<td>
{{#actions}}
{{> core/action_menu}}
{{/actions}}
</td>
</tr>
{{/requests}}
{{^requests}}
<tr>
<td class="text-muted" colspan="5">
{{#str}}nopersonaldatarequests, tool_dataprivacy{{/str}}
</td>
</tr>
{{/requests}}
</tbody>
</table>
</div>
{{#js}}
// Initialise the JS.
require(['tool_dataprivacy/myrequestactions'], function(ActionsMod) {
ActionsMod.init();
});
{{/js}}
@@ -0,0 +1,106 @@
{{!
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/>.
}}
{{!
@template tool_dataprivacy/purposes
Manage purposes.
Classes required for JS:
Data attributes required for JS:
Context variables required for this template:
* purposes - array of objects
* actions - array of actions (already in HTML).
Example context (json):
{
"purposesexist": 1,
"purposes": [
{
"name" : "Purpose 1",
"description": "<strong>Purpose 1 description</strong>",
"retentionperiod": 86400,
"protected": 1,
"formattedretentionperiod": "1 day",
"actions": [
]
}, {
"name" : "Purpose 2",
"description": "<strong>Purpose 2 description</strong>",
"retentionperiod": 7200,
"protected": 0,
"formattedretentionperiod": "2 hours",
"actions": [
]
}
]
}
}}
{{#navigation}}
{{> core/action_link}}
{{/navigation}}
<div data-region="purposes" class="m-t-3 m-b-1">
<h3>{{#str}}purposes, tool_dataprivacy{{/str}}</h3>
<div class="m-y-1">
<button class="btn btn-secondary" data-add-element="purpose">
{{#pix}}t/add, moodle, {{#str}}addpurpose, tool_dataprivacy{{/str}}{{/pix}}
</button>
</div>
<table class="generaltable fullwidth">
<caption class="accesshide">{{#str}}purposeslist, tool_dataprivacy{{/str}}</caption>
<thead>
<tr>
<th scope="col">{{#str}}name{{/str}}</th>
<th scope="col">{{#str}}description{{/str}}</th>
<th scope="col">{{#str}}retentionperiod, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}protected, tool_dataprivacy{{/str}}</th>
<th scope="col">{{#str}}actions{{/str}}</th>
</tr>
</thead>
<tbody>
{{#purposes}}
<tr data-purposeid="{{id}}">
<td>{{{name}}}</td>
<td>{{{description}}}</td>
<td>{{formattedretentionperiod}}</td>
<td>
{{#protected}}
{{#pix}}i/checked, core, {{#str}}yes{{/str}}{{/pix}}
{{/protected}}
{{^protected}}
{{#str}}no{{/str}}
{{/protected}}
</td>
<td>
{{#actions}}
{{> core/action_menu}}
{{/actions}}
</td>
</tr>
{{/purposes}}
</tbody>
</table>
{{^purposes}}
<p>
{{#str}}nopurposes, tool_dataprivacy{{/str}}
</p>
{{/purposes}}
</div>
@@ -0,0 +1,85 @@
{{!
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 comments.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_dataprivacy/request_details
Data request details
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* foruser Object The context data provided by core_user/external/user_summary_exporter.
* reviewurl String The URL for the Review request link.
* timecreated Number The timestamp when the request was created.
* statuslabel String The text equivalent of the status.
* statuslabelclass String The class to be used for rendering the status text.
* messagehtml String The HTML version of the request message.
Example context (json):
{
"foruser" : {
"fullname": "Martha Smith",
"email": "[email protected]",
"profileurl": "#",
"profileimageurl": "https://randomuser.me/api/portraits/women/60.jpg"
},
"canreview": true,
"reviewurl": "#",
"timecreated": 1517561224,
"statuslabel": "Pending",
"statuslabelclass": "label-default",
"messagehtml": "<p>Hello,</p><p>I would like to download all of my personal data.</p><p>Thanks!</p>"
}
}}
<div class="container" data-requestid="{{id}}">
<div class="media">
<div class="media-left">
<img class="userpicture" src="{{foruser.profileimageurl}}"
alt="{{#str}}pictureof, moodle, {{foruser.fullname}}{{/str}}"
title="{{#str}}pictureof, moodle, {{foruser.fullname}}{{/str}}" />
</div>
<div class="media-body">
<h4 class="m-t-0 m-b-1">
<a href="{{foruser.profileurl}}" title="{{#str}}viewprofile{{/str}}">{{foruser.fullname}}</a>
</h4>
<a href="mailto:{{foruser.email}}">{{foruser.email}}</a>
<div class="clearfix m-t-1 m-b-1">
<span class="pull-left m-r-1">
<strong>{{#str}}daterequesteddetail, tool_dataprivacy{{/str}}</strong> {{#userdate}} {{timecreated}}, {{#str}} strftimedate {{/str}} {{/userdate}}
</span>
<span class="pull-left m-r-1">
<strong>{{#str}}statusdetail, tool_dataprivacy{{/str}}</strong>
<span class="label {{statuslabelclass}}">{{statuslabel}}</span>
</span>
</div>
{{#canreview}}
<a href="{{reviewurl}}" class="btn btn-default">{{#str}}reviewdata, tool_dataprivacy{{/str}}</a>
{{/canreview}}
</div>
</div>
<hr>
<div class="m-b-1">
<strong>{{#str}}messagelabel, tool_dataprivacy{{/str}}</strong>
</div>
<div>
{{{messagehtml}}}
</div>
</div>
File diff suppressed because it is too large Load Diff
@@ -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/>.
/**
* Expired contexts tests.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_dataprivacy\api;
use tool_dataprivacy\data_registry;
use tool_dataprivacy\expired_context;
defined('MOODLE_INTERNAL') || die();
global $CFG;
/**
* Expired contexts tests.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
/**
* setUp.
*/
public function setUp() {
$this->resetAfterTest();
$this->setAdminUser();
}
/**
* Test expired users flagging and deletion.
*
* @return null
*/
public function test_expired_users() {
global $DB;
$purpose = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H']);
$cat = api::create_category((object)['name' => 'a']);
$record = (object)[
'purposeid' => $purpose->get('id'),
'categoryid' => $cat->get('id'),
'contextlevel' => CONTEXT_SYSTEM,
];
api::set_contextlevel($record);
$record->contextlevel = CONTEXT_USER;
api::set_contextlevel($record);
$userdata = ['lastaccess' => '123'];
$user1 = $this->getDataGenerator()->create_user($userdata);
$user2 = $this->getDataGenerator()->create_user($userdata);
$user3 = $this->getDataGenerator()->create_user($userdata);
$user4 = $this->getDataGenerator()->create_user($userdata);
$user5 = $this->getDataGenerator()->create_user();
$course1 = $this->getDataGenerator()->create_course();
// Old course.
$course2 = $this->getDataGenerator()->create_course(['startdate' => '1', 'enddate' => '2']);
// Ongoing course.
$course3 = $this->getDataGenerator()->create_course(['startdate' => '1', 'enddate' => time()]);
$this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
$this->getDataGenerator()->enrol_user($user2->id, $course2->id, 'student');
$this->getDataGenerator()->enrol_user($user3->id, $course2->id, 'student');
$this->getDataGenerator()->enrol_user($user4->id, $course3->id, 'student');
// Users without lastaccess are skipped as well as users enroled in courses with no end date.
$expired = new \tool_dataprivacy\expired_user_contexts();
$numexpired = $expired->flag_expired();
$this->assertEquals(2, $numexpired);
$this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxexpired', ['status' => expired_context::STATUS_EXPIRED]));
// Approve user1 to be deleted.
$user2ctx = \context_user::instance($user2->id);
$expiredctx = expired_context::get_record(['contextid' => $user2ctx->id]);
api::set_expired_context_status($expiredctx, expired_context::STATUS_APPROVED);
$this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired', ['status' => expired_context::STATUS_APPROVED]));
// Delete expired contexts.
$deleted = $expired->delete();
$this->assertEquals(1, $deleted);
$this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired', ['status' => expired_context::STATUS_EXPIRED]));
$this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired', ['status' => expired_context::STATUS_CLEANED]));
// No new records are generated.
$numexpired = $expired->flag_expired();
$this->assertEquals(0, $numexpired);
$this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxexpired'));
$deleted = $expired->delete();
$this->assertEquals(0, $deleted);
}
/**
* Test expired course and course stuff flagging and deletion.
*
* @return null
*/
public function test_expired_course_related_contexts() {
global $DB;
$purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H']);
$purpose2 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'P1000Y']);
$cat = api::create_category((object)['name' => 'a']);
$record = (object)[
'purposeid' => $purpose1->get('id'),
'categoryid' => $cat->get('id'),
'contextlevel' => CONTEXT_SYSTEM,
];
api::set_contextlevel($record);
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
\context_helper::get_class_for_level(CONTEXT_COURSE)
);
set_config($purposevar, $purpose1->get('id'), 'tool_dataprivacy');
// A lot more time for modules
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
\context_helper::get_class_for_level(CONTEXT_MODULE)
);
set_config($purposevar, $purpose2->get('id'), 'tool_dataprivacy');
$course1 = $this->getDataGenerator()->create_course();
// Old course.
$course2 = $this->getDataGenerator()->create_course(['startdate' => '1', 'enddate' => '2']);
$forum1 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
$forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id));
// We want to override this last module instance purpose so we can test that modules are also
// returned as expired.
$forum2ctx = \context_module::instance($forum2->cmid);
$record = (object)[
'purposeid' => $purpose1->get('id'),
'categoryid' => $cat->get('id'),
'contextid' => $forum2ctx->id,
];
api::set_context_instance($record);
// Ongoing course.
$course3 = $this->getDataGenerator()->create_course(['startdate' => '1', 'enddate' => time()]);
$forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id));
$expired = new \tool_dataprivacy\expired_course_related_contexts();
$numexpired = $expired->flag_expired();
// Only 1 module has expired.
$this->assertEquals(1, $numexpired);
$this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired', ['status' => expired_context::STATUS_EXPIRED]));
// Add a forum1 override to 1h retention period so both forum1 and course2 are also expired.
$forum1ctx = \context_module::instance($forum1->cmid);
$record->purposeid = $purpose1->get('id');
$record->contextid = $forum1ctx->id;
api::set_context_instance($record);
$numexpired = $expired->flag_expired();
$this->assertEquals(2, $numexpired);
$this->assertEquals(3, $DB->count_records('tool_dataprivacy_ctxexpired', ['status' => expired_context::STATUS_EXPIRED]));
// Approve forum1 to be deleted.
$expiredctx = expired_context::get_record(['contextid' => $forum1ctx->id]);
api::set_expired_context_status($expiredctx, expired_context::STATUS_APPROVED);
// Delete expired contexts.
$deleted = $expired->delete();
$this->assertEquals(1, $deleted);
$this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired', ['status' => expired_context::STATUS_CLEANED]));
$expiredctx = expired_context::get_record(['contextid' => $forum2ctx->id]);
api::set_expired_context_status($expiredctx, expired_context::STATUS_APPROVED);
$course2ctx = \context_course::instance($course2->id);
$expiredctx = expired_context::get_record(['contextid' => $course2ctx->id]);
api::set_expired_context_status($expiredctx, expired_context::STATUS_APPROVED);
// Delete expired contexts.
$deleted = $expired->delete();
$this->assertEquals(2, $deleted);
$this->assertEquals(3, $DB->count_records('tool_dataprivacy_ctxexpired', ['status' => expired_context::STATUS_CLEANED]));
// No new records are generated.
$numexpired = $expired->flag_expired();
$this->assertEquals(0, $numexpired);
$this->assertEquals(3, $DB->count_records('tool_dataprivacy_ctxexpired'));
$deleted = $expired->delete();
$this->assertEquals(0, $deleted);
}
}
@@ -0,0 +1,285 @@
<?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/>.
/**
* External tests.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
use tool_dataprivacy\api;
use tool_dataprivacy\external;
/**
* External testcase.
*
* @package tool_dataprivacy
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_dataprivacy_external_testcase extends externallib_advanced_testcase {
/** @var stdClass The user making the request. */
protected $requester;
/** @var int The data request ID. */
protected $requestid;
/**
* Setup function- we will create a course and add an assign instance to it.
*/
protected function setUp() {
$this->resetAfterTest();
$generator = new testing_data_generator();
$requester = $generator->create_user();
$comment = 'sample comment';
// Login as user.
$this->setUser($requester->id);
// Test data request creation.
$datarequest = api::create_data_request($requester->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
$this->requestid = $datarequest->get('id');
$this->requester = $requester;
// Log out the user and set force login to true.
$this->setUser();
}
/**
* Test for external::approve_data_request() with the user not logged in.
*/
public function test_approve_data_request_not_logged_in() {
$this->expectException(require_login_exception::class);
external::approve_data_request($this->requestid);
}
/**
* Test for external::approve_data_request() with the user not having a DPO role.
*/
public function test_approve_data_request_not_dpo() {
// Login as the requester.
$this->setUser($this->requester->id);
$this->expectException(required_capability_exception::class);
external::approve_data_request($this->requestid);
}
/**
* Test for external::approve_data_request() for request that's not ready for approval
*/
public function test_approve_data_request_not_waiting_for_approval() {
// Admin as DPO. (The default when no one's assigned as a DPO in the site).
$this->setAdminUser();
$this->expectException(moodle_exception::class);
external::approve_data_request($this->requestid);
}
/**
* Test for external::approve_data_request()
*/
public function test_approve_data_request() {
// Admin as DPO. (The default when no one's assigned as a DPO in the site).
$this->setAdminUser();
api::update_request_status($this->requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
$result = external::approve_data_request($this->requestid);
$return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
$this->assertTrue($return->result);
$this->assertEmpty($return->warnings);
}
/**
* Test for external::approve_data_request() for a non-existent request ID.
*/
public function test_approve_data_request_non_existent() {
// Admin as DPO. (The default when no one's assigned as a DPO in the site).
$this->setAdminUser();
api::update_request_status($this->requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
$result = external::approve_data_request($this->requestid + 1);
$return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
$this->assertFalse($return->result);
$this->assertCount(1, $return->warnings);
$warning = reset($return->warnings);
$this->assertEquals('errorrequestnotfound', $warning['warningcode']);
}
/**
* Test for external::cancel_data_request() of another user.
*/
public function test_cancel_data_request_other_user() {
$generator = $this->getDataGenerator();
$otheruser = $generator->create_user();
// Login as another user.
$this->setUser($otheruser);
$result = external::cancel_data_request($this->requestid);
$return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
$this->assertFalse($return->result);
$this->assertCount(1, $return->warnings);
$warning = reset($return->warnings);
$this->assertEquals('errorrequestnotfound', $warning['warningcode']);
}
/**
* Test for external::cancel_data_request()
*/
public function test_cancel_data_request() {
// Login as the requester.
$this->setUser($this->requester);
$result = external::cancel_data_request($this->requestid);
$return = (object) external_api::clean_returnvalue(external::approve_data_request_returns(), $result);
$this->assertTrue($return->result);
$this->assertEmpty($return->warnings);
}
/**
* Test contact DPO.
*/
public function test_contact_dpo() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$this->setUser($user);
$message = 'Hello world!';
$result = external::contact_dpo($message);
$return = (object) external_api::clean_returnvalue(external::contact_dpo_returns(), $result);
$this->assertTrue($return->result);
$this->assertEmpty($return->warnings);
}
/**
* Test contact DPO with message containing invalid input.
*/
public function test_contact_dpo_with_nasty_input() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$this->setUser($user);
$this->expectException('invalid_parameter_exception');
external::contact_dpo('de<>\\..scription');
}
/**
* Test for external::deny_data_request() with the user not logged in.
*/
public function test_deny_data_request_not_logged_in() {
$this->expectException(require_login_exception::class);
external::deny_data_request($this->requestid);
}
/**
* Test for external::deny_data_request() with the user not having a DPO role.
*/
public function test_deny_data_request_not_dpo() {
// Login as the requester.
$this->setUser($this->requester->id);
$this->expectException(required_capability_exception::class);
external::deny_data_request($this->requestid);
}
/**
* Test for external::deny_data_request() for request that's not ready for approval
*/
public function test_deny_data_request_not_waiting_for_approval() {
// Admin as DPO. (The default when no one's assigned as a DPO in the site).
$this->setAdminUser();
$this->expectException(moodle_exception::class);
external::deny_data_request($this->requestid);
}
/**
* Test for external::deny_data_request()
*/
public function test_deny_data_request() {
// Admin as DPO. (The default when no one's assigned as a DPO in the site).
$this->setAdminUser();
api::update_request_status($this->requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
$result = external::approve_data_request($this->requestid);
$return = (object) external_api::clean_returnvalue(external::deny_data_request_returns(), $result);
$this->assertTrue($return->result);
$this->assertEmpty($return->warnings);
}
/**
* Test for external::deny_data_request() for a non-existent request ID.
*/
public function test_deny_data_request_non_existent() {
// Admin as DPO. (The default when no one's assigned as a DPO in the site).
$this->setAdminUser();
api::update_request_status($this->requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
$result = external::deny_data_request($this->requestid + 1);
$return = (object) external_api::clean_returnvalue(external::deny_data_request_returns(), $result);
$this->assertFalse($return->result);
$this->assertCount(1, $return->warnings);
$warning = reset($return->warnings);
$this->assertEquals('errorrequestnotfound', $warning['warningcode']);
}
/**
* Test for external::get_data_request() with the user not logged in.
*/
public function test_get_data_request_not_logged_in() {
$this->expectException(require_login_exception::class);
external::get_data_request($this->requestid);
}
/**
* Test for external::get_data_request() with the user not having a DPO role.
*/
public function test_get_data_request_not_dpo() {
$generator = $this->getDataGenerator();
$otheruser = $generator->create_user();
// Login as the requester.
$this->setUser($otheruser);
$this->expectException(required_capability_exception::class);
external::get_data_request($this->requestid);
}
/**
* Test for external::get_data_request()
*/
public function test_get_data_request() {
// Admin as DPO. (The default when no one's assigned as a DPO in the site).
$this->setAdminUser();
$result = external::get_data_request($this->requestid);
$return = (object) external_api::clean_returnvalue(external::get_data_request_returns(), $result);
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $return->result['type']);
$this->assertEquals('sample comment', $return->result['comments']);
$this->assertEquals($this->requester->id, $return->result['userid']);
$this->assertEquals($this->requester->id, $return->result['requestedby']);
$this->assertEmpty($return->warnings);
}
/**
* Test for external::get_data_request() for a non-existent request ID.
*/
public function test_get_data_request_non_existent() {
// Admin as DPO. (The default when no one's assigned as a DPO in the site).
$this->setAdminUser();
$this->expectException(dml_missing_record_exception::class);
external::get_data_request($this->requestid + 1);
}
}

Some files were not shown because too many files have changed in this diff Show More