From bca6b06a1c9be394c59ee70be3bb2f9e4730f20b Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Sun, 1 Aug 2021 21:18:51 +0700 Subject: [PATCH] MDL-71953 calendar: Accessibility improvement for manage subscription - Create new web services for manage subscription (Update calendar subscription) - Modified delete subscription feature to use Web service. - Midified update subscription feature to use in-place editbale - Delete subscription feature now have a confirmation box before processing. - Fixed some accessibility issues - Used 'Delete' instead of 'Remove' for deleting subscriptions --- .../amd/build/manage_subscriptions.min.js | 2 + .../amd/build/manage_subscriptions.min.js.map | 1 + calendar/amd/build/repository.min.js | 2 +- calendar/amd/build/repository.min.js.map | 2 +- calendar/amd/build/selectors.min.js | 2 +- calendar/amd/build/selectors.min.js.map | 2 +- calendar/amd/src/manage_subscriptions.js | 145 ++++++++++++++++++ calendar/amd/src/repository.js | 17 ++ calendar/amd/src/selectors.js | 1 + .../classes/external/event_exporter_base.php | 25 --- .../classes/external/subscription/delete.php | 104 +++++++++++++ .../output/refreshintervalcollection.php | 54 +++++++ calendar/import.php | 23 ++- calendar/lib.php | 118 +++++++------- calendar/managesubscriptions.php | 20 --- calendar/renderer.php | 77 +++++----- .../subscription_update_result.mustache | 61 ++++++++ calendar/tests/behat/calendar_import.feature | 4 +- calendar/tests/lib_test.php | 20 ++- calendar/upgrade.txt | 4 + lang/en/calendar.php | 3 +- lib/classes/task/calendar_cron_task.php | 11 +- lib/db/services.php | 6 + lib/deprecatedlib.php | 121 +++++++++++++++ lib/tests/calendar_cron_task_test.php | 6 +- lib/tests/upgradelib_test.php | 2 +- version.php | 2 +- 27 files changed, 670 insertions(+), 165 deletions(-) create mode 100644 calendar/amd/build/manage_subscriptions.min.js create mode 100644 calendar/amd/build/manage_subscriptions.min.js.map create mode 100644 calendar/amd/src/manage_subscriptions.js create mode 100644 calendar/classes/external/subscription/delete.php create mode 100644 calendar/classes/output/refreshintervalcollection.php create mode 100644 calendar/templates/subscription_update_result.mustache diff --git a/calendar/amd/build/manage_subscriptions.min.js b/calendar/amd/build/manage_subscriptions.min.js new file mode 100644 index 00000000000..e758241bcba --- /dev/null +++ b/calendar/amd/build/manage_subscriptions.min.js @@ -0,0 +1,2 @@ +function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_calendar/manage_subscriptions",["exports","core_calendar/selectors","core_calendar/repository","core/modal_factory","core/modal_events","core/notification","core/prefetch","core/str","core/local/inplace_editable/events"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=k(b);c=k(c);d=k(d);e=k(e);g=function(a){return a&&a.__esModule?a:{default:a}}(g);function j(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;j=function(){return a};return a}function k(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=j();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function l(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function m(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){l(h,d,e,f,g,"next",a)}function g(a){l(h,d,e,f,g,"throw",a)}f(void 0)})}}var n=function(a){return parseInt(a.closest("tr").dataset.subid)},o=function(a){return a.closest("tr").dataset.subname},p=function(a){return document.querySelector("tr[data-subid=\"".concat(a,"\"]"))},q=function(a,b){var c=o(a);return d.create({type:d.types.SAVE_CANCEL,title:(0,h.get_string)("confirmation","admin"),body:(0,h.get_string)(b,"calendar",c),buttons:{save:(0,h.get_string)("yes")}}).then(function(b){b.getRoot().on(e.hidden,function(){a.focus()});b.show();return b})},r=function(){var a=m(regeneratorRuntime.mark(function a(b,c){var d,e,g;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=o(b);if(!c.status){a.next=7;break}a.next=4;return(0,h.get_string)("subscriptionremoved","calendar",d);case 4:a.t0=a.sent;a.next=8;break;case 7:a.t0=c.warnings[0].message;case 8:e=a.t0;g=c.status?"info":"error";return a.abrupt("return",(0,f.addNotification)({message:e,type:g}));case 11:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),s=function(){document.addEventListener("click",function(a){var d=a.target.closest(b.actions.deleteSubscription);if(d){a.preventDefault();var g=q(d,"confirmsubscriptiondelete");g.then(function(a){a.getRoot().on(e.save,function(){var a=n(d);c.deleteSubscription(a).then(function(b){var c=r(d,b);return c.then(function(){var b=p(a);return b.remove()})}).catch(f.displayException)});return a}).catch(f.displayException)}});document.addEventListener(i.eventTypes.elementUpdated,function(a){var b=a.target;if("core_calendar"==b.getAttribute("data-component")){(0,f.fetchNotifications)()}})},t=function(){g.default.prefetchStrings("moodle",["yes"]);g.default.prefetchStrings("core_admin",["confirmation"]);g.default.prefetchStrings("core_calendar",["confirmsubscriptiondelete","subscriptionremoved"]);s()};a.init=t}); +//# sourceMappingURL=manage_subscriptions.min.js.map diff --git a/calendar/amd/build/manage_subscriptions.min.js.map b/calendar/amd/build/manage_subscriptions.min.js.map new file mode 100644 index 00000000000..dc3d8601192 --- /dev/null +++ b/calendar/amd/build/manage_subscriptions.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/manage_subscriptions.js"],"names":["getSubscriptionId","element","parseInt","closest","dataset","subid","getSubscriptionName","subname","getSubscriptionRow","subscriptionId","document","querySelector","createModal","messageCode","subscriptionName","Modal","create","type","types","SAVE_CANCEL","title","body","buttons","save","then","modal","getRoot","on","ModalEvents","hidden","focus","show","responseHandlerForDelete","data","status","warnings","message","registerEventListeners","addEventListener","e","deleteAction","target","CalendarSelectors","actions","deleteSubscription","preventDefault","modalPromise","CalendarRepository","response","subscriptionRow","remove","catch","displayException","eventTypes","elementUpdated","inplaceEditable","getAttribute","init","Prefetch","prefetchStrings"],"mappings":"snBAwBA,OACA,OACA,OACA,OAEA,uD,w2BAUMA,CAAAA,CAAiB,CAAG,SAAAC,CAAO,CAAI,CACjC,MAAOC,CAAAA,QAAQ,CAACD,CAAO,CAACE,OAAR,CAAgB,IAAhB,EAAsBC,OAAtB,CAA8BC,KAA/B,CAClB,C,CAQKC,CAAmB,CAAG,SAAAL,CAAO,CAAI,CACnC,MAAOA,CAAAA,CAAO,CAACE,OAAR,CAAgB,IAAhB,EAAsBC,OAAtB,CAA8BG,OACxC,C,CAQKC,CAAkB,CAAG,SAAAC,CAAc,CAAI,CACzC,MAAOC,CAAAA,QAAQ,CAACC,aAAT,2BAAyCF,CAAzC,QACV,C,CASKG,CAAW,CAAG,SAACX,CAAD,CAAUY,CAAV,CAA0B,CAC1C,GAAMC,CAAAA,CAAgB,CAAGR,CAAmB,CAACL,CAAD,CAA5C,CACA,MAAOc,CAAAA,CAAK,CAACC,MAAN,CAAa,CAChBC,IAAI,CAAEF,CAAK,CAACG,KAAN,CAAYC,WADF,CAEhBC,KAAK,CAAE,iBAAU,cAAV,CAA0B,OAA1B,CAFS,CAGhBC,IAAI,CAAE,iBAAUR,CAAV,CAAuB,UAAvB,CAAmCC,CAAnC,CAHU,CAIhBQ,OAAO,CAAE,CACLC,IAAI,CAAE,iBAAU,KAAV,CADD,CAJO,CAAb,EAOJC,IAPI,CAOC,SAAAC,CAAK,CAAI,CACbA,CAAK,CAACC,OAAN,GAAgBC,EAAhB,CAAmBC,CAAW,CAACC,MAA/B,CAAuC,UAAM,CACzC5B,CAAO,CAAC6B,KAAR,EACH,CAFD,EAGAL,CAAK,CAACM,IAAN,GACA,MAAON,CAAAA,CACV,CAbM,CAcV,C,CASKO,CAAwB,4CAAG,WAAM/B,CAAN,CAAegC,CAAf,6FACvBnB,CADuB,CACJR,CAAmB,CAACL,CAAD,CADf,KAEbgC,CAAI,CAACC,MAFQ,gCAEO,iBAAU,qBAAV,CAAiC,UAAjC,CAA6CpB,CAA7C,CAFP,+CAEwEmB,CAAI,CAACE,QAAL,CAAc,CAAd,EAAiBC,OAFzF,QAEvBA,CAFuB,MAGvBnB,CAHuB,CAGhBgB,CAAI,CAACC,MAAL,CAAc,MAAd,CAAuB,OAHP,0BAItB,sBAAgB,CAACE,OAAO,CAAPA,CAAD,CAAUnB,IAAI,CAAJA,CAAV,CAAhB,CAJsB,2CAAH,uD,CAUxBoB,CAAsB,CAAG,UAAM,CACjC3B,QAAQ,CAAC4B,gBAAT,CAA0B,OAA1B,CAAmC,SAAAC,CAAC,CAAI,CACpC,GAAMC,CAAAA,CAAY,CAAGD,CAAC,CAACE,MAAF,CAAStC,OAAT,CAAiBuC,CAAiB,CAACC,OAAlB,CAA0BC,kBAA3C,CAArB,CACA,GAAIJ,CAAJ,CAAkB,CACdD,CAAC,CAACM,cAAF,GACA,GAAMC,CAAAA,CAAY,CAAGlC,CAAW,CAAC4B,CAAD,CAAe,2BAAf,CAAhC,CACAM,CAAY,CAACtB,IAAb,CAAkB,SAAAC,CAAK,CAAI,CACvBA,CAAK,CAACC,OAAN,GAAgBC,EAAhB,CAAmBC,CAAW,CAACL,IAA/B,CAAqC,UAAM,CACvC,GAAMd,CAAAA,CAAc,CAAGT,CAAiB,CAACwC,CAAD,CAAxC,CACAO,CAAkB,CAACH,kBAAnB,CAAsCnC,CAAtC,EAAsDe,IAAtD,CAA2D,SAAAS,CAAI,CAAI,CAC/D,GAAMe,CAAAA,CAAQ,CAAGhB,CAAwB,CAACQ,CAAD,CAAeP,CAAf,CAAzC,CACA,MAAOe,CAAAA,CAAQ,CAACxB,IAAT,CAAc,UAAM,CACvB,GAAMyB,CAAAA,CAAe,CAAGzC,CAAkB,CAACC,CAAD,CAA1C,CACA,MAAOwC,CAAAA,CAAe,CAACC,MAAhB,EACV,CAHM,CAIV,CAND,EAMGC,KANH,CAMSC,kBANT,CAOH,CATD,EAWA,MAAO3B,CAAAA,CACV,CAbD,EAaG0B,KAbH,CAaSC,kBAbT,CAcH,CACJ,CApBD,EAsBA1C,QAAQ,CAAC4B,gBAAT,CAA0Be,aAAWC,cAArC,CAAqD,SAAAf,CAAC,CAAI,CACtD,GAAMgB,CAAAA,CAAe,CAAGhB,CAAC,CAACE,MAA1B,CACA,GAAsD,eAAlD,EAAAc,CAAe,CAACC,YAAhB,CAA6B,gBAA7B,CAAJ,CAAuE,CACnE,0BACH,CACJ,CALD,CAMH,C,CAKYC,CAAI,CAAG,UAAM,CACtBC,UAASC,eAAT,CAAyB,QAAzB,CAAmC,CAAC,KAAD,CAAnC,EACAD,UAASC,eAAT,CAAyB,YAAzB,CAAuC,CAAC,cAAD,CAAvC,EACAD,UAASC,eAAT,CAAyB,eAAzB,CAA0C,CAAC,2BAAD,CAA8B,qBAA9B,CAA1C,EACAtB,CAAsB,EACzB,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 * A module to handle Delete/Update operations of the manage subscription page.\n *\n * @module core_calendar/manage_subscriptions\n * @copyright 2021 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.0\n */\n\nimport * as CalendarSelectors from 'core_calendar/selectors';\nimport * as CalendarRepository from 'core_calendar/repository';\nimport * as Modal from 'core/modal_factory';\nimport * as ModalEvents from 'core/modal_events';\nimport {displayException, addNotification, fetchNotifications} from 'core/notification';\nimport Prefetch from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport {eventTypes} from 'core/local/inplace_editable/events';\n\n/**\n * Get subscription id for given element.\n *\n * @param {HTMLElement} element update/delete link\n * @return {Number}\n */\nconst getSubscriptionId = element => {\n return parseInt(element.closest('tr').dataset.subid);\n};\n\n/**\n * Get subscription name for given element.\n *\n * @param {HTMLElement} element update/delete link\n * @return {String}\n */\nconst getSubscriptionName = element => {\n return element.closest('tr').dataset.subname;\n};\n\n/**\n * Get subscription table row for subscription id.\n *\n * @param {string} subscriptionId Subscription id\n * @return {Element}\n */\nconst getSubscriptionRow = subscriptionId => {\n return document.querySelector(`tr[data-subid=\"${subscriptionId}\"]`);\n};\n\n/**\n * Create modal.\n *\n * @param {HTMLElement} element\n * @param {string} messageCode Message code.\n * @return {promise} Promise for modal\n */\nconst createModal = (element, messageCode) => {\n const subscriptionName = getSubscriptionName(element);\n return Modal.create({\n type: Modal.types.SAVE_CANCEL,\n title: getString('confirmation', 'admin'),\n body: getString(messageCode, 'calendar', subscriptionName),\n buttons: {\n save: getString('yes')\n },\n }).then(modal => {\n modal.getRoot().on(ModalEvents.hidden, () => {\n element.focus();\n });\n modal.show();\n return modal;\n });\n};\n\n/**\n * Response handler for delete action.\n *\n * @param {HTMLElement} element\n * @param {Object} data\n * @return {Promise}\n */\nconst responseHandlerForDelete = async(element, data) => {\n const subscriptionName = getSubscriptionName(element);\n const message = data.status ? await getString('subscriptionremoved', 'calendar', subscriptionName) : data.warnings[0].message;\n const type = data.status ? 'info' : 'error';\n return addNotification({message, type});\n};\n\n/**\n * Register events for update/delete links.\n */\nconst registerEventListeners = () => {\n document.addEventListener('click', e => {\n const deleteAction = e.target.closest(CalendarSelectors.actions.deleteSubscription);\n if (deleteAction) {\n e.preventDefault();\n const modalPromise = createModal(deleteAction, 'confirmsubscriptiondelete');\n modalPromise.then(modal => {\n modal.getRoot().on(ModalEvents.save, () => {\n const subscriptionId = getSubscriptionId(deleteAction);\n CalendarRepository.deleteSubscription(subscriptionId).then(data => {\n const response = responseHandlerForDelete(deleteAction, data);\n return response.then(() => {\n const subscriptionRow = getSubscriptionRow(subscriptionId);\n return subscriptionRow.remove();\n });\n }).catch(displayException);\n });\n\n return modal;\n }).catch(displayException);\n }\n });\n\n document.addEventListener(eventTypes.elementUpdated, e => {\n const inplaceEditable = e.target;\n if (inplaceEditable.getAttribute('data-component') == 'core_calendar') {\n fetchNotifications();\n }\n });\n};\n\n/**\n * Initialises.\n */\nexport const init = () => {\n Prefetch.prefetchStrings('moodle', ['yes']);\n Prefetch.prefetchStrings('core_admin', ['confirmation']);\n Prefetch.prefetchStrings('core_calendar', ['confirmsubscriptiondelete', 'subscriptionremoved']);\n registerEventListeners();\n};\n"],"file":"manage_subscriptions.min.js"} \ No newline at end of file diff --git a/calendar/amd/build/repository.min.js b/calendar/amd/build/repository.min.js index c75808e638d..16d63c76132 100644 --- a/calendar/amd/build/repository.min.js +++ b/calendar/amd/build/repository.min.js @@ -1,2 +1,2 @@ -define ("core_calendar/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getCourseGroupsData=a.getCalendarUpcomingData=a.updateEventStartDay=a.getCalendarDayData=a.getCalendarMonthData=a.submitCreateUpdateForm=a.getEventById=a.deleteEvent=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){var c=1.\n\n/**\n * A javascript module to handle calendar ajax actions.\n *\n * @module core_calendar/repository\n * @copyright 2017 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\n\n/**\n * Delete a calendar event.\n *\n * @method deleteEvent\n * @param {number} eventId The event id.\n * @param {boolean} deleteSeries Whether to delete all events in the series\n * @return {promise} Resolved with requested calendar event\n */\nexport const deleteEvent = (eventId, deleteSeries = false) => {\n const request = {\n methodname: 'core_calendar_delete_calendar_events',\n args: {\n events: [{\n eventid: eventId,\n repeat: deleteSeries,\n }]\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get a calendar event by id.\n *\n * @method getEventById\n * @param {number} eventId The event id.\n * @return {promise} Resolved with requested calendar event\n */\nexport const getEventById = (eventId) => {\n\n const request = {\n methodname: 'core_calendar_get_calendar_event_by_id',\n args: {\n eventid: eventId\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Submit the form data for the event form.\n *\n * @method submitCreateUpdateForm\n * @param {string} formData The URL encoded values from the form\n * @return {promise} Resolved with the new or edited event\n */\nexport const submitCreateUpdateForm = (formData) => {\n const request = {\n methodname: 'core_calendar_submit_create_update_form',\n args: {\n formdata: formData\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get calendar data for the month view.\n *\n * @method getCalendarMonthData\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} courseId The course id.\n * @param {number} categoryId The category id.\n * @param {boolean} includeNavigation Whether to include navigation.\n * @param {boolean} mini Whether the month is in mini view.\n * @param {number} day Day (optional)\n * @param {string} view The calendar view mode.\n * @return {promise} Resolved with the month view data.\n */\nexport const getCalendarMonthData = (year, month, courseId, categoryId, includeNavigation, mini, day = 1, view = 'month') => {\n const request = {\n methodname: 'core_calendar_get_calendar_monthly_view',\n args: {\n year,\n month,\n courseid: courseId,\n categoryid: categoryId,\n includenavigation: includeNavigation,\n mini,\n day,\n view,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get calendar data for the day view.\n *\n * @method getCalendarDayData\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} day Day\n * @param {number} courseId The course id.\n * @param {number} categoryId The id of the category whose events are shown\n * @return {promise} Resolved with the day view data.\n */\nexport const getCalendarDayData = (year, month, day, courseId, categoryId) => {\n const request = {\n methodname: 'core_calendar_get_calendar_day_view',\n args: {\n year,\n month,\n day,\n courseid: courseId,\n categoryid: categoryId,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Change the start day for the given event id. The day timestamp\n * only has to be any time during the target day because only the\n * date information is extracted, the time of the day is ignored.\n *\n * @param {int} eventId The id of the event to update\n * @param {int} dayTimestamp A timestamp for some time during the target day\n * @return {promise}\n */\nexport const updateEventStartDay = (eventId, dayTimestamp) => {\n const request = {\n methodname: 'core_calendar_update_event_start_day',\n args: {\n eventid: eventId,\n daytimestamp: dayTimestamp\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get calendar upcoming data.\n *\n * @method getCalendarUpcomingData\n * @param {number} courseId The course id.\n * @param {number} categoryId The category id.\n * @return {promise} Resolved with the month view data.\n */\nexport const getCalendarUpcomingData = (courseId, categoryId) => {\n const request = {\n methodname: 'core_calendar_get_calendar_upcoming_view',\n args: {\n courseid: courseId,\n categoryid: categoryId,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get the groups by course id.\n *\n * @param {Number} courseId The course id to fetch the groups from.\n * @return {promise} Resolved with the course groups.\n */\nexport const getCourseGroupsData = (courseId) => {\n const request = {\n methodname: 'core_group_get_course_groups',\n args: {\n courseid: courseId\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"repository.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/repository.js"],"names":["deleteEvent","eventId","deleteSeries","Ajax","call","methodname","args","events","eventid","repeat","getEventById","submitCreateUpdateForm","formData","formdata","getCalendarMonthData","year","month","courseId","categoryId","includeNavigation","mini","day","view","courseid","categoryid","includenavigation","getCalendarDayData","updateEventStartDay","dayTimestamp","daytimestamp","getCalendarUpcomingData","getCourseGroupsData","deleteSubscription","subscriptionId","subscriptionid"],"mappings":"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","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 * A javascript module to handle calendar ajax actions.\n *\n * @module core_calendar/repository\n * @copyright 2017 Simey Lameze \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\n\n/**\n * Delete a calendar event.\n *\n * @method deleteEvent\n * @param {number} eventId The event id.\n * @param {boolean} deleteSeries Whether to delete all events in the series\n * @return {promise} Resolved with requested calendar event\n */\nexport const deleteEvent = (eventId, deleteSeries = false) => {\n const request = {\n methodname: 'core_calendar_delete_calendar_events',\n args: {\n events: [{\n eventid: eventId,\n repeat: deleteSeries,\n }]\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get a calendar event by id.\n *\n * @method getEventById\n * @param {number} eventId The event id.\n * @return {promise} Resolved with requested calendar event\n */\nexport const getEventById = (eventId) => {\n\n const request = {\n methodname: 'core_calendar_get_calendar_event_by_id',\n args: {\n eventid: eventId\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Submit the form data for the event form.\n *\n * @method submitCreateUpdateForm\n * @param {string} formData The URL encoded values from the form\n * @return {promise} Resolved with the new or edited event\n */\nexport const submitCreateUpdateForm = (formData) => {\n const request = {\n methodname: 'core_calendar_submit_create_update_form',\n args: {\n formdata: formData\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get calendar data for the month view.\n *\n * @method getCalendarMonthData\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} courseId The course id.\n * @param {number} categoryId The category id.\n * @param {boolean} includeNavigation Whether to include navigation.\n * @param {boolean} mini Whether the month is in mini view.\n * @param {number} day Day (optional)\n * @param {string} view The calendar view mode.\n * @return {promise} Resolved with the month view data.\n */\nexport const getCalendarMonthData = (year, month, courseId, categoryId, includeNavigation, mini, day = 1, view = 'month') => {\n const request = {\n methodname: 'core_calendar_get_calendar_monthly_view',\n args: {\n year,\n month,\n courseid: courseId,\n categoryid: categoryId,\n includenavigation: includeNavigation,\n mini,\n day,\n view,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get calendar data for the day view.\n *\n * @method getCalendarDayData\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} day Day\n * @param {number} courseId The course id.\n * @param {number} categoryId The id of the category whose events are shown\n * @return {promise} Resolved with the day view data.\n */\nexport const getCalendarDayData = (year, month, day, courseId, categoryId) => {\n const request = {\n methodname: 'core_calendar_get_calendar_day_view',\n args: {\n year,\n month,\n day,\n courseid: courseId,\n categoryid: categoryId,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Change the start day for the given event id. The day timestamp\n * only has to be any time during the target day because only the\n * date information is extracted, the time of the day is ignored.\n *\n * @param {int} eventId The id of the event to update\n * @param {int} dayTimestamp A timestamp for some time during the target day\n * @return {promise}\n */\nexport const updateEventStartDay = (eventId, dayTimestamp) => {\n const request = {\n methodname: 'core_calendar_update_event_start_day',\n args: {\n eventid: eventId,\n daytimestamp: dayTimestamp\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get calendar upcoming data.\n *\n * @method getCalendarUpcomingData\n * @param {number} courseId The course id.\n * @param {number} categoryId The category id.\n * @return {promise} Resolved with the month view data.\n */\nexport const getCalendarUpcomingData = (courseId, categoryId) => {\n const request = {\n methodname: 'core_calendar_get_calendar_upcoming_view',\n args: {\n courseid: courseId,\n categoryid: categoryId,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Get the groups by course id.\n *\n * @param {Number} courseId The course id to fetch the groups from.\n * @return {promise} Resolved with the course groups.\n */\nexport const getCourseGroupsData = (courseId) => {\n const request = {\n methodname: 'core_group_get_course_groups',\n args: {\n courseid: courseId\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Delete calendar subscription by id.\n *\n * @param {Number} subscriptionId The subscription id\n * @return {promise}\n */\nexport const deleteSubscription = (subscriptionId) => {\n const request = {\n methodname: 'core_calendar_delete_subscription',\n args: {\n subscriptionid: subscriptionId\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"repository.min.js"} \ No newline at end of file diff --git a/calendar/amd/build/selectors.min.js b/calendar/amd/build/selectors.min.js index a106b987011..225064d23db 100644 --- a/calendar/amd/build/selectors.min.js +++ b/calendar/amd/build/selectors.min.js @@ -1,2 +1,2 @@ -define ("core_calendar/selectors",[],function(){return{eventFilterItem:"[data-action='filter-event-type']",eventType:{site:"[data-eventtype-site]",category:"[data-eventtype-category]",course:"[data-eventtype-course]",group:"[data-eventtype-group]",user:"[data-eventtype-user]",other:"[data-eventtype-other]"},popoverType:{site:"[data-popover-eventtype-site]",category:"[data-popover-eventtype-category]",course:"[data-popover-eventtype-course]",group:"[data-popover-eventtype-group]",user:"[data-popover-eventtype-user]",other:"[data-popover-eventtype-other]"},calendarPeriods:{month:"[data-period='month']"},courseSelector:"select[name=\"course\"]",viewSelector:"div[data-region=\"view-selector\"]",actions:{create:"[data-action=\"new-event-button\"]",edit:"[data-action=\"edit\"]",remove:"[data-action=\"delete\"]",viewEvent:"[data-action=\"view-event\"]"},elements:{courseSelector:"select[name=\"course\"]",dateContainer:".clickable.hasevent",dateContent:"[data-region=\"day-content\"]"},today:".today",day:"[data-region=\"day\"]",calendarMain:"[data-region=\"calendar\"]",wrapper:".calendarwrapper",eventItem:"[data-type=\"event\"]",links:{navLink:".calendarwrapper .arrow_link",eventLink:"[data-region='event-item']",miniDayLink:"[data-region='mini-day-link']"},containers:{loadingIcon:"[data-region=\"overlay-icon-container\"]"},fullCalendarView:"page-calendar-view"}}); +define ("core_calendar/selectors",[],function(){return{eventFilterItem:"[data-action='filter-event-type']",eventType:{site:"[data-eventtype-site]",category:"[data-eventtype-category]",course:"[data-eventtype-course]",group:"[data-eventtype-group]",user:"[data-eventtype-user]",other:"[data-eventtype-other]"},popoverType:{site:"[data-popover-eventtype-site]",category:"[data-popover-eventtype-category]",course:"[data-popover-eventtype-course]",group:"[data-popover-eventtype-group]",user:"[data-popover-eventtype-user]",other:"[data-popover-eventtype-other]"},calendarPeriods:{month:"[data-period='month']"},courseSelector:"select[name=\"course\"]",viewSelector:"div[data-region=\"view-selector\"]",actions:{create:"[data-action=\"new-event-button\"]",edit:"[data-action=\"edit\"]",remove:"[data-action=\"delete\"]",viewEvent:"[data-action=\"view-event\"]",deleteSubscription:"[data-action=\"delete-subscription\"]"},elements:{courseSelector:"select[name=\"course\"]",dateContainer:".clickable.hasevent",dateContent:"[data-region=\"day-content\"]"},today:".today",day:"[data-region=\"day\"]",calendarMain:"[data-region=\"calendar\"]",wrapper:".calendarwrapper",eventItem:"[data-type=\"event\"]",links:{navLink:".calendarwrapper .arrow_link",eventLink:"[data-region='event-item']",miniDayLink:"[data-region='mini-day-link']"},containers:{loadingIcon:"[data-region=\"overlay-icon-container\"]"},fullCalendarView:"page-calendar-view"}}); //# sourceMappingURL=selectors.min.js.map diff --git a/calendar/amd/build/selectors.min.js.map b/calendar/amd/build/selectors.min.js.map index 3074c67b488..24de117ca66 100644 --- a/calendar/amd/build/selectors.min.js.map +++ b/calendar/amd/build/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/selectors.js"],"names":["define","eventFilterItem","eventType","site","category","course","group","user","other","popoverType","calendarPeriods","month","courseSelector","viewSelector","actions","create","edit","remove","viewEvent","elements","dateContainer","dateContent","today","day","calendarMain","wrapper","eventItem","links","navLink","eventLink","miniDayLink","containers","loadingIcon","fullCalendarView"],"mappings":"AAsBAA,OAAM,2BAAC,EAAD,CAAK,UAAW,CAClB,MAAO,CACHC,eAAe,CAAE,mCADd,CAEHC,SAAS,CAAE,CACPC,IAAI,CAAE,uBADC,CAEPC,QAAQ,CAAE,2BAFH,CAGPC,MAAM,CAAE,yBAHD,CAIPC,KAAK,CAAE,wBAJA,CAKPC,IAAI,CAAE,uBALC,CAMPC,KAAK,CAAE,wBANA,CAFR,CAUHC,WAAW,CAAE,CACTN,IAAI,CAAE,+BADG,CAETC,QAAQ,CAAE,mCAFD,CAGTC,MAAM,CAAE,iCAHC,CAITC,KAAK,CAAE,gCAJE,CAKTC,IAAI,CAAE,+BALG,CAMTC,KAAK,CAAE,gCANE,CAVV,CAkBHE,eAAe,CAAE,CACbC,KAAK,CAAE,uBADM,CAlBd,CAqBHC,cAAc,CAAE,yBArBb,CAsBHC,YAAY,CAAE,oCAtBX,CAuBHC,OAAO,CAAE,CACLC,MAAM,CAAE,oCADH,CAELC,IAAI,CAAE,wBAFD,CAGLC,MAAM,CAAE,0BAHH,CAILC,SAAS,CAAE,8BAJN,CAvBN,CA6BHC,QAAQ,CAAE,CACNP,cAAc,CAAE,yBADV,CAENQ,aAAa,CAAE,qBAFT,CAGNC,WAAW,CAAE,+BAHP,CA7BP,CAkCHC,KAAK,CAAE,QAlCJ,CAmCHC,GAAG,CAAE,uBAnCF,CAoCHC,YAAY,CAAE,4BApCX,CAqCHC,OAAO,CAAE,kBArCN,CAsCHC,SAAS,CAAE,uBAtCR,CAuCHC,KAAK,CAAE,CACHC,OAAO,CAAE,8BADN,CAEHC,SAAS,CAAE,4BAFR,CAGHC,WAAW,CAAE,+BAHV,CAvCJ,CA4CHC,UAAU,CAAE,CACRC,WAAW,CAAE,0CADL,CA5CT,CA+CHC,gBAAgB,CAAE,oBA/Cf,CAiDV,CAlDK,CAAN","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 * This module is responsible for the calendar filter.\n *\n * @module core_calendar/calendar_selectors\n * @copyright 2017 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n return {\n eventFilterItem: \"[data-action='filter-event-type']\",\n eventType: {\n site: \"[data-eventtype-site]\",\n category: \"[data-eventtype-category]\",\n course: \"[data-eventtype-course]\",\n group: \"[data-eventtype-group]\",\n user: \"[data-eventtype-user]\",\n other: \"[data-eventtype-other]\",\n },\n popoverType: {\n site: \"[data-popover-eventtype-site]\",\n category: \"[data-popover-eventtype-category]\",\n course: \"[data-popover-eventtype-course]\",\n group: \"[data-popover-eventtype-group]\",\n user: \"[data-popover-eventtype-user]\",\n other: \"[data-popover-eventtype-other]\",\n },\n calendarPeriods: {\n month: \"[data-period='month']\",\n },\n courseSelector: 'select[name=\"course\"]',\n viewSelector: 'div[data-region=\"view-selector\"]',\n actions: {\n create: '[data-action=\"new-event-button\"]',\n edit: '[data-action=\"edit\"]',\n remove: '[data-action=\"delete\"]',\n viewEvent: '[data-action=\"view-event\"]',\n },\n elements: {\n courseSelector: 'select[name=\"course\"]',\n dateContainer: '.clickable.hasevent',\n dateContent: '[data-region=\"day-content\"]',\n },\n today: '.today',\n day: '[data-region=\"day\"]',\n calendarMain: '[data-region=\"calendar\"]',\n wrapper: '.calendarwrapper',\n eventItem: '[data-type=\"event\"]',\n links: {\n navLink: '.calendarwrapper .arrow_link',\n eventLink: \"[data-region='event-item']\",\n miniDayLink: \"[data-region='mini-day-link']\",\n },\n containers: {\n loadingIcon: '[data-region=\"overlay-icon-container\"]',\n },\n fullCalendarView: 'page-calendar-view',\n };\n});\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/selectors.js"],"names":["define","eventFilterItem","eventType","site","category","course","group","user","other","popoverType","calendarPeriods","month","courseSelector","viewSelector","actions","create","edit","remove","viewEvent","deleteSubscription","elements","dateContainer","dateContent","today","day","calendarMain","wrapper","eventItem","links","navLink","eventLink","miniDayLink","containers","loadingIcon","fullCalendarView"],"mappings":"AAsBAA,OAAM,2BAAC,EAAD,CAAK,UAAW,CAClB,MAAO,CACHC,eAAe,CAAE,mCADd,CAEHC,SAAS,CAAE,CACPC,IAAI,CAAE,uBADC,CAEPC,QAAQ,CAAE,2BAFH,CAGPC,MAAM,CAAE,yBAHD,CAIPC,KAAK,CAAE,wBAJA,CAKPC,IAAI,CAAE,uBALC,CAMPC,KAAK,CAAE,wBANA,CAFR,CAUHC,WAAW,CAAE,CACTN,IAAI,CAAE,+BADG,CAETC,QAAQ,CAAE,mCAFD,CAGTC,MAAM,CAAE,iCAHC,CAITC,KAAK,CAAE,gCAJE,CAKTC,IAAI,CAAE,+BALG,CAMTC,KAAK,CAAE,gCANE,CAVV,CAkBHE,eAAe,CAAE,CACbC,KAAK,CAAE,uBADM,CAlBd,CAqBHC,cAAc,CAAE,yBArBb,CAsBHC,YAAY,CAAE,oCAtBX,CAuBHC,OAAO,CAAE,CACLC,MAAM,CAAE,oCADH,CAELC,IAAI,CAAE,wBAFD,CAGLC,MAAM,CAAE,0BAHH,CAILC,SAAS,CAAE,8BAJN,CAKLC,kBAAkB,CAAE,uCALf,CAvBN,CA8BHC,QAAQ,CAAE,CACNR,cAAc,CAAE,yBADV,CAENS,aAAa,CAAE,qBAFT,CAGNC,WAAW,CAAE,+BAHP,CA9BP,CAmCHC,KAAK,CAAE,QAnCJ,CAoCHC,GAAG,CAAE,uBApCF,CAqCHC,YAAY,CAAE,4BArCX,CAsCHC,OAAO,CAAE,kBAtCN,CAuCHC,SAAS,CAAE,uBAvCR,CAwCHC,KAAK,CAAE,CACHC,OAAO,CAAE,8BADN,CAEHC,SAAS,CAAE,4BAFR,CAGHC,WAAW,CAAE,+BAHV,CAxCJ,CA6CHC,UAAU,CAAE,CACRC,WAAW,CAAE,0CADL,CA7CT,CAgDHC,gBAAgB,CAAE,oBAhDf,CAkDV,CAnDK,CAAN","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 * This module is responsible for the calendar filter.\n *\n * @module core_calendar/calendar_selectors\n * @copyright 2017 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n return {\n eventFilterItem: \"[data-action='filter-event-type']\",\n eventType: {\n site: \"[data-eventtype-site]\",\n category: \"[data-eventtype-category]\",\n course: \"[data-eventtype-course]\",\n group: \"[data-eventtype-group]\",\n user: \"[data-eventtype-user]\",\n other: \"[data-eventtype-other]\",\n },\n popoverType: {\n site: \"[data-popover-eventtype-site]\",\n category: \"[data-popover-eventtype-category]\",\n course: \"[data-popover-eventtype-course]\",\n group: \"[data-popover-eventtype-group]\",\n user: \"[data-popover-eventtype-user]\",\n other: \"[data-popover-eventtype-other]\",\n },\n calendarPeriods: {\n month: \"[data-period='month']\",\n },\n courseSelector: 'select[name=\"course\"]',\n viewSelector: 'div[data-region=\"view-selector\"]',\n actions: {\n create: '[data-action=\"new-event-button\"]',\n edit: '[data-action=\"edit\"]',\n remove: '[data-action=\"delete\"]',\n viewEvent: '[data-action=\"view-event\"]',\n deleteSubscription: '[data-action=\"delete-subscription\"]',\n },\n elements: {\n courseSelector: 'select[name=\"course\"]',\n dateContainer: '.clickable.hasevent',\n dateContent: '[data-region=\"day-content\"]',\n },\n today: '.today',\n day: '[data-region=\"day\"]',\n calendarMain: '[data-region=\"calendar\"]',\n wrapper: '.calendarwrapper',\n eventItem: '[data-type=\"event\"]',\n links: {\n navLink: '.calendarwrapper .arrow_link',\n eventLink: \"[data-region='event-item']\",\n miniDayLink: \"[data-region='mini-day-link']\",\n },\n containers: {\n loadingIcon: '[data-region=\"overlay-icon-container\"]',\n },\n fullCalendarView: 'page-calendar-view',\n };\n});\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/calendar/amd/src/manage_subscriptions.js b/calendar/amd/src/manage_subscriptions.js new file mode 100644 index 00000000000..1e5b1fb9c80 --- /dev/null +++ b/calendar/amd/src/manage_subscriptions.js @@ -0,0 +1,145 @@ +// 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 . + +/** + * A module to handle Delete/Update operations of the manage subscription page. + * + * @module core_calendar/manage_subscriptions + * @copyright 2021 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 4.0 + */ + +import * as CalendarSelectors from 'core_calendar/selectors'; +import * as CalendarRepository from 'core_calendar/repository'; +import * as Modal from 'core/modal_factory'; +import * as ModalEvents from 'core/modal_events'; +import {displayException, addNotification, fetchNotifications} from 'core/notification'; +import Prefetch from 'core/prefetch'; +import {get_string as getString} from 'core/str'; +import {eventTypes} from 'core/local/inplace_editable/events'; + +/** + * Get subscription id for given element. + * + * @param {HTMLElement} element update/delete link + * @return {Number} + */ +const getSubscriptionId = element => { + return parseInt(element.closest('tr').dataset.subid); +}; + +/** + * Get subscription name for given element. + * + * @param {HTMLElement} element update/delete link + * @return {String} + */ +const getSubscriptionName = element => { + return element.closest('tr').dataset.subname; +}; + +/** + * Get subscription table row for subscription id. + * + * @param {string} subscriptionId Subscription id + * @return {Element} + */ +const getSubscriptionRow = subscriptionId => { + return document.querySelector(`tr[data-subid="${subscriptionId}"]`); +}; + +/** + * Create modal. + * + * @param {HTMLElement} element + * @param {string} messageCode Message code. + * @return {promise} Promise for modal + */ +const createModal = (element, messageCode) => { + const subscriptionName = getSubscriptionName(element); + return Modal.create({ + type: Modal.types.SAVE_CANCEL, + title: getString('confirmation', 'admin'), + body: getString(messageCode, 'calendar', subscriptionName), + buttons: { + save: getString('yes') + }, + }).then(modal => { + modal.getRoot().on(ModalEvents.hidden, () => { + element.focus(); + }); + modal.show(); + return modal; + }); +}; + +/** + * Response handler for delete action. + * + * @param {HTMLElement} element + * @param {Object} data + * @return {Promise} + */ +const responseHandlerForDelete = async(element, data) => { + const subscriptionName = getSubscriptionName(element); + const message = data.status ? await getString('subscriptionremoved', 'calendar', subscriptionName) : data.warnings[0].message; + const type = data.status ? 'info' : 'error'; + return addNotification({message, type}); +}; + +/** + * Register events for update/delete links. + */ +const registerEventListeners = () => { + document.addEventListener('click', e => { + const deleteAction = e.target.closest(CalendarSelectors.actions.deleteSubscription); + if (deleteAction) { + e.preventDefault(); + const modalPromise = createModal(deleteAction, 'confirmsubscriptiondelete'); + modalPromise.then(modal => { + modal.getRoot().on(ModalEvents.save, () => { + const subscriptionId = getSubscriptionId(deleteAction); + CalendarRepository.deleteSubscription(subscriptionId).then(data => { + const response = responseHandlerForDelete(deleteAction, data); + return response.then(() => { + const subscriptionRow = getSubscriptionRow(subscriptionId); + return subscriptionRow.remove(); + }); + }).catch(displayException); + }); + + return modal; + }).catch(displayException); + } + }); + + document.addEventListener(eventTypes.elementUpdated, e => { + const inplaceEditable = e.target; + if (inplaceEditable.getAttribute('data-component') == 'core_calendar') { + fetchNotifications(); + } + }); +}; + +/** + * Initialises. + */ +export const init = () => { + Prefetch.prefetchStrings('moodle', ['yes']); + Prefetch.prefetchStrings('core_admin', ['confirmation']); + Prefetch.prefetchStrings('core_calendar', ['confirmsubscriptiondelete', 'subscriptionremoved']); + registerEventListeners(); +}; diff --git a/calendar/amd/src/repository.js b/calendar/amd/src/repository.js index 030f76ac90a..ef846c2e2c6 100644 --- a/calendar/amd/src/repository.js +++ b/calendar/amd/src/repository.js @@ -196,3 +196,20 @@ export const getCourseGroupsData = (courseId) => { return Ajax.call([request])[0]; }; + +/** + * Delete calendar subscription by id. + * + * @param {Number} subscriptionId The subscription id + * @return {promise} + */ +export const deleteSubscription = (subscriptionId) => { + const request = { + methodname: 'core_calendar_delete_subscription', + args: { + subscriptionid: subscriptionId + } + }; + + return Ajax.call([request])[0]; +}; diff --git a/calendar/amd/src/selectors.js b/calendar/amd/src/selectors.js index db5557f7f52..b1a96f35103 100644 --- a/calendar/amd/src/selectors.js +++ b/calendar/amd/src/selectors.js @@ -49,6 +49,7 @@ define([], function() { edit: '[data-action="edit"]', remove: '[data-action="delete"]', viewEvent: '[data-action="view-event"]', + deleteSubscription: '[data-action="delete-subscription"]', }, elements: { courseSelector: 'select[name="course"]', diff --git a/calendar/classes/external/event_exporter_base.php b/calendar/classes/external/event_exporter_base.php index 2ae3cc579bd..e284b24b962 100644 --- a/calendar/classes/external/event_exporter_base.php +++ b/calendar/classes/external/event_exporter_base.php @@ -101,19 +101,6 @@ class event_exporter_base extends exporter { if ($cm = $event->get_course_module()) { $data->modulename = $cm->get('modname'); $data->instance = $cm->get('id'); - $data->activityname = $cm->get('name'); - - $component = 'mod_' . $data->modulename; - if (!component_callback_exists($component, 'core_calendar_get_event_action_string')) { - $modulename = get_string('modulename', $data->modulename); - $data->activitystr = get_string('requiresaction', 'calendar', $modulename); - } else { - $data->activitystr = component_callback( - $component, - 'core_calendar_get_event_action_string', - [$event->get_type()] - ); - } } parent::__construct($data, $related); @@ -188,18 +175,6 @@ class event_exporter_base extends exporter { 'default' => null, 'null' => NULL_ALLOWED ], - 'activityname' => [ - 'type' => PARAM_TEXT, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], - 'activitystr' => [ - 'type' => PARAM_TEXT, - 'optional' => true, - 'default' => null, - 'null' => NULL_ALLOWED - ], 'instance' => [ 'type' => PARAM_INT, 'optional' => true, diff --git a/calendar/classes/external/subscription/delete.php b/calendar/classes/external/subscription/delete.php new file mode 100644 index 00000000000..ef6be587d0a --- /dev/null +++ b/calendar/classes/external/subscription/delete.php @@ -0,0 +1,104 @@ +. + +/** + * Calendar external API for deleting the subscription. + * + * @package core_calendar + * @category external + * @copyright 2021 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_calendar\external\subscription; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/calendar/lib.php'); + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use external_warnings; + +/** + * Calendar external API for deleting the subscription. + * + * @package core_calendar + * @category external + * @copyright 2021 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class delete extends external_api { + + /** + * Describes the parameters for deleting the subscription. + * + * @return external_function_parameters + * @since Moodle 4.0 + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'subscriptionid' => new external_value(PARAM_INT, 'The id of the subscription', VALUE_REQUIRED) + ]); + } + + /** + * External function to delete the calendar subscription. + * + * @param int $subscriptionid Subscription id. + * @return array + */ + public static function execute(int $subscriptionid): array { + [ + 'subscriptionid' => $subscriptionid + ] = self::validate_parameters(self::execute_parameters(), [ + 'subscriptionid' => $subscriptionid + ]); + $status = false; + $warnings = []; + if (calendar_can_edit_subscription($subscriptionid)) { + // Fetch the subscription from the database making sure it exists. + $sub = calendar_get_subscription($subscriptionid); + calendar_delete_subscription($subscriptionid); + $status = true; + } else { + $warnings = [ + 'item' => $subscriptionid, + 'warningcode' => 'errordeletingsubscription', + 'message' => get_string('nopermissions', 'error') + ]; + } + return [ + 'status' => $status, + 'warnings' => $warnings + ]; + } + + /** + * Describes the data returned from the external function. + * + * @return external_single_structure + * @since Moodle 4.0 + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'status' => new external_value(PARAM_BOOL, 'status: true if success'), + 'warnings' => new external_warnings() + ]); + } +} diff --git a/calendar/classes/output/refreshintervalcollection.php b/calendar/classes/output/refreshintervalcollection.php new file mode 100644 index 00000000000..3cb859bbe7d --- /dev/null +++ b/calendar/classes/output/refreshintervalcollection.php @@ -0,0 +1,54 @@ +. + +/** + * Class to display collection select for the refresh interval. + * + * @package core_calendar + * @copyright 2021 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_calendar\output; + +use core\output\inplace_editable; + +class refreshintervalcollection extends inplace_editable { + + /** + * Constructor. + * + * @param \stdClass $subscription Subscription object + */ + public function __construct(\stdClass $subscription) { + $collection = calendar_get_pollinterval_choices(); + parent::__construct('core_calendar', 'refreshinterval', $subscription->id, true, null, $subscription->pollinterval, null, + get_string('pollinterval', 'calendar')); + $this->set_type_select($collection); + } + + public static function update(int $subscriptionid, int $pollinterval) { + if (calendar_can_edit_subscription($subscriptionid)) { + $subscription = calendar_get_subscription($subscriptionid); + $subscription->pollinterval = $pollinterval; + calendar_update_subscription($subscription); + $tmpl = new self($subscription); + return $tmpl; + } else { + throw new \moodle_exception('nopermissions', 'error', '', get_string('managesubscriptions', 'calendar')); + } + } +} diff --git a/calendar/import.php b/calendar/import.php index d0d1df7ea59..1e561b7009f 100644 --- a/calendar/import.php +++ b/calendar/import.php @@ -90,6 +90,23 @@ if (!empty($groupcourseid)) { $data['eventtype'] = 'group'; $pageurl->param('groupcourseid', $groupcourseid); } +if (!empty($category)) { + $pageurl->param('category', $category); + $managesubscriptionsurl->param('category', $category); + $data['category'] = $category; + $data['eventtype'] = 'category'; +} + +$heading = get_string('importcalendar', 'calendar'); +$pagetitle = $course->shortname . ': ' . get_string('calendar', 'calendar') . ': ' . $heading; + +$PAGE->set_title($pagetitle); +$PAGE->set_heading($heading); +$PAGE->set_url($pageurl); +$PAGE->set_pagelayout('admin'); +$PAGE->navbar->add(get_string('managesubscriptions', 'calendar'), $managesubscriptionsurl); +$PAGE->navbar->add($heading); +$renderer = $PAGE->get_renderer('core_calendar'); $customdata = [ 'courseid' => $course->id, @@ -109,7 +126,7 @@ if (!empty($formdata)) { $calendar = $form->get_file_content('importfile'); $ical = new iCalendar(); $ical->unserialize($calendar); - $importresults = calendar_import_icalendar_events($ical, null, $subscriptionid); + $importresults = calendar_import_events_from_ical($ical, $subscriptionid); } else { try { $importresults = calendar_update_subscription_events($subscriptionid); @@ -125,11 +142,9 @@ if (!empty($formdata)) { if (!empty($formdata->categoryid)) { $managesubscriptionsurl->param('category', $formdata->categoryid); } - redirect($managesubscriptionsurl, $importresults); + redirect($managesubscriptionsurl, $renderer->render_import_result($importresults)); } -$renderer = $PAGE->get_renderer('core_calendar'); - echo $OUTPUT->header(); echo $renderer->start_layout(); echo $OUTPUT->heading($heading); diff --git a/calendar/lib.php b/calendar/lib.php index 072beac11bf..8b4951c9bb9 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -2721,12 +2721,12 @@ function calendar_add_event_allowed($event) { */ function calendar_get_pollinterval_choices() { return array( - '0' => new \lang_string('never', 'calendar'), - HOURSECS => new \lang_string('hourly', 'calendar'), - DAYSECS => new \lang_string('daily', 'calendar'), - WEEKSECS => new \lang_string('weekly', 'calendar'), - '2628000' => new \lang_string('monthly', 'calendar'), - YEARSECS => new \lang_string('annually', 'calendar') + '0' => get_string('never', 'calendar'), + HOURSECS => get_string('hourly', 'calendar'), + DAYSECS => get_string('daily', 'calendar'), + WEEKSECS => get_string('weekly', 'calendar'), + '2628000' => get_string('monthly', 'calendar'), + YEARSECS => get_string('annually', 'calendar') ); } @@ -2964,42 +2964,6 @@ function calendar_add_icalendar_event($event, $unused, $subscriptionid, $timezon } } -/** - * Update a subscription from the form data in one of the rows in the existing subscriptions table. - * - * @param int $subscriptionid The ID of the subscription we are acting upon. - * @param int $pollinterval The poll interval to use. - * @param int $action The action to be performed. One of update or remove. - * @throws dml_exception if invalid subscriptionid is provided - * @return string A log of the import progress, including errors - */ -function calendar_process_subscription_row($subscriptionid, $pollinterval, $action) { - // Fetch the subscription from the database making sure it exists. - $sub = calendar_get_subscription($subscriptionid); - - // Update or remove the subscription, based on action. - switch ($action) { - case CALENDAR_SUBSCRIPTION_UPDATE: - // Skip updating file subscriptions. - if (empty($sub->url)) { - break; - } - $sub->pollinterval = $pollinterval; - calendar_update_subscription($sub); - - // Update the events. - return "

" . get_string('subscriptionupdated', 'calendar', $sub->name) . "

" . - calendar_update_subscription_events($subscriptionid); - case CALENDAR_SUBSCRIPTION_REMOVE: - calendar_delete_subscription($subscriptionid); - return get_string('subscriptionremoved', 'calendar', $sub->name); - break; - default: - break; - } - return ''; -} - /** * Delete subscription and all related events. * @@ -3052,6 +3016,7 @@ function calendar_get_icalendar($url) { global $CFG; require_once($CFG->libdir . '/filelib.php'); + require_once($CFG->libdir . '/bennu/bennu.inc.php'); $curl = new \curl(); $curl->setopt(array('CURLOPT_FOLLOWLOCATION' => 1, 'CURLOPT_MAXREDIRS' => 5)); @@ -3072,17 +3037,17 @@ function calendar_get_icalendar($url) { * Import events from an iCalendar object into a course calendar. * * @param iCalendar $ical The iCalendar object. - * @param int $unused Deprecated - * @param int $subscriptionid The subscription ID. - * @return string A log of the import progress, including errors. + * @param int|null $subscriptionid The subscription ID. + * @return array A log of the import progress, including errors. */ -function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid = null) { +function calendar_import_events_from_ical(iCalendar $ical, int $subscriptionid = null): array { global $DB; - $return = ''; + $errors = []; $eventcount = 0; $updatecount = 0; $skippedcount = 0; + $deletedcount = 0; // Large calendars take a while... if (!CLI_SCRIPT) { @@ -3111,18 +3076,15 @@ function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid $skippedcount++; break; case 0: - $return .= '

' . get_string('erroraddingevent', 'calendar') . ': '; if (empty($event->properties['SUMMARY'])) { - $return .= '(' . get_string('notitle', 'calendar') . ')'; + $errors[] = '(' . get_string('notitle', 'calendar') . ')'; } else { - $return .= $event->properties['SUMMARY'][0]->value; + $errors[] = $event->properties['SUMMARY'][0]->value; } - $return .= "

\n"; break; } } - $return .= html_writer::start_tag('ul'); $existing = $DB->get_field('event_subscriptions', 'lastupdated', ['id' => $subscriptionid]); if (!empty($existing)) { $eventsuuids = $DB->get_records_menu('event', ['subscriptionid' => $subscriptionid], '', 'id, uuid'); @@ -3137,16 +3099,21 @@ function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid } if (!empty($tobedeleted)) { $DB->delete_records_list('event', 'id', $tobedeleted); - $return .= html_writer::tag('li', get_string('eventsdeleted', 'calendar', count($tobedeleted))); + $deletedcount = count($tobedeleted); } } } - $return .= html_writer::tag('li', get_string('eventsimported', 'calendar', $eventcount)); - $return .= html_writer::tag('li', get_string('eventsskipped', 'calendar', $skippedcount)); - $return .= html_writer::tag('li', get_string('eventsupdated', 'calendar', $updatecount)); - $return .= html_writer::end_tag('ul'); - return $return; + $result = [ + 'eventsimported' => $eventcount, + 'eventsskipped' => $skippedcount, + 'eventsupdated' => $updatecount, + 'eventsdeleted' => $deletedcount, + 'haserror' => !empty($errors), + 'errors' => $errors, + ]; + + return $result; } /** @@ -3164,7 +3131,7 @@ function calendar_update_subscription_events($subscriptionid) { } $ical = calendar_get_icalendar($sub->url); - $return = calendar_import_icalendar_events($ical, null, $subscriptionid); + $return = calendar_import_events_from_ical($ical, $subscriptionid); $sub->lastupdated = time(); calendar_update_subscription($sub); @@ -3979,3 +3946,36 @@ function calendar_get_export_import_link_params(): array { return $params; } + +/** + * Implements the inplace editable feature. + * + * @param string $itemtype Type of the inplace editable element + * @param int $itemid Id of the item to edit + * @param int $newvalue New value of the item + * @return \core\output\inplace_editable + */ +function calendar_inplace_editable(string $itemtype, int $itemid, int $newvalue): \core\output\inplace_editable { + global $OUTPUT; + + if ($itemtype === 'refreshinterval') { + + $subscription = calendar_get_subscription($itemid); + $context = calendar_get_calendar_context($subscription); + \external_api::validate_context($context); + + $updateresult = \core_calendar\output\refreshintervalcollection::update($itemid, $newvalue); + + $refreshresults = calendar_update_subscription_events($itemid); + \core\notification::add($OUTPUT->render_from_template( + 'core_calendar/subscription_update_result', + array_merge($refreshresults, [ + 'subscriptionname' => s($subscription->name), + ]) + ), \core\notification::INFO); + + return $updateresult; + } + + \external_api::validate_context(context_system::instance()); +} diff --git a/calendar/managesubscriptions.php b/calendar/managesubscriptions.php index 1410bd3e651..316b1f6b395 100644 --- a/calendar/managesubscriptions.php +++ b/calendar/managesubscriptions.php @@ -30,10 +30,6 @@ require_once($CFG->dirroot.'/calendar/lib.php'); // Required use. $courseid = optional_param('course', null, PARAM_INT); $categoryid = optional_param('category', null, PARAM_INT); -// Used for processing subscription actions. -$subscriptionid = optional_param('id', 0, PARAM_INT); -$pollinterval = optional_param('pollinterval', 0, PARAM_INT); -$action = optional_param('action', '', PARAM_INT); $url = new moodle_url('/calendar/managesubscriptions.php'); if ($courseid != SITEID && !empty($courseid)) { @@ -74,22 +70,6 @@ if (!calendar_user_can_add_event($course)) { $PAGE->navbar->add(get_string('managesubscriptions', 'calendar')); -if (!empty($subscriptionid)) { - // The user is wanting to perform an action upon an existing subscription. - require_sesskey(); // Must have sesskey for all actions. - if (calendar_can_edit_subscription($subscriptionid)) { - try { - $importresults = calendar_process_subscription_row($subscriptionid, $pollinterval, $action); - redirect($PAGE->url, $importresults); - } catch (moodle_exception $e) { - // If exception caught, then user should be redirected to page where he/she came from. - print_error($e->errorcode, $e->module, $PAGE->url); - } - } else { - print_error('nopermissions', 'error', $PAGE->url, get_string('managesubscriptions', 'calendar')); - } -} - $types = calendar_get_allowed_event_types($courseid); $searches = []; diff --git a/calendar/renderer.php b/calendar/renderer.php index 6c26be2d2e9..5268000dc53 100644 --- a/calendar/renderer.php +++ b/calendar/renderer.php @@ -371,8 +371,6 @@ class core_calendar_renderer extends plugin_renderer_base { get_string('colpoll', 'calendar'), get_string('colactions', 'calendar') ); - $table->align = array('left', 'left', 'left', 'left', 'left'); - $table->width = '100%'; $table->data = array(); $table->id = 'subscription_details_table'; @@ -394,20 +392,29 @@ class core_calendar_renderer extends plugin_renderer_base { } $type = $sub->eventtype . 'events'; + $calendarname = new html_table_cell($label); + $calendarname->header = true; - $table->data[] = new html_table_row(array( - new html_table_cell($label), + $tablerow = new html_table_row(array( + $calendarname, new html_table_cell($lastupdated), new html_table_cell(get_string($type, 'calendar')), new html_table_cell($this->render_subscription_update_interval($sub)), - new html_table_cell($this->subscription_action_form($sub)) + new html_table_cell($this->subscription_action_links()) )); + $tablerow->attributes += [ + 'data-subid' => $sub->id, + 'data-subname' => $sub->name + ]; + $table->data[] = $tablerow; } $out = $this->output->box_start('generalbox calendarsubs'); $out .= html_writer::table($table); $out .= $this->output->box_end(); + + $this->page->requires->js_call_amd('core_calendar/manage_subscriptions', 'init'); return $out; } @@ -418,51 +425,24 @@ class core_calendar_renderer extends plugin_renderer_base { * @return string */ protected function render_subscription_update_interval(stdClass $subscription): string { - // Assemble form for the subscription row. - $output = html_writer::start_tag('form', - ['action' => new moodle_url('/calendar/managesubscriptions.php', calendar_get_export_import_link_params()), - 'method' => 'post']); if (empty($subscription->url)) { - $output .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'pollinterval', 'value' => '0']); - } else { - // Assemble pollinterval control. - $output .= html_writer::start_div(); - $output .= html_writer::start_tag('select', ['name' => 'pollinterval', 'class' => 'custom-select']); - foreach (calendar_get_pollinterval_choices() as $k => $v) { - $attributes = array(); - if ($k == $subscription->pollinterval) { - $attributes['selected'] = 'selected'; - } - $attributes['value'] = $k; - $output .= html_writer::tag('option', $v, $attributes); - } - $output .= html_writer::end_tag('select'); - $output .= html_writer::end_div(); + return ''; } - return $output; + $tmpl = new \core_calendar\output\refreshintervalcollection($subscription); + return $this->output->render_from_template('core/inplace_editable', $tmpl->export_for_template($this->output)); } /** * Creates a form to perform actions on a given subscription. * - * @param stdClass $subscription * @return string */ - protected function subscription_action_form($subscription) { - $html = html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey())); - $html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'id', 'value' => $subscription->id)); - $html .= html_writer::start_tag('div', array('class' => 'btn-group float-left')); - if (!empty($subscription->url)) { - $html .= html_writer::tag('button', get_string('update'), array('type' => 'submit', 'name' => 'action', - 'class' => 'btn btn-link', - 'value' => CALENDAR_SUBSCRIPTION_UPDATE)); - } - $html .= html_writer::tag('button', get_string('remove'), array('type' => 'submit', 'name' => 'action', - 'class' => 'btn btn-link', - 'value' => CALENDAR_SUBSCRIPTION_REMOVE)); + protected function subscription_action_links(): string { + $html = html_writer::start_tag('div', array('class' => 'btn-group float-left')); + $html .= html_writer::span(html_writer::link('#', get_string('delete'), + ['data-action' => 'delete-subscription']), ''); $html .= html_writer::end_tag('div'); - $html .= html_writer::end_tag('form'); return $html; } @@ -477,4 +457,23 @@ class core_calendar_renderer extends plugin_renderer_base { ]; return $this->render_from_template('core_calendar/event_filter', $data); } + + /** + * Render the calendar import result. + * + * @param array $result Import result + * @return string|null + */ + public function render_import_result(array $result): ?string { + $data = [ + 'eventsimported' => $result['eventsimported'], + 'eventsskipped' => $result['eventsskipped'], + 'eventsupdated' => $result['eventsupdated'], + 'eventsdeleted' => $result['eventsdeleted'], + 'haserror' => $result['haserror'], + 'errors' => $result['errors'] + ]; + + return $this->render_from_template('core_calendar/subscription_update_result', $data); + } } diff --git a/calendar/templates/subscription_update_result.mustache b/calendar/templates/subscription_update_result.mustache new file mode 100644 index 00000000000..85085c920e2 --- /dev/null +++ b/calendar/templates/subscription_update_result.mustache @@ -0,0 +1,61 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_calendar/subscription_update_result + + Calendar subscription update result. + + The purpose of this template is to render the calendar subscription update result. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + "subscriptionname": "Moodle", + "eventsimported": 1, + "eventsskipped": 2, + "eventsupdated": 3, + "deletedcount": 4, + "errors": [ + "Event 1", + "Event 2" + ] + } +}} + +{{#subscriptionname}} +

{{#str}} subscriptionupdated, calendar, {{subscriptionname}} {{/str}}

+{{/subscriptionname}} +
    +
  • {{#str}} eventsimported, calendar, {{eventsimported}} {{/str}}
  • +
  • {{#str}} eventsskipped, calendar, {{eventsskipped}} {{/str}}
  • +
  • {{#str}} eventsupdated, calendar, {{eventsupdated}} {{/str}}
  • +
  • {{#str}} eventsdeleted, calendar, {{eventsdeleted}} {{/str}}
  • +
+{{#haserror}} +

{{#str}} erroraddingevent, calendar {{/str}}

+
    + {{#eventserror}} +
  • {{.}}
  • + {{/eventserror}} +
+{{/haserror}} + diff --git a/calendar/tests/behat/calendar_import.feature b/calendar/tests/behat/calendar_import.feature index e2c2289a676..a9279eb54f2 100644 --- a/calendar/tests/behat/calendar_import.feature +++ b/calendar/tests/behat/calendar_import.feature @@ -44,7 +44,9 @@ Feature: Import and edit calendar events And I should see "Event on 2-25-2017" And I should not see "Event on 2-15-2017" And I click on "Import or export calendars" "link" - And I press "Remove" + And I click on "Delete" "link" + And I should see "Are you sure you want to delete the \"Test Import\" calendar subscription?" in the ".modal .modal-body" "css_element" + And I click on "Yes" "button" in the ".modal.show" "css_element" And I view the calendar for "2" "2017" And I should not see "Event on 2-25-2017" And I should not see "Event on 2-20-2017" diff --git a/calendar/tests/lib_test.php b/calendar/tests/lib_test.php index 40fb91275ab..21e58eefcc7 100644 --- a/calendar/tests/lib_test.php +++ b/calendar/tests/lib_test.php @@ -190,7 +190,7 @@ class core_calendar_lib_testcase extends advanced_testcase { $this->assertEquals($ical->parser_errors, array()); $sub = calendar_get_subscription($id); - calendar_import_icalendar_events($ical, null, $sub->id); + calendar_import_events_from_ical($ical, $sub->id); $count = $DB->count_records('event', array('subscriptionid' => $sub->id)); $this->assertEquals($count, 1); @@ -207,7 +207,7 @@ class core_calendar_lib_testcase extends advanced_testcase { $this->assertEquals($ical->parser_errors, array()); $sub = calendar_get_subscription($id); - calendar_import_icalendar_events($ical, null, $sub->id); + calendar_import_events_from_ical($ical, $sub->id); $count = $DB->count_records('event', array('subscriptionid' => $sub->id)); $this->assertEquals($count, 1); @@ -224,7 +224,7 @@ class core_calendar_lib_testcase extends advanced_testcase { $this->assertEquals($ical->parser_errors, array()); $sub = calendar_get_subscription($id); - calendar_import_icalendar_events($ical, null, $sub->id); + calendar_import_events_from_ical($ical, $sub->id); $count = $DB->count_records('event', array('subscriptionid' => $sub->id)); $this->assertEquals($count, 1); @@ -240,11 +240,15 @@ class core_calendar_lib_testcase extends advanced_testcase { $this->assertEquals($ical->parser_errors, []); $sub = calendar_get_subscription($id); - $output = calendar_import_icalendar_events($ical, null, $sub->id); - $this->assertStringNotContainsString('Events deleted: 17', $output); - $this->assertStringContainsString('1 events were imported', $output); - $this->assertStringContainsString('0 events were skipped', $output); - $this->assertStringContainsString('0 events were updated', $output); + $output = calendar_import_events_from_ical($ical, $sub->id); + $this->assertArrayHasKey('eventsimported', $output); + $this->assertArrayHasKey('eventsskipped', $output); + $this->assertArrayHasKey('eventsupdated', $output); + $this->assertArrayHasKey('eventsdeleted', $output); + $this->assertEquals(1, $output['eventsimported']); + $this->assertEquals(0, $output['eventsskipped']); + $this->assertEquals(0, $output['eventsupdated']); + $this->assertEquals(0, $output['eventsdeleted']); } /** diff --git a/calendar/upgrade.txt b/calendar/upgrade.txt index 38e32781934..18fa1537f74 100644 --- a/calendar/upgrade.txt +++ b/calendar/upgrade.txt @@ -5,6 +5,10 @@ information provided here is intended especially for developers. * The following external functions now accepts an optional parameter 'searchvalue' to search the events: - core_calendar_external::get_calendar_action_events_by_timesort - core_calendar_external::get_calendar_action_events_by_courses +* Added core_calendar_delete_subscription, which allows to delete the calendar subscription. +* The following functions have been deprecated because they were no longer used: + - calendar_process_subscription_row() + - calendar_import_icalendar_events() === 3.10 === * The core_calendar\local\event\value_objects\times_interface class now has new method get_usermidnight_time() which diff --git a/lang/en/calendar.php b/lang/en/calendar.php index d4a970880de..10b89b82c4e 100644 --- a/lang/en/calendar.php +++ b/lang/en/calendar.php @@ -39,11 +39,12 @@ $string['clickhide'] = 'click to hide'; $string['clickshow'] = 'click to show'; $string['colcalendar'] = 'Calendar'; $string['collastupdated'] = 'Last updated'; -$string['colpoll'] = 'Update'; +$string['colpoll'] = 'Refresh interval'; $string['colactions'] = 'Actions'; $string['commontasks'] = 'Options'; $string['confirmeventdelete'] = 'Are you sure you want to delete the "{$a}" event?'; $string['confirmeventseriesdelete'] = 'The "{$a->name}" event is part of a series. Do you want to delete just this event, or all {$a->count} events in the series?'; +$string['confirmsubscriptiondelete'] = 'Are you sure you want to delete the "{$a}" calendar subscription?'; $string['copycalendarurl'] = 'Copy calendar URL'; $string['copyurl'] = 'Copy URL'; $string['course'] = 'Course'; diff --git a/lib/classes/task/calendar_cron_task.php b/lib/classes/task/calendar_cron_task.php index 0eec2bfd2d8..e6d3bac868b 100644 --- a/lib/classes/task/calendar_cron_task.php +++ b/lib/classes/task/calendar_cron_task.php @@ -60,7 +60,16 @@ class calendar_cron_task extends scheduled_task { mtrace("Updating calendar subscription {$sub->name} in course {$sub->courseid}"); try { $log = calendar_update_subscription_events($sub->id); - mtrace(trim(strip_tags($log))); + mtrace($log['eventsimported'] . ' events were imported'); + mtrace($log['eventsskipped'] . ' events were skipped'); + mtrace($log['eventsupdated'] . ' events were updated'); + mtrace($log['eventsdeleted'] . ' events were deleted'); + if ($log['haserror']) { + mtrace('Failed to add event'); + foreach ($log['errors'] as $error) { + mtrace(trim(strip_tags($error))); + } + } } catch (\moodle_exception $ex) { mtrace('Error updating calendar subscription: ' . $ex->getMessage()); } diff --git a/lib/db/services.php b/lib/db/services.php index 712c46c8c39..fd3c0a1e842 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -293,6 +293,12 @@ $functions = array( 'type' => 'read', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ], + 'core_calendar_delete_subscription' => [ + 'classname' => 'core_calendar\external\subscription\delete', + 'description' => 'Delete the calendar subscription', + 'type' => 'write', + 'ajax' => true + ], 'core_cohort_add_cohort_members' => array( 'classname' => 'core_cohort_external', 'methodname' => 'add_cohort_members', diff --git a/lib/deprecatedlib.php b/lib/deprecatedlib.php index 53a9e733b6e..a2fd303b38b 100644 --- a/lib/deprecatedlib.php +++ b/lib/deprecatedlib.php @@ -3428,3 +3428,124 @@ function get_all_user_name_fields($returnsql = false, $tableprefix = null, $pref } return $alternatenames; } + +/** + * Update a subscription from the form data in one of the rows in the existing subscriptions table. + * + * @param int $subscriptionid The ID of the subscription we are acting upon. + * @param int $pollinterval The poll interval to use. + * @param int $action The action to be performed. One of update or remove. + * @throws dml_exception if invalid subscriptionid is provided + * @return string A log of the import progress, including errors + * @deprecated since Moodle 4.0 MDL-71953 + */ +function calendar_process_subscription_row($subscriptionid, $pollinterval, $action) { + debugging('calendar_process_subscription_row() is deprecated.', DEBUG_DEVELOPER); + // Fetch the subscription from the database making sure it exists. + $sub = calendar_get_subscription($subscriptionid); + + // Update or remove the subscription, based on action. + switch ($action) { + case CALENDAR_SUBSCRIPTION_UPDATE: + // Skip updating file subscriptions. + if (empty($sub->url)) { + break; + } + $sub->pollinterval = $pollinterval; + calendar_update_subscription($sub); + + // Update the events. + return "

" . get_string('subscriptionupdated', 'calendar', $sub->name) . "

" . + calendar_update_subscription_events($subscriptionid); + case CALENDAR_SUBSCRIPTION_REMOVE: + calendar_delete_subscription($subscriptionid); + return get_string('subscriptionremoved', 'calendar', $sub->name); + break; + default: + break; + } + return ''; +} + +/** + * Import events from an iCalendar object into a course calendar. + * + * @param iCalendar $ical The iCalendar object. + * @param int $unused Deprecated + * @param int $subscriptionid The subscription ID. + * @return string A log of the import progress, including errors. + */ +function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid = null) { + debugging('calendar_import_icalendar_events() is deprecated. Please use calendar_import_events_from_ical() instead.', + DEBUG_DEVELOPER); + global $DB; + + $return = ''; + $eventcount = 0; + $updatecount = 0; + $skippedcount = 0; + + // Large calendars take a while... + if (!CLI_SCRIPT) { + \core_php_time_limit::raise(300); + } + + // Grab the timezone from the iCalendar file to be used later. + if (isset($ical->properties['X-WR-TIMEZONE'][0]->value)) { + $timezone = $ical->properties['X-WR-TIMEZONE'][0]->value; + } else { + $timezone = 'UTC'; + } + + $icaluuids = []; + foreach ($ical->components['VEVENT'] as $event) { + $icaluuids[] = $event->properties['UID'][0]->value; + $res = calendar_add_icalendar_event($event, null, $subscriptionid, $timezone); + switch ($res) { + case CALENDAR_IMPORT_EVENT_UPDATED: + $updatecount++; + break; + case CALENDAR_IMPORT_EVENT_INSERTED: + $eventcount++; + break; + case CALENDAR_IMPORT_EVENT_SKIPPED: + $skippedcount++; + break; + case 0: + $return .= '

' . get_string('erroraddingevent', 'calendar') . ': '; + if (empty($event->properties['SUMMARY'])) { + $return .= '(' . get_string('notitle', 'calendar') . ')'; + } else { + $return .= $event->properties['SUMMARY'][0]->value; + } + $return .= "

\n"; + break; + } + } + + $return .= html_writer::start_tag('ul'); + $existing = $DB->get_field('event_subscriptions', 'lastupdated', ['id' => $subscriptionid]); + if (!empty($existing)) { + $eventsuuids = $DB->get_records_menu('event', ['subscriptionid' => $subscriptionid], '', 'id, uuid'); + + $icaleventscount = count($icaluuids); + $tobedeleted = []; + if (count($eventsuuids) > $icaleventscount) { + foreach ($eventsuuids as $eventid => $eventuuid) { + if (!in_array($eventuuid, $icaluuids)) { + $tobedeleted[] = $eventid; + } + } + if (!empty($tobedeleted)) { + $DB->delete_records_list('event', 'id', $tobedeleted); + $return .= html_writer::tag('li', get_string('eventsdeleted', 'calendar', count($tobedeleted))); + } + } + } + + $return .= html_writer::tag('li', get_string('eventsimported', 'calendar', $eventcount)); + $return .= html_writer::tag('li', get_string('eventsskipped', 'calendar', $skippedcount)); + $return .= html_writer::tag('li', get_string('eventsupdated', 'calendar', $updatecount)); + $return .= html_writer::end_tag('ul'); + return $return; +} diff --git a/lib/tests/calendar_cron_task_test.php b/lib/tests/calendar_cron_task_test.php index 8b9c3a177bd..0a2bf8360c0 100644 --- a/lib/tests/calendar_cron_task_test.php +++ b/lib/tests/calendar_cron_task_test.php @@ -58,9 +58,13 @@ class core_calendar_cron_task_testcase extends advanced_testcase { $subscription->lastupdated = 0; calendar_add_subscription($subscription); - $this->expectOutputRegex('/.* events were imported.* events were skipped.* events were updated/'); $task = new \core\task\calendar_cron_task(); + ob_start(); $task->execute(); + $output = ob_get_clean(); + $this->assertStringContainsString('events were imported', $output); + $this->assertStringContainsString('events were skipped', $output); + $this->assertStringContainsString('events were updated', $output); } /** diff --git a/lib/tests/upgradelib_test.php b/lib/tests/upgradelib_test.php index e6bc221f1c7..0e1ce803cd0 100644 --- a/lib/tests/upgradelib_test.php +++ b/lib/tests/upgradelib_test.php @@ -1276,7 +1276,7 @@ class upgradelib_test extends advanced_testcase { $ical->unserialize($calendar); // Import subscription events. - calendar_import_icalendar_events($ical, null, $id); + calendar_import_events_from_ical($ical, $id); // Subscription should have added 18 events. $eventscount = $DB->count_records('event'); diff --git a/version.php b/version.php index 49a05533dac..c8778b1e5b4 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2021101900.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2021101900.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.0dev+ (Build: 20211019)'; // Human-friendly version name