mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 21:49:15 +01:00
MDL-67529 GDPR: Option to filter which courses are included
This commit is contained in:
parent
3cd84747cb
commit
65be92f661
@ -1,3 +1,3 @@
|
||||
define("tool_dataprivacy/data_request_modal",["exports","jquery","core/custom_interaction_events","core/modal","./events"],(function(_exports,_jquery,CustomEvents,_modal,_events){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),CustomEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CustomEvents),_modal=_interopRequireDefault(_modal),_events=_interopRequireDefault(_events);const SELECTORS_APPROVE_BUTTON='[data-action="approve"]',SELECTORS_DENY_BUTTON='[data-action="deny"]',SELECTORS_COMPLETE_BUTTON='[data-action="complete"]';class ModalDataRequest extends _modal.default{registerEventListeners(){super.registerEventListeners(this),this.getModal().on(CustomEvents.events.activate,SELECTORS_APPROVE_BUTTON,((e,data)=>{const approveEvent=_jquery.default.Event(_events.default.approve);this.getRoot().trigger(approveEvent,this),approveEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())})),this.getModal().on(CustomEvents.events.activate,SELECTORS_DENY_BUTTON,((e,data)=>{const denyEvent=_jquery.default.Event(_events.default.deny);this.getRoot().trigger(denyEvent,this),denyEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())})),this.getModal().on(CustomEvents.events.activate,SELECTORS_COMPLETE_BUTTON,((e,data)=>{const completeEvent=_jquery.default.Event(_events.default.complete);this.getRoot().trigger(completeEvent,this),completeEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())}))}}return _exports.default=ModalDataRequest,_defineProperty(ModalDataRequest,"TYPE","tool_dataprivacy-data_request"),_defineProperty(ModalDataRequest,"TEMPLATE","tool_dataprivacy/data_request_modal"),ModalDataRequest.registerModalType(),_exports.default}));
|
||||
define("tool_dataprivacy/data_request_modal",["exports","jquery","core/custom_interaction_events","core/modal","./events"],(function(_exports,_jquery,CustomEvents,_modal,_events){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),CustomEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CustomEvents),_modal=_interopRequireDefault(_modal),_events=_interopRequireDefault(_events);const SELECTORS_APPROVE_BUTTON='[data-action="approve"]',SELECTORS_DENY_BUTTON='[data-action="deny"]',SELECTORS_COMPLETE_BUTTON='[data-action="complete"]',SELECTORS_APPROVE_REQUEST_SELECT_COURSE='[data-action="approve-selected-courses"]';class ModalDataRequest extends _modal.default{registerEventListeners(){super.registerEventListeners(this),this.getModal().on(CustomEvents.events.activate,SELECTORS_APPROVE_BUTTON,((e,data)=>{const approveEvent=_jquery.default.Event(_events.default.approve);this.getRoot().trigger(approveEvent,this),approveEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())})),this.getModal().on(CustomEvents.events.activate,SELECTORS_DENY_BUTTON,((e,data)=>{const denyEvent=_jquery.default.Event(_events.default.deny);this.getRoot().trigger(denyEvent,this),denyEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())})),this.getModal().on(CustomEvents.events.activate,SELECTORS_COMPLETE_BUTTON,((e,data)=>{const completeEvent=_jquery.default.Event(_events.default.complete);this.getRoot().trigger(completeEvent,this),completeEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())})),this.getModal().on(CustomEvents.events.activate,SELECTORS_APPROVE_REQUEST_SELECT_COURSE,((e,data)=>{let approveSelectCoursesEvent=_jquery.default.Event(_events.default.approveSelectCourses);this.getRoot().trigger(approveSelectCoursesEvent,this),approveSelectCoursesEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())}))}}return _exports.default=ModalDataRequest,_defineProperty(ModalDataRequest,"TYPE","tool_dataprivacy-data_request"),_defineProperty(ModalDataRequest,"TEMPLATE","tool_dataprivacy/data_request_modal"),ModalDataRequest.registerModalType(),_exports.default}));
|
||||
|
||||
//# sourceMappingURL=data_request_modal.min.js.map
|
@ -1 +1 @@
|
||||
{"version":3,"file":"data_request_modal.min.js","sources":["../src/data_request_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Request actions.\n *\n * @module tool_dataprivacy/data_request_modal\n * @copyright 2018 Jun Pataleta\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as CustomEvents from 'core/custom_interaction_events';\nimport Modal from 'core/modal';\nimport DataPrivacyEvents from './events';\n\nconst SELECTORS = {\n APPROVE_BUTTON: '[data-action=\"approve\"]',\n DENY_BUTTON: '[data-action=\"deny\"]',\n COMPLETE_BUTTON: '[data-action=\"complete\"]',\n};\n\nexport default class ModalDataRequest extends Modal {\n static TYPE = 'tool_dataprivacy-data_request';\n static TEMPLATE = 'tool_dataprivacy/data_request_modal';\n\n /**\n * Set up all of the event handling for the modal.\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners(this);\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.APPROVE_BUTTON, (e, data) => {\n const approveEvent = $.Event(DataPrivacyEvents.approve);\n this.getRoot().trigger(approveEvent, this);\n\n if (!approveEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.DENY_BUTTON, (e, data) => {\n const denyEvent = $.Event(DataPrivacyEvents.deny);\n this.getRoot().trigger(denyEvent, this);\n\n if (!denyEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.COMPLETE_BUTTON, (e, data) => {\n const completeEvent = $.Event(DataPrivacyEvents.complete);\n this.getRoot().trigger(completeEvent, this);\n\n if (!completeEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n }\n}\n\nModalDataRequest.registerModalType();\n"],"names":["SELECTORS","ModalDataRequest","Modal","registerEventListeners","this","getModal","on","CustomEvents","events","activate","e","data","approveEvent","$","Event","DataPrivacyEvents","approve","getRoot","trigger","isDefaultPrevented","hide","originalEvent","preventDefault","denyEvent","deny","completeEvent","complete","registerModalType"],"mappings":"gjDA4BMA,yBACc,0BADdA,sBAEW,uBAFXA,0BAGe,iCAGAC,yBAAyBC,eAO1CC,+BAEUA,uBAAuBC,WAExBC,WAAWC,GAAGC,aAAaC,OAAOC,SAAUT,0BAA0B,CAACU,EAAGC,cACrEC,aAAeC,gBAAEC,MAAMC,gBAAkBC,cAC1CC,UAAUC,QAAQN,aAAcR,MAEhCQ,aAAaO,4BACTC,OACLT,KAAKU,cAAcC,0BAItBjB,WAAWC,GAAGC,aAAaC,OAAOC,SAAUT,uBAAuB,CAACU,EAAGC,cAClEY,UAAYV,gBAAEC,MAAMC,gBAAkBS,WACvCP,UAAUC,QAAQK,UAAWnB,MAE7BmB,UAAUJ,4BACNC,OACLT,KAAKU,cAAcC,0BAItBjB,WAAWC,GAAGC,aAAaC,OAAOC,SAAUT,2BAA2B,CAACU,EAAGC,cACtEc,cAAgBZ,gBAAEC,MAAMC,gBAAkBW,eAC3CT,UAAUC,QAAQO,cAAerB,MAEjCqB,cAAcN,4BACVC,OACLT,KAAKU,cAAcC,+EArCdrB,wBACH,iDADGA,4BAEC,uCAyCtBA,iBAAiB0B"}
|
||||
{"version":3,"file":"data_request_modal.min.js","sources":["../src/data_request_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Request actions.\n *\n * @module tool_dataprivacy/data_request_modal\n * @copyright 2018 Jun Pataleta\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as CustomEvents from 'core/custom_interaction_events';\nimport Modal from 'core/modal';\nimport DataPrivacyEvents from './events';\n\nconst SELECTORS = {\n APPROVE_BUTTON: '[data-action=\"approve\"]',\n DENY_BUTTON: '[data-action=\"deny\"]',\n COMPLETE_BUTTON: '[data-action=\"complete\"]',\n APPROVE_REQUEST_SELECT_COURSE: '[data-action=\"approve-selected-courses\"]',\n};\n\nexport default class ModalDataRequest extends Modal {\n static TYPE = 'tool_dataprivacy-data_request';\n static TEMPLATE = 'tool_dataprivacy/data_request_modal';\n\n /**\n * Set up all of the event handling for the modal.\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners(this);\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.APPROVE_BUTTON, (e, data) => {\n const approveEvent = $.Event(DataPrivacyEvents.approve);\n this.getRoot().trigger(approveEvent, this);\n\n if (!approveEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.DENY_BUTTON, (e, data) => {\n const denyEvent = $.Event(DataPrivacyEvents.deny);\n this.getRoot().trigger(denyEvent, this);\n\n if (!denyEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.COMPLETE_BUTTON, (e, data) => {\n const completeEvent = $.Event(DataPrivacyEvents.complete);\n this.getRoot().trigger(completeEvent, this);\n\n if (!completeEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.APPROVE_REQUEST_SELECT_COURSE, (e, data) => {\n let approveSelectCoursesEvent = $.Event(DataPrivacyEvents.approveSelectCourses);\n this.getRoot().trigger(approveSelectCoursesEvent, this);\n\n if (!approveSelectCoursesEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n }\n}\n\nModalDataRequest.registerModalType();\n"],"names":["SELECTORS","ModalDataRequest","Modal","registerEventListeners","this","getModal","on","CustomEvents","events","activate","e","data","approveEvent","$","Event","DataPrivacyEvents","approve","getRoot","trigger","isDefaultPrevented","hide","originalEvent","preventDefault","denyEvent","deny","completeEvent","complete","approveSelectCoursesEvent","approveSelectCourses","registerModalType"],"mappings":"gjDA4BMA,yBACc,0BADdA,sBAEW,uBAFXA,0BAGe,2BAHfA,wCAI6B,iDAGdC,yBAAyBC,eAO1CC,+BAEUA,uBAAuBC,WAExBC,WAAWC,GAAGC,aAAaC,OAAOC,SAAUT,0BAA0B,CAACU,EAAGC,cACrEC,aAAeC,gBAAEC,MAAMC,gBAAkBC,cAC1CC,UAAUC,QAAQN,aAAcR,MAEhCQ,aAAaO,4BACTC,OACLT,KAAKU,cAAcC,0BAItBjB,WAAWC,GAAGC,aAAaC,OAAOC,SAAUT,uBAAuB,CAACU,EAAGC,cAClEY,UAAYV,gBAAEC,MAAMC,gBAAkBS,WACvCP,UAAUC,QAAQK,UAAWnB,MAE7BmB,UAAUJ,4BACNC,OACLT,KAAKU,cAAcC,0BAItBjB,WAAWC,GAAGC,aAAaC,OAAOC,SAAUT,2BAA2B,CAACU,EAAGC,cACtEc,cAAgBZ,gBAAEC,MAAMC,gBAAkBW,eAC3CT,UAAUC,QAAQO,cAAerB,MAEjCqB,cAAcN,4BACVC,OACLT,KAAKU,cAAcC,0BAItBjB,WAAWC,GAAGC,aAAaC,OAAOC,SAAUT,yCAAyC,CAACU,EAAGC,YACtFgB,0BAA4Bd,gBAAEC,MAAMC,gBAAkBa,2BACrDX,UAAUC,QAAQS,0BAA2BvB,MAE7CuB,0BAA0BR,4BACtBC,OACLT,KAAKU,cAAcC,+EA/CdrB,wBACH,iDADGA,4BAEC,uCAoDtBA,iBAAiB4B"}
|
@ -1,3 +1,3 @@
|
||||
define("tool_dataprivacy/events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={approve:"tool_dataprivacy-data_request:approve",bulkApprove:"tool_dataprivacy-data_request:bulk_approve",deny:"tool_dataprivacy-data_request:deny",bulkDeny:"tool_dataprivacy-data_request:bulk_deny",complete:"tool_dataprivacy-data_request:complete"},_exports.default}));
|
||||
define("tool_dataprivacy/events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={approve:"tool_dataprivacy-data_request:approve",bulkApprove:"tool_dataprivacy-data_request:bulk_approve",deny:"tool_dataprivacy-data_request:deny",bulkDeny:"tool_dataprivacy-data_request:bulk_deny",complete:"tool_dataprivacy-data_request:complete",approveSelectCourses:"tool_dataprivacy-data_request:approve-selected-courses"},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=events.min.js.map
|
@ -1 +1 @@
|
||||
{"version":3,"file":"events.min.js","sources":["../src/events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Contain the events the data privacy tool can fire.\n *\n * @module tool_dataprivacy/events\n * @copyright 2018 Jun Pataleta\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n approve: 'tool_dataprivacy-data_request:approve',\n bulkApprove: 'tool_dataprivacy-data_request:bulk_approve',\n deny: 'tool_dataprivacy-data_request:deny',\n bulkDeny: 'tool_dataprivacy-data_request:bulk_deny',\n complete: 'tool_dataprivacy-data_request:complete',\n};\n"],"names":["approve","bulkApprove","deny","bulkDeny","complete"],"mappings":"yKAsBe,CACXA,QAAS,wCACTC,YAAa,6CACbC,KAAM,qCACNC,SAAU,0CACVC,SAAU"}
|
||||
{"version":3,"file":"events.min.js","sources":["../src/events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Contain the events the data privacy tool can fire.\n *\n * @module tool_dataprivacy/events\n * @copyright 2018 Jun Pataleta\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n approve: 'tool_dataprivacy-data_request:approve',\n bulkApprove: 'tool_dataprivacy-data_request:bulk_approve',\n deny: 'tool_dataprivacy-data_request:deny',\n bulkDeny: 'tool_dataprivacy-data_request:bulk_deny',\n complete: 'tool_dataprivacy-data_request:complete',\n approveSelectCourses: 'tool_dataprivacy-data_request:approve-selected-courses'\n};\n"],"names":["approve","bulkApprove","deny","bulkDeny","complete","approveSelectCourses"],"mappings":"yKAsBe,CACXA,QAAS,wCACTC,YAAa,6CACbC,KAAM,qCACNC,SAAU,0CACVC,SAAU,yCACVC,qBAAsB"}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
admin/tool/dataprivacy/amd/build/selectedcourses.min.js
vendored
Normal file
3
admin/tool/dataprivacy/amd/build/selectedcourses.min.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
define("tool_dataprivacy/selectedcourses",["exports","core/ajax","core/notification","core/modal_factory","core/modal_events","core/fragment","core/prefetch","core/str"],(function(_exports,_ajax,_notification,_modal_factory,_modal_events,_fragment,_prefetch,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_fragment=_interopRequireDefault(_fragment),(0,_prefetch.prefetchStrings)("tool_dataprivacy",["selectedcourses","approverequest","errornoselectedcourse"]);return _exports.default=class{constructor(contextId,requestId){_defineProperty(this,"contextId",0),_defineProperty(this,"requestId",0),_defineProperty(this,"strings",null),_defineProperty(this,"getBody",(formdata=>{let params=null;return void 0!==formdata&&(params={jsonformdata:JSON.stringify(formdata)}),_fragment.default.loadFragment("tool_dataprivacy","selectcourses_form",this.contextId,params)})),_defineProperty(this,"submitForm",(e=>{e.preventDefault(),this.modal.getRoot().find("form").submit()})),_defineProperty(this,"submitFormAjax",(e=>{e.preventDefault();let formData=this.modal.getRoot().find("form").serialize();if(-1===formData.indexOf("coursecontextids")){const customSelect=this.modal.getRoot().find(".custom-select"),invalidText=this.modal.getRoot().find(".invalid-feedback");return customSelect.addClass("is-invalid"),invalidText.attr("style","display: block"),void(0,_str.get_string)("errornoselectedcourse","tool_dataprivacy").then((value=>{invalidText.empty().append(value)})).catch(_notification.default.exception)}_ajax.default.call([{methodname:"tool_dataprivacy_submit_selected_courses_form",args:{requestid:this.requestId,jsonformdata:JSON.stringify(formData)},done:data=>{data.warnings.length>0?this.modal.setBody(this.getBody(formData)):(this.modal.destroy(),document.location.reload())},fail:_notification.default.exception}])})),this.contextId=contextId,this.requestId=requestId,_modal_factory.default.create({type:_modal_factory.default.types.SAVE_CANCEL,title:(0,_str.get_string)("selectcourses","tool_dataprivacy"),body:this.getBody({requestid:requestId}),large:!0,removeOnClose:!0,buttons:{save:(0,_str.get_string)("approverequest","tool_dataprivacy")}}).then((modal=>(this.modal=modal,modal))).then((modal=>(modal.getRoot().on(_modal_events.default.save,this.submitForm.bind(this)),modal.getRoot().on("submit","form",this.submitFormAjax.bind(this)),modal.show(),modal))).catch(_notification.default.exception)}},_exports.default}));
|
||||
|
||||
//# sourceMappingURL=selectedcourses.min.js.map
|
File diff suppressed because one or more lines are too long
@ -30,6 +30,7 @@ const SELECTORS = {
|
||||
APPROVE_BUTTON: '[data-action="approve"]',
|
||||
DENY_BUTTON: '[data-action="deny"]',
|
||||
COMPLETE_BUTTON: '[data-action="complete"]',
|
||||
APPROVE_REQUEST_SELECT_COURSE: '[data-action="approve-selected-courses"]',
|
||||
};
|
||||
|
||||
export default class ModalDataRequest extends Modal {
|
||||
@ -72,6 +73,17 @@ export default class ModalDataRequest extends Modal {
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
this.getModal().on(CustomEvents.events.activate, SELECTORS.APPROVE_REQUEST_SELECT_COURSE, (e, data) => {
|
||||
let approveSelectCoursesEvent = $.Event(DataPrivacyEvents.approveSelectCourses);
|
||||
this.getRoot().trigger(approveSelectCoursesEvent, this);
|
||||
|
||||
if (!approveSelectCoursesEvent.isDefaultPrevented()) {
|
||||
this.hide();
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,4 +26,5 @@ export default {
|
||||
deny: 'tool_dataprivacy-data_request:deny',
|
||||
bulkDeny: 'tool_dataprivacy-data_request:bulk_deny',
|
||||
complete: 'tool_dataprivacy-data_request:complete',
|
||||
approveSelectCourses: 'tool_dataprivacy-data_request:approve-selected-courses'
|
||||
};
|
||||
|
@ -29,8 +29,9 @@ define([
|
||||
'core/modal_events',
|
||||
'core/templates',
|
||||
'tool_dataprivacy/data_request_modal',
|
||||
'tool_dataprivacy/events'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, ModalDataRequest, DataPrivacyEvents) {
|
||||
'tool_dataprivacy/events',
|
||||
'tool_dataprivacy/selectedcourses'],
|
||||
function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, ModalDataRequest, DataPrivacyEvents, SelectedCourses) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
@ -50,7 +51,8 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
MARK_COMPLETE: '[data-action="complete"]',
|
||||
CHANGE_BULK_ACTION: '[id="bulk-action"]',
|
||||
CONFIRM_BULK_ACTION: '[id="confirm-bulk-action"]',
|
||||
SELECT_ALL: '[data-action="selectall"]'
|
||||
SELECT_ALL: '[data-action="selectall"]',
|
||||
APPROVE_REQUEST_SELECT_COURSE: '[data-action="approve-selected-courses"]',
|
||||
};
|
||||
|
||||
/**
|
||||
@ -88,6 +90,7 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
e.preventDefault();
|
||||
|
||||
var requestId = $(this).data('requestid');
|
||||
var contextId = $(this).data('contextid');
|
||||
|
||||
// Cancel the request.
|
||||
var params = {
|
||||
@ -115,7 +118,8 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
var body = Templates.render('tool_dataprivacy/request_details', data);
|
||||
var templateContext = {
|
||||
approvedeny: data.approvedeny,
|
||||
canmarkcomplete: data.canmarkcomplete
|
||||
canmarkcomplete: data.canmarkcomplete,
|
||||
allowfiltering: data.allowfiltering
|
||||
};
|
||||
return ModalFactory.create({
|
||||
title: data.typename,
|
||||
@ -150,6 +154,10 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
modal.getRoot().on(DataPrivacyEvents.approveSelectCourses, function() {
|
||||
new SelectedCourses(contextId, requestId);
|
||||
});
|
||||
|
||||
// Show the modal!
|
||||
modal.show();
|
||||
|
||||
@ -158,6 +166,15 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
|
||||
$(ACTIONS.APPROVE_REQUEST_SELECT_COURSE).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var requestId = $(this).data('requestid');
|
||||
var contextId = $(this).data('contextid');
|
||||
|
||||
new SelectedCourses(contextId, requestId);
|
||||
});
|
||||
|
||||
$(ACTIONS.APPROVE_REQUEST).click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
168
admin/tool/dataprivacy/amd/src/selectedcourses.js
Normal file
168
admin/tool/dataprivacy/amd/src/selectedcourses.js
Normal file
@ -0,0 +1,168 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Selected courses.
|
||||
*
|
||||
* @module tool_dataprivacy/selectedcourses
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 4.3
|
||||
*/
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
import Notification from 'core/notification';
|
||||
import ModalFactory from 'core/modal_factory';
|
||||
import ModalEvents from 'core/modal_events';
|
||||
import Fragment from 'core/fragment';
|
||||
import {prefetchStrings} from 'core/prefetch';
|
||||
import {get_string as getString} from 'core/str';
|
||||
|
||||
prefetchStrings('tool_dataprivacy', [
|
||||
'selectedcourses',
|
||||
'approverequest',
|
||||
'errornoselectedcourse',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Selected Courses popup modal.
|
||||
*
|
||||
*/
|
||||
export default class SelectedCourses {
|
||||
/**
|
||||
* @var {String} contextId Context ID to load the fragment.
|
||||
* @private
|
||||
*/
|
||||
contextId = 0;
|
||||
|
||||
/**
|
||||
* @var {String} requestId ID of data export request.
|
||||
* @private
|
||||
*/
|
||||
requestId = 0;
|
||||
|
||||
/**
|
||||
* @var {Promise}
|
||||
* @private
|
||||
*/
|
||||
strings = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param {String} contextId Context ID to load the fragment.
|
||||
* @param {String} requestId ID of data export request.
|
||||
*/
|
||||
constructor(contextId, requestId) {
|
||||
|
||||
this.contextId = contextId;
|
||||
this.requestId = requestId;
|
||||
// Now create the modal.
|
||||
ModalFactory.create({
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
title: getString('selectcourses', 'tool_dataprivacy'),
|
||||
body: this.getBody({requestid: requestId}),
|
||||
large: true,
|
||||
removeOnClose: true,
|
||||
buttons: {
|
||||
save: getString('approverequest', 'tool_dataprivacy'),
|
||||
},
|
||||
}).then(modal => {
|
||||
this.modal = modal;
|
||||
|
||||
return modal;
|
||||
}).then(modal => {
|
||||
// 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));
|
||||
modal.show();
|
||||
return modal;
|
||||
}).catch(Notification.exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get body of modal.
|
||||
*
|
||||
* @method getBody
|
||||
* @param {Object} formdata
|
||||
* @private
|
||||
* @return {Promise}
|
||||
*/
|
||||
getBody = (formdata) => {
|
||||
|
||||
let params = null;
|
||||
if (typeof formdata !== "undefined") {
|
||||
params = {jsonformdata: JSON.stringify(formdata)};
|
||||
}
|
||||
// Get the content of the modal.
|
||||
return Fragment.loadFragment('tool_dataprivacy', 'selectcourses_form', this.contextId, params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
submitForm = (e) => {
|
||||
e.preventDefault();
|
||||
this.modal.getRoot().find('form').submit();
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit select courses form using ajax.
|
||||
*
|
||||
* @method submitFormAjax
|
||||
* @private
|
||||
* @param {Event} e Form submission event.
|
||||
*/
|
||||
submitFormAjax = (e) => {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Convert all the form elements values to a serialised string.
|
||||
let formData = this.modal.getRoot().find('form').serialize();
|
||||
|
||||
if (formData.indexOf('coursecontextids') === -1) {
|
||||
const customSelect = this.modal.getRoot().find('.custom-select');
|
||||
const invalidText = this.modal.getRoot().find('.invalid-feedback');
|
||||
customSelect.addClass('is-invalid');
|
||||
invalidText.attr('style', 'display: block');
|
||||
getString('errornoselectedcourse', 'tool_dataprivacy').then(value => {
|
||||
invalidText.empty().append(value);
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
return;
|
||||
}
|
||||
|
||||
Ajax.call([{
|
||||
methodname: 'tool_dataprivacy_submit_selected_courses_form',
|
||||
args: {requestid: this.requestId, jsonformdata: JSON.stringify(formData)},
|
||||
done: (data) => {
|
||||
if (data.warnings.length > 0) {
|
||||
this.modal.setBody(this.getBody(formData));
|
||||
} else {
|
||||
this.modal.destroy();
|
||||
document.location.reload();
|
||||
}
|
||||
},
|
||||
fail: Notification.exception
|
||||
}]);
|
||||
};
|
||||
}
|
@ -39,8 +39,8 @@ use required_capability_exception;
|
||||
use stdClass;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
use tool_dataprivacy\local\helper;
|
||||
use tool_dataprivacy\task\initiate_data_request_task;
|
||||
use tool_dataprivacy\task\process_data_request_task;
|
||||
use tool_dataprivacy\data_request;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@ -64,6 +64,9 @@ class api {
|
||||
/** 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;
|
||||
|
||||
@ -259,18 +262,24 @@ class api {
|
||||
// The user making the request.
|
||||
$datarequest->set('requestedby', $requestinguser);
|
||||
// Set status.
|
||||
$status = self::DATAREQUEST_STATUS_AWAITING_APPROVAL;
|
||||
if (self::is_automatic_request_approval_on($type)) {
|
||||
// Set status to approved if automatic data request approval is enabled.
|
||||
$status = self::DATAREQUEST_STATUS_APPROVED;
|
||||
// Set the privacy officer field if the one making the data request is a privacy officer.
|
||||
if (self::is_site_dpo($requestinguser)) {
|
||||
$datarequest->set('dpo', $requestinguser);
|
||||
|
||||
$allowfiltering = get_config('tool_dataprivacy', 'allowfiltering') && ($type != self::DATAREQUEST_TYPE_DELETE);
|
||||
if ($allowfiltering) {
|
||||
$status = self::DATAREQUEST_STATUS_PENDING;
|
||||
} else {
|
||||
$status = self::DATAREQUEST_STATUS_AWAITING_APPROVAL;
|
||||
if (self::is_automatic_request_approval_on($type)) {
|
||||
// Set status to approved if automatic data request approval is enabled.
|
||||
$status = self::DATAREQUEST_STATUS_APPROVED;
|
||||
// Set the privacy officer field if the one making the data request is a privacy officer.
|
||||
if (self::is_site_dpo($requestinguser)) {
|
||||
$datarequest->set('dpo', $requestinguser);
|
||||
}
|
||||
// Mark this request as system approved.
|
||||
$datarequest->set('systemapproved', true);
|
||||
// No need to notify privacy officer(s) about automatically approved data requests.
|
||||
$notify = false;
|
||||
}
|
||||
// Mark this request as system approved.
|
||||
$datarequest->set('systemapproved', true);
|
||||
// No need to notify privacy officer(s) about automatically approved data requests.
|
||||
$notify = false;
|
||||
}
|
||||
$datarequest->set('status', $status);
|
||||
// Set request type.
|
||||
@ -302,6 +311,13 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
if ($status == self::DATAREQUEST_STATUS_PENDING) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -615,6 +631,7 @@ class api {
|
||||
* Approves a data request based on the request ID.
|
||||
*
|
||||
* @param int $requestid The request identifier
|
||||
* @param array $filtercoursecontexts Apply to export request, only approve contexts belong to these courses.
|
||||
* @return bool
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
@ -622,7 +639,7 @@ class api {
|
||||
* @throws required_capability_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function approve_data_request($requestid) {
|
||||
public static function approve_data_request($requestid, $filtercoursecontexts = []) {
|
||||
global $USER;
|
||||
|
||||
// Check first whether the user can manage data requests.
|
||||
@ -646,6 +663,18 @@ class api {
|
||||
// Update the status and the DPO.
|
||||
$result = self::update_request_status($requestid, self::DATAREQUEST_STATUS_APPROVED, $USER->id);
|
||||
|
||||
if ($request->get('type') != self::DATAREQUEST_TYPE_DELETE) {
|
||||
$allowfiltering = get_config('tool_dataprivacy', 'allowfiltering');
|
||||
if ($allowfiltering) {
|
||||
if ($filtercoursecontexts) {
|
||||
// Only approve the context belong to selected courses.
|
||||
self::approve_contexts_belonging_to_request($requestid, $filtercoursecontexts);
|
||||
} else {
|
||||
// Approve all the contexts attached to the request.
|
||||
self::update_request_contexts_with_status($requestid, contextlist_context::STATUS_APPROVED);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fire an ad hoc task to initiate the data request process.
|
||||
$userid = null;
|
||||
if ($request->get('type') == self::DATAREQUEST_TYPE_EXPORT) {
|
||||
@ -1342,4 +1371,264 @@ class api {
|
||||
}
|
||||
manager::queue_adhoc_task($task, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the contexts from the contextlist_collection to the request with the status provided.
|
||||
*
|
||||
* @since Moodle 4.0
|
||||
* @param contextlist_collection $clcollection a collection of contextlists for all components.
|
||||
* @param int $requestid the id of the request.
|
||||
* @param int $status the status to set the contexts to.
|
||||
* return void
|
||||
*/
|
||||
public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
|
||||
global $DB;
|
||||
|
||||
// Wrap the SQL queries in a transaction.
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
|
||||
foreach ($clcollection as $contextlist) {
|
||||
// Convert the \core_privacy\local\request\contextlist into a dataprivacy_contextlist persistent and store it.
|
||||
$clp = \tool_dataprivacy\dataprivacy_contextlist::from_contextlist($contextlist);
|
||||
$clp->create();
|
||||
$contextlistid = $clp->get('id');
|
||||
|
||||
// Store the associated contexts in the contextlist.
|
||||
foreach ($contextlist->get_contextids() as $contextid) {
|
||||
mtrace('Pushing data for ' . \context::instance_by_id($contextid)->get_context_name());
|
||||
$context = new contextlist_context();
|
||||
$context->set('contextid', $contextid)
|
||||
->set('contextlistid', $contextlistid)
|
||||
->set('status', $status)
|
||||
->create();
|
||||
}
|
||||
|
||||
// Create the relation to the request.
|
||||
$requestcontextlist = request_contextlist::create_relation($requestid, $contextlistid);
|
||||
$requestcontextlist->create();
|
||||
}
|
||||
|
||||
$transaction->allow_commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all request contextlists having at least on approved context, and returns them as in a contextlist_collection.
|
||||
*
|
||||
* @since Moodle 3.11
|
||||
* @param data_request $request the data request with which the contextlists are associated.
|
||||
* @return contextlist_collection the collection of approved_contextlist objects.
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function get_approved_contextlist_collection_for_request(data_request $request): contextlist_collection {
|
||||
global $DB;
|
||||
$foruser = core_user::get_user($request->get('userid'));
|
||||
|
||||
// Fetch all approved contextlists and create the core_privacy\local\request\contextlist objects here.
|
||||
$sql = "SELECT cl.component, ctx.contextid
|
||||
FROM {" . request_contextlist::TABLE . "} rcl
|
||||
JOIN {" . dataprivacy_contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id
|
||||
JOIN {" . contextlist_context::TABLE . "} ctx ON cl.id = ctx.contextlistid
|
||||
WHERE rcl.requestid = ? AND ctx.status = ?
|
||||
ORDER BY cl.component, ctx.contextid";
|
||||
|
||||
// Create the approved contextlist collection object.
|
||||
$lastcomponent = null;
|
||||
$approvedcollection = new contextlist_collection($foruser->id);
|
||||
|
||||
$rs = $DB->get_recordset_sql($sql, [$request->get('id'), contextlist_context::STATUS_APPROVED]);
|
||||
$contexts = [];
|
||||
foreach ($rs as $record) {
|
||||
// If we encounter a new component, and we've built up contexts for the last, then add the approved_contextlist for the
|
||||
// last (the one we've just finished with) and reset the context array for the next one.
|
||||
if ($lastcomponent != $record->component) {
|
||||
if ($contexts) {
|
||||
$approvedcollection->add_contextlist(new approved_contextlist($foruser, $lastcomponent, $contexts));
|
||||
}
|
||||
$contexts = [];
|
||||
}
|
||||
$contexts[] = $record->contextid;
|
||||
$lastcomponent = $record->component;
|
||||
}
|
||||
$rs->close();
|
||||
|
||||
// The data for the last component contextlist won't have been written yet, so write it now.
|
||||
if ($contexts) {
|
||||
$approvedcollection->add_contextlist(new approved_contextlist($foruser, $lastcomponent, $contexts));
|
||||
}
|
||||
|
||||
return $approvedcollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status of all contexts associated with the request.
|
||||
*
|
||||
* @since Moodle 3.11
|
||||
* @param int $requestid the requestid to which the contexts belong.
|
||||
* @param int $status the status to set to.
|
||||
* @throws \dml_exception if the requestid is invalid.
|
||||
* @throws \coding_exception if the status is invalid.
|
||||
*/
|
||||
public static function update_request_contexts_with_status(int $requestid, int $status) {
|
||||
// Validate contextlist_context status using the persistent's attribute validation.
|
||||
$contextlistcontext = new contextlist_context();
|
||||
$contextlistcontext->set('status', $status);
|
||||
if (array_key_exists('status', $contextlistcontext->get_errors())) {
|
||||
throw new coding_exception("Invalid contextlist_context status: $status");
|
||||
}
|
||||
|
||||
global $DB;
|
||||
$select = "SELECT ctx.id as id
|
||||
FROM {" . request_contextlist::TABLE . "} rcl
|
||||
JOIN {" . dataprivacy_contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id
|
||||
JOIN {" . contextlist_context::TABLE . "} ctx ON cl.id = ctx.contextlistid
|
||||
WHERE rcl.requestid = ?";
|
||||
|
||||
// Fetch records IDs to be updated and update by chunks, if applicable (limit of 1000 records per update).
|
||||
$limit = 1000;
|
||||
$idstoupdate = $DB->get_fieldset_sql($select, [$requestid]);
|
||||
$count = count($idstoupdate);
|
||||
$idchunks = $idstoupdate;
|
||||
if ($count > $limit) {
|
||||
$idchunks = array_chunk($idstoupdate, $limit);
|
||||
} else {
|
||||
$idchunks = [$idchunks];
|
||||
}
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$initialparams = [$status];
|
||||
foreach ($idchunks as $chunk) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($chunk);
|
||||
$update = "UPDATE {" . contextlist_context::TABLE . "}
|
||||
SET status = ?
|
||||
WHERE id $insql";
|
||||
$params = array_merge($initialparams, $inparams);
|
||||
$DB->execute($update, $params);
|
||||
}
|
||||
$transaction->allow_commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only approve the contexts which are children of the provided course contexts.
|
||||
*
|
||||
* @since Moodle 3.11
|
||||
* @param int $requestid Request identifier
|
||||
* @param array $coursecontextids List of course context identifier.
|
||||
* @throws \dml_transaction_exception
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public static function approve_contexts_belonging_to_request(int $requestid, array $coursecontextids = []) {
|
||||
global $DB;
|
||||
$select = "SELECT clc.id as id, ctx.id as contextid, ctx.path, ctx.contextlevel
|
||||
FROM {" . request_contextlist::TABLE . "} rcl
|
||||
JOIN {" . dataprivacy_contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id
|
||||
JOIN {" . contextlist_context::TABLE . "} clc ON cl.id = clc.contextlistid
|
||||
JOIN {context} ctx ON clc.contextid = ctx.id
|
||||
WHERE rcl.requestid = ?";
|
||||
$items = $DB->get_records_sql($select, [$requestid]);
|
||||
$acceptcourses = [];
|
||||
$listidstoapprove = [];
|
||||
$listidstoreject = [];
|
||||
foreach ($items as $item) {
|
||||
if (in_array($item->contextid, $coursecontextids) && ($item->contextlevel == CONTEXT_COURSE)
|
||||
&& !in_array($item->contextid, $acceptcourses)) {
|
||||
$acceptcourses[$item->contextid] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($item->contextlevel >= CONTEXT_COURSE) {
|
||||
$approve = false;
|
||||
foreach ($acceptcourses as $acceptcourse) {
|
||||
if (strpos($item->path, $acceptcourse->path) === 0) {
|
||||
$approve = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($approve) {
|
||||
$listidstoapprove[] = $item->id;
|
||||
} else {
|
||||
$listidstoreject[] = $item->id;
|
||||
}
|
||||
} else {
|
||||
$listidstoapprove[] = $item->id;
|
||||
}
|
||||
}
|
||||
|
||||
$limit = 1000;
|
||||
$count = count($listidstoapprove);
|
||||
if ($count > $limit) {
|
||||
$listidstoapprove = array_chunk($listidstoapprove, $limit);
|
||||
} else {
|
||||
$listidstoapprove = [$listidstoapprove];
|
||||
}
|
||||
$count = count($listidstoreject);
|
||||
if ($count > $limit) {
|
||||
$listidstoreject = array_chunk($listidstoreject, $limit);
|
||||
} else {
|
||||
$listidstoreject = [$listidstoreject];
|
||||
}
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
|
||||
$initialparams = [contextlist_context::STATUS_APPROVED];
|
||||
foreach ($listidstoapprove as $chunk) {
|
||||
if (!empty($chunk)) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($chunk);
|
||||
$update = "UPDATE {" . contextlist_context::TABLE . "}
|
||||
SET status = ?
|
||||
WHERE id $insql";
|
||||
$params = array_merge($initialparams, $inparams);
|
||||
$DB->execute($update, $params);
|
||||
}
|
||||
}
|
||||
|
||||
$initialparams = [contextlist_context::STATUS_REJECTED];
|
||||
foreach ($listidstoreject as $chunk) {
|
||||
if (!empty($chunk)) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($chunk);
|
||||
$update = "UPDATE {" . contextlist_context::TABLE . "}
|
||||
SET status = ?
|
||||
WHERE id $insql";
|
||||
|
||||
$params = array_merge($initialparams, $inparams);
|
||||
$DB->execute($update, $params);
|
||||
}
|
||||
}
|
||||
|
||||
$transaction->allow_commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of course context for user to filter.
|
||||
*
|
||||
* @since Moodle 3.11
|
||||
* @param int $requestid Request identifier.
|
||||
* @return array
|
||||
* @throws dml_exception
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public static function get_course_contexts_for_view_filter(int $requestid): array {
|
||||
global $DB;
|
||||
|
||||
$contexts = [];
|
||||
|
||||
$query = "SELECT DISTINCT c.id as ctxid, c.contextlevel as ctxlevel, c.instanceid as ctxinstance, c.path as ctxpath,
|
||||
c.depth as ctxdepth, c.locked as ctxlocked
|
||||
FROM {" . \tool_dataprivacy\request_contextlist::TABLE . "} rcl
|
||||
JOIN {" . \tool_dataprivacy\dataprivacy_contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id
|
||||
JOIN {" . \tool_dataprivacy\contextlist_context::TABLE . "} ctx ON cl.id = ctx.contextlistid
|
||||
JOIN {context} c ON c.id = ctx.contextid
|
||||
WHERE rcl.requestid = ? AND c.contextlevel = ?
|
||||
ORDER BY c.path ASC";
|
||||
|
||||
$result = $DB->get_records_sql($query, [$requestid, CONTEXT_COURSE]);
|
||||
foreach ($result as $item) {
|
||||
$ctxid = $item->ctxid;
|
||||
context_helper::preload_from_record($item);
|
||||
$contexts[$ctxid] = \context::instance_by_id($ctxid);
|
||||
}
|
||||
|
||||
return $contexts;
|
||||
}
|
||||
}
|
||||
|
68
admin/tool/dataprivacy/classes/contextlist_context.php
Normal file
68
admin/tool/dataprivacy/classes/contextlist_context.php
Normal file
@ -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/>.
|
||||
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use core\persistent;
|
||||
|
||||
/**
|
||||
* The contextlist_context persistent.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
class contextlist_context extends persistent {
|
||||
|
||||
/** The table name this persistent object maps to. */
|
||||
const TABLE = 'tool_dataprivacy_ctxlst_ctx';
|
||||
|
||||
/** This context is pending approval. */
|
||||
const STATUS_PENDING = 0;
|
||||
|
||||
/** This context has been approved. */
|
||||
const STATUS_APPROVED = 1;
|
||||
|
||||
/** This context has been rejected. */
|
||||
const STATUS_REJECTED = 2;
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
protected static function define_properties(): array {
|
||||
return [
|
||||
'contextid' => [
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'contextlistid' => [
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'status' => [
|
||||
'choices' => [
|
||||
self::STATUS_PENDING,
|
||||
self::STATUS_APPROVED,
|
||||
self::STATUS_REJECTED,
|
||||
],
|
||||
'default' => self::STATUS_PENDING,
|
||||
'type' => PARAM_INT
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
@ -91,6 +91,7 @@ class data_request extends persistent {
|
||||
'default' => api::DATAREQUEST_STATUS_AWAITING_APPROVAL,
|
||||
'choices' => [
|
||||
api::DATAREQUEST_STATUS_PENDING,
|
||||
api::DATAREQUEST_STATUS_PREPROCESSING,
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL,
|
||||
api::DATAREQUEST_STATUS_APPROVED,
|
||||
api::DATAREQUEST_STATUS_PROCESSING,
|
||||
|
59
admin/tool/dataprivacy/classes/dataprivacy_contextlist.php
Normal file
59
admin/tool/dataprivacy/classes/dataprivacy_contextlist.php
Normal 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/>.
|
||||
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use core\persistent;
|
||||
|
||||
/**
|
||||
* The dataprivacy_contextlist persistent.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
class dataprivacy_contextlist extends persistent {
|
||||
|
||||
/** The table name this persistent object maps to. */
|
||||
const TABLE = 'tool_dataprivacy_contextlist';
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
protected static function define_properties(): array {
|
||||
return [
|
||||
'component' => [
|
||||
'type' => PARAM_TEXT
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new contextlist persistent from an instance of \core_privacy\local\request\contextlist.
|
||||
*
|
||||
* @param \core_privacy\local\request\contextlist $contextlist the core privacy contextlist.
|
||||
* @return dataprivacy_contextlist a dataprivacy_contextlist persistent.
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
public static function from_contextlist(\core_privacy\local\request\contextlist $contextlist): dataprivacy_contextlist {
|
||||
$contextlistpersistent = new dataprivacy_contextlist();
|
||||
return $contextlistpersistent->set('component', $contextlist->get_component());
|
||||
}
|
||||
}
|
@ -358,6 +358,7 @@ class external extends external_api {
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parameter description for approve_data_request().
|
||||
*
|
||||
|
@ -104,6 +104,11 @@ class data_request_exporter extends persistent_exporter {
|
||||
'optional' => true,
|
||||
'default' => false
|
||||
],
|
||||
'allowfiltering' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
'default' => false
|
||||
],
|
||||
'canmarkcomplete' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'optional' => true,
|
||||
@ -153,10 +158,12 @@ class data_request_exporter extends persistent_exporter {
|
||||
|
||||
$values['canreview'] = false;
|
||||
$values['approvedeny'] = false;
|
||||
$values['allowfiltering'] = get_config('tool_dataprivacy', 'allowfiltering');
|
||||
$values['statuslabel'] = helper::get_request_status_string($this->persistent->get('status'));
|
||||
|
||||
switch ($this->persistent->get('status')) {
|
||||
case api::DATAREQUEST_STATUS_PENDING:
|
||||
case api::DATAREQUEST_STATUS_PREPROCESSING:
|
||||
$values['statuslabelclass'] = 'badge-info';
|
||||
// Request can be manually completed for general enquiry requests.
|
||||
$values['canmarkcomplete'] = $requesttype == api::DATAREQUEST_TYPE_OTHERS;
|
||||
|
136
admin/tool/dataprivacy/classes/external/submit_selected_courses_form.php
vendored
Normal file
136
admin/tool/dataprivacy/classes/external/submit_selected_courses_form.php
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
<?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 submit selected courses from form.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2021 The Open University.
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace tool_dataprivacy\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/externallib.php');
|
||||
|
||||
use context_system;
|
||||
use external_api;
|
||||
use external_function_parameters;
|
||||
use external_single_structure;
|
||||
use external_value;
|
||||
use external_warnings;
|
||||
use core\notification;
|
||||
|
||||
/**
|
||||
* Class for submit selected courses from form.
|
||||
*
|
||||
* @copyright 2021 The Open University.
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* since Moodle 4.0
|
||||
*/
|
||||
class submit_selected_courses_form extends external_api {
|
||||
/**
|
||||
* Parameter description for get_data_request().
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'requestid' => new external_value(PARAM_INT, 'The id of data request'),
|
||||
'jsonformdata' => new external_value(PARAM_RAW, 'The data of selected courses form, encoded as a json array')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the list of course which user can select to export data.
|
||||
*
|
||||
* @param int $requestid The request ID.
|
||||
* @param string $jsonformdata The data of selected courses form.
|
||||
* @return array
|
||||
*/
|
||||
public static function execute(int $requestid, string $jsonformdata): array {
|
||||
|
||||
$warnings = [];
|
||||
$result = false;
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'requestid' => $requestid,
|
||||
'jsonformdata' => $jsonformdata
|
||||
]);
|
||||
|
||||
$context = context_system::instance();
|
||||
self::validate_context($context);
|
||||
|
||||
// Make sure the user has the proper capability.
|
||||
require_capability('tool/dataprivacy:managedatarequests', $context);
|
||||
|
||||
$requestid = $params['requestid'];
|
||||
$serialiseddata = json_decode($params['jsonformdata']);
|
||||
$data = array();
|
||||
parse_str($serialiseddata, $data);
|
||||
|
||||
$mform = new \tool_dataprivacy\form\exportfilter_form(null, ['requestid' => $requestid], 'post', '', null, true, $data);
|
||||
|
||||
if (PHPUNIT_TEST) {
|
||||
$validateddata = $mform->mock_ajax_submit($data);
|
||||
} else {
|
||||
$validateddata = $mform->get_data();
|
||||
}
|
||||
if ($validateddata) {
|
||||
// Ensure the request exists.
|
||||
$requestexists = \tool_dataprivacy\data_request::record_exists($requestid);
|
||||
|
||||
if ($requestexists) {
|
||||
$coursecontextids = [];
|
||||
if (!empty($validateddata->coursecontextids)) {
|
||||
$coursecontextids = $validateddata->coursecontextids;
|
||||
}
|
||||
if (PHPUNIT_TEST) {
|
||||
if (!empty($validateddata['coursecontextids'])) {
|
||||
$coursecontextids = $validateddata['coursecontextids'];
|
||||
}
|
||||
}
|
||||
$result = \tool_dataprivacy\api::approve_data_request($requestid, $coursecontextids);
|
||||
|
||||
// Add notification in the session to be shown when the page is reloaded on the JS side.
|
||||
notification::success(get_string('requestapproved', 'tool_dataprivacy'));
|
||||
} else {
|
||||
$warnings = [
|
||||
'item' => $requestid,
|
||||
'warningcode' => 'errorrequestnotfound',
|
||||
'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
|
||||
];
|
||||
}
|
||||
}
|
||||
return [
|
||||
'result' => $result,
|
||||
'warnings' => $warnings
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameter description for submit_selected_courses_form().
|
||||
*
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'result' => new external_value(PARAM_BOOL, 'The processing result'),
|
||||
'warnings' => new external_warnings()
|
||||
]);
|
||||
}
|
||||
}
|
106
admin/tool/dataprivacy/classes/form/exportfilter_form.php
Normal file
106
admin/tool/dataprivacy/classes/form/exportfilter_form.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Form to filter export data.
|
||||
*
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package tool_dataprivacy
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/formslib.php');
|
||||
|
||||
/**
|
||||
* Form to filter export data.
|
||||
*
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package tool_dataprivacy
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
class exportfilter_form extends \moodleform {
|
||||
/**
|
||||
* Form definition.
|
||||
*
|
||||
* @return void
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
public function definition(): void {
|
||||
$requestid = $this->_customdata['requestid'];
|
||||
$mform = $this->_form;
|
||||
$selectitems = [];
|
||||
|
||||
$mform->addElement('hidden', 'requestid', $requestid);
|
||||
$mform->setType('requestid', PARAM_INT);
|
||||
$contexts = \tool_dataprivacy\api::get_course_contexts_for_view_filter($requestid);
|
||||
foreach ($contexts as $context) {
|
||||
$coursename = '';
|
||||
$groupname = '';
|
||||
$parentcontexts = $context->get_parent_contexts(true);
|
||||
$parentcontexts = array_reverse($parentcontexts);
|
||||
end($parentcontexts);
|
||||
$lastkey = key($parentcontexts);
|
||||
reset($parentcontexts);
|
||||
$firstkey = key($parentcontexts);
|
||||
|
||||
foreach ($parentcontexts as $key => $parentcontext) {
|
||||
if ($key !== $lastkey) {
|
||||
if ($key !== $firstkey) {
|
||||
$groupname .= ' > ';
|
||||
}
|
||||
$groupname .= $parentcontext->get_context_name(false);
|
||||
} else {
|
||||
$coursename = $parentcontext->get_context_name(false);
|
||||
}
|
||||
}
|
||||
|
||||
$selectitems[$groupname][$context->id] = $coursename;
|
||||
}
|
||||
|
||||
if ($contexts) {
|
||||
$mform->addElement('selectgroups', 'coursecontextids', get_string('selectcourses', 'tool_dataprivacy'),
|
||||
$selectitems);
|
||||
$mform->getElement('coursecontextids')->setMultiple(true);
|
||||
$mform->getElement('coursecontextids')->setSize(15);
|
||||
} else {
|
||||
$mform->addElement('html', get_string('nocoursetofilter', 'tool_dataprivacy'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form validation.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @return array
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
$errors = [];
|
||||
|
||||
if (empty($data['coursecontextids'])) {
|
||||
$errors['coursecontextids'] = get_string('errornoselectedcourse', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
@ -136,6 +136,7 @@ class helper {
|
||||
public static function get_request_statuses() {
|
||||
return [
|
||||
api::DATAREQUEST_STATUS_PENDING => get_string('statuspending', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_PREPROCESSING => get_string('statuspreprocessing', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL => get_string('statusawaitingapproval', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_APPROVED => get_string('statusapproved', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_PROCESSING => get_string('statusprocessing', 'tool_dataprivacy'),
|
||||
|
@ -35,6 +35,7 @@ use moodle_url;
|
||||
use stdClass;
|
||||
use table_sql;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\external\data_request_exporter;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
@ -224,7 +225,8 @@ class data_requests_table extends table_sql {
|
||||
|
||||
// View action.
|
||||
$actionurl = new moodle_url('#');
|
||||
$actiondata = ['data-action' => 'view', 'data-requestid' => $requestid];
|
||||
$actiondata = ['data-action' => 'view', 'data-requestid' => $requestid,
|
||||
'data-contextid' => \context_system::instance()->id];
|
||||
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
@ -251,8 +253,20 @@ class data_requests_table extends table_sql {
|
||||
}
|
||||
// Approve.
|
||||
$actiondata['data-action'] = 'approve';
|
||||
$actiontext = get_string('approverequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
if (get_config('tool_dataprivacy', 'allowfiltering') && $data->type == api::DATAREQUEST_TYPE_EXPORT) {
|
||||
$actiontext = get_string('approverequestall', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
|
||||
// Approve selected courses.
|
||||
$actiontext = get_string('filterexportdata', 'tool_dataprivacy');
|
||||
$actiondata = ['data-action' => 'approve-selected-courses', 'data-requestid' => $requestid,
|
||||
'data-contextid' => \context_system::instance()->id];
|
||||
$actions[] = new \action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
} else {
|
||||
$actiontext = get_string('approverequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
}
|
||||
|
||||
// Deny.
|
||||
$actiondata['data-action'] = 'deny';
|
||||
|
63
admin/tool/dataprivacy/classes/request_contextlist.php
Normal file
63
admin/tool/dataprivacy/classes/request_contextlist.php
Normal 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/>.
|
||||
|
||||
namespace tool_dataprivacy;
|
||||
|
||||
use core\persistent;
|
||||
|
||||
/**
|
||||
* The request_contextlist persistent.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
class request_contextlist extends persistent {
|
||||
|
||||
/** The table name this persistent object maps to. */
|
||||
const TABLE = 'tool_dataprivacy_rqst_ctxlst';
|
||||
|
||||
/**
|
||||
* Return the definition of the properties of this model.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function define_properties(): array {
|
||||
return [
|
||||
'requestid' => [
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'contextlistid' => [
|
||||
'type' => PARAM_INT
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new relation, but does not persist it.
|
||||
*
|
||||
* @param int $requestid ID of data request.
|
||||
* @param int $contextlistid ID of context list.
|
||||
* @return $this
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
public static function create_relation(int $requestid, int $contextlistid): request_contextlist {
|
||||
$requestcontextlist = new request_contextlist();
|
||||
return $requestcontextlist->set('requestid', $requestid)
|
||||
->set('contextlistid', $contextlistid);
|
||||
}
|
||||
}
|
@ -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/>.
|
||||
|
||||
namespace tool_dataprivacy\task;
|
||||
|
||||
use coding_exception;
|
||||
use core\task\adhoc_task;
|
||||
use moodle_exception;
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\contextlist_context;
|
||||
use tool_dataprivacy\data_request;
|
||||
|
||||
/**
|
||||
* Class that processes a data request and prepares the user's relevant contexts for review.
|
||||
*
|
||||
* Custom data accepted:
|
||||
* - requestid -> The ID of the data request to be processed.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
class initiate_data_request_task extends adhoc_task {
|
||||
|
||||
/**
|
||||
* Run the task to initiate the data request process.
|
||||
*
|
||||
* return void
|
||||
* @since Moodle 4.0
|
||||
*/
|
||||
public function execute():void {
|
||||
if (!isset($this->get_custom_data()->requestid)) {
|
||||
throw new coding_exception('The custom data \'requestid\' is required.');
|
||||
}
|
||||
$requestid = $this->get_custom_data()->requestid;
|
||||
|
||||
$datarequest = new data_request($requestid);
|
||||
|
||||
// Check if this request still needs to be processed. e.g. The user might have cancelled it before this task has run.
|
||||
$status = $datarequest->get('status');
|
||||
if (!api::is_active($status)) {
|
||||
mtrace('Request ' . $requestid . ' with status ' . $status . ' doesn\'t need to be processed. Skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the status of this request as pre-processing.
|
||||
mtrace('Generating the contexts containing personal data for the user...');
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PREPROCESSING);
|
||||
|
||||
// Add the list of relevant contexts to the request, and mark all as pending approval.
|
||||
$privacymanager = new \core_privacy\manager();
|
||||
$privacymanager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$contextlistcollection = $privacymanager->get_contexts_for_userid($datarequest->get('userid'));
|
||||
api::add_request_contexts_with_status($contextlistcollection, $requestid, contextlist_context::STATUS_PENDING);
|
||||
|
||||
// When the preparation of the contexts finishes, update the request status to awaiting approval.
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
|
||||
mtrace('Context generation complete...');
|
||||
|
||||
// Get the list of the site Data Protection Officers.
|
||||
$dpos = api::get_site_dpos();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -82,6 +82,7 @@ class process_data_request_task extends adhoc_task {
|
||||
|
||||
// Grab the manager.
|
||||
// We set an observer against it to handle failures.
|
||||
$allowfiltering = get_config('tool_dataprivacy', 'allowfiltering');
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
@ -94,8 +95,6 @@ class process_data_request_task extends adhoc_task {
|
||||
$contextlistcollection = $manager->get_contexts_for_userid($requestpersistent->get('userid'));
|
||||
|
||||
mtrace('Fetching approved contextlists from collection');
|
||||
$approvedclcollection = api::get_approved_contextlist_collection_for_collection(
|
||||
$contextlistcollection, $foruser, $request->type);
|
||||
|
||||
mtrace('Processing request...');
|
||||
$completestatus = api::DATAREQUEST_STATUS_COMPLETE;
|
||||
@ -103,6 +102,14 @@ class process_data_request_task extends adhoc_task {
|
||||
|
||||
if ($request->type == api::DATAREQUEST_TYPE_EXPORT) {
|
||||
// Get the user context.
|
||||
if ($allowfiltering) {
|
||||
// Get the collection of approved_contextlist objects needed for core_privacy data export.
|
||||
$approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
|
||||
} else {
|
||||
$approvedclcollection = api::get_approved_contextlist_collection_for_collection(
|
||||
$contextlistcollection, $foruser, $request->type);
|
||||
}
|
||||
|
||||
$usercontext = \context_user::instance($foruser->id, IGNORE_MISSING);
|
||||
if (!$usercontext) {
|
||||
mtrace("Request {$requestid} cannot be processed due to a missing user context instance for the user
|
||||
@ -132,6 +139,8 @@ class process_data_request_task extends adhoc_task {
|
||||
if (is_primary_admin($foruser->id)) {
|
||||
$completestatus = api::DATAREQUEST_STATUS_REJECTED;
|
||||
} else {
|
||||
$approvedclcollection = api::get_approved_contextlist_collection_for_collection(
|
||||
$contextlistcollection, $foruser, $request->type);
|
||||
$manager = new \core_privacy\manager();
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
|
@ -99,7 +99,12 @@ class tool_dataprivacy_data_request_form extends \core\form\persistent {
|
||||
// Subject access request type.
|
||||
$options = [];
|
||||
if ($this->manage || api::can_create_data_download_request_for_self()) {
|
||||
$options[api::DATAREQUEST_TYPE_EXPORT] = get_string('requesttypeexport', 'tool_dataprivacy');
|
||||
$allowfiltering = get_config('tool_dataprivacy', 'allowfiltering');
|
||||
if ($allowfiltering) {
|
||||
$options[api::DATAREQUEST_TYPE_EXPORT] = get_string('requesttypeexportallowfiltering', 'tool_dataprivacy');
|
||||
} else {
|
||||
$options[api::DATAREQUEST_TYPE_EXPORT] = get_string('requesttypeexport', 'tool_dataprivacy');
|
||||
}
|
||||
}
|
||||
$options[api::DATAREQUEST_TYPE_DELETE] = get_string('requesttypedelete', 'tool_dataprivacy');
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="admin/tool/dataprivacy/db" VERSION="20220530" COMMENT="XMLDB file for Moodle tool/dataprivacy"
|
||||
<XMLDB PATH="admin/tool/dataprivacy/db" VERSION="20230522" COMMENT="XMLDB file for Moodle tool/dataprivacy"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@ -136,5 +136,43 @@
|
||||
<INDEX NAME="purposerole" UNIQUE="true" FIELDS="purposeid, roleid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_contextlist" COMMENT="List of contexts for a component">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="component" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Frankenstyle component name"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_ctxlst_ctx" COMMENT="A contextlist context item">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="contextlistid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Approval status of the context item"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="contextlistid" TYPE="foreign" FIELDS="contextlistid" REFTABLE="tool_dataprivacy_contextlist" REFFIELDS="id" COMMENT="Reference to the contextlist containing this context item"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="tool_dataprivacy_rqst_ctxlst" COMMENT="Association table joining requests and contextlists">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="requestid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="contextlistid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="requestid" TYPE="foreign" FIELDS="requestid" REFTABLE="tool_dataprivacy_request" REFFIELDS="id" COMMENT="Reference to the request"/>
|
||||
<KEY NAME="contextlistid" TYPE="foreign" FIELDS="contextlistid" REFTABLE="tool_dataprivacy_contextlist" REFFIELDS="id" COMMENT="Reference to the contextlist"/>
|
||||
<KEY NAME="requestidcontextlistid" TYPE="unique" FIELDS="requestid, contextlistid" COMMENT="Uniqueness constraint on request and contextlist"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
|
@ -73,6 +73,15 @@ $functions = [
|
||||
'ajax' => true,
|
||||
'loginrequired' => true,
|
||||
],
|
||||
'tool_dataprivacy_submit_selected_courses_form' => [
|
||||
'classname' => 'tool_dataprivacy\external\submit_selected_courses_form',
|
||||
'classpath' => '',
|
||||
'description' => 'Save list of selected courses for export',
|
||||
'type' => 'write',
|
||||
'capabilities' => 'tool/dataprivacy:managedatarequests',
|
||||
'ajax' => 'true',
|
||||
'loginrequired' => 'true',
|
||||
],
|
||||
'tool_dataprivacy_bulk_approve_data_requests' => [
|
||||
'classname' => 'tool_dataprivacy\external',
|
||||
'methodname' => 'bulk_approve_data_requests',
|
||||
|
@ -90,5 +90,67 @@ function xmldb_tool_dataprivacy_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v4.2.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2023052200) {
|
||||
|
||||
// Define table tool_dataprivacy_contextlist to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_contextlist');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_contextlist.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('component', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
|
||||
// Adding keys to table tool_dataprivacy_contextlist.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_contextlist.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table tool_dataprivacy_ctxlst_ctx to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_ctxlst_ctx');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_ctxlst_ctx.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
|
||||
|
||||
// Adding keys to table tool_dataprivacy_ctxlst_ctx.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, ['contextlistid'], 'tool_dataprivacy_contextlist', ['id']);
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_ctxlst_ctx.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Define table tool_dataprivacy_rqst_ctxlst to be created.
|
||||
$table = new xmldb_table('tool_dataprivacy_rqst_ctxlst');
|
||||
|
||||
// Adding fields to table tool_dataprivacy_rqst_ctxlst.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('requestid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table tool_dataprivacy_rqst_ctxlst.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$table->add_key('requestid', XMLDB_KEY_FOREIGN, ['requestid'], 'tool_dataprivacy_request', ['id']);
|
||||
$table->add_key('contextlistid', XMLDB_KEY_FOREIGN, ['contextlistid'], 'tool_dataprivacy_contextlist', ['id']);
|
||||
$table->add_key('requestidcontextlistid', XMLDB_KEY_UNIQUE, ['requestid', 'contextlistid']);
|
||||
|
||||
// Conditionally launch create table for tool_dataprivacy_rqst_ctxlst.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Dataprivacy savepoint reached.
|
||||
upgrade_plugin_savepoint(true, 2023052200, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ $string['addpurpose'] = 'Add purpose';
|
||||
$string['approve'] = 'Approve';
|
||||
$string['approvedrequestsubmitted'] = 'Your request has been submitted and will be processed soon.';
|
||||
$string['approverequest'] = 'Approve request';
|
||||
$string['approverequestall'] = 'Approve request (all data)';
|
||||
$string['approveselectedcourses'] = 'Approve selected courses';
|
||||
$string['automaticdatadeletionapproval'] = 'Automatic data deletion request approval';
|
||||
$string['automaticdatadeletionapproval_desc'] = 'If enabled, data deletion requests are automatically approved.<br/>Note that the automatic approval will only apply to new data deletion requests with this setting enabled. Existing data deletion requests pending approval will still have to be manually approved by the privacy officer.';
|
||||
$string['automaticdataexportapproval'] = 'Automatic data export request approval';
|
||||
@ -142,6 +144,7 @@ $string['errorinvalidrequestcreationmethod'] = 'Invalid request creation method!
|
||||
$string['errorinvalidrequeststatus'] = 'Invalid request status!';
|
||||
$string['errorinvalidrequesttype'] = 'Invalid request type!';
|
||||
$string['errornocapabilitytorequestforothers'] = 'User {$a->requestedby} doesn\'t have the capability to make a data request on behalf of user {$a->userid}';
|
||||
$string['errornoselectedcourse'] = 'You must select at least one course';
|
||||
$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.';
|
||||
@ -160,6 +163,7 @@ $string['explanationtitle'] = 'Icons used on this page and what they mean.';
|
||||
$string['external'] = 'Additional';
|
||||
$string['externalexplanation'] = 'An additional plugin installed on this site.';
|
||||
$string['filteroption'] = '{$a->category}: {$a->name}';
|
||||
$string['filterexportdata'] = 'Approve request (data from selected courses)';
|
||||
$string['frontpagecourse'] = 'Site home course';
|
||||
$string['gdpr_art_6_1_a_description'] = 'The data subject has given consent to the processing of his or her personal data for one or more specific purposes';
|
||||
$string['gdpr_art_6_1_a_name'] = 'Consent (GDPR Art. 6.1(a))';
|
||||
@ -225,6 +229,7 @@ $string['nosubjectaccessrequests'] = 'There are no data requests that you need t
|
||||
$string['nosystemdefaults'] = 'Site purpose and category have not yet been defined.';
|
||||
$string['notset'] = 'Not set (use the default value)';
|
||||
$string['notyetexpired'] = '{$a} (not yet expired)';
|
||||
$string['nocoursetofilter'] = 'The user isn\'t enrolled in any courses to select';
|
||||
$string['overrideinstances'] = 'Reset instances with custom values';
|
||||
$string['pluginregistry'] = 'Plugin privacy registry';
|
||||
$string['pluginregistrytitle'] = 'Plugin privacy compliance registry';
|
||||
@ -275,6 +280,7 @@ $string['requesttype_help'] = 'Select the reason for contacting the privacy offi
|
||||
$string['requesttypedelete'] = 'Delete all of my personal data';
|
||||
$string['requesttypedeleteshort'] = 'Delete';
|
||||
$string['requesttypeexport'] = 'Export all of my personal data';
|
||||
$string['requesttypeexportallowfiltering'] = 'Export my personal data';
|
||||
$string['requesttypeexportshort'] = 'Export';
|
||||
$string['requesttypeothers'] = 'General enquiry';
|
||||
$string['requesttypeothersshort'] = 'Message';
|
||||
@ -324,9 +330,14 @@ $string['subjectscope'] = 'Subject scope';
|
||||
$string['subjectscope_help'] = 'The subject scope lists the roles which may be assigned in this context.';
|
||||
$string['summary'] = 'Registry configuration summary';
|
||||
$string['systemconfignotsetwarning'] = 'A site purpose and category have not been defined. When these are not defined, all data will be removed when processing deletion requests.';
|
||||
$string['selectcourses'] = 'Select courses to export';
|
||||
$string['statuspreprocessing'] = 'Pre-processing';
|
||||
$string['user'] = 'User';
|
||||
$string['userlistnoncompliant'] = 'Userlist provider missing';
|
||||
$string['userlistexplanation'] = 'This plugin has the base provider but should also implement the userlist provider for full support of privacy functionality.';
|
||||
$string['allowfiltering'] = 'Allow filtering of exports by course';
|
||||
$string['allowfiltering_desc'] = 'If enabled, the privacy officer can choose to export user data from selected courses instead of all courses. When this feature is enabled,
|
||||
export requests will start from \'Pending\' and can only be approved after a background task has completed.';
|
||||
$string['viewrequest'] = 'View the request';
|
||||
$string['visible'] = 'Expand all';
|
||||
$string['unexpiredrolewithretention'] = '{$a->retention} (Unexpired)';
|
||||
|
@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
use core_user\output\myprofile\tree;
|
||||
use tool_dataprivacy\form\exportfilter_form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@ -279,3 +280,24 @@ function tool_dataprivacy_pluginfile($course, $cm, $context, $filearea, $args, $
|
||||
send_file_not_found();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fragment to add a select course.
|
||||
*
|
||||
* @param array $args The fragment arguments.
|
||||
* @return string The rendered mform fragment.
|
||||
*/
|
||||
function tool_dataprivacy_output_fragment_selectcourses_form(array $args): string {
|
||||
$args = (object)$args;
|
||||
|
||||
$context = context_system::instance();
|
||||
require_capability('tool/dataprivacy:managedatarequests', $context);
|
||||
|
||||
if (!empty($args->jsonformdata)) {
|
||||
$serialiseddata = json_decode($args->jsonformdata);
|
||||
}
|
||||
|
||||
$mform = new exportfilter_form(null, ['requestid' => $serialiseddata->requestid]);
|
||||
|
||||
return $mform->render();
|
||||
}
|
||||
|
@ -90,6 +90,15 @@ if ($hassiteconfig) {
|
||||
new lang_string('showdataretentionsummary', 'tool_dataprivacy'),
|
||||
new lang_string('showdataretentionsummary_desc', 'tool_dataprivacy'),
|
||||
1));
|
||||
|
||||
// Whether to allow PO to select courses for data export, instead of always exporting all data.
|
||||
$privacysettings->add(new admin_setting_configcheckbox('tool_dataprivacy/allowfiltering',
|
||||
new lang_string('allowfiltering', 'tool_dataprivacy'),
|
||||
new lang_string('allowfiltering_desc', 'tool_dataprivacy'),
|
||||
0));
|
||||
// Prevent the case where the automaticdataexportapproval setting is set to automatically approve,
|
||||
// but the allowfiltering option is also enabled and non-functional.
|
||||
$privacysettings->hide_if('tool_dataprivacy/allowfiltering', 'tool_dataprivacy/automaticdataexportapproval', 'checked', 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,12 +30,18 @@
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"approvedeny": true,
|
||||
"allowfiltering": true,
|
||||
"canmarkcomplete": true,
|
||||
"title": "Data request modal title"
|
||||
}
|
||||
}}
|
||||
{{< core/modal }}
|
||||
{{$footer}}
|
||||
{{#approvedeny}}
|
||||
{{#allowfiltering}}
|
||||
<button type="button" class="btn btn-primary" data-action="approve-selected-courses">{{#str}} approveselectedcourses, tool_dataprivacy {{/str}}</button>
|
||||
{{/allowfiltering}}
|
||||
<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>
|
||||
{{/approvedeny}}
|
||||
|
@ -21,6 +21,7 @@ use core\task\manager;
|
||||
use testing_data_generator;
|
||||
use tool_dataprivacy\local\helper;
|
||||
use tool_dataprivacy\task\process_data_request_task;
|
||||
use tool_dataprivacy\task\initiate_data_request_task;
|
||||
|
||||
/**
|
||||
* API tests.
|
||||
@ -272,6 +273,55 @@ class api_test extends \advanced_testcase {
|
||||
$this->assertCount(1, $adhoctasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for api::approve_data_request() when allow filtering of exports by course.
|
||||
*/
|
||||
public function test_approve_data_request_with_allow_filtering() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
set_config('allowfiltering', 1, 'tool_dataprivacy');
|
||||
$this->setAdminUser();
|
||||
|
||||
$generator = new testing_data_generator();
|
||||
$s1 = $generator->create_user();
|
||||
$u1 = $generator->create_user();
|
||||
|
||||
$context = \context_system::instance();
|
||||
|
||||
// Manager role.
|
||||
$managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
|
||||
// Give the manager role with the capability to manage data requests.
|
||||
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
|
||||
// Assign u1 as a manager.
|
||||
role_assign($managerroleid, $u1->id, $context->id);
|
||||
|
||||
// Map the manager role to the DPO role.
|
||||
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
||||
|
||||
$course = $this->getDataGenerator()->create_course([]);
|
||||
|
||||
$coursecontext1 = \context_course::instance($course->id);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($s1->id, $course->id, 'student');
|
||||
|
||||
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
$requestid = $datarequest->get('id');
|
||||
ob_start();
|
||||
$this->runAdhocTasks('tool_dataprivacy\task\initiate_data_request_task');
|
||||
ob_end_clean();
|
||||
|
||||
$this->setUser($u1);
|
||||
$result = api::approve_data_request($requestid, [$coursecontext1]);
|
||||
$this->assertTrue($result);
|
||||
$datarequest = new data_request($requestid);
|
||||
$this->assertEquals($u1->id, $datarequest->get('dpo'));
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_APPROVED, $datarequest->get('status'));
|
||||
|
||||
// Test adhoc task creation.
|
||||
$adhoctasks = manager::get_adhoc_tasks(process_data_request_task::class);
|
||||
$this->assertCount(1, $adhoctasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for api::approve_data_request() when called by a user who doesn't have the DPO role.
|
||||
*/
|
||||
@ -294,6 +344,44 @@ class api_test extends \advanced_testcase {
|
||||
api::approve_data_request($requestid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for api::add_request_contexts_with_status().
|
||||
*/
|
||||
public function test_add_request_contexts_with_status() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
set_config('allowfiltering', 1, 'tool_dataprivacy');
|
||||
|
||||
$this->setAdminUser();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
||||
|
||||
// Create the initial contextlist.
|
||||
$initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
|
||||
|
||||
$contextlist = new \core_privacy\local\request\contextlist();
|
||||
$contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
|
||||
$contextlist->set_component('tool_dataprivacy');
|
||||
$initialcollection->add_contextlist($contextlist);
|
||||
|
||||
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
ob_start();
|
||||
api::add_request_contexts_with_status($initialcollection, $requestid, contextlist_context::STATUS_PENDING);
|
||||
ob_end_clean();
|
||||
|
||||
$result = $DB->get_record('tool_dataprivacy_ctxlst_ctx', ['contextid' => $coursecontext->id]);
|
||||
$this->assertEquals($result->status, contextlist_context::STATUS_PENDING);
|
||||
|
||||
$result1 = $DB->get_field('tool_dataprivacy_rqst_ctxlst', 'requestid', ['contextlistid' => $result->contextlistid]);
|
||||
$this->assertEquals($result1, $requestid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that deletion requests for the primary admin are rejected
|
||||
*/
|
||||
@ -549,35 +637,39 @@ class api_test extends \advanced_testcase {
|
||||
return [
|
||||
'Export request by user, automatic approval off' => [
|
||||
false, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', false, 0,
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, 0
|
||||
],
|
||||
'Export request by user, automatic approval on' => [
|
||||
false, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', true, 0,
|
||||
api::DATAREQUEST_STATUS_APPROVED, 1
|
||||
api::DATAREQUEST_STATUS_APPROVED, 1 , 0
|
||||
],
|
||||
'Export request by PO, automatic approval off' => [
|
||||
true, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', false, 0,
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, 0
|
||||
],
|
||||
'Export request by PO, automatic approval off, allow filtering of exports by course' => [
|
||||
true, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', false, 0,
|
||||
api::DATAREQUEST_STATUS_PENDING, 0, 1
|
||||
],
|
||||
'Export request by PO, automatic approval on' => [
|
||||
true, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', true, 'dpo',
|
||||
api::DATAREQUEST_STATUS_APPROVED, 1
|
||||
api::DATAREQUEST_STATUS_APPROVED, 1, 0
|
||||
],
|
||||
'Delete request by user, automatic approval off' => [
|
||||
false, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', false, 0,
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, 0
|
||||
],
|
||||
'Delete request by user, automatic approval on' => [
|
||||
false, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', true, 0,
|
||||
api::DATAREQUEST_STATUS_APPROVED, 1
|
||||
api::DATAREQUEST_STATUS_APPROVED, 1, 0
|
||||
],
|
||||
'Delete request by PO, automatic approval off' => [
|
||||
true, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', false, 0,
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
|
||||
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, 0
|
||||
],
|
||||
'Delete request by PO, automatic approval on' => [
|
||||
true, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', true, 'dpo',
|
||||
api::DATAREQUEST_STATUS_APPROVED, 1
|
||||
api::DATAREQUEST_STATUS_APPROVED, 1, 0
|
||||
],
|
||||
];
|
||||
}
|
||||
@ -595,11 +687,12 @@ class api_test extends \advanced_testcase {
|
||||
* someone else and automatic data request approval is turned on.
|
||||
* @param int $expectedstatus The expected status of the data request.
|
||||
* @param int $expectedtaskcount The number of expected queued data requests tasks.
|
||||
* @param bool $allowfiltering Whether allow filtering of exports by course turn on or off.
|
||||
* @throws coding_exception
|
||||
* @throws invalid_persistent_exception
|
||||
*/
|
||||
public function test_create_data_request($asprivacyofficer, $type, $setting, $automaticapproval, $expecteddpoval,
|
||||
$expectedstatus, $expectedtaskcount) {
|
||||
$expectedstatus, $expectedtaskcount, $allowfiltering) {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest();
|
||||
@ -622,6 +715,9 @@ class api_test extends \advanced_testcase {
|
||||
if ($expecteddpoval === 'dpo') {
|
||||
$expecteddpoval = $USER->id;
|
||||
}
|
||||
if ($allowfiltering) {
|
||||
set_config('allowfiltering', 1, 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
// Test data request creation.
|
||||
$datarequest = api::create_data_request($user->id, $type, $comment);
|
||||
@ -636,6 +732,12 @@ class api_test extends \advanced_testcase {
|
||||
// Test number of queued data request tasks.
|
||||
$datarequesttasks = manager::get_adhoc_tasks(process_data_request_task::class);
|
||||
$this->assertCount($expectedtaskcount, $datarequesttasks);
|
||||
|
||||
if ($allowfiltering) {
|
||||
// Test number of queued initiate data request tasks.
|
||||
$datarequesttasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
|
||||
$this->assertCount(1, $datarequesttasks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -839,6 +941,54 @@ class api_test extends \advanced_testcase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for api::get_approved_contextlist_collection_for_request.
|
||||
*/
|
||||
public function test_get_approved_contextlist_collection_for_request() {
|
||||
$this->resetAfterTest();
|
||||
set_config('allowfiltering', 1, 'tool_dataprivacy');
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course([]);
|
||||
|
||||
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
$record = new \stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $user->id;
|
||||
$record->forum = $forum->id;
|
||||
$generator->create_discussion($record);
|
||||
|
||||
$generator->create_discussion($record);
|
||||
|
||||
$coursecontext1 = \context_course::instance($course->id);
|
||||
|
||||
$forumcontext1 = \context_module::instance($forum->cmid);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
||||
|
||||
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
|
||||
ob_start();
|
||||
$this->runAdhocTasks('tool_dataprivacy\task\initiate_data_request_task');
|
||||
ob_end_clean();
|
||||
|
||||
api::approve_contexts_belonging_to_request($datarequest->get('id'), [$coursecontext1->id]);
|
||||
$contextlistcollection = api::get_approved_contextlist_collection_for_request($datarequest);
|
||||
$approvecontexts = [];
|
||||
foreach ($contextlistcollection->get_contextlists() as $contextlist) {
|
||||
foreach ($contextlist->get_contextids() as $contextid) {
|
||||
$approvecontexts[] = $contextid;
|
||||
}
|
||||
}
|
||||
$this->assertContains(strval($coursecontext1->id), $approvecontexts);
|
||||
$this->assertContains(strval($forumcontext1->id), $approvecontexts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_has_ongoing_request.
|
||||
*/
|
||||
@ -2397,4 +2547,158 @@ class api_test extends \advanced_testcase {
|
||||
|
||||
$this->assertEquals($expected, api::is_automatic_request_approval_on($type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test approve part of context list before export if filtering of exports by course is allowed.
|
||||
*/
|
||||
public function test_approve_contexts_belonging_to_request(): void {
|
||||
global $DB;
|
||||
set_config('allowfiltering', 1, 'tool_dataprivacy');
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course([]);
|
||||
$course2 = $this->getDataGenerator()->create_course([]);
|
||||
|
||||
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
||||
$forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course2->id]);
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
$record = new \stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $user->id;
|
||||
$record->forum = $forum->id;
|
||||
$generator->create_discussion($record);
|
||||
|
||||
$record->course = $course2->id;
|
||||
$record->forum = $forum2->id;
|
||||
$generator->create_discussion($record);
|
||||
|
||||
$coursecontext1 = \context_course::instance($course->id);
|
||||
$coursecontext2 = \context_course::instance($course2->id);
|
||||
|
||||
$forumcontext1 = \context_module::instance($forum->cmid);
|
||||
$forumcontext2 = \context_module::instance($forum2->cmid);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course2->id, 'student');
|
||||
|
||||
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
|
||||
ob_start();
|
||||
$this->runAdhocTasks('tool_dataprivacy\task\initiate_data_request_task');
|
||||
ob_end_clean();
|
||||
|
||||
$contextcount = $DB->count_records('tool_dataprivacy_ctxlst_ctx');
|
||||
api::approve_contexts_belonging_to_request($datarequest->get('id'), [$coursecontext1->id]);
|
||||
$items = $DB->get_records('tool_dataprivacy_ctxlst_ctx', null, '', 'id, contextid, status');
|
||||
|
||||
$approvecontexts = [];
|
||||
$rejectedcontext = [];
|
||||
foreach ($items as $item) {
|
||||
if ($item->status == contextlist_context::STATUS_APPROVED) {
|
||||
$approvecontexts[] = $item->contextid;
|
||||
}
|
||||
if ($item->status == contextlist_context::STATUS_REJECTED) {
|
||||
$rejectedcontext[] = $item->contextid;
|
||||
}
|
||||
}
|
||||
|
||||
// Check no pending context left.
|
||||
$this->assertEquals($contextcount, count($approvecontexts) + count($rejectedcontext));
|
||||
|
||||
$this->assertContains(strval($coursecontext1->id), $approvecontexts);
|
||||
$this->assertContains(strval($forumcontext1->id), $approvecontexts);
|
||||
$this->assertContains(strval($coursecontext2->id), $rejectedcontext);
|
||||
$this->assertContains(strval($forumcontext2->id), $rejectedcontext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update request contexts with status.
|
||||
*/
|
||||
public function test_update_request_contexts_with_status(): void {
|
||||
global $DB;
|
||||
set_config('allowfiltering', 1, 'tool_dataprivacy');
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course([]);
|
||||
|
||||
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
||||
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
|
||||
|
||||
$record = new \stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $user->id;
|
||||
$record->forum = $forum->id;
|
||||
$generator->create_discussion($record);
|
||||
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
|
||||
$forumcontext = \context_module::instance($forum->cmid);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
||||
|
||||
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
|
||||
ob_start();
|
||||
$this->runAdhocTasks('tool_dataprivacy\task\initiate_data_request_task');
|
||||
ob_end_clean();
|
||||
|
||||
$requestid = $datarequest->get("id");
|
||||
|
||||
api::update_request_contexts_with_status($requestid, contextlist_context::STATUS_APPROVED);
|
||||
// Test all request contexts is updated with status approved.
|
||||
$results = $DB->get_records(contextlist_context::TABLE, ['contextid' => $coursecontext->id]);
|
||||
foreach ($results as $result) {
|
||||
$this->assertEquals($result->status, contextlist_context::STATUS_APPROVED);
|
||||
}
|
||||
$results = $DB->get_records(contextlist_context::TABLE, ['contextid' => $forumcontext->id]);
|
||||
foreach ($results as $result) {
|
||||
$this->assertEquals($result->status, contextlist_context::STATUS_APPROVED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test api get_course_contexts_for_view_filter.
|
||||
*/
|
||||
public function test_get_course_contexts_for_view_filter(): void {
|
||||
set_config('allowfiltering', 1, 'tool_dataprivacy');
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course([]);
|
||||
$course2 = $this->getDataGenerator()->create_course([]);
|
||||
|
||||
$record = new \stdClass();
|
||||
$record->course = $course->id;
|
||||
$record->userid = $user->id;
|
||||
|
||||
$coursecontext1 = \context_course::instance($course->id);
|
||||
$coursecontext2 = \context_course::instance($course2->id);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course2->id, 'student');
|
||||
|
||||
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
|
||||
ob_start();
|
||||
$this->runAdhocTasks('tool_dataprivacy\task\initiate_data_request_task');
|
||||
ob_end_clean();
|
||||
|
||||
api::approve_contexts_belonging_to_request($datarequest->get('id'), [$coursecontext1->id]);
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
$result = api::get_course_contexts_for_view_filter($requestid);
|
||||
$this->assertContains($coursecontext1, $result);
|
||||
$this->assertContains($coursecontext2, $result);
|
||||
}
|
||||
}
|
||||
|
@ -250,3 +250,31 @@ Feature: Data delete from the privacy API
|
||||
When I press "Save changes"
|
||||
Then I should see "Your request has been submitted and will be processed soon."
|
||||
And I should see "Approved" in the "Delete all of my personal data" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: Delete flow stay the same even allow filtering of exports by course setting is enabled.
|
||||
Given the following config values are set as admin:
|
||||
| allowfiltering | 1 | tool_dataprivacy |
|
||||
And I log in as "victim"
|
||||
And I should see "Victim User 1"
|
||||
And I log out
|
||||
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I follow "New request"
|
||||
And I set the field "User" to "Victim User 1"
|
||||
And I set the field "Type" to "Delete all of my personal data"
|
||||
And I press "Save changes"
|
||||
Then I should see "Victim User 1"
|
||||
And I should see "Awaiting approval" in the "Victim User 1" "table_row"
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I follow "Approve request"
|
||||
And I press "Approve request"
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Deleted" in the "Victim User 1" "table_row"
|
||||
|
||||
And I log out
|
||||
And I log in as "victim"
|
||||
And I should see "Invalid login"
|
||||
|
@ -161,3 +161,105 @@ Feature: Data export from the privacy API
|
||||
When I press "Save changes"
|
||||
Then I should see "Your request has been submitted and will be processed soon."
|
||||
And I should see "Approved" in the "Export all of my personal data" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: As admin, enable allow filtering of exports by course setting, export data for a user and download it
|
||||
Given the following config values are set as admin:
|
||||
| allowfiltering | 1 | tool_dataprivacy |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I follow "New request"
|
||||
And I set the field "User" to "Victim User 1"
|
||||
And I should see "Export my personal data"
|
||||
And I press "Save changes"
|
||||
Then I should see "Victim User 1"
|
||||
And I should see "Pending" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Awaiting approval" in the "Victim User 1" "table_row"
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I follow "Approve request (all data)"
|
||||
And I press "Approve request"
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Download ready" in the "Victim User 1" "table_row"
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And following "Download" should download between "1" and "144000" bytes
|
||||
And the following config values are set as admin:
|
||||
| privacyrequestexpiry | 1 | tool_dataprivacy |
|
||||
And I wait "1" seconds
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I should see "Expired" in the "Victim User 1" "table_row"
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I should not see "Download"
|
||||
|
||||
@javascript
|
||||
Scenario: As admin, enable allow filtering of exports by course setting, filter before export data for a user and download it
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
| Course 2 | C2 |
|
||||
| Coruse 3 | C3 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| victim | C1 | student |
|
||||
| victim | C2 | student |
|
||||
And the following config values are set as admin:
|
||||
| allowfiltering | 1 | tool_dataprivacy |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I follow "New request"
|
||||
And I set the field "User" to "Victim User 1"
|
||||
And I press "Save changes"
|
||||
Then I should see "Victim User 1"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I follow "Approve request (data from selected courses)"
|
||||
And I should see "Course 1"
|
||||
And I should see "Course 2"
|
||||
And I should not see "Course 3"
|
||||
And I press "Approve request"
|
||||
And I should see "You must select at least one course"
|
||||
And I set the field "Select courses to export" to "Course 1"
|
||||
And I press "Approve request"
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Download ready" in the "Victim User 1" "table_row"
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And following "Download" should download between "1" and "144000" bytes
|
||||
|
||||
@javascript
|
||||
Scenario: Filter before export data for a user and download it in the view request action
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
| Course 2 | C2 |
|
||||
| Coruse 3 | C3 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| victim | C1 | student |
|
||||
| victim | C2 | student |
|
||||
And the following config values are set as admin:
|
||||
| allowfiltering | 1 | tool_dataprivacy |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I follow "New request"
|
||||
And I set the field "User" to "Victim User 1"
|
||||
And I press "Save changes"
|
||||
Then I should see "Victim User 1"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And I follow "View the request"
|
||||
And I press "Approve selected courses"
|
||||
And I set the field "Select courses to export" to "Course 1"
|
||||
And I press "Approve request"
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Download ready" in the "Victim User 1" "table_row"
|
||||
And I open the action menu in "Victim User 1" "table_row"
|
||||
And following "Download" should download between "1" and "144000" bytes
|
||||
|
91
admin/tool/dataprivacy/tests/external/submit_selected_courses_form_test.php
vendored
Normal file
91
admin/tool/dataprivacy/tests/external/submit_selected_courses_form_test.php
vendored
Normal file
@ -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/>.
|
||||
|
||||
/**
|
||||
* This is the external method for submit selected courses.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @since Moodle 4.0
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
|
||||
|
||||
use tool_dataprivacy\api;
|
||||
|
||||
/**
|
||||
* External function submit_selected_courses_form_test.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2021 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \tool_dataprivacy\api
|
||||
*/
|
||||
class submit_selected_courses_form_test extends \externallib_advanced_testcase {
|
||||
/**
|
||||
* Test for submit_selected_courses_form().
|
||||
*
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function test_submit_selected_courses_form() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
set_config('allowfiltering', 1, 'tool_dataprivacy');
|
||||
$generator = new \testing_data_generator();
|
||||
$s1 = $generator->create_user();
|
||||
$s1->ignoresesskey = true;
|
||||
$u1 = $generator->create_user();
|
||||
$u1->ignoresesskey = true;
|
||||
|
||||
$context = \context_system::instance();
|
||||
$course = $this->getDataGenerator()->create_course([]);
|
||||
|
||||
$coursecontext1 = \context_course::instance($course->id);
|
||||
|
||||
$this->getDataGenerator()->enrol_user($s1->id, $course->id, 'student');
|
||||
|
||||
// Manager role.
|
||||
$managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
|
||||
// Give the manager role with the capability to manage data requests.
|
||||
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
|
||||
// Assign u1 as a manager.
|
||||
role_assign($managerroleid, $u1->id, $context->id);
|
||||
|
||||
// Map the manager role to the DPO role.
|
||||
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
||||
|
||||
// Create the sample data request.
|
||||
$this->setUser($s1);
|
||||
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
// Make this ready for approval.
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
|
||||
|
||||
$this->setUser($u1);
|
||||
$jsonstring = "requestid=" . $requestid . "&sesskey=" . sesskey() .
|
||||
"&_qf__tool_dataprivacy_form_exportfilter_form=1&coursecontextids%5B%5D=" . $coursecontext1->id;
|
||||
$results = submit_selected_courses_form::execute($requestid, json_encode($jsonstring));
|
||||
$this->assertTrue($results["result"]);
|
||||
}
|
||||
}
|
@ -1,6 +1,19 @@
|
||||
This file describes API changes in /admin/tool/dataprivacy/*
|
||||
Information provided here is intended especially for developers.
|
||||
|
||||
=== 4.3 ===
|
||||
* New add_request_contexts_with_status function adds the contexts from the contextlist_collection to
|
||||
the request with the status provided.
|
||||
* New get_approved_contextlist_collection_for_request function finds all request contextlists having
|
||||
at least on approved context, and returns them as in a contextlist_collection.
|
||||
* New update_request_contexts_with_status function sets the status of all contexts associated with
|
||||
the request.
|
||||
* New approve_contexts_belonging_to_request function approves the contexts which are children of the
|
||||
provided course contexts.
|
||||
* New get_course_contexts_for_view_filter function gets list of course context for user to filter.
|
||||
* New submit_selected_courses_form function fetches the list of course which user can select to export
|
||||
data through form.
|
||||
|
||||
=== 4.1 ===
|
||||
* New `api::can_create_data_download_request_for_self` method for determining whether user has permission to create their
|
||||
own data download requests
|
||||
|
@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$plugin->version = 2023042400;
|
||||
$plugin->version = 2023052200;
|
||||
$plugin->requires = 2023041800;
|
||||
$plugin->component = 'tool_dataprivacy';
|
||||
|
Loading…
x
Reference in New Issue
Block a user