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
This commit is contained in:
Huong Nguyen 2021-08-01 21:18:51 +07:00
parent 8885e22a0b
commit bca6b06a1c
27 changed files with 670 additions and 165 deletions

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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<arguments.length&&arguments[1]!==void 0?arguments[1]:!1;return b.default.call([{methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:c}]}}])[0]};a.deleteEvent=c;var d=function(a){return b.default.call([{methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}}])[0]};a.getEventById=d;var e=function(a){return b.default.call([{methodname:"core_calendar_submit_create_update_form",args:{formdata:a}}])[0]};a.submitCreateUpdateForm=e;var f=function(a,c,d,e,f,g){var h=6<arguments.length&&arguments[6]!==void 0?arguments[6]:1,i=7<arguments.length&&arguments[7]!==void 0?arguments[7]:"month";return b.default.call([{methodname:"core_calendar_get_calendar_monthly_view",args:{year:a,month:c,courseid:d,categoryid:e,includenavigation:f,mini:g,day:h,view:i}}])[0]};a.getCalendarMonthData=f;var g=function(a,c,d,e,f){return b.default.call([{methodname:"core_calendar_get_calendar_day_view",args:{year:a,month:c,day:d,courseid:e,categoryid:f}}])[0]};a.getCalendarDayData=g;var h=function(a,c){return b.default.call([{methodname:"core_calendar_update_event_start_day",args:{eventid:a,daytimestamp:c}}])[0]};a.updateEventStartDay=h;var i=function(a,c){return b.default.call([{methodname:"core_calendar_get_calendar_upcoming_view",args:{courseid:a,categoryid:c}}])[0]};a.getCalendarUpcomingData=i;var j=function(a){return b.default.call([{methodname:"core_group_get_course_groups",args:{courseid:a}}])[0]};a.getCourseGroupsData=j});
define ("core_calendar/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.deleteSubscription=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<arguments.length&&arguments[1]!==void 0?arguments[1]:!1;return b.default.call([{methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:c}]}}])[0]};a.deleteEvent=c;var d=function(a){return b.default.call([{methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}}])[0]};a.getEventById=d;var e=function(a){return b.default.call([{methodname:"core_calendar_submit_create_update_form",args:{formdata:a}}])[0]};a.submitCreateUpdateForm=e;var f=function(a,c,d,e,f,g){var h=6<arguments.length&&arguments[6]!==void 0?arguments[6]:1,i=7<arguments.length&&arguments[7]!==void 0?arguments[7]:"month";return b.default.call([{methodname:"core_calendar_get_calendar_monthly_view",args:{year:a,month:c,courseid:d,categoryid:e,includenavigation:f,mini:g,day:h,view:i}}])[0]};a.getCalendarMonthData=f;var g=function(a,c,d,e,f){return b.default.call([{methodname:"core_calendar_get_calendar_day_view",args:{year:a,month:c,day:d,courseid:e,categoryid:f}}])[0]};a.getCalendarDayData=g;var h=function(a,c){return b.default.call([{methodname:"core_calendar_update_event_start_day",args:{eventid:a,daytimestamp:c}}])[0]};a.updateEventStartDay=h;var i=function(a,c){return b.default.call([{methodname:"core_calendar_get_calendar_upcoming_view",args:{courseid:a,categoryid:c}}])[0]};a.getCalendarUpcomingData=i;var j=function(a){return b.default.call([{methodname:"core_group_get_course_groups",args:{courseid:a}}])[0]};a.getCourseGroupsData=j;var k=function(a){return b.default.call([{methodname:"core_calendar_delete_subscription",args:{subscriptionid:a}}])[0]};a.deleteSubscription=k});
//# sourceMappingURL=repository.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.\n\n/**\n * This module is responsible for the calendar filter.\n *\n * @module core_calendar/calendar_selectors\n * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>\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"}
{"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 <http://www.gnu.org/licenses/>.\n\n/**\n * This module is responsible for the calendar filter.\n *\n * @module core_calendar/calendar_selectors\n * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>\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"}

View File

@ -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 <http://www.gnu.org/licenses/>.
/**
* A module to handle Delete/Update operations of the manage subscription page.
*
* @module core_calendar/manage_subscriptions
* @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
* @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();
};

View File

@ -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];
};

View File

@ -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"]',

View File

@ -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,

View File

@ -0,0 +1,104 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Calendar external API for deleting the subscription.
*
* @package core_calendar
* @category external
* @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
* @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 <huongnv13@gmail.com>
* @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()
]);
}
}

View File

@ -0,0 +1,54 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class to display collection select for the refresh interval.
*
* @package core_calendar
* @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
* @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'));
}
}
}

View File

@ -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);

View File

@ -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 "<p>" . get_string('subscriptionupdated', 'calendar', $sub->name) . "</p>" .
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 .= '<p>' . 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 .= "</p>\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());
}

View File

@ -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 = [];

View File

@ -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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@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}}
<p>{{#str}} subscriptionupdated, calendar, {{subscriptionname}} {{/str}}</p>
{{/subscriptionname}}
<ul>
<li>{{#str}} eventsimported, calendar, {{eventsimported}} {{/str}}</li>
<li>{{#str}} eventsskipped, calendar, {{eventsskipped}} {{/str}}</li>
<li>{{#str}} eventsupdated, calendar, {{eventsupdated}} {{/str}}</li>
<li>{{#str}} eventsdeleted, calendar, {{eventsdeleted}} {{/str}}</li>
</ul>
{{#haserror}}
<p>{{#str}} erroraddingevent, calendar {{/str}}</p>
<ul>
{{#eventserror}}
<li>{{.}}</li>
{{/eventserror}}
</ul>
{{/haserror}}

View File

@ -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"

View File

@ -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']);
}
/**

View File

@ -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

View File

@ -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';

View File

@ -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());
}

View File

@ -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',

View File

@ -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 "<p>" . get_string('subscriptionupdated', 'calendar', $sub->name) . "</p>" .
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 .= '<p>' . get_string('erroraddingevent', 'calendar') . ': ';
if (empty($event->properties['SUMMARY'])) {
$return .= '(' . get_string('notitle', 'calendar') . ')';
} else {
$return .= $event->properties['SUMMARY'][0]->value;
}
$return .= "</p>\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;
}

View File

@ -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);
}
/**

View File

@ -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');

View File

@ -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