diff --git a/admin/tool/dataprivacy/amd/build/data_request_modal.min.js b/admin/tool/dataprivacy/amd/build/data_request_modal.min.js
index 7e9bda8ba609b..e8b22a22917f9 100644
--- a/admin/tool/dataprivacy/amd/build/data_request_modal.min.js
+++ b/admin/tool/dataprivacy/amd/build/data_request_modal.min.js
@@ -1 +1 @@
-define(["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","tool_dataprivacy/events"],function(a,b,c,d,e,f){var g=!1,h={APPROVE_BUTTON:'[data-action="approve"]',DENY_BUTTON:'[data-action="deny"]'},i=function(a){d.call(this,a),this.getFooter().find(h.APPROVE_BUTTON).length||b.exception({message:"No approve button found"}),this.getFooter().find(h.DENY_BUTTON).length||b.exception({message:"No deny button found"})};return i.TYPE="tool_dataprivacy-data_request",i.prototype=Object.create(d.prototype),i.prototype.constructor=i,i.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this),this.getModal().on(c.events.activate,h.APPROVE_BUTTON,function(b,c){var d=a.Event(f.approve);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.DENY_BUTTON,function(b,c){var d=a.Event(f.deny);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this))},g||(e.register(i.TYPE,i,"tool_dataprivacy/data_request_modal"),g=!0),i});
\ No newline at end of file
+define(["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","tool_dataprivacy/events"],function(a,b,c,d,e,f){var g=!1,h={APPROVE_BUTTON:'[data-action="approve"]',DENY_BUTTON:'[data-action="deny"]',COMPLETE_BUTTON:'[data-action="complete"]'},i=function(a){d.call(this,a)};return i.TYPE="tool_dataprivacy-data_request",i.prototype=Object.create(d.prototype),i.prototype.constructor=i,i.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this),this.getModal().on(c.events.activate,h.APPROVE_BUTTON,function(b,c){var d=a.Event(f.approve);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.DENY_BUTTON,function(b,c){var d=a.Event(f.deny);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this)),this.getModal().on(c.events.activate,h.COMPLETE_BUTTON,function(b,c){var d=a.Event(f.complete);this.getRoot().trigger(d,this),d.isDefaultPrevented()||(this.hide(),c.originalEvent.preventDefault())}.bind(this))},g||(e.register(i.TYPE,i,"tool_dataprivacy/data_request_modal"),g=!0),i});
\ No newline at end of file
diff --git a/admin/tool/dataprivacy/amd/build/events.min.js b/admin/tool/dataprivacy/amd/build/events.min.js
index 1d4e97399b52f..0ecae4c42499e 100644
--- a/admin/tool/dataprivacy/amd/build/events.min.js
+++ b/admin/tool/dataprivacy/amd/build/events.min.js
@@ -1 +1 @@
-define([],function(){return{approve:"tool_dataprivacy-data_request:approve",deny:"tool_dataprivacy-data_request:deny"}});
\ No newline at end of file
+define([],function(){return{approve:"tool_dataprivacy-data_request:approve",deny:"tool_dataprivacy-data_request:deny",complete:"tool_dataprivacy-data_request:complete"}});
\ No newline at end of file
diff --git a/admin/tool/dataprivacy/amd/build/requestactions.min.js b/admin/tool/dataprivacy/amd/build/requestactions.min.js
index 586a2da288829..c405d17f21514 100644
--- a/admin/tool/dataprivacy/amd/build/requestactions.min.js
+++ b/admin/tool/dataprivacy/amd/build/requestactions.min.js
@@ -1 +1 @@
-define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","tool_dataprivacy/data_request_modal","tool_dataprivacy/events"],function(a,b,c,d,e,f,g,h,i){function j(a,g){var h=[],j="";switch(a){case i.approve:h=[{key:"approverequest",component:"tool_dataprivacy"},{key:"confirmapproval",component:"tool_dataprivacy"}],j="tool_dataprivacy_approve_data_request";break;case i.deny:h=[{key:"denyrequest",component:"tool_dataprivacy"},{key:"confirmdenial",component:"tool_dataprivacy"}],j="tool_dataprivacy_deny_data_request"}var k="";d.get_strings(h).then(function(a){k=a[0];var b=a[1];return e.create({title:k,body:b,type:e.types.SAVE_CANCEL})}).then(function(a){return a.setSaveButtonText(k),a.getRoot().on(f.save,function(){var a={requestid:g},d={methodname:j,args:a};b.call([d])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a}).done(function(a){a.show()}).fail(c.exception)}var k={APPROVE_REQUEST:'[data-action="approve"]',DENY_REQUEST:'[data-action="deny"]',VIEW_REQUEST:'[data-action="view"]'},l=function(){this.registerEvents()};return l.prototype.registerEvents=function(){a(k.VIEW_REQUEST).click(function(d){d.preventDefault();var k=a(this).data("requestid"),l={requestid:k},m={methodname:"tool_dataprivacy_get_data_request",args:l},n=b.call([m]),o="",p=e.types.DEFAULT;a.when(n[0]).then(function(a){return a.result?(2==a.result.status&&(p=h.TYPE),o=a.result.typename,g.render("tool_dataprivacy/request_details",a.result)):(c.addNotification({message:a.warnings[0].message,type:"error"}),!1)}).then(function(a){return e.create({title:o,body:a,type:p,large:!0}).then(function(a){return a.getRoot().on(i.approve,function(){j(i.approve,k)}),a.getRoot().on(i.deny,function(){j(i.deny,k)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a})}).done(function(a){a.show()}).fail(c.exception)}),a(k.APPROVE_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.approve,c)}),a(k.DENY_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.deny,c)})},l});
\ No newline at end of file
+define(["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","tool_dataprivacy/data_request_modal","tool_dataprivacy/events"],function(a,b,c,d,e,f,g,h,i){function j(a,b){var g=[],h="",j={requestid:b};switch(a){case i.approve:g=[{key:"approverequest",component:"tool_dataprivacy"},{key:"confirmapproval",component:"tool_dataprivacy"}],h="tool_dataprivacy_approve_data_request";break;case i.deny:g=[{key:"denyrequest",component:"tool_dataprivacy"},{key:"confirmdenial",component:"tool_dataprivacy"}],h="tool_dataprivacy_deny_data_request";break;case i.complete:g=[{key:"markcomplete",component:"tool_dataprivacy"},{key:"confirmcompletion",component:"tool_dataprivacy"}],h="tool_dataprivacy_mark_complete"}var l="";d.get_strings(g).then(function(a){l=a[0];var b=a[1];return e.create({title:l,body:b,type:e.types.SAVE_CANCEL})}).then(function(a){a.setSaveButtonText(l),a.getRoot().on(f.save,function(){k(h,j)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show()})["catch"](c.exception)}function k(a,d){var e={methodname:a,args:d};b.call([e])[0].done(function(a){a.result?window.location.reload():c.addNotification({message:a.warnings[0].message,type:"error"})}).fail(c.exception)}var l={APPROVE_REQUEST:'[data-action="approve"]',DENY_REQUEST:'[data-action="deny"]',VIEW_REQUEST:'[data-action="view"]',MARK_COMPLETE:'[data-action="complete"]'},m=function(){this.registerEvents()};return m.prototype.registerEvents=function(){a(l.VIEW_REQUEST).click(function(d){d.preventDefault();var l=a(this).data("requestid"),m={requestid:l},n={methodname:"tool_dataprivacy_get_data_request",args:m},o=b.call([n]);a.when(o[0]).then(function(a){return a.result?a.result:(c.addNotification({message:a.warnings[0].message,type:"error"}),!1)}).then(function(a){var b=g.render("tool_dataprivacy/request_details",a),c={approvedeny:a.approvedeny,canmarkcomplete:a.canmarkcomplete};return e.create({title:a.typename,body:b,type:h.TYPE,large:!0,templateContext:c})}).then(function(a){a.getRoot().on(i.approve,function(){j(i.approve,l)}),a.getRoot().on(i.deny,function(){j(i.deny,l)}),a.getRoot().on(i.complete,function(){var a={requestid:l};k("tool_dataprivacy_mark_complete",a)}),a.getRoot().on(f.hidden,function(){a.destroy()}),a.show()})["catch"](c.exception)}),a(l.APPROVE_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.approve,c)}),a(l.DENY_REQUEST).click(function(b){b.preventDefault();var c=a(this).data("requestid");j(i.deny,c)}),a(l.MARK_COMPLETE).click(function(b){b.preventDefault(),j(i.complete,a(this).data("requestid"))})},m});
\ No newline at end of file
diff --git a/admin/tool/dataprivacy/amd/src/data_request_modal.js b/admin/tool/dataprivacy/amd/src/data_request_modal.js
index abf717b526b1d..5f841c5cfd451 100644
--- a/admin/tool/dataprivacy/amd/src/data_request_modal.js
+++ b/admin/tool/dataprivacy/amd/src/data_request_modal.js
@@ -29,6 +29,7 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
var SELECTORS = {
APPROVE_BUTTON: '[data-action="approve"]',
DENY_BUTTON: '[data-action="deny"]',
+ COMPLETE_BUTTON: '[data-action="complete"]'
};
/**
@@ -38,14 +39,6 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
*/
var ModalDataRequest = function(root) {
Modal.call(this, root);
-
- if (!this.getFooter().find(SELECTORS.APPROVE_BUTTON).length) {
- Notification.exception({message: 'No approve button found'});
- }
-
- if (!this.getFooter().find(SELECTORS.DENY_BUTTON).length) {
- Notification.exception({message: 'No deny button found'});
- }
};
ModalDataRequest.TYPE = 'tool_dataprivacy-data_request';
@@ -80,6 +73,16 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
data.originalEvent.preventDefault();
}
}.bind(this));
+
+ this.getModal().on(CustomEvents.events.activate, SELECTORS.COMPLETE_BUTTON, function(e, data) {
+ var completeEvent = $.Event(DataPrivacyEvents.complete);
+ this.getRoot().trigger(completeEvent, this);
+
+ if (!completeEvent.isDefaultPrevented()) {
+ this.hide();
+ data.originalEvent.preventDefault();
+ }
+ }.bind(this));
};
// Automatically register with the modal registry the first time this module is imported so that you can create modals
diff --git a/admin/tool/dataprivacy/amd/src/events.js b/admin/tool/dataprivacy/amd/src/events.js
index 9398dc45398a2..4e7ff77030a15 100644
--- a/admin/tool/dataprivacy/amd/src/events.js
+++ b/admin/tool/dataprivacy/amd/src/events.js
@@ -26,5 +26,6 @@ define([], function() {
return {
approve: 'tool_dataprivacy-data_request:approve',
deny: 'tool_dataprivacy-data_request:deny',
+ complete: 'tool_dataprivacy-data_request:complete'
};
});
diff --git a/admin/tool/dataprivacy/amd/src/requestactions.js b/admin/tool/dataprivacy/amd/src/requestactions.js
index c0941f8f93cb2..4f3c40607ba89 100644
--- a/admin/tool/dataprivacy/amd/src/requestactions.js
+++ b/admin/tool/dataprivacy/amd/src/requestactions.js
@@ -39,11 +39,13 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
* @type {{APPROVE_REQUEST: string}}
* @type {{DENY_REQUEST: string}}
* @type {{VIEW_REQUEST: string}}
+ * @type {{MARK_COMPLETE: string}}
*/
var ACTIONS = {
APPROVE_REQUEST: '[data-action="approve"]',
DENY_REQUEST: '[data-action="deny"]',
- VIEW_REQUEST: '[data-action="view"]'
+ VIEW_REQUEST: '[data-action="view"]',
+ MARK_COMPLETE: '[data-action="complete"]'
};
/**
@@ -73,16 +75,9 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
};
var promises = Ajax.call([request]);
- var modalTitle = '';
- var modalType = ModalFactory.types.DEFAULT;
$.when(promises[0]).then(function(data) {
if (data.result) {
- // Check if the status is awaiting approval.
- if (data.result.status == 2) {
- modalType = ModalDataRequest.TYPE;
- }
- modalTitle = data.result.typename;
- return Templates.render('tool_dataprivacy/request_details', data.result);
+ return data.result;
}
// Fail.
Notification.addNotification({
@@ -91,35 +86,51 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
});
return false;
- }).then(function(html) {
+ }).then(function(data) {
+ var body = Templates.render('tool_dataprivacy/request_details', data);
+ var templateContext = {
+ approvedeny: data.approvedeny,
+ canmarkcomplete: data.canmarkcomplete
+ };
return ModalFactory.create({
- title: modalTitle,
- body: html,
- type: modalType,
- large: true
- }).then(function(modal) {
- // Handle approve event.
- modal.getRoot().on(DataPrivacyEvents.approve, function() {
- showConfirmation(DataPrivacyEvents.approve, requestId);
- });
-
- // Handle deny event.
- modal.getRoot().on(DataPrivacyEvents.deny, function() {
- showConfirmation(DataPrivacyEvents.deny, requestId);
- });
-
- // Handle hidden event.
- modal.getRoot().on(ModalEvents.hidden, function() {
- // Destroy when hidden.
- modal.destroy();
- });
-
- return modal;
+ title: data.typename,
+ body: body,
+ type: ModalDataRequest.TYPE,
+ large: true,
+ templateContext: templateContext
+ });
+
+ }).then(function(modal) {
+ // Handle approve event.
+ modal.getRoot().on(DataPrivacyEvents.approve, function() {
+ showConfirmation(DataPrivacyEvents.approve, requestId);
+ });
+
+ // Handle deny event.
+ modal.getRoot().on(DataPrivacyEvents.deny, function() {
+ showConfirmation(DataPrivacyEvents.deny, requestId);
});
- }).done(function(modal) {
+
+ // Handle send event.
+ modal.getRoot().on(DataPrivacyEvents.complete, function() {
+ var params = {
+ 'requestid': requestId
+ };
+ handleSave('tool_dataprivacy_mark_complete', params);
+ });
+
+ // Handle hidden event.
+ modal.getRoot().on(ModalEvents.hidden, function() {
+ // Destroy when hidden.
+ modal.destroy();
+ });
+
// Show the modal!
modal.show();
- }).fail(Notification.exception);
+
+ return;
+
+ }).catch(Notification.exception);
});
$(ACTIONS.APPROVE_REQUEST).click(function(e) {
@@ -135,6 +146,11 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
var requestId = $(this).data('requestid');
showConfirmation(DataPrivacyEvents.deny, requestId);
});
+
+ $(ACTIONS.MARK_COMPLETE).click(function(e) {
+ e.preventDefault();
+ showConfirmation(DataPrivacyEvents.complete, $(this).data('requestid'));
+ });
};
/**
@@ -146,6 +162,9 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
function showConfirmation(action, requestId) {
var keys = [];
var wsfunction = '';
+ var params = {
+ 'requestid': requestId
+ };
switch (action) {
case DataPrivacyEvents.approve:
keys = [
@@ -173,6 +192,19 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
];
wsfunction = 'tool_dataprivacy_deny_data_request';
break;
+ case DataPrivacyEvents.complete:
+ keys = [
+ {
+ key: 'markcomplete',
+ component: 'tool_dataprivacy'
+ },
+ {
+ key: 'confirmcompletion',
+ component: 'tool_dataprivacy'
+ }
+ ];
+ wsfunction = 'tool_dataprivacy_mark_complete';
+ break;
}
var modalTitle = '';
@@ -189,26 +221,7 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
- // Confirm the request.
- var params = {
- 'requestid': requestId
- };
-
- var request = {
- methodname: wsfunction,
- args: params
- };
-
- Ajax.call([request])[0].done(function(data) {
- if (data.result) {
- window.location.reload();
- } else {
- Notification.addNotification({
- message: data.warnings[0].message,
- type: 'error'
- });
- }
- }).fail(Notification.exception);
+ handleSave(wsfunction, params);
});
// Handle hidden event.
@@ -217,9 +230,39 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
modal.destroy();
});
- return modal;
- }).done(function(modal) {
modal.show();
+
+ return;
+
+ }).catch(Notification.exception);
+ }
+
+ /**
+ * Calls a web service function and reloads the page on success and shows a notification.
+ * Displays an error notification, otherwise.
+ *
+ * @param {String} wsfunction The web service function to call.
+ * @param {Object} params The parameters for the web service functoon.
+ */
+ function handleSave(wsfunction, params) {
+ // Confirm the request.
+ var request = {
+ methodname: wsfunction,
+ args: params
+ };
+
+ Ajax.call([request])[0].done(function(data) {
+ if (data.result) {
+ // On success, reload the page so that the data request table will be updated.
+ // TODO: Probably in the future, better to reload the table or the target data request via AJAX.
+ window.location.reload();
+ } else {
+ // Add the notification.
+ Notification.addNotification({
+ message: data.warnings[0].message,
+ type: 'error'
+ });
+ }
}).fail(Notification.exception);
}
diff --git a/admin/tool/dataprivacy/classes/api.php b/admin/tool/dataprivacy/classes/api.php
index 42acb4259e74c..9aaea2865c239 100644
--- a/admin/tool/dataprivacy/classes/api.php
+++ b/admin/tool/dataprivacy/classes/api.php
@@ -24,6 +24,7 @@
namespace tool_dataprivacy;
use coding_exception;
+use context_course;
use context_system;
use core\invalid_persistent_exception;
use core\message\message;
@@ -426,7 +427,22 @@ public static function update_request_status($requestid, $status, $dpoid = 0, $c
if ($dpoid) {
$datarequest->set('dpo', $dpoid);
}
- $datarequest->set('dpocomment', $comment);
+ // Update the comment if necessary.
+ if (!empty(trim($comment))) {
+ $params = [
+ 'date' => userdate(time()),
+ 'comment' => $comment
+ ];
+ $commenttosave = get_string('datecomment', 'tool_dataprivacy', $params);
+ // Check if there's an existing DPO comment.
+ $currentcomment = trim($datarequest->get('dpocomment'));
+ if ($currentcomment) {
+ // Append the new comment to the current comment and give them 1 line space in between.
+ $commenttosave = $currentcomment . PHP_EOL . PHP_EOL . $commenttosave;
+ }
+ $datarequest->set('dpocomment', $commenttosave);
+ }
+
return $datarequest->update();
}
@@ -521,7 +537,6 @@ public static function deny_data_request($requestid) {
* @param data_request $request The data request
* @return int|false
* @throws coding_exception
- * @throws dml_exception
* @throws moodle_exception
*/
public static function notify_dpo($dpo, data_request $request) {
diff --git a/admin/tool/dataprivacy/classes/external.php b/admin/tool/dataprivacy/classes/external.php
index 6587c40b2624a..e14e07269008f 100644
--- a/admin/tool/dataprivacy/classes/external.php
+++ b/admin/tool/dataprivacy/classes/external.php
@@ -31,6 +31,7 @@
use context_system;
use context_user;
use core\invalid_persistent_exception;
+use core\notification;
use core_user;
use dml_exception;
use external_api;
@@ -144,7 +145,7 @@ public static function contact_dpo_parameters() {
}
/**
- * Deny a data request.
+ * Make a general enquiry to a DPO.
*
* @since Moodle 3.5
* @param string $message The message to be sent to the DPO.
@@ -210,7 +211,7 @@ public static function contact_dpo($message) {
}
/**
- * Parameter description for deny_data_request().
+ * Parameter description for contact_dpo().
*
* @since Moodle 3.5
* @return external_description
@@ -222,6 +223,70 @@ public static function contact_dpo_returns() {
]);
}
+ /**
+ * Parameter description for mark_complete().
+ *
+ * @since Moodle 3.5.2
+ * @return external_function_parameters
+ */
+ public static function mark_complete_parameters() {
+ return new external_function_parameters([
+ 'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
+ ]);
+ }
+
+ /**
+ * Mark a user's general enquiry's status as complete.
+ *
+ * @since Moodle 3.5.2
+ * @param int $requestid The request ID of the general enquiry.
+ * @return array
+ * @throws coding_exception
+ * @throws invalid_parameter_exception
+ * @throws invalid_persistent_exception
+ * @throws restricted_context_exception
+ * @throws dml_exception
+ * @throws moodle_exception
+ */
+ public static function mark_complete($requestid) {
+ global $USER;
+
+ $warnings = [];
+ $params = external_api::validate_parameters(self::mark_complete_parameters(), [
+ 'requestid' => $requestid,
+ ]);
+ $requestid = $params['requestid'];
+
+ // Validate context.
+ $context = context_system::instance();
+ self::validate_context($context);
+
+ $message = get_string('markedcomplete', 'tool_dataprivacy');
+ // Update the data request record.
+ if ($result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE, $USER->id, $message)) {
+ // Add notification in the session to be shown when the page is reloaded on the JS side.
+ notification::success(get_string('requestmarkedcomplete', 'tool_dataprivacy'));
+ }
+
+ return [
+ 'result' => $result,
+ 'warnings' => $warnings
+ ];
+ }
+
+ /**
+ * Parameter description for mark_complete().
+ *
+ * @since Moodle 3.5.2
+ * @return external_description
+ */
+ public static function mark_complete_returns() {
+ return new external_single_structure([
+ 'result' => new external_value(PARAM_BOOL, 'The processing result'),
+ 'warnings' => new external_warnings()
+ ]);
+ }
+
/**
* Parameter description for get_data_request().
*
@@ -258,9 +323,9 @@ public static function get_data_request($requestid) {
// Validate context.
$context = context_system::instance();
self::validate_context($context);
+ $requestpersistent = new data_request($requestid);
require_capability('tool/dataprivacy:managedatarequests', $context);
- $requestpersistent = new data_request($requestid);
$exporter = new data_request_exporter($requestpersistent, ['context' => $context]);
$renderer = $PAGE->get_renderer('tool_dataprivacy');
$result = $exporter->export($renderer);
@@ -326,6 +391,9 @@ public static function approve_data_request($requestid) {
$result = false;
if ($requestexists) {
$result = api::approve_data_request($requestid);
+
+ // 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,
@@ -395,6 +463,9 @@ public static function deny_data_request($requestid) {
$result = false;
if ($requestexists) {
$result = api::deny_data_request($requestid);
+
+ // Add notification in the session to be shown when the page is reloaded on the JS side.
+ notification::success(get_string('requestdenied', 'tool_dataprivacy'));
} else {
$warnings[] = [
'item' => $requestid,
diff --git a/admin/tool/dataprivacy/classes/external/data_request_exporter.php b/admin/tool/dataprivacy/classes/external/data_request_exporter.php
index 999634933170e..93b33e3af4749 100644
--- a/admin/tool/dataprivacy/classes/external/data_request_exporter.php
+++ b/admin/tool/dataprivacy/classes/external/data_request_exporter.php
@@ -102,6 +102,16 @@ protected static function define_other_properties() {
'optional' => true,
'default' => false
],
+ 'approvedeny' => [
+ 'type' => PARAM_BOOL,
+ 'optional' => true,
+ 'default' => false
+ ],
+ 'canmarkcomplete' => [
+ 'type' => PARAM_BOOL,
+ 'optional' => true,
+ 'default' => false
+ ],
];
}
@@ -140,14 +150,19 @@ protected function get_other_values(renderer_base $output) {
$values['messagehtml'] = text_to_html($this->persistent->get('comments'));
- $values['typename'] = helper::get_request_type_string($this->persistent->get('type'));
- $values['typenameshort'] = helper::get_shortened_request_type_string($this->persistent->get('type'));
+ $requesttype = $this->persistent->get('type');
+ $values['typename'] = helper::get_request_type_string($requesttype);
+ $values['typenameshort'] = helper::get_shortened_request_type_string($requesttype);
$values['canreview'] = false;
+ $values['approvedeny'] = false;
$values['statuslabel'] = helper::get_request_status_string($this->persistent->get('status'));
+
switch ($this->persistent->get('status')) {
case api::DATAREQUEST_STATUS_PENDING:
$values['statuslabelclass'] = 'label-default';
+ // Request can be manually completed for general enquiry requests.
+ $values['canmarkcomplete'] = $requesttype == api::DATAREQUEST_TYPE_OTHERS;
break;
case api::DATAREQUEST_STATUS_PREPROCESSING:
$values['statuslabelclass'] = 'label-default';
@@ -156,6 +171,8 @@ protected function get_other_values(renderer_base $output) {
$values['statuslabelclass'] = 'label-info';
// DPO can review the request once it's ready.
$values['canreview'] = true;
+ // Whether the DPO can approve or deny the request.
+ $values['approvedeny'] = in_array($requesttype, [api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_TYPE_DELETE]);
break;
case api::DATAREQUEST_STATUS_APPROVED:
$values['statuslabelclass'] = 'label-info';
diff --git a/admin/tool/dataprivacy/classes/output/data_requests_table.php b/admin/tool/dataprivacy/classes/output/data_requests_table.php
index 97918d9b86a4f..d8b0644a0db3d 100644
--- a/admin/tool/dataprivacy/classes/output/data_requests_table.php
+++ b/admin/tool/dataprivacy/classes/output/data_requests_table.php
@@ -180,16 +180,32 @@ public function col_actions($data) {
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
- if ($status == api::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
- // Approve.
- $actiondata['data-action'] = 'approve';
- $actiontext = get_string('approverequest', 'tool_dataprivacy');
- $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
-
- // Deny.
- $actiondata['data-action'] = 'deny';
- $actiontext = get_string('denyrequest', 'tool_dataprivacy');
- $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+ switch ($status) {
+ case api::DATAREQUEST_STATUS_PENDING:
+ // Add action to mark a general enquiry request as complete.
+ if ($data->type == api::DATAREQUEST_TYPE_OTHERS) {
+ $actiondata['data-action'] = 'complete';
+ $nameemail = (object)[
+ 'name' => $data->foruser->fullname,
+ 'email' => $data->foruser->email
+ ];
+ $actiondata['data-requestid'] = $data->id;
+ $actiondata['data-replytoemail'] = get_string('nameemail', 'tool_dataprivacy', $nameemail);
+ $actiontext = get_string('markcomplete', 'tool_dataprivacy');
+ $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+ }
+ break;
+ case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
+ // Approve.
+ $actiondata['data-action'] = 'approve';
+ $actiontext = get_string('approverequest', 'tool_dataprivacy');
+ $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+
+ // Deny.
+ $actiondata['data-action'] = 'deny';
+ $actiontext = get_string('denyrequest', 'tool_dataprivacy');
+ $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+ break;
}
$actionsmenu = new action_menu($actions);
diff --git a/admin/tool/dataprivacy/classes/output/my_data_requests_page.php b/admin/tool/dataprivacy/classes/output/my_data_requests_page.php
index 25229f008ac47..c5e18a135086d 100644
--- a/admin/tool/dataprivacy/classes/output/my_data_requests_page.php
+++ b/admin/tool/dataprivacy/classes/output/my_data_requests_page.php
@@ -82,6 +82,7 @@ public function export_for_template(renderer_base $output) {
$requestid = $request->get('id');
$status = $request->get('status');
$userid = $request->get('userid');
+ $type = $request->get('type');
$usercontext = context_user::instance($userid, IGNORE_MISSING);
if (!$usercontext) {
@@ -107,7 +108,8 @@ public function export_for_template(renderer_base $output) {
$item->statuslabelclass = 'label-success';
$item->statuslabel = get_string('statuscomplete', 'tool_dataprivacy');
$cancancel = false;
- $candownload = true;
+ // Show download links only for export-type data requests.
+ $candownload = $type == api::DATAREQUEST_TYPE_EXPORT;
break;
case api::DATAREQUEST_STATUS_CANCELLED:
case api::DATAREQUEST_STATUS_REJECTED:
diff --git a/admin/tool/dataprivacy/db/services.php b/admin/tool/dataprivacy/db/services.php
index b72b2010dc4a2..9c71e8c4dc2ff 100644
--- a/admin/tool/dataprivacy/db/services.php
+++ b/admin/tool/dataprivacy/db/services.php
@@ -43,6 +43,16 @@
'ajax' => true,
'loginrequired' => true,
],
+ 'tool_dataprivacy_mark_complete' => [
+ 'classname' => 'tool_dataprivacy\external',
+ 'methodname' => 'mark_complete',
+ 'classpath' => '',
+ 'description' => 'Mark a user\'s general enquiry as complete',
+ 'type' => 'write',
+ 'capabilities' => 'tool/dataprivacy:managedatarequests',
+ 'ajax' => true,
+ 'loginrequired' => true,
+ ],
'tool_dataprivacy_get_data_request' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_data_request',
diff --git a/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php b/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
index aa640478bae1a..b01a0b6b0dc80 100644
--- a/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
+++ b/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
@@ -46,6 +46,7 @@
$string['close'] = 'Close';
$string['compliant'] = 'Compliant';
$string['confirmapproval'] = 'Do you really want to approve this data request?';
+$string['confirmcompletion'] = 'Do you really want to mark this user enquiry as complete?';
$string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
$string['confirmdenial'] = 'Do you really want deny this data request?';
$string['contactdataprotectionofficer'] = 'Contact the privacy officer';
@@ -70,6 +71,7 @@
$string['datarequestcreatedforuser'] = 'Data request created for {$a}';
$string['datarequestemailsubject'] = 'Data request: {$a}';
$string['datarequests'] = 'Data requests';
+$string['datecomment'] = '[{$a->date}]: ' . PHP_EOL . ' {$a->comment}';
$string['daterequested'] = 'Date requested';
$string['daterequesteddetail'] = 'Date requested:';
$string['defaultsinfo'] = 'Default categories and purposes are applied to all newly created instances.';
@@ -151,6 +153,8 @@
$string['inherit'] = 'Inherit';
$string['lawfulbases'] = 'Lawful bases';
$string['lawfulbases_help'] = 'Select at least one option that will serve as the lawful basis for processing personal data. For details on these lawful bases, please see GDPR Art. 6.1';
+$string['markcomplete'] = 'Mark as complete';
+$string['markedcomplete'] = 'Your enquiry has been marked as complete by the privacy officer.';
$string['messageprovider:contactdataprotectionofficer'] = 'Data requests';
$string['messageprovider:datarequestprocessingresults'] = 'Data request processing results';
$string['messageprovider:notifyexceptions'] = 'Data requests exceptions notifications';
@@ -197,12 +201,15 @@
$string['purposeupdated'] = 'Purpose updated';
$string['replyto'] = 'Reply to';
$string['requestactions'] = 'Actions';
+$string['requestapproved'] = 'The request has been approved';
$string['requestby'] = 'Requested by';
$string['requestbydetail'] = 'Requested by:';
$string['requestcomments'] = 'Comments';
$string['requestcomments_help'] = 'This box enables you to enter any further details about your data request.';
+$string['requestdenied'] = 'The request has been denied';
$string['requestemailintro'] = 'You have received a data request:';
$string['requestfor'] = 'Requesting for';
+$string['requestmarkedcomplete'] = 'The request has been marked as complete';
$string['requeststatus'] = 'Status';
$string['requestsubmitted'] = 'Your request has been submitted to the privacy officer';
$string['requesttype'] = 'Type';
diff --git a/admin/tool/dataprivacy/templates/data_request_modal.mustache b/admin/tool/dataprivacy/templates/data_request_modal.mustache
index cae5022f947c9..e5a1ff90a10f7 100644
--- a/admin/tool/dataprivacy/templates/data_request_modal.mustache
+++ b/admin/tool/dataprivacy/templates/data_request_modal.mustache
@@ -35,7 +35,12 @@
}}
{{< core/modal }}
{{$footer}}
-
-
+ {{#approvedeny}}
+
+
+ {{/approvedeny}}
+ {{#canmarkcomplete}}
+
+ {{/canmarkcomplete}}
{{/footer}}
{{/ core/modal }}
diff --git a/admin/tool/dataprivacy/version.php b/admin/tool/dataprivacy/version.php
index 7b7bde4be6e9f..01b8b2b6a6bd5 100644
--- a/admin/tool/dataprivacy/version.php
+++ b/admin/tool/dataprivacy/version.php
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die;
-$plugin->version = 2018051401;
+$plugin->version = 2018051402;
$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
$plugin->component = 'tool_dataprivacy';