From 1a972b06f174ada653c2a57454cc3f1ec2baafff Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Wed, 29 Apr 2020 22:22:10 +0200 Subject: [PATCH] MDL-58866 core_calendar: allow component events --- .../templates/event-list-item.mustache | 2 +- calendar/amd/build/view_manager.min.js | 2 +- calendar/amd/build/view_manager.min.js.map | 2 +- calendar/amd/src/view_manager.js | 3 ++- .../external/calendar_event_exporter.php | 1 - .../external/event_action_exporter.php | 18 +++++++++----- calendar/classes/external/event_exporter.php | 16 ------------- .../classes/external/event_exporter_base.php | 23 +++++++++++++++++- calendar/classes/local/event/container.php | 10 ++++---- .../local/event/entities/action_event.php | 8 +++++++ .../classes/local/event/entities/event.php | 18 +++++++++++++- .../local/event/entities/event_interface.php | 6 +++++ .../factories/event_abstract_factory.php | 8 ++++++- .../local/event/mappers/event_mapper.php | 3 +++ calendar/externallib.php | 17 ++++++------- calendar/lib.php | 17 +++++++++++-- calendar/renderer.php | 3 +++ calendar/templates/event_details.mustache | 17 ++++--------- calendar/templates/event_item.mustache | 11 +++++++-- .../templates/event_summary_modal.mustache | 12 +++++++--- calendar/tests/action_event_test.php | 8 +++++++ calendar/tests/event_mapper_test.php | 16 +++++++++++++ calendar/tests/event_test.php | 9 ++++--- calendar/tests/helpers.php | 3 ++- .../tests/repeat_event_collection_test.php | 3 ++- calendar/upgrade.txt | 5 ++++ lib/adminlib.php | 1 + lib/db/install.xml | 4 +++- lib/db/upgrade.php | 24 +++++++++++++++++++ version.php | 2 +- 30 files changed, 201 insertions(+), 71 deletions(-) diff --git a/blocks/timeline/templates/event-list-item.mustache b/blocks/timeline/templates/event-list-item.mustache index dc98508dcf3..205d67fcf12 100644 --- a/blocks/timeline/templates/event-list-item.mustache +++ b/blocks/timeline/templates/event-list-item.mustache @@ -56,7 +56,7 @@ {{#quote}}{{{course.fullnamedisplay}}}{{/quote}} {{#action.actionable}}
- {{{action.name}}} + {{{action.name}}} {{#action.showitemcount}} {{action.itemcount}} {{/action.showitemcount}} diff --git a/calendar/amd/build/view_manager.min.js b/calendar/amd/build/view_manager.min.js index fa3574a9b93..0e7cb63e937 100644 --- a/calendar/amd/build/view_manager.min.js +++ b/calendar/amd/build/view_manager.min.js @@ -1,2 +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/view_manager",["exports","jquery","core/templates","core/notification","core_calendar/repository","core_calendar/events","core_calendar/selectors","core/modal_factory","core/modal_events","core_calendar/summary_modal","core/custom_interaction_events","core/pending"],function(a,b,c,d,e,f,g,h,i,j,k,l){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=a.reloadCurrentUpcoming=a.changeDay=a.reloadCurrentDay=a.refreshDayContent=a.reloadCurrentMonth=a.changeMonth=a.refreshMonthContent=void 0;b=o(b);c=o(c);d=o(d);e=n(e);f=o(f);g=n(g);h=o(h);i=o(i);j=o(j);k=o(k);l=o(l);function m(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;m=function(){return a};return a}function n(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=m();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 o(a){return a&&a.__esModule?a:{default:a}}var p=function(a){a=(0,b.default)(a);a.on("click",g.links.eventLink,function(a){var b=a.target,c=null,d=null,e=new l.default("core_calendar/view_manager:eventLink:click");if(b.matches(g.actions.viewEvent)){c=b}else{c=b.closest(g.actions.viewEvent)}if(c){d=c.dataset.eventId}else{d=b.querySelector(g.actions.viewEvent).dataset.eventId}if(d){a.preventDefault();a.stopPropagation();A(d).then(e.resolve).catch()}else{e.resolve()}});a.on("click",g.links.navLink,function(b){var c=a.find(g.wrapper),d=c.data("view"),e=c.data("courseid"),f=c.data("categoryid"),h=b.currentTarget;if("month"===d){r(a,h.href,h.dataset.year,h.dataset.month,e,f,h.dataset.day);b.preventDefault()}else if("day"===d){v(a,h.href,h.dataset.year,h.dataset.month,h.dataset.day,e,f);b.preventDefault()}});var c=a.find(g.viewSelector);k.default.define(c,[k.default.events.activate]);c.on(k.default.events.activate,function(b){b.preventDefault();var c=b.target;if(c.classList.contains("active")){return}var e=c.dataset.view,f=c.dataset.year,g=c.dataset.month,h=c.dataset.day,i=c.dataset.courseid,j=c.dataset.categoryid;if("month"==e){q(a,f,g,i,j,a,"core_calendar/calendar_month",h).then(function(){return window.history.pushState({},"","?view=month")}).fail(d.default.exception)}else if("day"==e){t(a,f,g,h,i,j,a,"core_calendar/calendar_day").then(function(){return window.history.pushState({},"","?view=day")}).fail(d.default.exception)}else if("upcoming"==e){y(a,i,j,a,"core_calendar/calendar_upcoming").then(function(){return window.history.pushState({},"","?view=upcoming")}).fail(d.default.exception)}})},q=function(a,b,h,i,j){var k=5.\n\n/**\n * A javascript module to handler calendar view changes.\n *\n * @module core_calendar/view_manager\n * @package core_calendar\n * @copyright 2017 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\nimport * as CalendarRepository from 'core_calendar/repository';\nimport CalendarEvents from 'core_calendar/events';\nimport * as CalendarSelectors from 'core_calendar/selectors';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport SummaryModal from 'core_calendar/summary_modal';\nimport CustomEvents from 'core/custom_interaction_events';\nimport Pending from 'core/pending';\n\n/**\n * Register event listeners for the module.\n *\n * @param {object} root The root element.\n */\nconst registerEventListeners = (root) => {\n root = $(root);\n\n // Bind click events to event links.\n root.on('click', CalendarSelectors.links.eventLink, (e) => {\n const target = e.target;\n let eventLink = null;\n let eventId = null;\n const pendingPromise = new Pending('core_calendar/view_manager:eventLink:click');\n\n if (target.matches(CalendarSelectors.actions.viewEvent)) {\n eventLink = target;\n } else {\n eventLink = target.closest(CalendarSelectors.actions.viewEvent);\n }\n\n if (eventLink) {\n eventId = eventLink.dataset.eventId;\n } else {\n eventId = target.querySelector(CalendarSelectors.actions.viewEvent).dataset.eventId;\n }\n\n if (eventId) {\n // A link was found. Show the modal.\n\n e.preventDefault();\n // We've handled the event so stop it from bubbling\n // and causing the day click handler to fire.\n e.stopPropagation();\n\n renderEventSummaryModal(eventId)\n .then(pendingPromise.resolve)\n .catch();\n } else {\n pendingPromise.resolve();\n }\n });\n\n root.on('click', CalendarSelectors.links.navLink, (e) => {\n const wrapper = root.find(CalendarSelectors.wrapper);\n const view = wrapper.data('view');\n const courseId = wrapper.data('courseid');\n const categoryId = wrapper.data('categoryid');\n const link = e.currentTarget;\n\n if (view === 'month') {\n changeMonth(root, link.href, link.dataset.year, link.dataset.month, courseId, categoryId, link.dataset.day);\n e.preventDefault();\n } else if (view === 'day') {\n changeDay(root, link.href, link.dataset.year, link.dataset.month, link.dataset.day, courseId, categoryId);\n e.preventDefault();\n }\n });\n\n const viewSelector = root.find(CalendarSelectors.viewSelector);\n CustomEvents.define(viewSelector, [CustomEvents.events.activate]);\n viewSelector.on(\n CustomEvents.events.activate,\n (e) => {\n e.preventDefault();\n\n const option = e.target;\n if (option.classList.contains('active')) {\n return;\n }\n\n const view = option.dataset.view,\n year = option.dataset.year,\n month = option.dataset.month,\n day = option.dataset.day,\n courseId = option.dataset.courseid,\n categoryId = option.dataset.categoryid;\n\n if (view == 'month') {\n refreshMonthContent(root, year, month, courseId, categoryId, root, 'core_calendar/calendar_month', day)\n .then(() => {\n return window.history.pushState({}, '', '?view=month');\n }).fail(Notification.exception);\n } else if (view == 'day') {\n refreshDayContent(root, year, month, day, courseId, categoryId, root, 'core_calendar/calendar_day')\n .then(() => {\n return window.history.pushState({}, '', '?view=day');\n }).fail(Notification.exception);\n } else if (view == 'upcoming') {\n reloadCurrentUpcoming(root, courseId, categoryId, root, 'core_calendar/calendar_upcoming')\n .then(() => {\n return window.history.pushState({}, '', '?view=upcoming');\n }).fail(Notification.exception);\n }\n }\n );\n};\n\n/**\n * Refresh the month content.\n *\n * @param {object} root The root element.\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} courseId The id of the course whose events are shown\n * @param {number} categoryId The id of the category whose events are shown\n * @param {object} target The element being replaced. If not specified, the calendarwrapper is used.\n * @param {string} template The template to be rendered.\n * @param {number} day Day (optional)\n * @return {promise}\n */\nexport const refreshMonthContent = (root, year, month, courseId, categoryId, target = null, template = '', day = 1) => {\n startLoading(root);\n\n target = target || root.find(CalendarSelectors.wrapper);\n template = template || root.attr('data-template');\n M.util.js_pending([root.get('id'), year, month, courseId].join('-'));\n const includenavigation = root.data('includenavigation');\n const mini = root.data('mini');\n return CalendarRepository.getCalendarMonthData(year, month, courseId, categoryId, includenavigation, mini, day)\n .then(context => {\n context.viewingmonth = true;\n return Templates.render(template, context);\n })\n .then((html, js) => {\n return Templates.replaceNode(target, html, js);\n })\n .then(() => {\n document.querySelector('body').dispatchEvent(new Event(CalendarEvents.viewUpdated));\n return;\n })\n .always(() => {\n M.util.js_complete([root.get('id'), year, month, courseId].join('-'));\n return stopLoading(root);\n })\n .fail(Notification.exception);\n};\n\n/**\n * Handle changes to the current calendar view.\n *\n * @param {object} root The container element\n * @param {string} url The calendar url to be shown\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} courseId The id of the course whose events are shown\n * @param {number} categoryId The id of the category whose events are shown\n * @param {number} day Day (optional)\n * @return {promise}\n */\nexport const changeMonth = (root, url, year, month, courseId, categoryId, day = 1) => {\n return refreshMonthContent(root, year, month, courseId, categoryId, null, '', day)\n .then((...args) => {\n if (url.length && url !== '#') {\n window.history.pushState({}, '', url);\n }\n return args;\n })\n .then((...args) => {\n $('body').trigger(CalendarEvents.monthChanged, [year, month, courseId, categoryId]);\n return args;\n });\n};\n\n/**\n * Reload the current month view data.\n *\n * @param {object} root The container element.\n * @param {number} courseId The course id.\n * @param {number} categoryId The id of the category whose events are shown\n * @return {promise}\n */\nexport const reloadCurrentMonth = (root, courseId = 0, categoryId = 0) => {\n const year = root.find(CalendarSelectors.wrapper).data('year');\n const month = root.find(CalendarSelectors.wrapper).data('month');\n const day = root.find(CalendarSelectors.wrapper).data('day');\n\n courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');\n categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');\n\n return refreshMonthContent(root, year, month, courseId, categoryId, null, '', day);\n};\n\n\n/**\n * Refresh the day content.\n *\n * @param {object} root The root element.\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} day Day\n * @param {number} courseId The id of the course whose events are shown\n * @param {number} categoryId The id of the category whose events are shown\n * @param {object} target The element being replaced. If not specified, the calendarwrapper is used.\n * @param {string} template The template to be rendered.\n *\n * @return {promise}\n */\nexport const refreshDayContent = (root, year, month, day, courseId, categoryId, target = null, template = '') => {\n startLoading(root);\n\n target = target || root.find(CalendarSelectors.wrapper);\n template = template || root.attr('data-template');\n M.util.js_pending([root.get('id'), year, month, day, courseId, categoryId].join('-'));\n const includenavigation = root.data('includenavigation');\n return CalendarRepository.getCalendarDayData(year, month, day, courseId, categoryId, includenavigation)\n .then((context) => {\n context.viewingday = true;\n return Templates.render(template, context);\n })\n .then((html, js) => {\n return Templates.replaceNode(target, html, js);\n })\n .then(() => {\n document.querySelector('body').dispatchEvent(new Event(CalendarEvents.viewUpdated));\n return;\n })\n .always(() => {\n M.util.js_complete([root.get('id'), year, month, day, courseId, categoryId].join('-'));\n return stopLoading(root);\n })\n .fail(Notification.exception);\n};\n\n/**\n * Reload the current day view data.\n *\n * @param {object} root The container element.\n * @param {number} courseId The course id.\n * @param {number} categoryId The id of the category whose events are shown\n * @return {promise}\n */\nexport const reloadCurrentDay = (root, courseId = 0, categoryId = 0) => {\n const wrapper = root.find(CalendarSelectors.wrapper);\n const year = wrapper.data('year');\n const month = wrapper.data('month');\n const day = wrapper.data('day');\n\n courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');\n categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');\n\n return refreshDayContent(root, year, month, day, courseId, categoryId);\n};\n\n/**\n * Handle changes to the current calendar view.\n *\n * @param {object} root The root element.\n * @param {String} url The calendar url to be shown\n * @param {Number} year Year\n * @param {Number} month Month\n * @param {Number} day Day\n * @param {Number} courseId The id of the course whose events are shown\n * @param {Number} categoryId The id of the category whose events are shown\n * @return {promise}\n */\nexport const changeDay = (root, url, year, month, day, courseId, categoryId) => {\n return refreshDayContent(root, year, month, day, courseId, categoryId)\n .then((...args) => {\n if (url.length && url !== '#') {\n window.history.pushState({}, '', url);\n }\n return args;\n })\n .then((...args) => {\n $('body').trigger(CalendarEvents.dayChanged, [year, month, courseId, categoryId]);\n return args;\n });\n};\n\n/**\n * Set the element state to loading.\n *\n * @param {object} root The container element\n * @method startLoading\n */\nconst startLoading = (root) => {\n const loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);\n\n loadingIconContainer.removeClass('hidden');\n};\n\n/**\n * Remove the loading state from the element.\n *\n * @param {object} root The container element\n * @method stopLoading\n */\nconst stopLoading = (root) => {\n const loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);\n\n loadingIconContainer.addClass('hidden');\n};\n\n/**\n * Reload the current month view data.\n *\n * @param {object} root The container element.\n * @param {number} courseId The course id.\n * @param {number} categoryId The id of the category whose events are shown\n * @param {object} target The element being replaced. If not specified, the calendarwrapper is used.\n * @param {string} template The template to be rendered.\n * @return {promise}\n */\nexport const reloadCurrentUpcoming = (root, courseId = 0, categoryId = 0, target = null, template = '') => {\n startLoading(root);\n\n target = target || root.find(CalendarSelectors.wrapper);\n template = template || root.attr('data-template');\n courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');\n categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');\n\n return CalendarRepository.getCalendarUpcomingData(courseId, categoryId)\n .then((context) => {\n context.viewingupcoming = true;\n return Templates.render(template, context);\n })\n .then((html, js) => {\n return Templates.replaceNode(target, html, js);\n })\n .then(() => {\n document.querySelector('body').dispatchEvent(new Event(CalendarEvents.viewUpdated));\n return;\n })\n .always(function() {\n return stopLoading(root);\n })\n .fail(Notification.exception);\n};\n\n/**\n * Get the CSS class to apply for the given event type.\n *\n * @param {string} eventType The calendar event type\n * @return {string}\n */\nconst getEventTypeClassFromType = (eventType) => {\n return 'calendar_event_' + eventType;\n};\n\n/**\n * Render the event summary modal.\n *\n * @param {Number} eventId The calendar event id.\n * @returns {Promise}\n */\nconst renderEventSummaryModal = (eventId) => {\n const pendingPromise = new Pending('core_calendar/view_manager:renderEventSummaryModal');\n\n // Calendar repository promise.\n return CalendarRepository.getEventById(eventId)\n .then((getEventResponse) => {\n if (!getEventResponse.event) {\n throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);\n }\n\n return getEventResponse.event;\n })\n .then(eventData => {\n // Build the modal parameters from the event data.\n const modalParams = {\n title: eventData.name,\n type: SummaryModal.TYPE,\n body: Templates.render('core_calendar/event_summary_body', eventData),\n templateContext: {\n canedit: eventData.canedit,\n candelete: eventData.candelete,\n headerclasses: getEventTypeClassFromType(eventData.normalisedeventtype),\n isactionevent: eventData.isactionevent,\n url: eventData.url\n }\n };\n\n // Create the modal.\n return ModalFactory.create(modalParams);\n })\n .then(modal => {\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Destroy when hidden.\n modal.destroy();\n });\n\n // Finally, render the modal!\n modal.show();\n\n return modal;\n })\n .then(modal => {\n pendingPromise.resolve();\n\n return modal;\n })\n .catch(Notification.exception);\n};\n\nexport const init = (root, view) => {\n registerEventListeners(root, view);\n};\n"],"file":"view_manager.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/view_manager.js"],"names":["registerEventListeners","root","on","CalendarSelectors","links","eventLink","e","target","eventId","pendingPromise","Pending","matches","actions","viewEvent","closest","dataset","querySelector","preventDefault","stopPropagation","renderEventSummaryModal","then","resolve","catch","navLink","wrapper","find","view","data","courseId","categoryId","link","currentTarget","changeMonth","href","year","month","day","changeDay","viewSelector","CustomEvents","define","events","activate","option","classList","contains","courseid","categoryid","refreshMonthContent","window","history","pushState","fail","Notification","exception","refreshDayContent","reloadCurrentUpcoming","template","startLoading","attr","M","util","js_pending","get","join","includenavigation","mini","CalendarRepository","getCalendarMonthData","context","viewingmonth","Templates","render","html","js","replaceNode","document","dispatchEvent","Event","CalendarEvents","viewUpdated","always","js_complete","stopLoading","url","length","args","trigger","monthChanged","reloadCurrentMonth","getCalendarDayData","viewingday","reloadCurrentDay","dayChanged","loadingIconContainer","containers","loadingIcon","removeClass","addClass","getCalendarUpcomingData","viewingupcoming","getEventTypeClassFromType","eventType","getEventById","getEventResponse","event","Error","eventData","modalParams","title","name","type","SummaryModal","TYPE","body","templateContext","canedit","candelete","headerclasses","normalisedeventtype","isactionevent","action","ModalFactory","create","modal","getRoot","ModalEvents","hidden","destroy","show","init"],"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 handler calendar view changes.\n *\n * @module core_calendar/view_manager\n * @package core_calendar\n * @copyright 2017 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\nimport * as CalendarRepository from 'core_calendar/repository';\nimport CalendarEvents from 'core_calendar/events';\nimport * as CalendarSelectors from 'core_calendar/selectors';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport SummaryModal from 'core_calendar/summary_modal';\nimport CustomEvents from 'core/custom_interaction_events';\nimport Pending from 'core/pending';\n\n/**\n * Register event listeners for the module.\n *\n * @param {object} root The root element.\n */\nconst registerEventListeners = (root) => {\n root = $(root);\n\n // Bind click events to event links.\n root.on('click', CalendarSelectors.links.eventLink, (e) => {\n const target = e.target;\n let eventLink = null;\n let eventId = null;\n const pendingPromise = new Pending('core_calendar/view_manager:eventLink:click');\n\n if (target.matches(CalendarSelectors.actions.viewEvent)) {\n eventLink = target;\n } else {\n eventLink = target.closest(CalendarSelectors.actions.viewEvent);\n }\n\n if (eventLink) {\n eventId = eventLink.dataset.eventId;\n } else {\n eventId = target.querySelector(CalendarSelectors.actions.viewEvent).dataset.eventId;\n }\n\n if (eventId) {\n // A link was found. Show the modal.\n\n e.preventDefault();\n // We've handled the event so stop it from bubbling\n // and causing the day click handler to fire.\n e.stopPropagation();\n\n renderEventSummaryModal(eventId)\n .then(pendingPromise.resolve)\n .catch();\n } else {\n pendingPromise.resolve();\n }\n });\n\n root.on('click', CalendarSelectors.links.navLink, (e) => {\n const wrapper = root.find(CalendarSelectors.wrapper);\n const view = wrapper.data('view');\n const courseId = wrapper.data('courseid');\n const categoryId = wrapper.data('categoryid');\n const link = e.currentTarget;\n\n if (view === 'month') {\n changeMonth(root, link.href, link.dataset.year, link.dataset.month, courseId, categoryId, link.dataset.day);\n e.preventDefault();\n } else if (view === 'day') {\n changeDay(root, link.href, link.dataset.year, link.dataset.month, link.dataset.day, courseId, categoryId);\n e.preventDefault();\n }\n });\n\n const viewSelector = root.find(CalendarSelectors.viewSelector);\n CustomEvents.define(viewSelector, [CustomEvents.events.activate]);\n viewSelector.on(\n CustomEvents.events.activate,\n (e) => {\n e.preventDefault();\n\n const option = e.target;\n if (option.classList.contains('active')) {\n return;\n }\n\n const view = option.dataset.view,\n year = option.dataset.year,\n month = option.dataset.month,\n day = option.dataset.day,\n courseId = option.dataset.courseid,\n categoryId = option.dataset.categoryid;\n\n if (view == 'month') {\n refreshMonthContent(root, year, month, courseId, categoryId, root, 'core_calendar/calendar_month', day)\n .then(() => {\n return window.history.pushState({}, '', '?view=month');\n }).fail(Notification.exception);\n } else if (view == 'day') {\n refreshDayContent(root, year, month, day, courseId, categoryId, root, 'core_calendar/calendar_day')\n .then(() => {\n return window.history.pushState({}, '', '?view=day');\n }).fail(Notification.exception);\n } else if (view == 'upcoming') {\n reloadCurrentUpcoming(root, courseId, categoryId, root, 'core_calendar/calendar_upcoming')\n .then(() => {\n return window.history.pushState({}, '', '?view=upcoming');\n }).fail(Notification.exception);\n }\n }\n );\n};\n\n/**\n * Refresh the month content.\n *\n * @param {object} root The root element.\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} courseId The id of the course whose events are shown\n * @param {number} categoryId The id of the category whose events are shown\n * @param {object} target The element being replaced. If not specified, the calendarwrapper is used.\n * @param {string} template The template to be rendered.\n * @param {number} day Day (optional)\n * @return {promise}\n */\nexport const refreshMonthContent = (root, year, month, courseId, categoryId, target = null, template = '', day = 1) => {\n startLoading(root);\n\n target = target || root.find(CalendarSelectors.wrapper);\n template = template || root.attr('data-template');\n M.util.js_pending([root.get('id'), year, month, courseId].join('-'));\n const includenavigation = root.data('includenavigation');\n const mini = root.data('mini');\n return CalendarRepository.getCalendarMonthData(year, month, courseId, categoryId, includenavigation, mini, day)\n .then(context => {\n context.viewingmonth = true;\n return Templates.render(template, context);\n })\n .then((html, js) => {\n return Templates.replaceNode(target, html, js);\n })\n .then(() => {\n document.querySelector('body').dispatchEvent(new Event(CalendarEvents.viewUpdated));\n return;\n })\n .always(() => {\n M.util.js_complete([root.get('id'), year, month, courseId].join('-'));\n return stopLoading(root);\n })\n .fail(Notification.exception);\n};\n\n/**\n * Handle changes to the current calendar view.\n *\n * @param {object} root The container element\n * @param {string} url The calendar url to be shown\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} courseId The id of the course whose events are shown\n * @param {number} categoryId The id of the category whose events are shown\n * @param {number} day Day (optional)\n * @return {promise}\n */\nexport const changeMonth = (root, url, year, month, courseId, categoryId, day = 1) => {\n return refreshMonthContent(root, year, month, courseId, categoryId, null, '', day)\n .then((...args) => {\n if (url.length && url !== '#') {\n window.history.pushState({}, '', url);\n }\n return args;\n })\n .then((...args) => {\n $('body').trigger(CalendarEvents.monthChanged, [year, month, courseId, categoryId]);\n return args;\n });\n};\n\n/**\n * Reload the current month view data.\n *\n * @param {object} root The container element.\n * @param {number} courseId The course id.\n * @param {number} categoryId The id of the category whose events are shown\n * @return {promise}\n */\nexport const reloadCurrentMonth = (root, courseId = 0, categoryId = 0) => {\n const year = root.find(CalendarSelectors.wrapper).data('year');\n const month = root.find(CalendarSelectors.wrapper).data('month');\n const day = root.find(CalendarSelectors.wrapper).data('day');\n\n courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');\n categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');\n\n return refreshMonthContent(root, year, month, courseId, categoryId, null, '', day);\n};\n\n\n/**\n * Refresh the day content.\n *\n * @param {object} root The root element.\n * @param {number} year Year\n * @param {number} month Month\n * @param {number} day Day\n * @param {number} courseId The id of the course whose events are shown\n * @param {number} categoryId The id of the category whose events are shown\n * @param {object} target The element being replaced. If not specified, the calendarwrapper is used.\n * @param {string} template The template to be rendered.\n *\n * @return {promise}\n */\nexport const refreshDayContent = (root, year, month, day, courseId, categoryId, target = null, template = '') => {\n startLoading(root);\n\n target = target || root.find(CalendarSelectors.wrapper);\n template = template || root.attr('data-template');\n M.util.js_pending([root.get('id'), year, month, day, courseId, categoryId].join('-'));\n const includenavigation = root.data('includenavigation');\n return CalendarRepository.getCalendarDayData(year, month, day, courseId, categoryId, includenavigation)\n .then((context) => {\n context.viewingday = true;\n return Templates.render(template, context);\n })\n .then((html, js) => {\n return Templates.replaceNode(target, html, js);\n })\n .then(() => {\n document.querySelector('body').dispatchEvent(new Event(CalendarEvents.viewUpdated));\n return;\n })\n .always(() => {\n M.util.js_complete([root.get('id'), year, month, day, courseId, categoryId].join('-'));\n return stopLoading(root);\n })\n .fail(Notification.exception);\n};\n\n/**\n * Reload the current day view data.\n *\n * @param {object} root The container element.\n * @param {number} courseId The course id.\n * @param {number} categoryId The id of the category whose events are shown\n * @return {promise}\n */\nexport const reloadCurrentDay = (root, courseId = 0, categoryId = 0) => {\n const wrapper = root.find(CalendarSelectors.wrapper);\n const year = wrapper.data('year');\n const month = wrapper.data('month');\n const day = wrapper.data('day');\n\n courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');\n categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');\n\n return refreshDayContent(root, year, month, day, courseId, categoryId);\n};\n\n/**\n * Handle changes to the current calendar view.\n *\n * @param {object} root The root element.\n * @param {String} url The calendar url to be shown\n * @param {Number} year Year\n * @param {Number} month Month\n * @param {Number} day Day\n * @param {Number} courseId The id of the course whose events are shown\n * @param {Number} categoryId The id of the category whose events are shown\n * @return {promise}\n */\nexport const changeDay = (root, url, year, month, day, courseId, categoryId) => {\n return refreshDayContent(root, year, month, day, courseId, categoryId)\n .then((...args) => {\n if (url.length && url !== '#') {\n window.history.pushState({}, '', url);\n }\n return args;\n })\n .then((...args) => {\n $('body').trigger(CalendarEvents.dayChanged, [year, month, courseId, categoryId]);\n return args;\n });\n};\n\n/**\n * Set the element state to loading.\n *\n * @param {object} root The container element\n * @method startLoading\n */\nconst startLoading = (root) => {\n const loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);\n\n loadingIconContainer.removeClass('hidden');\n};\n\n/**\n * Remove the loading state from the element.\n *\n * @param {object} root The container element\n * @method stopLoading\n */\nconst stopLoading = (root) => {\n const loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);\n\n loadingIconContainer.addClass('hidden');\n};\n\n/**\n * Reload the current month view data.\n *\n * @param {object} root The container element.\n * @param {number} courseId The course id.\n * @param {number} categoryId The id of the category whose events are shown\n * @param {object} target The element being replaced. If not specified, the calendarwrapper is used.\n * @param {string} template The template to be rendered.\n * @return {promise}\n */\nexport const reloadCurrentUpcoming = (root, courseId = 0, categoryId = 0, target = null, template = '') => {\n startLoading(root);\n\n target = target || root.find(CalendarSelectors.wrapper);\n template = template || root.attr('data-template');\n courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');\n categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');\n\n return CalendarRepository.getCalendarUpcomingData(courseId, categoryId)\n .then((context) => {\n context.viewingupcoming = true;\n return Templates.render(template, context);\n })\n .then((html, js) => {\n return Templates.replaceNode(target, html, js);\n })\n .then(() => {\n document.querySelector('body').dispatchEvent(new Event(CalendarEvents.viewUpdated));\n return;\n })\n .always(function() {\n return stopLoading(root);\n })\n .fail(Notification.exception);\n};\n\n/**\n * Get the CSS class to apply for the given event type.\n *\n * @param {string} eventType The calendar event type\n * @return {string}\n */\nconst getEventTypeClassFromType = (eventType) => {\n return 'calendar_event_' + eventType;\n};\n\n/**\n * Render the event summary modal.\n *\n * @param {Number} eventId The calendar event id.\n * @returns {Promise}\n */\nconst renderEventSummaryModal = (eventId) => {\n const pendingPromise = new Pending('core_calendar/view_manager:renderEventSummaryModal');\n\n // Calendar repository promise.\n return CalendarRepository.getEventById(eventId)\n .then((getEventResponse) => {\n if (!getEventResponse.event) {\n throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);\n }\n\n return getEventResponse.event;\n })\n .then(eventData => {\n // Build the modal parameters from the event data.\n const modalParams = {\n title: eventData.name,\n type: SummaryModal.TYPE,\n body: Templates.render('core_calendar/event_summary_body', eventData),\n templateContext: {\n canedit: eventData.canedit,\n candelete: eventData.candelete,\n headerclasses: getEventTypeClassFromType(eventData.normalisedeventtype),\n isactionevent: eventData.isactionevent,\n url: eventData.url,\n action: eventData.action\n }\n };\n\n // Create the modal.\n return ModalFactory.create(modalParams);\n })\n .then(modal => {\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Destroy when hidden.\n modal.destroy();\n });\n\n // Finally, render the modal!\n modal.show();\n\n return modal;\n })\n .then(modal => {\n pendingPromise.resolve();\n\n return modal;\n })\n .catch(Notification.exception);\n};\n\nexport const init = (root, view) => {\n registerEventListeners(root, view);\n};\n"],"file":"view_manager.min.js"} \ No newline at end of file diff --git a/calendar/amd/src/view_manager.js b/calendar/amd/src/view_manager.js index d89d148755f..e5edf31dbbf 100644 --- a/calendar/amd/src/view_manager.js +++ b/calendar/amd/src/view_manager.js @@ -403,7 +403,8 @@ const renderEventSummaryModal = (eventId) => { candelete: eventData.candelete, headerclasses: getEventTypeClassFromType(eventData.normalisedeventtype), isactionevent: eventData.isactionevent, - url: eventData.url + url: eventData.url, + action: eventData.action } }; diff --git a/calendar/classes/external/calendar_event_exporter.php b/calendar/classes/external/calendar_event_exporter.php index ea32de3521a..7d548f918a4 100644 --- a/calendar/classes/external/calendar_event_exporter.php +++ b/calendar/classes/external/calendar_event_exporter.php @@ -109,7 +109,6 @@ class calendar_event_exporter extends event_exporter_base { } else if ($event->get_type() == 'category') { $url = $event->get_category()->get_proxied_instance()->get_view_link(); } else { - // TODO MDL-58866 We do not have any way to find urls for events outside of course modules. $url = course_get_url($hascourse ? $course : SITEID); } diff --git a/calendar/classes/external/event_action_exporter.php b/calendar/classes/external/event_action_exporter.php index 097adbcc825..7711071195d 100644 --- a/calendar/classes/external/event_action_exporter.php +++ b/calendar/classes/external/event_action_exporter.php @@ -49,7 +49,7 @@ class event_action_exporter extends exporter { public function __construct(action_interface $action, $related = []) { $data = new \stdClass(); $data->name = $action->get_name(); - $data->url = $action->get_url()->out(true); + $data->url = $action->get_url()->out(false); $data->itemcount = $action->get_item_count(); $data->actionable = $action->is_actionable(); @@ -90,17 +90,14 @@ class event_action_exporter extends exporter { protected function get_other_values(renderer_base $output) { $event = $this->related['event']; - if (!$event->get_course_module()) { - // TODO MDL-58866 Only activity modules currently support this callback. + if (!$event->get_component()) { return ['showitemcount' => false]; } - $modulename = $event->get_course_module()->get('modname'); - $component = 'mod_' . $modulename; $showitemcountcallback = 'core_calendar_event_action_shows_item_count'; $mapper = container::get_event_mapper(); $calevent = $mapper->from_event_to_legacy_event($event); $params = [$calevent, $this->data->itemcount]; - $showitemcount = component_callback($component, $showitemcountcallback, $params, false); + $showitemcount = component_callback($event->get_component(), $showitemcountcallback, $params, false); // Prepare other values data. $data = [ @@ -120,4 +117,13 @@ class event_action_exporter extends exporter { 'event' => '\\core_calendar\\local\\event\\entities\\event_interface' ]; } + + /** + * Magic method returning parameters for formatting 'name' property + * + * @return bool[] + */ + protected function get_format_parameters_for_name() { + return ['escape' => false]; + } } diff --git a/calendar/classes/external/event_exporter.php b/calendar/classes/external/event_exporter.php index 015d71acb77..1bbe0b7356a 100644 --- a/calendar/classes/external/event_exporter.php +++ b/calendar/classes/external/event_exporter.php @@ -51,10 +51,6 @@ class event_exporter extends event_exporter_base { $values = parent::define_other_properties(); $values['url'] = ['type' => PARAM_URL]; - $values['action'] = [ - 'type' => event_action_exporter::read_properties_definition(), - 'optional' => true, - ]; return $values; } @@ -86,7 +82,6 @@ class event_exporter extends event_exporter_base { } else if ($event->get_type() == 'course') { $url = \course_get_url($this->related['course'] ?: SITEID); } else { - // TODO MDL-58866 We do not have any way to find urls for events outside of course modules. $url = \course_get_url($this->related['course'] ?: SITEID); } $values['url'] = $url->out(false); @@ -95,17 +90,6 @@ class event_exporter extends event_exporter_base { $legacyevent = container::get_event_mapper()->from_event_to_legacy_event($event); $values['formattedtime'] = calendar_format_event_time($legacyevent, time(), null, false); - if ($event instanceof action_event_interface) { - $actionrelated = [ - 'context' => $context, - 'event' => $event - ]; - $actionexporter = new event_action_exporter($event->get_action(), $actionrelated); - $values['action'] = $actionexporter->export($output); - } - - - return $values; } } diff --git a/calendar/classes/external/event_exporter_base.php b/calendar/classes/external/event_exporter_base.php index f761f705bcd..ed241f21e1a 100644 --- a/calendar/classes/external/event_exporter_base.php +++ b/calendar/classes/external/event_exporter_base.php @@ -89,6 +89,7 @@ class event_exporter_base extends exporter { $data->timesort = $event->get_times()->get_sort_time()->getTimestamp(); $data->visible = $event->is_visible() ? 1 : 0; $data->timemodified = $event->get_times()->get_modified_time()->getTimestamp(); + $data->component = $event->get_component(); if ($repeats = $event->get_repeats()) { $data->repeatid = $repeats->get_id(); @@ -160,6 +161,12 @@ class event_exporter_base extends exporter { 'default' => null, 'null' => NULL_ALLOWED ], + 'component' => [ + 'type' => PARAM_COMPONENT, + 'optional' => true, + 'default' => null, + 'null' => NULL_ALLOWED + ], 'modulename' => [ 'type' => PARAM_TEXT, 'optional' => true, @@ -242,6 +249,10 @@ class event_exporter_base extends exporter { 'normalisedeventtypetext' => [ 'type' => PARAM_TEXT ], + 'action' => [ + 'type' => event_action_exporter::read_properties_definition(), + 'optional' => true, + ], ]; } @@ -291,7 +302,7 @@ class event_exporter_base extends exporter { $values['category'] = $categorysummaryexporter->export($output); } - if ($course) { + if ($course && $course->id != SITEID) { $coursesummaryexporter = new course_summary_exporter($course, ['context' => $context]); $values['course'] = $coursesummaryexporter->export($output); } @@ -319,6 +330,16 @@ class event_exporter_base extends exporter { ['context' => \context_course::instance($event->get_course()->get('id'))]); } + if ($event instanceof action_event_interface) { + // Export event action if applicable. + $actionrelated = [ + 'context' => $this->related['context'], + 'event' => $event + ]; + $actionexporter = new event_action_exporter($event->get_action(), $actionrelated); + $values['action'] = $actionexporter->export($output); + } + return $values; } diff --git a/calendar/classes/local/event/container.php b/calendar/classes/local/event/container.php index 4b7a850f40a..76f7736399f 100644 --- a/calendar/classes/local/event/container.php +++ b/calendar/classes/local/event/container.php @@ -279,7 +279,7 @@ class container { // of the event class. $mapper = self::$eventmapper; $action = null; - if ($event->get_course_module()) { + if ($event->get_component()) { $requestinguserid = self::get_requesting_user(); $legacyevent = $mapper->from_event_to_legacy_event($event); // We know for a fact that the the requesting user might be different from the logged in user, @@ -288,10 +288,9 @@ class container { $legacyevent->userid = $requestinguserid; } - // TODO MDL-58866 Only activity modules currently support this callback. // Any other event will not be displayed on the dashboard. $action = component_callback( - 'mod_' . $event->get_course_module()->get('modname'), + $event->get_component(), 'core_calendar_provide_event_action', [ $legacyevent, @@ -322,7 +321,7 @@ class container { public static function apply_component_is_event_visible(event_interface $event) { $mapper = self::$eventmapper; $eventvisible = null; - if ($event->get_course_module()) { + if ($event->get_component()) { $requestinguserid = self::get_requesting_user(); $legacyevent = $mapper->from_event_to_legacy_event($event); // We know for a fact that the the requesting user might be different from the logged in user, @@ -331,9 +330,8 @@ class container { $legacyevent->userid = $requestinguserid; } - // TODO MDL-58866 Only activity modules currently support this callback. $eventvisible = component_callback( - 'mod_' . $event->get_course_module()->get('modname'), + $event->get_component(), 'core_calendar_is_event_visible', [ $legacyevent, diff --git a/calendar/classes/local/event/entities/action_event.php b/calendar/classes/local/event/entities/action_event.php index 6c8b0f1797b..57b7631c8d6 100644 --- a/calendar/classes/local/event/entities/action_event.php +++ b/calendar/classes/local/event/entities/action_event.php @@ -125,4 +125,12 @@ class action_event implements action_event_interface { public function get_action() { return $this->action; } + + /** + * Event component + * @return string + */ + public function get_component() { + return $this->event->get_component(); + } } diff --git a/calendar/classes/local/event/entities/event.php b/calendar/classes/local/event/entities/event.php index fdfee976161..b3a7c748768 100644 --- a/calendar/classes/local/event/entities/event.php +++ b/calendar/classes/local/event/entities/event.php @@ -102,6 +102,11 @@ class event implements event_interface { */ protected $visible; + /** + * @var string $component + */ + protected $component; + /** * @var proxy_interface $subscription Subscription for this event. */ @@ -124,6 +129,7 @@ class event implements event_interface { * @param bool $visible The event's visibility. True for visible, false for invisible. * @param proxy_interface $subscription The event's subscription. * @param string $location The event's location. + * @param string $component The event's component. */ public function __construct( $id, @@ -139,7 +145,8 @@ class event implements event_interface { times_interface $times, $visible, proxy_interface $subscription = null, - $location = null + $location = null, + $component = null ) { $this->id = $id; $this->name = $name; @@ -155,6 +162,7 @@ class event implements event_interface { $this->times = $times; $this->visible = $visible; $this->subscription = $subscription; + $this->component = $component; } public function get_id() { @@ -212,4 +220,12 @@ class event implements event_interface { public function is_visible() { return $this->visible; } + + /** + * Resolved event component (frankenstyle name of activity module or the component) + * @return string|null + */ + public function get_component() { + return $this->get_course_module() ? 'mod_' . $this->get_course_module()->get('modname') : $this->component; + } } diff --git a/calendar/classes/local/event/entities/event_interface.php b/calendar/classes/local/event/entities/event_interface.php index 5561f622f58..29e9d1c287f 100644 --- a/calendar/classes/local/event/entities/event_interface.php +++ b/calendar/classes/local/event/entities/event_interface.php @@ -133,4 +133,10 @@ interface event_interface { * @return bool true if the event is visible, false otherwise */ public function is_visible(); + + /** + * Resolved event component (frankenstyle name of activity module or the component) + * @return string|null + */ + public function get_component(); } diff --git a/calendar/classes/local/event/factories/event_abstract_factory.php b/calendar/classes/local/event/factories/event_abstract_factory.php index b810ff8b5f7..1fa43c217fe 100644 --- a/calendar/classes/local/event/factories/event_abstract_factory.php +++ b/calendar/classes/local/event/factories/event_abstract_factory.php @@ -133,6 +133,7 @@ abstract class event_abstract_factory implements event_factory_interface { $user = null; $module = null; $subscription = null; + $component = null; if ($dbrow->modulename && $dbrow->instance) { $module = new cm_info_proxy($dbrow->modulename, $dbrow->instance, $dbrow->courseid); @@ -171,6 +172,10 @@ abstract class event_abstract_factory implements event_factory_interface { $repeatcollection = null; } + if (!empty($dbrow->component)) { + $component = $dbrow->component; + } + $event = new event( $dbrow->id, $dbrow->name, @@ -190,7 +195,8 @@ abstract class event_abstract_factory implements event_factory_interface { ), !empty($dbrow->visible), $subscription, - $dbrow->location + $dbrow->location, + $component ); $isactionevent = !empty($dbrow->type) && $dbrow->type == CALENDAR_EVENT_TYPE_ACTION; diff --git a/calendar/classes/local/event/mappers/event_mapper.php b/calendar/classes/local/event/mappers/event_mapper.php index dc2ef2cb416..a6746eadb0d 100644 --- a/calendar/classes/local/event/mappers/event_mapper.php +++ b/calendar/classes/local/event/mappers/event_mapper.php @@ -75,6 +75,7 @@ class event_mapper implements event_mapper_interface { 'groupid' => $coalesce('groupid'), 'userid' => $coalesce('userid'), 'repeatid' => $coalesce('repeatid'), + 'component' => $coalesce('component'), 'modulename' => $coalesce('modulename'), 'instance' => $coalesce('instance'), 'eventtype' => $coalesce('eventtype'), @@ -98,6 +99,7 @@ class event_mapper implements event_mapper_interface { $properties->categoryid = empty($properties->categoryid) ? 0 : $properties->categoryid; $properties->groupid = empty($properties->groupid) ? 0 : $properties->groupid; $properties->userid = empty($properties->userid) ? 0 : $properties->userid; + $properties->component = empty($properties->component) ? 0 : $properties->component; $properties->modulename = empty($properties->modulename) ? 0 : $properties->modulename; $properties->instance = empty($properties->instance) ? 0 : $properties->instance; $properties->repeatid = empty($properties->repeatid) ? 0 : $properties->repeatid; @@ -127,6 +129,7 @@ class event_mapper implements event_mapper_interface { 'groupid' => $event->get_group() ? $event->get_group()->get('id') : null, 'userid' => $event->get_user() ? $event->get_user()->get('id') : null, 'repeatid' => $event->get_repeats() ? $event->get_repeats()->get_id() : null, + 'component' => $event->get_component(), 'modulename' => $event->get_course_module() ? $event->get_course_module()->get('modname') : null, 'instance' => $event->get_course_module() ? $event->get_course_module()->get('instance') : null, 'eventtype' => $event->get_type(), diff --git a/calendar/externallib.php b/calendar/externallib.php index 4d544456b7d..3f186f1ee86 100644 --- a/calendar/externallib.php +++ b/calendar/externallib.php @@ -797,20 +797,21 @@ class core_calendar_external extends external_api { self::validate_context($context); $warnings = array(); - $legacyevent = calendar_event::load($eventid); - // Must check we can see this event. - if (!calendar_view_event_allowed($legacyevent)) { + $eventvault = event_container::get_event_vault(); + if ($event = $eventvault->get_event_by_id($eventid)) { + $mapper = event_container::get_event_mapper(); + if (!calendar_view_event_allowed($mapper->from_event_to_legacy_event($event))) { + $event = null; + } + } + + if (!$event) { // We can't return a warning in this case because the event is not optional. // We don't know the context for the event and it's not worth loading it. $syscontext = context_system::instance(); throw new \required_capability_exception($syscontext, 'moodle/course:view', 'nopermission', ''); } - $legacyevent->count_repeats(); - - $eventmapper = event_container::get_event_mapper(); - $event = $eventmapper->from_legacy_event_to_event($legacyevent); - $cache = new events_related_objects_cache([$event]); $relatedobjects = [ 'context' => $cache->get_context($event), diff --git a/calendar/lib.php b/calendar/lib.php index f378c102b0a..2156628a481 100644 --- a/calendar/lib.php +++ b/calendar/lib.php @@ -172,6 +172,7 @@ define('CALENDAR_EVENT_TYPE_ACTION', 1); * @property int $userid The user the event is associated with (0 if none) * @property int $repeatid If this is a repeated event this will be set to the * id of the original + * @property string $component If created by a plugin/component (other than module), the full frankenstyle name of a component * @property string $modulename If added by a module this will be the module name * @property int $instance If added by a module this will be the module instance * @property string $eventtype The event type @@ -257,6 +258,10 @@ class calendar_event { $data->format = editors_get_preferred_format(); } + if (empty($data->component)) { + $data->component = null; + } + $this->properties = $data; } @@ -337,6 +342,7 @@ class calendar_event { $context = \context_user::instance($this->properties->userid); } else if (isset($this->properties->userid) && $this->properties->userid > 0 && $this->properties->userid != $USER->id && + !empty($this->properties->modulename) && isset($this->properties->instance) && $this->properties->instance > 0) { $cm = get_coursemodule_from_instance($this->properties->modulename, $this->properties->instance, 0, false, MUST_EXIST); @@ -1523,10 +1529,13 @@ function calendar_get_group_cached($groupid) { /** * Add calendar event metadata * + * @deprecated since 3.9 + * * @param stdClass $event event info * @return stdClass $event metadata */ function calendar_add_event_metadata($event) { + debugging('This function is no longer used', DEBUG_DEVELOPER); global $CFG, $OUTPUT; // Support multilang in event->name. @@ -2276,6 +2285,10 @@ function calendar_edit_event_allowed($event, $manualedit = false) { return has_capability('moodle/course:manageactivities', $context); } + if ($manualedit && !empty($event->component)) { + return false; + } + // You cannot edit URL based calendar subscription events presently. if (!empty($event->subscriptionid)) { if (!empty($event->subscription->url)) { @@ -2324,8 +2337,8 @@ function calendar_edit_event_allowed($event, $manualedit = false) { * @return bool Whether the user has permission to delete the event or not. */ function calendar_delete_event_allowed($event) { - // Only allow delete if you have capabilities and it is not an module event. - return (calendar_edit_event_allowed($event) && empty($event->modulename)); + // Only allow delete if you have capabilities and it is not an module or component event. + return (calendar_edit_event_allowed($event) && empty($event->modulename) && empty($event->component)); } /** diff --git a/calendar/renderer.php b/calendar/renderer.php index 0679979dc62..dcb3afcebbc 100644 --- a/calendar/renderer.php +++ b/calendar/renderer.php @@ -135,12 +135,15 @@ class core_calendar_renderer extends plugin_renderer_base { /** * Displays an event * + * @deprecated since 3.9 + * * @param calendar_event $event * @param bool $showactions * @return string */ public function event(calendar_event $event, $showactions=true) { global $CFG; + debugging('This function is no longer used', DEBUG_DEVELOPER); $event = calendar_add_event_metadata($event); $context = $event->context; diff --git a/calendar/templates/event_details.mustache b/calendar/templates/event_details.mustache index 9f92043ec41..0d9bf1e4b21 100644 --- a/calendar/templates/event_details.mustache +++ b/calendar/templates/event_details.mustache @@ -34,6 +34,7 @@ { "formattedtime": "Wednesday, 17 April, 9:27 AM", "normalisedeventtype": "Group", + "normalisedeventtypetext": "Group event", "description": "An random event description", "location": "Moodle HQ", "isactionevent": "true", @@ -77,29 +78,19 @@
{{{.}}}
{{/location}} -{{#isactionevent}} -
-
{{#pix}} i/courseevent, core, {{#str}} course {{/str}} {{/pix}}
- -
-{{/isactionevent}} {{#iscategoryevent}}
{{#pix}} i/categoryevent, core, {{#str}} category {{/str}} {{/pix}}
{{{category.nestedname}}}
{{/iscategoryevent}} -{{#iscourseevent}} +{{#course.viewurl}}
{{#pix}} i/courseevent, core, {{#str}} course {{/str}} {{/pix}}
- +
-{{/iscourseevent}} +{{/course.viewurl}} {{#groupname}} -
-
{{#pix}} i/courseevent, core, {{#str}} course {{/str}} {{/pix}}
- -
{{#pix}} i/groupevent, core, {{#str}} group {{/str}} {{/pix}}
{{{groupname}}}
diff --git a/calendar/templates/event_item.mustache b/calendar/templates/event_item.mustache index ccbf7739e85..9b4c16026e1 100644 --- a/calendar/templates/event_item.mustache +++ b/calendar/templates/event_item.mustache @@ -80,10 +80,17 @@
{{> core_calendar/event_details }}
- {{#isactionevent}} + {{#action.actionable}} + + {{/action.actionable}} + {{^action.actionable}} + {{#isactionevent}} - {{/isactionevent}} + {{/isactionevent}} + {{/action.actionable}}
diff --git a/calendar/templates/event_summary_modal.mustache b/calendar/templates/event_summary_modal.mustache index a6e7c2d2b8f..fc7edd748c0 100644 --- a/calendar/templates/event_summary_modal.mustache +++ b/calendar/templates/event_summary_modal.mustache @@ -21,7 +21,7 @@ Example context (json): { - "title": "Assignment due 1", + "title": "Assignment due 1" } }} {{< core/modal }} @@ -29,9 +29,15 @@ {{#candelete}} {{/candelete}} - {{#isactionevent}} + {{#action.actionable}} + {{{action.name}}} + {{/action.actionable}} + {{^action.actionable}} + {{#isactionevent}} {{#str}} gotoactivity, core_calendar {{/str}} - {{/isactionevent}} + {{/isactionevent}} + {{/action.actionable}} + {{^isactionevent}} {{#canedit}} diff --git a/calendar/tests/action_event_test.php b/calendar/tests/action_event_test.php index d6ae9f6d6cf..27aa5a26e73 100644 --- a/calendar/tests/action_event_test.php +++ b/calendar/tests/action_event_test.php @@ -156,6 +156,14 @@ class core_calendar_action_event_test_event implements event_interface { public function is_visible() { return true; } + + /** + * Component + * @return string|null + */ + public function get_component() { + return null; + } } /** diff --git a/calendar/tests/event_mapper_test.php b/calendar/tests/event_mapper_test.php index a9a39a5cc93..e9cafd73f8d 100644 --- a/calendar/tests/event_mapper_test.php +++ b/calendar/tests/event_mapper_test.php @@ -255,6 +255,14 @@ class event_mapper_test_action_event implements action_event_interface { true ); } + + /** + * Component + * @return string|null + */ + public function get_component() { + return $this->event->get_component(); + } } /** @@ -374,6 +382,14 @@ class event_mapper_test_event implements event_interface { public function is_visible() { return true; } + + /** + * Component + * @return string|null + */ + public function get_component() { + return null; + } } /** diff --git a/calendar/tests/event_test.php b/calendar/tests/event_test.php index 3a70913c635..9302a9d7915 100644 --- a/calendar/tests/event_test.php +++ b/calendar/tests/event_test.php @@ -59,16 +59,18 @@ class core_calendar_event_testcase extends advanced_testcase { $constructorparams['times'], $constructorparams['visible'], $constructorparams['subscription'], - $constructorparams['location'] + $constructorparams['location'], + $constructorparams['component'] ); foreach ($constructorparams as $name => $value) { - if ($name !== 'visible') { + if ($name !== 'visible' && $name !== 'component') { $this->assertEquals($event->{'get_' . $name}(), $value); } } $this->assertEquals($event->is_visible(), $constructorparams['visible']); + $this->assertEquals('mod_' . $event->get_course_module()->get('modname'), $event->get_component()); } /** @@ -76,7 +78,7 @@ class core_calendar_event_testcase extends advanced_testcase { */ public function getters_testcases() { $lamecallable = function($id) { - return (object)['id' => $id]; + return (object)['id' => $id, 'modname' => 'assign']; }; return [ @@ -101,6 +103,7 @@ class core_calendar_event_testcase extends advanced_testcase { 'visible' => true, 'subscription' => new std_proxy(1, $lamecallable), 'location' => 'Test', + 'component' => null ] ], ]; diff --git a/calendar/tests/helpers.php b/calendar/tests/helpers.php index 9a875f88746..67f41fffce0 100644 --- a/calendar/tests/helpers.php +++ b/calendar/tests/helpers.php @@ -137,7 +137,8 @@ class action_event_test_factory implements event_factory_interface { ), !empty($record->visible), $subscription, - $record->location + $record->location, + !empty($record->component) ? $record->component : null ); $action = new action( diff --git a/calendar/tests/repeat_event_collection_test.php b/calendar/tests/repeat_event_collection_test.php index 8c963bd07bc..f11e2710bd7 100644 --- a/calendar/tests/repeat_event_collection_test.php +++ b/calendar/tests/repeat_event_collection_test.php @@ -205,7 +205,8 @@ class core_calendar_repeat_event_collection_event_test_factory implements event_ ), !empty($dbrow->visible), new std_proxy($dbrow->subscriptionid, $identity), - $dbrow->location + $dbrow->location, + $dbrow->component ); } } diff --git a/calendar/upgrade.txt b/calendar/upgrade.txt index 2f4273a7a98..395195cceab 100644 --- a/calendar/upgrade.txt +++ b/calendar/upgrade.txt @@ -1,6 +1,11 @@ This files describes API changes in /calendar/* , information provided here is intended especially for developers. +=== 3.9 === +* Plugins can now create their own calendar events, both standard and action ones. To do it they need to specify + $event->component when creating an event. Component events can not be edited or deleted manually. + See https://docs.moodle.org/dev/Calendar_API#Component_events + === 3.8 === * The following functions have been finally deprecated and can not be used anymore: * calendar_wday_name() diff --git a/lib/adminlib.php b/lib/adminlib.php index afa63303d70..b75e4e19c14 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -206,6 +206,7 @@ function uninstall_plugin($type, $name) { // delete calendar events $DB->delete_records('event', array('modulename' => $pluginname)); + $DB->delete_records('event', ['component' => $component]); // Delete scheduled tasks. $DB->delete_records('task_scheduled', array('component' => $component)); diff --git a/lib/db/install.xml b/lib/db/install.xml index 0fd662a40b8..e14ab608d54 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -433,6 +433,7 @@ + @@ -463,6 +464,7 @@ + diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 0b5f9e4a4c2..4ff7553d746 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2314,5 +2314,29 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2020042800.01); } + + if ($oldversion < 2020051900.01) { + // Define field component to be added to event. + $table = new xmldb_table('event'); + $field = new xmldb_field('component', XMLDB_TYPE_CHAR, '100', null, null, null, null, 'repeatid'); + + // Conditionally launch add field component. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define index component (not unique) to be added to event. + $table = new xmldb_table('event'); + $index = new xmldb_index('component', XMLDB_INDEX_NOTUNIQUE, ['component', 'eventtype', 'instance']); + + // Conditionally launch add index component. + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2020051900.01); + } + return true; } diff --git a/version.php b/version.php index 8048ee3e640..4796e7b6ebe 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2020051900.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2020051900.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '3.9dev+ (Build: 20200519)'; // Human-friendly version name