first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* This module is responsible for the calendar filter.
*
* @module core_calendar/calendar_filter
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_calendar/calendar_filter",["jquery","core_calendar/selectors","core_calendar/events","core/str","core/templates"],(function($,CalendarSelectors,CalendarEvents,Str,Templates){var toggleFilter=function(target){var data=getFilterData(target);return data.hidden=!data.hidden,M.util.js_pending("core_calendar/calendar_filter:toggleFilter"),Str.get_string("eventtype"+data.eventtype,"calendar").then((function(nameStr){return data.name=nameStr,data.icon=!0,data.key="i/"+data.eventtype+"event",data.component="core",data})).then((function(context){return Templates.render("core_calendar/event_filter_key",context)})).then((function(html,js){return Templates.replaceNode(target,html,js)})).then((function(){fireFilterChangedEvent(data),M.util.js_complete("core_calendar/calendar_filter:toggleFilter")}))},fireFilterChangedEvent=function(data){M.util.js_pending("month-mini-filterChanged"),$("body").trigger(CalendarEvents.filterChanged,{type:data.eventtype,hidden:data.hidden}),M.util.js_complete("month-mini-filterChanged")},getFilterData=function(target){return{eventtype:target.data("eventtype"),hidden:target.data("eventtype-hidden")}};return{init:function(root){!function(root){root.on("click",CalendarSelectors.eventFilterItem,(function(e){var target=$(e.currentTarget);toggleFilter(target),e.preventDefault()})),$("body").on(CalendarEvents.viewUpdated,(function(){root.find(CalendarSelectors.eventFilterItem).each((function(i,filter){if((filter=$(filter)).data("eventtype-hidden")){var data=getFilterData(filter);fireFilterChangedEvent(data)}}))}))}(root=$(root))}}}));
//# sourceMappingURL=calendar_filter.min.js.map
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module core_calendar/calendar_mini
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_calendar/calendar_mini",["jquery","core_calendar/selectors","core_calendar/events","core_calendar/view_manager"],(function($,CalendarSelectors,CalendarEvents,CalendarViewManager){var reloadMonth=function(e){var root=e.data,body=$("body"),namespace="."+root.attr("id");root.is(":visible")?CalendarViewManager.reloadCurrentMonth(root):(body.off(CalendarEvents.created+namespace),body.off(CalendarEvents.deleted+namespace),body.off(CalendarEvents.updated+namespace),body.off(CalendarEvents.eventMoved+namespace))};return{init:function(root,loadOnInit){root=$(root),CalendarViewManager.init(root),function(root){$("body").on(CalendarEvents.filterChanged,(function(e,data){root.find(CalendarSelectors.eventType[data.type]).toggleClass("calendar_event_"+data.type,!data.hidden)}));var namespace="."+root.attr("id");$("body").on("change"+namespace,CalendarSelectors.elements.courseSelector,(function(){if(root.is(":visible")){var courseId=$(this).val();CalendarViewManager.reloadCurrentMonth(root,courseId,null)}else $("body").off("change"+namespace)}))}(root),function(root){var body=$("body"),namespace="."+root.attr("id");body.on(CalendarEvents.created+namespace,root,reloadMonth),body.on(CalendarEvents.deleted+namespace,root,reloadMonth),body.on(CalendarEvents.updated+namespace,root,reloadMonth),body.on(CalendarEvents.eventMoved+namespace,root,reloadMonth)}(root),loadOnInit&&CalendarViewManager.reloadCurrentMonth(root)}}}));
//# sourceMappingURL=calendar_mini.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* This module is responsible for handle calendar day and upcoming view.
*
* @module core_calendar/calendar_view
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_calendar/calendar_view",["jquery","core/notification","core_calendar/selectors","core_calendar/events","core_calendar/view_manager","core_calendar/crud"],(function($,Notification,CalendarSelectors,CalendarEvents,CalendarViewManager,CalendarCrud){var registerEventListeners=function(root,type){var body=$("body");CalendarCrud.registerRemove(root);var reloadFunction="reloadCurrent"+type.charAt(0).toUpperCase()+type.slice(1);body.on(CalendarEvents.created,(function(){CalendarViewManager[reloadFunction](root)})),body.on(CalendarEvents.deleted,(function(){CalendarViewManager[reloadFunction](root)})),body.on(CalendarEvents.updated,(function(){CalendarViewManager[reloadFunction](root)})),root.on("change",CalendarSelectors.courseSelector,(function(){var selectElement=$(this),courseId=selectElement.val();const courseName=$("option:selected",selectElement).text();CalendarViewManager[reloadFunction](root,courseId,null).then((function(){return root.find(CalendarSelectors.courseSelector).val(courseId)})).then((function(){CalendarViewManager.updateUrl("?view="+type+"&course="+courseId),CalendarViewManager.handleCourseChange(Number(courseId),courseName)})).fail(Notification.exception)})),body.on(CalendarEvents.filterChanged,(function(e,data){var daysWithEvent=root.find(CalendarSelectors.eventType[data.type]);1==data.hidden?daysWithEvent.addClass("hidden"):daysWithEvent.removeClass("hidden"),CalendarViewManager.foldDayEvents(root)}));var eventFormPromise=CalendarCrud.registerEventFormModal(root);CalendarCrud.registerEditListeners(root,eventFormPromise)};return{init:function(root,type){let isCalendarBlock=arguments.length>2&&void 0!==arguments[2]&&arguments[2];root=$(root),CalendarViewManager.init(root,type,isCalendarBlock),registerEventListeners(root,type)}}}));
//# sourceMappingURL=calendar_view.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* A module to handle CRUD operations within the UI.
*
* @module core_calendar/crud
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_calendar/crud",["jquery","core/str","core/notification","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors","core/pending","core/modal_save_cancel","core/config"],(function($,Str,Notification,ModalEvents,ModalEventForm,CalendarRepository,CalendarEvents,CalendarModalDelete,CalendarSelectors,Pending,ModalSaveCancel,Config){return{registerRemove:function(root){root.on("click",CalendarSelectors.actions.remove,(function(e){var eventSource=$(this).closest(CalendarSelectors.eventItem);!function(eventId,eventTitle,eventCount){var deletePromise,pendingPromise=new Pending("core_calendar/crud:confirmDeletion"),deleteStrings=[{key:"deleteevent",component:"calendar"}],isRepeatedEvent=(eventCount=parseInt(eventCount,10))>1;isRepeatedEvent?(deleteStrings.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:eventTitle,count:eventCount}}),deletePromise=CalendarModalDelete.create()):(deleteStrings.push({key:"confirmeventdelete",component:"calendar",param:eventTitle}),deletePromise=ModalSaveCancel.create());var stringsPromise=Str.get_strings(deleteStrings);$.when(stringsPromise,deletePromise).then((function(strings,deleteModal){return deleteModal.setRemoveOnClose(!0),deleteModal.setTitle(strings[0]),deleteModal.setBody(strings[1]),isRepeatedEvent||deleteModal.setSaveButtonText(strings[0]),deleteModal.show(),deleteModal.getRoot().on(ModalEvents.save,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedevent");CalendarRepository.deleteEvent(eventId,!1).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!1])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal.getRoot().on(CalendarEvents.deleteAll,(function(){var pendingPromise=new Pending("calendar/crud:initModal:deletedallevent");CalendarRepository.deleteEvent(eventId,!0).then((function(){$("body").trigger(CalendarEvents.deleted,[eventId,!0])})).then(pendingPromise.resolve).catch(Notification.exception)})),deleteModal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)}(eventSource.data("eventId"),eventSource.data("eventTitle"),eventSource.data("eventCount")),e.preventDefault()}))},registerEditListeners:function(root,eventFormModalPromise){var pendingPromise=new Pending("core_calendar/crud:registerEditListeners");return eventFormModalPromise.then((function(modal){return $("body").on(CalendarEvents.editEvent,(function(e,eventId){var target=root.find("[data-event-id=".concat(eventId,"]")),calendarWrapper=root.find(CalendarSelectors.wrapper);modal.setEventId(eventId),modal.setContextId(calendarWrapper.data("contextId")),modal.setReturnElement(target),modal.show(),e.stopImmediatePropagation()})),modal})).then((function(modal){return pendingPromise.resolve(),modal})).catch(Notification.exception)},registerEventFormModal:function(root){var eventFormPromise=ModalEventForm.create();return root.on("click",CalendarSelectors.actions.create,(function(e){eventFormPromise.then((function(modal){var wrapper=root.find(CalendarSelectors.wrapper),categoryId=wrapper.data("categoryid");const courseId=wrapper.data("courseid");void 0!==categoryId&&courseId!=Config.siteId&&modal.setCategoryId(categoryId);var today=root.find(CalendarSelectors.today),firstDay=root.find(CalendarSelectors.day);!today.length&&firstDay.length&&modal.setStartTime(firstDay.data("newEventTimestamp")),modal.setContextId(wrapper.data("contextId")),modal.setCourseId(wrapper.data("courseid")),modal.show()})).catch(Notification.exception),e.preventDefault()})),root.on("click",CalendarSelectors.actions.edit,(function(e){e.preventDefault();var target=$(e.currentTarget),calendarWrapper=target.closest(CalendarSelectors.wrapper),eventWrapper=target.closest(CalendarSelectors.eventItem);eventFormPromise.then((function(modal){modal.setEventId(eventWrapper.data("eventId")),modal.setContextId(calendarWrapper.data("contextId")),modal.setCourseId(eventWrapper.data("courseId")),modal.show(),e.stopImmediatePropagation()})).catch(Notification.exception)})),eventFormPromise}}}));
//# sourceMappingURL=crud.min.js.map
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
/**
* A javascript module to store calendar drag and drop data.
*
* This module is unfortunately required because of the limitations
* of the HTML5 drag and drop API and it's ability to provide data
* between the different stages of the drag/drop lifecycle.
*
* @module core_calendar/drag_drop_data_store
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_calendar/drag_drop_data_store",[],(function(){var eventId=null,durationDays=null,minTimestart=null,maxTimestart=null,minError=null,maxError=null,setEventId=function(id){eventId=id},setDurationDays=function(days){durationDays=days},setMinTimestart=function(timestamp){minTimestart=timestamp},setMaxTimestart=function(timestamp){maxTimestart=timestamp},setMinError=function(message){minError=message},setMaxError=function(message){maxError=message};return{setEventId:setEventId,getEventId:function(){return eventId},hasEventId:function(){return null!==eventId},setDurationDays:setDurationDays,getDurationDays:function(){return durationDays},setMinTimestart:setMinTimestart,getMinTimestart:function(){return minTimestart},hasMinTimestart:function(){return null!==minTimestart},setMaxTimestart:setMaxTimestart,getMaxTimestart:function(){return maxTimestart},hasMaxTimestart:function(){return null!==maxTimestart},setMinError:setMinError,getMinError:function(){return minError},setMaxError:setMaxError,getMaxError:function(){return maxError},clearAll:function(){setEventId(null),setDurationDays(null),setMinTimestart(null),setMaxTimestart(null),setMinError(null),setMaxError(null)}}}));
//# sourceMappingURL=drag_drop_data_store.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* A javascript module to enhance the event form.
*
* @module core_calendar/event_form
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_calendar/event_form",["jquery","core_calendar/repository","core/notification"],(function($,CalendarRepository,Notification){var SELECTORS_EVENT_GROUP_COURSE_ID='[name="groupcourseid"]',SELECTORS_EVENT_GROUP_ID='[name="groupid"]',SELECTORS_SELECT_OPTION="option";return{init:function(formId){!function(formElement){var courseGroupSelect=formElement.find(SELECTORS_EVENT_GROUP_COURSE_ID),loadGroupSelectOptions=function(groups){var groupSelect=formElement.find(SELECTORS_EVENT_GROUP_ID),groupSelectOptions=groupSelect.find(SELECTORS_SELECT_OPTION),courseGroups=$(groups);groupSelectOptions.remove(),groupSelect.prop("disabled",!1),courseGroups.each((function(id,group){$(groupSelect).append($("<option></option>").attr("value",group.id).text(group.name))}))};courseGroupSelect.on("change",(function(){var courseId=formElement.find(SELECTORS_EVENT_GROUP_COURSE_ID).val();isNaN(courseId)||courseId<=0?loadGroupSelectOptions([]):CalendarRepository.getCourseGroupsData(courseId).then((function(groups){return loadGroupSelectOptions(groups)})).catch(Notification.exception)}))}($("#"+formId))}}}));
//# sourceMappingURL=event_form.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"event_form.min.js","sources":["../src/event_form.js"],"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 * A javascript module to enhance the event form.\n *\n * @module core_calendar/event_form\n * @copyright 2017 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core_calendar/repository', 'core/notification'], function($, CalendarRepository, Notification) {\n\n var SELECTORS = {\n EVENT_GROUP_COURSE_ID: '[name=\"groupcourseid\"]',\n EVENT_GROUP_ID: '[name=\"groupid\"]',\n SELECT_OPTION: 'option'\n };\n\n /**\n * Listen for when the user changes the group course when configuring\n * a group event and filter the options in the group select to only\n * show the groups available within the course the user has selected.\n *\n * @method addCourseGroupSelectListeners\n * @param {object} formElement The root form element\n */\n var addCourseGroupSelectListeners = function(formElement) {\n var courseGroupSelect = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID);\n\n var loadGroupSelectOptions = function(groups) {\n var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID),\n groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION),\n courseGroups = $(groups);\n\n // Let's clear all options first.\n groupSelectOptions.remove();\n groupSelect.prop(\"disabled\", false);\n courseGroups.each(function(id, group) {\n $(groupSelect).append($(\"<option></option>\").attr(\"value\", group.id).text(group.name));\n });\n };\n\n // If the user choose a course in the selector do a WS request to get groups.\n courseGroupSelect.on('change', function() {\n var courseId = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID).val();\n if (isNaN(courseId) || courseId <= 0) {\n loadGroupSelectOptions([]);\n } else {\n CalendarRepository.getCourseGroupsData(courseId)\n .then(function(groups) {\n return loadGroupSelectOptions(groups);\n })\n .catch(Notification.exception);\n }\n });\n };\n\n /**\n * Initialise all of the form enhancements.\n *\n * @method init\n * @param {string} formId The value of the form's id attribute\n */\n var init = function(formId) {\n var formElement = $('#' + formId);\n addCourseGroupSelectListeners(formElement);\n };\n\n return {\n init: init,\n };\n});\n"],"names":["define","$","CalendarRepository","Notification","SELECTORS","init","formId","formElement","courseGroupSelect","find","loadGroupSelectOptions","groups","groupSelect","groupSelectOptions","courseGroups","remove","prop","each","id","group","append","attr","text","name","on","courseId","val","isNaN","getCourseGroupsData","then","catch","exception","addCourseGroupSelectListeners"],"mappings":";;;;;;;AAsBAA,kCAAO,CAAC,SAAU,2BAA4B,sBAAsB,SAASC,EAAGC,mBAAoBC,kBAE5FC,gCACuB,yBADvBA,yBAEgB,mBAFhBA,wBAGe,eAqDZ,CACHC,KANO,SAASC,SArCgB,SAASC,iBACrCC,kBAAoBD,YAAYE,KAAKL,iCAErCM,uBAAyB,SAASC,YAC9BC,YAAcL,YAAYE,KAAKL,0BAC/BS,mBAAqBD,YAAYH,KAAKL,yBACtCU,aAAeb,EAAEU,QAGrBE,mBAAmBE,SACnBH,YAAYI,KAAK,YAAY,GAC7BF,aAAaG,MAAK,SAASC,GAAIC,OAC3BlB,EAAEW,aAAaQ,OAAOnB,EAAE,qBAAqBoB,KAAK,QAASF,MAAMD,IAAII,KAAKH,MAAMI,WAKxFf,kBAAkBgB,GAAG,UAAU,eACvBC,SAAWlB,YAAYE,KAAKL,iCAAiCsB,MAC7DC,MAAMF,WAAaA,UAAY,EAC/Bf,uBAAuB,IAEvBR,mBAAmB0B,oBAAoBH,UAClCI,MAAK,SAASlB,eACJD,uBAAuBC,WAEjCmB,MAAM3B,aAAa4B,cAahCC,CADkB/B,EAAE,IAAMK"}
+3
View File
@@ -0,0 +1,3 @@
define("core_calendar/events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={created:"calendar-events:created",deleted:"calendar-events:deleted",deleteAll:"calendar-events:delete_all",updated:"calendar-events:updated",editEvent:"calendar-events:edit_event",editActionEvent:"calendar-events:edit_action_event",eventMoved:"calendar-events:event_moved",dayChanged:"calendar-events:day_changed",monthChanged:"calendar-events:month_changed",moveEvent:"calendar-events:move_event",filterChanged:"calendar-events:filter_changed",courseChanged:"calendar-events:course_changed",viewUpdated:"calendar-events:view_updated"},_exports.default}));
//# sourceMappingURL=events.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"events.min.js","sources":["../src/events.js"],"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 * Contain the events the calendar component can fire.\n *\n * @module core_calendar/events\n * @copyright 2017 Simey Lameze <simey@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n created: 'calendar-events:created',\n deleted: 'calendar-events:deleted',\n deleteAll: 'calendar-events:delete_all',\n updated: 'calendar-events:updated',\n editEvent: 'calendar-events:edit_event',\n editActionEvent: 'calendar-events:edit_action_event',\n eventMoved: 'calendar-events:event_moved',\n dayChanged: 'calendar-events:day_changed',\n monthChanged: 'calendar-events:month_changed',\n moveEvent: 'calendar-events:move_event',\n filterChanged: 'calendar-events:filter_changed',\n courseChanged: 'calendar-events:course_changed',\n viewUpdated: 'calendar-events:view_updated',\n};\n"],"names":["created","deleted","deleteAll","updated","editEvent","editActionEvent","eventMoved","dayChanged","monthChanged","moveEvent","filterChanged","courseChanged","viewUpdated"],"mappings":"sKAsBe,CACXA,QAAS,0BACTC,QAAS,0BACTC,UAAW,6BACXC,QAAS,0BACTC,UAAW,6BACXC,gBAAiB,oCACjBC,WAAY,8BACZC,WAAY,8BACZC,aAAc,gCACdC,UAAW,6BACXC,cAAe,iCACfC,cAAe,iCACfC,YAAa"}
+11
View File
@@ -0,0 +1,11 @@
define("core_calendar/export",["exports","core/copy_to_clipboard"],(function(_exports,_copy_to_clipboard){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
/**
* A javascript module to enhance the calendar export form.
*
* @module core_calendar/export
* @copyright 2021 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const selectors_copyUrlId="copyexporturl";_exports.init=()=>{const copyUrl=document.getElementById(selectors_copyUrlId);copyUrl.removeAttribute("disabled"),copyUrl.focus()}}));
//# sourceMappingURL=export.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"export.min.js","sources":["../src/export.js"],"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 * A javascript module to enhance the calendar export form.\n *\n * @module core_calendar/export\n * @copyright 2021 Jun Pataleta\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport 'core/copy_to_clipboard';\n\n/**\n * Selectors for the calendar export page.\n *\n * @property {string} copyUrlId The element ID of the Copy URL button.\n */\nconst selectors = {\n copyUrlId: 'copyexporturl',\n};\n\n/**\n * Initialises the calendar export JS module.\n *\n * @method init\n */\nexport const init = () => {\n // Enable the copy URL button and focus on it.\n const copyUrl = document.getElementById(selectors.copyUrlId);\n copyUrl.removeAttribute('disabled');\n copyUrl.focus();\n};\n"],"names":["selectors","copyUrl","document","getElementById","removeAttribute","focus"],"mappings":";;;;;;;;MA8BMA,oBACS,8BAQK,WAEVC,QAAUC,SAASC,eAAeH,qBACxCC,QAAQG,gBAAgB,YACxBH,QAAQI"}
+11
View File
@@ -0,0 +1,11 @@
define("core_calendar/manage_subscriptions",["exports","core_calendar/selectors","core_calendar/repository","core/modal_save_cancel","core/modal_events","core/notification","core/prefetch","core/str","core/local/inplace_editable/events"],(function(_exports,CalendarSelectors,CalendarRepository,_modal_save_cancel,ModalEvents,_notification,_prefetch,_str,_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* 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
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,CalendarSelectors=_interopRequireWildcard(CalendarSelectors),CalendarRepository=_interopRequireWildcard(CalendarRepository),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),ModalEvents=_interopRequireWildcard(ModalEvents),_prefetch=_interopRequireDefault(_prefetch);const getSubscriptionName=element=>element.closest("tr").dataset.subname,registerEventListeners=()=>{document.addEventListener("click",(e=>{const deleteAction=e.target.closest(CalendarSelectors.actions.deleteSubscription);if(deleteAction){e.preventDefault();((element,messageCode)=>{const subscriptionName=getSubscriptionName(element);return _modal_save_cancel.default.create({title:(0,_str.getString)("confirmation","admin"),body:(0,_str.getString)(messageCode,"calendar",subscriptionName),buttons:{save:(0,_str.getString)("yes")}}).then((modal=>(modal.getRoot().on(ModalEvents.hidden,(()=>{element.focus()})),modal.show(),modal)))})(deleteAction,"confirmsubscriptiondelete").then((modal=>(modal.getRoot().on(ModalEvents.save,(()=>{const subscriptionId=parseInt(deleteAction.closest("tr").dataset.subid);CalendarRepository.deleteSubscription(subscriptionId).then((data=>{const response=(async(element,data)=>{const subscriptionName=getSubscriptionName(element),message=data.status?await(0,_str.getString)("subscriptionremoved","calendar",subscriptionName):data.warnings[0].message,type=data.status?"info":"error";return(0,_notification.addNotification)({message:message,type:type})})(deleteAction,data);return response.then((()=>{const subscriptionRow=(subscriptionId=>document.querySelector('tr[data-subid="'.concat(subscriptionId,'"]')))(subscriptionId);return subscriptionRow.remove()}))})).catch(_notification.exception)})),modal))).catch(_notification.exception)}})),document.addEventListener(_events.eventTypes.elementUpdated,(e=>{"core_calendar"==e.target.getAttribute("data-component")&&(0,_notification.fetchNotifications)()}))};_exports.init=()=>{_prefetch.default.prefetchStrings("moodle",["yes"]),_prefetch.default.prefetchStrings("core_admin",["confirmation"]),_prefetch.default.prefetchStrings("core_calendar",["confirmsubscriptiondelete","subscriptionremoved"]),registerEventListeners()}}));
//# sourceMappingURL=manage_subscriptions.min.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("core_calendar/modal_delete",["exports","jquery","core/custom_interaction_events","core/modal","core/modal_events","./events"],(function(_exports,_jquery,CustomEvents,_modal,_modal_events,_events){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),CustomEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CustomEvents),_modal=_interopRequireDefault(_modal),_modal_events=_interopRequireDefault(_modal_events),_events=_interopRequireDefault(_events);const SELECTORS_DELETE_ONE_BUTTON='[data-action="deleteone"]',SELECTORS_DELETE_ALL_BUTTON='[data-action="deleteall"]',SELECTORS_CANCEL_BUTTON='[data-action="cancel"]';class ModalDelete extends _modal.default{constructor(root){super(root),this.setRemoveOnClose(!0)}registerEventListeners(){super.registerEventListeners(this),this.getModal().on(CustomEvents.events.activate,SELECTORS_DELETE_ONE_BUTTON,((e,data)=>{const saveEvent=_jquery.default.Event(_modal_events.default.save);this.getRoot().trigger(saveEvent,this),saveEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())})),this.getModal().on(CustomEvents.events.activate,SELECTORS_DELETE_ALL_BUTTON,((e,data)=>{const saveEvent=_jquery.default.Event(_events.default.deleteAll);this.getRoot().trigger(saveEvent,this),saveEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())})),this.getModal().on(CustomEvents.events.activate,SELECTORS_CANCEL_BUTTON,((e,data)=>{const cancelEvent=_jquery.default.Event(_modal_events.default.cancel);this.getRoot().trigger(cancelEvent,this),cancelEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())}))}}return _exports.default=ModalDelete,_defineProperty(ModalDelete,"TYPE","core_calendar-modal_delete"),_defineProperty(ModalDelete,"TEMPLATE","calendar/event_delete_modal"),ModalDelete.registerModalType(),_exports.default}));
//# sourceMappingURL=modal_delete.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"modal_delete.min.js","sources":["../src/modal_delete.js"],"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 * Contain the logic for the delete modal.\n *\n * @module core_calendar/modal_delete\n * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as CustomEvents from 'core/custom_interaction_events';\nimport Modal from 'core/modal';\nimport ModalEvents from 'core/modal_events';\nimport CalendarEvents from './events';\n\nconst SELECTORS = {\n DELETE_ONE_BUTTON: '[data-action=\"deleteone\"]',\n DELETE_ALL_BUTTON: '[data-action=\"deleteall\"]',\n CANCEL_BUTTON: '[data-action=\"cancel\"]',\n};\n\n/**\n * Constructor for the Modal.\n *\n * @class\n * @param {object} root The root jQuery element for the modal\n */\nexport default class ModalDelete extends Modal {\n static TYPE = 'core_calendar-modal_delete';\n static TEMPLATE = 'calendar/event_delete_modal';\n\n constructor(root) {\n super(root);\n this.setRemoveOnClose(true);\n }\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners(this);\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.DELETE_ONE_BUTTON, (e, data) => {\n const saveEvent = $.Event(ModalEvents.save);\n this.getRoot().trigger(saveEvent, this);\n\n if (!saveEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.DELETE_ALL_BUTTON, (e, data) => {\n const saveEvent = $.Event(CalendarEvents.deleteAll);\n this.getRoot().trigger(saveEvent, this);\n\n if (!saveEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, (e, data) => {\n const cancelEvent = $.Event(ModalEvents.cancel);\n this.getRoot().trigger(cancelEvent, this);\n\n if (!cancelEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n }\n}\n\nModalDelete.registerModalType();\n"],"names":["SELECTORS","ModalDelete","Modal","constructor","root","setRemoveOnClose","registerEventListeners","this","getModal","on","CustomEvents","events","activate","e","data","saveEvent","$","Event","ModalEvents","save","getRoot","trigger","isDefaultPrevented","hide","originalEvent","preventDefault","CalendarEvents","deleteAll","cancelEvent","cancel","registerModalType"],"mappings":"6nDA6BMA,4BACiB,4BADjBA,4BAEiB,4BAFjBA,wBAGa,+BASEC,oBAAoBC,eAIrCC,YAAYC,YACFA,WACDC,kBAAiB,GAQ1BC,+BAEUA,uBAAuBC,WAExBC,WAAWC,GAAGC,aAAaC,OAAOC,SAAUZ,6BAA6B,CAACa,EAAGC,cACxEC,UAAYC,gBAAEC,MAAMC,sBAAYC,WACjCC,UAAUC,QAAQN,UAAWR,MAE7BQ,UAAUO,4BACNC,OACLT,KAAKU,cAAcC,0BAItBjB,WAAWC,GAAGC,aAAaC,OAAOC,SAAUZ,6BAA6B,CAACa,EAAGC,cACxEC,UAAYC,gBAAEC,MAAMS,gBAAeC,gBACpCP,UAAUC,QAAQN,UAAWR,MAE7BQ,UAAUO,4BACNC,OACLT,KAAKU,cAAcC,0BAItBjB,WAAWC,GAAGC,aAAaC,OAAOC,SAAUZ,yBAAyB,CAACa,EAAGC,cACpEc,YAAcZ,gBAAEC,MAAMC,sBAAYW,aACnCT,UAAUC,QAAQO,YAAarB,MAE/BqB,YAAYN,4BACRC,OACLT,KAAKU,cAAcC,0EA5CdxB,mBACH,8CADGA,uBAEC,+BAgDtBA,YAAY6B"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
/**
* A javascript module to handle calendar drag and drop in the calendar
* month view navigation.
*
* This code is run each time the calendar month view is re-rendered. We
* only register the event handlers once per page load so that the in place
* DOM updates that happen on month change don't continue to register handlers.
*
* @module core_calendar/month_navigation_drag_drop
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_calendar/month_navigation_drag_drop",["jquery","core_calendar/drag_drop_data_store"],(function($,DataStore){var SELECTORS_DRAGGABLE='[draggable="true"][data-region="event-item"]',SELECTORS_DROP_ZONE='[data-drop-zone="nav-link"]',registered=!1,hoverTimer=null,root=null,updateHoverState=function(target,hovered){hovered?target.addClass("bg-primary text-white"):target.removeClass("bg-primary text-white")},addDropZoneIndicator=function(){root.find(SELECTORS_DROP_ZONE).addClass("drop-target")},removeDropZoneIndicator=function(){root.find(SELECTORS_DROP_ZONE).removeClass("drop-target")},getTargetFromEvent=function(e){var target=$(e.target).closest(SELECTORS_DROP_ZONE);return target.length?target:null},dragstartHandler=function(e){$(e.target).closest(SELECTORS_DRAGGABLE).length&&addDropZoneIndicator()},dragoverHandler=function(e){if(DataStore.hasEventId()){e.preventDefault();var target=getTargetFromEvent(e);target&&DataStore.hasEventId()&&(hoverTimer||(hoverTimer=setTimeout((function(){target.click(),hoverTimer=null}),1e3)),updateHoverState(target,!0),removeDropZoneIndicator())}},dragleaveHandler=function(e){if(DataStore.hasEventId()){var target=getTargetFromEvent(e);target&&(hoverTimer&&(clearTimeout(hoverTimer),hoverTimer=null),updateHoverState(target,!1),addDropZoneIndicator(),e.preventDefault())}},dropHandler=function(e){if(DataStore.hasEventId()){removeDropZoneIndicator();var target=getTargetFromEvent(e);target&&(updateHoverState(target,!1),e.preventDefault())}};return{init:function(rootElement){registered||(document.addEventListener("dragstart",dragstartHandler,!1),document.addEventListener("dragover",dragoverHandler,!1),document.addEventListener("dragleave",dragleaveHandler,!1),document.addEventListener("drop",dropHandler,!1),document.addEventListener("dragend",removeDropZoneIndicator,!1),registered=!0),root=$(rootElement),DataStore.hasEventId()&&addDropZoneIndicator()}}}));
//# sourceMappingURL=month_navigation_drag_drop.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* A javascript module to handle calendar drag and drop in the calendar
* month view.
*
* @module core_calendar/month_view_drag_drop
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_calendar/month_view_drag_drop",["jquery","core/notification","core/str","core_calendar/events","core_calendar/drag_drop_data_store"],(function($,Notification,Str,CalendarEvents,DataStore){var SELECTORS_ROOT="[data-region='calendar']",SELECTORS_DRAGGABLE='[draggable="true"][data-region="event-item"]',SELECTORS_DROP_ZONE='[data-drop-zone="month-view-day"]',SELECTORS_WEEK='[data-region="month-view-week"]',ALL_CLASSES="bg-faded bg-danger text-white bg-primary text-white",registered=!1,getDropZoneFromEvent=function(e){var dropZone=$(e.target).closest(SELECTORS_DROP_ZONE);return dropZone.length?dropZone:null},isValidDropZone=function(dropZone){var dropTimestamp=dropZone.attr("data-day-timestamp"),minTimestart=DataStore.getMinTimestart(),maxTimestart=DataStore.getMaxTimestart();return!(minTimestart&&minTimestart>dropTimestamp)&&!(maxTimestart&&maxTimestart<dropTimestamp)},clearAllDropZonesState=function(){$(SELECTORS_ROOT).find(SELECTORS_DROP_ZONE).each((function(index,dropZone){(dropZone=$(dropZone)).removeClass(ALL_CLASSES)}))},updateHoverState=function(dropZone,hovered,count){void 0===count&&(count=DataStore.getDurationDays());var valid=isValidDropZone(dropZone);if(dropZone.removeClass(ALL_CLASSES),hovered?valid?dropZone.addClass("bg-primary text-white"):dropZone.addClass("bg-danger text-white"):(dropZone.removeClass("bg-primary text-white bg-danger text-white"),valid||dropZone.addClass("bg-faded")),--count>0){var nextDropZone=dropZone.next();if(!nextDropZone.length){var nextWeek=dropZone.closest(SELECTORS_WEEK).next();nextWeek.length&&(nextDropZone=nextWeek.children(SELECTORS_DROP_ZONE).first())}nextDropZone.length&&updateHoverState(nextDropZone,hovered,count)}},updateAllDropZonesState=function(){$(SELECTORS_ROOT).find(SELECTORS_DROP_ZONE).each((function(index,dropZone){dropZone=$(dropZone),isValidDropZone(dropZone)||updateHoverState(dropZone,!1)}))},dragstartHandler=function(e){var draggableElement=$(e.target).closest(SELECTORS_DRAGGABLE);if(draggableElement.length){var eventId=draggableElement.find("[data-event-id]").attr("data-event-id"),minTimestart=draggableElement.attr("data-min-day-timestamp"),maxTimestart=draggableElement.attr("data-max-day-timestamp"),minError=draggableElement.attr("data-min-day-error"),maxError=draggableElement.attr("data-max-day-error"),duration=$(SELECTORS_ROOT+' [data-event-id="'+eventId+'"]').length;DataStore.setEventId(eventId),DataStore.setDurationDays(duration),minTimestart&&DataStore.setMinTimestart(minTimestart),maxTimestart&&DataStore.setMaxTimestart(maxTimestart),minError&&DataStore.setMinError(minError),maxError&&DataStore.setMaxError(maxError),e.dataTransfer.effectAllowed="move",e.dataTransfer.dropEffect="move",e.dataTransfer.setData("text/plain",eventId),e.dropEffect="move",updateAllDropZonesState()}},dragoverHandler=function(e){if(DataStore.hasEventId()){e.preventDefault();var dropZone=getDropZoneFromEvent(e);dropZone&&updateHoverState(dropZone,!0)}},dragleaveHandler=function(e){if(DataStore.hasEventId()){var dropZone=getDropZoneFromEvent(e);dropZone&&(updateHoverState(dropZone,!1),e.preventDefault())}},dropHandler=function(e){if(DataStore.hasEventId()){var dropZone=getDropZoneFromEvent(e);if(!dropZone)return DataStore.clearAll(),void clearAllDropZonesState();if(isValidDropZone(dropZone)){var eventId=DataStore.getEventId(),eventElement=$(SELECTORS_ROOT+' [data-event-id="'+eventId+'"]'),origin=null;eventElement.length&&(origin=eventElement.closest(SELECTORS_DROP_ZONE)),$("body").trigger(CalendarEvents.moveEvent,[eventId,origin,dropZone])}else{var message=function(dropZone){var dropTimestamp=dropZone.attr("data-day-timestamp"),minTimestart=DataStore.getMinTimestart(),maxTimestart=DataStore.getMaxTimestart();return minTimestart&&minTimestart>dropTimestamp?DataStore.getMinError():maxTimestart&&maxTimestart<dropTimestamp?DataStore.getMaxError():null}(dropZone);Str.get_string("errorinvaliddate","calendar").then((function(string){Notification.exception({name:string,message:message||string})}))}DataStore.clearAll(),clearAllDropZonesState(),e.preventDefault()}},dragendHandler=function(){DataStore.clearAll(),clearAllDropZonesState()},calendarMonthChangedHandler=function(){updateAllDropZonesState()};return{init:function(){registered||(document.addEventListener("dragstart",dragstartHandler,!1),document.addEventListener("dragover",dragoverHandler,!1),document.addEventListener("dragleave",dragleaveHandler,!1),document.addEventListener("drop",dropHandler,!1),document.addEventListener("dragend",dragendHandler,!1),$("body").on(CalendarEvents.monthChanged,calendarMonthChangedHandler),registered=!0)}}}));
//# sourceMappingURL=month_view_drag_drop.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
define("core_calendar/popover",["theme_boost/popover","jquery","core_calendar/selectors"],(function(_popover,_jquery,CalendarSelectors){var obj;
/**
* Javascript popover for the `core_calendar` subsystem.
*
* @module core_calendar/popover
* @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.0
*/function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj},CalendarSelectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CalendarSelectors);const isPopoverConfigured=new Map,showPopover=target=>{const dateContainer=target.closest(CalendarSelectors.elements.dateContainer);if(!isPopoverConfigured.has(dateContainer)){(0,_jquery.default)(target).popover({trigger:"manual",placement:"top",html:!0,title:dateContainer.dataset.title,content:()=>{const source=(0,_jquery.default)(dateContainer).find(CalendarSelectors.elements.dateContent),content=(0,_jquery.default)("<div>");if(source.length){const temptContent=source.find(".hidden").clone(!1);content.html(temptContent.html())}return content.html()},animation:!1}),isPopoverConfigured.set(dateContainer,!0)}(dateContainer=>"none"===window.getComputedStyle(dateContainer.querySelector(CalendarSelectors.elements.dateContent)).display)(dateContainer)&&((0,_jquery.default)(target).popover("show"),target.addEventListener("mouseleave",hidePopover),target.addEventListener("focusout",hidePopover),target.addEventListener("click",hidePopover))},hidePopover=e=>{const target=e.target,dateContainer=e.target.closest(CalendarSelectors.elements.dateContainer);if(dateContainer&&isPopoverConfigured.has(dateContainer)){const isTargetActive=target.contains(document.activeElement),isTargetHover=target.matches(":hover"),isTargetClicked=document.activeElement.contains(target);let removeListener=!0;isTargetActive||isTargetHover?isTargetClicked?(0,_jquery.default)(document.activeElement).popover("hide"):removeListener=!1:(0,_jquery.default)(target).popover("hide"),removeListener&&(target.removeEventListener("mouseleave",hidePopover),target.removeEventListener("focusout",hidePopover),target.removeEventListener("click",hidePopover))}};let listenersRegistered=!1;listenersRegistered||((()=>{const showPopoverHandler=e=>{const dayLink=e.target.closest(CalendarSelectors.links.dayLink);dayLink&&(e.preventDefault(),showPopover(dayLink))};document.addEventListener("mouseover",showPopoverHandler),document.addEventListener("focusin",showPopoverHandler)})(),listenersRegistered=!0)}));
//# sourceMappingURL=popover.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
define("core_calendar/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* A javascript module to handle calendar ajax actions.
*
* @module core_calendar/repository
* @copyright 2017 Simey Lameze <lameze@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateEventStartDay=_exports.submitCreateUpdateForm=_exports.getEventById=_exports.getCourseGroupsData=_exports.getCalendarUpcomingData=_exports.getCalendarMonthData=_exports.getCalendarDayData=_exports.deleteSubscription=_exports.deleteEvent=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.deleteEvent=function(eventId){let deleteSeries=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const request={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:eventId,repeat:deleteSeries}]}};return _ajax.default.call([request])[0]};_exports.getEventById=eventId=>{const request={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:eventId}};return _ajax.default.call([request])[0]};_exports.submitCreateUpdateForm=formData=>{const request={methodname:"core_calendar_submit_create_update_form",args:{formdata:formData}};return _ajax.default.call([request])[0]};_exports.getCalendarMonthData=function(year,month,courseId,categoryId,includeNavigation,mini){let day=arguments.length>6&&void 0!==arguments[6]?arguments[6]:1,view=arguments.length>7&&void 0!==arguments[7]?arguments[7]:"month";const request={methodname:"core_calendar_get_calendar_monthly_view",args:{year:year,month:month,courseid:courseId,categoryid:categoryId,includenavigation:includeNavigation,mini:mini,day:day,view:view}};return _ajax.default.call([request])[0]};_exports.getCalendarDayData=(year,month,day,courseId,categoryId)=>{const request={methodname:"core_calendar_get_calendar_day_view",args:{year:year,month:month,day:day,courseid:courseId,categoryid:categoryId}};return _ajax.default.call([request])[0]};_exports.updateEventStartDay=(eventId,dayTimestamp)=>{const request={methodname:"core_calendar_update_event_start_day",args:{eventid:eventId,daytimestamp:dayTimestamp}};return _ajax.default.call([request])[0]};_exports.getCalendarUpcomingData=(courseId,categoryId)=>{const request={methodname:"core_calendar_get_calendar_upcoming_view",args:{courseid:courseId,categoryid:categoryId}};return _ajax.default.call([request])[0]};_exports.getCourseGroupsData=courseId=>{const request={methodname:"core_group_get_course_groups",args:{courseid:courseId}};return _ajax.default.call([request])[0]};_exports.deleteSubscription=subscriptionId=>{const request={methodname:"core_calendar_delete_subscription",args:{subscriptionid:subscriptionId}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=repository.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* CSS selectors for the calendar.
*
* @module core_calendar/selectors
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
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"]',monthDetailed:".calendarmonth.calendartable"},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']",dayLink:"[data-action='view-day-link']"},containers:{loadingIcon:'[data-region="overlay-icon-container"]'},mainCalendar:".maincalendar .heightcontainer",fullCalendarView:"page-calendar-view",pageHeaderHeadings:".page-header-headings h1"}}));
//# sourceMappingURL=selectors.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"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 * CSS selectors for the calendar.\n *\n * @module core_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 monthDetailed: '.calendarmonth.calendartable',\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 dayLink: \"[data-action='view-day-link']\",\n },\n containers: {\n loadingIcon: '[data-region=\"overlay-icon-container\"]',\n },\n mainCalendar: '.maincalendar .heightcontainer',\n fullCalendarView: 'page-calendar-view',\n pageHeaderHeadings: '.page-header-headings h1',\n };\n});\n"],"names":["define","eventFilterItem","eventType","site","category","course","group","user","other","popoverType","calendarPeriods","month","courseSelector","viewSelector","actions","create","edit","remove","viewEvent","deleteSubscription","elements","dateContainer","dateContent","monthDetailed","today","day","calendarMain","wrapper","eventItem","links","navLink","eventLink","miniDayLink","dayLink","containers","loadingIcon","mainCalendar","fullCalendarView","pageHeaderHeadings"],"mappings":";;;;;;;AAsBAA,iCAAO,IAAI,iBACA,CACHC,gBAAiB,oCACjBC,UAAW,CACPC,KAAM,wBACNC,SAAU,4BACVC,OAAQ,0BACRC,MAAO,yBACPC,KAAM,wBACNC,MAAO,0BAEXC,YAAa,CACTN,KAAM,gCACNC,SAAU,oCACVC,OAAQ,kCACRC,MAAO,iCACPC,KAAM,gCACNC,MAAO,kCAEXE,gBAAiB,CACbC,MAAO,yBAEXC,eAAgB,wBAChBC,aAAc,mCACdC,QAAS,CACLC,OAAQ,mCACRC,KAAM,uBACNC,OAAQ,yBACRC,UAAW,6BACXC,mBAAoB,uCAExBC,SAAU,CACNR,eAAgB,wBAChBS,cAAe,sBACfC,YAAa,8BACbC,cAAe,gCAEnBC,MAAO,SACPC,IAAK,sBACLC,aAAc,2BACdC,QAAS,mBACTC,UAAW,sBACXC,MAAO,CACHC,QAAS,+BACTC,UAAW,6BACXC,YAAa,gCACbC,QAAS,iCAEbC,WAAY,CACRC,YAAa,0CAEjBC,aAAc,iCACdC,iBAAkB,qBAClBC,mBAAoB"}
+3
View File
@@ -0,0 +1,3 @@
define("core_calendar/summary_modal",["exports","jquery","core/custom_interaction_events","core/modal","./events","core_calendar/crud","core/modal_events"],(function(_exports,_jquery,CustomEvents,_modal,_events,CalendarCrud,ModalEvents){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),CustomEvents=_interopRequireWildcard(CustomEvents),_modal=_interopRequireDefault(_modal),_events=_interopRequireDefault(_events),CalendarCrud=_interopRequireWildcard(CalendarCrud),ModalEvents=_interopRequireWildcard(ModalEvents);const SELECTORS_ROOT="[data-region='summary-modal-container']",SELECTORS_EDIT_BUTTON='[data-action="edit"]',SELECTORS_DELETE_BUTTON='[data-action="delete"]';class ModalEventSummary extends _modal.default{getEditButton(){return void 0===this.editButton&&(this.editButton=this.getFooter().find(SELECTORS_EDIT_BUTTON)),this.editButton}getDeleteButton(){return void 0===this.deleteButton&&(this.deleteButton=this.getFooter().find(SELECTORS_DELETE_BUTTON)),this.deleteButton}getEventId(){return this.getBody().find(SELECTORS_ROOT).attr("data-event-id")}getEventTitle(){return this.getBody().find(SELECTORS_ROOT).attr("data-event-title")}getEventCount(){return this.getBody().find(SELECTORS_ROOT).attr("data-event-count")}getEditUrl(){return this.getBody().find(SELECTORS_ROOT).attr("data-edit-url")}isActionEvent(){return"true"==this.getBody().find(SELECTORS_ROOT).attr("data-action-event")}registerEventListeners(){super.registerEventListeners(this),M.util.js_pending("core_calendar/summary_modal:registerEventListeners:bodyRendered"),this.getRoot().on(ModalEvents.bodyRendered,function(){this.getModal().data({eventTitle:this.getEventTitle(),eventId:this.getEventId(),eventCount:this.getEventCount()}).attr("data-type","event"),CalendarCrud.registerRemove(this.getModal()),M.util.js_complete("core_calendar/summary_modal:registerEventListeners:bodyRendered")}.bind(this)),(0,_jquery.default)("body").on(_events.default.deleted,function(){this.hide()}.bind(this)),CustomEvents.define(this.getEditButton(),[CustomEvents.events.activate]),this.getEditButton().on(CustomEvents.events.activate,function(e,data){this.isActionEvent()?(0,_jquery.default)("body").trigger(_events.default.editActionEvent,[this.getEditUrl()]):(0,_jquery.default)("body").trigger(_events.default.editEvent,[this.getEventId()]),this.hide(),e.preventDefault(),e.stopPropagation(),data.originalEvent.preventDefault(),data.originalEvent.stopPropagation()}.bind(this))}}return _exports.default=ModalEventSummary,_defineProperty(ModalEventSummary,"TEMPLATE","core_calendar/event_summary_modal"),_defineProperty(ModalEventSummary,"TYPE","core_calendar-event_summary"),ModalEventSummary.registerModalType(),_exports.default}));
//# sourceMappingURL=summary_modal.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+284
View File
@@ -0,0 +1,284 @@
// 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/>.
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module core_calendar/calendar
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/templates',
'core/notification',
'core_calendar/repository',
'core_calendar/events',
'core_calendar/view_manager',
'core_calendar/crud',
'core_calendar/selectors',
'core/url',
'core/str',
],
function(
$,
Templates,
Notification,
CalendarRepository,
CalendarEvents,
CalendarViewManager,
CalendarCrud,
CalendarSelectors,
Url,
Str,
) {
var SELECTORS = {
ROOT: "[data-region='calendar']",
DAY: "[data-region='day']",
NEW_EVENT_BUTTON: "[data-action='new-event-button']",
DAY_CONTENT: "[data-region='day-content']",
LOADING_ICON: '.loading-icon',
VIEW_DAY_LINK: "[data-action='view-day-link']",
CALENDAR_MONTH_WRAPPER: ".calendarwrapper",
TODAY: '.today',
DAY_NUMBER_CIRCLE: '.day-number-circle',
DAY_NUMBER: '.day-number',
SCREEN_READER_ANNOUNCEMENTS: '.calendar-announcements',
CURRENT_MONTH: '.calendar-controls .current'
};
/**
* Handler for the drag and drop move event. Provides a loading indicator
* while the request is sent to the server to update the event start date.
*
* Triggers a eventMoved calendar javascript event if the event was successfully
* updated.
*
* @param {event} e The calendar move event
* @param {int} eventId The event id being moved
* @param {object|null} originElement The jQuery element for where the event is moving from
* @param {object} destinationElement The jQuery element for where the event is moving to
*/
var handleMoveEvent = function(e, eventId, originElement, destinationElement) {
var originTimestamp = null;
var destinationTimestamp = destinationElement.attr('data-day-timestamp');
if (originElement) {
originTimestamp = originElement.attr('data-day-timestamp');
}
// If the event has actually changed day.
if (!originElement || originTimestamp != destinationTimestamp) {
Templates.render('core/loading', {})
.then(function(html, js) {
// First we show some loading icons in each of the days being affected.
destinationElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
Templates.appendNodeContents(destinationElement, html, js);
if (originElement) {
originElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
Templates.appendNodeContents(originElement, html, js);
}
return;
})
.then(function() {
// Send a request to the server to make the change.
return CalendarRepository.updateEventStartDay(eventId, destinationTimestamp);
})
.then(function() {
// If the update was successful then broadcast an event letting the calendar
// know that an event has been moved.
$('body').trigger(CalendarEvents.eventMoved, [eventId, originElement, destinationElement]);
return;
})
.always(function() {
// Always remove the loading icons regardless of whether the update
// request was successful or not.
var destinationLoadingElement = destinationElement.find(SELECTORS.LOADING_ICON);
destinationElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
Templates.replaceNode(destinationLoadingElement, '', '');
if (originElement) {
var originLoadingElement = originElement.find(SELECTORS.LOADING_ICON);
originElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
Templates.replaceNode(originLoadingElement, '', '');
}
return;
})
.catch(Notification.exception);
}
};
/**
* Listen to and handle any calendar events fired by the calendar UI.
*
* @method registerCalendarEventListeners
* @param {object} root The calendar root element
* @param {object} eventFormModalPromise A promise reolved with the event form modal
*/
var registerCalendarEventListeners = function(root, eventFormModalPromise) {
var body = $('body');
body.on(CalendarEvents.created, function() {
CalendarViewManager.reloadCurrentMonth(root);
});
body.on(CalendarEvents.deleted, function() {
CalendarViewManager.reloadCurrentMonth(root);
});
body.on(CalendarEvents.updated, function() {
CalendarViewManager.reloadCurrentMonth(root);
});
body.on(CalendarEvents.editActionEvent, function(e, url) {
// Action events needs to be edit directly on the course module.
window.location.assign(url);
});
// Handle the event fired by the drag and drop code.
body.on(CalendarEvents.moveEvent, handleMoveEvent);
// When an event is successfully moved we should updated the UI.
body.on(CalendarEvents.eventMoved, function() {
CalendarViewManager.reloadCurrentMonth(root);
});
// Announce the newly loaded month to screen readers.
body.on(CalendarEvents.monthChanged, root, async function() {
const monthName = body.find(SELECTORS.CURRENT_MONTH).text();
const monthAnnoucement = await Str.get_string('newmonthannouncement', 'calendar', monthName);
body.find(SELECTORS.SCREEN_READER_ANNOUNCEMENTS).html(monthAnnoucement);
});
CalendarCrud.registerEditListeners(root, eventFormModalPromise);
};
/**
* Register event listeners for the module.
*
* @param {object} root The calendar root element
* @param {boolean} isCalendarBlock - A flag indicating whether this is a calendar block.
*/
var registerEventListeners = function(root, isCalendarBlock) {
const viewingFullCalendar = document.getElementById(CalendarSelectors.fullCalendarView);
// Listen the click on the day link to render the day view.
root.on('click', SELECTORS.VIEW_DAY_LINK, function(e) {
var dayLink = $(e.target).closest(SELECTORS.VIEW_DAY_LINK);
var year = dayLink.data('year'),
month = dayLink.data('month'),
day = dayLink.data('day'),
courseId = dayLink.data('courseid'),
categoryId = dayLink.data('categoryid');
const urlParams = {
view: 'day',
time: dayLink.data('timestamp'),
course: courseId,
};
if (viewingFullCalendar) {
// Construct the URL parameter string from the urlParams object.
const urlParamString = Object.entries(urlParams)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
CalendarViewManager.refreshDayContent(root, year, month, day, courseId, categoryId, root,
'core_calendar/calendar_day', isCalendarBlock).then(function() {
e.preventDefault();
// Update the URL if it's not calendar block.
if (!isCalendarBlock) {
CalendarViewManager.updateUrl('?' + urlParamString);
}
return;
}).catch(Notification.exception);
} else {
window.location.assign(Url.relativeUrl('calendar/view.php', urlParams));
}
});
root.on('change', CalendarSelectors.elements.courseSelector, function() {
var selectElement = $(this);
var courseId = selectElement.val();
const courseName = $("option:selected", selectElement).text();
CalendarViewManager.reloadCurrentMonth(root, courseId, null)
.then(function() {
// We need to get the selector again because the content has changed.
return root.find(CalendarSelectors.elements.courseSelector).val(courseId);
})
.then(function() {
CalendarViewManager.updateUrl('?view=month&course=' + courseId);
CalendarViewManager.handleCourseChange(Number(courseId), courseName);
return;
})
.catch(Notification.exception);
});
var eventFormPromise = CalendarCrud.registerEventFormModal(root),
contextId = $(SELECTORS.CALENDAR_MONTH_WRAPPER).data('context-id');
registerCalendarEventListeners(root, eventFormPromise);
if (contextId) {
// Bind click events to calendar days.
root.on('click', SELECTORS.DAY, function(e) {
var target = $(e.target);
const displayingSmallBlockCalendar = root.parents('aside').data('blockregion') === 'side-pre';
if (!viewingFullCalendar && displayingSmallBlockCalendar) {
const dateContainer = target.closest(SELECTORS.DAY);
const wrapper = target.closest(CalendarSelectors.wrapper);
const courseId = wrapper.data('courseid');
const params = {
view: 'day',
time: dateContainer.data('day-timestamp'),
course: courseId,
};
window.location.assign(Url.relativeUrl('calendar/view.php', params));
} else {
const hasViewDayLink = target.closest(SELECTORS.VIEW_DAY_LINK).length;
const shouldShowNewEventModal = !hasViewDayLink;
if (shouldShowNewEventModal) {
var startTime = $(this).attr('data-new-event-timestamp');
eventFormPromise.then(function(modal) {
var wrapper = target.closest(CalendarSelectors.wrapper);
modal.setCourseId(wrapper.data('courseid'));
var categoryId = wrapper.data('categoryid');
if (typeof categoryId !== 'undefined') {
modal.setCategoryId(categoryId);
}
modal.setContextId(wrapper.data('contextId'));
modal.setStartTime(startTime);
modal.show();
return;
}).catch(Notification.exception);
}
}
e.preventDefault();
});
}
};
return {
/**
* Initializes the calendar view manager and registers event listeners.
*
* @param {HTMLElement} root - The root element where the calendar view manager and event listeners will be attached.
* @param {boolean} [isCalendarBlock=false] - A flag indicating whether this is a calendar block.
*/
init: function(root, isCalendarBlock = false) {
root = $(root);
CalendarViewManager.init(root, 'month', isCalendarBlock);
registerEventListeners(root, isCalendarBlock);
}
};
});
+123
View File
@@ -0,0 +1,123 @@
// 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/>.
/**
* This module is responsible for the calendar filter.
*
* @module core_calendar/calendar_filter
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core_calendar/selectors',
'core_calendar/events',
'core/str',
'core/templates',
],
function(
$,
CalendarSelectors,
CalendarEvents,
Str,
Templates
) {
var registerEventListeners = function(root) {
root.on('click', CalendarSelectors.eventFilterItem, function(e) {
var target = $(e.currentTarget);
toggleFilter(target);
e.preventDefault();
});
$('body').on(CalendarEvents.viewUpdated, function() {
var filters = root.find(CalendarSelectors.eventFilterItem);
filters.each(function(i, filter) {
filter = $(filter);
if (filter.data('eventtype-hidden')) {
var data = getFilterData(filter);
fireFilterChangedEvent(data);
}
});
});
};
var toggleFilter = function(target) {
var data = getFilterData(target);
// Toggle the hidden. We need to render the template before we change the value.
data.hidden = !data.hidden;
M.util.js_pending("core_calendar/calendar_filter:toggleFilter");
return Str.get_string('eventtype' + data.eventtype, 'calendar')
.then(function(nameStr) {
data.name = nameStr;
data.icon = true;
data.key = 'i/' + data.eventtype + 'event';
data.component = 'core';
return data;
})
.then(function(context) {
return Templates.render('core_calendar/event_filter_key', context);
})
.then(function(html, js) {
return Templates.replaceNode(target, html, js);
})
.then(function() {
fireFilterChangedEvent(data);
M.util.js_complete("core_calendar/calendar_filter:toggleFilter");
return;
});
};
/**
* Fire the filterChanged event for the specified data.
*
* @param {object} data The data to include
*/
var fireFilterChangedEvent = function(data) {
M.util.js_pending("month-mini-filterChanged");
$('body').trigger(CalendarEvents.filterChanged, {
type: data.eventtype,
hidden: data.hidden,
});
M.util.js_complete("month-mini-filterChanged");
};
/**
* Get the filter data for the specified target.
*
* @param {jQuery} target The target node
* @return {Object}
*/
var getFilterData = function(target) {
return {
eventtype: target.data('eventtype'),
hidden: target.data('eventtype-hidden'),
};
};
return {
init: function(root) {
root = $(root);
registerEventListeners(root);
}
};
});
+116
View File
@@ -0,0 +1,116 @@
// 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/>.
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module core_calendar/calendar_mini
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core_calendar/selectors',
'core_calendar/events',
'core_calendar/view_manager',
],
function(
$,
CalendarSelectors,
CalendarEvents,
CalendarViewManager
) {
/**
* Listen to and handle any calendar events fired by the calendar UI.
*
* @method registerCalendarEventListeners
* @param {object} root The calendar root element
*/
var registerCalendarEventListeners = function(root) {
var body = $('body');
var namespace = '.' + root.attr('id');
body.on(CalendarEvents.created + namespace, root, reloadMonth);
body.on(CalendarEvents.deleted + namespace, root, reloadMonth);
body.on(CalendarEvents.updated + namespace, root, reloadMonth);
body.on(CalendarEvents.eventMoved + namespace, root, reloadMonth);
};
/**
* Reload the month view in this month.
*
* @param {EventFacade} e
*/
var reloadMonth = function(e) {
var root = e.data;
var body = $('body');
var namespace = '.' + root.attr('id');
if (root.is(':visible')) {
CalendarViewManager.reloadCurrentMonth(root);
} else {
// The root has been removed.
// Remove all events in the namespace.
body.off(CalendarEvents.created + namespace);
body.off(CalendarEvents.deleted + namespace);
body.off(CalendarEvents.updated + namespace);
body.off(CalendarEvents.eventMoved + namespace);
}
};
var registerEventListeners = function(root) {
$('body').on(CalendarEvents.filterChanged, function(e, data) {
var daysWithEvent = root.find(CalendarSelectors.eventType[data.type]);
daysWithEvent.toggleClass('calendar_event_' + data.type, !data.hidden);
});
var namespace = '.' + root.attr('id');
$('body').on('change' + namespace, CalendarSelectors.elements.courseSelector, function() {
if (root.is(':visible')) {
var selectElement = $(this);
var courseId = selectElement.val();
var categoryId = null;
CalendarViewManager.reloadCurrentMonth(root, courseId, categoryId);
} else {
$('body').off('change' + namespace);
}
});
};
return {
init: function(root, loadOnInit) {
root = $(root);
CalendarViewManager.init(root);
registerEventListeners(root);
registerCalendarEventListeners(root);
if (loadOnInit) {
// The calendar hasn't yet loaded it's events so we
// should load them as soon as we've initialised.
CalendarViewManager.reloadCurrentMonth(root);
}
}
};
});
+96
View File
@@ -0,0 +1,96 @@
// 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/>.
/**
* This module is responsible for handle calendar day and upcoming view.
*
* @module core_calendar/calendar_view
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/notification',
'core_calendar/selectors',
'core_calendar/events',
'core_calendar/view_manager',
'core_calendar/crud'
],
function(
$,
Notification,
CalendarSelectors,
CalendarEvents,
CalendarViewManager,
CalendarCrud
) {
var registerEventListeners = function(root, type) {
var body = $('body');
CalendarCrud.registerRemove(root);
var reloadFunction = 'reloadCurrent' + type.charAt(0).toUpperCase() + type.slice(1);
body.on(CalendarEvents.created, function() {
CalendarViewManager[reloadFunction](root);
});
body.on(CalendarEvents.deleted, function() {
CalendarViewManager[reloadFunction](root);
});
body.on(CalendarEvents.updated, function() {
CalendarViewManager[reloadFunction](root);
});
root.on('change', CalendarSelectors.courseSelector, function() {
var selectElement = $(this);
var courseId = selectElement.val();
const courseName = $("option:selected", selectElement).text();
CalendarViewManager[reloadFunction](root, courseId, null)
.then(function() {
// We need to get the selector again because the content has changed.
return root.find(CalendarSelectors.courseSelector).val(courseId);
})
.then(function() {
CalendarViewManager.updateUrl('?view=' + type + '&course=' + courseId);
CalendarViewManager.handleCourseChange(Number(courseId), courseName);
return;
})
.fail(Notification.exception);
});
body.on(CalendarEvents.filterChanged, function(e, data) {
var daysWithEvent = root.find(CalendarSelectors.eventType[data.type]);
if (data.hidden == true) {
daysWithEvent.addClass('hidden');
} else {
daysWithEvent.removeClass('hidden');
}
CalendarViewManager.foldDayEvents(root);
});
var eventFormPromise = CalendarCrud.registerEventFormModal(root);
CalendarCrud.registerEditListeners(root, eventFormPromise);
};
return {
init: function(root, type, isCalendarBlock = false) {
root = $(root);
CalendarViewManager.init(root, type, isCalendarBlock);
registerEventListeners(root, type, isCalendarBlock);
}
};
});
+262
View File
@@ -0,0 +1,262 @@
// 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 CRUD operations within the UI.
*
* @module core_calendar/crud
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/str',
'core/notification',
'core/modal_events',
'core_calendar/modal_event_form',
'core_calendar/repository',
'core_calendar/events',
'core_calendar/modal_delete',
'core_calendar/selectors',
'core/pending',
'core/modal_save_cancel',
'core/config',
],
function(
$,
Str,
Notification,
ModalEvents,
ModalEventForm,
CalendarRepository,
CalendarEvents,
CalendarModalDelete,
CalendarSelectors,
Pending,
ModalSaveCancel,
Config,
) {
/**
* Prepares the action for the summary modal's delete action.
*
* @param {Number} eventId The ID of the event.
* @param {string} eventTitle The event title.
* @param {Number} eventCount The number of events in the series.
* @return {Promise}
*/
function confirmDeletion(eventId, eventTitle, eventCount) {
var pendingPromise = new Pending('core_calendar/crud:confirmDeletion');
var deleteStrings = [
{
key: 'deleteevent',
component: 'calendar'
},
];
eventCount = parseInt(eventCount, 10);
var deletePromise;
var isRepeatedEvent = eventCount > 1;
if (isRepeatedEvent) {
deleteStrings.push({
key: 'confirmeventseriesdelete',
component: 'calendar',
param: {
name: eventTitle,
count: eventCount,
},
});
deletePromise = CalendarModalDelete.create();
} else {
deleteStrings.push({
key: 'confirmeventdelete',
component: 'calendar',
param: eventTitle
});
deletePromise = ModalSaveCancel.create();
}
var stringsPromise = Str.get_strings(deleteStrings);
var finalPromise = $.when(stringsPromise, deletePromise)
.then(function(strings, deleteModal) {
deleteModal.setRemoveOnClose(true);
deleteModal.setTitle(strings[0]);
deleteModal.setBody(strings[1]);
if (!isRepeatedEvent) {
deleteModal.setSaveButtonText(strings[0]);
}
deleteModal.show();
deleteModal.getRoot().on(ModalEvents.save, function() {
var pendingPromise = new Pending('calendar/crud:initModal:deletedevent');
CalendarRepository.deleteEvent(eventId, false)
.then(function() {
$('body').trigger(CalendarEvents.deleted, [eventId, false]);
return;
})
.then(pendingPromise.resolve)
.catch(Notification.exception);
});
deleteModal.getRoot().on(CalendarEvents.deleteAll, function() {
var pendingPromise = new Pending('calendar/crud:initModal:deletedallevent');
CalendarRepository.deleteEvent(eventId, true)
.then(function() {
$('body').trigger(CalendarEvents.deleted, [eventId, true]);
return;
})
.then(pendingPromise.resolve)
.catch(Notification.exception);
});
return deleteModal;
})
.then(function(modal) {
pendingPromise.resolve();
return modal;
})
.catch(Notification.exception);
return finalPromise;
}
/**
* Create the event form modal for creating new events and
* editing existing events.
*
* @method registerEventFormModal
* @param {object} root The calendar root element
* @return {object} The create modal promise
*/
var registerEventFormModal = function(root) {
var eventFormPromise = ModalEventForm.create();
// Bind click event on the new event button.
root.on('click', CalendarSelectors.actions.create, function(e) {
eventFormPromise.then(function(modal) {
var wrapper = root.find(CalendarSelectors.wrapper);
var categoryId = wrapper.data('categoryid');
const courseId = wrapper.data('courseid');
if (typeof categoryId !== 'undefined' && courseId != Config.siteId) {
modal.setCategoryId(categoryId);
}
// Attempt to find the cell for today.
// If it can't be found, then use the start time of the first day on the calendar.
var today = root.find(CalendarSelectors.today);
var firstDay = root.find(CalendarSelectors.day);
if (!today.length && firstDay.length) {
modal.setStartTime(firstDay.data('newEventTimestamp'));
}
modal.setContextId(wrapper.data('contextId'));
modal.setCourseId(wrapper.data('courseid'));
modal.show();
return;
})
.catch(Notification.exception);
e.preventDefault();
});
root.on('click', CalendarSelectors.actions.edit, function(e) {
e.preventDefault();
var target = $(e.currentTarget),
calendarWrapper = target.closest(CalendarSelectors.wrapper),
eventWrapper = target.closest(CalendarSelectors.eventItem);
eventFormPromise.then(function(modal) {
// When something within the calendar tells us the user wants
// to edit an event then show the event form modal.
modal.setEventId(eventWrapper.data('eventId'));
modal.setContextId(calendarWrapper.data('contextId'));
modal.setCourseId(eventWrapper.data('courseId'));
modal.show();
e.stopImmediatePropagation();
return;
}).catch(Notification.exception);
});
return eventFormPromise;
};
/**
* Register the listeners required to remove the event.
*
* @param {jQuery} root
*/
function registerRemove(root) {
root.on('click', CalendarSelectors.actions.remove, function(e) {
// Fetch the event title, count, and pass them into the new dialogue.
var eventSource = $(this).closest(CalendarSelectors.eventItem);
var eventId = eventSource.data('eventId'),
eventTitle = eventSource.data('eventTitle'),
eventCount = eventSource.data('eventCount');
confirmDeletion(eventId, eventTitle, eventCount);
e.preventDefault();
});
}
/**
* Register the listeners required to edit the event.
*
* @param {jQuery} root
* @param {Promise} eventFormModalPromise
* @returns {Promise}
*/
function registerEditListeners(root, eventFormModalPromise) {
var pendingPromise = new Pending('core_calendar/crud:registerEditListeners');
return eventFormModalPromise
.then(function(modal) {
// When something within the calendar tells us the user wants
// to edit an event then show the event form modal.
$('body').on(CalendarEvents.editEvent, function(e, eventId) {
var target = root.find(`[data-event-id=${eventId}]`),
calendarWrapper = root.find(CalendarSelectors.wrapper);
modal.setEventId(eventId);
modal.setContextId(calendarWrapper.data('contextId'));
modal.setReturnElement(target);
modal.show();
e.stopImmediatePropagation();
});
return modal;
})
.then(function(modal) {
pendingPromise.resolve();
return modal;
})
.catch(Notification.exception);
}
return {
registerRemove: registerRemove,
registerEditListeners: registerEditListeners,
registerEventFormModal: registerEventFormModal
};
});
+208
View File
@@ -0,0 +1,208 @@
// 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 javascript module to store calendar drag and drop data.
*
* This module is unfortunately required because of the limitations
* of the HTML5 drag and drop API and it's ability to provide data
* between the different stages of the drag/drop lifecycle.
*
* @module core_calendar/drag_drop_data_store
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
/* @var {int|null} eventId The id of the event being dragged */
var eventId = null;
/* @var {int|null} durationDays How many days the event spans */
var durationDays = null;
/* @var {int|null} minTimestart The earliest valid timestart */
var minTimestart = null;
/* @var {int|null} maxTimestart The latest valid tiemstart */
var maxTimestart = null;
/* @var {string|null} minError Error message for min timestamp violation */
var minError = null;
/* @var {string|null} maxError Error message for max timestamp violation */
var maxError = null;
/**
* Store the id of the event being dragged.
*
* @param {int} id The event id
*/
var setEventId = function(id) {
eventId = id;
};
/**
* Get the stored event id.
*
* @return {int|null}
*/
var getEventId = function() {
return eventId;
};
/**
* Check if the store has an event id.
*
* @return {bool}
*/
var hasEventId = function() {
return eventId !== null;
};
/**
* Store the duration (in days) of the event being dragged.
*
* @param {int} days Number of days the event spans
*/
var setDurationDays = function(days) {
durationDays = days;
};
/**
* Get the stored number of days.
*
* @return {int|null}
*/
var getDurationDays = function() {
return durationDays;
};
/**
* Store the minimum timestart valid for an event being dragged.
*
* @param {int} timestamp The unix timstamp
*/
var setMinTimestart = function(timestamp) {
minTimestart = timestamp;
};
/**
* Get the minimum valid timestart.
*
* @return {int|null}
*/
var getMinTimestart = function() {
return minTimestart;
};
/**
* Check if a minimum timestamp is set.
*
* @return {bool}
*/
var hasMinTimestart = function() {
return minTimestart !== null;
};
/**
* Store the maximum timestart valid for an event being dragged.
*
* @param {int} timestamp The unix timstamp
*/
var setMaxTimestart = function(timestamp) {
maxTimestart = timestamp;
};
/**
* Get the maximum valid timestart.
*
* @return {int|null}
*/
var getMaxTimestart = function() {
return maxTimestart;
};
/**
* Check if a maximum timestamp is set.
*
* @return {bool}
*/
var hasMaxTimestart = function() {
return maxTimestart !== null;
};
/**
* Store the error string to display if trying to drag an event
* earlier than the minimum allowed date.
*
* @param {string} message The error message
*/
var setMinError = function(message) {
minError = message;
};
/**
* Get the error message for a minimum time start violation.
*
* @return {string|null}
*/
var getMinError = function() {
return minError;
};
/**
* Store the error string to display if trying to drag an event
* later than the maximum allowed date.
*
* @param {string} message The error message
*/
var setMaxError = function(message) {
maxError = message;
};
/**
* Get the error message for a maximum time start violation.
*
* @return {string|null}
*/
var getMaxError = function() {
return maxError;
};
/**
* Reset all of the stored values.
*/
var clearAll = function() {
setEventId(null);
setDurationDays(null);
setMinTimestart(null);
setMaxTimestart(null);
setMinError(null);
setMaxError(null);
};
return {
setEventId: setEventId,
getEventId: getEventId,
hasEventId: hasEventId,
setDurationDays: setDurationDays,
getDurationDays: getDurationDays,
setMinTimestart: setMinTimestart,
getMinTimestart: getMinTimestart,
hasMinTimestart: hasMinTimestart,
setMaxTimestart: setMaxTimestart,
getMaxTimestart: getMaxTimestart,
hasMaxTimestart: hasMaxTimestart,
setMinError: setMinError,
getMinError: getMinError,
setMaxError: setMaxError,
getMaxError: getMaxError,
clearAll: clearAll
};
});
+84
View File
@@ -0,0 +1,84 @@
// 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 javascript module to enhance the event form.
*
* @module core_calendar/event_form
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core_calendar/repository', 'core/notification'], function($, CalendarRepository, Notification) {
var SELECTORS = {
EVENT_GROUP_COURSE_ID: '[name="groupcourseid"]',
EVENT_GROUP_ID: '[name="groupid"]',
SELECT_OPTION: 'option'
};
/**
* Listen for when the user changes the group course when configuring
* a group event and filter the options in the group select to only
* show the groups available within the course the user has selected.
*
* @method addCourseGroupSelectListeners
* @param {object} formElement The root form element
*/
var addCourseGroupSelectListeners = function(formElement) {
var courseGroupSelect = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID);
var loadGroupSelectOptions = function(groups) {
var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID),
groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION),
courseGroups = $(groups);
// Let's clear all options first.
groupSelectOptions.remove();
groupSelect.prop("disabled", false);
courseGroups.each(function(id, group) {
$(groupSelect).append($("<option></option>").attr("value", group.id).text(group.name));
});
};
// If the user choose a course in the selector do a WS request to get groups.
courseGroupSelect.on('change', function() {
var courseId = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID).val();
if (isNaN(courseId) || courseId <= 0) {
loadGroupSelectOptions([]);
} else {
CalendarRepository.getCourseGroupsData(courseId)
.then(function(groups) {
return loadGroupSelectOptions(groups);
})
.catch(Notification.exception);
}
});
};
/**
* Initialise all of the form enhancements.
*
* @method init
* @param {string} formId The value of the form's id attribute
*/
var init = function(formId) {
var formElement = $('#' + formId);
addCourseGroupSelectListeners(formElement);
};
return {
init: init,
};
});
+37
View File
@@ -0,0 +1,37 @@
// 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/>.
/**
* Contain the events the calendar component can fire.
*
* @module core_calendar/events
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
created: 'calendar-events:created',
deleted: 'calendar-events:deleted',
deleteAll: 'calendar-events:delete_all',
updated: 'calendar-events:updated',
editEvent: 'calendar-events:edit_event',
editActionEvent: 'calendar-events:edit_action_event',
eventMoved: 'calendar-events:event_moved',
dayChanged: 'calendar-events:day_changed',
monthChanged: 'calendar-events:month_changed',
moveEvent: 'calendar-events:move_event',
filterChanged: 'calendar-events:filter_changed',
courseChanged: 'calendar-events:course_changed',
viewUpdated: 'calendar-events:view_updated',
};
+45
View File
@@ -0,0 +1,45 @@
// 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 javascript module to enhance the calendar export form.
*
* @module core_calendar/export
* @copyright 2021 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import 'core/copy_to_clipboard';
/**
* Selectors for the calendar export page.
*
* @property {string} copyUrlId The element ID of the Copy URL button.
*/
const selectors = {
copyUrlId: 'copyexporturl',
};
/**
* Initialises the calendar export JS module.
*
* @method init
*/
export const init = () => {
// Enable the copy URL button and focus on it.
const copyUrl = document.getElementById(selectors.copyUrlId);
copyUrl.removeAttribute('disabled');
copyUrl.focus();
};
+144
View File
@@ -0,0 +1,144 @@
// 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 ModalSaveCancel from 'core/modal_save_cancel';
import * as ModalEvents from 'core/modal_events';
import {exception as displayException, addNotification, fetchNotifications} from 'core/notification';
import Prefetch from 'core/prefetch';
import {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 ModalSaveCancel.create({
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();
};
+92
View File
@@ -0,0 +1,92 @@
// 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/>.
/**
* Contain the logic for the delete modal.
*
* @module core_calendar/modal_delete
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import * as CustomEvents from 'core/custom_interaction_events';
import Modal from 'core/modal';
import ModalEvents from 'core/modal_events';
import CalendarEvents from './events';
const SELECTORS = {
DELETE_ONE_BUTTON: '[data-action="deleteone"]',
DELETE_ALL_BUTTON: '[data-action="deleteall"]',
CANCEL_BUTTON: '[data-action="cancel"]',
};
/**
* Constructor for the Modal.
*
* @class
* @param {object} root The root jQuery element for the modal
*/
export default class ModalDelete extends Modal {
static TYPE = 'core_calendar-modal_delete';
static TEMPLATE = 'calendar/event_delete_modal';
constructor(root) {
super(root);
this.setRemoveOnClose(true);
}
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
*/
registerEventListeners() {
// Apply parent event listeners.
super.registerEventListeners(this);
this.getModal().on(CustomEvents.events.activate, SELECTORS.DELETE_ONE_BUTTON, (e, data) => {
const saveEvent = $.Event(ModalEvents.save);
this.getRoot().trigger(saveEvent, this);
if (!saveEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
});
this.getModal().on(CustomEvents.events.activate, SELECTORS.DELETE_ALL_BUTTON, (e, data) => {
const saveEvent = $.Event(CalendarEvents.deleteAll);
this.getRoot().trigger(saveEvent, this);
if (!saveEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
});
this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, (e, data) => {
const cancelEvent = $.Event(ModalEvents.cancel);
this.getRoot().trigger(cancelEvent, this);
if (!cancelEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
});
}
}
ModalDelete.registerModalType();
+484
View File
@@ -0,0 +1,484 @@
// 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/>.
/**
* Contain the logic for the quick add or update event modal.
*
* @module core_calendar/modal_event_form
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import * as CustomEvents from 'core/custom_interaction_events';
import Modal from 'core/modal';
import * as FormEvents from 'core_form/events';
import CalendarEvents from './events';
import * as Str from 'core/str';
import * as Notification from 'core/notification';
import * as Fragment from 'core/fragment';
import * as Repository from 'core_calendar/repository';
const SELECTORS = {
SAVE_BUTTON: '[data-action="save"]',
LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
};
export default class ModalEventForm extends Modal {
static TYPE = 'core_calendar-modal_event_form';
static TEMPLATE = 'calendar/modal_event_form';
/**
* Constructor for the Modal.
*
* @param {object} root The root jQuery element for the modal
*/
constructor(root) {
super(root);
this.eventId = null;
this.startTime = null;
this.courseId = null;
this.categoryId = null;
this.contextId = null;
this.reloadingBody = false;
this.reloadingTitle = false;
this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);
}
configure(modalConfig) {
modalConfig.large = true;
super.configure(modalConfig);
}
/**
* Set the context id to the given value.
*
* @method setContextId
* @param {Number} id The event id
*/
setContextId(id) {
this.contextId = id;
}
/**
* Retrieve the current context id, if any.
*
* @method getContextId
* @return {Number|null} The event id
*/
getContextId() {
return this.contextId;
}
/**
* Set the course id to the given value.
*
* @method setCourseId
* @param {Number} id The event id
*/
setCourseId(id) {
this.courseId = id;
}
/**
* Retrieve the current course id, if any.
*
* @method getCourseId
* @return {Number|null} The event id
*/
getCourseId() {
return this.courseId;
}
/**
* Set the category id to the given value.
*
* @method setCategoryId
* @param {Number} id The event id
*/
setCategoryId(id) {
this.categoryId = id;
}
/**
* Retrieve the current category id, if any.
*
* @method getCategoryId
* @return {Number|null} The event id
*/
getCategoryId() {
return this.categoryId;
}
/**
* Check if the modal has an course id.
*
* @method hasCourseId
* @return {bool}
*/
hasCourseId() {
return this.courseId !== null;
}
/**
* Check if the modal has an category id.
*
* @method hasCategoryId
* @return {bool}
*/
hasCategoryId() {
return this.categoryId !== null;
}
/**
* Set the event id to the given value.
*
* @method setEventId
* @param {Number} id The event id
*/
setEventId(id) {
this.eventId = id;
}
/**
* Retrieve the current event id, if any.
*
* @method getEventId
* @return {Number|null} The event id
*/
getEventId() {
return this.eventId;
}
/**
* Check if the modal has an event id.
*
* @method hasEventId
* @return {bool}
*/
hasEventId() {
return this.eventId !== null;
}
/**
* Set the start time to the given value.
*
* @method setStartTime
* @param {Number} time The start time
*/
setStartTime(time) {
this.startTime = time;
}
/**
* Retrieve the current start time, if any.
*
* @method getStartTime
* @return {Number|null} The start time
*/
getStartTime() {
return this.startTime;
}
/**
* Check if the modal has start time.
*
* @method hasStartTime
* @return {bool}
*/
hasStartTime() {
return this.startTime !== null;
}
/**
* Get the form element from the modal.
*
* @method getForm
* @return {object}
*/
getForm() {
return this.getBody().find('form');
}
/**
* Disable the buttons in the footer.
*
* @method disableButtons
*/
disableButtons() {
this.saveButton.prop('disabled', true);
}
/**
* Enable the buttons in the footer.
*
* @method enableButtons
*/
enableButtons() {
this.saveButton.prop('disabled', false);
}
/**
* Reload the title for the modal to the appropriate value
* depending on whether we are creating a new event or
* editing an existing event.
*
* @method reloadTitleContent
* @return {object} A promise resolved with the new title text
*/
reloadTitleContent() {
if (this.reloadingTitle) {
return this.titlePromise;
}
this.reloadingTitle = true;
if (this.hasEventId()) {
this.titlePromise = Str.get_string('editevent', 'calendar');
} else {
this.titlePromise = Str.get_string('newevent', 'calendar');
}
this.titlePromise.then((string) => {
this.setTitle(string);
return string;
})
.catch(Notification.exception)
.always(() => {
this.reloadingTitle = false;
return;
});
return this.titlePromise;
}
/**
* Send a request to the server to get the event_form in a fragment
* and render the result in the body of the modal.
*
* If serialised form data is provided then it will be sent in the
* request to the server to have the form rendered with the data. This
* is used when the form had a server side error and we need the server
* to re-render it for us to display the error to the user.
*
* @method reloadBodyContent
* @param {string} formData The serialised form data
* @return {object} A promise resolved with the fragment html and js from
*/
reloadBodyContent(formData) {
if (this.reloadingBody) {
return this.bodyPromise;
}
this.reloadingBody = true;
this.disableButtons();
const args = {};
if (this.hasEventId()) {
args.eventid = this.getEventId();
}
if (this.hasStartTime()) {
args.starttime = this.getStartTime();
}
if (this.hasCourseId()) {
args.courseid = this.getCourseId();
}
if (this.hasCategoryId()) {
args.categoryid = this.getCategoryId();
}
if (typeof formData !== 'undefined') {
args.formdata = formData;
}
this.bodyPromise = Fragment.loadFragment('calendar', 'event_form', this.getContextId(), args);
this.setBody(this.bodyPromise);
this.bodyPromise.then(() => {
this.enableButtons();
return;
})
.catch(Notification.exception)
.always(() => {
this.reloadingBody = false;
return;
});
return this.bodyPromise;
}
/**
* Reload both the title and body content.
*
* @method reloadAllContent
* @return {object} promise
*/
reloadAllContent() {
return $.when(this.reloadTitleContent(), this.reloadBodyContent());
}
/**
* Kick off a reload the modal content before showing it. This
* is to allow us to re-use the same modal for creating and
* editing different events within the page.
*
* We do the reload when showing the modal rather than hiding it
* to save a request to the server if the user closes the modal
* and never re-opens it.
*
* @method show
*/
show() {
this.reloadAllContent();
super.show(this);
}
/**
* Clear the event id from the modal when it's closed so
* that it is loaded fresh next time it's displayed.
*
* The event id will be set by the calling code if it wants
* to edit a specific event.
*
* @method hide
*/
hide() {
super.hide(this);
this.setEventId(null);
this.setStartTime(null);
this.setCourseId(null);
this.setCategoryId(null);
}
/**
* Get the serialised form data.
*
* @method getFormData
* @return {string} serialised form data
*/
getFormData() {
return this.getForm().serialize();
}
/**
* Send the form data to the server to create or update
* an event.
*
* If there is a server side validation error then we re-request the
* rendered form (with the data) from the server in order to get the
* server side errors to display.
*
* On success the modal is hidden and the page is reloaded so that the
* new event will display.
*
* @method save
* @return {object} A promise
*/
save() {
const loadingContainer = this.saveButton.find(SELECTORS.LOADING_ICON_CONTAINER);
// Now the change events have run, see if there are any "invalid" form fields.
const invalid = this.getForm().find('[aria-invalid="true"]');
// If we found invalid fields, focus on the first one and do not submit via ajax.
if (invalid.length) {
invalid.first().focus();
return Promise.resolve();
}
loadingContainer.removeClass('hidden');
this.disableButtons();
const formData = this.getFormData();
// Send the form data to the server for processing.
return Repository.submitCreateUpdateForm(formData)
.then((response) => {
if (response.validationerror) {
// If there was a server side validation error then
// we need to re-request the rendered form from the server
// in order to display the error for the user.
this.reloadBodyContent(formData);
return;
} else {
// Check whether this was a new event or not.
// The hide function unsets the form data so grab this before the hide.
const isExisting = this.hasEventId();
// No problemo! Our work here is done.
this.hide();
// Trigger the appropriate calendar event so that the view can be updated.
if (isExisting) {
$('body').trigger(CalendarEvents.updated, [response.event]);
} else {
$('body').trigger(CalendarEvents.created, [response.event]);
}
}
return;
})
.catch(Notification.exception)
.always(() => {
// Regardless of success or error we should always stop
// the loading icon and re-enable the buttons.
loadingContainer.addClass('hidden');
this.enableButtons();
return;
});
}
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
* @fires event:uploadStarted
* @fires event:formSubmittedByJavascript
*/
registerEventListeners() {
// Apply parent event listeners.
super.registerEventListeners(this);
// When the user clicks the save button we trigger the form submission. We need to
// trigger an actual submission because there is some JS code in the form that is
// listening for this event and doing some stuff (e.g. saving draft areas etc).
this.getModal().on(CustomEvents.events.activate, SELECTORS.SAVE_BUTTON, (e, data) => {
this.getForm().submit();
data.originalEvent.preventDefault();
e.stopPropagation();
});
// Catch the submit event before it is actually processed by the browser and
// prevent the submission. We'll take it from here.
this.getModal().on('submit', (e) => {
FormEvents.notifyFormSubmittedByJavascript(this.getForm()[0]);
this.save();
// Stop the form from actually submitting and prevent it's
// propagation because we have already handled the event.
e.preventDefault();
e.stopPropagation();
});
}
}
ModalEventForm.registerModalType();
@@ -0,0 +1,236 @@
// 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 javascript module to handle calendar drag and drop in the calendar
* month view navigation.
*
* This code is run each time the calendar month view is re-rendered. We
* only register the event handlers once per page load so that the in place
* DOM updates that happen on month change don't continue to register handlers.
*
* @module core_calendar/month_navigation_drag_drop
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core_calendar/drag_drop_data_store',
],
function(
$,
DataStore
) {
var SELECTORS = {
DRAGGABLE: '[draggable="true"][data-region="event-item"]',
DROP_ZONE: '[data-drop-zone="nav-link"]',
};
var HOVER_CLASS = 'bg-primary text-white';
var TARGET_CLASS = 'drop-target';
var HOVER_TIME = 1000; // 1 second hover to change month.
// We store some static variables at the module level because this
// module is called each time the calendar month view is reloaded but
// we want some actions to only occur ones.
/* @var {bool} registered If the event listeners have been added */
var registered = false;
/* @var {int} hoverTimer The timeout id of any timeout waiting for hover */
var hoverTimer = null;
/* @var {object} root The root nav element we're operating on */
var root = null;
/**
* Add or remove the appropriate styling to indicate whether
* the drop target is being hovered over.
*
* @param {object} target The target drop zone element
* @param {bool} hovered If the element is hovered over ot not
*/
var updateHoverState = function(target, hovered) {
if (hovered) {
target.addClass(HOVER_CLASS);
} else {
target.removeClass(HOVER_CLASS);
}
};
/**
* Add some styling to the UI to indicate that the nav links
* are an acceptable drop target.
*/
var addDropZoneIndicator = function() {
root.find(SELECTORS.DROP_ZONE).addClass(TARGET_CLASS);
};
/**
* Remove the styling from the nav links.
*/
var removeDropZoneIndicator = function() {
root.find(SELECTORS.DROP_ZONE).removeClass(TARGET_CLASS);
};
/**
* Get the drop zone target from the event, if one is found.
*
* @param {event} e Javascript event
* @return {object|null}
*/
var getTargetFromEvent = function(e) {
var target = $(e.target).closest(SELECTORS.DROP_ZONE);
return (target.length) ? target : null;
};
/**
* This will add a visual indicator to the calendar UI to
* indicate which nav link is a valid drop zone.
*
* @param {Event} e
*/
var dragstartHandler = function(e) {
// Make sure the drag event is for a calendar event.
var eventElement = $(e.target).closest(SELECTORS.DRAGGABLE);
if (eventElement.length) {
addDropZoneIndicator();
}
};
/**
* Update the hover state of the target nav element when
* the user is dragging an event over it.
*
* This will add a visual indicator to the calendar UI to
* indicate which nav link is being hovered.
*
* @param {event} e The dragover event
*/
var dragoverHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
e.preventDefault();
var target = getTargetFromEvent(e);
if (!target) {
return;
}
// If we're not draggin a calendar event then
// ignore it.
if (!DataStore.hasEventId()) {
return;
}
if (!hoverTimer) {
hoverTimer = setTimeout(function() {
target.click();
hoverTimer = null;
}, HOVER_TIME);
}
updateHoverState(target, true);
removeDropZoneIndicator();
};
/**
* Update the hover state of the target nav element that was
* previously dragged over but has is no longer a drag target.
*
* This will remove the visual indicator from the calendar UI
* that was added by the dragoverHandler.
*
* @param {event} e The dragstart event
*/
var dragleaveHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
var target = getTargetFromEvent(e);
if (!target) {
return;
}
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
updateHoverState(target, false);
addDropZoneIndicator();
e.preventDefault();
};
/**
* Remove the visual indicator from the calendar UI that was
* added by the dragoverHandler.
*
* @param {event} e The drop event
*/
var dropHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
removeDropZoneIndicator();
var target = getTargetFromEvent(e);
if (!target) {
return;
}
updateHoverState(target, false);
e.preventDefault();
};
return {
/**
* Initialise the event handlers for the drag events.
*
* @param {object} rootElement The element containing calendar nav links
*/
init: function(rootElement) {
// Only register the handlers once on the first load.
if (!registered) {
// These handlers are only added the first time the module
// is loaded because we don't want to have a new listener
// added each time the "init" function is called otherwise we'll
// end up with lots of stale handlers.
document.addEventListener('dragstart', dragstartHandler, false);
document.addEventListener('dragover', dragoverHandler, false);
document.addEventListener('dragleave', dragleaveHandler, false);
document.addEventListener('drop', dropHandler, false);
document.addEventListener('dragend', removeDropZoneIndicator, false);
registered = true;
}
// Update the module variable to operate on the given
// root element.
root = $(rootElement);
// If we're currently dragging then add the indicators.
if (DataStore.hasEventId()) {
addDropZoneIndicator();
}
},
};
});
+403
View File
@@ -0,0 +1,403 @@
// 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 javascript module to handle calendar drag and drop in the calendar
* month view.
*
* @module core_calendar/month_view_drag_drop
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/notification',
'core/str',
'core_calendar/events',
'core_calendar/drag_drop_data_store'
],
function(
$,
Notification,
Str,
CalendarEvents,
DataStore
) {
var SELECTORS = {
ROOT: "[data-region='calendar']",
DRAGGABLE: '[draggable="true"][data-region="event-item"]',
DROP_ZONE: '[data-drop-zone="month-view-day"]',
WEEK: '[data-region="month-view-week"]',
};
var INVALID_DROP_ZONE_CLASS = 'bg-faded';
var INVALID_HOVER_CLASS = 'bg-danger text-white';
var VALID_HOVER_CLASS = 'bg-primary text-white';
var ALL_CLASSES = INVALID_DROP_ZONE_CLASS + ' ' + INVALID_HOVER_CLASS + ' ' + VALID_HOVER_CLASS;
/* @var {bool} registered If the event listeners have been added */
var registered = false;
/**
* Get the correct drop zone element from the given javascript
* event.
*
* @param {event} e The javascript event
* @return {object|null}
*/
var getDropZoneFromEvent = function(e) {
var dropZone = $(e.target).closest(SELECTORS.DROP_ZONE);
return (dropZone.length) ? dropZone : null;
};
/**
* Determine if the given dropzone element is within the acceptable
* time range.
*
* The drop zone timestamp is midnight on that day so we should check
* that the event's acceptable timestart value
*
* @param {object} dropZone The drop zone day from the calendar
* @return {bool}
*/
var isValidDropZone = function(dropZone) {
var dropTimestamp = dropZone.attr('data-day-timestamp');
var minTimestart = DataStore.getMinTimestart();
var maxTimestart = DataStore.getMaxTimestart();
if (minTimestart && minTimestart > dropTimestamp) {
return false;
}
if (maxTimestart && maxTimestart < dropTimestamp) {
return false;
}
return true;
};
/**
* Get the error string to display for a given drop zone element
* if it is invalid.
*
* @param {object} dropZone The drop zone day from the calendar
* @return {string}
*/
var getDropZoneError = function(dropZone) {
var dropTimestamp = dropZone.attr('data-day-timestamp');
var minTimestart = DataStore.getMinTimestart();
var maxTimestart = DataStore.getMaxTimestart();
if (minTimestart && minTimestart > dropTimestamp) {
return DataStore.getMinError();
}
if (maxTimestart && maxTimestart < dropTimestamp) {
return DataStore.getMaxError();
}
return null;
};
/**
* Remove all of the styling from each of the drop zones in the calendar.
*/
var clearAllDropZonesState = function() {
$(SELECTORS.ROOT).find(SELECTORS.DROP_ZONE).each(function(index, dropZone) {
dropZone = $(dropZone);
dropZone.removeClass(ALL_CLASSES);
});
};
/**
* Update the hover state for the event in the calendar to reflect
* which days the event will be moved to.
*
* If the drop zone is not being hovered then it will apply some
* styling to reflect whether the drop zone is a valid or invalid
* drop place for the current dragging event.
*
* This funciton supports events spanning multiple days and will
* recurse to highlight (or remove highlight) each of the days
* that the event will be moved to.
*
* For example: An event with a duration of 3 days will have
* 3 days highlighted when it's dragged elsewhere in the calendar.
* The current drag target and the 2 days following it (including
* wrapping to the next week if necessary).
*
* @param {string|object} dropZone The drag target element
* @param {bool} hovered If the target is hovered or not
* @param {Number} count How many days to highlight (default to duration)
*/
var updateHoverState = function(dropZone, hovered, count) {
if (typeof count === 'undefined') {
// This is how many days we need to highlight.
count = DataStore.getDurationDays();
}
var valid = isValidDropZone(dropZone);
dropZone.removeClass(ALL_CLASSES);
if (hovered) {
if (valid) {
dropZone.addClass(VALID_HOVER_CLASS);
} else {
dropZone.addClass(INVALID_HOVER_CLASS);
}
} else {
dropZone.removeClass(VALID_HOVER_CLASS + ' ' + INVALID_HOVER_CLASS);
if (!valid) {
dropZone.addClass(INVALID_DROP_ZONE_CLASS);
}
}
count--;
// If we've still got days to highlight then we should
// find the next day.
if (count > 0) {
var nextDropZone = dropZone.next();
// If there are no more days in this week then we
// need to move down to the next week in the calendar.
if (!nextDropZone.length) {
var nextWeek = dropZone.closest(SELECTORS.WEEK).next();
if (nextWeek.length) {
nextDropZone = nextWeek.children(SELECTORS.DROP_ZONE).first();
}
}
// If we found another day then let's recursively
// update it's hover state.
if (nextDropZone.length) {
updateHoverState(nextDropZone, hovered, count);
}
}
};
/**
* Find all of the calendar event drop zones in the calendar and update the display
* for the user to indicate which zones are valid and invalid.
*/
var updateAllDropZonesState = function() {
$(SELECTORS.ROOT).find(SELECTORS.DROP_ZONE).each(function(index, dropZone) {
dropZone = $(dropZone);
if (!isValidDropZone(dropZone)) {
updateHoverState(dropZone, false);
}
});
};
/**
* Set up the module level variables to track which event is being
* dragged and how many days it spans.
*
* @param {event} e The dragstart event
*/
var dragstartHandler = function(e) {
var target = $(e.target);
var draggableElement = target.closest(SELECTORS.DRAGGABLE);
if (!draggableElement.length) {
return;
}
var eventElement = draggableElement.find('[data-event-id]');
var eventId = eventElement.attr('data-event-id');
var minTimestart = draggableElement.attr('data-min-day-timestamp');
var maxTimestart = draggableElement.attr('data-max-day-timestamp');
var minError = draggableElement.attr('data-min-day-error');
var maxError = draggableElement.attr('data-max-day-error');
var eventsSelector = SELECTORS.ROOT + ' [data-event-id="' + eventId + '"]';
var duration = $(eventsSelector).length;
DataStore.setEventId(eventId);
DataStore.setDurationDays(duration);
if (minTimestart) {
DataStore.setMinTimestart(minTimestart);
}
if (maxTimestart) {
DataStore.setMaxTimestart(maxTimestart);
}
if (minError) {
DataStore.setMinError(minError);
}
if (maxError) {
DataStore.setMaxError(maxError);
}
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.dropEffect = "move";
// Firefox requires a value to be set here or the drag won't
// work and the dragover handler won't fire.
e.dataTransfer.setData('text/plain', eventId);
e.dropEffect = "move";
updateAllDropZonesState();
};
/**
* Update the hover state of the target day element when
* the user is dragging an event over it.
*
* This will add a visual indicator to the calendar UI to
* indicate which day(s) the event will be moved to.
*
* @param {event} e The dragstart event
*/
var dragoverHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
e.preventDefault();
var dropZone = getDropZoneFromEvent(e);
if (!dropZone) {
return;
}
updateHoverState(dropZone, true);
};
/**
* Update the hover state of the target day element that was
* previously dragged over but has is no longer a drag target.
*
* This will remove the visual indicator from the calendar UI
* that was added by the dragoverHandler.
*
* @param {event} e The dragstart event
*/
var dragleaveHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
var dropZone = getDropZoneFromEvent(e);
if (!dropZone) {
return;
}
updateHoverState(dropZone, false);
e.preventDefault();
};
/**
* Determines the event element, origin day, and destination day
* once the user drops the calendar event. These three bits of data
* are provided as the payload to the "moveEvent" calendar javascript
* event that is fired.
*
* This will remove the visual indicator from the calendar UI
* that was added by the dragoverHandler.
*
* @param {event} e The dragstart event
*/
var dropHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
var dropZone = getDropZoneFromEvent(e);
if (!dropZone) {
DataStore.clearAll();
clearAllDropZonesState();
return;
}
if (isValidDropZone(dropZone)) {
var eventId = DataStore.getEventId();
var eventElementSelector = SELECTORS.ROOT + ' [data-event-id="' + eventId + '"]';
var eventElement = $(eventElementSelector);
var origin = null;
if (eventElement.length) {
origin = eventElement.closest(SELECTORS.DROP_ZONE);
}
$('body').trigger(CalendarEvents.moveEvent, [eventId, origin, dropZone]);
} else {
// If the drop zone is not valid then there is not need for us to
// try to process it. Instead we can just show an error to the user.
var message = getDropZoneError(dropZone);
Str.get_string('errorinvaliddate', 'calendar').then(function(string) {
Notification.exception({
name: string,
message: message || string
});
});
}
DataStore.clearAll();
clearAllDropZonesState();
e.preventDefault();
};
/**
* Clear the data store and remove the drag indicators from the UI
* when the drag event has finished.
*/
var dragendHandler = function() {
DataStore.clearAll();
clearAllDropZonesState();
};
/**
* Re-render the drop zones in the new month to highlight
* which areas are or aren't acceptable to drop the calendar
* event.
*/
var calendarMonthChangedHandler = function() {
updateAllDropZonesState();
};
return {
/**
* Initialise the event handlers for the drag events.
*/
init: function() {
if (!registered) {
// These handlers are only added the first time the module
// is loaded because we don't want to have a new listener
// added each time the "init" function is called otherwise we'll
// end up with lots of stale handlers.
document.addEventListener('dragstart', dragstartHandler, false);
document.addEventListener('dragover', dragoverHandler, false);
document.addEventListener('dragleave', dragleaveHandler, false);
document.addEventListener('drop', dropHandler, false);
document.addEventListener('dragend', dragendHandler, false);
$('body').on(CalendarEvents.monthChanged, calendarMonthChangedHandler);
registered = true;
}
},
};
});
+125
View File
@@ -0,0 +1,125 @@
// 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/>.
/**
* Javascript popover for the `core_calendar` subsystem.
*
* @module core_calendar/popover
* @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.0
*/
import 'theme_boost/popover';
import jQuery from 'jquery';
import * as CalendarSelectors from 'core_calendar/selectors';
/**
* Check if we are allowing to enable the popover or not.
* @param {Element} dateContainer
* @returns {boolean}
*/
const isPopoverAvailable = (dateContainer) => {
return window.getComputedStyle(dateContainer.querySelector(CalendarSelectors.elements.dateContent)).display === 'none';
};
const isPopoverConfigured = new Map();
const showPopover = target => {
const dateContainer = target.closest(CalendarSelectors.elements.dateContainer);
if (!isPopoverConfigured.has(dateContainer)) {
const dateEle = jQuery(target);
dateEle.popover({
trigger: 'manual',
placement: 'top',
html: true,
title: dateContainer.dataset.title,
content: () => {
const source = jQuery(dateContainer).find(CalendarSelectors.elements.dateContent);
const content = jQuery('<div>');
if (source.length) {
const temptContent = source.find('.hidden').clone(false);
content.html(temptContent.html());
}
return content.html();
},
'animation': false,
});
isPopoverConfigured.set(dateContainer, true);
}
if (isPopoverAvailable(dateContainer)) {
jQuery(target).popover('show');
target.addEventListener('mouseleave', hidePopover);
target.addEventListener('focusout', hidePopover);
// Set up the hide function to the click event type.
target.addEventListener('click', hidePopover);
}
};
const hidePopover = e => {
const target = e.target;
const dateContainer = e.target.closest(CalendarSelectors.elements.dateContainer);
if (!dateContainer) {
return;
}
if (isPopoverConfigured.has(dateContainer)) {
const isTargetActive = target.contains(document.activeElement);
const isTargetHover = target.matches(':hover');
// Checks if a target element is clicked or pressed.
const isTargetClicked = document.activeElement.contains(target);
let removeListener = true;
if (!isTargetActive && !isTargetHover) {
jQuery(target).popover('hide');
} else if (isTargetClicked) {
jQuery(document.activeElement).popover('hide');
} else {
removeListener = false;
}
if (removeListener) {
target.removeEventListener('mouseleave', hidePopover);
target.removeEventListener('focusout', hidePopover);
target.removeEventListener('click', hidePopover);
}
}
};
/**
* Register events for date container.
*/
const registerEventListeners = () => {
const showPopoverHandler = (e) => {
const dayLink = e.target.closest(CalendarSelectors.links.dayLink);
if (!dayLink) {
return;
}
e.preventDefault();
showPopover(dayLink);
};
document.addEventListener('mouseover', showPopoverHandler);
document.addEventListener('focusin', showPopoverHandler);
};
let listenersRegistered = false;
if (!listenersRegistered) {
registerEventListeners();
listenersRegistered = true;
}
+215
View File
@@ -0,0 +1,215 @@
// 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 javascript module to handle calendar ajax actions.
*
* @module core_calendar/repository
* @copyright 2017 Simey Lameze <lameze@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
/**
* Delete a calendar event.
*
* @method deleteEvent
* @param {number} eventId The event id.
* @param {boolean} deleteSeries Whether to delete all events in the series
* @return {promise} Resolved with requested calendar event
*/
export const deleteEvent = (eventId, deleteSeries = false) => {
const request = {
methodname: 'core_calendar_delete_calendar_events',
args: {
events: [{
eventid: eventId,
repeat: deleteSeries,
}]
}
};
return Ajax.call([request])[0];
};
/**
* Get a calendar event by id.
*
* @method getEventById
* @param {number} eventId The event id.
* @return {promise} Resolved with requested calendar event
*/
export const getEventById = (eventId) => {
const request = {
methodname: 'core_calendar_get_calendar_event_by_id',
args: {
eventid: eventId
}
};
return Ajax.call([request])[0];
};
/**
* Submit the form data for the event form.
*
* @method submitCreateUpdateForm
* @param {string} formData The URL encoded values from the form
* @return {promise} Resolved with the new or edited event
*/
export const submitCreateUpdateForm = (formData) => {
const request = {
methodname: 'core_calendar_submit_create_update_form',
args: {
formdata: formData
}
};
return Ajax.call([request])[0];
};
/**
* Get calendar data for the month view.
*
* @method getCalendarMonthData
* @param {number} year Year
* @param {number} month Month
* @param {number} courseId The course id.
* @param {number} categoryId The category id.
* @param {boolean} includeNavigation Whether to include navigation.
* @param {boolean} mini Whether the month is in mini view.
* @param {number} day Day (optional)
* @param {string} view The calendar view mode.
* @return {promise} Resolved with the month view data.
*/
export const getCalendarMonthData = (year, month, courseId, categoryId, includeNavigation, mini, day = 1, view = 'month') => {
const request = {
methodname: 'core_calendar_get_calendar_monthly_view',
args: {
year,
month,
courseid: courseId,
categoryid: categoryId,
includenavigation: includeNavigation,
mini,
day,
view,
}
};
return Ajax.call([request])[0];
};
/**
* Get calendar data for the day view.
*
* @method getCalendarDayData
* @param {number} year Year
* @param {number} month Month
* @param {number} day Day
* @param {number} courseId The course id.
* @param {number} categoryId The id of the category whose events are shown
* @return {promise} Resolved with the day view data.
*/
export const getCalendarDayData = (year, month, day, courseId, categoryId) => {
const request = {
methodname: 'core_calendar_get_calendar_day_view',
args: {
year,
month,
day,
courseid: courseId,
categoryid: categoryId,
}
};
return Ajax.call([request])[0];
};
/**
* Change the start day for the given event id. The day timestamp
* only has to be any time during the target day because only the
* date information is extracted, the time of the day is ignored.
*
* @param {int} eventId The id of the event to update
* @param {int} dayTimestamp A timestamp for some time during the target day
* @return {promise}
*/
export const updateEventStartDay = (eventId, dayTimestamp) => {
const request = {
methodname: 'core_calendar_update_event_start_day',
args: {
eventid: eventId,
daytimestamp: dayTimestamp
}
};
return Ajax.call([request])[0];
};
/**
* Get calendar upcoming data.
*
* @method getCalendarUpcomingData
* @param {number} courseId The course id.
* @param {number} categoryId The category id.
* @return {promise} Resolved with the month view data.
*/
export const getCalendarUpcomingData = (courseId, categoryId) => {
const request = {
methodname: 'core_calendar_get_calendar_upcoming_view',
args: {
courseid: courseId,
categoryid: categoryId,
}
};
return Ajax.call([request])[0];
};
/**
* Get the groups by course id.
*
* @param {Number} courseId The course id to fetch the groups from.
* @return {promise} Resolved with the course groups.
*/
export const getCourseGroupsData = (courseId) => {
const request = {
methodname: 'core_group_get_course_groups',
args: {
courseid: 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];
};
+78
View File
@@ -0,0 +1,78 @@
// 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/>.
/**
* CSS selectors for the calendar.
*
* @module core_calendar/selectors
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], 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"]',
monthDetailed: '.calendarmonth.calendartable',
},
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']",
dayLink: "[data-action='view-day-link']",
},
containers: {
loadingIcon: '[data-region="overlay-icon-container"]',
},
mainCalendar: '.maincalendar .heightcontainer',
fullCalendarView: 'page-calendar-view',
pageHeaderHeadings: '.page-header-headings h1',
};
});
+181
View File
@@ -0,0 +1,181 @@
// 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 javascript module to handle summary modal.
*
* @module core_calendar/summary_modal
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import * as CustomEvents from 'core/custom_interaction_events';
import Modal from 'core/modal';
import CalendarEvents from './events';
import * as CalendarCrud from 'core_calendar/crud';
import * as ModalEvents from 'core/modal_events';
const SELECTORS = {
ROOT: "[data-region='summary-modal-container']",
EDIT_BUTTON: '[data-action="edit"]',
DELETE_BUTTON: '[data-action="delete"]',
};
export default class ModalEventSummary extends Modal {
static TEMPLATE = 'core_calendar/event_summary_modal';
static TYPE = 'core_calendar-event_summary';
/**
* Get the edit button element from the footer. The button is cached
* as it's not expected to change.
*
* @method getEditButton
* @return {object} button element
*/
getEditButton() {
if (typeof this.editButton == 'undefined') {
this.editButton = this.getFooter().find(SELECTORS.EDIT_BUTTON);
}
return this.editButton;
}
/**
* Get the delete button element from the footer. The button is cached
* as it's not expected to change.
*
* @method getDeleteButton
* @return {object} button element
*/
getDeleteButton() {
if (typeof this.deleteButton == 'undefined') {
this.deleteButton = this.getFooter().find(SELECTORS.DELETE_BUTTON);
}
return this.deleteButton;
}
/**
* Get the id for the event being shown in this modal. This value is
* not cached because it will change depending on which event is
* being displayed.
*
* @method getEventId
* @return {int}
*/
getEventId() {
return this.getBody().find(SELECTORS.ROOT).attr('data-event-id');
}
/**
* Get the title for the event being shown in this modal. This value is
* not cached because it will change depending on which event is
* being displayed.
*
* @method getEventTitle
* @return {String}
*/
getEventTitle() {
return this.getBody().find(SELECTORS.ROOT).attr('data-event-title');
}
/**
* Get the number of events in the series for the event being shown in
* this modal. This value is not cached because it will change
* depending on which event is being displayed.
*
* @method getEventCount
* @return {int}
*/
getEventCount() {
return this.getBody().find(SELECTORS.ROOT).attr('data-event-count');
}
/**
* Get the url for the event being shown in this modal.
*
* @method getEventUrl
* @return {String}
*/
getEditUrl() {
return this.getBody().find(SELECTORS.ROOT).attr('data-edit-url');
}
/**
* Is this an action event.
*
* @method getEventUrl
* @return {String}
*/
isActionEvent() {
return (this.getBody().find(SELECTORS.ROOT).attr('data-action-event') == 'true');
}
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
*/
registerEventListeners() {
// Apply parent event listeners.
super.registerEventListeners(this);
// We have to wait for the modal to finish rendering in order to ensure that
// the data-event-title property is available to use as the modal title.
M.util.js_pending('core_calendar/summary_modal:registerEventListeners:bodyRendered');
this.getRoot().on(ModalEvents.bodyRendered, function() {
this.getModal().data({
eventTitle: this.getEventTitle(),
eventId: this.getEventId(),
eventCount: this.getEventCount(),
})
.attr('data-type', 'event');
CalendarCrud.registerRemove(this.getModal());
M.util.js_complete('core_calendar/summary_modal:registerEventListeners:bodyRendered');
}.bind(this));
$('body').on(CalendarEvents.deleted, function() {
// Close the dialogue on delete.
this.hide();
}.bind(this));
CustomEvents.define(this.getEditButton(), [
CustomEvents.events.activate
]);
this.getEditButton().on(CustomEvents.events.activate, function(e, data) {
if (this.isActionEvent()) {
// Action events cannot be edited on the event form and must be redirected to the module UI.
$('body').trigger(CalendarEvents.editActionEvent, [this.getEditUrl()]);
} else {
// When the edit button is clicked we fire an event for the calendar UI to handle.
// We don't care how the UI chooses to handle it.
$('body').trigger(CalendarEvents.editEvent, [this.getEventId()]);
}
// There is nothing else for us to do so let's hide.
this.hide();
// We've handled this event so no need to propagate it.
e.preventDefault();
e.stopPropagation();
data.originalEvent.preventDefault();
data.originalEvent.stopPropagation();
}.bind(this));
}
}
ModalEventSummary.registerModalType();
+618
View File
@@ -0,0 +1,618 @@
// 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 javascript module to handler calendar view changes.
*
* @module core_calendar/view_manager
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import Templates from 'core/templates';
import Notification from 'core/notification';
import * as CalendarRepository from 'core_calendar/repository';
import CalendarEvents from 'core_calendar/events';
import * as CalendarSelectors from 'core_calendar/selectors';
import ModalEvents from 'core/modal_events';
import SummaryModal from 'core_calendar/summary_modal';
import CustomEvents from 'core/custom_interaction_events';
import {getString} from 'core/str';
import Pending from 'core/pending';
import {prefetchStrings} from 'core/prefetch';
import Url from 'core/url';
import Config from 'core/config';
/**
* Limit number of events per day
*
*/
const LIMIT_DAY_EVENTS = 5;
/**
* Hide day events if more than 5.
*
*/
export const foldDayEvents = () => {
const root = $(CalendarSelectors.elements.monthDetailed);
const days = root.find(CalendarSelectors.day);
if (days.length === 0) {
return;
}
days.each(function() {
const dayContainer = $(this);
const eventsSelector = `${CalendarSelectors.elements.dateContent} ul li[data-event-eventtype]`;
const filteredEventsSelector = `${CalendarSelectors.elements.dateContent} ul li[data-event-filtered="true"]`;
const moreEventsSelector = `${CalendarSelectors.elements.dateContent} [data-action="view-more-events"]`;
const events = dayContainer.find(eventsSelector);
if (events.length === 0) {
return;
}
const filteredEvents = dayContainer.find(filteredEventsSelector);
const numberOfFiltered = filteredEvents.length;
const numberOfEvents = events.length - numberOfFiltered;
let count = 1;
events.each(function() {
const event = $(this);
const isNotFiltered = event.attr('data-event-filtered') !== 'true';
const offset = (numberOfEvents === LIMIT_DAY_EVENTS) ? 0 : 1;
if (isNotFiltered) {
if (count > LIMIT_DAY_EVENTS - offset) {
event.attr('data-event-folded', 'true');
event.hide();
} else {
event.attr('data-event-folded', 'false');
event.show();
count++;
}
} else {
// It's being filtered out.
event.attr('data-event-folded', 'false');
}
});
const moreEventsLink = dayContainer.find(moreEventsSelector);
if (numberOfEvents > LIMIT_DAY_EVENTS) {
const numberOfHiddenEvents = numberOfEvents - LIMIT_DAY_EVENTS + 1;
moreEventsLink.show();
getString('moreevents', 'calendar', numberOfHiddenEvents).then(str => {
const link = moreEventsLink.find('strong a');
moreEventsLink.attr('data-event-folded', 'false');
link.text(str);
return str;
}).catch(Notification.exception);
} else {
moreEventsLink.hide();
}
});
};
/**
* Register and handle month calendar events.
*
* @param {string} pendingId pending id.
*/
export const registerEventListenersForMonthDetailed = (pendingId) => {
const events = `${CalendarEvents.viewUpdated}`;
$('body').on(events, function(e) {
foldDayEvents(e);
});
foldDayEvents();
$('body').on(CalendarEvents.filterChanged, function(e, data) {
const root = $(CalendarSelectors.elements.monthDetailed);
const pending = new Pending(pendingId);
const target = root.find(CalendarSelectors.eventType[data.type]);
const transitionPromise = $.Deferred();
if (data.hidden) {
transitionPromise.then(function() {
target.attr('data-event-filtered', 'true');
return target.hide().promise();
}).fail();
} else {
transitionPromise.then(function() {
target.attr('data-event-filtered', 'false');
return target.show().promise();
}).fail();
}
transitionPromise.then(function() {
foldDayEvents();
return;
})
.always(pending.resolve)
.fail();
transitionPromise.resolve();
});
};
/**
* Register event listeners for the module.
*
* @param {object} root The root element.
* @param {boolean} isCalendarBlock - A flag indicating whether this is a calendar block.
*/
const registerEventListeners = (root, isCalendarBlock) => {
root = $(root);
// Bind click events to event links.
root.on('click', CalendarSelectors.links.eventLink, (e) => {
const target = e.target;
let eventLink = null;
let eventId = null;
const pendingPromise = new Pending('core_calendar/view_manager:eventLink:click');
if (target.matches(CalendarSelectors.actions.viewEvent)) {
eventLink = target;
} else {
eventLink = target.closest(CalendarSelectors.actions.viewEvent);
}
if (eventLink) {
eventId = eventLink.dataset.eventId;
} else {
eventId = target.querySelector(CalendarSelectors.actions.viewEvent).dataset.eventId;
}
if (eventId) {
// A link was found. Show the modal.
e.preventDefault();
// We've handled the event so stop it from bubbling
// and causing the day click handler to fire.
e.stopPropagation();
renderEventSummaryModal(eventId)
.then(pendingPromise.resolve)
.catch();
} else {
pendingPromise.resolve();
}
});
root.on('click', CalendarSelectors.links.navLink, (e) => {
const wrapper = root.find(CalendarSelectors.wrapper);
const view = wrapper.data('view');
const courseId = wrapper.data('courseid');
const categoryId = wrapper.data('categoryid');
const link = e.currentTarget;
if (view === 'month' || view === 'monthblock') {
changeMonth(root, link.href, link.dataset.year, link.dataset.month,
courseId, categoryId, link.dataset.day, isCalendarBlock);
e.preventDefault();
} else if (view === 'day') {
changeDay(root, link.href, link.dataset.year, link.dataset.month, link.dataset.day,
courseId, categoryId, isCalendarBlock);
e.preventDefault();
}
});
const viewSelector = root.find(CalendarSelectors.viewSelector);
CustomEvents.define(viewSelector, [CustomEvents.events.activate]);
viewSelector.on(
CustomEvents.events.activate,
(e) => {
e.preventDefault();
const option = e.target;
if (option.classList.contains('active')) {
return;
}
const view = option.dataset.view,
year = option.dataset.year,
month = option.dataset.month,
day = option.dataset.day,
courseId = option.dataset.courseid,
categoryId = option.dataset.categoryid;
if (view == 'month') {
refreshMonthContent(root, year, month, courseId, categoryId, root, 'core_calendar/calendar_month', day)
.then(() => {
updateUrl('?view=month&course=' + courseId);
return;
}).fail(Notification.exception);
} else if (view == 'day') {
refreshDayContent(root, year, month, day, courseId, categoryId, root, 'core_calendar/calendar_day')
.then(() => {
updateUrl('?view=day&course=' + courseId);
return;
}).fail(Notification.exception);
} else if (view == 'upcoming') {
reloadCurrentUpcoming(root, courseId, categoryId, root, 'core_calendar/calendar_upcoming')
.then(() => {
updateUrl('?view=upcoming&course=' + courseId);
return;
}).fail(Notification.exception);
}
}
);
};
/**
* Refresh the month content.
*
* @param {object} root The root element.
* @param {number} year Year
* @param {number} month Month
* @param {number} courseId The id of the course whose events are shown
* @param {number} categoryId The id of the category whose events are shown
* @param {object} target The element being replaced. If not specified, the calendarwrapper is used.
* @param {string} template The template to be rendered.
* @param {number} day Day (optional)
* @return {promise}
*/
export const refreshMonthContent = (root, year, month, courseId, categoryId, target = null, template = '', day = 1) => {
startLoading(root);
target = target || root.find(CalendarSelectors.wrapper);
template = template || root.attr('data-template');
M.util.js_pending([root.get('id'), year, month, courseId].join('-'));
const includenavigation = root.data('includenavigation');
const mini = root.data('mini');
const viewMode = target.data('view');
return CalendarRepository.getCalendarMonthData(year, month, courseId, categoryId, includenavigation, mini, day, viewMode)
.then(context => {
return Templates.render(template, context);
})
.then((html, js) => {
return Templates.replaceNode(target, html, js);
})
.then(() => {
document.querySelector('body').dispatchEvent(new CustomEvent(CalendarEvents.viewUpdated));
return;
})
.always(() => {
M.util.js_complete([root.get('id'), year, month, courseId].join('-'));
return stopLoading(root);
})
.fail(Notification.exception);
};
/**
* Handle changes to the current calendar view.
*
* @param {object} root The container element
* @param {string} url The calendar url to be shown
* @param {number} year Year
* @param {number} month Month
* @param {number} courseId The id of the course whose events are shown
* @param {number} categoryId The id of the category whose events are shown
* @param {number} day Day (optional)
* @param {boolean} [isCalendarBlock=false] - A flag indicating whether this is a calendar block.
* @return {promise}
*/
export const changeMonth = (root, url, year, month, courseId, categoryId, day = 1, isCalendarBlock = false) => {
return refreshMonthContent(root, year, month, courseId, categoryId, null, '', day)
.then((...args) => {
if (url.length && url !== '#' && !isCalendarBlock) {
updateUrl(url);
}
return args;
})
.then((...args) => {
$('body').trigger(CalendarEvents.monthChanged, [year, month, courseId, categoryId, day, isCalendarBlock]);
return args;
});
};
/**
* Reload the current month view data.
*
* @param {object} root The container element.
* @param {number} courseId The course id.
* @param {number} categoryId The id of the category whose events are shown
* @return {promise}
*/
export const reloadCurrentMonth = (root, courseId = 0, categoryId = 0) => {
const year = root.find(CalendarSelectors.wrapper).data('year');
const month = root.find(CalendarSelectors.wrapper).data('month');
const day = root.find(CalendarSelectors.wrapper).data('day');
courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');
categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');
return refreshMonthContent(root, year, month, courseId, categoryId, null, '', day).
then((...args) => {
$('body').trigger(CalendarEvents.courseChanged, [year, month, courseId, categoryId]);
return args;
});
};
/**
* Refresh the day content.
*
* @param {object} root The root element.
* @param {number} year Year
* @param {number} month Month
* @param {number} day Day
* @param {number} courseId The id of the course whose events are shown
* @param {number} categoryId The id of the category whose events are shown
* @param {object} target The element being replaced. If not specified, the calendarwrapper is used.
* @param {string} template The template to be rendered.
* @param {boolean} isCalendarBlock - A flag indicating whether this is a calendar block.
*
* @return {promise}
*/
export const refreshDayContent = (root, year, month, day, courseId, categoryId,
target = null, template = '', isCalendarBlock = false) => {
startLoading(root);
if (!target || target.length == 0){
target = root.find(CalendarSelectors.wrapper);
}
template = template || root.attr('data-template');
M.util.js_pending([root.get('id'), year, month, day, courseId, categoryId].join('-'));
const includenavigation = root.data('includenavigation');
return CalendarRepository.getCalendarDayData(year, month, day, courseId, categoryId, includenavigation)
.then((context) => {
context.viewingday = true;
context.showviewselector = true;
context.iscalendarblock = isCalendarBlock;
return Templates.render(template, context);
})
.then((html, js) => {
return Templates.replaceNode(target, html, js);
})
.then(() => {
document.querySelector('body').dispatchEvent(new CustomEvent(CalendarEvents.viewUpdated));
return;
})
.always(() => {
M.util.js_complete([root.get('id'), year, month, day, courseId, categoryId].join('-'));
return stopLoading(root);
})
.fail(Notification.exception);
};
/**
* Reload the current day view data.
*
* @param {object} root The container element.
* @param {number} courseId The course id.
* @param {number} categoryId The id of the category whose events are shown
* @return {promise}
*/
export const reloadCurrentDay = (root, courseId = 0, categoryId = 0) => {
const wrapper = root.find(CalendarSelectors.wrapper);
const year = wrapper.data('year');
const month = wrapper.data('month');
const day = wrapper.data('day');
courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');
categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');
return refreshDayContent(root, year, month, day, courseId, categoryId);
};
/**
* Handle changes to the current calendar view.
*
* @param {object} root The root element.
* @param {String} url The calendar url to be shown
* @param {Number} year Year
* @param {Number} month Month
* @param {Number} day Day
* @param {Number} courseId The id of the course whose events are shown
* @param {Number} categoryId The id of the category whose events are shown
* @param {boolean} [isCalendarBlock=false] - A flag indicating whether this is a calendar block.
* @return {promise}
*/
export const changeDay = (root, url, year, month, day, courseId, categoryId, isCalendarBlock = false) => {
return refreshDayContent(root, year, month, day, courseId, categoryId, null, '', isCalendarBlock)
.then((...args) => {
if (url.length && url !== '#' && !isCalendarBlock) {
updateUrl(url);
}
return args;
})
.then((...args) => {
$('body').trigger(CalendarEvents.dayChanged, [year, month, courseId, categoryId, isCalendarBlock]);
return args;
});
};
/**
* Update calendar URL.
*
* @param {String} url The calendar url to be updated.
*/
export const updateUrl = (url) => {
const viewingFullCalendar = document.getElementById(CalendarSelectors.fullCalendarView);
// We want to update the url only if the user is viewing the full calendar.
if (viewingFullCalendar) {
window.history.pushState({}, '', url);
}
};
/**
* Set the element state to loading.
*
* @param {object} root The container element
* @method startLoading
*/
const startLoading = (root) => {
const loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
loadingIconContainer.removeClass('hidden');
};
/**
* Remove the loading state from the element.
*
* @param {object} root The container element
* @method stopLoading
*/
const stopLoading = (root) => {
const loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
loadingIconContainer.addClass('hidden');
};
/**
* Reload the current month view data.
*
* @param {object} root The container element.
* @param {number} courseId The course id.
* @param {number} categoryId The id of the category whose events are shown
* @param {object} target The element being replaced. If not specified, the calendarwrapper is used.
* @param {string} template The template to be rendered.
* @return {promise}
*/
export const reloadCurrentUpcoming = (root, courseId = 0, categoryId = 0, target = null, template = '') => {
startLoading(root);
target = target || root.find(CalendarSelectors.wrapper);
template = template || root.attr('data-template');
courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');
categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');
return CalendarRepository.getCalendarUpcomingData(courseId, categoryId)
.then((context) => {
context.viewingupcoming = true;
context.showviewselector = true;
return Templates.render(template, context);
})
.then((html, js) => {
return Templates.replaceNode(target, html, js);
})
.then(() => {
document.querySelector('body').dispatchEvent(new CustomEvent(CalendarEvents.viewUpdated));
return;
})
.always(function() {
return stopLoading(root);
})
.fail(Notification.exception);
};
/**
* Get the CSS class to apply for the given event type.
*
* @param {string} eventType The calendar event type
* @return {string}
*/
const getEventTypeClassFromType = (eventType) => {
return 'calendar_event_' + eventType;
};
/**
* Render the event summary modal.
*
* @param {Number} eventId The calendar event id.
* @returns {Promise}
*/
const renderEventSummaryModal = (eventId) => {
const pendingPromise = new Pending('core_calendar/view_manager:renderEventSummaryModal');
// Calendar repository promise.
return CalendarRepository.getEventById(eventId)
.then((getEventResponse) => {
if (!getEventResponse.event) {
throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
}
return getEventResponse.event;
})
.then(eventData => {
// Build the modal parameters from the event data.
const modalParams = {
title: eventData.name,
body: Templates.render('core_calendar/event_summary_body', eventData),
templateContext: {
canedit: eventData.canedit,
candelete: eventData.candelete,
headerclasses: getEventTypeClassFromType(eventData.normalisedeventtype),
isactionevent: eventData.isactionevent,
url: eventData.url,
action: eventData.action
}
};
// Create the modal.
return SummaryModal.create(modalParams);
})
.then(modal => {
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
// Finally, render the modal!
modal.show();
return modal;
})
.then(modal => {
pendingPromise.resolve();
return modal;
})
.catch(Notification.exception);
};
/**
* Initializes the calendar component by prefetching strings, folding day events,
* and registering event listeners.
*
* @param {HTMLElement} root - The root element where the calendar view manager and event listeners will be attached.
* @param {string} view - A flag indicating whether this is a calendar block.
* @param {boolean} isCalendarBlock - A flag indicating whether this is a calendar block.
*/
export const init = (root, view, isCalendarBlock) => {
prefetchStrings('calendar', ['moreevents']);
foldDayEvents();
registerEventListeners(root, isCalendarBlock);
const calendarTable = root.find(CalendarSelectors.elements.monthDetailed);
if (calendarTable.length) {
const pendingId = `month-detailed-${calendarTable.id}-filterChanged`;
registerEventListenersForMonthDetailed(calendarTable, pendingId);
}
};
/**
* Handles the change of course and updates the relevant elements on the page.
*
* @param {integer} courseId - The ID of the new course.
* @param {string} courseName - The name of the new course.
* @returns {Promise<void>} - A promise that resolves after the updates are applied.
*/
export const handleCourseChange = async(courseId, courseName) => {
// Select the <ul> element inside the data-region="view-selector".
const ulElement = document.querySelector(CalendarSelectors.viewSelector + ' ul');
// Select all <li><a> elements within the <ul>.
const liElements = ulElement.querySelectorAll('li a');
// Loop through the selected elements and update the courseid.
liElements.forEach(element => {
element.setAttribute('data-courseid', courseId);
});
const calendar = await getString('calendar', 'calendar');
const pageHeaderHeadingsElement = document.querySelector(CalendarSelectors.pageHeaderHeadings);
const courseUrl = Url.relativeUrl('/course/view.php', {id: courseId});
// Apply the page header text.
if (courseId !== Config.siteId) {
pageHeaderHeadingsElement.innerHTML = calendar + ': <a href="' + courseUrl + '">' + courseName + '</a>';
} else {
pageHeaderHeadingsElement.innerHTML = calendar;
}
};
+43
View File
@@ -0,0 +1,43 @@
<?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/>.
/**
* Action factory.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\factories\action_factory_interface;
use core_calendar\local\event\value_objects\action;
/**
* Action factory class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class action_factory implements action_factory_interface {
public function create_instance($name, \moodle_url $url, $itemcount, $actionable) {
return new action ($name, $url, $itemcount, $actionable);
}
}
+93
View File
@@ -0,0 +1,93 @@
<?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/>.
/**
* The mform for exporting calendar events
*
* @package core_calendar
* @copyright 2014 Brian Barnes
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// Always include formslib.
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); // It must be included from a Moodle page.
}
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* The mform class for creating and editing a calendar
*
* @copyright 2014 Brian Barnes
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_calendar_export_form extends moodleform {
/**
* The export form definition
* @throws coding_exception
*/
public function definition() {
global $CFG;
$mform = $this->_form;
$mform->addElement('html', '<div class="mt-3 mb-xl-6">' . get_string('exporthelp', 'calendar') . '</div>');
$export = array();
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsall', 'calendar'), 'all');
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtocategories', 'calendar'), 'categories');
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtocourses', 'calendar'), 'courses');
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtogroups', 'calendar'), 'groups');
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventspersonal', 'calendar'), 'user');
$mform->addGroup($export, 'events', get_string('eventstoexport', 'calendar'), '<br/>');
$mform->addGroupRule('events', get_string('required'), 'required');
$mform->setDefault('events', 'all');
$range = array();
if ($this->_customdata['allowthisweek']) {
$range[] = $mform->createElement('radio', 'timeperiod', '', get_string('weekthis', 'calendar'), 'weeknow');
}
if ($this->_customdata['allownextweek']) {
$range[] = $mform->createElement('radio', 'timeperiod', '', get_string('weeknext', 'calendar'), 'weeknext');
}
$range[] = $mform->createElement('radio', 'timeperiod', '', get_string('monththis', 'calendar'), 'monthnow');
if ($this->_customdata['allownextmonth']) {
$range[] = $mform->createElement('radio', 'timeperiod', '', get_string('monthnext', 'calendar'), 'monthnext');
}
$range[] = $mform->createElement('radio', 'timeperiod', '', get_string('recentupcoming', 'calendar'), 'recentupcoming');
if ($CFG->calendar_customexport) {
$a = new stdClass();
$now = time();
$time = $now - $CFG->calendar_exportlookback * DAYSECS;
$a->timestart = userdate($time, get_string('strftimedatefullshort', 'langconfig'));
$time = $now + $CFG->calendar_exportlookahead * DAYSECS;
$a->timeend = userdate($time, get_string('strftimedatefullshort', 'langconfig'));
$range[] = $mform->createElement('radio', 'timeperiod', '', get_string('customexport', 'calendar', $a), 'custom');
}
$mform->addGroup($range, 'period', get_string('timeperiod', 'calendar'), '<br/>');
$mform->addGroupRule('period', get_string('required'), 'required');
$mform->setDefault('period', 'recentupcoming');
$buttons = array();
$buttons[] = $mform->createElement('submit', 'generateurl', get_string('generateurlbutton', 'calendar'));
$buttons[] = $mform->createElement('submit', 'export', get_string('exportbutton', 'calendar'));
$mform->addGroup($buttons);
}
}
+279
View File
@@ -0,0 +1,279 @@
<?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/>.
namespace core_calendar\external;
use core\external\exporter;
use core_date;
use DateTimeImmutable;
use renderer_base;
use moodle_url;
use core_calendar\local\event\container;
/**
* Class for displaying the day view.
*
* @package core_calendar
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class calendar_day_exporter extends exporter {
/**
* @var \calendar_information $calendar The calendar to be rendered.
*/
protected $calendar;
/**
* @var moodle_url $url The URL for the day view page.
*/
protected $url;
/**
* Constructor for day exporter.
*
* @param \calendar_information $calendar The calendar being represented.
* @param array $related The related information
*/
public function __construct(\calendar_information $calendar, $related) {
$this->calendar = $calendar;
parent::__construct([], $related);
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'events' => [
'type' => calendar_event_exporter::read_properties_definition(),
'multiple' => true,
],
'defaulteventcontext' => [
'type' => PARAM_INT,
'default' => 0,
],
'filter_selector' => [
'type' => PARAM_RAW,
],
'courseid' => [
'type' => PARAM_INT,
],
'categoryid' => [
'type' => PARAM_INT,
'optional' => true,
'default' => 0,
],
'neweventtimestamp' => [
'type' => PARAM_INT,
],
'date' => [
'type' => date_exporter::read_properties_definition(),
],
'periodname' => [
// Note: We must use RAW here because the calendar type returns the formatted month name based on a
// calendar format.
'type' => PARAM_RAW,
],
'previousperiod' => [
'type' => date_exporter::read_properties_definition(),
],
'previousperiodlink' => [
'type' => PARAM_URL,
],
'previousperiodname' => [
// Note: We must use RAW here because the calendar type returns the formatted month name based on a
// calendar format.
'type' => PARAM_RAW,
],
'nextperiod' => [
'type' => date_exporter::read_properties_definition(),
],
'nextperiodname' => [
// Note: We must use RAW here because the calendar type returns the formatted month name based on a
// calendar format.
'type' => PARAM_RAW,
],
'nextperiodlink' => [
'type' => PARAM_URL,
],
'larrow' => [
// The left arrow defined by the theme.
'type' => PARAM_RAW,
],
'rarrow' => [
// The right arrow defined by the theme.
'type' => PARAM_RAW,
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$timestamp = $this->calendar->time;
$cache = $this->related['cache'];
$url = new moodle_url('/calendar/view.php', [
'view' => 'day',
'time' => $timestamp,
]);
if ($this->calendar->course && SITEID !== $this->calendar->course->id) {
$url->param('course', $this->calendar->course->id);
} else if ($this->calendar->categoryid) {
$url->param('category', $this->calendar->categoryid);
}
$this->url = $url;
$return['events'] = array_map(function($event) use ($cache, $output, $url) {
$context = $cache->get_context($event);
$course = $cache->get_course($event);
$moduleinstance = $cache->get_module_instance($event);
$exporter = new calendar_event_exporter($event, [
'context' => $context,
'course' => $course,
'moduleinstance' => $moduleinstance,
'daylink' => $url,
'type' => $this->related['type'],
'today' => $this->calendar->time,
]);
$data = $exporter->export($output);
// We need to override default formatted time because it differs from day view.
// Formatted time for day view adds a link to the day view.
$legacyevent = container::get_event_mapper()->from_event_to_legacy_event($event);
$data->formattedtime = calendar_format_event_time($legacyevent, time(), null);
return $data;
}, $this->related['events']);
if ($context = $this->get_default_add_context()) {
$return['defaulteventcontext'] = $context->id;
}
if ($this->calendar->categoryid) {
$return['categoryid'] = $this->calendar->categoryid;
}
$return['filter_selector'] = $this->get_course_filter_selector($output);
$return['courseid'] = $this->calendar->courseid;
$previousperiod = $this->get_previous_day_data();
$nextperiod = $this->get_next_day_data();
$date = $this->related['type']->timestamp_to_date_array($this->calendar->time);
$nextperiodlink = new moodle_url($this->url);
$nextperiodlink->param('time', $nextperiod[0]);
$previousperiodlink = new moodle_url($this->url);
$previousperiodlink->param('time', $previousperiod[0]);
$days = calendar_get_days();
$return['date'] = (new date_exporter($date))->export($output);
$return['periodname'] = userdate($this->calendar->time, get_string('strftimedaydate'));
$return['previousperiod'] = (new date_exporter($previousperiod))->export($output);
$return['previousperiodname'] = $days[$previousperiod['wday']]['fullname'];
$return['previousperiodlink'] = $previousperiodlink->out(false);
$return['nextperiod'] = (new date_exporter($nextperiod))->export($output);
$return['nextperiodname'] = $days[$nextperiod['wday']]['fullname'];
$return['nextperiodlink'] = $nextperiodlink->out(false);
$return['larrow'] = $output->larrow();
$return['rarrow'] = $output->rarrow();
// Need to account for user's timezone.
$usernow = usergetdate(time());
$today = new DateTimeImmutable(
timezone: core_date::get_user_timezone_object(),
);
// The start time should use the day's date but the current
// time of the day (adjusted for user's timezone).
$neweventtimestamp = $today->setTimestamp($date[0])->setTime(
$usernow['hours'],
$usernow['minutes'],
$usernow['seconds']
);
$return['neweventtimestamp'] = $neweventtimestamp->getTimestamp();
return $return;
}
/**
* Get the default context for use when adding a new event.
*
* @return null|\context
*/
protected function get_default_add_context() {
if (calendar_user_can_add_event($this->calendar->course)) {
return \context_course::instance($this->calendar->course->id);
}
return null;
}
/**
* Get the course filter selector.
*
* @param renderer_base $output
* @return string The html code for the course filter selector.
*/
protected function get_course_filter_selector(renderer_base $output) {
return $output->course_filter_selector($this->url, '', $this->calendar->course->id);
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'events' => '\core_calendar\local\event\entities\event_interface[]',
'cache' => '\core_calendar\external\events_related_objects_cache',
'type' => '\core_calendar\type_base',
];
}
/**
* Get the previous day timestamp.
*
* @return int The previous day timestamp.
*/
protected function get_previous_day_data() {
$type = $this->related['type'];
$time = $type->get_prev_day($this->calendar->time);
return $type->timestamp_to_date_array($time);
}
/**
* Get the next day timestamp.
*
* @return int The next day timestamp.
*/
protected function get_next_day_data() {
$type = $this->related['type'];
$time = $type->get_next_day($this->calendar->time);
return $type->timestamp_to_date_array($time);
}
}
+405
View File
@@ -0,0 +1,405 @@
<?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/>.
/**
* Contains event class for displaying a calendar event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use \core_calendar\local\event\container;
use \renderer_base;
require_once($CFG->dirroot . '/course/lib.php');
/**
* Class for displaying a calendar event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class calendar_event_exporter extends event_exporter_base {
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
$values = parent::define_other_properties();
$values['url'] = ['type' => PARAM_URL];
$values['islastday'] = [
'type' => PARAM_BOOL,
'default' => false,
];
$values['popupname'] = [
'type' => PARAM_RAW,
];
$values['mindaytimestamp'] = [
'type' => PARAM_INT,
'optional' => true
];
$values['mindayerror'] = [
'type' => PARAM_TEXT,
'optional' => true
];
$values['maxdaytimestamp'] = [
'type' => PARAM_INT,
'optional' => true
];
$values['maxdayerror'] = [
'type' => PARAM_TEXT,
'optional' => true
];
$values['draggable'] = [
'type' => PARAM_BOOL,
'default' => false
];
return $values;
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
global $CFG;
$values = parent::get_other_values($output);
$event = $this->event;
$course = $this->related['course'];
$hascourse = !empty($course);
// By default all events that can be edited are
// draggable.
$values['draggable'] = $values['canedit'];
if ($moduleproxy = $event->get_course_module()) {
$modulename = $moduleproxy->get('modname');
$moduleid = $moduleproxy->get('id');
$url = new \moodle_url(sprintf('/mod/%s/view.php', $modulename), ['id' => $moduleid]);
// Build edit event url for action events.
$params = array('update' => $moduleid, 'return' => true, 'sesskey' => sesskey());
$editurl = new \moodle_url('/course/mod.php', $params);
$values['editurl'] = $editurl->out(false);
} else if ($event->get_type() == 'category') {
$url = $event->get_category()->get_proxied_instance()->get_view_link();
} else {
$url = course_get_url($hascourse ? $course : SITEID);
}
$values['url'] = $url->out(false);
$values['islastday'] = false;
$today = $this->related['type']->timestamp_to_date_array($this->related['today']);
if ($hascourse) {
$values['popupname'] = \core_external\util::format_string(
$this->event->get_name(),
\context_course::instance($course->id),
true
);
} else {
$values['popupname'] = \core_external\util::format_string($this->event->get_name(), \context_system::instance(), true);
}
$times = $this->event->get_times();
if ($duration = $times->get_duration()) {
$enddate = $this->related['type']->timestamp_to_date_array($times->get_end_time()->getTimestamp());
$values['islastday'] = true;
$values['islastday'] = $values['islastday'] && $enddate['year'] == $today['year'];
$values['islastday'] = $values['islastday'] && $enddate['mon'] == $today['mon'];
$values['islastday'] = $values['islastday'] && $enddate['mday'] == $today['mday'];
}
$subscription = $this->event->get_subscription();
if ($subscription && !empty($subscription->get('id')) && $CFG->calendar_showicalsource) {
$a = (object) [
'name' => $values['popupname'],
'source' => $subscription->get('name'),
];
$values['popupname'] = get_string('namewithsource', 'calendar', $a);
} else {
if ($values['islastday']) {
$startdate = $this->related['type']->timestamp_to_date_array($times->get_start_time()->getTimestamp());
$samedate = true;
$samedate = $samedate && $startdate['mon'] == $enddate['mon'];
$samedate = $samedate && $startdate['year'] == $enddate['year'];
$samedate = $samedate && $startdate['mday'] == $enddate['mday'];
if (!$samedate) {
$values['popupname'] = get_string('eventendtimewrapped', 'calendar', $values['popupname']);
}
}
}
// Include category name into the event name, if applicable.
$proxy = $this->event->get_category();
if ($proxy && $proxy->get('id')) {
$category = $proxy->get_proxied_instance();
$eventnameparams = (object) [
'name' => $values['popupname'],
'category' => $category->get_formatted_name(),
];
$values['popupname'] = get_string('eventnameandcategory', 'calendar', $eventnameparams);
}
// Include course's shortname into the event name, if applicable.
if ($hascourse && $course->id !== SITEID) {
$eventnameparams = (object) [
'name' => $values['popupname'],
'course' => $values['course']->shortname,
];
$values['popupname'] = get_string('eventnameandcourse', 'calendar', $eventnameparams);
}
if ($event->get_course_module()) {
$values = array_merge($values, $this->get_module_timestamp_limits($event));
} else if ($hascourse && $course->id != SITEID && empty($event->get_group())) {
// This is a course event.
$values = array_merge($values, $this->get_course_timestamp_limits($event));
}
return $values;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
$related = parent::define_related();
$related['daylink'] = \moodle_url::class;
$related['type'] = '\core_calendar\type_base';
$related['today'] = 'int';
$related['moduleinstance'] = 'stdClass?';
return $related;
}
/**
* Return the normalised event type.
* Activity events are normalised to be course events.
*
* @return string
*/
public function get_calendar_event_type() {
if ($this->event->get_course_module()) {
return 'course';
}
return $this->event->get_type();
}
/**
* Return the set of minimum and maximum date timestamp values
* for the given event.
*
* @param \core_calendar\local\event\entities\event_interface $event
* @return array
*/
protected function get_course_timestamp_limits($event) {
$values = [];
$mapper = container::get_event_mapper();
$starttime = $event->get_times()->get_start_time();
list($min, $max) = component_callback(
'core_course',
'core_calendar_get_valid_event_timestart_range',
[$mapper->from_event_to_legacy_event($event), $event->get_course()->get_proxied_instance()],
[false, false]
);
// The callback will return false for either of the
// min or max cutoffs to indicate that there are no
// valid timestart values. In which case the event is
// not draggable.
if ($min === false || $max === false) {
return ['draggable' => false];
}
if ($min) {
$values = array_merge($values, $this->get_timestamp_min_limit($starttime, $min));
}
if ($max) {
$values = array_merge($values, $this->get_timestamp_max_limit($starttime, $max));
}
return $values;
}
/**
* Return the set of minimum and maximum date timestamp values
* for the given event.
*
* @param \core_calendar\local\event\entities\event_interface $event
* @return array
*/
protected function get_module_timestamp_limits($event) {
$values = [];
$mapper = container::get_event_mapper();
$starttime = $event->get_times()->get_start_time();
$modname = $event->get_course_module()->get('modname');
$moduleinstance = $this->related['moduleinstance'];
list($min, $max) = component_callback(
'mod_' . $modname,
'core_calendar_get_valid_event_timestart_range',
[$mapper->from_event_to_legacy_event($event), $moduleinstance],
[false, false]
);
// The callback will return false for either of the
// min or max cutoffs to indicate that there are no
// valid timestart values. In which case the event is
// not draggable.
if ($min === false || $max === false) {
return ['draggable' => false];
}
if ($min) {
$values = array_merge($values, $this->get_timestamp_min_limit($starttime, $min));
}
if ($max) {
$values = array_merge($values, $this->get_timestamp_max_limit($starttime, $max));
}
return $values;
}
/**
* Get the correct minimum midnight day limit based on the event start time
* and the minimum timestamp limit of what the event belongs to.
*
* @param DateTimeInterface $starttime The event start time
* @param array $min The module's minimum limit for the event
* @return array Returns an array with mindaytimestamp and mindayerror keys.
*/
protected function get_timestamp_min_limit(\DateTimeInterface $starttime, $min) {
// We need to check that the minimum valid time is earlier in the
// day than the current event time so that if the user drags and drops
// the event to this day (which changes the date but not the time) it
// will result in a valid time start for the event.
//
// For example:
// An event that starts on 2017-01-10 08:00 with a minimum cutoff
// of 2017-01-05 09:00 means that 2017-01-05 is not a valid start day
// for the drag and drop because it would result in the event start time
// being set to 2017-01-05 08:00, which is invalid. Instead the minimum
// valid start day would be 2017-01-06.
$values = [];
$timestamp = $min[0];
$errorstring = $min[1];
$mindate = (new \DateTimeImmutable())->setTimestamp($timestamp);
$minstart = $mindate->setTime(
$starttime->format('H'),
$starttime->format('i'),
$starttime->format('s')
);
$midnight = usergetmidnight($timestamp);
if ($mindate <= $minstart) {
$values['mindaytimestamp'] = $midnight;
} else {
$tomorrow = (new \DateTime())->setTimestamp($midnight)->modify('+1 day');
$values['mindaytimestamp'] = $tomorrow->getTimestamp();
}
// Get the human readable error message to display if the min day
// timestamp is violated.
$values['mindayerror'] = $errorstring;
return $values;
}
/**
* Get the correct maximum midnight day limit based on the event start time
* and the maximum timestamp limit of what the event belongs to.
*
* @param DateTimeInterface $starttime The event start time
* @param array $max The module's maximum limit for the event
* @return array Returns an array with maxdaytimestamp and maxdayerror keys.
*/
protected function get_timestamp_max_limit(\DateTimeInterface $starttime, $max) {
// We're doing a similar calculation here as we are for the minimum
// day timestamp. See the explanation above.
$values = [];
$timestamp = $max[0];
$errorstring = $max[1];
$maxdate = (new \DateTimeImmutable())->setTimestamp($timestamp);
$maxstart = $maxdate->setTime(
$starttime->format('H'),
$starttime->format('i'),
$starttime->format('s')
);
$midnight = usergetmidnight($timestamp);
if ($maxdate >= $maxstart) {
$values['maxdaytimestamp'] = $midnight;
} else {
$yesterday = (new \DateTime())->setTimestamp($midnight)->modify('-1 day');
$values['maxdaytimestamp'] = $yesterday->getTimestamp();
}
// Get the human readable error message to display if the max day
// timestamp is violated.
$values['maxdayerror'] = $errorstring;
return $values;
}
/**
* Get the correct minimum midnight day limit based on the event start time
* and the module's minimum timestamp limit.
*
* @deprecated since Moodle 3.6. Please use get_timestamp_min_limit().
* @todo final deprecation. To be removed in Moodle 3.10
* @param \DateTimeInterface $starttime The event start time
* @param array $min The module's minimum limit for the event
* @return array Returns an array with mindaytimestamp and mindayerror keys.
*/
protected function get_module_timestamp_min_limit(\DateTimeInterface $starttime, $min) {
debugging('get_module_timestamp_min_limit() has been deprecated. Please call get_timestamp_min_limit() instead.',
DEBUG_DEVELOPER);
return $this->get_timestamp_min_limit($starttime, $min);
}
/**
* Get the correct maximum midnight day limit based on the event start time
* and the module's maximum timestamp limit.
*
* @deprecated since Moodle 3.6. Please use get_timestamp_max_limit().
* @todo final deprecation. To be removed in Moodle 3.10
* @param \DateTimeInterface $starttime The event start time
* @param array $max The module's maximum limit for the event
* @return array Returns an array with maxdaytimestamp and maxdayerror keys.
*/
protected function get_module_timestamp_max_limit(\DateTimeInterface $starttime, $max) {
debugging('get_module_timestamp_max_limit() has been deprecated. Please call get_timestamp_max_limit() instead.',
DEBUG_DEVELOPER);
return $this->get_timestamp_max_limit($starttime, $max);
}
}
+188
View File
@@ -0,0 +1,188 @@
<?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/>.
/**
* Contains event class for displaying the upcoming view.
*
* @package core_calendar
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
use moodle_url;
use \core_calendar\local\event\container;
/**
* Class for displaying the day view.
*
* @package core_calendar
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class calendar_upcoming_exporter extends exporter {
/**
* @var \calendar_information $calendar The calendar to be rendered.
*/
protected $calendar;
/**
* @var moodle_url $url The URL for the upcoming view page.
*/
protected $url;
/**
* Constructor for upcoming exporter.
*
* @param \calendar_information $calendar The calendar being represented.
* @param array $related The related information
*/
public function __construct(\calendar_information $calendar, $related) {
$this->calendar = $calendar;
parent::__construct([], $related);
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'events' => [
'type' => calendar_event_exporter::read_properties_definition(),
'multiple' => true,
],
'defaulteventcontext' => [
'type' => PARAM_INT,
'default' => 0,
],
'filter_selector' => [
'type' => PARAM_RAW,
],
'courseid' => [
'type' => PARAM_INT,
],
'categoryid' => [
'type' => PARAM_INT,
'optional' => true,
'default' => 0,
],
'isloggedin' => [
'type' => PARAM_BOOL,
],
'date' => [
'type' => date_exporter::read_properties_definition(),
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$timestamp = $this->calendar->time;
$cache = $this->related['cache'];
$url = new moodle_url('/calendar/view.php', [
'view' => 'upcoming',
'time' => $timestamp,
'course' => $this->calendar->course->id,
]);
$this->url = $url;
$return['isloggedin'] = isloggedin();
$return['events'] = array_map(function($event) use ($cache, $output, $url) {
$context = $cache->get_context($event);
$course = $cache->get_course($event);
$moduleinstance = $cache->get_module_instance($event);
$exporter = new calendar_event_exporter($event, [
'context' => $context,
'course' => $course,
'moduleinstance' => $moduleinstance,
'daylink' => $url,
'type' => $this->related['type'],
'today' => $this->calendar->time,
]);
$data = $exporter->export($output);
// We need to override default formatted time because it differs from day view.
// Formatted time for upcoming view adds a link to the day view.
$legacyevent = container::get_event_mapper()->from_event_to_legacy_event($event);
$data->formattedtime = calendar_format_event_time($legacyevent, time(), null);
return $data;
}, $this->related['events']);
if ($context = $this->get_default_add_context()) {
$return['defaulteventcontext'] = $context->id;
}
$return['filter_selector'] = $this->get_course_filter_selector($output);
$return['courseid'] = $this->calendar->courseid;
$date = $this->related['type']->timestamp_to_date_array($this->calendar->time);
$return['date'] = (new date_exporter($date))->export($output);
if ($this->calendar->categoryid) {
$return['categoryid'] = $this->calendar->categoryid;
}
return $return;
}
/**
* Get the default context for use when adding a new event.
*
* @return null|\context
*/
protected function get_default_add_context() {
if (calendar_user_can_add_event($this->calendar->course)) {
return \context_course::instance($this->calendar->course->id);
}
return null;
}
/**
* Get the course filter selector.
*
* @param renderer_base $output
* @return string The html code for the course filter selector.
*/
protected function get_course_filter_selector(renderer_base $output) {
return $output->course_filter_selector($this->url, '', $this->calendar->course->id);
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'events' => '\core_calendar\local\event\entities\event_interface[]',
'cache' => '\core_calendar\external\events_related_objects_cache',
'type' => '\core_calendar\type_base',
];
}
}
+90
View File
@@ -0,0 +1,90 @@
<?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 for normalising the date data.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for normalising the date data.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class date_exporter extends exporter {
/**
* Constructor for date_exporter.
*
* @param array $data
* @param array $related The related information
*/
public function __construct($data, $related = []) {
$data['timestamp'] = $data[0];
unset($data[0]);
parent::__construct($data, $related);
}
protected static function define_properties() {
return [
'seconds' => [
'type' => PARAM_INT,
],
'minutes' => [
'type' => PARAM_INT,
],
'hours' => [
'type' => PARAM_INT,
],
'mday' => [
'type' => PARAM_INT,
],
'wday' => [
'type' => PARAM_INT,
],
'mon' => [
'type' => PARAM_INT,
],
'year' => [
'type' => PARAM_INT,
],
'yday' => [
'type' => PARAM_INT,
],
'weekday' => [
'type' => PARAM_RAW,
],
'month' => [
'type' => PARAM_RAW,
],
'timestamp' => [
'type' => PARAM_INT,
],
];
}
}
+277
View File
@@ -0,0 +1,277 @@
<?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/>.
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/calendar/lib.php');
use core\external\exporter;
use core_date;
use DateTimeImmutable;
use renderer_base;
use moodle_url;
/**
* Class for displaying the day view.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class day_exporter extends exporter {
/**
* @var \calendar_information $calendar The calendar being displayed.
*/
protected $calendar;
/**
* @var moodle_url
*/
protected $url;
/**
* Constructor.
*
* @param \calendar_information $calendar The calendar information for the period being displayed
* @param mixed $data Either an stdClass or an array of values.
* @param array $related Related objects.
*/
public function __construct(\calendar_information $calendar, $data, $related) {
$this->calendar = $calendar;
$url = new moodle_url('/calendar/view.php', [
'view' => 'day',
'time' => $calendar->time,
]);
if ($this->calendar->course && SITEID !== $this->calendar->course->id) {
$url->param('course', $this->calendar->course->id);
} else if ($this->calendar->categoryid) {
$url->param('category', $this->calendar->categoryid);
}
$this->url = $url;
parent::__construct($data, $related);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
// These are the default properties as returned by getuserdate()
// but without the formatted month and week names.
return [
'seconds' => [
'type' => PARAM_INT,
],
'minutes' => [
'type' => PARAM_INT,
],
'hours' => [
'type' => PARAM_INT,
],
'mday' => [
'type' => PARAM_INT,
],
'wday' => [
'type' => PARAM_INT,
],
'year' => [
'type' => PARAM_INT,
],
'yday' => [
'type' => PARAM_INT,
],
];
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'timestamp' => [
'type' => PARAM_INT,
],
'neweventtimestamp' => [
'type' => PARAM_INT,
],
'viewdaylink' => [
'type' => PARAM_URL,
'optional' => true,
],
'viewdaylinktitle' => [
'type' => PARAM_RAW,
'optional' => true,
],
'events' => [
'type' => calendar_event_exporter::read_properties_definition(),
'multiple' => true,
],
'hasevents' => [
'type' => PARAM_BOOL,
'default' => false,
],
'calendareventtypes' => [
'type' => PARAM_RAW,
'multiple' => true,
],
'previousperiod' => [
'type' => PARAM_INT,
],
'nextperiod' => [
'type' => PARAM_INT,
],
'haslastdayofevent' => [
'type' => PARAM_BOOL,
'default' => false,
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$daytimestamp = $this->calendar->time;
$timestamp = $this->data[0];
// Need to account for user's timezone.
$usernow = usergetdate(time());
$today = new DateTimeImmutable(
timezone: core_date::get_user_timezone_object(),
);
// The start time should use the day's date but the current
// time of the day (adjusted for user's timezone).
$neweventstarttime = $today->setTimestamp($timestamp)->setTime(
$usernow['hours'],
$usernow['minutes'],
$usernow['seconds']
);
$return = [
'timestamp' => $timestamp,
'neweventtimestamp' => $neweventstarttime->getTimestamp(),
'previousperiod' => $this->get_previous_day_timestamp($daytimestamp),
'nextperiod' => $this->get_next_day_timestamp($daytimestamp),
'viewdaylink' => $this->url->out(false),
];
if ($viewdaylinktitle = $this->get_view_link_title()) {
$return['viewdaylinktitle'] = $viewdaylinktitle;
}
$cache = $this->related['cache'];
$eventexporters = array_map(function($event) use ($cache, $output) {
$context = $cache->get_context($event);
$course = $cache->get_course($event);
$moduleinstance = $cache->get_module_instance($event);
$exporter = new calendar_event_exporter($event, [
'context' => $context,
'course' => $course,
'moduleinstance' => $moduleinstance,
'daylink' => $this->url,
'type' => $this->related['type'],
'today' => $this->data[0],
]);
return $exporter;
}, $this->related['events']);
$return['events'] = array_map(function($exporter) use ($output) {
return $exporter->export($output);
}, $eventexporters);
$return['hasevents'] = !empty($return['events']);
$return['calendareventtypes'] = array_map(function($exporter) {
return $exporter->get_calendar_event_type();
}, $eventexporters);
$return['calendareventtypes'] = array_values(array_unique($return['calendareventtypes']));
$return['haslastdayofevent'] = false;
foreach ($return['events'] as $event) {
if ($event->islastday) {
$return['haslastdayofevent'] = true;
break;
}
}
return $return;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'events' => '\core_calendar\local\event\entities\event_interface[]',
'cache' => '\core_calendar\external\events_related_objects_cache',
'type' => '\core_calendar\type_base',
];
}
/**
* Get the previous day timestamp.
*
* @param int $daytimestamp The current day timestamp.
* @return int The previous day timestamp.
*/
protected function get_previous_day_timestamp($daytimestamp) {
return $this->related['type']->get_prev_day($daytimestamp);
}
/**
* Get the next day timestamp.
*
* @param int $daytimestamp The current day timestamp.
* @return int The next day timestamp.
*/
protected function get_next_day_timestamp($daytimestamp) {
return $this->related['type']->get_next_day($daytimestamp);
}
/**
* Get the title for view link.
*
* @return string
*/
protected function get_view_link_title() {
$title = null;
$userdate = userdate($this->data[0], get_string('strftimedayshort'));
if ($this->data['istoday']) {
$title = get_string('todayplustitle', 'calendar', $userdate);
} else if (count($this->related['events'])) {
$title = get_string('eventsfor', 'calendar', $userdate);
}
return $title;
}
}
+87
View File
@@ -0,0 +1,87 @@
<?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/>.
/**
* Contains event class for displaying the day name.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
/**
* Class for displaying the day view.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class day_name_exporter extends exporter {
/**
* @var int $dayno The day number.
*/
protected $dayno;
/**
* @var string $shortname The formatted short name of the day.
*/
protected $shortname;
/**
* @var string $fullname The formatted full name of the day.
*/
protected $fullname;
/**
* Constructor.
*
* @param int $dayno The day number.
* @param array $names The list of names.
*/
public function __construct($dayno, $names) {
$data = $names + ['dayno' => $dayno];
parent::__construct($data, []);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'dayno' => [
'type' => PARAM_INT,
],
'shortname' => [
// Note: The calendar type class has already formatted the names.
'type' => PARAM_RAW,
],
'fullname' => [
// Note: The calendar type class has already formatted the names.
'type' => PARAM_RAW,
],
];
}
}
+129
View File
@@ -0,0 +1,129 @@
<?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/>.
/**
* Contains event class for displaying a calendar event's action.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use core_calendar\local\event\entities\action_interface;
use core_calendar\local\event\container;
use renderer_base;
/**
* Class for displaying a calendar event's action.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_action_exporter extends exporter {
/**
* Constructor.
*
* @param action_interface $action The action object.
* @param array $related Related data.
*/
public function __construct(action_interface $action, $related = []) {
$data = new \stdClass();
$data->name = $action->get_name();
$data->url = $action->get_url()->out(false);
$data->itemcount = $action->get_item_count();
$data->actionable = $action->is_actionable();
parent::__construct($data, $related);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'name' => ['type' => PARAM_TEXT],
'url' => ['type' => PARAM_URL],
'itemcount' => ['type' => PARAM_INT],
'actionable' => ['type' => PARAM_BOOL]
];
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'showitemcount' => ['type' => PARAM_BOOL, 'default' => false]
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$event = $this->related['event'];
if (!$event->get_component()) {
return ['showitemcount' => false];
}
$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($event->get_component(), $showitemcountcallback, $params, false);
// Prepare other values data.
$data = [
'showitemcount' => $showitemcount
];
return $data;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'context' => 'context',
'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];
}
}
+93
View File
@@ -0,0 +1,93 @@
<?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/>.
/**
* Contains event class for displaying a calendar event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . "/calendar/lib.php");
use \core_calendar\local\event\container;
use \renderer_base;
/**
* Class for displaying a calendar event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_exporter extends event_exporter_base {
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
$values = parent::define_other_properties();
$values['url'] = ['type' => PARAM_URL];
return $values;
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$values = parent::get_other_values($output);
global $CFG;
require_once($CFG->dirroot.'/course/lib.php');
$event = $this->event;
$context = $this->related['context'];
if ($moduleproxy = $event->get_course_module()) {
$modulename = $moduleproxy->get('modname');
$moduleid = $moduleproxy->get('id');
$url = new \moodle_url(sprintf('/mod/%s/view.php', $modulename), ['id' => $moduleid]);
// Build edit event url for action events.
$params = array('update' => $moduleid, 'return' => true, 'sesskey' => sesskey());
$editurl = new \moodle_url('/course/mod.php', $params);
$values['editurl'] = $editurl->out(false);
} else if ($event->get_type() == 'category') {
$url = $event->get_category()->get_proxied_instance()->get_view_link();
} else if ($event->get_type() == 'course') {
$url = \course_get_url($this->related['course'] ?: SITEID);
} else {
$url = \course_get_url($this->related['course'] ?: SITEID);
}
$values['url'] = $url->out(false);
// Override default formatted time to make sure the date portion of the time is always rendered.
$legacyevent = container::get_event_mapper()->from_event_to_legacy_event($event);
$values['formattedtime'] = calendar_format_event_time($legacyevent, time(), null, false);
return $values;
}
}
+415
View File
@@ -0,0 +1,415 @@
<?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/>.
/**
* Contains event class for displaying a calendar event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . "/calendar/lib.php");
require_once($CFG->libdir . "/filelib.php");
use \core\external\exporter;
use \core_calendar\local\event\container;
use \core_calendar\local\event\entities\event_interface;
use \core_calendar\local\event\entities\action_event_interface;
use \core_course\external\course_summary_exporter;
use \core\external\coursecat_summary_exporter;
use \renderer_base;
use moodle_url;
/**
* Class for displaying a calendar event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_exporter_base extends exporter {
/**
* @var event_interface $event
*/
protected $event;
/**
* Constructor.
*
* @param event_interface $event
* @param array $related The related data.
*/
public function __construct(event_interface $event, $related = []) {
$this->event = $event;
$starttimestamp = $event->get_times()->get_start_time()->getTimestamp();
$endtimestamp = $event->get_times()->get_end_time()->getTimestamp();
$groupid = $event->get_group() ? $event->get_group()->get('id') : null;
$userid = $event->get_user() ? $event->get_user()->get('id') : null;
$categoryid = $event->get_category() ? $event->get_category()->get('id') : null;
$data = new \stdClass();
$data->id = $event->get_id();
$data->name = $event->get_name();
$data->description = file_rewrite_pluginfile_urls(
$event->get_description()->get_value(),
'pluginfile.php',
$related['context']->id,
'calendar',
'event_description',
$event->get_id()
);
$data->descriptionformat = $event->get_description()->get_format();
$data->location = \core_external\util::format_text($event->get_location(), FORMAT_PLAIN, $related['context']->id)[0];
$data->groupid = $groupid;
$data->userid = $userid;
$data->categoryid = $categoryid;
$data->eventtype = $event->get_type();
$data->timestart = $starttimestamp;
$data->timeduration = $endtimestamp - $starttimestamp;
$data->timesort = $event->get_times()->get_sort_time()->getTimestamp();
$data->timeusermidnight = $event->get_times()->get_usermidnight_time()->getTimestamp();
$data->visible = $event->is_visible() ? 1 : 0;
$data->timemodified = $event->get_times()->get_modified_time()->getTimestamp();
$data->component = $event->get_component();
$data->overdue = $data->timesort < time();
if ($repeats = $event->get_repeats()) {
$data->repeatid = $repeats->get_id();
$data->eventcount = $repeats->get_num() + 1;
}
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);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'id' => ['type' => PARAM_INT],
'name' => ['type' => PARAM_TEXT],
'description' => [
'type' => PARAM_RAW,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'descriptionformat' => [
'type' => PARAM_INT,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'location' => [
'type' => PARAM_RAW,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'categoryid' => [
'type' => PARAM_INT,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'groupid' => [
'type' => PARAM_INT,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'userid' => [
'type' => PARAM_INT,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'repeatid' => [
'type' => PARAM_INT,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'eventcount' => [
'type' => PARAM_INT,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'component' => [
'type' => PARAM_COMPONENT,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'modulename' => [
'type' => PARAM_TEXT,
'optional' => true,
'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,
'default' => null,
'null' => NULL_ALLOWED
],
'eventtype' => ['type' => PARAM_TEXT],
'timestart' => ['type' => PARAM_INT],
'timeduration' => ['type' => PARAM_INT],
'timesort' => ['type' => PARAM_INT],
'timeusermidnight' => ['type' => PARAM_INT],
'visible' => ['type' => PARAM_INT],
'timemodified' => ['type' => PARAM_INT],
'overdue' => [
'type' => PARAM_BOOL,
'optional' => true,
'default' => false,
'null' => NULL_ALLOWED
],
];
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'icon' => [
'type' => event_icon_exporter::read_properties_definition(),
],
'category' => [
'type' => coursecat_summary_exporter::read_properties_definition(),
'optional' => true,
],
'course' => [
'type' => course_summary_exporter::read_properties_definition(),
'optional' => true,
],
'subscription' => [
'type' => event_subscription_exporter::read_properties_definition(),
'optional' => true,
],
'canedit' => [
'type' => PARAM_BOOL
],
'candelete' => [
'type' => PARAM_BOOL
],
'deleteurl' => [
'type' => PARAM_URL
],
'editurl' => [
'type' => PARAM_URL
],
'viewurl' => [
'type' => PARAM_URL
],
'formattedtime' => [
'type' => PARAM_RAW,
],
'formattedlocation' => [
'type' => PARAM_RAW,
],
'isactionevent' => [
'type' => PARAM_BOOL
],
'iscourseevent' => [
'type' => PARAM_BOOL
],
'iscategoryevent' => [
'type' => PARAM_BOOL
],
'groupname' => [
'type' => PARAM_RAW,
'optional' => true,
'default' => null,
'null' => NULL_ALLOWED
],
'normalisedeventtype' => [
'type' => PARAM_TEXT
],
'normalisedeventtypetext' => [
'type' => PARAM_TEXT
],
'action' => [
'type' => event_action_exporter::read_properties_definition(),
'optional' => true,
],
'purpose' => [
'type' => PARAM_TEXT
],
'branded' => [
'type' => PARAM_BOOL,
'optional' => true,
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$values = [];
$event = $this->event;
$legacyevent = container::get_event_mapper()->from_event_to_legacy_event($event);
$context = $this->related['context'];
$course = $this->related['course'];
$values['isactionevent'] = false;
$values['iscourseevent'] = false;
$values['iscategoryevent'] = false;
$values['normalisedeventtype'] = $event->get_type();
if ($moduleproxy = $event->get_course_module()) {
// We need a separate property to flag if an event is action event.
// That's required because canedit return true but action action events cannot be edited on the calendar UI.
// But they are considered editable because you can drag and drop the event on the month view.
$values['isactionevent'] = true;
// Activity events are normalised to "look" like course events.
$values['normalisedeventtype'] = 'course';
} else if ($event->get_type() == 'course') {
$values['iscourseevent'] = true;
} else if ($event->get_type() == 'category') {
$values['iscategoryevent'] = true;
}
$timesort = $event->get_times()->get_sort_time()->getTimestamp();
$iconexporter = new event_icon_exporter($event, ['context' => $context]);
$identifier = 'type' . $values['normalisedeventtype'];
$stringexists = get_string_manager()->string_exists($identifier, 'calendar');
if (!$stringexists) {
// Property normalisedeventtype is used to build the name of the CSS class for the events.
$values['normalisedeventtype'] = 'other';
}
$values['normalisedeventtypetext'] = $stringexists ? get_string($identifier, 'calendar') : '';
$purpose = 'none';
$isbranded = false;
if ($moduleproxy) {
$purpose = plugin_supports('mod', $moduleproxy->get('modname'), FEATURE_MOD_PURPOSE, 'none');
$isbranded = component_callback('mod_' . $moduleproxy->get('modname'), 'is_branded') !== null ? : false;
}
$values['purpose'] = $purpose;
$values['branded'] = $isbranded;
$values['icon'] = $iconexporter->export($output);
$subscriptionexporter = new event_subscription_exporter($event);
$values['subscription'] = $subscriptionexporter->export($output);
$proxy = $this->event->get_category();
if ($proxy && $proxy->get('id')) {
$category = $proxy->get_proxied_instance();
$categorysummaryexporter = new coursecat_summary_exporter($category, ['context' => $context]);
$values['category'] = $categorysummaryexporter->export($output);
}
if ($course && $course->id != SITEID) {
$coursesummaryexporter = new course_summary_exporter($course, ['context' => $context]);
$values['course'] = $coursesummaryexporter->export($output);
}
$courseid = (!$course) ? SITEID : $course->id;
$values['canedit'] = calendar_edit_event_allowed($legacyevent, true);
$values['candelete'] = calendar_delete_event_allowed($legacyevent);
$deleteurl = new moodle_url('/calendar/delete.php', ['id' => $event->get_id(), 'course' => $courseid]);
$values['deleteurl'] = $deleteurl->out(false);
$editurl = new moodle_url('/calendar/event.php', ['action' => 'edit', 'id' => $event->get_id(),
'course' => $courseid]);
$values['editurl'] = $editurl->out(false);
$viewurl = new moodle_url('/calendar/view.php', ['view' => 'day', 'course' => $courseid,
'time' => $timesort]);
$viewurl->set_anchor('event_' . $event->get_id());
$values['viewurl'] = $viewurl->out(false);
$values['formattedtime'] = calendar_format_event_time($legacyevent, time(), null, false,
$timesort);
$values['formattedlocation'] = calendar_format_event_location($legacyevent);
if ($group = $event->get_group()) {
$values['groupname'] = format_string($group->get('name'), true,
['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;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'context' => 'context',
'course' => 'stdClass?',
];
}
}
+157
View File
@@ -0,0 +1,157 @@
<?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/>.
/**
* Contains event class for displaying a calendar event's icon.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use \core\external\exporter;
use \core_calendar\local\event\entities\event_interface;
/**
* Class for displaying a calendar event's icon.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_icon_exporter extends exporter {
/**
* Constructor.
*
* @param event_interface $event
* @param array $related The related data.
*/
public function __construct(event_interface $event, $related = []) {
global $PAGE;
$coursemodule = $event->get_course_module();
$category = $event->get_category();
$categoryid = $category ? $category->get('id') : null;
$course = $event->get_course();
$courseid = $course ? $course->get('id') : null;
$group = $event->get_group();
$groupid = $group ? $group->get('id') : null;
$user = $event->get_user();
$userid = $user ? $user->get('id') : null;
$isactivityevent = !empty($coursemodule);
$issiteevent = ($course && $courseid == SITEID);
$iscategoryevent = ($category && !empty($categoryid));
$iscourseevent = ($course && !empty($courseid) && $courseid != SITEID && empty($groupid));
$isgroupevent = ($group && !empty($groupid));
$isuserevent = ($user && !empty($userid));
$iconurl = '';
$iconclass = '';
if ($isactivityevent) {
$key = 'monologo';
$component = $coursemodule->get('modname');
$iconurl = get_fast_modinfo($courseid)->get_cm($coursemodule->get('id'))->get_icon_url();
$iconclass = $iconurl->get_param('filtericon') ? '' : 'nofilter';
$iconurl = $iconurl->out(false);
if (get_string_manager()->string_exists($event->get_type(), $component)) {
$alttext = get_string($event->get_type(), $component);
} else {
$alttext = get_string('activityevent', 'calendar');
}
} else if ($event->get_component()) {
// Guess the icon and the title for the component event. By default display calendar icon and the
// plugin name as the alttext.
if ($PAGE->theme->resolve_image_location($event->get_type(), $event->get_component())) {
$key = $event->get_type();
$component = $event->get_component();
} else {
$key = 'i/otherevent';
$component = 'core';
}
if (get_string_manager()->string_exists($event->get_type(), $event->get_component())) {
$alttext = get_string($event->get_type(), $event->get_component());
} else {
$alttext = get_string('pluginname', $event->get_component());
}
} else if ($issiteevent) {
$key = 'i/siteevent';
$component = 'core';
$alttext = get_string('typesite', 'calendar');
} else if ($iscategoryevent) {
$key = 'i/categoryevent';
$component = 'core';
$alttext = get_string('typecategory', 'calendar');
} else if ($iscourseevent) {
$key = 'i/courseevent';
$component = 'core';
$alttext = get_string('typecourse', 'calendar');
} else if ($isgroupevent) {
$key = 'i/groupevent';
$component = 'core';
$alttext = get_string('typegroup', 'calendar');
} else if ($isuserevent) {
$key = 'i/userevent';
$component = 'core';
$alttext = get_string('typeuser', 'calendar');
} else {
// Default to site event icon?
$key = 'i/siteevent';
$component = 'core';
$alttext = get_string('typesite', 'calendar');
}
$data = new \stdClass();
$data->key = $key;
$data->component = $component;
$data->alttext = $alttext;
$data->iconurl = $iconurl;
$data->iconclass = $iconclass;
parent::__construct($data, $related);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'key' => ['type' => PARAM_TEXT],
'component' => ['type' => PARAM_TEXT],
'alttext' => ['type' => PARAM_TEXT],
'iconurl' => ['type' => PARAM_TEXT],
'iconclass' => ['type' => PARAM_TEXT],
];
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'context' => 'context',
];
}
}
@@ -0,0 +1,85 @@
<?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/>.
/**
* Contains event class for displaying a calendar event's subscription.
*
* @package core_calendar
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use \core\external\exporter;
use \core_calendar\local\event\entities\event_interface;
/**
* Class for displaying a calendar event's subscription.
*
* @package core_calendar
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_subscription_exporter extends exporter {
/**
* Constructor.
*
* @param event_interface $event
*/
public function __construct(event_interface $event) {
global $CFG;
$data = new \stdClass();
$data->displayeventsource = false;
if ($event->get_subscription()) {
$subscription = calendar_get_subscription($event->get_subscription()->get('id'));
if (!empty($subscription) && $CFG->calendar_showicalsource) {
$data->displayeventsource = true;
if (!empty($subscription->url)) {
$data->subscriptionurl = $subscription->url;
}
$data->subscriptionname = $subscription->name;
}
}
parent::__construct($data);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
return [
'displayeventsource' => [
'type' => PARAM_BOOL
],
'subscriptionname' => [
'type' => PARAM_RAW,
'optional' => true
],
'subscriptionurl' => [
'type' => PARAM_URL,
'optional' => true
],
];
}
}
+121
View File
@@ -0,0 +1,121 @@
<?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/>.
/**
* Contains event class for displaying a list of calendar events.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use \core\external\exporter;
use \renderer_base;
/**
* Class for displaying a list of calendar events.
*
* This class uses the events relateds cache in order to get the related
* data for exporting an event without having to naively hit the database
* for each event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_exporter extends exporter {
/**
* @var array $events An array of event_interface objects.
*/
protected $events;
/**
* Constructor.
*
* @param array $events An array of event_interface objects
* @param array $related An array of related objects
*/
public function __construct(array $events, $related = []) {
$this->events = $events;
parent::__construct([], $related);
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'events' => [
'type' => event_exporter::read_properties_definition(),
'multiple' => true,
],
'firstid' => [
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'default' => null,
],
'lastid' => [
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'default' => null,
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$return = [];
$cache = $this->related['cache'];
$return['events'] = array_map(function($event) use ($cache, $output) {
$context = $cache->get_context($event);
$course = $cache->get_course($event);
$exporter = new event_exporter($event, ['context' => $context, 'course' => $course]);
return $exporter->export($output);
}, $this->events);
if ($count = count($return['events'])) {
$return['firstid'] = $return['events'][0]->id;
$return['lastid'] = $return['events'][$count - 1]->id;
}
return $return;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'cache' => 'core_calendar\external\events_related_objects_cache',
];
}
}
@@ -0,0 +1,106 @@
<?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/>.
/**
* Contains event class for displaying a list of calendar events grouped by course id.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use \core\external\exporter;
use \renderer_base;
/**
* Class for displaying a list of calendar events grouped by course id.
*
* This class uses the events relateds cache in order to get the related
* data for exporting an event without having to naively hit the database
* for each event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_grouped_by_course_exporter extends exporter {
/**
* @var array $events An array of event_interface objects
* grouped and index by course id.
*/
protected $eventsbycourse;
/**
* Constructor.
*
* @param array $eventsbycourse An array of event_interface objects
* @param array $related An array of related objects
*/
public function __construct(array $eventsbycourse, $related = []) {
$this->eventsbycourse = $eventsbycourse;
parent::__construct([], $related);
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'groupedbycourse' => [
'type' => events_same_course_exporter::read_properties_definition(),
'multiple' => true,
'default' => [],
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$return = [];
$cache = $this->related['cache'];
foreach ($this->eventsbycourse as $courseid => $events) {
$eventsexporter = new events_same_course_exporter(
$courseid, $events, ['cache' => $cache]);
$return['groupedbycourse'][] = $eventsexporter->export($output);
}
return $return;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'cache' => 'core_calendar\external\events_related_objects_cache',
];
}
}
@@ -0,0 +1,288 @@
<?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/>.
/**
* Contains event class for providing the related objects when exporting a list of calendar events.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use \core_calendar\local\event\entities\event_interface;
/**
* Class to providing the related objects when exporting a list of calendar events.
*
* This class is only meant for use with exporters. It attempts to bulk load
* the related objects for a list of events and cache them to avoid having
* to query the database when exporting each individual event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_related_objects_cache {
/**
* @var array $events The events for which we need related objects.
*/
protected $events;
/**
* @var array $courses The related courses.
*/
protected $courses = null;
/**
* @var array $groups The related groups.
*/
protected $groups = null;
/**
* @var array $coursemodules The related course modules.
*/
protected $coursemodules = [];
/**
* @var array $moduleinstances The related module instances.
*/
protected $moduleinstances = null;
/**
* Constructor.
*
* @param array $events Array of event_interface events
* @param array $courses Array of courses to populate the cache with
*/
public function __construct(array $events, array $courses = null) {
$this->events = $events;
if (!is_null($courses)) {
$this->courses = [];
foreach ($courses as $course) {
$this->courses[$course->id] = $course;
}
}
}
/**
* Get the related course object for a given event.
*
* @param event_interface $event The event object.
* @return \stdClass|null
*/
public function get_course(event_interface $event) {
if (is_null($this->courses)) {
$this->load_courses();
}
if ($course = $event->get_course()) {
$courseid = $course->get('id');
return isset($this->courses[$courseid]) ? $this->courses[$courseid] : null;
} else {
return null;
}
}
/**
* Get the related context for a given event.
*
* @param event_interface $event The event object.
* @return \context|null
*/
public function get_context(event_interface $event) {
global $USER;
$categoryid = $event->get_category() ? $event->get_category()->get('id') : null;
$courseid = $event->get_course() ? $event->get_course()->get('id') : null;
$groupid = $event->get_group() ? $event->get_group()->get('id') : null;
$userid = $event->get_user() ? $event->get_user()->get('id') : null;
$moduleid = $event->get_course_module() ? $event->get_course_module()->get('id') : null;
if (!empty($categoryid)) {
return \context_coursecat::instance($categoryid);
} else if (!empty($courseid)) {
return \context_course::instance($event->get_course()->get('id'));
} else if (!empty($groupid)) {
$group = $this->get_group($event);
return \context_course::instance($group->courseid);
} else if (!empty($userid) && $userid == $USER->id) {
return \context_user::instance($userid);
} else if (!empty($userid) && $userid != $USER->id && $moduleid && $moduleid > 0) {
$cm = $this->get_course_module($event);
return \context_course::instance($cm->course);
} else {
return \context_user::instance($userid);
}
}
/**
* Get the related group object for a given event.
*
* @param event_interface $event The event object.
* @return \stdClass|null
*/
public function get_group(event_interface $event) {
if (is_null($this->groups)) {
$this->load_groups();
}
if ($group = $event->get_group()) {
$groupid = $group->get('id');
return isset($this->groups[$groupid]) ? $this->groups[$groupid] : null;
} else {
return null;
}
}
/**
* Get the related course module for a given event.
*
* @param event_interface $event The event object.
* @return \stdClass|null
*/
public function get_course_module(event_interface $event) {
if (!$event->get_course_module()) {
return null;
}
$id = $event->get_course_module()->get('id');
$name = $event->get_course_module()->get('modname');
$key = $name . '_' . $id;
if (!isset($this->coursemodules[$key])) {
$this->coursemodules[$key] = get_coursemodule_from_instance($name, $id, 0, false, MUST_EXIST);
}
return $this->coursemodules[$key];
}
/**
* Get the related module instance for a given event.
*
* @param event_interface $event The event object.
* @return \stdClass|null
*/
public function get_module_instance(event_interface $event) {
if (!$event->get_course_module()) {
return null;
}
if (is_null($this->moduleinstances)) {
$this->load_module_instances();
}
$id = $event->get_course_module()->get('instance');
$name = $event->get_course_module()->get('modname');
if (isset($this->moduleinstances[$name])) {
if (isset($this->moduleinstances[$name][$id])) {
return $this->moduleinstances[$name][$id];
}
}
return null;
}
/**
* Load the list of all of the distinct courses required for the
* list of provided events and save the result in memory.
*/
protected function load_courses() {
global $DB;
$courseids = [];
foreach ($this->events as $event) {
if ($course = $event->get_course()) {
$id = $course->get('id');
$courseids[$id] = true;
}
}
if (empty($courseids)) {
$this->courses = [];
return;
}
list($idsql, $params) = $DB->get_in_or_equal(array_keys($courseids));
$sql = "SELECT * FROM {course} WHERE id {$idsql}";
$this->courses = $DB->get_records_sql($sql, $params);
}
/**
* Load the list of all of the distinct groups required for the
* list of provided events and save the result in memory.
*/
protected function load_groups() {
global $DB;
$groupids = [];
foreach ($this->events as $event) {
if ($group = $event->get_group()) {
$id = $group->get('id');
$groupids[$id] = true;
}
}
if (empty($groupids)) {
$this->groups = [];
return;
}
list($idsql, $params) = $DB->get_in_or_equal(array_keys($groupids));
$sql = "SELECT * FROM {groups} WHERE id {$idsql}";
$this->groups = $DB->get_records_sql($sql, $params);
}
/**
* Load the list of all of the distinct module instances required for the
* list of provided events and save the result in memory.
*/
protected function load_module_instances() {
global $DB;
$this->moduleinstances = [];
$modulestoload = [];
foreach ($this->events as $event) {
if ($module = $event->get_course_module()) {
$id = $module->get('instance');
$name = $module->get('modname');
$ids = isset($modulestoload[$name]) ? $modulestoload[$name] : [];
$ids[$id] = true;
$modulestoload[$name] = $ids;
}
}
if (empty($modulestoload)) {
return;
}
foreach ($modulestoload as $modulename => $ids) {
list($idsql, $params) = $DB->get_in_or_equal(array_keys($ids));
$sql = "SELECT * FROM {" . $modulename . "} WHERE id {$idsql}";
$this->moduleinstances[$modulename] = $DB->get_records_sql($sql, $params);
}
}
}
@@ -0,0 +1,83 @@
<?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/>.
/**
* Contains event class for displaying a list of calendar events for a single course.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use \renderer_base;
/**
* Class for displaying a list of calendar events for a single course.
*
* This class uses the events relateds cache in order to get the related
* data for exporting an event without having to naively hit the database
* for each event.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_same_course_exporter extends events_exporter {
/**
* @var array $courseid The id of the course for these events.
*/
protected $courseid;
/**
* Constructor.
*
* @param int $courseid The course id for these events
* @param array $events An array of event_interface objects
* @param array $related An array of related objects
*/
public function __construct($courseid, array $events, $related = []) {
parent::__construct($events, $related);
$this->courseid = $courseid;
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
$properties = parent::define_other_properties();
$properties['courseid'] = ['type' => PARAM_INT];
return $properties;
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$values = parent::get_other_values($output);
$values['courseid'] = $this->courseid;
return $values;
}
}
+97
View File
@@ -0,0 +1,97 @@
<?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/>.
/**
* This is the external method for exporting a calendar token.
*
* @package core_calendar
* @since Moodle 3.10
* @copyright 2020 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external\export;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/calendar/lib.php');
use context_system;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use moodle_exception;
/**
* This is the external method for exporting a calendar token.
*
* @copyright 2020 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class token extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters.
* @since Moodle 3.10
*/
public static function execute_parameters() {
return new external_function_parameters([]);
}
/**
* Return the auth token required for exporting a calendar.
*
* @return array The access information
* @throws moodle_exception
* @since Moodle 3.10
*/
public static function execute() {
global $CFG, $USER;
$context = context_system::instance();
self::validate_context($context);
if (empty($CFG->enablecalendarexport)) {
throw new moodle_exception('Calendar export is disabled in this site.');
}
return [
'token' => calendar_get_export_token($USER),
'warnings' => [],
];
}
/**
* Returns description of method result value.
*
* @return \core_external\external_description.
* @since Moodle 3.10
*/
public static function execute_returns() {
return new external_single_structure(
[
'token' => new external_value(PARAM_RAW, 'The calendar permanent access token for calendar export.'),
'warnings' => new external_warnings(),
]
);
}
}
+153
View File
@@ -0,0 +1,153 @@
<?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/>.
namespace core_calendar\external;
use core\external\exporter;
use renderer_base;
use stdClass;
use moodle_url;
/**
* Class for exporting calendar footer view options data.
*
* @package core_calendar
* @copyright 2017 Simey Lameze
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class footer_options_exporter extends exporter {
/**
* @var \calendar_information $calendar The calendar to be rendered.
*/
protected $calendar;
/**
* @var int $userid The user id.
*/
protected $userid;
/**
* @var string $token The user sha1 token.
*/
protected $token;
/**
* @var bool $showfullcalendarlink Whether the full calendar link should be displayed or not.
*/
protected $showfullcalendarlink;
/**
* Constructor for month_exporter.
*
* @param \calendar_information $calendar The calendar being represented
* @param int $userid The user id
* @param string $token The user sha1 token.
* @param array $options Display options for the footer. If an option is not set, a default value will be provided.
* It consists of:
* - showfullcalendarlink - bool - Whether to show the full calendar link or not. Defaults to false.
*/
public function __construct(\calendar_information $calendar, $userid, $token, array $options = []) {
$this->calendar = $calendar;
$this->userid = $userid;
$this->token = $token;
$this->showfullcalendarlink = $options['showfullcalendarlink'] ?? false;
}
/**
* Get manage subscription link.
*
* @return string|null The manage subscription hyperlink.
*/
protected function get_manage_subscriptions_link(): ?string {
if (calendar_user_can_add_event($this->calendar->course)) {
$managesubscriptionurl = new moodle_url('/calendar/managesubscriptions.php');
return $managesubscriptionurl->out(true);
}
return null;
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
global $CFG;
$values = new stdClass();
$values->footerlinks = [];
if ($this->showfullcalendarlink) {
if ($this->calendar->courseid !== SITEID) {
$linkname = get_string('coursecalendarlink', 'calendar');
} else {
$linkname = get_string('fullcalendar', 'calendar');
}
$values->footerlinks[] = (object)[
'url' => $this->get_calendar_url(),
'linkname' => $linkname,
];
}
if (!empty($CFG->enablecalendarexport) && $managesubscriptionlink = $this->get_manage_subscriptions_link()) {
$values->footerlinks[] = (object)[
'url' => $managesubscriptionlink,
'linkname' => get_string('managesubscriptions', 'calendar'),
];
}
return (array) $values;
}
/**
* Return the list of additional properties.
*
* @return array
*/
public static function define_other_properties() {
return [
'footerlinks' => [
'type' => [
'url' => [
'type' => PARAM_URL,
],
'linkname' => [
'type' => PARAM_TEXT,
],
],
'multiple' => true,
'optional' => true,
],
];
}
/**
* Build the calendar URL.
*
* @return string The calendar URL.
*/
public function get_calendar_url() {
$url = new moodle_url('/calendar/view.php', [
'view' => 'month',
'time' => $this->calendar->time,
'course' => $this->calendar->courseid,
]);
return $url->out(false);
}
}
+471
View File
@@ -0,0 +1,471 @@
<?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/>.
/**
* Contains event class for displaying the month view.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
use moodle_url;
/**
* Class for displaying the month view.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class month_exporter extends exporter {
/** @var int Number of calendar instances displayed. */
protected static $calendarinstances = 0;
/** @var int This calendar instance's ID. */
protected $calendarinstanceid = 0;
/**
* @var \calendar_information $calendar The calendar to be rendered.
*/
protected $calendar;
/**
* @var int $firstdayofweek The first day of the week.
*/
protected $firstdayofweek;
/**
* @var moodle_url $url The URL for the events page.
*/
protected $url;
/**
* @var bool $includenavigation Whether navigation should be included on the output.
*/
protected $includenavigation = true;
/**
* @var bool $initialeventsloaded Whether the events have been loaded for this month.
*/
protected $initialeventsloaded = true;
/**
* @var bool $showcoursefilter Whether to render the course filter selector as well.
*/
protected $showcoursefilter = false;
/**
* Constructor for month_exporter.
*
* @param \calendar_information $calendar The calendar being represented
* @param \core_calendar\type_base $type The calendar type (e.g. Gregorian)
* @param array $related The related information
*/
public function __construct(\calendar_information $calendar, \core_calendar\type_base $type, $related) {
// Increment the calendar instances count on initialisation.
self::$calendarinstances++;
// Assign this instance an ID based on the latest calendar instances count.
$this->calendarinstanceid = self::$calendarinstances;
$this->calendar = $calendar;
$this->firstdayofweek = $type->get_starting_weekday();
$this->url = new moodle_url('/calendar/view.php', [
'view' => 'month',
'time' => $calendar->time,
]);
if ($this->calendar->course && SITEID !== $this->calendar->course->id) {
$this->url->param('course', $this->calendar->course->id);
} else if ($this->calendar->categoryid) {
$this->url->param('category', $this->calendar->categoryid);
}
$related['type'] = $type;
$data = [
'url' => $this->url->out(false),
];
parent::__construct($data, $related);
}
protected static function define_properties() {
return [
'url' => [
'type' => PARAM_URL,
],
];
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'courseid' => [
'type' => PARAM_INT,
],
'categoryid' => [
'type' => PARAM_INT,
'optional' => true,
'default' => 0,
],
'filter_selector' => [
'type' => PARAM_RAW,
'optional' => true,
],
'weeks' => [
'type' => week_exporter::read_properties_definition(),
'multiple' => true,
],
'daynames' => [
'type' => day_name_exporter::read_properties_definition(),
'multiple' => true,
],
'view' => [
'type' => PARAM_ALPHA,
],
'date' => [
'type' => date_exporter::read_properties_definition(),
],
'periodname' => [
// Note: We must use RAW here because the calendar type returns the formatted month name based on a
// calendar format.
'type' => PARAM_RAW,
],
'includenavigation' => [
'type' => PARAM_BOOL,
'default' => true,
],
// Tracks whether the first set of events have been loaded and provided
// to the exporter.
'initialeventsloaded' => [
'type' => PARAM_BOOL,
'default' => true,
],
'previousperiod' => [
'type' => date_exporter::read_properties_definition(),
],
'previousperiodlink' => [
'type' => PARAM_URL,
],
'previousperiodname' => [
// Note: We must use RAW here because the calendar type returns the formatted month name based on a
// calendar format.
'type' => PARAM_RAW,
],
'nextperiod' => [
'type' => date_exporter::read_properties_definition(),
],
'nextperiodname' => [
// Note: We must use RAW here because the calendar type returns the formatted month name based on a
// calendar format.
'type' => PARAM_RAW,
],
'nextperiodlink' => [
'type' => PARAM_URL,
],
'larrow' => [
// The left arrow defined by the theme.
'type' => PARAM_RAW,
],
'rarrow' => [
// The right arrow defined by the theme.
'type' => PARAM_RAW,
],
'defaulteventcontext' => [
'type' => PARAM_INT,
'default' => 0,
],
'calendarinstanceid' => [
'type' => PARAM_INT,
'default' => 0,
],
'viewingmonth' => [
'type' => PARAM_BOOL,
'default' => true,
],
'showviewselector' => [
'type' => PARAM_BOOL,
'default' => true,
],
'viewinginblock' => [
'type' => PARAM_BOOL,
'default' => false,
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$previousperiod = $this->get_previous_month_data();
$nextperiod = $this->get_next_month_data();
$date = $this->related['type']->timestamp_to_date_array($this->calendar->time);
$nextperiodlink = new moodle_url($this->url);
$nextperiodlink->param('time', $nextperiod[0]);
$previousperiodlink = new moodle_url($this->url);
$previousperiodlink->param('time', $previousperiod[0]);
$viewmode = $this->calendar->get_viewmode() ?? 'month';
$return = [
'courseid' => $this->calendar->courseid,
'weeks' => $this->get_weeks($output),
'daynames' => $this->get_day_names($output),
'view' => $viewmode,
'date' => (new date_exporter($date))->export($output),
'periodname' => userdate($this->calendar->time, get_string('strftimemonthyear')),
'previousperiod' => (new date_exporter($previousperiod))->export($output),
'previousperiodname' => userdate($previousperiod[0], get_string('strftimemonth')),
'previousperiodlink' => $previousperiodlink->out(false),
'nextperiod' => (new date_exporter($nextperiod))->export($output),
'nextperiodname' => userdate($nextperiod[0], get_string('strftimemonth')),
'nextperiodlink' => $nextperiodlink->out(false),
'larrow' => $output->larrow(),
'rarrow' => $output->rarrow(),
'includenavigation' => $this->includenavigation,
'initialeventsloaded' => $this->initialeventsloaded,
'calendarinstanceid' => $this->calendarinstanceid,
'showviewselector' => $viewmode === 'month',
'viewinginblock' => $viewmode === 'monthblock',
];
if ($this->showcoursefilter) {
$return['filter_selector'] = $this->get_course_filter_selector($output);
}
if ($context = $this->get_default_add_context()) {
$return['defaulteventcontext'] = $context->id;
}
if ($this->calendar->categoryid) {
$return['categoryid'] = $this->calendar->categoryid;
}
return $return;
}
/**
* Get the course filter selector.
*
* @param renderer_base $output
* @return string The html code for the course filter selector.
*/
protected function get_course_filter_selector(renderer_base $output) {
$content = '';
$content .= $output->course_filter_selector($this->url, '', $this->calendar->course->id, $this->calendarinstanceid);
return $content;
}
/**
* Get the list of day names for display, re-ordered from the first day
* of the week.
*
* @param renderer_base $output
* @return day_name_exporter[]
*/
protected function get_day_names(renderer_base $output) {
$weekdays = $this->related['type']->get_weekdays();
$daysinweek = count($weekdays);
$daynames = [];
for ($i = 0; $i < $daysinweek; $i++) {
// Bump the currentdayno and ensure it loops.
$dayno = ($i + $this->firstdayofweek + $daysinweek) % $daysinweek;
$dayname = new day_name_exporter($dayno, $weekdays[$dayno]);
$daynames[] = $dayname->export($output);
}
return $daynames;
}
/**
* Get the list of week days, ordered into weeks and padded according
* to the value of the first day of the week.
*
* @param renderer_base $output
* @return array The list of weeks.
*/
protected function get_weeks(renderer_base $output) {
$weeks = [];
$alldays = $this->get_days();
$daysinweek = count($this->related['type']->get_weekdays());
// Calculate which day number is the first, and last day of the week.
$firstdayofweek = $this->firstdayofweek;
// The first week is special as it may have padding at the beginning.
$day = reset($alldays);
$firstdayno = $day['wday'];
$prepadding = ($firstdayno + $daysinweek - $firstdayofweek) % $daysinweek;
$daysinfirstweek = $daysinweek - $prepadding;
$days = array_slice($alldays, 0, $daysinfirstweek);
$week = new week_exporter($this->calendar, $days, $prepadding, ($daysinweek - count($days) - $prepadding), $this->related);
$weeks[] = $week->export($output);
// Now chunk up the remaining day. and turn them into weeks.
$daychunks = array_chunk(array_slice($alldays, $daysinfirstweek), $daysinweek);
foreach ($daychunks as $days) {
$week = new week_exporter($this->calendar, $days, 0, ($daysinweek - count($days)), $this->related);
$weeks[] = $week->export($output);
}
return $weeks;
}
/**
* Get the list of days with the matching date array.
*
* @return array
*/
protected function get_days() {
$date = $this->related['type']->timestamp_to_date_array($this->calendar->time);
$monthdays = $this->related['type']->get_num_days_in_month($date['year'], $date['mon']);
$days = [];
for ($dayno = 1; $dayno <= $monthdays; $dayno++) {
// Get the gregorian representation of the day.
$timestamp = $this->related['type']->convert_to_timestamp($date['year'], $date['mon'], $dayno);
$days[] = $this->related['type']->timestamp_to_date_array($timestamp);
}
return $days;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'events' => '\core_calendar\local\event\entities\event_interface[]',
'cache' => '\core_calendar\external\events_related_objects_cache',
'type' => '\core_calendar\type_base',
];
}
/**
* Get the current month timestamp.
*
* @return int The month timestamp.
*/
protected function get_month_data() {
$date = $this->related['type']->timestamp_to_date_array($this->calendar->time);
$monthtime = $this->related['type']->convert_to_gregorian($date['year'], $date['month'], 1);
return make_timestamp($monthtime['year'], $monthtime['month']);
}
/**
* Get the previous month timestamp.
*
* @return int The previous month timestamp.
*/
protected function get_previous_month_data() {
$type = $this->related['type'];
$date = $type->timestamp_to_date_array($this->calendar->time);
list($date['mon'], $date['year']) = $type->get_prev_month($date['year'], $date['mon']);
$time = $type->convert_to_timestamp($date['year'], $date['mon'], 1);
return $type->timestamp_to_date_array($time);
}
/**
* Get the next month timestamp.
*
* @return int The next month timestamp.
*/
protected function get_next_month_data() {
$type = $this->related['type'];
$date = $type->timestamp_to_date_array($this->calendar->time);
list($date['mon'], $date['year']) = $type->get_next_month($date['year'], $date['mon']);
$time = $type->convert_to_timestamp($date['year'], $date['mon'], 1);
return $type->timestamp_to_date_array($time);
}
/**
* Set whether the navigation should be shown.
*
* @param bool $include
* @return $this
*/
public function set_includenavigation($include) {
$this->includenavigation = $include;
return $this;
}
/**
* Set whether the initial events have already been loaded and
* provided to the exporter.
*
* @param bool $loaded
* @return $this
*/
public function set_initialeventsloaded(bool $loaded) {
$this->initialeventsloaded = $loaded;
return $this;
}
/**
* Set whether the course filter selector should be shown.
*
* @param bool $show
* @return $this
*/
public function set_showcoursefilter(bool $show) {
$this->showcoursefilter = $show;
return $this;
}
/**
* Get the default context for use when adding a new event.
*
* @return null|\context
*/
protected function get_default_add_context() {
if (calendar_user_can_add_event($this->calendar->course)) {
return \context_course::instance($this->calendar->course->id);
}
return null;
}
}
+104
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 core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\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()
]);
}
}
+168
View File
@@ -0,0 +1,168 @@
<?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/>.
/**
* Contains event class for displaying the day on month view.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
/**
* Class for displaying the day on month view.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class week_day_exporter extends day_exporter {
/**
* Constructor.
*
* @param \calendar_information $calendar The calendar information for the period being displayed
* @param mixed $data Either an stdClass or an array of values.
* @param array $related Related objects.
*/
public function __construct(\calendar_information $calendar, $data, $related) {
parent::__construct($calendar, $data, $related);
// Fix the url for today to be based on the today timestamp
// rather than the calendar_information time set in the parent
// constructor.
$this->url->param('time', $this->data[0]);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_properties() {
$return = parent::define_properties();
$return = array_merge($return, [
// These are additional params.
'istoday' => [
'type' => PARAM_BOOL,
'default' => false,
],
'isweekend' => [
'type' => PARAM_BOOL,
'default' => false,
],
]);
return $return;
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
$return = parent::define_other_properties();
$return = array_merge($return, [
'popovertitle' => [
'type' => PARAM_RAW,
'default' => '',
],
'daytitle' => [
'type' => PARAM_RAW,
]
]);
return $return;
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
$return = parent::get_other_values($output);
if ($popovertitle = $this->get_popover_title()) {
$return['popovertitle'] = $popovertitle;
}
$return['daytitle'] = $this->get_day_title();
return $return;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'events' => '\core_calendar\local\event\entities\event_interface[]',
'cache' => '\core_calendar\external\events_related_objects_cache',
'type' => '\core_calendar\type_base',
];
}
/**
* Get the title for this popover.
*
* @return string
*/
protected function get_popover_title() {
$title = null;
$userdate = userdate($this->data[0], get_string('strftimedayshort'));
if (count($this->related['events'])) {
$title = get_string('eventsfor', 'calendar', $userdate);
} else if ($this->data['istoday']) {
$title = $userdate;
}
if ($this->data['istoday']) {
$title = get_string('todayplustitle', 'calendar', $userdate);
}
return $title;
}
/**
* Get the title for this day.
*
* @return string
*/
protected function get_day_title(): string {
$userdate = userdate($this->data[0], get_string('strftimedayshort'));
$numevents = count($this->related['events']);
if ($numevents == 1) {
$title = get_string('dayeventsone', 'calendar', $userdate);
} else if ($numevents) {
$title = get_string('dayeventsmany', 'calendar', ['num' => $numevents, 'day' => $userdate]);
} else {
$title = get_string('dayeventsnone', 'calendar', $userdate);
}
return $title;
}
}
+182
View File
@@ -0,0 +1,182 @@
<?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/>.
/**
* Contains event class for displaying the week view.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
/**
* Class for displaying the week view.
*
* @package core_calendar
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class week_exporter extends exporter {
/**
* @var array $days An array of day_exporter objects.
*/
protected $days = [];
/**
* @var int $prepadding The number of pre-padding days at the start of the week.
*/
protected $prepadding = 0;
/**
* @var int $postpadding The number of post-padding days at the start of the week.
*/
protected $postpadding = 0;
/**
* @var \calendar_information $calendar The calendar being displayed.
*/
protected $calendar;
/**
* Constructor.
*
* @param \calendar_information $calendar The calendar information for the period being displayed
* @param mixed $days An array of day_exporter objects.
* @param int $prepadding The number of pre-padding days at the start of the week.
* @param int $postpadding The number of post-padding days at the start of the week.
* @param array $related Related objects.
*/
public function __construct(\calendar_information $calendar, $days, $prepadding, $postpadding, $related) {
$this->days = $days;
$this->prepadding = $prepadding;
$this->postpadding = $postpadding;
$this->calendar = $calendar;
parent::__construct([], $related);
}
/**
* Return the list of additional properties.
*
* @return array
*/
protected static function define_other_properties() {
return [
'prepadding' => [
'type' => PARAM_INT,
'multiple' => true,
],
'postpadding' => [
'type' => PARAM_INT,
'multiple' => true,
],
'days' => [
'type' => week_day_exporter::read_properties_definition(),
'multiple' => true,
],
];
}
/**
* Get the additional values to inject while exporting.
*
* @param renderer_base $output The renderer.
* @return array Keys are the property names, values are their values.
*/
protected function get_other_values(renderer_base $output) {
global $CFG;
$return = [
'prepadding' => [],
'postpadding' => [],
'days' => [],
];
for ($i = 0; $i < $this->prepadding; $i++) {
$return['prepadding'][] = $i;
}
for ($i = 0; $i < $this->postpadding; $i++) {
$return['postpadding'][] = $i;
}
$return['days'] = [];
$today = $this->related['type']->timestamp_to_date_array(time());
$weekend = CALENDAR_DEFAULT_WEEKEND;
if (isset($CFG->calendar_weekend)) {
$weekend = intval($CFG->calendar_weekend);
}
$numberofdaysinweek = $this->related['type']->get_num_weekdays();
foreach ($this->days as $daydata) {
$events = [];
foreach ($this->related['events'] as $event) {
$times = $event->get_times();
$starttime = $times->get_start_time()->getTimestamp();
$startdate = $this->related['type']->timestamp_to_date_array($starttime);
$endtime = $times->get_end_time()->getTimestamp();
$enddate = $this->related['type']->timestamp_to_date_array($endtime);
if ((($startdate['year'] * 366) + $startdate['yday']) > ($daydata['year'] * 366) + $daydata['yday']) {
// Starts after today.
continue;
}
if ((($enddate['year'] * 366) + $enddate['yday']) < ($daydata['year'] * 366) + $daydata['yday']) {
// Ends before today.
continue;
}
$events[] = $event;
}
$istoday = true;
$istoday = $istoday && $today['year'] == $daydata['year'];
$istoday = $istoday && $today['yday'] == $daydata['yday'];
$daydata['istoday'] = $istoday;
$daydata['isweekend'] = !!($weekend & (1 << ($daydata['wday'] % $numberofdaysinweek)));
$day = new week_day_exporter($this->calendar, $daydata, [
'events' => $events,
'cache' => $this->related['cache'],
'type' => $this->related['type'],
]);
$return['days'][] = $day->export($output);
}
return $return;
}
/**
* Returns a list of objects that are related.
*
* @return array
*/
protected static function define_related() {
return [
'events' => '\core_calendar\local\event\entities\event_interface[]',
'cache' => '\core_calendar\external\events_related_objects_cache',
'type' => '\core_calendar\type_base',
];
}
}
+338
View File
@@ -0,0 +1,338 @@
<?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/>.
/**
* Contains class containing the internal calendar API.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\container;
use core_calendar\local\event\entities\event_interface;
use core_calendar\local\event\exceptions\limit_invalid_parameter_exception;
/**
* Class containing the local calendar API.
*
* This should not be used outside of core_calendar.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api {
/**
* Get all events restricted by various parameters, taking in to account user and group overrides.
*
* @param int|null $timestartfrom Events with timestart from this value (inclusive).
* @param int|null $timestartto Events with timestart until this value (inclusive).
* @param int|null $timesortfrom Events with timesort from this value (inclusive).
* @param int|null $timesortto Events with timesort until this value (inclusive).
* @param int|null $timestartaftereventid Restrict the events in the timestart range to ones after this ID.
* @param int|null $timesortaftereventid Restrict the events in the timesort range to ones after this ID.
* @param int $limitnum Return at most this number of events.
* @param int|null $type Return only events of this type.
* @param array|null $usersfilter Return only events for these users.
* @param array|null $groupsfilter Return only events for these groups.
* @param array|null $coursesfilter Return only events for these courses.
* @param bool $withduration If true return only events starting within specified
* timestart otherwise return in progress events as well.
* @param bool $ignorehidden If true don't return hidden events.
* @return \core_calendar\local\event\entities\event_interface[] Array of event_interfaces.
*/
public static function get_events(
$timestartfrom = null,
$timestartto = null,
$timesortfrom = null,
$timesortto = null,
$timestartaftereventid = null,
$timesortaftereventid = null,
$limitnum = 20,
$type = null,
array $usersfilter = null,
array $groupsfilter = null,
array $coursesfilter = null,
array $categoriesfilter = null,
$withduration = true,
$ignorehidden = true,
callable $filter = null
) {
global $USER;
$vault = \core_calendar\local\event\container::get_event_vault();
$timestartafterevent = null;
$timesortafterevent = null;
if ($timestartaftereventid && $event = $vault->get_event_by_id($timestartaftereventid)) {
$timestartafterevent = $event;
}
if ($timesortaftereventid && $event = $vault->get_event_by_id($timesortaftereventid)) {
$timesortafterevent = $event;
}
return $vault->get_events(
$timestartfrom,
$timestartto,
$timesortfrom,
$timesortto,
$timestartafterevent,
$timesortafterevent,
$limitnum,
$type,
$usersfilter,
$groupsfilter,
$coursesfilter,
$categoriesfilter,
$withduration,
$ignorehidden,
$filter
);
}
/**
* Get a list of action events for the logged in user by the given
* timesort values.
*
* @param int|null $timesortfrom The start timesort value (inclusive)
* @param int|null $timesortto The end timesort value (inclusive)
* @param int|null $aftereventid Only return events after this one
* @param int $limitnum Limit results to this amount (between 1 and 50)
* @param bool $lmittononsuspendedevents Limit course events to courses the user is active in (not suspended).
* @param \stdClass|null $user The user id or false for $USER
* @param string|null $searchvalue The value a user wishes to search against
* @return array A list of action_event_interface objects
* @throws \moodle_exception
*/
public static function get_action_events_by_timesort(
$timesortfrom = null,
$timesortto = null,
$aftereventid = null,
$limitnum = 20,
$limittononsuspendedevents = false,
?\stdClass $user = null,
?string $searchvalue = null
) {
global $USER;
if (!$user) {
$user = $USER;
}
if (is_null($timesortfrom) && is_null($timesortto)) {
throw new \moodle_exception("Must provide a timesort to and/or from value");
}
if ($limitnum < 1 || $limitnum > 50) {
throw new \moodle_exception("Limit must be between 1 and 50 (inclusive)");
}
\core_calendar\local\event\container::set_requesting_user($user->id);
$vault = \core_calendar\local\event\container::get_event_vault();
$afterevent = null;
if ($aftereventid && $event = $vault->get_event_by_id($aftereventid)) {
$afterevent = $event;
}
return $vault->get_action_events_by_timesort($user, $timesortfrom, $timesortto, $afterevent, $limitnum,
$limittononsuspendedevents, $searchvalue);
}
/**
* Get a list of action events for the logged in user by the given
* course and timesort values.
*
* @param \stdClass $course The course the events must belong to
* @param int|null $timesortfrom The start timesort value (inclusive)
* @param int|null $timesortto The end timesort value (inclusive)
* @param int|null $aftereventid Only return events after this one
* @param int $limitnum Limit results to this amount (between 1 and 50)
* @param string|null $searchvalue The value a user wishes to search against
* @return array A list of action_event_interface objects
* @throws limit_invalid_parameter_exception
*/
public static function get_action_events_by_course(
$course,
$timesortfrom = null,
$timesortto = null,
$aftereventid = null,
$limitnum = 20,
?string $searchvalue = null
) {
global $USER;
if ($limitnum < 1 || $limitnum > 50) {
throw new limit_invalid_parameter_exception(
"Limit must be between 1 and 50 (inclusive)");
}
$vault = \core_calendar\local\event\container::get_event_vault();
$afterevent = null;
if ($aftereventid && $event = $vault->get_event_by_id($aftereventid)) {
$afterevent = $event;
}
return $vault->get_action_events_by_course(
$USER, $course, $timesortfrom, $timesortto, $afterevent, $limitnum, $searchvalue);
}
/**
* Get a list of action events for the logged in user by the given
* courses and timesort values.
*
* The limit number applies per course, not for the result set as a whole.
* E.g. Requesting 3 courses with a limit of 10 will result in up to 30
* events being returned (up to 10 per course).
*
* @param array $courses The courses the events must belong to
* @param int|null $timesortfrom The start timesort value (inclusive)
* @param int|null $timesortto The end timesort value (inclusive)
* @param int $limitnum Limit results per course to this amount (between 1 and 50)
* @param string|null $searchvalue The value a user wishes to search against
* @return array A list of action_event_interface objects indexed by course id
*/
public static function get_action_events_by_courses(
$courses = [],
$timesortfrom = null,
$timesortto = null,
$limitnum = 20,
?string $searchvalue = null
) {
$return = [];
foreach ($courses as $course) {
$return[$course->id] = self::get_action_events_by_course(
$course,
$timesortfrom,
$timesortto,
null,
$limitnum,
$searchvalue
);
}
return $return;
}
/**
* Change the start day for an event. Only the date will be
* modified, the time of day for the event will be left as is.
*
* @param event_interface $event The existing event to modify
* @param DateTimeInterface $startdate The new date to use for the start day
* @return event_interface The new event with updated start date
*/
public static function update_event_start_day(
event_interface $event,
\DateTimeInterface $startdate
) {
global $DB;
$mapper = container::get_event_mapper();
$legacyevent = $mapper->from_event_to_legacy_event($event);
$hascoursemodule = !empty($event->get_course_module());
$moduleinstance = null;
$starttime = $event->get_times()->get_start_time()->setDate(
$startdate->format('Y'),
$startdate->format('n'),
$startdate->format('j')
);
$starttimestamp = $starttime->getTimestamp();
if ($hascoursemodule) {
$moduleinstance = $DB->get_record(
$event->get_course_module()->get('modname'),
['id' => $event->get_course_module()->get('instance')],
'*',
MUST_EXIST
);
// If there is a timestart range callback implemented then we can
// use the values returned from the valid timestart range to apply
// some default validation on the event's timestart value to ensure
// that it falls within the specified range.
list($min, $max) = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_get_valid_event_timestart_range',
[$legacyevent, $moduleinstance],
[false, false]
);
} else if ($legacyevent->courseid != 0 && $legacyevent->courseid != SITEID && $legacyevent->groupid == 0) {
// This is a course event.
list($min, $max) = component_callback(
'core_course',
'core_calendar_get_valid_event_timestart_range',
[$legacyevent, $event->get_course()->get_proxied_instance()],
[0, 0]
);
} else {
$min = $max = 0;
}
// If the callback returns false for either value it means that
// there is no valid time start range.
if ($min === false || $max === false) {
throw new \moodle_exception('The start day of this event can not be modified');
}
if ($min && $starttimestamp < $min[0]) {
throw new \moodle_exception($min[1]);
}
if ($max && $starttimestamp > $max[0]) {
throw new \moodle_exception($max[1]);
}
// This function does our capability checks.
$legacyevent->update((object) ['timestart' => $starttime->getTimestamp()]);
// Check that the user is allowed to manually edit calendar events before
// calling the event updated callback. The manual flag causes the code to
// check the user has the capabilities to modify the modules.
//
// We don't want to call the event update callback if the user isn't allowed
// to modify course modules because depending on the callback it can make
// some changes that would be considered security issues, such as updating the
// due date for an assignment.
if ($hascoursemodule && calendar_edit_event_allowed($legacyevent, true)) {
// If this event is from an activity then we need to call
// the activity callback to let it know that the event it
// created has been modified so it needs to update accordingly.
component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_event_timestart_updated',
[$legacyevent, $moduleinstance]
);
// Rebuild the course cache to make sure the updated dates are reflected.
$courseid = $event->get_course()->get('id');
$cmid = $event->get_course_module()->get('id');
\course_modinfo::purge_course_module_cache($courseid, $cmid);
rebuild_course_cache($courseid, true, true);
}
return $mapper->from_legacy_event_to_event($legacyevent);
}
}
+361
View File
@@ -0,0 +1,361 @@
<?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/>.
/**
* Core container for calendar events.
*
* The purpose of this class is simply to wire together the various
* implementations of calendar event components to produce a solution
* to the problems Moodle core wants to solve.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event;
defined('MOODLE_INTERNAL') || die();
use core_calendar\action_factory;
use core_calendar\local\event\data_access\event_vault;
use core_calendar\local\event\entities\action_event;
use core_calendar\local\event\entities\action_event_interface;
use core_calendar\local\event\entities\event_interface;
use core_calendar\local\event\factories\event_factory;
use core_calendar\local\event\mappers\event_mapper;
use core_calendar\local\event\strategies\raw_event_retrieval_strategy;
/**
* Core container.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class container {
/**
* @var event_factory $eventfactory Event factory.
*/
protected static $eventfactory;
/**
* @var event_mapper $eventmapper Event mapper.
*/
protected static $eventmapper;
/**
* @var action_factory $actionfactory Action factory.
*/
protected static $actionfactory;
/**
* @var event_vault $eventvault Event vault.
*/
protected static $eventvault;
/**
* @var raw_event_retrieval_strategy $eventretrievalstrategy Event retrieval strategy.
*/
protected static $eventretrievalstrategy;
/**
* @var \stdClass[] An array of cached courses to use with the event factory.
*/
protected static $coursecache = array();
/**
* @var \stdClass[] An array of cached modules to use with the event factory.
*/
protected static $modulecache = array();
/**
* @var int The requesting user. All capability checks are done against this user.
*/
protected static $requestinguserid;
/**
* Initialises the dependency graph if it hasn't yet been.
*/
private static function init() {
if (empty(self::$eventfactory)) {
self::$actionfactory = new action_factory();
self::$eventmapper = new event_mapper(
// The event mapper we return from here needs to know how to
// make events, so it needs an event factory. However we can't
// give it the same one as we store and return in the container
// as that one uses all our plumbing to control event visibility.
//
// So we make a new even factory that doesn't do anyting other than
// return the instance.
new event_factory(
// Never apply actions, simply return.
function(event_interface $event) {
return $event;
},
// Never hide an event.
function() {
return true;
},
// Never bail out early when instantiating an event.
function() {
return false;
},
self::$coursecache,
self::$modulecache
)
);
self::$eventfactory = new event_factory(
[self::class, 'apply_component_provide_event_action'],
[self::class, 'apply_component_is_event_visible'],
function ($dbrow) {
$requestinguserid = self::get_requesting_user();
if (!empty($dbrow->categoryid)) {
// This is a category event. Check that the category is visible to this user.
$category = \core_course_category::get($dbrow->categoryid, IGNORE_MISSING, true, $requestinguserid);
if (empty($category) || !$category->is_uservisible($requestinguserid)) {
return true;
}
}
// For non-module events we assume that all checks were done in core_calendar_is_event_visible callback.
// For module events we also check that the course module and course itself are visible to the user.
if (empty($dbrow->modulename)) {
return false;
}
$instances = get_fast_modinfo($dbrow->courseid, $requestinguserid)->instances;
// If modinfo doesn't know about the module, we should ignore it.
if (!isset($instances[$dbrow->modulename]) || !isset($instances[$dbrow->modulename][$dbrow->instance])) {
return true;
}
$cm = $instances[$dbrow->modulename][$dbrow->instance];
// If the module is not visible to the current user, we should ignore it.
// We have to check enrolment here as well because the uservisible check
// looks for the "view" capability however some activities (such as Lesson)
// have that capability set on the "Authenticated User" role rather than
// on "Student" role, which means uservisible returns true even when the user
// is no longer enrolled in the course.
// So, with the following we are checking -
// 1) Only process modules if $cm->uservisible is true.
// 2) Only process modules for courses a user has the capability to view OR they are enrolled in.
// 3) Only process modules for courses that are visible OR if the course is not visible, the user
// has the capability to view hidden courses.
if (!$cm->uservisible) {
return true;
}
$coursecontext = \context_course::instance($dbrow->courseid);
if (!$cm->get_course()->visible &&
!has_capability('moodle/course:viewhiddencourses', $coursecontext, $requestinguserid)) {
return true;
}
if (!has_capability('moodle/course:view', $coursecontext, $requestinguserid) &&
!is_enrolled($coursecontext, $requestinguserid)) {
return true;
}
// Ok, now check if we are looking at a completion event.
if ($dbrow->eventtype === \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
// Need to have completion enabled before displaying these events.
$course = new \stdClass();
$course->id = $dbrow->courseid;
$completion = new \completion_info($course);
if ($completion->is_enabled($cm)) {
// Check if the event is completed, then in this case we do not need to complete it.
// Make sure we're using a cm_info object.
$completiondata = $completion->get_data($cm);
return intval($completiondata->completionstate) === COMPLETION_COMPLETE;
}
return true;
}
return false;
},
self::$coursecache,
self::$modulecache
);
}
if (empty(self::$eventvault)) {
self::$eventretrievalstrategy = new raw_event_retrieval_strategy();
self::$eventvault = new event_vault(self::$eventfactory, self::$eventretrievalstrategy);
}
}
/**
* Reset all static caches, called between tests.
*/
public static function reset_caches() {
self::$requestinguserid = null;
self::$eventfactory = null;
self::$eventmapper = null;
self::$eventvault = null;
self::$actionfactory = null;
self::$eventretrievalstrategy = null;
self::$coursecache = [];
self::$modulecache = [];
}
/**
* Gets the event factory.
*
* @return event_factory
*/
public static function get_event_factory() {
self::init();
return self::$eventfactory;
}
/**
* Gets the event mapper.
*
* @return event_mapper
*/
public static function get_event_mapper() {
self::init();
return self::$eventmapper;
}
/**
* Return an event vault.
*
* @return event_vault
*/
public static function get_event_vault() {
self::init();
return self::$eventvault;
}
/**
* Sets the requesting user so that all capability checks are done against this user.
* Setting the requesting user (hence calling this function) is optional and if you do not so,
* $USER will be used as the requesting user. However, if you wish to set the requesting user yourself,
* you should call this function before any other function of the container class is called.
*
* @param int $userid The user id.
* @throws \coding_exception
*/
public static function set_requesting_user($userid) {
self::$requestinguserid = $userid;
}
/**
* Returns the requesting user id.
* It usually is the current user unless it has been set explicitly using set_requesting_user.
*
* @return int
*/
public static function get_requesting_user() {
global $USER;
return empty(self::$requestinguserid) ? $USER->id : self::$requestinguserid;
}
/**
* Calls callback 'core_calendar_provide_event_action' from the component responsible for the event
*
* If no callback is present or callback returns null, there is no action on the event
* and it will not be displayed on the dashboard.
*
* @param event_interface $event
* @return action_event|event_interface
*/
public static function apply_component_provide_event_action(event_interface $event) {
// Callbacks will get supplied a "legacy" version
// of the event class.
$mapper = self::$eventmapper;
$action = null;
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,
// but the event mapper is not aware of that.
if (empty($event->user) && !empty($legacyevent->userid)) {
$legacyevent->userid = $requestinguserid;
}
// Any other event will not be displayed on the dashboard.
$action = component_callback(
$event->get_component(),
'core_calendar_provide_event_action',
[
$legacyevent,
self::$actionfactory,
$requestinguserid
]
);
}
// If we get an action back, return an action event, otherwise
// continue piping through the original event.
//
// If a module does not implement the callback, component_callback
// returns null.
return $action ? new action_event($event, $action) : $event;
}
/**
* Calls callback 'core_calendar_is_event_visible' from the component responsible for the event
*
* The visibility callback is optional, if not present it is assumed as visible.
* If it is an actionable event but the get_item_count() returns 0 the visibility
* is set to false.
*
* @param event_interface $event
* @return bool
*/
public static function apply_component_is_event_visible(event_interface $event) {
$mapper = self::$eventmapper;
$eventvisible = null;
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,
// but the event mapper is not aware of that.
if (empty($event->user) && !empty($legacyevent->userid)) {
$legacyevent->userid = $requestinguserid;
}
$eventvisible = component_callback(
$event->get_component(),
'core_calendar_is_event_visible',
[
$legacyevent,
$requestinguserid
]
);
}
// Do not display the event if there is nothing to action.
if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
return false;
}
// Module does not implement the callback, event should be visible.
if (is_null($eventvisible)) {
return true;
}
return $eventvisible ? true : false;
}
}
@@ -0,0 +1,456 @@
<?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/>.
/**
* Event vault class
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\data_access;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\entities\action_event_interface;
use core_calendar\local\event\entities\event_interface;
use core_calendar\local\event\exceptions\limit_invalid_parameter_exception;
use core_calendar\local\event\factories\action_factory_interface;
use core_calendar\local\event\factories\event_factory_interface;
use core_calendar\local\event\strategies\raw_event_retrieval_strategy_interface;
/**
* Event vault class.
*
* This class will handle interacting with the database layer to retrieve
* the records. This is required to house the complex logic required for
* pagination because it's not a one-to-one mapping between database records
* and users.
*
* This is a repository. It's called a vault to reduce confusion because
* Moodle has already taken the name repository. Vault is cooler anyway.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_vault implements event_vault_interface {
/**
* @var event_factory_interface $factory Factory for creating events.
*/
protected $factory;
/**
* @var raw_event_retrieval_strategy_interface $retrievalstrategy Strategy for getting events from the DB.
*/
protected $retrievalstrategy;
/**
* Create an event vault.
*
* @param event_factory_interface $factory An event factory
* @param raw_event_retrieval_strategy_interface $retrievalstrategy
*/
public function __construct(
event_factory_interface $factory,
raw_event_retrieval_strategy_interface $retrievalstrategy
) {
$this->factory = $factory;
$this->retrievalstrategy = $retrievalstrategy;
}
public function get_event_by_id($id) {
global $DB;
if ($record = $DB->get_record('event', ['id' => $id])) {
return $this->transform_from_database_record($record);
} else {
return false;
}
}
public function get_events(
$timestartfrom = null,
$timestartto = null,
$timesortfrom = null,
$timesortto = null,
event_interface $timestartafterevent = null,
event_interface $timesortafterevent = null,
$limitnum = 20,
$type = null,
array $usersfilter = null,
array $groupsfilter = null,
array $coursesfilter = null,
array $categoriesfilter = null,
$withduration = true,
$ignorehidden = true,
callable $filter = null,
?string $searchvalue = null
) {
$fromquery = function($field, $timefrom, $lastseenmethod, $afterevent, $withduration) {
if (!$timefrom) {
return false;
}
return $this->timefield_pagination_from(
$field,
$timefrom,
$afterevent ? $afterevent->get_times()->{$lastseenmethod}()->getTimestamp() : null,
$afterevent ? $afterevent->get_id() : null,
$withduration
);
};
$toquery = function($field, $timeto, $lastseenmethod, $afterevent) {
if (!$timeto) {
return false;
}
return $this->timefield_pagination_to(
$field,
$timeto,
$afterevent ? $afterevent->get_times()->{$lastseenmethod}()->getTimestamp() : null,
$afterevent ? $afterevent->get_id() : null
);
};
$timesortfromquery = $fromquery('timesort', $timesortfrom, 'get_sort_time', $timesortafterevent, $withduration);
$timesorttoquery = $toquery('timesort', $timesortto, 'get_sort_time', $timesortafterevent);
$timestartfromquery = $fromquery('timestart', $timestartfrom, 'get_start_time', $timestartafterevent, $withduration);
$timestarttoquery = $toquery('timestart', $timestartto, 'get_start_time', $timestartafterevent);
if (($timesortto && !$timesorttoquery) || ($timestartto && !$timestarttoquery)) {
return [];
}
$searchquery = $this->generate_search_subquery($searchvalue);
$params = array_merge(
$type ? ['type' => $type] : [],
$timesortfromquery ? $timesortfromquery['params'] : [],
$timesorttoquery ? $timesorttoquery['params'] : [],
$timestartfromquery ? $timestartfromquery['params'] : [],
$timestarttoquery ? $timestarttoquery['params'] : [],
$searchquery ? $searchquery['params'] : [],
);
$where = array_merge(
$type ? ['type = :type'] : [],
$timesortfromquery ? $timesortfromquery['where'] : [],
$timesorttoquery ? $timesorttoquery['where'] : [],
$timestartfromquery ? $timestartfromquery['where'] : [],
$timestarttoquery ? $timestarttoquery['where'] : [],
$searchquery ? [$searchquery['where']] : [],
);
$offset = 0;
$events = [];
while ($records = array_values($this->retrievalstrategy->get_raw_events(
$usersfilter,
$groupsfilter,
$coursesfilter,
$categoriesfilter,
$where,
$params,
"COALESCE(e.timesort, e.timestart) ASC, e.id ASC",
$offset,
$limitnum,
$ignorehidden
))) {
foreach ($records as $record) {
if ($event = $this->transform_from_database_record($record)) {
$filtertest = $filter ? $filter($event) : true;
if ($event && $filtertest) {
$events[] = $event;
}
if (count($events) == $limitnum) {
// We've got all of the events so break both loops.
break 2;
}
}
}
if (!$limitnum) {
break;
} else {
$offset += $limitnum;
}
}
return $events;
}
public function get_action_events_by_timesort(
\stdClass $user,
$timesortfrom = null,
$timesortto = null,
event_interface $afterevent = null,
$limitnum = 20,
$limittononsuspendedevents = false,
?string $searchvalue = null
) {
$courseids = array_map(function($course) {
return $course->id;
}, enrol_get_all_users_courses($user->id, $limittononsuspendedevents));
$groupids = array_reduce($courseids, function($carry, $courseid) use ($user) {
$groupings = groups_get_user_groups($courseid, $user->id);
// Grouping 0 is all groups.
return array_merge($carry, $groupings[0]);
}, []);
// Always include the site events.
$courseids = $courseids ? array_merge($courseids, [SITEID]) : $courseids;
return $this->get_events(
null,
null,
$timesortfrom,
$timesortto,
null,
$afterevent,
$limitnum,
CALENDAR_EVENT_TYPE_ACTION,
[$user->id],
$groupids ? $groupids : null,
$courseids ? $courseids : null,
null, // All categories.
true,
true,
function ($event) {
return $event instanceof action_event_interface;
},
$searchvalue
);
}
public function get_action_events_by_course(
\stdClass $user,
\stdClass $course,
$timesortfrom = null,
$timesortto = null,
event_interface $afterevent = null,
$limitnum = 20,
?string $searchvalue = null
) {
$groupings = groups_get_user_groups($course->id, $user->id);
return array_values(
$this->get_events(
null,
null,
$timesortfrom,
$timesortto,
null,
$afterevent,
$limitnum,
CALENDAR_EVENT_TYPE_ACTION,
[$user->id],
$groupings[0] ? $groupings[0] : null,
[$course->id],
[],
true,
true,
function ($event) use ($course) {
return $event instanceof action_event_interface && $event->get_course()->get('id') == $course->id;
},
$searchvalue
)
);
}
/**
* Generates SQL subquery and parameters for 'from' pagination.
*
* @param string $field
* @param int $timefrom
* @param int|null $lastseentime
* @param int|null $lastseenid
* @param bool $withduration
* @return array
*/
protected function timefield_pagination_from(
$field,
$timefrom,
$lastseentime = null,
$lastseenid = null,
$withduration = true
) {
$where = '';
$params = [];
if ($lastseentime && $lastseentime >= $timefrom) {
$where = '((timesort = :timefrom1 AND e.id > :timefromid) OR timesort > :timefrom2)';
if ($field === 'timestart') {
$where = '((timestart = :timefrom1 AND e.id > :timefromid) OR timestart > :timefrom2' .
($withduration ? ' OR timestart + timeduration > :timefrom3' : '') . ')';
}
$params['timefromid'] = $lastseenid;
$params['timefrom1'] = $lastseentime;
$params['timefrom2'] = $lastseentime;
$params['timefrom3'] = $lastseentime;
} else {
$where = 'timesort >= :timefrom';
if ($field === 'timestart') {
$where = '(timestart >= :timefrom' .
($withduration ? ' OR timestart + timeduration > :timefrom2' : '') . ')';
}
$params['timefrom'] = $timefrom;
$params['timefrom2'] = $timefrom;
}
return ['where' => [$where], 'params' => $params];
}
/**
* Generates SQL subquery and parameters for 'to' pagination.
*
* @param string $field
* @param int $timeto
* @param int|null $lastseentime
* @param int|null $lastseenid
* @return array|bool
*/
protected function timefield_pagination_to(
$field,
$timeto,
$lastseentime = null,
$lastseenid = null
) {
$where = [];
$params = [];
if ($lastseentime && $lastseentime > $timeto) {
// The last seen event from this set is after the time sort range which
// means all events in this range have been seen, so we can just return
// early here.
return false;
} else if ($lastseentime && $lastseentime == $timeto) {
$where[] = '((timesort = :timeto1 AND e.id > :timetoid) OR timesort < :timeto2)';
if ($field === 'timestart') {
$where[] = '((timestart = :timeto1 AND e.id > :timetoid) OR timestart < :timeto2)';
}
$params['timetoid'] = $lastseenid;
$params['timeto1'] = $timeto;
$params['timeto2'] = $timeto;
} else {
$where[] = ($field === 'timestart' ? 'timestart' : 'timesort') . ' <= :timeto';
$params['timeto'] = $timeto;
}
return ['where' => $where, 'params' => $params];
}
/**
* Create an event from a database record.
*
* @param \stdClass $record The database record
* @return event_interface|null
*/
protected function transform_from_database_record(\stdClass $record) {
return $this->factory->create_instance($record);
}
/**
* Fetches records from DB.
*
* @param int $userid
* @param array|null $whereconditions
* @param array $whereparams
* @param string $ordersql
* @param int $offset
* @param int $limitnum
* @return array
*/
protected function get_from_db(
$userid,
$whereconditions,
$whereparams,
$ordersql,
$offset,
$limitnum
) {
return array_values(
$this->retrievalstrategy->get_raw_events(
[$userid],
null,
null,
null,
$whereconditions,
$whereparams,
$ordersql,
$offset,
$limitnum
)
);
}
/**
* Generates SQL subquery and parameters for event searching.
*
* @param string|null $searchvalue Search value.
* @return array|null
*/
protected function generate_search_subquery(?string $searchvalue): ?array {
global $CFG, $DB;
if (!$searchvalue) {
return null;
}
$parts = preg_split('/\s+/', $searchvalue);
$wherecoursenameconditions = [];
$whereactivitynameconditions = [];
foreach ($parts as $index => $part) {
// Course name searching.
$wherecoursenameconditions[] = $DB->sql_like('c.fullname', ':cfullname' . $index, false);
$params['cfullname'. $index] = '%' . $DB->sql_like_escape($part) . '%';
// Activity name searching.
$whereactivitynameconditions[] = $DB->sql_like('e.name', ':eventname' . $index, false);
$params['eventname'. $index] = '%' . $DB->sql_like_escape($part) . '%';
}
// Activity type searching.
$whereconditions[] = $DB->sql_like('e.modulename', ':modulename', false);
$params['modulename'] = '%' . $DB->sql_like_escape($searchvalue) . '%';
// Activity type searching (localised type name).
require_once($CFG->dirroot . '/course/lib.php');
// Search in modules' singular and plural names.
$modules = array_keys(array_merge(
preg_grep('/' . $searchvalue . '/i', get_module_types_names()) ?: [],
preg_grep('/' . $searchvalue . '/i', get_module_types_names(true)) ?: [],
));
if ($modules) {
[$insql, $inparams] = $DB->get_in_or_equal($modules, SQL_PARAMS_NAMED, 'exactmodulename');
$whereconditions[] = 'e.modulename ' . $insql;
$params += $inparams;
}
$whereclause = '(';
$whereclause .= implode(' OR ', $whereconditions);
$whereclause .= ' OR (' . implode(' AND ', $wherecoursenameconditions) . ')';
$whereclause .= ' OR (' . implode(' AND ', $whereactivitynameconditions) . ')';
$whereclause .= ')';
return ['where' => $whereclause, 'params' => $params];
}
}
@@ -0,0 +1,137 @@
<?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/>.
/**
* Event vault interface
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\data_access;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\entities\event_interface;
/**
* Interface for an event vault class
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface event_vault_interface {
/**
* Retrieve an event for the given id.
*
* @param int $id The event id
* @return event_interface|false
*/
public function get_event_by_id($id);
/**
* Get all events restricted by various parameters, taking in to account user and group overrides.
*
* @param int|null $timestartfrom Events with timestart from this value (inclusive).
* @param int|null $timestartto Events with timestart until this value (inclusive).
* @param int|null $timesortfrom Events with timesort from this value (inclusive).
* @param int|null $timesortto Events with timesort until this value (inclusive).
* @param event_interface|null $timestartafterevent Restrict the events in the timestart range to ones after this one.
* @param event_interface|null $timesortafterevent Restrict the events in the timesort range to ones after this one.
* @param int $limitnum Return at most this number of events.
* @param int|null $type Return only events of this type.
* @param array|null $usersfilter Return only events for these users.
* @param array|null $groupsfilter Return only events for these groups.
* @param array|null $coursesfilter Return only events for these courses.
* @param bool $withduration If true return only events starting within specified
* timestart otherwise return in progress events as well.
* @param bool $ignorehidden If true don't return hidden events.
* @param callable|null $filter Additional logic to filter out unwanted events.
* Must return true to keep the event, false to discard it.
* @param string|null $searchvalue The value a user wishes to search against
* @return event_interface[] Array of event_interfaces.
*/
public function get_events(
$timestartfrom = null,
$timestartto = null,
$timesortfrom = null,
$timesortto = null,
event_interface $timestartafterevent = null,
event_interface $timesortafterevent = null,
$limitnum = 20,
$type = null,
array $usersfilter = null,
array $groupsfilter = null,
array $coursesfilter = null,
array $categoriesfilter = null,
$withduration = true,
$ignorehidden = true,
callable $filter = null,
?string $searchvalue = null
);
/**
* Retrieve an array of events for the given user and time constraints.
*
* If using this function for pagination then you can provide the last event that you've seen
* ($afterevent) and it will be used to appropriately offset the result set so that you don't
* receive the same events again.
* @param \stdClass $user The user for whom the events belong
* @param int $timesortfrom Events with timesort from this value (inclusive)
* @param int $timesortto Events with timesort until this value (inclusive)
* @param event_interface $afterevent Only return events after this one
* @param int $limitnum Return at most this number of events
* @param bool $lmittononsuspendedevents Limit course events to courses the user is active in (not suspended).
* @param string|null $searchvalue The value a user wishes to search against
* @return event_interface
*/
public function get_action_events_by_timesort(
\stdClass $user,
$timesortfrom,
$timesortto,
event_interface $afterevent,
$limitnum,
$limittononsuspendedevents,
?string $searchvalue = null
);
/**
* Retrieve an array of events for the given user filtered by the course and time constraints.
*
* If using this function for pagination then you can provide the last event that you've seen
* ($afterevent) and it will be used to appropriately offset the result set so that you don't
* receive the same events again.
*
* @param \stdClass $user The user for whom the events belong
* @param \stdClass $course The course to filter by
* @param int $timesortfrom Events with timesort from this value (inclusive)
* @param int $timesortto Events with timesort until this value (inclusive)
* @param event_interface $afterevent Only return events after this one
* @param int $limitnum Return at most this number of events
* @param string|null $searchvalue The value a user wishes to search against
* @return action_event_interface
*/
public function get_action_events_by_course(
\stdClass $user,
\stdClass $course,
$timesortfrom,
$timesortto,
event_interface $afterevent,
$limitnum,
?string $searchvalue = null
);
}
@@ -0,0 +1,136 @@
<?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 action event class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\factories\action_factory_interface;
/**
* Class representing an actionable event.
*
* An actionable event can be thought of as an embellished event. That is,
* it does everything a regular event does, but has some extra information
* attached to it. For example, the URL a user needs to visit to complete
* an action, the number of actionable items, etc.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class action_event implements action_event_interface {
/**
* @var event_interface $event The event to delegate to.
*/
protected $event;
/**
* @var action_interface $action The action associated with this event.
*/
protected $action;
/**
* @var proxy_interface $category Category for this event.
*/
protected $category;
/**
* Constructor.
*
* @param event_interface $event The event to delegate to.
* @param action_interface $action The action associated with this event.
*/
public function __construct(event_interface $event, action_interface $action) {
$this->event = $event;
$this->action = $action;
}
public function get_id() {
return $this->event->get_id();
}
public function get_name() {
return $this->event->get_name();
}
public function get_description() {
return $this->event->get_description();
}
public function get_location() {
return $this->event->get_location();
}
public function get_category() {
return $this->event->get_category();
}
public function get_course() {
return $this->event->get_course();
}
public function get_course_module() {
return $this->event->get_course_module();
}
public function get_group() {
return $this->event->get_group();
}
public function get_user() {
return $this->event->get_user();
}
public function get_type() {
return $this->event->get_type();
}
public function get_times() {
return $this->event->get_times();
}
public function get_repeats() {
return $this->event->get_repeats();
}
public function get_subscription() {
return $this->event->get_subscription();
}
public function is_visible() {
return $this->event->is_visible();
}
public function get_action() {
return $this->action;
}
/**
* Event component
* @return string
*/
public function get_component() {
return $this->event->get_component();
}
}
@@ -0,0 +1,42 @@
<?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 action event interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for an action event class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface action_event_interface extends event_interface {
/**
* Get the action event's action.
*
* @return action_interface
*/
public function get_action();
}
@@ -0,0 +1,63 @@
<?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/>.
/**
* Action interface.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for a action class.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface action_interface {
/**
* Get the name of the action.
*
* @return string
*/
public function get_name();
/**
* Get the URL of the action.
*
* @return \moodle_url
*/
public function get_url();
/**
* Get the number of items that need actioning.
*
* @return int
*/
public function get_item_count();
/**
* Get the actions actionability.
*
* @return bool
*/
public function is_actionable();
}
@@ -0,0 +1,231 @@
<?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 event class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\proxies\proxy_interface;
use core_calendar\local\event\value_objects\description_interface;
use core_calendar\local\event\value_objects\times_interface;
/**
* Class representing a calendar event.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event implements event_interface {
/**
* @var int $id The event's id in the database.
*/
protected $id;
/**
* @var string $name The name of this event.
*/
protected $name;
/**
* @var description_interface $description Description for this event.
*/
protected $description;
/**
* @var string $location Location of this event.
*/
protected $location;
/**
* @var proxy_interface $category Category for this event.
*/
protected $category;
/**
* @var proxy_interface $course Course for this event.
*/
protected $course;
/**
* @var proxy_interface $group Group for this event.
*/
protected $group;
/**
* @var proxy_interface $user User for this event.
*/
protected $user;
/**
* @var event_collection_interface $repeats Collection of repeat events.
*/
protected $repeats;
/**
* @var proxy_interface $coursemodule The course module that created this event.
*/
protected $coursemodule;
/**
* @var string type The type of this event.
*/
protected $type;
/**
* @var times_interface $times The times for this event.
*/
protected $times;
/**
* @var bool $visible The visibility of this event.
*/
protected $visible;
/**
* @var string $component
*/
protected $component;
/**
* @var proxy_interface $subscription Subscription for this event.
*/
protected $subscription;
/**
* Constructor.
*
* @param int $id The event's ID in the database.
* @param string $name The event's name.
* @param description_interface $description The event's description.
* @param proxy_interface|null $category The category associated with the event.
* @param proxy_interface|null $course The course associated with the event.
* @param proxy_interface|null $group The group associated with the event.
* @param proxy_interface|null $user The user associated with the event.
* @param event_collection_interface|null $repeats Collection of repeat events.
* @param proxy_interface|null $coursemodule The course module that created the event.
* @param string $type The event's type.
* @param times_interface $times The times associated with the event.
* @param bool $visible The event's visibility. True for visible, false for invisible.
* @param proxy_interface|null $subscription The event's subscription.
* @param string $location The event's location.
* @param string $component The event's component.
*/
public function __construct(
$id,
$name,
description_interface $description,
?proxy_interface $category,
?proxy_interface $course,
?proxy_interface $group,
?proxy_interface $user,
?event_collection_interface $repeats,
?proxy_interface $coursemodule,
$type,
times_interface $times,
$visible,
proxy_interface $subscription = null,
$location = null,
$component = null
) {
$this->id = $id;
$this->name = $name;
$this->description = $description;
$this->location = $location;
$this->category = $category;
$this->course = $course;
$this->group = $group;
$this->user = $user;
$this->repeats = $repeats;
$this->coursemodule = $coursemodule;
$this->type = $type;
$this->times = $times;
$this->visible = $visible;
$this->subscription = $subscription;
$this->component = $component;
}
public function get_id() {
return $this->id;
}
public function get_name() {
return $this->name;
}
public function get_description() {
return $this->description;
}
public function get_location() {
return $this->location;
}
public function get_category() {
return $this->category;
}
public function get_course() {
return $this->course;
}
public function get_course_module() {
return $this->coursemodule;
}
public function get_group() {
return $this->group;
}
public function get_user() {
return $this->user;
}
public function get_type() {
return $this->type;
}
public function get_times() {
return $this->times;
}
public function get_repeats() {
return $this->repeats;
}
public function get_subscription() {
return $this->subscription;
}
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;
}
}
@@ -0,0 +1,49 @@
<?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/>.
/**
* Interface for an event collection class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for an event collection class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface event_collection_interface extends \IteratorAggregate {
/**
* Get the event collection's ID.
*
* @return int
*/
public function get_id();
/**
* Get the total number of repeats in the collection.
*
* @return int
*/
public function get_num();
}
@@ -0,0 +1,142 @@
<?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 event interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
use core_calendar\local\event\proxies\proxy_interface;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for an event class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface event_interface {
/**
* Get the event's ID.
*
* @return integer
*/
public function get_id();
/**
* Get the event's name.
*
* @return string
*/
public function get_name();
/**
* Get the event's description.
*
* @return description_interface
*/
public function get_description();
/**
* Get the event's location.
*
* @return location_interface
*/
public function get_location();
/**
* Get the category object associated with the event.
*
* @return proxy_interface
*/
public function get_category();
/**
* Get the course object associated with the event.
*
* @return proxy_interface
*/
public function get_course();
/**
* Get the course module object that created the event.
*
* @return proxy_interface
*/
public function get_course_module();
/**
* Get the group object associated with the event.
*
* @return proxy_interface
*/
public function get_group();
/**
* Get the user object associated with the event.
*
* @return proxy_interface
*/
public function get_user();
/**
* Get the event's type.
*
* @return string
*/
public function get_type();
/**
* Get the times associated with the event.
*
* @return times_interface
*/
public function get_times();
/**
* Get repeats of this event or null if the event has no
* repeats.
*
* @return event_collection_interface|null
*/
public function get_repeats();
/**
* Get the event's subscription.
*
* @return proxy_interface
*/
public function get_subscription();
/**
* Get the event's visibility.
*
* @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();
}
@@ -0,0 +1,151 @@
<?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/>.
/**
* Event collection class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\entities;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\factories\event_factory_interface;
/**
* Class representing a collection of repeat events.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class repeat_event_collection implements event_collection_interface {
/**
* @var int DB_QUERY_LIMIT How many records to pull from the DB at once.
*/
const DB_QUERY_LIMIT = 100;
/**
* @var int $parentid The ID of the event which the events in this collection are repeats of.
*/
protected $parentid;
/**
* @var \stdClass $parentrecord The parent event record from the database.
*/
protected $parentrecord;
/**
* @var event_factory_interface $factory Event factory.
*/
protected $factory;
/**
* @var int $num Total number of events that could be retrieved by this collection.
*/
protected $num;
/**
* Constructor.
*
* @param stdClass $dbrow The event dbrow that is being repeated.
* @param event_factory_interface $factory Event factory.
*/
public function __construct($dbrow, event_factory_interface $factory) {
$eventid = $dbrow->id;
$repeatid = $dbrow->repeatid;
if (empty($repeatid)) {
$this->parentrecord = $dbrow;
$this->parentid = $eventid;
} else {
$this->parentid = $repeatid;
}
if ($eventid === $repeatid) {
// This means the record we've been given is the parent
// record.
$this->parentrecord = $dbrow;
}
$this->factory = $factory;
}
public function get_id() {
return $this->parentid;
}
public function get_num() {
global $DB;
// Subtract one because the original event has repeatid = its own id.
return $this->num = max(
isset($this->num) ? $this->num : ($DB->count_records('event', ['repeatid' => $this->parentid]) - 1),
0
);
}
public function getIterator(): \Traversable {
$parentrecord = $this->get_parent_record();
foreach ($this->load_event_records() as $eventrecords) {
foreach ($eventrecords as $eventrecord) {
// In the case of the repeat event having unset information, fallback on the parent.
yield $this->factory->create_instance((object)array_merge((array)$parentrecord, (array)$eventrecord));
}
}
}
/**
* Return the parent DB record.
*
* @return \stdClass
*/
protected function get_parent_record() {
global $DB;
if (!isset($this->parentrecord)) {
$this->parentrecord = $DB->get_record('event', ['id' => $this->parentid]);
}
return $this->parentrecord;
}
/**
* Generate more event records.
*
* @param int $start Start offset.
* @return \stdClass[]
*/
protected function load_event_records($start = 0) {
global $DB;
while ($records = $DB->get_records_select(
'event',
'id <> :parentid AND repeatid = :repeatid',
[
'parentid' => $this->parentid,
'repeatid' => $this->parentid,
],
'id ASC',
'*',
$start,
self::DB_QUERY_LIMIT
)) {
yield $records;
$start += self::DB_QUERY_LIMIT;
}
}
}
@@ -0,0 +1,36 @@
<?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/>.
/**
* Invalid callback exception.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* Invalid callback exception.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class invalid_callback_exception extends \moodle_exception {
}
@@ -0,0 +1,36 @@
<?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/>.
/**
* General invalid parameter exception.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* General invalid parameter exception.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class invalid_parameter_exception extends \moodle_exception {
}
@@ -0,0 +1,36 @@
<?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/>.
/**
* Invalid limit parameter exception.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* Invalid limit parameter exception.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class limit_invalid_parameter_exception extends invalid_parameter_exception {
}
@@ -0,0 +1,36 @@
<?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/>.
/**
* Member does not exist exception.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* Member does not exist exception.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class member_does_not_exist_exception extends \moodle_exception {
}
@@ -0,0 +1,36 @@
<?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/>.
/**
* Invalid timesort parameter exception.
*
* @package core_calendar
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\exceptions;
defined('MOODLE_INTERNAL') || die();
/**
* Invalid timesort parameter exception.
*
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class timesort_invalid_parameter_exception extends invalid_parameter_exception {
}
@@ -0,0 +1,40 @@
<?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/>.
/**
* Action factory interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\factories;
defined('MOODLE_INTERNAL') || die();
interface action_factory_interface {
/**
* Creates an instance of an action.
*
* @param string $name The action's name.
* @param \moodle_url $url The action's URL.
* @param int $itemcount The number of items needing action.
* @param bool $actionable The action's actionability.
* @return \core_calendar\local\event\entities\action_interface The action.
*/
public function create_instance($name, \moodle_url $url, $itemcount, $actionable);
}
@@ -0,0 +1,207 @@
<?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/>.
/**
* Abstract event factory.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\factories;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\entities\event;
use core_calendar\local\event\entities\repeat_event_collection;
use core_calendar\local\event\exceptions\invalid_callback_exception;
use core_calendar\local\event\proxies\cm_info_proxy;
use core_calendar\local\event\proxies\coursecat_proxy;
use core_calendar\local\event\proxies\std_proxy;
use core_calendar\local\event\value_objects\event_description;
use core_calendar\local\event\value_objects\event_times;
use core_calendar\local\event\entities\event_interface;
/**
* Abstract factory for creating calendar events.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class event_abstract_factory implements event_factory_interface {
/**
* @var callable $actioncallbackapplier Function to apply component action callbacks.
*/
protected $actioncallbackapplier;
/**
* @var callable $visibilitycallbackapplier Function to apply component visibility callbacks.
*/
protected $visibilitycallbackapplier;
/**
* @var array Course cache for use with get_course_cached.
*/
protected $coursecachereference;
/**
* @var array Module cache reference for use with get_module_cached.
*/
protected $modulecachereference;
/**
* @var callable Bail out check for create_instance.
*/
protected $bailoutcheck;
/**
* Applies component actions to the event.
*
* @param event_interface $event The event to be updated.
* @return event_interface The potentially modified event.
*/
abstract protected function apply_component_action(event_interface $event);
/**
* Exposes the event (or not).
*
* @param event_interface $event The event to potentially expose.
* @return event_interface|null The exposed event or null.
*/
abstract protected function expose_event(event_interface $event);
/**
* Constructor.
*
* @param callable $actioncallbackapplier Function to apply component action callbacks.
* @param callable $visibilitycallbackapplier Function to apply component visibility callbacks.
* @param callable $bailoutcheck Function to test if we can return null early.
* @param array $coursecachereference Cache to use with get_course_cached.
* @param array $modulecachereference Cache to use with get_module_cached.
*/
public function __construct(
callable $actioncallbackapplier,
callable $visibilitycallbackapplier,
callable $bailoutcheck,
array &$coursecachereference,
array &$modulecachereference
) {
$this->actioncallbackapplier = $actioncallbackapplier;
$this->visibilitycallbackapplier = $visibilitycallbackapplier;
$this->bailoutcheck = $bailoutcheck;
$this->coursecachereference = &$coursecachereference;
$this->modulecachereference = &$modulecachereference;
}
public function create_instance(\stdClass $dbrow) {
if ($dbrow->modulename && $dbrow->instance && $dbrow->courseid == 0) {
// Some events (for example user overrides) may contain module instance but not course id. Find course id.
$cm = calendar_get_module_cached($this->modulecachereference, $dbrow->modulename, $dbrow->instance);
$dbrow->courseid = $cm->course;
}
$bailcheck = $this->bailoutcheck;
$bail = $bailcheck($dbrow);
if (!is_bool($bail)) {
throw new invalid_callback_exception(
'Bail check must return true or false'
);
}
if ($bail) {
return null;
}
$category = null;
$course = null;
$group = null;
$user = null;
$module = null;
$subscription = null;
$component = null;
if ($dbrow->modulename && $dbrow->instance) {
$module = new cm_info_proxy($dbrow->modulename, $dbrow->instance, $dbrow->courseid);
}
if ($dbrow->categoryid) {
$category = new coursecat_proxy($dbrow->categoryid);
}
$course = new std_proxy($dbrow->courseid, function($id) {
return calendar_get_course_cached($this->coursecachereference, $id);
});
if ($dbrow->groupid) {
$group = new std_proxy($dbrow->groupid, function($id) {
return calendar_get_group_cached($id);
});
}
if ($dbrow->userid) {
$user = new std_proxy($dbrow->userid, function($id) {
global $DB;
return $DB->get_record('user', ['id' => $id]);
});
}
if ($dbrow->subscriptionid) {
$subscription = new std_proxy($dbrow->subscriptionid, function($id) {
return calendar_get_subscription($id);
});
}
if (!empty($dbrow->repeatid)) {
$repeatcollection = new repeat_event_collection($dbrow, $this);
} else {
$repeatcollection = null;
}
if (!empty($dbrow->component)) {
$component = $dbrow->component;
}
$event = new event(
$dbrow->id,
$dbrow->name,
new event_description($dbrow->description, $dbrow->format),
$category,
$course,
$group,
$user,
$repeatcollection,
$module,
$dbrow->eventtype,
new event_times(
(new \DateTimeImmutable())->setTimestamp($dbrow->timestart),
(new \DateTimeImmutable())->setTimestamp($dbrow->timestart + $dbrow->timeduration),
(new \DateTimeImmutable())->setTimestamp($dbrow->timesort ? $dbrow->timesort : $dbrow->timestart),
(new \DateTimeImmutable())->setTimestamp($dbrow->timemodified),
(new \DateTimeImmutable())->setTimestamp($dbrow->timesort ? usergetmidnight($dbrow->timesort) : 0)
),
!empty($dbrow->visible),
$subscription,
$dbrow->location,
$component
);
$isactionevent = !empty($dbrow->type) && $dbrow->type == CALENDAR_EVENT_TYPE_ACTION;
return $isactionevent ? $this->expose_event($this->apply_component_action($event)) : $event;
}
}
@@ -0,0 +1,62 @@
<?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/>.
/**
* Event factory class.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\factories;
defined('MOODLE_INTERNAL') || die();
use core_calendar\local\event\exceptions\invalid_callback_exception;
use core_calendar\local\event\entities\event_interface;
/**
* Event factory class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class event_factory extends event_abstract_factory {
protected function apply_component_action(event_interface $event) {
$callbackapplier = $this->actioncallbackapplier;
$callbackresult = $callbackapplier($event);
if (!$callbackresult instanceof event_interface) {
throw new invalid_callback_exception(
'Event factory action callback applier must return an instance of event_interface');
}
return $callbackresult;
}
protected function expose_event(event_interface $event) {
$callbackapplier = $this->visibilitycallbackapplier;
$callbackresult = $callbackapplier($event);
if (!is_bool($callbackresult)) {
throw new invalid_callback_exception('Event factory visibility callback applier must return true or false');
}
return $callbackresult === true ? $event : null;
}
}
@@ -0,0 +1,43 @@
<?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/>.
/**
* Event factory interface.
*
* @package core_calendar
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_calendar\local\event\factories;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for an event factory class.
*
* @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface event_factory_interface {
/**
* Creates an instance of an event.
*
* @param \stdClass $dbrow The event row from the database.
* @return \core_calendar\local\event\entities\event_interface
*/
public function create_instance(\stdClass $dbrow);
}

Some files were not shown because too many files have changed in this diff Show More