Merge branch 'MDL-60058-master-4' of git://github.com/ryanwyllie/moodle
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
define([],function(){var a=null,b=null,c=function(b){a=b},d=function(){return a},e=function(){return null!==a},f=function(a){b=a},g=function(){return b},h=function(){c(null),f(null)};return{setEventId:c,getEventId:d,hasEventId:e,setDurationDays:f,getDurationDays:g,clearAll:h}});
|
||||
define([],function(){var a=null,b=null,c=null,d=null,e=null,f=null,g=function(b){a=b},h=function(){return a},i=function(){return null!==a},j=function(a){b=a},k=function(){return b},l=function(a){c=a},m=function(){return c},n=function(){return null!==c},o=function(a){d=a},p=function(){return d},q=function(){return null!==d},r=function(a){e=a},s=function(){return e},t=function(a){f=a},u=function(){return f},v=function(){g(null),j(null),l(null),o(null),r(null),t(null)};return{setEventId:g,getEventId:h,hasEventId:i,setDurationDays:j,getDurationDays:k,setMinTimestart:l,getMinTimestart:m,hasMinTimestart:n,setMaxTimestart:o,getMaxTimestart:p,hasMaxTimestart:q,setMinError:r,getMinError:s,setMaxError:t,getMaxError:u,clearAll:v}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core_calendar/drag_drop_data_store"],function(a,b){var c={DRAGGABLE:'[draggable="true"][data-region="event-item"]',DROP_ZONE:'[data-drop-zone="nav-link"]'},d="bg-primary text-white",e="drop-target",f=1e3,g=!1,h=null,i=null,j=function(a,b){b?a.addClass(d):a.removeClass(d)},k=function(){i.find(c.DROP_ZONE).addClass(e)},l=function(){i.find(c.DROP_ZONE).removeClass(e)},m=function(b){var d=a(b.target).closest(c.DROP_ZONE);return d.length?d:null},n=function(b){var d=a(b.target).closest(c.DRAGGABLE);d.length&&k()},o=function(a){a.preventDefault();var c=m(a);c&&b.hasEventId()&&(h||(h=setTimeout(function(){c.click(),h=null},f)),j(c,!0),l())},p=function(a){var b=m(a);b&&(h&&(clearTimeout(h),h=null),j(b,!1),k(),a.preventDefault())},q=function(a){l();var b=m(a);b&&(j(b,!1),a.preventDefault())};return{init:function(c){g||(document.addEventListener("dragstart",n,!1),document.addEventListener("dragover",o,!1),document.addEventListener("dragleave",p,!1),document.addEventListener("drop",q,!1),document.addEventListener("dragend",l,!1),g=!0),i=a(c),b.hasEventId()&&k()}}});
|
||||
define(["jquery","core_calendar/drag_drop_data_store"],function(a,b){var c={DRAGGABLE:'[draggable="true"][data-region="event-item"]',DROP_ZONE:'[data-drop-zone="nav-link"]'},d="bg-primary text-white",e="drop-target",f=1e3,g=!1,h=null,i=null,j=function(a,b){b?a.addClass(d):a.removeClass(d)},k=function(){i.find(c.DROP_ZONE).addClass(e)},l=function(){i.find(c.DROP_ZONE).removeClass(e)},m=function(b){var d=a(b.target).closest(c.DROP_ZONE);return d.length?d:null},n=function(b){var d=a(b.target).closest(c.DRAGGABLE);d.length&&k()},o=function(a){if(b.hasEventId()){a.preventDefault();var c=m(a);c&&b.hasEventId()&&(h||(h=setTimeout(function(){c.click(),h=null},f)),j(c,!0),l())}},p=function(a){if(b.hasEventId()){var c=m(a);c&&(h&&(clearTimeout(h),h=null),j(c,!1),k(),a.preventDefault())}},q=function(a){if(b.hasEventId()){l();var c=m(a);c&&(j(c,!1),a.preventDefault())}};return{init:function(c){g||(document.addEventListener("dragstart",n,!1),document.addEventListener("dragover",o,!1),document.addEventListener("dragleave",p,!1),document.addEventListener("drop",q,!1),document.addEventListener("dragend",l,!1),g=!0),i=a(c),b.hasEventId()&&k()}}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core_calendar/events","core_calendar/drag_drop_data_store"],function(a,b,c){var d={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"]'},e="bg-primary text-white",f=!1,g=function(b){var c=a(b.target).closest(d.DROP_ZONE);return c.length?c:null},h=function(a,b,f){if("undefined"==typeof f&&(f=c.getDurationDays()),b?a.addClass(e):a.removeClass(e),f--,f>0){var g=a.next();if(!g.length){var i=a.closest(d.WEEK).next();i.length&&(g=i.children(d.DROP_ZONE).first())}g.length&&h(g,b,f)}},i=function(b){var e=a(b.target).closest(d.DRAGGABLE);if(e.length){e=e.find("[data-event-id]");var f=e.attr("data-event-id"),g=d.ROOT+' [data-event-id="'+f+'"]',h=a(g).length;c.setEventId(f),c.setDurationDays(h),b.dataTransfer.effectAllowed="move",b.dataTransfer.dropEffect="move",b.dataTransfer.setData("text/plain",f),b.dropEffect="move"}},j=function(a){a.preventDefault();var b=g(a);b&&h(b,!0)},k=function(a){var b=g(a);b&&(h(b,!1),a.preventDefault())},l=function(e){var f=g(e);if(!f)return void c.clearAll();var i=c.getEventId(),j=d.ROOT+' [data-event-id="'+i+'"]',k=a(j),l=null,m=a(e.target).closest(d.DROP_ZONE);k.length&&(l=k.closest(d.DROP_ZONE)),h(f,!1),a("body").trigger(b.moveEvent,[i,l,m]),c.clearAll(),e.preventDefault()};return{init:function(){f||(document.addEventListener("dragstart",i,!1),document.addEventListener("dragover",j,!1),document.addEventListener("dragleave",k,!1),document.addEventListener("drop",l,!1),f=!0)}}});
|
||||
define(["jquery","core/notification","core/str","core_calendar/events","core_calendar/drag_drop_data_store"],function(a,b,c,d,e){var f={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"]'},g="bg-faded",h="bg-danger text-white",i="bg-primary text-white",j=g+" "+h+" "+i,k=!1,l=function(b){var c=a(b.target).closest(f.DROP_ZONE);return c.length?c:null},m=function(a){var b=a.attr("data-day-timestamp"),c=e.getMinTimestart(),d=e.getMaxTimestart();return!(c&&c>b)&&!(d&&d<b)},n=function(a){var b=a.attr("data-day-timestamp"),c=e.getMinTimestart(),d=e.getMaxTimestart();return c&&c>b?e.getMinError():d&&d<b?e.getMaxError():null},o=function(){a(f.ROOT).find(f.DROP_ZONE).each(function(b,c){c=a(c),c.removeClass(j)})},p=function(a,b,c){"undefined"==typeof c&&(c=e.getDurationDays());var d=m(a);if(a.removeClass(j),b?d?a.addClass(i):a.addClass(h):(a.removeClass(i+" "+h),d||a.addClass(g)),c--,c>0){var k=a.next();if(!k.length){var l=a.closest(f.WEEK).next();l.length&&(k=l.children(f.DROP_ZONE).first())}k.length&&p(k,b,c)}},q=function(){a(f.ROOT).find(f.DROP_ZONE).each(function(b,c){c=a(c),m(c)||p(c,!1)})},r=function(b){var c=a(b.target),d=c.closest(f.DRAGGABLE);if(d.length){var g=d.find("[data-event-id]"),h=g.attr("data-event-id"),i=d.attr("data-min-day-timestamp"),j=d.attr("data-max-day-timestamp"),k=d.attr("data-min-day-error"),l=d.attr("data-max-day-error"),m=f.ROOT+' [data-event-id="'+h+'"]',n=a(m).length;e.setEventId(h),e.setDurationDays(n),i&&e.setMinTimestart(i),j&&e.setMaxTimestart(j),k&&e.setMinError(k),l&&e.setMaxError(l),b.dataTransfer.effectAllowed="move",b.dataTransfer.dropEffect="move",b.dataTransfer.setData("text/plain",h),b.dropEffect="move",q()}},s=function(a){if(e.hasEventId()){a.preventDefault();var b=l(a);b&&p(b,!0)}},t=function(a){if(e.hasEventId()){var b=l(a);b&&(p(b,!1),a.preventDefault())}},u=function(g){if(e.hasEventId()){var h=l(g);if(!h)return e.clearAll(),void o();if(m(h)){var i=e.getEventId(),j=f.ROOT+' [data-event-id="'+i+'"]',k=a(j),p=null;k.length&&(p=k.closest(f.DROP_ZONE)),a("body").trigger(d.moveEvent,[i,p,h])}else{var q=n(h);c.get_string("errorinvaliddate","calendar").then(function(a){b.exception({name:a,message:q||a})})}e.clearAll(),o(),g.preventDefault()}},v=function(){e.clearAll(),o()},w=function(){q()};return{init:function(){k||(document.addEventListener("dragstart",r,!1),document.addEventListener("dragover",s,!1),document.addEventListener("dragleave",t,!1),document.addEventListener("drop",u,!1),document.addEventListener("dragend",v,!1),a("body").on(d.monthChanged,w),k=!0)}}});
|
||||
@@ -30,6 +30,14 @@ define([], function() {
|
||||
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.
|
||||
@@ -76,12 +84,108 @@ define([], 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 {
|
||||
@@ -90,6 +194,16 @@ define([], function() {
|
||||
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
|
||||
};
|
||||
});
|
||||
|
||||
@@ -119,6 +119,11 @@ define([
|
||||
* @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);
|
||||
|
||||
@@ -153,6 +158,11 @@ define([
|
||||
* @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) {
|
||||
@@ -176,6 +186,11 @@ define([
|
||||
* @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);
|
||||
|
||||
|
||||
@@ -25,11 +25,15 @@
|
||||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/notification',
|
||||
'core/str',
|
||||
'core_calendar/events',
|
||||
'core_calendar/drag_drop_data_store'
|
||||
],
|
||||
function(
|
||||
$,
|
||||
Notification,
|
||||
Str,
|
||||
CalendarEvents,
|
||||
DataStore
|
||||
) {
|
||||
@@ -40,7 +44,10 @@ define([
|
||||
DROP_ZONE: '[data-drop-zone="month-view-day"]',
|
||||
WEEK: '[data-region="month-view-week"]',
|
||||
};
|
||||
var HOVER_CLASS = 'bg-primary text-white';
|
||||
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;
|
||||
|
||||
@@ -56,10 +63,73 @@ define([
|
||||
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.
|
||||
@@ -79,10 +149,22 @@ define([
|
||||
count = DataStore.getDurationDays();
|
||||
}
|
||||
|
||||
var valid = isValidDropZone(dropZone);
|
||||
dropZone.removeClass(ALL_CLASSES);
|
||||
|
||||
if (hovered) {
|
||||
dropZone.addClass(HOVER_CLASS);
|
||||
|
||||
if (valid) {
|
||||
dropZone.addClass(VALID_HOVER_CLASS);
|
||||
} else {
|
||||
dropZone.addClass(INVALID_HOVER_CLASS);
|
||||
}
|
||||
} else {
|
||||
dropZone.removeClass(HOVER_CLASS);
|
||||
dropZone.removeClass(VALID_HOVER_CLASS + ' ' + INVALID_HOVER_CLASS);
|
||||
|
||||
if (!valid) {
|
||||
dropZone.addClass(INVALID_DROP_ZONE_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
count--;
|
||||
@@ -110,6 +192,21 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -117,27 +214,49 @@ define([
|
||||
* @param {event} e The dragstart event
|
||||
*/
|
||||
var dragstartHandler = function(e) {
|
||||
var eventElement = $(e.target).closest(SELECTORS.DRAGGABLE);
|
||||
var target = $(e.target);
|
||||
var draggableElement = target.closest(SELECTORS.DRAGGABLE);
|
||||
|
||||
if (!eventElement.length) {
|
||||
if (!draggableElement.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventElement = eventElement.find('[data-event-id]');
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -150,6 +269,11 @@ define([
|
||||
* @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);
|
||||
@@ -171,6 +295,11 @@ define([
|
||||
* @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) {
|
||||
@@ -193,30 +322,66 @@ define([
|
||||
* @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;
|
||||
}
|
||||
|
||||
var eventId = DataStore.getEventId();
|
||||
var eventElementSelector = SELECTORS.ROOT + ' [data-event-id="' + eventId + '"]';
|
||||
var eventElement = $(eventElementSelector);
|
||||
var origin = null;
|
||||
var destination = $(e.target).closest(SELECTORS.DROP_ZONE);
|
||||
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);
|
||||
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
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateHoverState(dropZone, false);
|
||||
$('body').trigger(CalendarEvents.moveEvent, [eventId, origin, destination]);
|
||||
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.
|
||||
@@ -231,6 +396,8 @@ define([
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
||||
+129
-1
@@ -26,6 +26,7 @@ namespace core_calendar\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use \core_calendar\local\event\container;
|
||||
use \core_course\external\course_summary_exporter;
|
||||
use \renderer_base;
|
||||
require_once($CFG->dirroot . '/course/lib.php');
|
||||
@@ -57,6 +58,22 @@ class calendar_event_exporter extends event_exporter_base {
|
||||
$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
|
||||
];
|
||||
|
||||
return $values;
|
||||
}
|
||||
@@ -89,9 +106,9 @@ class calendar_event_exporter extends event_exporter_base {
|
||||
} else {
|
||||
// TODO MDL-58866 We do not have any way to find urls for events outside of course modules.
|
||||
$course = $event->get_course()->get('id') ?: SITEID;
|
||||
|
||||
$url = course_get_url($course);
|
||||
}
|
||||
|
||||
$values['url'] = $url->out(false);
|
||||
$values['islastday'] = false;
|
||||
$today = $this->related['type']->timestamp_to_date_array($this->related['today']);
|
||||
@@ -153,6 +170,10 @@ class calendar_event_exporter extends event_exporter_base {
|
||||
|
||||
$values['calendareventtype'] = $this->get_calendar_event_type();
|
||||
|
||||
if ($event->get_course_module()) {
|
||||
$values = array_merge($values, $this->get_module_timestamp_limits($event));
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
@@ -184,4 +205,111 @@ class calendar_event_exporter extends event_exporter_base {
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of minimum and maximum date timestamp values
|
||||
* for the given event.
|
||||
*
|
||||
* @param 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();
|
||||
|
||||
list($min, $max) = component_callback(
|
||||
'mod_' . $event->get_course_module()->get('modname'),
|
||||
'core_calendar_get_valid_event_timestart_range',
|
||||
[$mapper->from_event_to_legacy_event($event)],
|
||||
[null, null]
|
||||
);
|
||||
|
||||
if ($min) {
|
||||
$values = array_merge($values, $this->get_module_timestamp_min_limit($starttime, $min));
|
||||
}
|
||||
|
||||
if ($max) {
|
||||
$values = array_merge($values, $this->get_module_timestamp_max_limit($starttime, $max));
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct minimum midnight day limit based on the event start time
|
||||
* and the module's minimum timestamp limit.
|
||||
*
|
||||
* @param DateTimeInterface $starttime The event start time
|
||||
* @param array $min The module's minimum limit for the event
|
||||
*/
|
||||
protected function get_module_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 module's maximum timestamp limit.
|
||||
*
|
||||
* @param DateTimeInterface $starttime The event start time
|
||||
* @param array $max The module's maximum limit for the event
|
||||
*/
|
||||
protected function get_module_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;
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -130,6 +130,10 @@ class day_exporter extends exporter {
|
||||
'type' => calendar_event_exporter::read_properties_definition(),
|
||||
'multiple' => true,
|
||||
],
|
||||
'hasevents' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'default' => false,
|
||||
],
|
||||
'calendareventtypes' => [
|
||||
'type' => PARAM_RAW,
|
||||
'multiple' => true,
|
||||
@@ -211,6 +215,8 @@ class day_exporter extends exporter {
|
||||
return $exporter->export($output);
|
||||
}, $eventexporters);
|
||||
|
||||
$return['hasevents'] = !empty($return['events']);
|
||||
|
||||
$return['calendareventtypes'] = array_map(function($exporter) {
|
||||
return $exporter->get_calendar_event_type();
|
||||
}, $eventexporters);
|
||||
|
||||
@@ -71,13 +71,13 @@
|
||||
data-region="day"
|
||||
data-new-event-timestamp="{{neweventtimestamp}}">
|
||||
<div class="hidden-sm-down text-xs-center">
|
||||
{{#events.0}}
|
||||
{{#hasevents}}
|
||||
<a data-action="view-day-link" href="{{viewdaylink}}" class="day" title="{{viewdaylinktitle}}">{{mday}}</a>
|
||||
{{/events.0}}
|
||||
{{^events.0}}
|
||||
{{/hasevents}}
|
||||
{{^hasevents}}
|
||||
{{mday}}
|
||||
{{/events.0}}
|
||||
{{#events.0}}
|
||||
{{/hasevents}}
|
||||
{{#hasevents}}
|
||||
<div data-region="day-content">
|
||||
<ul>
|
||||
{{#events}}
|
||||
@@ -90,6 +90,18 @@
|
||||
{{#canedit}}
|
||||
draggable="true"
|
||||
data-drag-type="move"
|
||||
{{#mindaytimestamp}}
|
||||
data-min-day-timestamp="{{.}}"
|
||||
{{/mindaytimestamp}}
|
||||
{{#mindayerror}}
|
||||
data-min-day-error="{{.}}"
|
||||
{{/mindayerror}}
|
||||
{{#maxdaytimestamp}}
|
||||
data-max-day-timestamp="{{.}}"
|
||||
{{/maxdaytimestamp}}
|
||||
{{#maxdayerror}}
|
||||
data-max-day-error="{{.}}"
|
||||
{{/maxdayerror}}
|
||||
{{/canedit}}>
|
||||
|
||||
<a data-action="view-event" data-event-id="{{id}}" href="{{url}}" title="{{name}}">
|
||||
@@ -104,17 +116,17 @@
|
||||
{{/events}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/events.0}}
|
||||
{{/hasevents}}
|
||||
</div>
|
||||
<div class="hidden-md-up hidden-desktop">
|
||||
{{#events.0}}
|
||||
{{#hasevents}}
|
||||
<a href="{{viewdaylink}}" class="day" title="{{viewdaylinktitle}}">{{mday}}</a>
|
||||
{{/events.0}}
|
||||
{{^events.0}}
|
||||
{{/hasevents}}
|
||||
{{^hasevents}}
|
||||
<div data-region="day-content">
|
||||
{{mday}}
|
||||
</div>
|
||||
{{/events.0}}
|
||||
{{/hasevents}}
|
||||
</div>
|
||||
</td>
|
||||
{{/days}}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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 exporter tests tests.
|
||||
*
|
||||
* @package core_calendar
|
||||
* @copyright 2017 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_calendar\external\calendar_event_exporter;
|
||||
use core_calendar\local\event\container;
|
||||
|
||||
/**
|
||||
* Calendar event exporter testcase.
|
||||
*
|
||||
* @copyright 2017 Ryan Wyllie <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_calendar_event_exporter_testcase extends advanced_testcase {
|
||||
/**
|
||||
* Data provider for the module timestamp min limit test case to confirm
|
||||
* that the minimum time limit is set correctly on the boundary cases.
|
||||
*/
|
||||
public function get_module_timestamp_min_limit_test_cases() {
|
||||
$now = time();
|
||||
$todaymidnight = usergetmidnight($now);
|
||||
$tomorrowmidnight = $todaymidnight + DAYSECS;
|
||||
$eightam = $todaymidnight + (60 * 60 * 8);
|
||||
$starttime = (new DateTime())->setTimestamp($eightam);
|
||||
|
||||
return [
|
||||
'before min' => [
|
||||
$starttime,
|
||||
[
|
||||
($starttime->getTimestamp() + 1),
|
||||
'some error'
|
||||
],
|
||||
$tomorrowmidnight
|
||||
],
|
||||
'equal min' => [
|
||||
$starttime,
|
||||
[
|
||||
$starttime->getTimestamp(),
|
||||
'some error'
|
||||
],
|
||||
$todaymidnight
|
||||
],
|
||||
'after min' => [
|
||||
$starttime,
|
||||
[
|
||||
($starttime->getTimestamp() - 1),
|
||||
'some error'
|
||||
],
|
||||
$todaymidnight
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider get_module_timestamp_min_limit_test_cases()
|
||||
*/
|
||||
public function test_get_module_timestamp_min_limit($starttime, $min, $expected) {
|
||||
$class = \core_calendar\external\calendar_event_exporter::class;
|
||||
$mock = $this->getMockBuilder($class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(null)
|
||||
->getMock();
|
||||
$reflector = new ReflectionClass($class);
|
||||
$method = $reflector->getMethod('get_module_timestamp_min_limit');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($mock, $starttime, $min);
|
||||
$this->assertEquals($expected, $result['mindaytimestamp']);
|
||||
$this->assertEquals($min[1], $result['mindayerror']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the module timestamp min limit test case to confirm
|
||||
* that the minimum time limit is set correctly on the boundary cases.
|
||||
*/
|
||||
public function get_module_timestamp_max_limit_test_cases() {
|
||||
$now = time();
|
||||
$todaymidnight = usergetmidnight($now);
|
||||
$yesterdaymidnight = $todaymidnight - DAYSECS;
|
||||
$eightam = $todaymidnight + (60 * 60 * 8);
|
||||
$starttime = (new DateTime())->setTimestamp($eightam);
|
||||
|
||||
return [
|
||||
'before max' => [
|
||||
$starttime,
|
||||
[
|
||||
($starttime->getTimestamp() + 1),
|
||||
'some error'
|
||||
],
|
||||
$todaymidnight
|
||||
],
|
||||
'equal max' => [
|
||||
$starttime,
|
||||
[
|
||||
$starttime->getTimestamp(),
|
||||
'some error'
|
||||
],
|
||||
$todaymidnight
|
||||
],
|
||||
'after max' => [
|
||||
$starttime,
|
||||
[
|
||||
($starttime->getTimestamp() - 1),
|
||||
'some error'
|
||||
],
|
||||
$yesterdaymidnight
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider get_module_timestamp_max_limit_test_cases()
|
||||
*/
|
||||
public function test_get_module_timestamp_max_limit($starttime, $max, $expected) {
|
||||
$class = \core_calendar\external\calendar_event_exporter::class;
|
||||
$mock = $this->getMockBuilder($class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(null)
|
||||
->getMock();
|
||||
$reflector = new ReflectionClass($class);
|
||||
$method = $reflector->getMethod('get_module_timestamp_max_limit');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($mock, $starttime, $max);
|
||||
$this->assertEquals($expected, $result['maxdaytimestamp']);
|
||||
$this->assertEquals($max[1], $result['maxdayerror']);
|
||||
}
|
||||
}
|
||||
+163
-3
@@ -1823,8 +1823,7 @@ function assign_check_updates_since(cm_info $cm, $from, $filter = array()) {
|
||||
* Is the event visible?
|
||||
*
|
||||
* This is used to determine global visibility of an event in all places throughout Moodle. For example,
|
||||
* the ASSIGN_EVENT_TYPE_GRADINGDUE event will not be shown to students on their calendar, and
|
||||
* ASSIGN_EVENT_TYPE_DUE events will not be shown to teachers.
|
||||
* the ASSIGN_EVENT_TYPE_GRADINGDUE event will not be shown to students on their calendar.
|
||||
*
|
||||
* @param calendar_event $event
|
||||
* @return bool Returns true if the event is visible to the current user, false otherwise.
|
||||
@@ -1842,7 +1841,7 @@ function mod_assign_core_calendar_is_event_visible(calendar_event $event) {
|
||||
if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
|
||||
return $assign->can_grade();
|
||||
} else {
|
||||
return !$assign->can_grade() && $assign->can_view_submission($USER->id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1887,6 +1886,14 @@ function mod_assign_core_calendar_provide_event_action(calendar_event $event,
|
||||
return null;
|
||||
}
|
||||
|
||||
$participant = $assign->get_participant($USER->id);
|
||||
|
||||
if (!$participant) {
|
||||
// If the user is not a participant in the assignment then they have
|
||||
// no action to take. This will filter out the events for teachers.
|
||||
return null;
|
||||
}
|
||||
|
||||
// The user has not yet submitted anything. Show the addsubmission link.
|
||||
$name = get_string('addsubmission', 'assign');
|
||||
$url = new \moodle_url('/mod/assign/view.php', [
|
||||
@@ -1921,3 +1928,156 @@ function mod_assign_core_calendar_event_action_shows_item_count(calendar_event $
|
||||
// For mod_assign, item count should be shown if the event type is 'gradingdue' and there is one or more item count.
|
||||
return in_array($event->eventtype, $eventtypesshowingitemcount) && $itemcount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function calculates the minimum and maximum cutoff values for the timestart of
|
||||
* the given event.
|
||||
*
|
||||
* It will return an array with two values, the first being the minimum cutoff value and
|
||||
* the second being the maximum cutoff value. Either or both values can be null, which
|
||||
* indicates there is no minimum or maximum, respectively.
|
||||
*
|
||||
* If a cutoff is required then the function must return an array containing the cutoff
|
||||
* timestamp and error string to display to the user if the cutoff value is violated.
|
||||
*
|
||||
* A minimum and maximum cutoff return value will look like:
|
||||
* [
|
||||
* [1505704373, 'The due date must be after the sbumission start date'],
|
||||
* [1506741172, 'The due date must be before the cutoff date']
|
||||
* ]
|
||||
*
|
||||
* @param calendar_event $event The calendar event to get the time range for
|
||||
* @param stdClass|null $instance The module instance to get the range from
|
||||
*/
|
||||
function mod_assign_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance = null) {
|
||||
global $DB;
|
||||
|
||||
if (!$instance) {
|
||||
$instance = $DB->get_record('assign', ['id' => $event->instance]);
|
||||
}
|
||||
|
||||
$coursemodule = get_coursemodule_from_instance('assign',
|
||||
$event->instance,
|
||||
$event->courseid,
|
||||
false,
|
||||
MUST_EXIST);
|
||||
|
||||
if (empty($coursemodule)) {
|
||||
// If we don't have a course module yet then it likely means
|
||||
// the activity is still being set up. In this case there is
|
||||
// nothing for us to do anyway.
|
||||
return;
|
||||
}
|
||||
|
||||
$context = context_module::instance($coursemodule->id);
|
||||
$assign = new assign($context, null, null);
|
||||
$assign->set_instance($instance);
|
||||
|
||||
return $assign->get_valid_calendar_event_timestart_range($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will check that the given event is valid for it's
|
||||
* corresponding assign module instance.
|
||||
*
|
||||
* An exception is thrown if the event fails validation.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param \calendar_event $event
|
||||
* @return bool
|
||||
*/
|
||||
function mod_assign_core_calendar_validate_event_timestart(\calendar_event $event) {
|
||||
global $DB;
|
||||
|
||||
if (!isset($event->instance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to read from the DB directly because course module may
|
||||
// currently be getting created so it won't be in mod info yet.
|
||||
$instance = $DB->get_record('assign', ['id' => $event->instance], '*', MUST_EXIST);
|
||||
$timestart = $event->timestart;
|
||||
list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event, $instance);
|
||||
|
||||
if ($min && $timestart < $min[0]) {
|
||||
throw new \moodle_exception($min[1]);
|
||||
}
|
||||
|
||||
if ($max && $timestart > $max[0]) {
|
||||
throw new \moodle_exception($max[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will update the assign module according to the
|
||||
* event that has been modified.
|
||||
*
|
||||
* @throws \moodle_exception
|
||||
* @param \calendar_event $event
|
||||
*/
|
||||
function mod_assign_core_calendar_event_timestart_updated(\calendar_event $event) {
|
||||
global $DB;
|
||||
|
||||
if (empty($event->instance) || $event->modulename != 'assign') {
|
||||
return;
|
||||
}
|
||||
|
||||
$coursemodule = get_coursemodule_from_instance('assign',
|
||||
$event->instance,
|
||||
$event->courseid,
|
||||
false,
|
||||
MUST_EXIST);
|
||||
|
||||
if (empty($coursemodule)) {
|
||||
// If we don't have a course module yet then it likely means
|
||||
// the activity is still being set up. In this case there is
|
||||
// nothing for us to do anyway.
|
||||
return;
|
||||
}
|
||||
|
||||
$context = context_module::instance($coursemodule->id);
|
||||
|
||||
// The user does not have the capability to modify this activity.
|
||||
if (!has_capability('moodle/course:manageactivities', $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$assign = new assign($context, $coursemodule, null);
|
||||
$modified = false;
|
||||
|
||||
if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
|
||||
// This check is in here because due date events are currently
|
||||
// the only events that can be overridden, so we can save a DB
|
||||
// query if we don't bother checking other events.
|
||||
if ($assign->is_override_calendar_event($event)) {
|
||||
// This is an override event so we should ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
$instance = $assign->get_instance();
|
||||
$newduedate = $event->timestart;
|
||||
|
||||
if ($newduedate != $instance->duedate) {
|
||||
$instance->duedate = $newduedate;
|
||||
$instance->timemodified = time();
|
||||
$modified = true;
|
||||
}
|
||||
} else if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
|
||||
$instance = $assign->get_instance();
|
||||
$newduedate = $event->timestart;
|
||||
|
||||
if ($newduedate != $instance->gradingduedate) {
|
||||
$instance->gradingduedate = $newduedate;
|
||||
$instance->timemodified = time();
|
||||
$modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($modified) {
|
||||
// Persist the assign instance changes.
|
||||
$DB->update_record('assign', $instance);
|
||||
$assign->update_calendar($coursemodule->id);
|
||||
$event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
|
||||
$event->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
+136
-7
@@ -943,6 +943,127 @@ class assign {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given calendar_event is either a user or group override
|
||||
* event.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_override_calendar_event(\calendar_event $event) {
|
||||
global $DB;
|
||||
|
||||
if (!isset($event->modulename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($event->modulename != 'assign') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($event->instance)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($event->userid) && !isset($event->groupid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$overrideparams = [
|
||||
'assignid' => $event->instance
|
||||
];
|
||||
|
||||
if (isset($event->groupid)) {
|
||||
$overrideparams['groupid'] = $event->groupid;
|
||||
} else if (isset($event->userid)) {
|
||||
$overrideparams['userid'] = $event->userid;
|
||||
}
|
||||
|
||||
if ($DB->get_record('assign_overrides', $overrideparams)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function calculates the minimum and maximum cutoff values for the timestart of
|
||||
* the given event.
|
||||
*
|
||||
* It will return an array with two values, the first being the minimum cutoff value and
|
||||
* the second being the maximum cutoff value. Either or both values can be null, which
|
||||
* indicates there is no minimum or maximum, respectively.
|
||||
*
|
||||
* If a cutoff is required then the function must return an array containing the cutoff
|
||||
* timestamp and error string to display to the user if the cutoff value is violated.
|
||||
*
|
||||
* A minimum and maximum cutoff return value will look like:
|
||||
* [
|
||||
* [1505704373, 'The due date must be after the sbumission start date'],
|
||||
* [1506741172, 'The due date must be before the cutoff date']
|
||||
* ]
|
||||
*
|
||||
* @param calendar_event $event The calendar event to get the time range for
|
||||
*/
|
||||
function get_valid_calendar_event_timestart_range(\calendar_event $event) {
|
||||
$instance = $this->get_instance();
|
||||
$submissionsfromdate = $instance->allowsubmissionsfromdate;
|
||||
$cutoffdate = $instance->cutoffdate;
|
||||
$duedate = $instance->duedate;
|
||||
$gradingduedate = $instance->gradingduedate;
|
||||
$mindate = null;
|
||||
$maxdate = null;
|
||||
|
||||
if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
|
||||
// This check is in here because due date events are currently
|
||||
// the only events that can be overridden, so we can save a DB
|
||||
// query if we don't bother checking other events.
|
||||
if ($this->is_override_calendar_event($event)) {
|
||||
// This is an override event so we should ignore it.
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
if ($submissionsfromdate) {
|
||||
$mindate = [
|
||||
$submissionsfromdate,
|
||||
get_string('duedatevalidation', 'assign'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($cutoffdate) {
|
||||
$maxdate = [
|
||||
$cutoffdate,
|
||||
get_string('cutoffdatevalidation', 'assign'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($gradingduedate) {
|
||||
// If we don't have a cutoff date or we've got a grading due date
|
||||
// that is earlier than the cutoff then we should use that as the
|
||||
// upper limit for the due date.
|
||||
if (!$cutoffdate || $gradingduedate < $cutoffdate) {
|
||||
$maxdate = [
|
||||
$gradingduedate,
|
||||
get_string('gradingdueduedatevalidation', 'assign'),
|
||||
];
|
||||
}
|
||||
}
|
||||
} else if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
|
||||
if ($duedate) {
|
||||
$mindate = [
|
||||
$duedate,
|
||||
get_string('gradingdueduedatevalidation', 'assign'),
|
||||
];
|
||||
} else if ($submissionsfromdate) {
|
||||
$mindate = [
|
||||
$submissionsfromdate,
|
||||
get_string('gradingduefromdatevalidation', 'assign'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [$mindate, $maxdate];
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual implementation of the reset course functionality, delete all the
|
||||
* assignment submissions for course $data->courseid.
|
||||
@@ -1915,9 +2036,13 @@ class assign {
|
||||
* @return null|stdClass user record
|
||||
*/
|
||||
public function get_participant($userid) {
|
||||
global $DB;
|
||||
global $DB, $USER;
|
||||
|
||||
$participant = $DB->get_record('user', array('id' => $userid));
|
||||
if ($userid == $USER->id) {
|
||||
$participant = clone ($USER);
|
||||
} else {
|
||||
$participant = $DB->get_record('user', array('id' => $userid));
|
||||
}
|
||||
if (!$participant) {
|
||||
return null;
|
||||
}
|
||||
@@ -5598,11 +5723,15 @@ class assign {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($userid == $graderid &&
|
||||
$this->submissions_open($userid) &&
|
||||
has_capability('mod/assign:submit', $this->context, $graderid)) {
|
||||
// User can edit their own submission.
|
||||
return true;
|
||||
if ($userid == $graderid) {
|
||||
if ($this->submissions_open($userid) &&
|
||||
has_capability('mod/assign:submit', $this->context, $graderid)) {
|
||||
// User can edit their own submission.
|
||||
return true;
|
||||
} else {
|
||||
// We need to return here because editothersubmission should never apply to a users own submission.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_capability('mod/assign:editothersubmission', $this->context, $graderid)) {
|
||||
|
||||
@@ -31,6 +31,9 @@ require_once($CFG->dirroot . '/mod/assign/lib.php');
|
||||
require_once($CFG->dirroot . '/mod/assign/locallib.php');
|
||||
require_once($CFG->dirroot . '/mod/assign/tests/base_test.php');
|
||||
|
||||
use \core_calendar\local\api as calendar_local_api;
|
||||
use \core_calendar\local\event\container as calendar_event_container;
|
||||
|
||||
/**
|
||||
* Unit tests for (some of) mod/assign/lib.php.
|
||||
*
|
||||
@@ -108,7 +111,7 @@ class mod_assign_lib_testcase extends mod_assign_base_testcase {
|
||||
$this->setAdminUser();
|
||||
$courses = $DB->get_records('course', array('id' => $this->course->id));
|
||||
// Past assignments should not show up.
|
||||
$pastassign = $this->create_instance(array('duedate' => time(),
|
||||
$pastassign = $this->create_instance(array('duedate' => time() - 370001,
|
||||
'cutoffdate' => time() - 370000,
|
||||
'nosubmissions' => 0,
|
||||
'assignsubmission_onlinetext_enabled' => 1));
|
||||
@@ -413,8 +416,8 @@ class mod_assign_lib_testcase extends mod_assign_base_testcase {
|
||||
// Set the user to a teacher.
|
||||
$this->setUser($this->editingteachers[0]);
|
||||
|
||||
// The teacher should not care about the due date event.
|
||||
$this->assertFalse(mod_assign_core_calendar_is_event_visible($event));
|
||||
// The teacher should see the due date event.
|
||||
$this->assertTrue(mod_assign_core_calendar_is_event_visible($event));
|
||||
}
|
||||
|
||||
public function test_assign_core_calendar_is_event_visible_duedate_event_as_student() {
|
||||
@@ -483,12 +486,8 @@ class mod_assign_lib_testcase extends mod_assign_base_testcase {
|
||||
// Decorate action event.
|
||||
$actionevent = mod_assign_core_calendar_provide_event_action($event, $factory);
|
||||
|
||||
// Confirm the event was decorated.
|
||||
$this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
|
||||
$this->assertEquals(get_string('addsubmission', 'assign'), $actionevent->get_name());
|
||||
$this->assertInstanceOf('moodle_url', $actionevent->get_url());
|
||||
$this->assertEquals(1, $actionevent->get_item_count());
|
||||
$this->assertFalse($actionevent->is_actionable());
|
||||
// The teacher should not have an action for a due date event.
|
||||
$this->assertNull($actionevent);
|
||||
}
|
||||
|
||||
public function test_assign_core_calendar_provide_event_action_duedate_as_student() {
|
||||
@@ -684,4 +683,866 @@ class mod_assign_lib_testcase extends mod_assign_base_testcase {
|
||||
$this->assertEquals($student0grade->grade, 5);
|
||||
$this->assertEquals($student1grade->grade, ASSIGN_GRADE_NOT_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false when there are not overrides for this assign instance.
|
||||
*/
|
||||
public function test_assign_is_override_calendar_event_no_override() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$userid = 1234;
|
||||
$duedate = time();
|
||||
$assign = $this->create_instance(['duedate' => $duedate]);
|
||||
|
||||
$instance = $assign->get_instance();
|
||||
$event = new \calendar_event((object)[
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'userid' => $userid
|
||||
]);
|
||||
|
||||
$this->assertFalse($assign->is_override_calendar_event($event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false if the given event isn't an assign module event.
|
||||
*/
|
||||
public function test_assign_is_override_calendar_event_no_nodule_event() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$userid = $this->students[0]->id;
|
||||
$duedate = time();
|
||||
$assign = $this->create_instance(['duedate' => $duedate]);
|
||||
|
||||
$instance = $assign->get_instance();
|
||||
$event = new \calendar_event((object)[
|
||||
'userid' => $userid
|
||||
]);
|
||||
|
||||
$this->assertFalse($assign->is_override_calendar_event($event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false if there is overrides for this use but they belong to another assign
|
||||
* instance.
|
||||
*/
|
||||
public function test_assign_is_override_calendar_event_different_assign_instance() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$userid = 1234;
|
||||
$duedate = time();
|
||||
$assign = $this->create_instance(['duedate' => $duedate]);
|
||||
$assign2 = $this->create_instance(['duedate' => $duedate]);
|
||||
|
||||
$instance = $assign->get_instance();
|
||||
$event = new \calendar_event((object) [
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'userid' => $userid
|
||||
]);
|
||||
|
||||
$record = (object) [
|
||||
'assignid' => $assign2->get_instance()->id,
|
||||
'userid' => $userid
|
||||
];
|
||||
|
||||
$DB->insert_record('assign_overrides', $record);
|
||||
|
||||
$this->assertFalse($assign->is_override_calendar_event($event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is a user override for this event and assign instance.
|
||||
*/
|
||||
public function test_assign_is_override_calendar_event_user_override() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$userid = 1234;
|
||||
$duedate = time();
|
||||
$assign = $this->create_instance(['duedate' => $duedate]);
|
||||
|
||||
$instance = $assign->get_instance();
|
||||
$event = new \calendar_event((object) [
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'userid' => $userid
|
||||
]);
|
||||
|
||||
$record = (object) [
|
||||
'assignid' => $instance->id,
|
||||
'userid' => $userid
|
||||
];
|
||||
|
||||
$DB->insert_record('assign_overrides', $record);
|
||||
|
||||
$this->assertTrue($assign->is_override_calendar_event($event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is a group override for the event and assign instance.
|
||||
*/
|
||||
public function test_assign_is_override_calendar_event_group_override() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$assign = $this->create_instance(['duedate' => $duedate]);
|
||||
$instance = $assign->get_instance();
|
||||
$group = $this->getDataGenerator()->create_group(array('courseid' => $instance->course));
|
||||
$groupid = $group->id;
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'groupid' => $groupid
|
||||
]);
|
||||
|
||||
$record = (object) [
|
||||
'assignid' => $instance->id,
|
||||
'groupid' => $groupid
|
||||
];
|
||||
|
||||
$DB->insert_record('assign_overrides', $record);
|
||||
|
||||
$this->assertTrue($assign->is_override_calendar_event($event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown event types should not have any limit restrictions returned.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_get_valid_event_timestart_range_unkown_event_type() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$assign = $this->create_instance(['duedate' => $duedate]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => 'SOME RANDOM EVENT'
|
||||
]);
|
||||
|
||||
list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event);
|
||||
$this->assertNull($min);
|
||||
$this->assertNull($max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override events should not have any limit restrictions returned.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_get_valid_event_timestart_range_override_event() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$assign = $this->create_instance(['duedate' => $duedate]);
|
||||
$instance = $assign->get_instance();
|
||||
$userid = $this->students[0]->id;
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'userid' => $userid,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE
|
||||
]);
|
||||
|
||||
$record = (object) [
|
||||
'assignid' => $instance->id,
|
||||
'userid' => $userid
|
||||
];
|
||||
|
||||
$DB->insert_record('assign_overrides', $record);
|
||||
|
||||
list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event);
|
||||
$this->assertNull($min);
|
||||
$this->assertNull($max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assignments configured without a submissions from and cutoff date should not have
|
||||
* any limits applied.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_no_limit() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => 0,
|
||||
'cutoffdate' => 0,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
$userid = $this->students[0]->id;
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE
|
||||
]);
|
||||
|
||||
list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event);
|
||||
$this->assertNull($min);
|
||||
$this->assertNull($max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assignments should be bottom and top bound by the submissions from date and cutoff date
|
||||
* respectively.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_get_valid_event_timestart_range_due_with_limits() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$submissionsfromdate = $duedate - DAYSECS;
|
||||
$cutoffdate = $duedate + DAYSECS;
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => $submissionsfromdate,
|
||||
'cutoffdate' => $cutoffdate,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
$userid = $this->students[0]->id;
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE
|
||||
]);
|
||||
|
||||
list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event);
|
||||
$this->assertEquals($submissionsfromdate, $min[0]);
|
||||
$this->assertNotEmpty($min[1]);
|
||||
$this->assertEquals($cutoffdate, $max[0]);
|
||||
$this->assertNotEmpty($max[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assignment grading due date should not have any limits of no due date and cutoff date is set.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_no_limit() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => 0,
|
||||
'allowsubmissionsfromdate' => 0,
|
||||
'cutoffdate' => 0,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
|
||||
]);
|
||||
|
||||
list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event);
|
||||
$this->assertNull($min);
|
||||
$this->assertNull($max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assignment grading due event is minimum bound by the due date, if it is set.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_get_valid_event_timestart_range_gradingdue_with_due_date() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
$userid = $this->students[0]->id;
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE
|
||||
]);
|
||||
|
||||
list($min, $max) = mod_assign_core_calendar_get_valid_event_timestart_range($event);
|
||||
$this->assertEquals($duedate, $min[0]);
|
||||
$this->assertNotEmpty($min[1]);
|
||||
$this->assertNull($max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calendar events without and instance id should be ignored by the validate
|
||||
* event function.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_validate_event_timestart_no_instance_id() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'modulename' => 'assign',
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE
|
||||
]);
|
||||
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
// The function above throws an exception so all we need to do is make sure
|
||||
// it gets here and that is considered success.
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calendar events for an unknown instance should throw an exception.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_validate_event_timestart_no_instance_found() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'modulename' => 'assign',
|
||||
'instance' => 1234,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE
|
||||
]);
|
||||
|
||||
$this->expectException('moodle_exception');
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assignments configured without any limits on the due date should not
|
||||
* throw an exception.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_validate_event_timestart_no_limit() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => 0,
|
||||
'allowsubmissionsfromdate' => 0,
|
||||
'cutoffdate' => 0,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE,
|
||||
'timestart' => time()
|
||||
]);
|
||||
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
// The function above throws an exception so all we need to do is make sure
|
||||
// it gets here and that is considered success.
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Due date events with a timestart equal to or greater than the minimum limit
|
||||
* should not throw an exception. Timestart values below the minimum limit should
|
||||
* throw an exception.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_validate_due_event_min_limit() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$submissionsfromdate = $duedate - DAYSECS;
|
||||
$cutoffdate = $duedate + DAYSECS;
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => $submissionsfromdate,
|
||||
'cutoffdate' => $cutoffdate,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE,
|
||||
'timestart' => $submissionsfromdate + 1,
|
||||
]);
|
||||
|
||||
// No exception when new time is above minimum cutoff.
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
$this->assertTrue(true);
|
||||
|
||||
// No exception when new time is equal to minimum cutoff.
|
||||
$event->timestart = $submissionsfromdate;
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
$this->assertTrue(true);
|
||||
|
||||
// Exception when new time is earlier than minimum cutoff.
|
||||
$event->timestart = $submissionsfromdate - 1;
|
||||
$this->expectException('moodle_exception');
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* A due date event with a timestart less than or equal to the max limit should
|
||||
* not throw an exception. A timestart greater than the max limit should throw
|
||||
* an exception.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_validate_due_event_max_limit() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$submissionsfromdate = $duedate - DAYSECS;
|
||||
$cutoffdate = $duedate + DAYSECS;
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => $submissionsfromdate,
|
||||
'cutoffdate' => $cutoffdate,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE,
|
||||
'timestart' => $cutoffdate - 1,
|
||||
]);
|
||||
|
||||
// No exception when new time is below maximum cutoff.
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
$this->assertTrue(true);
|
||||
|
||||
// No exception when new time is equal to maximum cutoff.
|
||||
$event->timestart = $cutoffdate;
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
$this->assertTrue(true);
|
||||
|
||||
// Exception when new time is later than maximum cutoff.
|
||||
$event->timestart = $submissionsfromdate - 1;
|
||||
$this->expectException('moodle_exception');
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Due date override events should not throw an exception.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_validate_due_event_override() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$submissionsfromdate = $duedate - DAYSECS;
|
||||
$cutoffdate = $duedate + DAYSECS;
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => $submissionsfromdate,
|
||||
'cutoffdate' => $cutoffdate,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
$userid = $this->students[0]->id;
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'userid' => $userid,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE,
|
||||
'timestart' => $duedate + (2 * DAYSECS)
|
||||
]);
|
||||
|
||||
$record = (object) [
|
||||
'assignid' => $instance->id,
|
||||
'userid' => $userid,
|
||||
'duedate' => $duedate + (2 * DAYSECS)
|
||||
];
|
||||
|
||||
$DB->insert_record('assign_overrides', $record);
|
||||
|
||||
// No exception when dealing with an override.
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grading due date event should throw an exception if it's timestart is less than the
|
||||
* assignment due date.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_validate_gradingdue_event_min_limit_duedate() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$submissionsfromdate = $duedate - DAYSECS;
|
||||
$cutoffdate = $duedate + DAYSECS;
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => $submissionsfromdate,
|
||||
'cutoffdate' => $cutoffdate,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE,
|
||||
'timestart' => $duedate + 1,
|
||||
]);
|
||||
|
||||
// No exception when new time is above minimum cutoff.
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
$this->assertTrue(true);
|
||||
|
||||
// No exception when new time is equal to minimum cutoff.
|
||||
$event->timestart = $duedate;
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
$this->assertTrue(true);
|
||||
|
||||
// Exception when new time is earlier than minimum cutoff.
|
||||
$event->timestart = $duedate - 1;
|
||||
$this->expectException('moodle_exception');
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grading due date event should throw an exception if it's timestart is less than the
|
||||
* submissions allowed from date if there is no due date set.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_validate_gradingdue_event_min_limit_submissionsfromdate() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = 0;
|
||||
$submissionsfromdate = time() - DAYSECS;
|
||||
$cutoffdate = time() + DAYSECS;
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => $submissionsfromdate,
|
||||
'cutoffdate' => $cutoffdate,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE,
|
||||
'timestart' => $submissionsfromdate + 1,
|
||||
]);
|
||||
|
||||
// No exception when new time is above minimum cutoff.
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
$this->assertTrue(true);
|
||||
|
||||
// No exception when new time is equal to minimum cutoff.
|
||||
$event->timestart = $submissionsfromdate;
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
$this->assertTrue(true);
|
||||
|
||||
// Exception when new time is earlier than minimum cutoff.
|
||||
$event->timestart = $submissionsfromdate - 1;
|
||||
$this->expectException('moodle_exception');
|
||||
mod_assign_core_calendar_validate_event_timestart($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non due date events should not update the assignment due date.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_event_timestart_updated_non_due_event() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$submissionsfromdate = $duedate - DAYSECS;
|
||||
$cutoffdate = $duedate + DAYSECS;
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => $submissionsfromdate,
|
||||
'cutoffdate' => $cutoffdate,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_GRADINGDUE,
|
||||
'timestart' => $duedate + 1
|
||||
]);
|
||||
|
||||
mod_assign_core_calendar_event_timestart_updated($event);
|
||||
|
||||
$newinstance = $DB->get_record('assign', ['id' => $instance->id]);
|
||||
$this->assertEquals($duedate, $newinstance->duedate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Due date override events should not change the assignment due date.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_event_timestart_updated_due_event_override() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$submissionsfromdate = $duedate - DAYSECS;
|
||||
$cutoffdate = $duedate + DAYSECS;
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => $submissionsfromdate,
|
||||
'cutoffdate' => $cutoffdate,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
$userid = $this->students[0]->id;
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'userid' => $userid,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE,
|
||||
'timestart' => $duedate + 1
|
||||
]);
|
||||
|
||||
$record = (object) [
|
||||
'assignid' => $instance->id,
|
||||
'userid' => $userid,
|
||||
'duedate' => $duedate + 1
|
||||
];
|
||||
|
||||
$DB->insert_record('assign_overrides', $record);
|
||||
|
||||
mod_assign_core_calendar_event_timestart_updated($event);
|
||||
|
||||
$newinstance = $DB->get_record('assign', ['id' => $instance->id]);
|
||||
$this->assertEquals($duedate, $newinstance->duedate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Due date events should update the assignment due date.
|
||||
*/
|
||||
public function test_mod_assign_core_calendar_event_timestart_updated_due_event() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$duedate = time();
|
||||
$newduedate = $duedate + 1;
|
||||
$submissionsfromdate = $duedate - DAYSECS;
|
||||
$cutoffdate = $duedate + DAYSECS;
|
||||
$assign = $this->create_instance([
|
||||
'duedate' => $duedate,
|
||||
'allowsubmissionsfromdate' => $submissionsfromdate,
|
||||
'cutoffdate' => $cutoffdate,
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$event = new \calendar_event((object) [
|
||||
'courseid' => $instance->course,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE,
|
||||
'timestart' => $newduedate
|
||||
]);
|
||||
|
||||
mod_assign_core_calendar_event_timestart_updated($event);
|
||||
|
||||
$newinstance = $DB->get_record('assign', ['id' => $instance->id]);
|
||||
$this->assertEquals($newduedate, $newinstance->duedate);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a student somehow finds a way to update the due date calendar event
|
||||
* then the callback should not be executed to update the assignment due
|
||||
* date as well otherwise that would be a security issue.
|
||||
*/
|
||||
public function test_student_role_cant_update_due_event() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$mapper = calendar_event_container::get_event_mapper();
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$context = context_course::instance($course->id);
|
||||
$roleid = $generator->create_role();
|
||||
$now = time();
|
||||
$duedate = (new DateTime())->setTimestamp($now);
|
||||
$newduedate = (new DateTime())->setTimestamp($now)->modify('+1 day');
|
||||
$assign = $this->create_instance([
|
||||
'course' => $course->id,
|
||||
'duedate' => $duedate->getTimestamp(),
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$generator->enrol_user($user->id, $course->id, 'student');
|
||||
$generator->role_assign($roleid, $user->id, $context->id);
|
||||
|
||||
$record = $DB->get_record('event', [
|
||||
'courseid' => $course->id,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE
|
||||
]);
|
||||
|
||||
$event = new \calendar_event($record);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
|
||||
assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleid, $context, true);
|
||||
|
||||
$this->setUser($user);
|
||||
|
||||
calendar_local_api::update_event_start_day(
|
||||
$mapper->from_legacy_event_to_event($event),
|
||||
$newduedate
|
||||
);
|
||||
|
||||
$newinstance = $DB->get_record('assign', ['id' => $instance->id]);
|
||||
$newevent = \calendar_event::load($event->id);
|
||||
// The due date shouldn't have changed even though we updated the calendar
|
||||
// event.
|
||||
$this->assertEquals($duedate->getTimestamp(), $newinstance->duedate);
|
||||
$this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
|
||||
}
|
||||
|
||||
/**
|
||||
* A teacher with the capability to modify an assignment module should be
|
||||
* able to update the assignment due date by changing the due date calendar
|
||||
* event.
|
||||
*/
|
||||
public function test_teacher_role_can_update_due_event() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$mapper = calendar_event_container::get_event_mapper();
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$context = context_course::instance($course->id);
|
||||
$roleid = $generator->create_role();
|
||||
$now = time();
|
||||
$duedate = (new DateTime())->setTimestamp($now);
|
||||
$newduedate = (new DateTime())->setTimestamp($now)->modify('+1 day');
|
||||
$assign = $this->create_instance([
|
||||
'course' => $course->id,
|
||||
'duedate' => $duedate->getTimestamp(),
|
||||
]);
|
||||
$instance = $assign->get_instance();
|
||||
|
||||
$generator->enrol_user($user->id, $course->id, 'teacher');
|
||||
$generator->role_assign($roleid, $user->id, $context->id);
|
||||
|
||||
$record = $DB->get_record('event', [
|
||||
'courseid' => $course->id,
|
||||
'modulename' => 'assign',
|
||||
'instance' => $instance->id,
|
||||
'eventtype' => ASSIGN_EVENT_TYPE_DUE
|
||||
]);
|
||||
|
||||
$event = new \calendar_event($record);
|
||||
|
||||
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
|
||||
assign_capability('moodle/course:manageactivities', CAP_ALLOW, $roleid, $context, true);
|
||||
|
||||
$this->setUser($user);
|
||||
// Trigger and capture the event when adding a contact.
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
calendar_local_api::update_event_start_day(
|
||||
$mapper->from_legacy_event_to_event($event),
|
||||
$newduedate
|
||||
);
|
||||
|
||||
$triggeredevents = $sink->get_events();
|
||||
$moduleupdatedevents = array_filter($triggeredevents, function($e) {
|
||||
return is_a($e, 'core\event\course_module_updated');
|
||||
});
|
||||
|
||||
$newinstance = $DB->get_record('assign', ['id' => $instance->id]);
|
||||
$newevent = \calendar_event::load($event->id);
|
||||
// The due date shouldn't have changed even though we updated the calendar
|
||||
// event.
|
||||
$this->assertEquals($newduedate->getTimestamp(), $newinstance->duedate);
|
||||
$this->assertEquals($newduedate->getTimestamp(), $newevent->timestart);
|
||||
// Confirm that a module updated event is fired when the module
|
||||
// is changed.
|
||||
$this->assertNotEmpty($moduleupdatedevents);
|
||||
}
|
||||
}
|
||||
|
||||
+94
-19
@@ -1240,6 +1240,55 @@ function mod_choice_core_calendar_provide_event_action(calendar_event $event,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function calculates the minimum and maximum cutoff values for the timestart of
|
||||
* the given event.
|
||||
*
|
||||
* It will return an array with two values, the first being the minimum cutoff value and
|
||||
* the second being the maximum cutoff value. Either or both values can be null, which
|
||||
* indicates there is no minimum or maximum, respectively.
|
||||
*
|
||||
* If a cutoff is required then the function must return an array containing the cutoff
|
||||
* timestamp and error string to display to the user if the cutoff value is violated.
|
||||
*
|
||||
* A minimum and maximum cutoff return value will look like:
|
||||
* [
|
||||
* [1505704373, 'The date must be after this date'],
|
||||
* [1506741172, 'The date must be before this date']
|
||||
* ]
|
||||
*
|
||||
* @param calendar_event $event The calendar event to get the time range for
|
||||
* @param stdClass|null $instance The module instance to get the range from
|
||||
*/
|
||||
function mod_choice_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $choice = null) {
|
||||
global $DB;
|
||||
|
||||
if (!$choice) {
|
||||
$choice = $DB->get_record('choice', ['id' => $event->instance]);
|
||||
}
|
||||
|
||||
$mindate = null;
|
||||
$maxdate = null;
|
||||
|
||||
if ($event->eventtype == CHOICE_EVENT_TYPE_OPEN) {
|
||||
if (!empty($choice->timeclose)) {
|
||||
$maxdate = [
|
||||
$choice->timeclose,
|
||||
get_string('openafterclose', 'choice')
|
||||
];
|
||||
}
|
||||
} else if ($event->eventtype == CHOICE_EVENT_TYPE_CLOSE) {
|
||||
if (!empty($choice->timeopen)) {
|
||||
$mindate = [
|
||||
$choice->timeopen,
|
||||
get_string('closebeforeopen', 'choice')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [$mindate, $maxdate];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will check that the given event is valid for it's
|
||||
* corresponding choice module.
|
||||
@@ -1253,23 +1302,23 @@ function mod_choice_core_calendar_provide_event_action(calendar_event $event,
|
||||
function mod_choice_core_calendar_validate_event_timestart(\calendar_event $event) {
|
||||
global $DB;
|
||||
|
||||
$record = $DB->get_record('choice', ['id' => $event->instance], '*', MUST_EXIST);
|
||||
|
||||
if ($event->eventtype == CHOICE_EVENT_TYPE_OPEN) {
|
||||
// The start time of the open event can't be equal to or after the
|
||||
// close time of the choice activity.
|
||||
if (!empty($record->timeclose) && $event->timestart > $record->timeclose) {
|
||||
throw new \moodle_exception('openafterclose', 'choice');
|
||||
}
|
||||
} else if ($event->eventtype == CHOICE_EVENT_TYPE_CLOSE) {
|
||||
// The start time of the close event can't be equal to or earlier than the
|
||||
// open time of the choice activity.
|
||||
if (!empty($record->timeopen) && $event->timestart < $record->timeopen) {
|
||||
throw new \moodle_exception('closebeforeopen', 'choice');
|
||||
}
|
||||
if (!isset($event->instance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
// We need to read from the DB directly because course module may
|
||||
// currently be getting created so it won't be in mod info yet.
|
||||
$instance = $DB->get_record('choice', ['id' => $event->instance], '*', MUST_EXIST);
|
||||
$timestart = $event->timestart;
|
||||
list($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $instance);
|
||||
|
||||
if ($min && $timestart < $min[0]) {
|
||||
throw new \moodle_exception($min[1]);
|
||||
}
|
||||
|
||||
if ($max && $timestart > $max[0]) {
|
||||
throw new \moodle_exception($max[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1285,29 +1334,55 @@ function mod_choice_core_calendar_validate_event_timestart(\calendar_event $even
|
||||
function mod_choice_core_calendar_event_timestart_updated(\calendar_event $event) {
|
||||
global $DB;
|
||||
|
||||
$courseid = $event->courseid;
|
||||
$modulename = $event->modulename;
|
||||
$instanceid = $event->instance;
|
||||
$modified = false;
|
||||
|
||||
// Something weird going on. The event is for a different module so
|
||||
// we should ignore it.
|
||||
if ($modulename != 'choice') {
|
||||
return;
|
||||
}
|
||||
|
||||
$coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
|
||||
$context = context_module::instance($coursemodule->id);
|
||||
|
||||
// The user does not have the capability to modify this activity.
|
||||
if (!has_capability('moodle/course:manageactivities', $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->eventtype == CHOICE_EVENT_TYPE_OPEN) {
|
||||
// If the event is for the choice activity opening then we should
|
||||
// set the start time of the choice activity to be the new start
|
||||
// time of the event.
|
||||
$record = $DB->get_record('choice', ['id' => $event->instance], '*', MUST_EXIST);
|
||||
$record = $DB->get_record('choice', ['id' => $instanceid], '*', MUST_EXIST);
|
||||
|
||||
if ($record->timeopen != $event->timestart) {
|
||||
$record->timeopen = $event->timestart;
|
||||
$record->timemodified = time();
|
||||
$DB->update_record('choice', $record);
|
||||
$modified = true;
|
||||
}
|
||||
} else if ($event->eventtype == CHOICE_EVENT_TYPE_CLOSE) {
|
||||
// If the event is for the choice activity closing then we should
|
||||
// set the end time of the choice activity to be the new start
|
||||
// time of the event.
|
||||
$record = $DB->get_record('choice', ['id' => $event->instance], '*', MUST_EXIST);
|
||||
$record = $DB->get_record('choice', ['id' => $instanceid], '*', MUST_EXIST);
|
||||
|
||||
if ($record->timeclose != $event->timestart) {
|
||||
$record->timeclose = $event->timestart;
|
||||
$record->timemodified = time();
|
||||
$DB->update_record('choice', $record);
|
||||
$modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($modified) {
|
||||
// Persist the instance changes.
|
||||
$DB->update_record('choice', $record);
|
||||
$event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
|
||||
$event->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -554,7 +554,10 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
|
||||
'visible' => 1
|
||||
]);
|
||||
|
||||
$this->assertTrue(mod_choice_core_calendar_validate_event_timestart($event));
|
||||
mod_choice_core_calendar_validate_event_timestart($event);
|
||||
// The function above will throw an exception if the event is
|
||||
// invalid.
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -630,7 +633,10 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
|
||||
'visible' => 1
|
||||
]);
|
||||
|
||||
$this->assertTrue(mod_choice_core_calendar_validate_event_timestart($event));
|
||||
mod_choice_core_calendar_validate_event_timestart($event);
|
||||
// The function above will throw an exception if the event isn't
|
||||
// valid.
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -754,8 +760,16 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
|
||||
'visible' => 1
|
||||
]);
|
||||
|
||||
// Trigger and capture the event when adding a contact.
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
mod_choice_core_calendar_event_timestart_updated($event);
|
||||
|
||||
$triggeredevents = $sink->get_events();
|
||||
$moduleupdatedevents = array_filter($triggeredevents, function($e) {
|
||||
return is_a($e, 'core\event\course_module_updated');
|
||||
});
|
||||
|
||||
$choice = $DB->get_record('choice', ['id' => $choice->id]);
|
||||
// Ensure the timeopen property matches the event timestart.
|
||||
$this->assertEquals($newtimeopen, $choice->timeopen);
|
||||
@@ -763,6 +777,9 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals($timeclose, $choice->timeclose);
|
||||
// Ensure the timemodified property has been changed.
|
||||
$this->assertNotEquals($timemodified, $choice->timemodified);
|
||||
// Confirm that a module updated event is fired when the module
|
||||
// is changed.
|
||||
$this->assertNotEmpty($moduleupdatedevents);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -804,8 +821,16 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
|
||||
'visible' => 1
|
||||
]);
|
||||
|
||||
// Trigger and capture the event when adding a contact.
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
mod_choice_core_calendar_event_timestart_updated($event);
|
||||
|
||||
$triggeredevents = $sink->get_events();
|
||||
$moduleupdatedevents = array_filter($triggeredevents, function($e) {
|
||||
return is_a($e, 'core\event\course_module_updated');
|
||||
});
|
||||
|
||||
$choice = $DB->get_record('choice', ['id' => $choice->id]);
|
||||
// Ensure the timeclose property matches the event timestart.
|
||||
$this->assertEquals($newtimeclose, $choice->timeclose);
|
||||
@@ -813,5 +838,140 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertEquals($timeopen, $choice->timeopen);
|
||||
// Ensure the timemodified property has been changed.
|
||||
$this->assertNotEquals($timemodified, $choice->timemodified);
|
||||
// Confirm that a module updated event is fired when the module
|
||||
// is changed.
|
||||
$this->assertNotEmpty($moduleupdatedevents);
|
||||
}
|
||||
|
||||
/**
|
||||
* An unkown event type should not have any limits
|
||||
*/
|
||||
public function test_mod_choice_core_calendar_get_valid_event_timestart_range_unknown_event() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . "/calendar/lib.php");
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
$timeopen = time();
|
||||
$timeclose = $timeopen + DAYSECS;
|
||||
$choice = new \stdClass();
|
||||
$choice->timeopen = $timeopen;
|
||||
$choice->timeclose = $timeclose;
|
||||
|
||||
// Create a valid event.
|
||||
$event = new \calendar_event([
|
||||
'name' => 'Test event',
|
||||
'description' => '',
|
||||
'format' => 1,
|
||||
'courseid' => $course->id,
|
||||
'groupid' => 0,
|
||||
'userid' => 2,
|
||||
'modulename' => 'choice',
|
||||
'instance' => 1,
|
||||
'eventtype' => CHOICE_EVENT_TYPE_OPEN . "SOMETHING ELSE",
|
||||
'timestart' => 1,
|
||||
'timeduration' => 86400,
|
||||
'visible' => 1
|
||||
]);
|
||||
|
||||
list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
|
||||
$this->assertNull($min);
|
||||
$this->assertNull($max);
|
||||
}
|
||||
|
||||
/**
|
||||
* The open event should be limited by the choice's timeclose property, if it's set.
|
||||
*/
|
||||
public function test_mod_choice_core_calendar_get_valid_event_timestart_range_open_event() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . "/calendar/lib.php");
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
$timeopen = time();
|
||||
$timeclose = $timeopen + DAYSECS;
|
||||
$choice = new \stdClass();
|
||||
$choice->timeopen = $timeopen;
|
||||
$choice->timeclose = $timeclose;
|
||||
|
||||
// Create a valid event.
|
||||
$event = new \calendar_event([
|
||||
'name' => 'Test event',
|
||||
'description' => '',
|
||||
'format' => 1,
|
||||
'courseid' => $course->id,
|
||||
'groupid' => 0,
|
||||
'userid' => 2,
|
||||
'modulename' => 'choice',
|
||||
'instance' => 1,
|
||||
'eventtype' => CHOICE_EVENT_TYPE_OPEN,
|
||||
'timestart' => 1,
|
||||
'timeduration' => 86400,
|
||||
'visible' => 1
|
||||
]);
|
||||
|
||||
// The max limit should be bounded by the timeclose value.
|
||||
list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
|
||||
|
||||
$this->assertNull($min);
|
||||
$this->assertEquals($timeclose, $max[0]);
|
||||
|
||||
// No timeclose value should result in no upper limit.
|
||||
$choice->timeclose = 0;
|
||||
list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
|
||||
|
||||
$this->assertNull($min);
|
||||
$this->assertNull($max);
|
||||
}
|
||||
|
||||
/**
|
||||
* The close event should be limited by the choice's timeopen property, if it's set.
|
||||
*/
|
||||
public function test_mod_choice_core_calendar_get_valid_event_timestart_range_close_event() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . "/calendar/lib.php");
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
$timeopen = time();
|
||||
$timeclose = $timeopen + DAYSECS;
|
||||
$choice = new \stdClass();
|
||||
$choice->timeopen = $timeopen;
|
||||
$choice->timeclose = $timeclose;
|
||||
|
||||
// Create a valid event.
|
||||
$event = new \calendar_event([
|
||||
'name' => 'Test event',
|
||||
'description' => '',
|
||||
'format' => 1,
|
||||
'courseid' => $course->id,
|
||||
'groupid' => 0,
|
||||
'userid' => 2,
|
||||
'modulename' => 'choice',
|
||||
'instance' => 1,
|
||||
'eventtype' => CHOICE_EVENT_TYPE_CLOSE,
|
||||
'timestart' => 1,
|
||||
'timeduration' => 86400,
|
||||
'visible' => 1
|
||||
]);
|
||||
|
||||
// The max limit should be bounded by the timeclose value.
|
||||
list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
|
||||
|
||||
$this->assertEquals($timeopen, $min[0]);
|
||||
$this->assertNull($max);
|
||||
|
||||
// No timeclose value should result in no upper limit.
|
||||
$choice->timeopen = 0;
|
||||
list ($min, $max) = mod_choice_core_calendar_get_valid_event_timestart_range($event, $choice);
|
||||
|
||||
$this->assertNull($min);
|
||||
$this->assertNull($max);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,3 +297,7 @@
|
||||
background-color: darken(@blue, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.bg-faded {
|
||||
background-color: @grayLighter;
|
||||
}
|
||||
|
||||
@@ -21942,3 +21942,6 @@ ul.indented-list {
|
||||
.bg-primary[href] {
|
||||
background-color: #0378a9;
|
||||
}
|
||||
.bg-faded {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user