mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 22:08:20 +01:00
Merge branch 'MDL-59393-master-2' of git://github.com/ryanwyllie/moodle
This commit is contained in:
commit
6ca4e64606
2
calendar/amd/build/calendar.min.js
vendored
2
calendar/amd/build/calendar.min.js
vendored
@ -1 +1 @@
|
|||||||
define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){var n={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']"},o=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},p=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?'<a href="'+a.url+'">'+b+"</a>":b})},q=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=o(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=p(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},r=function(a){var b=a.find(n.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},[a,n.NEW_EVENT_BUTTON])},s=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){m.reloadCurrentMonth()}),d.on(l.updated,function(){window.location.reload()}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},t=function(){var b=a(n.ROOT);b.on("click",n.EVENT_LINK,function(b){b.preventDefault();var c=a(this).attr("data-event-id");q(c)});var c=r(b);s(b,c)};return{init:function(){m.init(),t()}}});
|
define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){var n={ROOT:"[data-region='calendar']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']",DAY_CONTENT:"[data-region='day-content']",LOADING_ICON:".loading-icon"},o=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},p=function(a){return c.get_string("subsource","core_calendar",a).then(function(b){return a.url?'<a href="'+a.url+'">'+b+"</a>":b})},q=function(b){k.getEventById(b).then(function(c){if(!c.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+b);var d=c.event,e=o(d.eventtype);if(d.displayeventsource){d.subscription=JSON.parse(d.subscription);var f={url:d.subscription.url,name:d.subscription.name},g=p(f);return a.when(e,g).then(function(a,b){return d.eventtype=a,d.source=b,d})}return e.then(function(a){return d.eventtype=a,d})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},r=function(b,c,f,g){var h=c.attr("data-event-id"),i=f.attr("data-day-timestamp"),j=g.attr("data-day-timestamp");i!=j&&d.render("core/loading",{}).then(function(a,b){f.find(n.DAY_CONTENT).addClass("hidden"),g.find(n.DAY_CONTENT).addClass("hidden"),d.appendNodeContents(f,a,b),d.appendNodeContents(g,a,b)}).then(function(){return k.updateEventStartDay(h,j)}).then(function(){a("body").trigger(l.eventMoved,[c,f,g])}).always(function(){var a=f.find(n.LOADING_ICON),b=g.find(n.LOADING_ICON);f.find(n.DAY_CONTENT).removeClass("hidden"),g.find(n.DAY_CONTENT).removeClass("hidden"),d.replaceNode(a,"",""),d.replaceNode(b,"","")}).fail(e.exception)},s=function(a){var b=a.find(n.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}},[a,n.NEW_EVENT_BUTTON])},t=function(b,c){var d=a("body");d.on(l.created,function(){window.location.reload()}),d.on(l.deleted,function(){m.reloadCurrentMonth()}),d.on(l.updated,function(){window.location.reload()}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),d.on(l.moveEvent,r),d.on(l.eventMoved,function(){window.location.reload()}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})})},u=function(){var b=a(n.ROOT);b.on("click",n.EVENT_LINK,function(b){b.preventDefault();var c=a(this).attr("data-event-id");q(c)});var c=s(b);t(b,c)};return{init:function(){m.init(),u()}}});
|
1
calendar/amd/build/drag_drop.min.js
vendored
Normal file
1
calendar/amd/build/drag_drop.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
define(["jquery","core_calendar/events"],function(a,b){var c={ROOT:"[data-region='calendar']",DRAGGABLE:'[draggable="true"]',DROP_ZONE:'[data-drop-zone="true"]',WEEK:'[data-region="month-view-week"]'},d="bg-primary",e=null,f=null,g=function(b,e,h){var i=a(b).closest(c.DROP_ZONE);if("undefined"==typeof h&&(h=f),e?i.addClass(d):i.removeClass(d),h--,h>0){var j=i.next();if(!j.length){var k=i.closest(c.WEEK).next();k.length&&(j=k.children(c.DROP_ZONE).first())}j.length&&g(j,e,h)}},h=function(b){var d=a(b.target);d.is("[data-event-id]")||(d=d.find("[data-event-id]")),e=d.attr("data-event-id");var g=c.ROOT+' [data-event-id="'+e+'"]';f=a(g).length,b.dataTransfer.effectAllowed="move",b.dataTransfer.dropEffect="move",b.dataTransfer.setData("text/plain",e),b.dropEffect="move"},i=function(a){a.preventDefault(),g(a.target,!0)},j=function(a){a.preventDefault(),g(a.target,!1)},k=function(d){d.preventDefault();var f=c.ROOT+' [data-event-id="'+e+'"]',h=a(f),i=h.closest(c.DROP_ZONE),j=a(d.target).closest(c.DROP_ZONE);g(d.target,!1),a("body").trigger(b.moveEvent,[h,i,j])};return{init:function(b){b=a(b),b.find(c.DRAGGABLE).each(function(a,b){b.addEventListener("dragstart",h,!0)}),b.find(c.DROP_ZONE).each(function(a,b){b.addEventListener("dragover",i,!0),b.addEventListener("dragleave",j,!0),b.addEventListener("drop",k,!0)})}}});
|
2
calendar/amd/build/events.min.js
vendored
2
calendar/amd/build/events.min.js
vendored
@ -1 +1 @@
|
|||||||
define([],function(){return{created:"calendar-events:created",deleted:"calendar-events:deleted",updated:"calendar-events:updated",editEvent:"calendar-events:edit_event",editActionEvent:"calendar-events:edit_action_event",monthChanged:"calendar-events:month_changed"}});
|
define([],function(){return{created:"calendar-events:created",deleted:"calendar-events:deleted",updated:"calendar-events:updated",editEvent:"calendar-events:edit_event",editActionEvent:"calendar-events:edit_action_event",eventMoved:"calendar-events:event_moved",monthChanged:"calendar-events:month_changed",moveEvent:"calendar-events:move_event"}});
|
2
calendar/amd/build/repository.min.js
vendored
2
calendar/amd/build/repository.min.js
vendored
@ -1 +1 @@
|
|||||||
define(["jquery","core/ajax"],function(a,b){var c=function(a){var c={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:1}]}};return b.call([c])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]},f=function(a,c){var d={methodname:"core_calendar_get_calendar_monthly_view",args:{time:a,courseid:c}};return b.call([d])[0]};return{getEventById:d,deleteEvent:c,submitCreateUpdateForm:e,getCalendarMonthData:f}});
|
define(["jquery","core/ajax"],function(a,b){var c=function(a){var c={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:1}]}};return b.call([c])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]},f=function(a,c){var d={methodname:"core_calendar_get_calendar_monthly_view",args:{time:a,courseid:c}};return b.call([d])[0]},g=function(a,c){var d={methodname:"core_calendar_update_event_start_day",args:{eventId:a,dayTimestamp:c}};return b.call([d])[0]};return{getEventById:d,deleteEvent:c,updateEventStartDay:g,submitCreateUpdateForm:e,getCalendarMonthData:f}});
|
@ -59,7 +59,9 @@ define([
|
|||||||
var SELECTORS = {
|
var SELECTORS = {
|
||||||
ROOT: "[data-region='calendar']",
|
ROOT: "[data-region='calendar']",
|
||||||
EVENT_LINK: "[data-action='view-event']",
|
EVENT_LINK: "[data-action='view-event']",
|
||||||
NEW_EVENT_BUTTON: "[data-action='new-event-button']"
|
NEW_EVENT_BUTTON: "[data-action='new-event-button']",
|
||||||
|
DAY_CONTENT: "[data-region='day-content']",
|
||||||
|
LOADING_ICON: '.loading-icon',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,6 +157,60 @@ define([
|
|||||||
}).fail(Notification.exception);
|
}).fail(Notification.exception);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {object} eventElement The jQuery element with the event id
|
||||||
|
* @param {object} 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, eventElement, originElement, destinationElement) {
|
||||||
|
var eventId = eventElement.attr('data-event-id');
|
||||||
|
var originTimestamp = originElement.attr('data-day-timestamp');
|
||||||
|
var destinationTimestamp = destinationElement.attr('data-day-timestamp');
|
||||||
|
|
||||||
|
// If the event has actually changed day.
|
||||||
|
if (originTimestamp != destinationTimestamp) {
|
||||||
|
Templates.render('core/loading', {})
|
||||||
|
.then(function(html, js) {
|
||||||
|
// First we show some loading icons in each of the days being affected.
|
||||||
|
originElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
|
||||||
|
destinationElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
|
||||||
|
Templates.appendNodeContents(originElement, html, js);
|
||||||
|
Templates.appendNodeContents(destinationElement, 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, [eventElement, originElement, destinationElement]);
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
.always(function() {
|
||||||
|
// Always remove the loading icons regardless of whether the update
|
||||||
|
// request was successful or not.
|
||||||
|
var originLoadingElement = originElement.find(SELECTORS.LOADING_ICON);
|
||||||
|
var destinationLoadingElement = destinationElement.find(SELECTORS.LOADING_ICON);
|
||||||
|
originElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
|
||||||
|
destinationElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
|
||||||
|
|
||||||
|
Templates.replaceNode(originLoadingElement, '', '');
|
||||||
|
Templates.replaceNode(destinationLoadingElement, '', '');
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
.fail(Notification.exception);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the event form modal for creating new events and
|
* Create the event form modal for creating new events and
|
||||||
* editing existing events.
|
* editing existing events.
|
||||||
@ -204,6 +260,12 @@ define([
|
|||||||
// Action events needs to be edit directly on the course module.
|
// Action events needs to be edit directly on the course module.
|
||||||
window.location.assign(url);
|
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() {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
eventFormModalPromise.then(function(modal) {
|
eventFormModalPromise.then(function(modal) {
|
||||||
// When something within the calendar tells us the user wants
|
// When something within the calendar tells us the user wants
|
||||||
|
206
calendar/amd/src/drag_drop.js
Normal file
206
calendar/amd/src/drag_drop.js
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// 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. This module
|
||||||
|
* unfortunately requires some state to be maintained because of the
|
||||||
|
* limitations of the HTML5 drag and drop API which means it can't
|
||||||
|
* be used multiple times with the current implementation.
|
||||||
|
*
|
||||||
|
* @module core_calendar/drag_drop
|
||||||
|
* @class drag_drop
|
||||||
|
* @package core_calendar
|
||||||
|
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'core_calendar/events'
|
||||||
|
],
|
||||||
|
function(
|
||||||
|
$,
|
||||||
|
CalendarEvents
|
||||||
|
) {
|
||||||
|
|
||||||
|
var SELECTORS = {
|
||||||
|
ROOT: "[data-region='calendar']",
|
||||||
|
DRAGGABLE: '[draggable="true"]',
|
||||||
|
DROP_ZONE: '[data-drop-zone="true"]',
|
||||||
|
WEEK: '[data-region="month-view-week"]',
|
||||||
|
};
|
||||||
|
var HOVER_CLASS = 'bg-primary';
|
||||||
|
|
||||||
|
// Unfortunately we are required to maintain some module
|
||||||
|
// level state due to the limitations of the HTML5 drag
|
||||||
|
// and drop API. Specifically the inability to pass data
|
||||||
|
// between the dragstate and dragover events handlers
|
||||||
|
// using the DataTransfer object in the event.
|
||||||
|
|
||||||
|
/** @var int eventId The event id being moved. */
|
||||||
|
var eventId = null;
|
||||||
|
/** @var int duration The number of days the event spans */
|
||||||
|
var duration = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the hover state for the event in the calendar to reflect
|
||||||
|
* which days the event will be moved to.
|
||||||
|
*
|
||||||
|
* 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} target The drag target element
|
||||||
|
* @param {bool} hovered If the target is hovered or not
|
||||||
|
* @param {int} count How many days to highlight (default to duration)
|
||||||
|
*/
|
||||||
|
var updateHoverState = function(target, hovered, count) {
|
||||||
|
var dropZone = $(target).closest(SELECTORS.DROP_ZONE);
|
||||||
|
if (typeof count === 'undefined') {
|
||||||
|
// This is how many days we need to highlight.
|
||||||
|
count = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hovered) {
|
||||||
|
dropZone.addClass(HOVER_CLASS);
|
||||||
|
} else {
|
||||||
|
dropZone.removeClass(HOVER_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 eventElement = $(e.target);
|
||||||
|
|
||||||
|
if (!eventElement.is('[data-event-id]')) {
|
||||||
|
eventElement = eventElement.find('[data-event-id]');
|
||||||
|
}
|
||||||
|
|
||||||
|
eventId = eventElement.attr('data-event-id');
|
||||||
|
|
||||||
|
var eventsSelector = SELECTORS.ROOT + ' [data-event-id="' + eventId + '"]';
|
||||||
|
duration = $(eventsSelector).length;
|
||||||
|
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
e.preventDefault();
|
||||||
|
updateHoverState(e.target, 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) {
|
||||||
|
e.preventDefault();
|
||||||
|
updateHoverState(e.target, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var eventElementSelector = SELECTORS.ROOT + ' [data-event-id="' + eventId + '"]';
|
||||||
|
var eventElement = $(eventElementSelector);
|
||||||
|
var origin = eventElement.closest(SELECTORS.DROP_ZONE);
|
||||||
|
var destination = $(e.target).closest(SELECTORS.DROP_ZONE);
|
||||||
|
|
||||||
|
updateHoverState(e.target, false);
|
||||||
|
$('body').trigger(CalendarEvents.moveEvent, [eventElement, origin, destination]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Initialise the event handlers for the drag events.
|
||||||
|
*/
|
||||||
|
init: function(root) {
|
||||||
|
root = $(root);
|
||||||
|
|
||||||
|
root.find(SELECTORS.DRAGGABLE).each(function(index, element) {
|
||||||
|
element.addEventListener('dragstart', dragstartHandler, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
root.find(SELECTORS.DROP_ZONE).each(function(index, element) {
|
||||||
|
element.addEventListener('dragover', dragoverHandler, true);
|
||||||
|
element.addEventListener('dragleave', dragleaveHandler, true);
|
||||||
|
element.addEventListener('drop', dropHandler, true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
@ -29,6 +29,8 @@ define([], function() {
|
|||||||
updated: 'calendar-events:updated',
|
updated: 'calendar-events:updated',
|
||||||
editEvent: 'calendar-events:edit_event',
|
editEvent: 'calendar-events:edit_event',
|
||||||
editActionEvent: 'calendar-events:edit_action_event',
|
editActionEvent: 'calendar-events:edit_action_event',
|
||||||
monthChanged: 'calendar-events:month_changed'
|
eventMoved: 'calendar-events:event_moved',
|
||||||
|
monthChanged: 'calendar-events:month_changed',
|
||||||
|
moveEvent: 'calendar-events:move_event'
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -103,9 +103,31 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
|
|||||||
return Ajax.call([request])[0];
|
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}
|
||||||
|
*/
|
||||||
|
var updateEventStartDay = function(eventId, dayTimestamp) {
|
||||||
|
var request = {
|
||||||
|
methodname: 'core_calendar_update_event_start_day',
|
||||||
|
args: {
|
||||||
|
eventId: eventId,
|
||||||
|
dayTimestamp: dayTimestamp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ajax.call([request])[0];
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getEventById: getEventById,
|
getEventById: getEventById,
|
||||||
deleteEvent: deleteEvent,
|
deleteEvent: deleteEvent,
|
||||||
|
updateEventStartDay: updateEventStartDay,
|
||||||
submitCreateUpdateForm: submitCreateUpdateForm,
|
submitCreateUpdateForm: submitCreateUpdateForm,
|
||||||
getCalendarMonthData: getCalendarMonthData
|
getCalendarMonthData: getCalendarMonthData
|
||||||
};
|
};
|
||||||
|
@ -44,16 +44,11 @@ class calendar_event_exporter extends event_exporter_base {
|
|||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected static function define_other_properties() {
|
protected static function define_other_properties() {
|
||||||
return [
|
|
||||||
'url' => ['type' => PARAM_URL],
|
$values = parent::define_other_properties();
|
||||||
'icon' => [
|
$values['url'] = ['type' => PARAM_URL];
|
||||||
'type' => event_icon_exporter::read_properties_definition(),
|
|
||||||
],
|
return $values;
|
||||||
'course' => [
|
|
||||||
'type' => course_summary_exporter::read_properties_definition(),
|
|
||||||
'optional' => true,
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
20
calendar/classes/external/event_exporter.php
vendored
20
calendar/classes/external/event_exporter.php
vendored
@ -51,7 +51,6 @@ class event_exporter extends event_exporter_base {
|
|||||||
|
|
||||||
$values = parent::define_other_properties();
|
$values = parent::define_other_properties();
|
||||||
|
|
||||||
$values['canedit'] = ['type' => PARAM_BOOL];
|
|
||||||
$values['displayeventsource'] = ['type' => PARAM_BOOL];
|
$values['displayeventsource'] = ['type' => PARAM_BOOL];
|
||||||
$values['subscription'] = [
|
$values['subscription'] = [
|
||||||
'type' => PARAM_RAW,
|
'type' => PARAM_RAW,
|
||||||
@ -94,8 +93,6 @@ class event_exporter extends event_exporter_base {
|
|||||||
require_once($CFG->dirroot.'/course/lib.php');
|
require_once($CFG->dirroot.'/course/lib.php');
|
||||||
|
|
||||||
$event = $this->event;
|
$event = $this->event;
|
||||||
$legacyevent = container::get_event_mapper()->from_event_to_legacy_event($event);
|
|
||||||
|
|
||||||
$context = $this->related['context'];
|
$context = $this->related['context'];
|
||||||
$values['isactionevent'] = false;
|
$values['isactionevent'] = false;
|
||||||
$values['iscourseevent'] = false;
|
$values['iscourseevent'] = false;
|
||||||
@ -133,14 +130,11 @@ class event_exporter extends event_exporter_base {
|
|||||||
$values['course'] = $coursesummaryexporter->export($output);
|
$values['course'] = $coursesummaryexporter->export($output);
|
||||||
}
|
}
|
||||||
|
|
||||||
$values['canedit'] = calendar_edit_event_allowed($legacyevent);
|
|
||||||
$values['candelete'] = calendar_delete_event_allowed($legacyevent);
|
|
||||||
|
|
||||||
// Handle event subscription.
|
// Handle event subscription.
|
||||||
$values['subscription'] = null;
|
$values['subscription'] = null;
|
||||||
$values['displayeventsource'] = false;
|
$values['displayeventsource'] = false;
|
||||||
if (!empty($legacyevent->subscriptionid)) {
|
if ($event->get_subscription()) {
|
||||||
$subscription = calendar_get_subscription($legacyevent->subscriptionid);
|
$subscription = calendar_get_subscription($event->get_subscription()->get('id'));
|
||||||
if (!empty($subscription) && $CFG->calendar_showicalsource) {
|
if (!empty($subscription) && $CFG->calendar_showicalsource) {
|
||||||
$values['displayeventsource'] = true;
|
$values['displayeventsource'] = true;
|
||||||
$subscriptiondata = new \stdClass();
|
$subscriptiondata = new \stdClass();
|
||||||
@ -152,13 +146,9 @@ class event_exporter extends event_exporter_base {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($legacyevent->groupid) {
|
if ($group = $event->get_group()) {
|
||||||
if ($group = calendar_get_group_cached($legacyevent->groupid)) {
|
$values['groupname'] = format_string($group->get('name'), true,
|
||||||
$values['groupname'] = format_string($group->name, true,
|
['context' => \context_course::instance($event->get_course()->get('id'))]);
|
||||||
['context' => \context_course::instance($group->courseid)]);
|
|
||||||
} else {
|
|
||||||
$values['groupname'] = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $values;
|
return $values;
|
||||||
|
@ -26,7 +26,10 @@ namespace core_calendar\external;
|
|||||||
|
|
||||||
defined('MOODLE_INTERNAL') || die();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
require_once($CFG->dirroot . "/calendar/lib.php");
|
||||||
|
|
||||||
use \core\external\exporter;
|
use \core\external\exporter;
|
||||||
|
use \core_calendar\local\event\container;
|
||||||
use \core_calendar\local\event\entities\event_interface;
|
use \core_calendar\local\event\entities\event_interface;
|
||||||
use \core_calendar\local\event\entities\action_event_interface;
|
use \core_calendar\local\event\entities\action_event_interface;
|
||||||
use \core_course\external\course_summary_exporter;
|
use \core_course\external\course_summary_exporter;
|
||||||
@ -153,18 +156,19 @@ class event_exporter_base extends exporter {
|
|||||||
*/
|
*/
|
||||||
protected static function define_other_properties() {
|
protected static function define_other_properties() {
|
||||||
return [
|
return [
|
||||||
'url' => ['type' => PARAM_URL],
|
|
||||||
'icon' => [
|
'icon' => [
|
||||||
'type' => event_icon_exporter::read_properties_definition(),
|
'type' => event_icon_exporter::read_properties_definition(),
|
||||||
],
|
],
|
||||||
'action' => [
|
|
||||||
'type' => event_action_exporter::read_properties_definition(),
|
|
||||||
'optional' => true,
|
|
||||||
],
|
|
||||||
'course' => [
|
'course' => [
|
||||||
'type' => course_summary_exporter::read_properties_definition(),
|
'type' => course_summary_exporter::read_properties_definition(),
|
||||||
'optional' => true,
|
'optional' => true,
|
||||||
]
|
],
|
||||||
|
'canedit' => [
|
||||||
|
'type' => PARAM_BOOL
|
||||||
|
],
|
||||||
|
'candelete' => [
|
||||||
|
'type' => PARAM_BOOL
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,37 +181,21 @@ class event_exporter_base extends exporter {
|
|||||||
protected function get_other_values(renderer_base $output) {
|
protected function get_other_values(renderer_base $output) {
|
||||||
$values = [];
|
$values = [];
|
||||||
$event = $this->event;
|
$event = $this->event;
|
||||||
|
$legacyevent = container::get_event_mapper()->from_event_to_legacy_event($event);
|
||||||
$context = $this->related['context'];
|
$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]);
|
|
||||||
} else {
|
|
||||||
// TODO MDL-58866 We do not have any way to find urls for events outside of course modules.
|
|
||||||
global $CFG;
|
|
||||||
require_once($CFG->dirroot.'/course/lib.php');
|
|
||||||
$url = \course_get_url($this->related['course'] ?: SITEID);
|
|
||||||
}
|
|
||||||
$timesort = $event->get_times()->get_sort_time()->getTimestamp();
|
$timesort = $event->get_times()->get_sort_time()->getTimestamp();
|
||||||
$iconexporter = new event_icon_exporter($event, ['context' => $context]);
|
$iconexporter = new event_icon_exporter($event, ['context' => $context]);
|
||||||
|
|
||||||
$values['url'] = $url->out(false);
|
|
||||||
$values['icon'] = $iconexporter->export($output);
|
$values['icon'] = $iconexporter->export($output);
|
||||||
|
|
||||||
if ($event instanceof action_event_interface) {
|
|
||||||
$actionrelated = [
|
|
||||||
'context' => $context,
|
|
||||||
'event' => $event
|
|
||||||
];
|
|
||||||
$actionexporter = new event_action_exporter($event->get_action(), $actionrelated);
|
|
||||||
$values['action'] = $actionexporter->export($output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($course = $this->related['course']) {
|
if ($course = $this->related['course']) {
|
||||||
$coursesummaryexporter = new course_summary_exporter($course, ['context' => $context]);
|
$coursesummaryexporter = new course_summary_exporter($course, ['context' => $context]);
|
||||||
$values['course'] = $coursesummaryexporter->export($output);
|
$values['course'] = $coursesummaryexporter->export($output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$values['canedit'] = calendar_edit_event_allowed($legacyevent, true);
|
||||||
|
$values['candelete'] = calendar_delete_event_allowed($legacyevent);
|
||||||
|
|
||||||
return $values;
|
return $values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ namespace core_calendar\local;
|
|||||||
|
|
||||||
defined('MOODLE_INTERNAL') || die();
|
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;
|
use core_calendar\local\event\exceptions\limit_invalid_parameter_exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -214,4 +216,30 @@ class api {
|
|||||||
|
|
||||||
return $return;
|
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
|
||||||
|
) {
|
||||||
|
$mapper = container::get_event_mapper();
|
||||||
|
$legacyevent = $mapper->from_event_to_legacy_event($event);
|
||||||
|
$starttime = $event->get_times()->get_start_time()->setDate(
|
||||||
|
$startDate->format('Y'),
|
||||||
|
$startDate->format('n'),
|
||||||
|
$startDate->format('j')
|
||||||
|
);
|
||||||
|
|
||||||
|
// This function does our capability checks.
|
||||||
|
$legacyevent->update((object) ['timestart' => $starttime->getTimestamp()]);
|
||||||
|
|
||||||
|
return $mapper->from_legacy_event_to_event($legacyevent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,8 +89,16 @@ class event_mapper implements event_mapper_interface {
|
|||||||
public function from_event_to_legacy_event(event_interface $event) {
|
public function from_event_to_legacy_event(event_interface $event) {
|
||||||
$action = ($event instanceof action_event_interface) ? $event->get_action() : null;
|
$action = ($event instanceof action_event_interface) ? $event->get_action() : null;
|
||||||
$timeduration = $event->get_times()->get_end_time()->getTimestamp() - $event->get_times()->get_start_time()->getTimestamp();
|
$timeduration = $event->get_times()->get_end_time()->getTimestamp() - $event->get_times()->get_start_time()->getTimestamp();
|
||||||
|
$properties = $this->from_event_to_stdclass($event);
|
||||||
|
|
||||||
return new \calendar_event($this->from_event_to_stdclass($event));
|
// Normalise for the legacy event because it wants zero rather than null.
|
||||||
|
$properties->courseid = empty($properties->courseid) ? 0 : $properties->courseid;
|
||||||
|
$properties->groupid = empty($properties->groupid) ? 0 : $properties->groupid;
|
||||||
|
$properties->userid = empty($properties->userid) ? 0 : $properties->userid;
|
||||||
|
$properties->modulename = empty($properties->modulename) ? 0 : $properties->modulename;
|
||||||
|
$properties->instance = empty($properties->instance) ? 0 : $properties->instance;
|
||||||
|
|
||||||
|
return new \calendar_event($properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function from_event_to_stdclass(event_interface $event) {
|
public function from_event_to_stdclass(event_interface $event) {
|
||||||
|
@ -114,7 +114,7 @@ $formoptions = new stdClass;
|
|||||||
if ($eventid !== 0) {
|
if ($eventid !== 0) {
|
||||||
$title = get_string('editevent', 'calendar');
|
$title = get_string('editevent', 'calendar');
|
||||||
$event = calendar_event::load($eventid);
|
$event = calendar_event::load($eventid);
|
||||||
if (!calendar_edit_event_allowed($event)) {
|
if (!calendar_edit_event_allowed($event, true)) {
|
||||||
print_error('nopermissions');
|
print_error('nopermissions');
|
||||||
}
|
}
|
||||||
$event->action = $action;
|
$event->action = $action;
|
||||||
|
@ -811,6 +811,10 @@ class core_calendar_external extends external_api {
|
|||||||
$properties = $legacyevent->properties(true);
|
$properties = $legacyevent->properties(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!calendar_edit_event_allowed($legacyevent, true)) {
|
||||||
|
print_error('nopermissiontoupdatecalendar');
|
||||||
|
}
|
||||||
|
|
||||||
$legacyevent->update($properties);
|
$legacyevent->update($properties);
|
||||||
|
|
||||||
$eventmapper = event_container::get_event_mapper();
|
$eventmapper = event_container::get_event_mapper();
|
||||||
@ -906,4 +910,84 @@ class core_calendar_external extends external_api {
|
|||||||
public static function get_calendar_monthly_view_returns() {
|
public static function get_calendar_monthly_view_returns() {
|
||||||
return \core_calendar\external\month_exporter::get_read_structure();
|
return \core_calendar\external\month_exporter::get_read_structure();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns description of method parameters.
|
||||||
|
*
|
||||||
|
* @return external_function_parameters
|
||||||
|
*/
|
||||||
|
public static function update_event_start_day_parameters() {
|
||||||
|
return new external_function_parameters(
|
||||||
|
[
|
||||||
|
'eventId' => new external_value(PARAM_INT, 'Id of event to be updated', VALUE_REQUIRED),
|
||||||
|
'dayTimestamp' => new external_value(PARAM_INT, 'Timestamp for the new start day', VALUE_REQUIRED),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the start day for the given calendar event to the day that
|
||||||
|
* corresponds with the provided timestamp.
|
||||||
|
*
|
||||||
|
* The timestamp only needs to be anytime within the desired day as only
|
||||||
|
* the date data is extracted from it.
|
||||||
|
*
|
||||||
|
* The event's original time of day is maintained, only the date is shifted.
|
||||||
|
*
|
||||||
|
* @param int $eventId Id of event to be updated
|
||||||
|
* @param int $dayTimestamp Timestamp for the new start day
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function update_event_start_day($eventId, $dayTimestamp) {
|
||||||
|
global $USER, $PAGE;
|
||||||
|
|
||||||
|
// Parameter validation.
|
||||||
|
$params = self::validate_parameters(self::update_event_start_day_parameters(), [
|
||||||
|
'eventId' => $eventId,
|
||||||
|
'dayTimestamp' => $dayTimestamp,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$vault = event_container::get_event_vault();
|
||||||
|
$mapper = event_container::get_event_mapper();
|
||||||
|
$event = $vault->get_event_by_id($eventId);
|
||||||
|
|
||||||
|
if (!$event) {
|
||||||
|
throw new \moodle_exception('Unable to find event with id ' . $eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$legacyevent = $mapper->from_event_to_legacy_event($event);
|
||||||
|
|
||||||
|
if (!calendar_edit_event_allowed($legacyevent, true)) {
|
||||||
|
print_error('nopermissiontoupdatecalendar');
|
||||||
|
}
|
||||||
|
|
||||||
|
self::validate_context($legacyevent->context);
|
||||||
|
|
||||||
|
$newdate = usergetdate($dayTimestamp);
|
||||||
|
$startdatestring = implode('-', [$newdate['year'], $newdate['mon'], $newdate['mday']]);
|
||||||
|
$startdate = new DateTimeImmutable($startdatestring);
|
||||||
|
$event = local_api::update_event_start_day($event, $startdate);
|
||||||
|
$cache = new events_related_objects_cache([$event]);
|
||||||
|
$relatedobjects = [
|
||||||
|
'context' => $cache->get_context($event),
|
||||||
|
'course' => $cache->get_course($event),
|
||||||
|
];
|
||||||
|
$exporter = new event_exporter($event, $relatedobjects);
|
||||||
|
$renderer = $PAGE->get_renderer('core_calendar');
|
||||||
|
|
||||||
|
return array('event' => $exporter->export($renderer));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns description of method result value.
|
||||||
|
*
|
||||||
|
* @return external_description
|
||||||
|
*/
|
||||||
|
public static function update_event_start_day_returns() {
|
||||||
|
return new external_single_structure(
|
||||||
|
array(
|
||||||
|
'event' => event_exporter::get_read_structure()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2435,9 +2435,10 @@ function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false) {
|
|||||||
* Return the capability for editing calendar event.
|
* Return the capability for editing calendar event.
|
||||||
*
|
*
|
||||||
* @param calendar_event $event event object
|
* @param calendar_event $event event object
|
||||||
|
* @param bool $manualedit is the event being edited manually by the user
|
||||||
* @return bool capability to edit event
|
* @return bool capability to edit event
|
||||||
*/
|
*/
|
||||||
function calendar_edit_event_allowed($event) {
|
function calendar_edit_event_allowed($event, $manualedit = false) {
|
||||||
global $USER, $DB;
|
global $USER, $DB;
|
||||||
|
|
||||||
// Must be logged in.
|
// Must be logged in.
|
||||||
@ -2450,6 +2451,12 @@ function calendar_edit_event_allowed($event) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($manualedit && !empty($event->modulename)) {
|
||||||
|
// A user isn't allowed to directly edit an event generated
|
||||||
|
// by a module.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// You cannot edit URL based calendar subscription events presently.
|
// You cannot edit URL based calendar subscription events presently.
|
||||||
if (!empty($event->subscriptionid)) {
|
if (!empty($event->subscriptionid)) {
|
||||||
if (!empty($event->subscription->url)) {
|
if (!empty($event->subscription->url)) {
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
<span class="calendarwrapper" data-courseid="{{courseid}}" data-current-time="{{time}}">
|
<span id="month-detailed-{{uniqid}}" class="calendarwrapper" data-courseid="{{courseid}}" data-current-time="{{time}}">
|
||||||
{{> core_calendar/month_header }}
|
{{> core_calendar/month_header }}
|
||||||
{{> core_calendar/month_navigation }}
|
{{> core_calendar/month_navigation }}
|
||||||
<table class="calendarmonth calendartable card-deck m-b-0">
|
<table class="calendarmonth calendartable card-deck m-b-0">
|
||||||
@ -46,7 +46,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#weeks}}
|
{{#weeks}}
|
||||||
<tr>
|
<tr data-region="month-view-week">
|
||||||
{{#prepadding}}
|
{{#prepadding}}
|
||||||
<td class="dayblank"> </td>
|
<td class="dayblank"> </td>
|
||||||
{{/prepadding}}
|
{{/prepadding}}
|
||||||
@ -56,7 +56,9 @@
|
|||||||
}}{{#isweekend}} weekend{{/isweekend}}{{!
|
}}{{#isweekend}} weekend{{/isweekend}}{{!
|
||||||
}}{{#durationevents.0}} duration{{/durationevents.0}}{{!
|
}}{{#durationevents.0}} duration{{/durationevents.0}}{{!
|
||||||
}}{{#durationevents}} duration_{{.}}{{/durationevents}}{{!
|
}}{{#durationevents}} duration_{{.}}{{/durationevents}}{{!
|
||||||
}}">
|
}}"
|
||||||
|
data-day-timestamp="{{timestamp}}"
|
||||||
|
data-drop-zone="true">
|
||||||
<div class="hidden-sm-down text-xs-center">
|
<div class="hidden-sm-down text-xs-center">
|
||||||
{{#events.0}}
|
{{#events.0}}
|
||||||
<a href="{{viewdaylink}}" class="day" title="{{viewdaylinktitle}}">{{mday}}</a>
|
<a href="{{viewdaylink}}" class="day" title="{{viewdaylinktitle}}">{{mday}}</a>
|
||||||
@ -65,18 +67,25 @@
|
|||||||
{{mday}}
|
{{mday}}
|
||||||
{{/events.0}}
|
{{/events.0}}
|
||||||
{{#events.0}}
|
{{#events.0}}
|
||||||
<ul>
|
<div data-region="day-content">
|
||||||
{{#events}}
|
<ul>
|
||||||
|
{{#events}}
|
||||||
{{#underway}}
|
{{#underway}}
|
||||||
<li class="events-underway">[{{name}}]</li>
|
<li class="events-underway">[{{name}}]</li>
|
||||||
{{/underway}}
|
{{/underway}}
|
||||||
{{^underway}}
|
{{^underway}}
|
||||||
<li class="calendar_event_{{eventtype}}">
|
<li class="calendar_event_{{eventtype}}"
|
||||||
|
{{#canedit}}
|
||||||
|
draggable="true"
|
||||||
|
data-drag-type="move"
|
||||||
|
{{/canedit}}>
|
||||||
|
|
||||||
<a data-action="view-event" data-event-id="{{id}}" href="{{url}}">{{name}}</a>
|
<a data-action="view-event" data-event-id="{{id}}" href="{{url}}">{{name}}</a>
|
||||||
</li>
|
</li>
|
||||||
{{/underway}}
|
{{/underway}}
|
||||||
{{/events}}
|
{{/events}}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
{{/events.0}}
|
{{/events.0}}
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden-md-up hidden-desktop">
|
<div class="hidden-md-up hidden-desktop">
|
||||||
@ -84,7 +93,9 @@
|
|||||||
<a href="{{viewdaylink}}" class="day" title="{{viewdaylinktitle}}">{{mday}}</a>
|
<a href="{{viewdaylink}}" class="day" title="{{viewdaylinktitle}}">{{mday}}</a>
|
||||||
{{/events.0}}
|
{{/events.0}}
|
||||||
{{^events.0}}
|
{{^events.0}}
|
||||||
{{mday}}
|
<div data-region="day-content">
|
||||||
|
{{mday}}
|
||||||
|
</div>
|
||||||
{{/events.0}}
|
{{/events.0}}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -97,3 +108,9 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</span>
|
</span>
|
||||||
|
{{#js}}
|
||||||
|
require(['jquery', 'core_calendar/drag_drop'], function($, DragDrop) {
|
||||||
|
var root = $('#month-detailed-{{uniqid}}');
|
||||||
|
DragDrop.init(root);
|
||||||
|
});
|
||||||
|
{{/js}}
|
||||||
|
@ -1290,4 +1290,128 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
|||||||
$this->expectException('moodle_exception');
|
$this->expectException('moodle_exception');
|
||||||
core_calendar_external::delete_calendar_events($params);
|
core_calendar_external::delete_calendar_events($params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updating the event start day should change the date value but leave
|
||||||
|
* the time of day unchanged.
|
||||||
|
*/
|
||||||
|
public function test_update_event_start_day() {
|
||||||
|
$generator = $this->getDataGenerator();
|
||||||
|
$user = $generator->create_user();
|
||||||
|
$roleid = $generator->create_role();
|
||||||
|
$context = \context_system::instance();
|
||||||
|
$originalStartTime = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||||
|
$newStartDate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||||
|
$expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||||
|
|
||||||
|
$generator->role_assign($roleid, $user->id, $context->id);
|
||||||
|
assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
|
||||||
|
|
||||||
|
$this->setUser($user);
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
|
||||||
|
$event = $this->create_calendar_event(
|
||||||
|
'Test event',
|
||||||
|
$user->id,
|
||||||
|
'user',
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'courseid' => 0,
|
||||||
|
'timestart' => $originalStartTime->getTimestamp()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = core_calendar_external::update_event_start_day($event->id, $newStartDate->getTimestamp());
|
||||||
|
$result = external_api::clean_returnvalue(
|
||||||
|
core_calendar_external::update_event_start_day_returns(),
|
||||||
|
$result
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($expected->getTimestamp(), $result['event']['timestart']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user should not be able to edit an event that they don't have
|
||||||
|
* capabilities for.
|
||||||
|
*/
|
||||||
|
public function test_update_event_start_day_no_permission() {
|
||||||
|
$generator = $this->getDataGenerator();
|
||||||
|
$user = $generator->create_user();
|
||||||
|
$roleid = $generator->create_role();
|
||||||
|
$context = \context_system::instance();
|
||||||
|
$originalStartTime = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||||
|
$newStartDate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||||
|
$expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||||
|
|
||||||
|
$generator->role_assign($roleid, $user->id, $context->id);
|
||||||
|
assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
|
||||||
|
|
||||||
|
$this->setUser($user);
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
|
||||||
|
$event = $this->create_calendar_event(
|
||||||
|
'Test event',
|
||||||
|
$user->id,
|
||||||
|
'user',
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'courseid' => 0,
|
||||||
|
'timestart' => $originalStartTime->getTimestamp()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true);
|
||||||
|
$this->expectException('moodle_exception');
|
||||||
|
$result = core_calendar_external::update_event_start_day($event->id, $newStartDate->getTimestamp());
|
||||||
|
$result = external_api::clean_returnvalue(
|
||||||
|
core_calendar_external::update_event_start_day_returns(),
|
||||||
|
$result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user should not be able to update a module event.
|
||||||
|
*/
|
||||||
|
public function test_update_event_start_day_module_event() {
|
||||||
|
$generator = $this->getDataGenerator();
|
||||||
|
$user = $generator->create_user();
|
||||||
|
$course = $generator->create_course();
|
||||||
|
$moduleinstance = $generator->get_plugin_generator('mod_assign')
|
||||||
|
->create_instance(['course' => $course->id]);
|
||||||
|
$roleid = $generator->create_role();
|
||||||
|
$context = \context_course::instance($course->id);
|
||||||
|
$originalStartTime = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||||
|
$newStartDate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||||
|
$expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||||
|
|
||||||
|
$generator->role_assign($roleid, $user->id, $context->id);
|
||||||
|
$generator->enrol_user($user->id, $course->id);
|
||||||
|
|
||||||
|
$this->setUser($user);
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
|
||||||
|
$event = $this->create_calendar_event(
|
||||||
|
'Test event',
|
||||||
|
$user->id,
|
||||||
|
'user',
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'modulename' => 'assign',
|
||||||
|
'instance' => $moduleinstance->id,
|
||||||
|
'courseid' => $course->id,
|
||||||
|
'timestart' => $originalStartTime->getTimestamp()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
|
||||||
|
$this->expectException('moodle_exception');
|
||||||
|
$result = core_calendar_external::update_event_start_day($event->id, $newStartDate->getTimestamp());
|
||||||
|
$result = external_api::clean_returnvalue(
|
||||||
|
core_calendar_external::update_event_start_day_returns(),
|
||||||
|
$result
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ defined('MOODLE_INTERNAL') || die();
|
|||||||
|
|
||||||
require_once(__DIR__ . '/helpers.php');
|
require_once(__DIR__ . '/helpers.php');
|
||||||
|
|
||||||
|
use \core_calendar\local\event\container;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class contaning unit tests for the calendar local API.
|
* Class contaning unit tests for the calendar local API.
|
||||||
*
|
*
|
||||||
@ -858,4 +860,72 @@ class core_calendar_local_api_testcase extends advanced_testcase {
|
|||||||
$events = calendar_get_legacy_events($timestart, $timeend, true, true, true);
|
$events = calendar_get_legacy_events($timestart, $timeend, true, true, true);
|
||||||
$this->assertCount(3, $events);
|
$this->assertCount(3, $events);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting the start date on the calendar event should update the date
|
||||||
|
* of the event but should leave the time of day unchanged.
|
||||||
|
*/
|
||||||
|
public function test_update_event_start_day_updates_date() {
|
||||||
|
$generator = $this->getDataGenerator();
|
||||||
|
$user = $generator->create_user();
|
||||||
|
$roleid = $generator->create_role();
|
||||||
|
$context = \context_system::instance();
|
||||||
|
$originalStartTime = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||||
|
$newStartDate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||||
|
$expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||||
|
$mapper = container::get_event_mapper();
|
||||||
|
|
||||||
|
$generator->role_assign($roleid, $user->id, $context->id);
|
||||||
|
assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
|
||||||
|
|
||||||
|
$this->setUser($user);
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
|
||||||
|
$event = create_event([
|
||||||
|
'name' => 'Test event',
|
||||||
|
'userid' => $user->id,
|
||||||
|
'eventtype' => 'user',
|
||||||
|
'repeats' => 0,
|
||||||
|
'timestart' => $originalStartTime->getTimestamp(),
|
||||||
|
]);
|
||||||
|
$event = $mapper->from_legacy_event_to_event($event);
|
||||||
|
|
||||||
|
$newEvent = \core_calendar\local\api::update_event_start_day($event, $newStartDate);
|
||||||
|
$actual = $newEvent->get_times()->get_start_time();
|
||||||
|
|
||||||
|
$this->assertEquals($expected->getTimestamp(), $actual->getTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user should not be able to update the start date of the event
|
||||||
|
* that they don't have the capabilities to modify.
|
||||||
|
*/
|
||||||
|
public function test_update_event_start_day_no_permission() {
|
||||||
|
$generator = $this->getDataGenerator();
|
||||||
|
$user = $generator->create_user();
|
||||||
|
$roleid = $generator->create_role();
|
||||||
|
$context = \context_system::instance();
|
||||||
|
$originalStartTime = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||||
|
$newStartDate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||||
|
$expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||||
|
$mapper = container::get_event_mapper();
|
||||||
|
|
||||||
|
$generator->role_assign($roleid, $user->id, $context->id);
|
||||||
|
|
||||||
|
$this->setUser($user);
|
||||||
|
$this->resetAfterTest(true);
|
||||||
|
|
||||||
|
$event = create_event([
|
||||||
|
'name' => 'Test event',
|
||||||
|
'userid' => $user->id,
|
||||||
|
'eventtype' => 'user',
|
||||||
|
'repeats' => 0,
|
||||||
|
'timestart' => $originalStartTime->getTimestamp(),
|
||||||
|
]);
|
||||||
|
$event = $mapper->from_legacy_event_to_event($event);
|
||||||
|
|
||||||
|
assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true);
|
||||||
|
$this->expectException('moodle_exception');
|
||||||
|
$newEvent = \core_calendar\local\api::update_event_start_day($event, $newStartDate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,15 @@ $functions = array(
|
|||||||
'ajax' => true,
|
'ajax' => true,
|
||||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||||
),
|
),
|
||||||
|
'core_calendar_update_event_start_day' => array(
|
||||||
|
'classname' => 'core_calendar_external',
|
||||||
|
'methodname' => 'update_event_start_day',
|
||||||
|
'description' => 'Update the start day (but not time) for an event.',
|
||||||
|
'classpath' => 'calendar/externallib.php',
|
||||||
|
'type' => 'write',
|
||||||
|
'capabilities' => 'moodle/calendar:manageentries, moodle/calendar:manageownentries, moodle/calendar:managegroupentries',
|
||||||
|
'ajax' => true,
|
||||||
|
),
|
||||||
'core_calendar_create_calendar_events' => array(
|
'core_calendar_create_calendar_events' => array(
|
||||||
'classname' => 'core_calendar_external',
|
'classname' => 'core_calendar_external',
|
||||||
'methodname' => 'create_calendar_events',
|
'methodname' => 'create_calendar_events',
|
||||||
|
@ -2151,3 +2151,7 @@ $footer-link-color: $bg-inverse-link-color !default;
|
|||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-drag-type="move"] {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
defined('MOODLE_INTERNAL') || die();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
$version = 2017082200.01; // YYYYMMDD = weekly release date of this DEV branch.
|
$version = 2017082300.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||||
// RR = release increments - 00 in DEV branches.
|
// RR = release increments - 00 in DEV branches.
|
||||||
// .XX = incremental changes.
|
// .XX = incremental changes.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user