From 4cd11a3f3f96f678dcd1a79a75fb959b720ec852 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Sat, 20 Feb 2021 00:28:28 +0000 Subject: [PATCH 1/2] MDL-71090 tool_dataprivacy: convert contact DPO to modal forms. --- .../dataprivacy/amd/build/contactdpo.min.js | 2 + .../amd/build/contactdpo.min.js.map | 1 + admin/tool/dataprivacy/amd/src/contactdpo.js | 67 ++++++++++++ admin/tool/dataprivacy/classes/external.php | 3 +- .../dataprivacy/classes/form/contactdpo.php | 100 ++++++++++++++++++ .../dataprivacy/classes/output/renderer.php | 5 +- .../dataprivacy/lang/en/tool_dataprivacy.php | 1 + admin/tool/dataprivacy/lib.php | 6 +- .../templates/contact_dpo.mustache | 58 ---------- .../behat/contact_privacy_officer.feature | 16 +-- 10 files changed, 187 insertions(+), 72 deletions(-) create mode 100644 admin/tool/dataprivacy/amd/build/contactdpo.min.js create mode 100644 admin/tool/dataprivacy/amd/build/contactdpo.min.js.map create mode 100644 admin/tool/dataprivacy/amd/src/contactdpo.js create mode 100644 admin/tool/dataprivacy/classes/form/contactdpo.php delete mode 100644 admin/tool/dataprivacy/templates/contact_dpo.mustache diff --git a/admin/tool/dataprivacy/amd/build/contactdpo.min.js b/admin/tool/dataprivacy/amd/build/contactdpo.min.js new file mode 100644 index 00000000000..73514788d05 --- /dev/null +++ b/admin/tool/dataprivacy/amd/build/contactdpo.min.js @@ -0,0 +1,2 @@ +define ("tool_dataprivacy/contactdpo",["exports","core_form/modalform","core/notification","core/str","core/toast"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=f(b);c=f(c);function f(a){return a&&a.__esModule?a:{default:a}}var g={CONTACT_DPO:"[data-action=\"contactdpo\"]"};a.init=function init(){var a=document.querySelector(g.CONTACT_DPO);a.addEventListener("click",function(f){f.preventDefault();var g=new b.default({modalConfig:{title:(0,d.get_string)("contactdataprotectionofficer","tool_dataprivacy")},formClass:"tool_dataprivacy\\form\\contactdpo",saveButtonText:(0,d.get_string)("send","tool_dataprivacy"),returnFocus:a});g.addEventListener(g.events.FORM_SUBMITTED,function(a){if(a.detail.result){(0,d.get_string)("requestsubmitted","tool_dataprivacy").then(e.add).catch()}else{var b=a.detail.warnings.map(function(a){return a.message});c.default.addNotification({type:"error",message:b.join("
")})}});g.show()})}}); +//# sourceMappingURL=contactdpo.min.js.map diff --git a/admin/tool/dataprivacy/amd/build/contactdpo.min.js.map b/admin/tool/dataprivacy/amd/build/contactdpo.min.js.map new file mode 100644 index 00000000000..6445af76e86 --- /dev/null +++ b/admin/tool/dataprivacy/amd/build/contactdpo.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/contactdpo.js"],"names":["SELECTORS","CONTACT_DPO","init","triggerElement","document","querySelector","addEventListener","event","preventDefault","modalForm","ModalForm","modalConfig","title","formClass","saveButtonText","returnFocus","events","FORM_SUBMITTED","detail","result","then","addToast","catch","warningMessages","warnings","map","warning","message","Notification","addNotification","type","join","show"],"mappings":"oNAwBA,OACA,O,sDAIMA,CAAAA,CAAS,CAAG,CACdC,WAAW,CAAE,8BADC,C,QAOE,QAAPC,CAAAA,IAAO,EAAM,CACtB,GAAMC,CAAAA,CAAc,CAAGC,QAAQ,CAACC,aAAT,CAAuBL,CAAS,CAACC,WAAjC,CAAvB,CAEAE,CAAc,CAACG,gBAAf,CAAgC,OAAhC,CAAyC,SAAAC,CAAK,CAAI,CAC9CA,CAAK,CAACC,cAAN,GAEA,GAAMC,CAAAA,CAAS,CAAG,GAAIC,UAAJ,CAAc,CAC5BC,WAAW,CAAE,CACTC,KAAK,CAAE,iBAAU,8BAAV,CAA0C,kBAA1C,CADE,CADe,CAI5BC,SAAS,CAAE,oCAJiB,CAK5BC,cAAc,CAAE,iBAAU,MAAV,CAAkB,kBAAlB,CALY,CAM5BC,WAAW,CAAEZ,CANe,CAAd,CAAlB,CAUAM,CAAS,CAACH,gBAAV,CAA2BG,CAAS,CAACO,MAAV,CAAiBC,cAA5C,CAA4D,SAAAV,CAAK,CAAI,CACjE,GAAIA,CAAK,CAACW,MAAN,CAAaC,MAAjB,CAAyB,CACrB,iBAAU,kBAAV,CAA8B,kBAA9B,EAAkDC,IAAlD,CAAuDC,KAAvD,EAAiEC,KAAjE,EACH,CAFD,IAEO,CACH,GAAMC,CAAAA,CAAe,CAAGhB,CAAK,CAACW,MAAN,CAAaM,QAAb,CAAsBC,GAAtB,CAA0B,SAAAC,CAAO,QAAIA,CAAAA,CAAO,CAACC,OAAZ,CAAjC,CAAxB,CACAC,UAAaC,eAAb,CAA6B,CACzBC,IAAI,CAAE,OADmB,CAEzBH,OAAO,CAAEJ,CAAe,CAACQ,IAAhB,CAAqB,MAArB,CAFgB,CAA7B,CAIH,CACJ,CAVD,EAYAtB,CAAS,CAACuB,IAAV,EACH,CA1BD,CA2BH,C","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 .\n\n/**\n * Javascript module for contacting the site DPO\n *\n * @module tool_dataprivacy/contactdpo\n * @package tool_dataprivacy\n * @copyright 2021 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalForm from 'core_form/modalform';\nimport Notification from 'core/notification';\nimport {get_string as getString} from 'core/str';\nimport {add as addToast} from 'core/toast';\n\nconst SELECTORS = {\n CONTACT_DPO: '[data-action=\"contactdpo\"]',\n};\n\n/**\n * Initialize module\n */\nexport const init = () => {\n const triggerElement = document.querySelector(SELECTORS.CONTACT_DPO);\n\n triggerElement.addEventListener('click', event => {\n event.preventDefault();\n\n const modalForm = new ModalForm({\n modalConfig: {\n title: getString('contactdataprotectionofficer', 'tool_dataprivacy'),\n },\n formClass: 'tool_dataprivacy\\\\form\\\\contactdpo',\n saveButtonText: getString('send', 'tool_dataprivacy'),\n returnFocus: triggerElement,\n });\n\n // Show a toast notification when the form is submitted.\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, event => {\n if (event.detail.result) {\n getString('requestsubmitted', 'tool_dataprivacy').then(addToast).catch();\n } else {\n const warningMessages = event.detail.warnings.map(warning => warning.message);\n Notification.addNotification({\n type: 'error',\n message: warningMessages.join('
')\n });\n }\n });\n\n modalForm.show();\n });\n};\n"],"file":"contactdpo.min.js"} \ No newline at end of file diff --git a/admin/tool/dataprivacy/amd/src/contactdpo.js b/admin/tool/dataprivacy/amd/src/contactdpo.js new file mode 100644 index 00000000000..50417a84db9 --- /dev/null +++ b/admin/tool/dataprivacy/amd/src/contactdpo.js @@ -0,0 +1,67 @@ +// 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 . + +/** + * Javascript module for contacting the site DPO + * + * @module tool_dataprivacy/contactdpo + * @package tool_dataprivacy + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import ModalForm from 'core_form/modalform'; +import Notification from 'core/notification'; +import {get_string as getString} from 'core/str'; +import {add as addToast} from 'core/toast'; + +const SELECTORS = { + CONTACT_DPO: '[data-action="contactdpo"]', +}; + +/** + * Initialize module + */ +export const init = () => { + const triggerElement = document.querySelector(SELECTORS.CONTACT_DPO); + + triggerElement.addEventListener('click', event => { + event.preventDefault(); + + const modalForm = new ModalForm({ + modalConfig: { + title: getString('contactdataprotectionofficer', 'tool_dataprivacy'), + }, + formClass: 'tool_dataprivacy\\form\\contactdpo', + saveButtonText: getString('send', 'tool_dataprivacy'), + returnFocus: triggerElement, + }); + + // Show a toast notification when the form is submitted. + modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, event => { + if (event.detail.result) { + getString('requestsubmitted', 'tool_dataprivacy').then(addToast).catch(); + } else { + const warningMessages = event.detail.warnings.map(warning => warning.message); + Notification.addNotification({ + type: 'error', + message: warningMessages.join('
') + }); + } + }); + + modalForm.show(); + }); +}; diff --git a/admin/tool/dataprivacy/classes/external.php b/admin/tool/dataprivacy/classes/external.php index c2ab73195dd..d9e5a411a4e 100644 --- a/admin/tool/dataprivacy/classes/external.php +++ b/admin/tool/dataprivacy/classes/external.php @@ -212,7 +212,8 @@ class external extends external_api { $warnings[] = [ 'item' => $dpo->id, 'warningcode' => 'errorsendingtodpo', - 'message' => get_string('errorsendingmessagetodpo', 'tool_dataprivacy') + 'message' => get_string('errorsendingmessagetodpo', 'tool_dataprivacy', + fullname($dpo)) ]; } } diff --git a/admin/tool/dataprivacy/classes/form/contactdpo.php b/admin/tool/dataprivacy/classes/form/contactdpo.php new file mode 100644 index 00000000000..dc172913b08 --- /dev/null +++ b/admin/tool/dataprivacy/classes/form/contactdpo.php @@ -0,0 +1,100 @@ +. + + +namespace tool_dataprivacy\form; + +use context; +use context_user; +use moodle_exception; +use moodle_url; +use core_form\dynamic_form; +use tool_dataprivacy\api; +use tool_dataprivacy\external; + +/** + * Contact DPO modal form + * + * @package tool_dataprivacy + * @copyright 2021 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class contactdpo extends dynamic_form { + + /** + * Form definition + */ + protected function definition() { + global $USER; + + $mform = $this->_form; + + $mform->addElement('static', 'replyto', get_string('replyto', 'tool_dataprivacy'), s($USER->email)); + + $mform->addElement('textarea', 'message', get_string('message', 'tool_dataprivacy'), 'cols="60" rows="8"'); + $mform->setType('message', PARAM_TEXT); + $mform->addRule('message', get_string('required'), 'required', null, 'client'); + } + + /** + * Return form context + * + * @return context + */ + protected function get_context_for_dynamic_submission(): context { + global $USER; + + return context_user::instance($USER->id); + } + + /** + * Check if current user has access to this form, otherwise throw exception + * + * @throws moodle_exception + */ + protected function check_access_for_dynamic_submission(): void { + if (!api::can_contact_dpo()) { + throw new moodle_exception('errorcontactdpodisabled', 'tool_dataprivacy'); + } + } + + /** + * Process the form submission, used if form was submitted via AJAX + * + * @return array + */ + public function process_dynamic_submission() { + return external::contact_dpo($this->get_data()->message); + } + + /** + * Load in existing data as form defaults (not applicable) + */ + public function set_data_for_dynamic_submission(): void { + return; + } + + /** + * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX + * + * @return moodle_url + */ + protected function get_page_url_for_dynamic_submission(): moodle_url { + global $USER; + + return new moodle_url('/user/profile.php', ['id' => $USER->id]); + } +} diff --git a/admin/tool/dataprivacy/classes/output/renderer.php b/admin/tool/dataprivacy/classes/output/renderer.php index 803290669a5..5d3f4cb6bd7 100644 --- a/admin/tool/dataprivacy/classes/output/renderer.php +++ b/admin/tool/dataprivacy/classes/output/renderer.php @@ -55,14 +55,11 @@ class renderer extends plugin_renderer_base { /** * Render the contact DPO link. * - * @param string $replytoemail The Reply-to email address * @return string The HTML for the link. - * @throws coding_exception */ - public function render_contact_dpo_link($replytoemail) { + public function render_contact_dpo_link() { $params = [ 'data-action' => 'contactdpo', - 'data-replytoemail' => $replytoemail, ]; return html_writer::link('#', get_string('contactdataprotectionofficer', 'tool_dataprivacy'), $params); } diff --git a/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php b/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php index 46d7af262b8..53ce8601dcf 100644 --- a/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php +++ b/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php @@ -135,6 +135,7 @@ $string['effectiveretentionperioduser'] = '{$a} (since the last time the user ac $string['emailsalutation'] = 'Dear {$a},'; $string['errorcannotrequestdeleteforself'] = 'You don\'t have permission to create deletion request for yourself.'; $string['errorcannotrequestdeleteforother'] = 'You don\'t have permission to create deletion request for this user.'; +$string['errorcontactdpodisabled'] = 'Contacting the privacy officer is disabled'; $string['errorinvalidrequestcomments'] = 'The comments field may contain plain text only.'; $string['errorinvalidrequestcreationmethod'] = 'Invalid request creation method!'; $string['errorinvalidrequeststatus'] = 'Invalid request status!'; diff --git a/admin/tool/dataprivacy/lib.php b/admin/tool/dataprivacy/lib.php index 5b5f28acebf..25091fddf8c 100644 --- a/admin/tool/dataprivacy/lib.php +++ b/admin/tool/dataprivacy/lib.php @@ -54,10 +54,12 @@ function tool_dataprivacy_myprofile_navigation(tree $tree, $user, $iscurrentuser // Contact data protection officer link. if (\tool_dataprivacy\api::can_contact_dpo() && $iscurrentuser) { $renderer = $PAGE->get_renderer('tool_dataprivacy'); - $content = $renderer->render_contact_dpo_link($USER->email); + $content = $renderer->render_contact_dpo_link(); $node = new core_user\output\myprofile\node('privacyandpolicies', 'contactdpo', null, null, null, $content); $category->add_node($node); - $PAGE->requires->js_call_amd('tool_dataprivacy/myrequestactions', 'init'); + + // Require our Javascript module to handle contact DPO interaction. + $PAGE->requires->js_call_amd('tool_dataprivacy/contactdpo', 'init'); $url = new moodle_url('/admin/tool/dataprivacy/mydatarequests.php'); $node = new core_user\output\myprofile\node('privacyandpolicies', 'datarequests', diff --git a/admin/tool/dataprivacy/templates/contact_dpo.mustache b/admin/tool/dataprivacy/templates/contact_dpo.mustache deleted file mode 100644 index d3a0e868392..00000000000 --- a/admin/tool/dataprivacy/templates/contact_dpo.mustache +++ /dev/null @@ -1,58 +0,0 @@ -{{! - 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 . -}} -{{! - @template tool_dataprivacy/contact_dpo - - The purpose of this template is to enable the user to contact the site's DPO via email. - - Classes required for JS: - * none - - Data attributes required for JS: - * none - - Context variables required for this template: - * userid int The user's ID. - * email string The user's email address. - - Example context (json): - { - "userid": 1, - "replytoemail": "martha@example.com" - } -}} -
-
- -
{{replytoemail}}
-
-
- -
- - -
-
-
- diff --git a/admin/tool/dataprivacy/tests/behat/contact_privacy_officer.feature b/admin/tool/dataprivacy/tests/behat/contact_privacy_officer.feature index 528d1f69325..28f5de84d12 100644 --- a/admin/tool/dataprivacy/tests/behat/contact_privacy_officer.feature +++ b/admin/tool/dataprivacy/tests/behat/contact_privacy_officer.feature @@ -8,19 +8,21 @@ Feature: Contact the privacy officer Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | s1@example.com | - And I log in as "admin" - And I set the following administration settings values: - | contactdataprotectionofficer | 1 | - And I log out @javascript Scenario: Contacting the privacy officer - Given I log in as "student1" + Given the following config values are set as admin: + | contactdataprotectionofficer | 1 | tool_dataprivacy | + When I log in as "student1" And I follow "Profile" in the user menu - And I should see "Contact the privacy officer" And I click on "Contact the privacy officer" "link" And I set the field "Message" to "Hello DPO!" And I click on "Send" "button" in the "Contact the privacy officer" "dialogue" - And I should see "Your request has been submitted to the privacy officer" + Then I should see "Your request has been submitted to the privacy officer" And I click on "Data requests" "link" And I should see "Hello DPO!" in the "General inquiry" "table_row" + + Scenario: Contacting the privacy officer when not enabled + When I log in as "student1" + And I follow "Profile" in the user menu + Then "Contact the privacy officer" "link" should not exist From 6846ff49f1b2b08fd3528477aa55263cc7f12dc7 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Tue, 2 Mar 2021 16:01:51 +0000 Subject: [PATCH 2/2] MDL-71090 tool_dataprivacy: convert cancellation of requests to ES6. --- .../amd/build/myrequestactions.min.js | 2 +- .../amd/build/myrequestactions.min.js.map | 2 +- .../dataprivacy/amd/src/myrequestactions.js | 245 ++++-------------- .../tests/behat/my_data_requests.feature | 26 ++ 4 files changed, 74 insertions(+), 201 deletions(-) create mode 100644 admin/tool/dataprivacy/tests/behat/my_data_requests.feature diff --git a/admin/tool/dataprivacy/amd/build/myrequestactions.min.js b/admin/tool/dataprivacy/amd/build/myrequestactions.min.js index a6ba75991d1..b1b7317c48a 100644 --- a/admin/tool/dataprivacy/amd/build/myrequestactions.min.js +++ b/admin/tool/dataprivacy/amd/build/myrequestactions.min.js @@ -1,2 +1,2 @@ -define ("tool_dataprivacy/myrequestactions",["jquery","core/ajax","core/notification","core/str","core/modal_factory","core/modal_events","core/templates","core/pending"],function(a,b,c,d,e,f,g,h){var j={CANCEL_REQUEST:"[data-action=\"cancel\"]",CONTACT_DPO:"[data-action=\"contactdpo\"]"},k=function(){this.registerEvents()};k.prototype.registerEvents=function(){a(j.CANCEL_REQUEST).click(function(g){g.preventDefault();var h=a(this).data("requestid");d.get_strings([{key:"cancelrequest",component:"tool_dataprivacy"},{key:"cancelrequestconfirmation",component:"tool_dataprivacy"}]).then(function(a){var d=a[0],g=a[1];return e.create({title:d,body:g,type:e.types.SAVE_CANCEL}).then(function(a){a.setSaveButtonText(d);a.getRoot().on(f.save,function(){b.call([{methodname:"tool_dataprivacy_cancel_data_request",args:{requestid:h}}])[0].done(function(a){if(a.result){window.location.reload()}else{c.addNotification({message:a.warnings[0].message,type:"error"})}}).fail(c.exception)});a.getRoot().on(f.hidden,function(){a.destroy()});return a})}).done(function(a){a.show()}).fail(c.exception)});a(j.CONTACT_DPO).click(function(b){var j=new h("dataprivacy/crud:initModal:contactdpo");b.preventDefault();var k=a(this).data("replytoemail"),l="";d.get_strings([{key:"contactdataprotectionofficer",component:"tool_dataprivacy"},{key:"send",component:"tool_dataprivacy"}]).then(function(a){var b=a[0];l=a[1];return e.create({title:b,body:g.render("tool_dataprivacy/contact_dpo",{replytoemail:k}),type:e.types.SAVE_CANCEL,large:!0})}).then(function(b){b.setSaveButtonText(l);b.show();b.getRoot().on(f.save,function(b){var c=a("#message").val().trim();if(0===c.length){b.preventDefault();a("[data-region=\"messageinput\"]").addClass("has-danger notifyproblem");a("#id_error_message").removeAttr("hidden")}else{i(c)}});b.getRoot().on(f.hidden,function(){b.destroy()})}).then(j.resolve).catch(c.exception)})};function i(a){var e="success";b.call([{methodname:"tool_dataprivacy_contact_dpo",args:{message:a}}])[0].then(function(a){if(a.result){return d.get_string("requestsubmitted","tool_dataprivacy")}e="error";return a.warnings.join("
")}).done(function(a){c.addNotification({message:a,type:e})}).fail(c.exception)}return{init:function init(){return new k}}}); +define ("tool_dataprivacy/myrequestactions",["exports","core/ajax","core/notification","core/pending","core/str"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=f(b);c=f(c);d=f(d);function f(a){return a&&a.__esModule?a:{default:a}}function g(a,b){return m(a)||l(a,b)||j(a,b)||h()}function h(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function j(a,b){if(!a)return;if("string"==typeof a)return k(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return k(a,b)}function k(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * AMD module to enable users to manage their own data requests.\n *\n * @module tool_dataprivacy/myrequestactions\n * @package tool_dataprivacy\n * @copyright 2018 Jun Pataleta\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/ajax',\n 'core/notification',\n 'core/str',\n 'core/modal_factory',\n 'core/modal_events',\n 'core/templates',\n 'core/pending'],\nfunction($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Pending) {\n\n /**\n * List of action selectors.\n *\n * @type {{CANCEL_REQUEST: string}}\n * @type {{CONTACT_DPO: string}}\n */\n var ACTIONS = {\n CANCEL_REQUEST: '[data-action=\"cancel\"]',\n CONTACT_DPO: '[data-action=\"contactdpo\"]',\n };\n\n /**\n * MyRequestActions class.\n */\n var MyRequestActions = function() {\n this.registerEvents();\n };\n\n /**\n * Register event listeners.\n */\n MyRequestActions.prototype.registerEvents = function() {\n $(ACTIONS.CANCEL_REQUEST).click(function(e) {\n e.preventDefault();\n\n var requestId = $(this).data('requestid');\n var stringkeys = [\n {\n key: 'cancelrequest',\n component: 'tool_dataprivacy'\n },\n {\n key: 'cancelrequestconfirmation',\n component: 'tool_dataprivacy'\n }\n ];\n\n Str.get_strings(stringkeys).then(function(langStrings) {\n var title = langStrings[0];\n var confirmMessage = langStrings[1];\n return ModalFactory.create({\n title: title,\n body: confirmMessage,\n type: ModalFactory.types.SAVE_CANCEL\n }).then(function(modal) {\n modal.setSaveButtonText(title);\n\n // Handle save event.\n modal.getRoot().on(ModalEvents.save, function() {\n // Cancel the request.\n var params = {\n 'requestid': requestId\n };\n\n var request = {\n methodname: 'tool_dataprivacy_cancel_data_request',\n args: params\n };\n\n Ajax.call([request])[0].done(function(data) {\n if (data.result) {\n window.location.reload();\n } else {\n Notification.addNotification({\n message: data.warnings[0].message,\n type: 'error'\n });\n }\n }).fail(Notification.exception);\n });\n\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Destroy when hidden.\n modal.destroy();\n });\n\n return modal;\n });\n }).done(function(modal) {\n // Show the modal!\n modal.show();\n\n }).fail(Notification.exception);\n });\n\n $(ACTIONS.CONTACT_DPO).click(function(e) {\n var pendingPromise = new Pending('dataprivacy/crud:initModal:contactdpo');\n e.preventDefault();\n\n var replyToEmail = $(this).data('replytoemail');\n\n var keys = [\n {\n key: 'contactdataprotectionofficer',\n component: 'tool_dataprivacy'\n },\n {\n key: 'send',\n component: 'tool_dataprivacy'\n },\n ];\n\n var sendButtonText = '';\n Str.get_strings(keys).then(function(langStrings) {\n var modalTitle = langStrings[0];\n sendButtonText = langStrings[1];\n var context = {\n 'replytoemail': replyToEmail\n };\n return ModalFactory.create({\n title: modalTitle,\n body: Templates.render('tool_dataprivacy/contact_dpo', context),\n type: ModalFactory.types.SAVE_CANCEL,\n large: true\n });\n }).then(function(modal) {\n modal.setSaveButtonText(sendButtonText);\n\n // Show the modal!\n modal.show();\n\n // Handle send event.\n modal.getRoot().on(ModalEvents.save, function(e) {\n var message = $('#message').val().trim();\n if (message.length === 0) {\n e.preventDefault();\n // Show validation error when the message is empty.\n $('[data-region=\"messageinput\"]').addClass('has-danger notifyproblem');\n $('#id_error_message').removeAttr('hidden');\n } else {\n // Send the message.\n sendMessageToDPO(message);\n }\n });\n\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Destroy when hidden.\n modal.destroy();\n });\n\n return;\n }).then(pendingPromise.resolve)\n .catch(Notification.exception);\n });\n };\n\n /**\n * Send message to the Data Protection Officer.\n *\n * @param {String} message The message to send.\n */\n function sendMessageToDPO(message) {\n var request = {\n methodname: 'tool_dataprivacy_contact_dpo',\n args: {\n message: message\n }\n };\n\n var requestType = 'success';\n Ajax.call([request])[0].then(function(data) {\n if (data.result) {\n return Str.get_string('requestsubmitted', 'tool_dataprivacy');\n }\n requestType = 'error';\n return data.warnings.join('
');\n\n }).done(function(message) {\n Notification.addNotification({\n message: message,\n type: requestType\n });\n\n }).fail(Notification.exception);\n }\n\n return /** @alias module:tool_dataprivacy/myrequestactions */ {\n // Public variables and functions.\n\n /**\n * Initialise the unified user filter.\n *\n * @method init\n * @return {MyRequestActions}\n */\n 'init': function() {\n return new MyRequestActions();\n }\n };\n});\n"],"file":"myrequestactions.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/myrequestactions.js"],"names":["SELECTORS","CANCEL_REQUEST","init","document","addEventListener","event","triggerElement","target","closest","preventDefault","key","component","then","cancelRequest","cancelConfirm","Notification","confirm","pendingPromise","Pending","request","methodname","args","requestid","dataset","Ajax","call","response","result","window","location","reload","addNotification","type","message","warnings","resolve","catch","exception"],"mappings":"kNAwBA,OACA,OACA,O,khCAGMA,CAAAA,CAAS,CAAG,CACdC,cAAc,CAAE,0CADF,C,QAOE,QAAPC,CAAAA,IAAO,EAAM,CACtBC,QAAQ,CAACC,gBAAT,CAA0B,OAA1B,CAAmC,SAAAC,CAAK,CAAI,CACxC,GAAMC,CAAAA,CAAc,CAAGD,CAAK,CAACE,MAAN,CAAaC,OAAb,CAAqBR,CAAS,CAACC,cAA/B,CAAvB,CACA,GAAuB,IAAnB,GAAAK,CAAJ,CAA6B,CACzB,MACH,CAEDD,CAAK,CAACI,cAAN,GAOA,kBALwB,CACpB,CAACC,GAAG,CAAE,eAAN,CAAuBC,SAAS,CAAE,kBAAlC,CADoB,CAEpB,CAACD,GAAG,CAAE,2BAAN,CAAmCC,SAAS,CAAE,kBAA9C,CAFoB,CAKxB,EAA4BC,IAA5B,CAAiC,WAAoC,cAAlCC,CAAkC,MAAnBC,CAAmB,MACjE,MAAOC,WAAaC,OAAb,CAAqBH,CAArB,CAAoCC,CAApC,CAAmDD,CAAnD,CAAkE,IAAlE,CAAwE,UAAM,IAC3EI,CAAAA,CAAc,CAAG,GAAIC,UAAJ,CAAY,gCAAZ,CAD0D,CAE3EC,CAAO,CAAG,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CAACC,SAAS,CAAEhB,CAAc,CAACiB,OAAf,CAAuBD,SAAnC,CAFM,CAFiE,CAOjFE,UAAKC,IAAL,CAAU,CAACN,CAAD,CAAV,EAAqB,CAArB,EAAwBP,IAAxB,CAA6B,SAAAc,CAAQ,CAAI,CACrC,GAAIA,CAAQ,CAACC,MAAb,CAAqB,CACjBC,MAAM,CAACC,QAAP,CAAgBC,MAAhB,EACH,CAFD,IAEO,CACHf,UAAagB,eAAb,CAA6B,CACzBC,IAAI,CAAE,OADmB,CAEzBC,OAAO,CAAEP,CAAQ,CAACQ,QAAT,CAAkB,CAAlB,EAAqBD,OAFL,CAA7B,CAIH,CACD,MAAOhB,CAAAA,CAAc,CAACkB,OAAf,EACV,CAVD,EAUGC,KAVH,CAUSrB,UAAasB,SAVtB,CAWH,CAlBM,CAmBV,CApBD,EAoBGD,KApBH,EAqBH,CAlCD,CAmCH,C","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 .\n\n/**\n * AMD module to enable users to manage their own data requests.\n *\n * @module tool_dataprivacy/myrequestactions\n * @package tool_dataprivacy\n * @copyright 2018 Jun Pataleta\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_strings as getStrings} from 'core/str';\n\nconst SELECTORS = {\n CANCEL_REQUEST: '[data-action=\"cancel\"][data-requestid]',\n};\n\n/**\n * Initialize module\n */\nexport const init = () => {\n document.addEventListener('click', event => {\n const triggerElement = event.target.closest(SELECTORS.CANCEL_REQUEST);\n if (triggerElement === null) {\n return;\n }\n\n event.preventDefault();\n\n const requiredStrings = [\n {key: 'cancelrequest', component: 'tool_dataprivacy'},\n {key: 'cancelrequestconfirmation', component: 'tool_dataprivacy'},\n ];\n\n getStrings(requiredStrings).then(([cancelRequest, cancelConfirm]) => {\n return Notification.confirm(cancelRequest, cancelConfirm, cancelRequest, null, () => {\n const pendingPromise = new Pending('tool/dataprivacy:cancelRequest');\n const request = {\n methodname: 'tool_dataprivacy_cancel_data_request',\n args: {requestid: triggerElement.dataset.requestid}\n };\n\n Ajax.call([request])[0].then(response => {\n if (response.result) {\n window.location.reload();\n } else {\n Notification.addNotification({\n type: 'error',\n message: response.warnings[0].message\n });\n }\n return pendingPromise.resolve();\n }).catch(Notification.exception);\n });\n }).catch();\n });\n};\n"],"file":"myrequestactions.min.js"} \ No newline at end of file diff --git a/admin/tool/dataprivacy/amd/src/myrequestactions.js b/admin/tool/dataprivacy/amd/src/myrequestactions.js index 54f94fb3e01..b75b3869697 100644 --- a/admin/tool/dataprivacy/amd/src/myrequestactions.js +++ b/admin/tool/dataprivacy/amd/src/myrequestactions.js @@ -21,206 +21,53 @@ * @copyright 2018 Jun Pataleta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define([ - 'jquery', - 'core/ajax', - 'core/notification', - 'core/str', - 'core/modal_factory', - 'core/modal_events', - 'core/templates', - 'core/pending'], -function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Pending) { - /** - * List of action selectors. - * - * @type {{CANCEL_REQUEST: string}} - * @type {{CONTACT_DPO: string}} - */ - var ACTIONS = { - CANCEL_REQUEST: '[data-action="cancel"]', - CONTACT_DPO: '[data-action="contactdpo"]', - }; +import Ajax from 'core/ajax'; +import Notification from 'core/notification'; +import Pending from 'core/pending'; +import {get_strings as getStrings} from 'core/str'; - /** - * MyRequestActions class. - */ - var MyRequestActions = function() { - this.registerEvents(); - }; +const SELECTORS = { + CANCEL_REQUEST: '[data-action="cancel"][data-requestid]', +}; - /** - * Register event listeners. - */ - MyRequestActions.prototype.registerEvents = function() { - $(ACTIONS.CANCEL_REQUEST).click(function(e) { - e.preventDefault(); - - var requestId = $(this).data('requestid'); - var stringkeys = [ - { - key: 'cancelrequest', - component: 'tool_dataprivacy' - }, - { - key: 'cancelrequestconfirmation', - component: 'tool_dataprivacy' - } - ]; - - Str.get_strings(stringkeys).then(function(langStrings) { - var title = langStrings[0]; - var confirmMessage = langStrings[1]; - return ModalFactory.create({ - title: title, - body: confirmMessage, - type: ModalFactory.types.SAVE_CANCEL - }).then(function(modal) { - modal.setSaveButtonText(title); - - // Handle save event. - modal.getRoot().on(ModalEvents.save, function() { - // Cancel the request. - var params = { - 'requestid': requestId - }; - - var request = { - methodname: 'tool_dataprivacy_cancel_data_request', - args: params - }; - - Ajax.call([request])[0].done(function(data) { - if (data.result) { - window.location.reload(); - } else { - Notification.addNotification({ - message: data.warnings[0].message, - type: 'error' - }); - } - }).fail(Notification.exception); - }); - - // Handle hidden event. - modal.getRoot().on(ModalEvents.hidden, function() { - // Destroy when hidden. - modal.destroy(); - }); - - return modal; - }); - }).done(function(modal) { - // Show the modal! - modal.show(); - - }).fail(Notification.exception); - }); - - $(ACTIONS.CONTACT_DPO).click(function(e) { - var pendingPromise = new Pending('dataprivacy/crud:initModal:contactdpo'); - e.preventDefault(); - - var replyToEmail = $(this).data('replytoemail'); - - var keys = [ - { - key: 'contactdataprotectionofficer', - component: 'tool_dataprivacy' - }, - { - key: 'send', - component: 'tool_dataprivacy' - }, - ]; - - var sendButtonText = ''; - Str.get_strings(keys).then(function(langStrings) { - var modalTitle = langStrings[0]; - sendButtonText = langStrings[1]; - var context = { - 'replytoemail': replyToEmail - }; - return ModalFactory.create({ - title: modalTitle, - body: Templates.render('tool_dataprivacy/contact_dpo', context), - type: ModalFactory.types.SAVE_CANCEL, - large: true - }); - }).then(function(modal) { - modal.setSaveButtonText(sendButtonText); - - // Show the modal! - modal.show(); - - // Handle send event. - modal.getRoot().on(ModalEvents.save, function(e) { - var message = $('#message').val().trim(); - if (message.length === 0) { - e.preventDefault(); - // Show validation error when the message is empty. - $('[data-region="messageinput"]').addClass('has-danger notifyproblem'); - $('#id_error_message').removeAttr('hidden'); - } else { - // Send the message. - sendMessageToDPO(message); - } - }); - - // Handle hidden event. - modal.getRoot().on(ModalEvents.hidden, function() { - // Destroy when hidden. - modal.destroy(); - }); - - return; - }).then(pendingPromise.resolve) - .catch(Notification.exception); - }); - }; - - /** - * Send message to the Data Protection Officer. - * - * @param {String} message The message to send. - */ - function sendMessageToDPO(message) { - var request = { - methodname: 'tool_dataprivacy_contact_dpo', - args: { - message: message - } - }; - - var requestType = 'success'; - Ajax.call([request])[0].then(function(data) { - if (data.result) { - return Str.get_string('requestsubmitted', 'tool_dataprivacy'); - } - requestType = 'error'; - return data.warnings.join('
'); - - }).done(function(message) { - Notification.addNotification({ - message: message, - type: requestType - }); - - }).fail(Notification.exception); - } - - return /** @alias module:tool_dataprivacy/myrequestactions */ { - // Public variables and functions. - - /** - * Initialise the unified user filter. - * - * @method init - * @return {MyRequestActions} - */ - 'init': function() { - return new MyRequestActions(); +/** + * Initialize module + */ +export const init = () => { + document.addEventListener('click', event => { + const triggerElement = event.target.closest(SELECTORS.CANCEL_REQUEST); + if (triggerElement === null) { + return; } - }; -}); + + event.preventDefault(); + + const requiredStrings = [ + {key: 'cancelrequest', component: 'tool_dataprivacy'}, + {key: 'cancelrequestconfirmation', component: 'tool_dataprivacy'}, + ]; + + getStrings(requiredStrings).then(([cancelRequest, cancelConfirm]) => { + return Notification.confirm(cancelRequest, cancelConfirm, cancelRequest, null, () => { + const pendingPromise = new Pending('tool/dataprivacy:cancelRequest'); + const request = { + methodname: 'tool_dataprivacy_cancel_data_request', + args: {requestid: triggerElement.dataset.requestid} + }; + + Ajax.call([request])[0].then(response => { + if (response.result) { + window.location.reload(); + } else { + Notification.addNotification({ + type: 'error', + message: response.warnings[0].message + }); + } + return pendingPromise.resolve(); + }).catch(Notification.exception); + }); + }).catch(); + }); +}; diff --git a/admin/tool/dataprivacy/tests/behat/my_data_requests.feature b/admin/tool/dataprivacy/tests/behat/my_data_requests.feature new file mode 100644 index 00000000000..15de9cd732e --- /dev/null +++ b/admin/tool/dataprivacy/tests/behat/my_data_requests.feature @@ -0,0 +1,26 @@ +@tool @tool_dataprivacy +Feature: Manage my own data requests + In order to manage my own data requests + As a user + I need to be able to view and cancel all my data requests + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | 1 | s1@example.com | + And the following config values are set as admin: + | contactdataprotectionofficer | 1 | tool_dataprivacy | + + @javascript + Scenario: Cancel my own data request + Given I log in as "student1" + And I follow "Profile" in the user menu + And I click on "Contact the privacy officer" "link" + And I set the field "Message" to "Hello DPO!" + And I click on "Send" "button" in the "Contact the privacy officer" "dialogue" + And I should see "Your request has been submitted to the privacy officer" + When I click on "Data requests" "link" + And I open the action menu in "Hello DPO!" "table_row" + And I choose "Cancel" in the open action menu + And I click on "Cancel request" "button" in the "Cancel request" "dialogue" + Then I should see "Cancelled" in the "Hello DPO!" "table_row"