mirror of
https://github.com/moodle/moodle.git
synced 2025-08-02 15:49:43 +02:00
Merge branch 'MDL-59890-master' of git://github.com/andrewnicols/moodle
This commit is contained in:
commit
cd7cd9d9c1
66 changed files with 1410 additions and 123 deletions
|
@ -38,40 +38,37 @@ class block_calendar_month extends block_base {
|
|||
public function get_content() {
|
||||
global $CFG;
|
||||
|
||||
$calm = optional_param('cal_m', 0, PARAM_INT);
|
||||
$caly = optional_param('cal_y', 0, PARAM_INT);
|
||||
$time = optional_param('time', 0, PARAM_INT);
|
||||
|
||||
require_once($CFG->dirroot.'/calendar/lib.php');
|
||||
|
||||
if ($this->content !== null) {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
// If a day, month and year were passed then convert it to a timestamp. If these were passed then we can assume
|
||||
// the day, month and year are passed as Gregorian, as no where in core should we be passing these values rather
|
||||
// than the time. This is done for BC.
|
||||
if (!empty($calm) && (!empty($caly))) {
|
||||
$time = make_timestamp($caly, $calm, 1);
|
||||
} else if (empty($time)) {
|
||||
$time = time();
|
||||
}
|
||||
|
||||
$this->content = new stdClass;
|
||||
$this->content->text = '';
|
||||
$this->content->footer = '';
|
||||
|
||||
// [pj] To me it looks like this if would never be needed, but Penny added it
|
||||
// when committing the /my/ stuff. Reminder to discuss and learn what it's about.
|
||||
// It definitely needs SOME comment here!
|
||||
$courseid = $this->page->course->id;
|
||||
$issite = ($courseid == SITEID);
|
||||
|
||||
$course = null;
|
||||
$courses = null;
|
||||
$categories = null;
|
||||
|
||||
if ($issite) {
|
||||
// Being displayed at site level. This will cause the filter to fall back to auto-detecting
|
||||
// the list of courses it will be grabbing events from.
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
|
||||
if ($this->page->context->contextlevel === CONTEXT_COURSECAT) {
|
||||
// Restrict to categories, and their parents, and the courses that the user is enrolled in within those
|
||||
// categories.
|
||||
$categories = array_keys($this->page->categories);
|
||||
$courses = array_filter($courses, function($course) use ($categories) {
|
||||
return array_search($course->category, $categories) !== false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Forcibly filter events to include only those from the particular course we are in.
|
||||
$course = $this->page->course;
|
||||
|
@ -80,8 +77,8 @@ class block_calendar_month extends block_base {
|
|||
|
||||
$renderer = $this->page->get_renderer('core_calendar');
|
||||
|
||||
$calendar = new calendar_information(0, 0, 0, $time);
|
||||
$calendar->prepare_for_view($course, $courses);
|
||||
$calendar = new calendar_information();
|
||||
$calendar->set_sources($course, $courses, $this->page->category);
|
||||
|
||||
list($data, $template) = calendar_get_view($calendar, 'mini');
|
||||
$this->content->text .= $renderer->render_from_template($template, $data);
|
||||
|
|
2
calendar/amd/build/calendar.min.js
vendored
2
calendar/amd/build/calendar.min.js
vendored
|
@ -1 +1 @@
|
|||
define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){var n={ROOT:"[data-region='calendar']",DAY:"[data-region='day']",EVENT_ITEM:"[data-region='event-item']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']",DAY_CONTENT:"[data-region='day-content']",LOADING_ICON:".loading-icon",VIEW_DAY_LINK:"[data-action='view-day-link']",CALENDAR_MONTH_WRAPPER:".calendarwrapper",COURSE_SELECTOR:'select[name="course"]',TODAY:".today"},o=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},p=function(a){k.getEventById(a).then(function(b){if(!b.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+a);var c=b.event;return o(c.eventtype).then(function(a){return c.eventtype=a,c})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete,isactionevent:a.isactionevent,url:a.url}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},q=function(b,c,f,g){var h=null,i=g.attr("data-day-timestamp");f&&(h=f.attr("data-day-timestamp")),f&&h==i||d.render("core/loading",{}).then(function(a,b){g.find(n.DAY_CONTENT).addClass("hidden"),d.appendNodeContents(g,a,b),f&&(f.find(n.DAY_CONTENT).addClass("hidden"),d.appendNodeContents(f,a,b))}).then(function(){return k.updateEventStartDay(c,i)}).then(function(){a("body").trigger(l.eventMoved,[c,f,g])}).always(function(){var a=g.find(n.LOADING_ICON);if(g.find(n.DAY_CONTENT).removeClass("hidden"),d.replaceNode(a,"",""),f){var b=f.find(n.LOADING_ICON);f.find(n.DAY_CONTENT).removeClass("hidden"),d.replaceNode(b,"","")}}).fail(e.exception)},r=function(a){var b=a.find(n.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}})},s=function(b,c){var d=a("body"),f=a(b).find(n.CALENDAR_MONTH_WRAPPER).data("courseid");d.on(l.created,function(){m.reloadCurrentMonth(b)}),d.on(l.deleted,function(){m.reloadCurrentMonth(b)}),d.on(l.updated,function(){m.reloadCurrentMonth(b)}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),d.on(l.moveEvent,q),d.on(l.eventMoved,function(){m.reloadCurrentMonth(b)}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()}),a.setCourseId(f)}).fail(e.exception)},t=function(b){b.on("click",n.EVENT_ITEM,function(b){b.preventDefault(),b.stopPropagation();var c=a(b.target),d=null;d=c.is(n.EVENT_LINK)?c.attr("data-event-id"):c.find(n.EVENT_LINK).attr("data-event-id"),p(d)}),b.on("change",n.COURSE_SELECTOR,function(){var c=a(this),d=c.val();m.reloadCurrentMonth(b,d).then(function(){return b.find(n.COURSE_SELECTOR).val(d)}).fail(e.exception)});var c=r(b);s(b,c),b.on("click",n.NEW_EVENT_BUTTON,function(a){c.then(function(a){var c=b.find(n.TODAY);c.length||a.setStartTime(b.find(n.DAY).attr("data-new-event-timestamp")),a.show()}).fail(e.exception),a.preventDefault()}),b.on("click",n.DAY,function(b){var d=a(b.target);if(!d.is(n.VIEW_DAY_LINK)){var f=a(this).attr("data-new-event-timestamp");c.then(function(a){a.setStartTime(f),a.show()}).fail(e.exception),b.preventDefault()}})};return{init:function(b){b=a(b),m.init(b),t(b)}}});
|
||||
define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){var n={ROOT:"[data-region='calendar']",DAY:"[data-region='day']",EVENT_ITEM:"[data-region='event-item']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']",DAY_CONTENT:"[data-region='day-content']",LOADING_ICON:".loading-icon",VIEW_DAY_LINK:"[data-action='view-day-link']",CALENDAR_MONTH_WRAPPER:".calendarwrapper",COURSE_SELECTOR:'select[name="course"]',TODAY:".today"},o=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},p=function(a){k.getEventById(a).then(function(b){if(!b.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+a);var c=b.event;return o(c.eventtype).then(function(a){return c.eventtype=a,c})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete,isactionevent:a.isactionevent,url:a.url}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},q=function(b,c,f,g){var h=null,i=g.attr("data-day-timestamp");f&&(h=f.attr("data-day-timestamp")),f&&h==i||d.render("core/loading",{}).then(function(a,b){g.find(n.DAY_CONTENT).addClass("hidden"),d.appendNodeContents(g,a,b),f&&(f.find(n.DAY_CONTENT).addClass("hidden"),d.appendNodeContents(f,a,b))}).then(function(){return k.updateEventStartDay(c,i)}).then(function(){a("body").trigger(l.eventMoved,[c,f,g])}).always(function(){var a=g.find(n.LOADING_ICON);if(g.find(n.DAY_CONTENT).removeClass("hidden"),d.replaceNode(a,"",""),f){var b=f.find(n.LOADING_ICON);f.find(n.DAY_CONTENT).removeClass("hidden"),d.replaceNode(b,"","")}}).fail(e.exception)},r=function(a){var b=a.find(n.NEW_EVENT_BUTTON),c=b.attr("data-context-id");return h.create({type:i.TYPE,large:!0,templateContext:{contextid:c}})},s=function(b,c){var d=a("body");d.on(l.created,function(){m.reloadCurrentMonth(b)}),d.on(l.deleted,function(){m.reloadCurrentMonth(b)}),d.on(l.updated,function(){m.reloadCurrentMonth(b)}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),d.on(l.moveEvent,q),d.on(l.eventMoved,function(){m.reloadCurrentMonth(b)}),c.then(function(a){d.on(l.editEvent,function(b,c){a.setEventId(c),a.show()})}).fail(e.exception)},t=function(b){b.on("click",n.EVENT_ITEM,function(b){b.preventDefault(),b.stopPropagation();var c=a(b.target),d=null;d=c.is(n.EVENT_LINK)?c.attr("data-event-id"):c.find(n.EVENT_LINK).attr("data-event-id"),p(d)}),b.on("change",n.COURSE_SELECTOR,function(){var c=a(this),d=c.val();m.reloadCurrentMonth(b,d,null).then(function(){return b.find(n.COURSE_SELECTOR).val(d)}).fail(e.exception)});var c=r(b);s(b,c),f.define(b,[f.events.activate]),b.on("click",n.NEW_EVENT_BUTTON,function(a){c.then(function(a){var c=b.find(n.CALENDAR_MONTH_WRAPPER);a.setCourseId(c.data("courseid"));var d=c.data("categoryid");"undefined"!=typeof d&&a.setCategoryId(d);var e=b.find(n.TODAY);e.length||a.setStartTime(b.find(n.DAY).attr("data-new-event-timestamp")),a.show()}).fail(e.exception),a.preventDefault()}),b.on("click",n.DAY,function(b){var d=a(b.target);if(!d.is(n.VIEW_DAY_LINK)){var f=a(this).attr("data-new-event-timestamp");c.then(function(a){var b=d.closest(n.CALENDAR_MONTH_WRAPPER);a.setCourseId(b.data("courseid"));var c=b.data("categoryid");"undefined"!=typeof c&&a.setCategoryId(c),a.setStartTime(f),a.show()}).fail(e.exception),b.preventDefault()}})};return{init:function(b){b=a(b),m.init(b),t(b)}}});
|
|
@ -1 +1 @@
|
|||
define(["jquery","core_calendar/selectors","core_calendar/events","core/templates","core_calendar/view_manager"],function(a,b,c,d,e){var f=function(d){var f=a("body");f.on(c.monthChanged,function(a,b,c,e){d.queue(function(d){return g(a,b,c,e).then(function(){return d()})})});var g=function(c,f,g,h){var i=d.find('[data-year="'+f+'"][data-month="'+g+'"]'),j=i.closest(b.calendarPeriods.month),k=d.find(b.calendarPeriods.month),l=a(k[0]),m=a(k[2]),n=a("<span>");n.attr("data-template","core_calendar/threemonth_month"),n.attr("data-includenavigation",!1);var o=a("<div>");o.hide(),o.append(n);var p,q,r;return j.is(l)?(o.insertBefore(l),p=l.data("previousYear"),q=l.data("previousMonth"),r=m):j.is(m)&&(o.insertAfter(m),p=m.data("nextYear"),q=m.data("nextMonth"),r=l),e.refreshMonthContent(n,p,q,h,n).then(function(){var b=a.Deferred(),c=a.Deferred();return r.slideUp("fast",function(){a(this).remove(),b.resolve()}),o.slideDown("fast",function(){c.resolve()}),a.when(b,c)})}};return{init:function(b){b=a(b),f(b)}}});
|
||||
define(["jquery","core/notification","core_calendar/selectors","core_calendar/events","core/templates","core_calendar/view_manager"],function(a,b,c,d,e,f){var g=function(e){var g=a("body");g.on(d.monthChanged,function(a,c,d,f,g){e.queue(function(e){return h(a,c,d,f,g).then(function(){return e()}).fail(b.exception)})});var h=function(b,d,g,h,i){var j=e.find('[data-year="'+d+'"][data-month="'+g+'"]'),k=j.closest(c.calendarPeriods.month),l=e.find(c.calendarPeriods.month),m=a(l[0]),n=a(l[2]),o=a("<span>");o.attr("data-template","core_calendar/threemonth_month"),o.attr("data-includenavigation",!1);var p=a("<div>");p.hide(),p.append(o);var q,r,s;return k.is(m)?(p.insertBefore(m),q=m.data("previousYear"),r=m.data("previousMonth"),s=n):k.is(n)&&(p.insertAfter(n),q=n.data("nextYear"),r=n.data("nextMonth"),s=m),f.refreshMonthContent(o,q,r,h,i,o).then(function(){var b=a.Deferred(),c=a.Deferred();return s.slideUp("fast",function(){a(this).remove(),b.resolve()}),p.slideDown("fast",function(){c.resolve()}),a.when(b,c)})}};return{init:function(b){b=a(b),g(b)}}});
|
2
calendar/amd/build/modal_event_form.min.js
vendored
2
calendar/amd/build/modal_event_form.min.js
vendored
|
@ -1 +1 @@
|
|||
define(["jquery","core/event","core/str","core/notification","core/templates","core/custom_interaction_events","core/modal","core/modal_registry","core/fragment","core_calendar/events","core_calendar/repository"],function(a,b,c,d,e,f,g,h,i,j,k){var l=!1,m={SAVE_BUTTON:'[data-action="save"]',LOADING_ICON_CONTAINER:'[data-region="loading-icon-container"]'},n=function(a){g.call(this,a),this.eventId=null,this.startTime=null,this.courseId=null,this.reloadingBody=!1,this.reloadingTitle=!1,this.saveButton=this.getFooter().find(m.SAVE_BUTTON)};return n.TYPE="core_calendar-modal_event_form",n.prototype=Object.create(g.prototype),n.prototype.constructor=n,n.prototype.setCourseId=function(a){this.courseId=a},n.prototype.getCourseId=function(){return this.courseId},n.prototype.hasCourseId=function(){return null!==this.courseId},n.prototype.setEventId=function(a){this.eventId=a},n.prototype.getEventId=function(){return this.eventId},n.prototype.hasEventId=function(){return null!==this.eventId},n.prototype.setStartTime=function(a){this.startTime=a},n.prototype.getStartTime=function(){return this.startTime},n.prototype.hasStartTime=function(){return null!==this.startTime},n.prototype.getForm=function(){return this.getBody().find("form")},n.prototype.disableButtons=function(){this.saveButton.prop("disabled",!0)},n.prototype.enableButtons=function(){this.saveButton.prop("disabled",!1)},n.prototype.reloadTitleContent=function(){return this.reloadingTitle?this.titlePromise:(this.reloadingTitle=!0,this.hasEventId()?this.titlePromise=c.get_string("editevent","calendar"):this.titlePromise=c.get_string("newevent","calendar"),this.titlePromise.then(function(a){return this.setTitle(a),a}.bind(this)).always(function(){this.reloadingTitle=!1}.bind(this)),this.titlePromise)},n.prototype.reloadBodyContent=function(a){if(this.reloadingBody)return this.bodyPromise;this.reloadingBody=!0,this.disableButtons();var b=this.saveButton.attr("data-context-id"),c={};return this.hasEventId()&&(c.eventid=this.getEventId()),this.hasStartTime()&&(c.starttime=this.getStartTime()),this.hasCourseId()&&(c.courseid=this.getCourseId()),"undefined"!=typeof a&&(c.formdata=a),this.bodyPromise=i.loadFragment("calendar","event_form",b,c),this.setBody(this.bodyPromise),this.bodyPromise.then(function(){this.enableButtons()}.bind(this))["catch"](d.exception).always(function(){this.reloadingBody=!1}.bind(this)),this.bodyPromise},n.prototype.reloadAllContent=function(){return a.when(this.reloadTitleContent(),this.reloadBodyContent())},n.prototype.show=function(){this.reloadAllContent(),g.prototype.show.call(this)},n.prototype.hide=function(){g.prototype.hide.call(this),this.setEventId(null),this.setStartTime(null)},n.prototype.getFormData=function(){return this.getForm().serialize()},n.prototype.save=function(){var b=this.saveButton.find(m.LOADING_ICON_CONTAINER);b.removeClass("hidden"),this.disableButtons();var c=this.getFormData();return k.submitCreateUpdateForm(c).then(function(b){return b.validationerror?this.reloadBodyContent(c):(this.hide(),void(this.hasEventId()?a("body").trigger(j.updated,[b.event]):a("body").trigger(j.created,[b.event])))}.bind(this)).always(function(){b.addClass("hidden"),this.enableButtons()}.bind(this))["catch"](d.exception)},n.prototype.registerEventListeners=function(){g.prototype.registerEventListeners.call(this),this.getModal().on(f.events.activate,m.SAVE_BUTTON,function(a,b){this.getForm().submit(),b.originalEvent.preventDefault(),a.stopPropagation()}.bind(this)),this.getModal().on("submit",function(a){this.save(),a.preventDefault(),a.stopPropagation()}.bind(this))},l||(h.register(n.TYPE,n,"calendar/modal_event_form"),l=!0),n});
|
||||
define(["jquery","core/event","core/str","core/notification","core/templates","core/custom_interaction_events","core/modal","core/modal_registry","core/fragment","core_calendar/events","core_calendar/repository"],function(a,b,c,d,e,f,g,h,i,j,k){var l=!1,m={SAVE_BUTTON:'[data-action="save"]',LOADING_ICON_CONTAINER:'[data-region="loading-icon-container"]'},n=function(a){g.call(this,a),this.eventId=null,this.startTime=null,this.courseId=null,this.categoryId=null,this.reloadingBody=!1,this.reloadingTitle=!1,this.saveButton=this.getFooter().find(m.SAVE_BUTTON)};return n.TYPE="core_calendar-modal_event_form",n.prototype=Object.create(g.prototype),n.prototype.constructor=n,n.prototype.setCourseId=function(a){this.courseId=a},n.prototype.getCourseId=function(){return this.courseId},n.prototype.setCategoryId=function(a){this.categoryId=a},n.prototype.getCategoryId=function(){return this.categoryId},n.prototype.hasCourseId=function(){return null!==this.courseId},n.prototype.hasCategoryId=function(){return null!==this.categoryId},n.prototype.setEventId=function(a){this.eventId=a},n.prototype.getEventId=function(){return this.eventId},n.prototype.hasEventId=function(){return null!==this.eventId},n.prototype.setStartTime=function(a){this.startTime=a},n.prototype.getStartTime=function(){return this.startTime},n.prototype.hasStartTime=function(){return null!==this.startTime},n.prototype.getForm=function(){return this.getBody().find("form")},n.prototype.disableButtons=function(){this.saveButton.prop("disabled",!0)},n.prototype.enableButtons=function(){this.saveButton.prop("disabled",!1)},n.prototype.reloadTitleContent=function(){return this.reloadingTitle?this.titlePromise:(this.reloadingTitle=!0,this.hasEventId()?this.titlePromise=c.get_string("editevent","calendar"):this.titlePromise=c.get_string("newevent","calendar"),this.titlePromise.then(function(a){return this.setTitle(a),a}.bind(this)).always(function(){this.reloadingTitle=!1}.bind(this)).fail(d.exception),this.titlePromise)},n.prototype.reloadBodyContent=function(a){if(this.reloadingBody)return this.bodyPromise;this.reloadingBody=!0,this.disableButtons();var b=this.saveButton.attr("data-context-id"),c={};return this.hasEventId()&&(c.eventid=this.getEventId()),this.hasStartTime()&&(c.starttime=this.getStartTime()),this.hasCourseId()&&(c.courseid=this.getCourseId()),this.hasCategoryId()&&(c.categoryid=this.getCategoryId()),"undefined"!=typeof a&&(c.formdata=a),this.bodyPromise=i.loadFragment("calendar","event_form",b,c),this.setBody(this.bodyPromise),this.bodyPromise.then(function(){this.enableButtons()}.bind(this)).fail(d.exception).always(function(){this.reloadingBody=!1}.bind(this)).fail(d.exception),this.bodyPromise},n.prototype.reloadAllContent=function(){return a.when(this.reloadTitleContent(),this.reloadBodyContent())},n.prototype.show=function(){this.reloadAllContent(),g.prototype.show.call(this)},n.prototype.hide=function(){g.prototype.hide.call(this),this.setEventId(null),this.setStartTime(null),this.setCourseId(null),this.setCategoryId(null)},n.prototype.getFormData=function(){return this.getForm().serialize()},n.prototype.save=function(){var b=this.saveButton.find(m.LOADING_ICON_CONTAINER);b.removeClass("hidden"),this.disableButtons();var c=this.getFormData();return k.submitCreateUpdateForm(c).then(function(b){return b.validationerror?void this.reloadBodyContent(c):(this.hide(),void(this.hasEventId()?a("body").trigger(j.updated,[b.event]):a("body").trigger(j.created,[b.event])))}.bind(this)).always(function(){b.addClass("hidden"),this.enableButtons()}.bind(this)).fail(d.exception)},n.prototype.registerEventListeners=function(){g.prototype.registerEventListeners.call(this),this.getModal().on(f.events.activate,m.SAVE_BUTTON,function(a,b){this.getForm().submit(),b.originalEvent.preventDefault(),a.stopPropagation()}.bind(this)),this.getModal().on("submit",function(a){this.save(),a.preventDefault(),a.stopPropagation()}.bind(this))},l||(h.register(n.TYPE,n,"calendar/modal_event_form"),l=!0),n});
|
2
calendar/amd/build/repository.min.js
vendored
2
calendar/amd/build/repository.min.js
vendored
|
@ -1 +1 @@
|
|||
define(["jquery","core/ajax"],function(a,b){var c=function(a,c){"undefined"==typeof c&&(c=!1);var d={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:c}]}};return b.call([d])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]},f=function(a,c,d,e){var f={methodname:"core_calendar_get_calendar_monthly_view",args:{year:a,month:c,courseid:d,includenavigation:e}};return b.call([f])[0]},g=function(a,c){var d={methodname:"core_calendar_update_event_start_day",args:{eventid:a,daytimestamp:c}};return b.call([d])[0]};return{getEventById:d,deleteEvent:c,updateEventStartDay:g,submitCreateUpdateForm:e,getCalendarMonthData:f}});
|
||||
define(["jquery","core/ajax"],function(a,b){var c=function(a,c){"undefined"==typeof c&&(c=!1);var d={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:c}]}};return b.call([d])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]},f=function(a,c,d,e,f){var g={methodname:"core_calendar_get_calendar_monthly_view",args:{year:a,month:c,courseid:d,categoryid:e,includenavigation:f}};return b.call([g])[0]},g=function(a,c){var d={methodname:"core_calendar_update_event_start_day",args:{eventid:a,daytimestamp:c}};return b.call([d])[0]};return{getEventById:d,deleteEvent:c,updateEventStartDay:g,submitCreateUpdateForm:e,getCalendarMonthData:f}});
|
2
calendar/amd/build/selectors.min.js
vendored
2
calendar/amd/build/selectors.min.js
vendored
|
@ -1 +1 @@
|
|||
define([],function(){return{eventFilterItem:"[data-action='filter-event-type']",eventType:{site:"[data-eventtype-site]",course:"[data-eventtype-course]",group:"[data-eventtype-group]",user:"[data-eventtype-user]"},popoverType:{site:"[data-popover-eventtype-site]",course:"[data-popover-eventtype-course]",group:"[data-popover-eventtype-group]",user:"[data-popover-eventtype-user]"},calendarPeriods:{month:"[data-period='month']"}}});
|
||||
define([],function(){return{eventFilterItem:"[data-action='filter-event-type']",eventType:{site:"[data-eventtype-site]",category:"[data-eventtype-category]",course:"[data-eventtype-course]",group:"[data-eventtype-group]",user:"[data-eventtype-user]"},popoverType:{site:"[data-popover-eventtype-site]",category:"[data-popover-eventtype-category]",course:"[data-popover-eventtype-course]",group:"[data-popover-eventtype-group]",user:"[data-popover-eventtype-user]"},calendarPeriods:{month:"[data-period='month']"}}});
|
2
calendar/amd/build/view_manager.min.js
vendored
2
calendar/amd/build/view_manager.min.js
vendored
|
@ -1 +1 @@
|
|||
define(["jquery","core/templates","core/notification","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e){var f={ROOT:"[data-region='calendar']",CALENDAR_NAV_LINK:".calendarwrapper .arrow_link",CALENDAR_MONTH_WRAPPER:".calendarwrapper",LOADING_ICON_CONTAINER:'[data-region="overlay-icon-container"]'},g=function(b){b=a(b),b.on("click",f.CALENDAR_NAV_LINK,function(c){var d=a(b).find(f.CALENDAR_MONTH_WRAPPER).data("courseid"),e=a(c.currentTarget);i(b,e.attr("href"),e.data("year"),e.data("month"),d),c.preventDefault()})},h=function(g,h,i,j,m){k(g),m=m||g.find(f.CALENDAR_MONTH_WRAPPER),M.util.js_pending([g.get("id"),h,i,j].join("-"));var n=g.data("includenavigation");return d.getCalendarMonthData(h,i,j,n).then(function(a){return b.render(g.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(m,a,c)}).then(function(){a("body").trigger(e.viewUpdated)}).always(function(){return M.util.js_complete([g.get("id"),h,i,j].join("-")),l(g)}).fail(c.exception)},i=function(b,c,d,f,g){return h(b,d,f,g).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(e.monthChanged,[d,f,g]),arguments})},j=function(a,b){var c=a.find(f.CALENDAR_MONTH_WRAPPER).data("year"),d=a.find(f.CALENDAR_MONTH_WRAPPER).data("month");return b||(b=a.find(f.CALENDAR_MONTH_WRAPPER).data("courseid")),h(a,c,d,b)},k=function(a){var b=a.find(f.LOADING_ICON_CONTAINER);b.removeClass("hidden")},l=function(a){var b=a.find(f.LOADING_ICON_CONTAINER);b.addClass("hidden")};return{init:function(a){g(a)},reloadCurrentMonth:j,changeMonth:i,refreshMonthContent:h}});
|
||||
define(["jquery","core/templates","core/notification","core_calendar/repository","core_calendar/events"],function(a,b,c,d,e){var f={ROOT:"[data-region='calendar']",CALENDAR_NAV_LINK:".calendarwrapper .arrow_link",CALENDAR_MONTH_WRAPPER:".calendarwrapper",LOADING_ICON_CONTAINER:'[data-region="overlay-icon-container"]'},g=function(b){b=a(b),b.on("click",f.CALENDAR_NAV_LINK,function(c){var d=b.find(f.CALENDAR_MONTH_WRAPPER),e=d.data("courseid"),g=d.data("categoryid"),h=a(c.currentTarget);i(b,h.attr("href"),h.data("year"),h.data("month"),e,g),c.preventDefault()})},h=function(g,h,i,j,m,n){k(g),n=n||g.find(f.CALENDAR_MONTH_WRAPPER),M.util.js_pending([g.get("id"),h,i,j].join("-"));var o=g.data("includenavigation");return d.getCalendarMonthData(h,i,j,m,o).then(function(a){return b.render(g.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(n,a,c)}).then(function(){a("body").trigger(e.viewUpdated)}).always(function(){return M.util.js_complete([g.get("id"),h,i,j].join("-")),l(g)}).fail(c.exception)},i=function(b,c,d,f,g,i){return h(b,d,f,g,i).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(e.monthChanged,[d,f,g,i]),arguments})},j=function(a,b,c){var d=a.find(f.CALENDAR_MONTH_WRAPPER).data("year"),e=a.find(f.CALENDAR_MONTH_WRAPPER).data("month");return"undefined"==typeof b&&(b=a.find(f.CALENDAR_MONTH_WRAPPER).data("courseid")),"undefined"==typeof c&&(c=a.find(f.CALENDAR_MONTH_WRAPPER).data("categoryid")),h(a,d,e,b,c)},k=function(a){var b=a.find(f.LOADING_ICON_CONTAINER);b.removeClass("hidden")},l=function(a){var b=a.find(f.LOADING_ICON_CONTAINER);b.addClass("hidden")};return{init:function(a){g(a)},reloadCurrentMonth:j,changeMonth:i,refreshMonthContent:h}});
|
|
@ -223,8 +223,7 @@ define([
|
|||
* @param {object} eventFormModalPromise A promise reolved with the event form modal
|
||||
*/
|
||||
var registerCalendarEventListeners = function(root, eventFormModalPromise) {
|
||||
var body = $('body'),
|
||||
courseId = $(root).find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('courseid');
|
||||
var body = $('body');
|
||||
|
||||
body.on(CalendarEvents.created, function() {
|
||||
CalendarViewManager.reloadCurrentMonth(root);
|
||||
|
@ -246,14 +245,14 @@ define([
|
|||
CalendarViewManager.reloadCurrentMonth(root);
|
||||
});
|
||||
|
||||
eventFormModalPromise.then(function(modal) {
|
||||
eventFormModalPromise
|
||||
.then(function(modal) {
|
||||
// When something within the calendar tells us the user wants
|
||||
// to edit an event then show the event form modal.
|
||||
body.on(CalendarEvents.editEvent, function(e, eventId) {
|
||||
modal.setEventId(eventId);
|
||||
modal.show();
|
||||
});
|
||||
modal.setCourseId(courseId);
|
||||
return;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
|
@ -287,7 +286,7 @@ define([
|
|||
root.on('change', SELECTORS.COURSE_SELECTOR, function() {
|
||||
var selectElement = $(this);
|
||||
var courseId = selectElement.val();
|
||||
CalendarViewManager.reloadCurrentMonth(root, courseId)
|
||||
CalendarViewManager.reloadCurrentMonth(root, courseId, null)
|
||||
.then(function() {
|
||||
// We need to get the selector again because the content has changed.
|
||||
return root.find(SELECTORS.COURSE_SELECTOR).val(courseId);
|
||||
|
@ -299,8 +298,16 @@ define([
|
|||
registerCalendarEventListeners(root, eventFormPromise);
|
||||
|
||||
// Bind click event on the new event button.
|
||||
CustomEvents.define(root, [CustomEvents.events.activate]);
|
||||
root.on('click', SELECTORS.NEW_EVENT_BUTTON, function(e) {
|
||||
eventFormPromise.then(function(modal) {
|
||||
var wrapper = root.find(SELECTORS.CALENDAR_MONTH_WRAPPER);
|
||||
modal.setCourseId(wrapper.data('courseid'));
|
||||
var categoryId = wrapper.data('categoryid');
|
||||
if (typeof categoryId !== 'undefined') {
|
||||
modal.setCategoryId(categoryId);
|
||||
}
|
||||
|
||||
// Attempt to find the cell for today.
|
||||
// If it can't be found, then use the start time of the first day on the calendar.
|
||||
var today = root.find(SELECTORS.TODAY);
|
||||
|
@ -323,6 +330,15 @@ define([
|
|||
if (!target.is(SELECTORS.VIEW_DAY_LINK)) {
|
||||
var startTime = $(this).attr('data-new-event-timestamp');
|
||||
eventFormPromise.then(function(modal) {
|
||||
var wrapper = target.closest(SELECTORS.CALENDAR_MONTH_WRAPPER);
|
||||
|
||||
modal.setCourseId(wrapper.data('courseid'));
|
||||
|
||||
var categoryId = wrapper.data('categoryid');
|
||||
if (typeof categoryId !== 'undefined') {
|
||||
modal.setCategoryId(categoryId);
|
||||
}
|
||||
|
||||
modal.setStartTime(startTime);
|
||||
modal.show();
|
||||
return;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
*/
|
||||
define([
|
||||
'jquery',
|
||||
'core/notification',
|
||||
'core_calendar/selectors',
|
||||
'core_calendar/events',
|
||||
'core/templates',
|
||||
|
@ -31,6 +32,7 @@ define([
|
|||
],
|
||||
function(
|
||||
$,
|
||||
Notification,
|
||||
CalendarSelectors,
|
||||
CalendarEvents,
|
||||
Templates,
|
||||
|
@ -45,18 +47,20 @@ function(
|
|||
*/
|
||||
var registerCalendarEventListeners = function(root) {
|
||||
var body = $('body');
|
||||
body.on(CalendarEvents.monthChanged, function(e, year, month, courseId) {
|
||||
body.on(CalendarEvents.monthChanged, function(e, year, month, courseId, categoryId) {
|
||||
// We have to use a queue here because the calling code is decoupled from these listeners.
|
||||
// It's possible for the event to be called multiple times before one call is fully resolved.
|
||||
root.queue(function(next) {
|
||||
return processRequest(e, year, month, courseId)
|
||||
return processRequest(e, year, month, courseId, categoryId)
|
||||
.then(function() {
|
||||
return next();
|
||||
});
|
||||
})
|
||||
.fail(Notification.exception)
|
||||
;
|
||||
});
|
||||
});
|
||||
|
||||
var processRequest = function(e, year, month, courseId) {
|
||||
var processRequest = function(e, year, month, courseId, categoryId) {
|
||||
var newCurrentMonth = root.find('[data-year="' + year + '"][data-month="' + month + '"]');
|
||||
var newParent = newCurrentMonth.closest(CalendarSelectors.calendarPeriods.month);
|
||||
var allMonths = root.find(CalendarSelectors.calendarPeriods.month);
|
||||
|
@ -95,6 +99,7 @@ function(
|
|||
requestYear,
|
||||
requestMonth,
|
||||
courseId,
|
||||
categoryId,
|
||||
placeHolder
|
||||
)
|
||||
.then(function() {
|
||||
|
|
|
@ -65,6 +65,7 @@ define([
|
|||
this.eventId = null;
|
||||
this.startTime = null;
|
||||
this.courseId = null;
|
||||
this.categoryId = null;
|
||||
this.reloadingBody = false;
|
||||
this.reloadingTitle = false;
|
||||
this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);
|
||||
|
@ -94,6 +95,26 @@ define([
|
|||
return this.courseId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the category id to the given value.
|
||||
*
|
||||
* @method setCategoryId
|
||||
* @param {int} id The event id
|
||||
*/
|
||||
ModalEventForm.prototype.setCategoryId = function(id) {
|
||||
this.categoryId = id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the current category id, if any.
|
||||
*
|
||||
* @method getCategoryId
|
||||
* @return {int|null} The event id
|
||||
*/
|
||||
ModalEventForm.prototype.getCategoryId = function() {
|
||||
return this.categoryId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the modal has an course id.
|
||||
*
|
||||
|
@ -104,6 +125,16 @@ define([
|
|||
return this.courseId !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the modal has an category id.
|
||||
*
|
||||
* @method hasCategoryId
|
||||
* @return {bool}
|
||||
*/
|
||||
ModalEventForm.prototype.hasCategoryId = function() {
|
||||
return this.categoryId !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the event id to the given value.
|
||||
*
|
||||
|
@ -220,7 +251,8 @@ define([
|
|||
.always(function() {
|
||||
this.reloadingTitle = false;
|
||||
return;
|
||||
}.bind(this));
|
||||
}.bind(this))
|
||||
.fail(Notification.exception);
|
||||
|
||||
return this.titlePromise;
|
||||
};
|
||||
|
@ -261,6 +293,10 @@ define([
|
|||
args.courseid = this.getCourseId();
|
||||
}
|
||||
|
||||
if (this.hasCategoryId()) {
|
||||
args.categoryid = this.getCategoryId();
|
||||
}
|
||||
|
||||
if (typeof formData !== 'undefined') {
|
||||
args.formdata = formData;
|
||||
}
|
||||
|
@ -273,11 +309,12 @@ define([
|
|||
this.enableButtons();
|
||||
return;
|
||||
}.bind(this))
|
||||
.catch(Notification.exception)
|
||||
.fail(Notification.exception)
|
||||
.always(function() {
|
||||
this.reloadingBody = false;
|
||||
return;
|
||||
}.bind(this));
|
||||
}.bind(this))
|
||||
.fail(Notification.exception);
|
||||
|
||||
return this.bodyPromise;
|
||||
};
|
||||
|
@ -321,6 +358,8 @@ define([
|
|||
Modal.prototype.hide.call(this);
|
||||
this.setEventId(null);
|
||||
this.setStartTime(null);
|
||||
this.setCourseId(null);
|
||||
this.setCategoryId(null);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -361,7 +400,8 @@ define([
|
|||
// If there was a server side validation error then
|
||||
// we need to re-request the rendered form from the server
|
||||
// in order to display the error for the user.
|
||||
return this.reloadBodyContent(formData);
|
||||
this.reloadBodyContent(formData);
|
||||
return;
|
||||
} else {
|
||||
// No problemo! Our work here is done.
|
||||
this.hide();
|
||||
|
@ -382,8 +422,10 @@ define([
|
|||
// the loading icon and re-enable the buttons.
|
||||
loadingContainer.addClass('hidden');
|
||||
this.enableButtons();
|
||||
|
||||
return;
|
||||
}.bind(this))
|
||||
.catch(Notification.exception);
|
||||
.fail(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,7 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
|
|||
*
|
||||
* @method deleteEvent
|
||||
* @param {int} eventId The event id.
|
||||
* @arapm {bool} deleteSeries Whether to delete all events in the series
|
||||
* @param {bool} deleteSeries Whether to delete all events in the series
|
||||
* @return {promise} Resolved with requested calendar event
|
||||
*/
|
||||
var deleteEvent = function(eventId, deleteSeries) {
|
||||
|
@ -93,16 +93,18 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
|
|||
* @param {Number} year Year
|
||||
* @param {Number} month Month
|
||||
* @param {Number} courseid The course id.
|
||||
* @param {Number} categoryid The category id.
|
||||
* @param {Bool} includenavigation Whether to include navigation.
|
||||
* @return {promise} Resolved with the month view data.
|
||||
*/
|
||||
var getCalendarMonthData = function(year, month, courseid, includenavigation) {
|
||||
var getCalendarMonthData = function(year, month, courseid, categoryid, includenavigation) {
|
||||
var request = {
|
||||
methodname: 'core_calendar_get_calendar_monthly_view',
|
||||
args: {
|
||||
year: year,
|
||||
month: month,
|
||||
courseid: courseid,
|
||||
categoryid: categoryid,
|
||||
includenavigation: includenavigation,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -26,12 +26,14 @@ define([], function() {
|
|||
eventFilterItem: "[data-action='filter-event-type']",
|
||||
eventType: {
|
||||
site: "[data-eventtype-site]",
|
||||
category: "[data-eventtype-category]",
|
||||
course: "[data-eventtype-course]",
|
||||
group: "[data-eventtype-group]",
|
||||
user: "[data-eventtype-user]",
|
||||
},
|
||||
popoverType: {
|
||||
site: "[data-popover-eventtype-site]",
|
||||
category: "[data-popover-eventtype-category]",
|
||||
course: "[data-popover-eventtype-course]",
|
||||
group: "[data-popover-eventtype-group]",
|
||||
user: "[data-popover-eventtype-user]",
|
||||
|
|
|
@ -40,9 +40,11 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito
|
|||
root = $(root);
|
||||
|
||||
root.on('click', SELECTORS.CALENDAR_NAV_LINK, function(e) {
|
||||
var courseId = $(root).find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('courseid');
|
||||
var wrapper = root.find(SELECTORS.CALENDAR_MONTH_WRAPPER);
|
||||
var courseId = wrapper.data('courseid');
|
||||
var categoryId = wrapper.data('categoryid');
|
||||
var link = $(e.currentTarget);
|
||||
changeMonth(root, link.attr('href'), link.data('year'), link.data('month'), courseId);
|
||||
changeMonth(root, link.attr('href'), link.data('year'), link.data('month'), courseId, categoryId);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
@ -55,17 +57,18 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito
|
|||
* @param {Number} year Year
|
||||
* @param {Number} month Month
|
||||
* @param {Number} courseid The id of the course whose events are shown
|
||||
* @param {Number} categoryid The id of the category whose events are shown
|
||||
* @param {object} target The element being replaced. If not specified, the calendarwrapper is used.
|
||||
* @return {promise}
|
||||
*/
|
||||
var refreshMonthContent = function(root, year, month, courseid, target) {
|
||||
var refreshMonthContent = function(root, year, month, courseid, categoryid, target) {
|
||||
startLoading(root);
|
||||
|
||||
target = target || root.find(SELECTORS.CALENDAR_MONTH_WRAPPER);
|
||||
|
||||
M.util.js_pending([root.get('id'), year, month, courseid].join('-'));
|
||||
var includenavigation = root.data('includenavigation');
|
||||
return CalendarRepository.getCalendarMonthData(year, month, courseid, includenavigation)
|
||||
return CalendarRepository.getCalendarMonthData(year, month, courseid, categoryid, includenavigation)
|
||||
.then(function(context) {
|
||||
return Templates.render(root.attr('data-template'), context);
|
||||
})
|
||||
|
@ -86,15 +89,16 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito
|
|||
/**
|
||||
* Handle changes to the current calendar view.
|
||||
*
|
||||
* @param {object} root The root element.
|
||||
* @param {object} root The container element
|
||||
* @param {String} url The calendar url to be shown
|
||||
* @param {Number} year Year
|
||||
* @param {Number} month Month
|
||||
* @param {Number} courseid The id of the course whose events are shown
|
||||
* @param {Number} categoryid The id of the category whose events are shown
|
||||
* @return {promise}
|
||||
*/
|
||||
var changeMonth = function(root, url, year, month, courseid) {
|
||||
return refreshMonthContent(root, year, month, courseid)
|
||||
var changeMonth = function(root, url, year, month, courseid, categoryid) {
|
||||
return refreshMonthContent(root, year, month, courseid, categoryid)
|
||||
.then(function() {
|
||||
if (url.length && url !== '#') {
|
||||
window.history.pushState({}, '', url);
|
||||
|
@ -102,7 +106,7 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito
|
|||
return arguments;
|
||||
})
|
||||
.then(function() {
|
||||
$('body').trigger(CalendarEvents.monthChanged, [year, month, courseid]);
|
||||
$('body').trigger(CalendarEvents.monthChanged, [year, month, courseid, categoryid]);
|
||||
return arguments;
|
||||
});
|
||||
};
|
||||
|
@ -112,16 +116,23 @@ define(['jquery', 'core/templates', 'core/notification', 'core_calendar/reposito
|
|||
*
|
||||
* @param {object} root The container element.
|
||||
* @param {Number} courseId The course id.
|
||||
* @param {Number} categoryId The id of the category whose events are shown
|
||||
* @return {promise}
|
||||
*/
|
||||
var reloadCurrentMonth = function(root, courseId) {
|
||||
var reloadCurrentMonth = function(root, courseId, categoryId) {
|
||||
var year = root.find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('year');
|
||||
var month = root.find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('month');
|
||||
|
||||
if (!courseId) {
|
||||
if (typeof courseId === 'undefined') {
|
||||
courseId = root.find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('courseid');
|
||||
}
|
||||
return refreshMonthContent(root, year, month, courseId);
|
||||
|
||||
if (typeof categoryId === 'undefined') {
|
||||
categoryId = root.find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('categoryid');
|
||||
}
|
||||
|
||||
return refreshMonthContent(root, year, month, courseId, categoryId);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -82,6 +82,8 @@ class calendar_event_exporter extends event_exporter_base {
|
|||
$params = array('update' => $moduleid, 'return' => true, 'sesskey' => sesskey());
|
||||
$editurl = new \moodle_url('/course/mod.php', $params);
|
||||
$values['editurl'] = $editurl->out(false);
|
||||
} else if ($event->get_type() == 'category') {
|
||||
$url = $event->get_category()->get_proxied_instance()->get_view_link();
|
||||
} else if ($event->get_type() == 'course') {
|
||||
$url = course_get_url($event->get_course()->get('id') ?: SITEID);
|
||||
} else {
|
||||
|
@ -126,6 +128,17 @@ class calendar_event_exporter extends event_exporter_base {
|
|||
}
|
||||
}
|
||||
|
||||
// Include category name into the event name, if applicable.
|
||||
$proxy = $this->event->get_category();
|
||||
if ($proxy && $proxy->get('id')) {
|
||||
$category = $proxy->get_proxied_instance();
|
||||
$eventnameparams = (object) [
|
||||
'name' => $values['popupname'],
|
||||
'category' => $category->get_formatted_name(),
|
||||
];
|
||||
$values['popupname'] = get_string('eventnameandcategory', 'calendar', $eventnameparams);
|
||||
}
|
||||
|
||||
// Include course's shortname into the event name, if applicable.
|
||||
$course = $this->event->get_course();
|
||||
if ($course && $course->get('id') && $course->get('id') !== SITEID) {
|
||||
|
|
21
calendar/classes/external/day_exporter.php
vendored
21
calendar/classes/external/day_exporter.php
vendored
|
@ -59,11 +59,20 @@ class day_exporter extends exporter {
|
|||
*/
|
||||
public function __construct(\calendar_information $calendar, $data, $related) {
|
||||
$this->calendar = $calendar;
|
||||
$this->url = new moodle_url('/calendar/view.php', [
|
||||
'view' => 'day',
|
||||
'time' => $calendar->time,
|
||||
'course' => $this->calendar->course->id,
|
||||
]);
|
||||
|
||||
$url = new moodle_url('/calendar/view.php', [
|
||||
'view' => 'day',
|
||||
'time' => $calendar->time,
|
||||
]);
|
||||
|
||||
if ($this->calendar->course && SITEID !== $this->calendar->course->id) {
|
||||
$url->param('course', $this->calendar->course->id);
|
||||
} else if ($this->calendar->categoryid) {
|
||||
$url->param('category', $this->calendar->categoryid);
|
||||
}
|
||||
|
||||
$this->url = $url;
|
||||
|
||||
parent::__construct($data, $related);
|
||||
}
|
||||
|
||||
|
@ -179,9 +188,9 @@ class day_exporter extends exporter {
|
|||
'navigation' => $this->get_navigation(),
|
||||
'filter_selector' => $this->get_course_filter_selector($output),
|
||||
'new_event_button' => $this->get_new_event_button(),
|
||||
'viewdaylink' => $this->url->out(false),
|
||||
];
|
||||
|
||||
$return['viewdaylink'] = $this->url->out(false);
|
||||
|
||||
$cache = $this->related['cache'];
|
||||
$eventexporters = array_map(function($event) use ($cache, $output) {
|
||||
|
|
8
calendar/classes/external/event_exporter.php
vendored
8
calendar/classes/external/event_exporter.php
vendored
|
@ -48,17 +48,13 @@ class event_exporter extends event_exporter_base {
|
|||
* @return array
|
||||
*/
|
||||
protected static function define_other_properties() {
|
||||
|
||||
$values = parent::define_other_properties();
|
||||
|
||||
$values['url'] = ['type' => PARAM_URL];
|
||||
$values['action'] = [
|
||||
'type' => event_action_exporter::read_properties_definition(),
|
||||
'optional' => true,
|
||||
];
|
||||
$values['editurl'] = [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
];
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
@ -86,6 +82,8 @@ class event_exporter extends event_exporter_base {
|
|||
$params = array('update' => $moduleid, 'return' => true, 'sesskey' => sesskey());
|
||||
$editurl = new \moodle_url('/course/mod.php', $params);
|
||||
$values['editurl'] = $editurl->out(false);
|
||||
} else if ($event->get_type() == 'category') {
|
||||
$url = $event->get_category()->get_proxied_instance()->get_view_link();
|
||||
} else if ($event->get_type() == 'course') {
|
||||
$url = \course_get_url($this->related['course'] ?: SITEID);
|
||||
} else {
|
||||
|
|
|
@ -34,6 +34,7 @@ use \core_calendar\local\event\container;
|
|||
use \core_calendar\local\event\entities\event_interface;
|
||||
use \core_calendar\local\event\entities\action_event_interface;
|
||||
use \core_course\external\course_summary_exporter;
|
||||
use \core\external\coursecat_summary_exporter;
|
||||
use \renderer_base;
|
||||
use moodle_url;
|
||||
|
||||
|
@ -64,6 +65,7 @@ class event_exporter_base extends exporter {
|
|||
$endtimestamp = $event->get_times()->get_end_time()->getTimestamp();
|
||||
$groupid = $event->get_group() ? $event->get_group()->get('id') : null;
|
||||
$userid = $event->get_user() ? $event->get_user()->get('id') : null;
|
||||
$categoryid = $event->get_category() ? $event->get_category()->get('id') : null;
|
||||
|
||||
$data = new \stdClass();
|
||||
$data->id = $event->get_id();
|
||||
|
@ -79,6 +81,7 @@ class event_exporter_base extends exporter {
|
|||
$data->descriptionformat = $event->get_description()->get_format();
|
||||
$data->groupid = $groupid;
|
||||
$data->userid = $userid;
|
||||
$data->categoryid = $categoryid;
|
||||
$data->eventtype = $event->get_type();
|
||||
$data->timestart = $starttimestamp;
|
||||
$data->timeduration = $endtimestamp - $starttimestamp;
|
||||
|
@ -120,6 +123,12 @@ class event_exporter_base extends exporter {
|
|||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'categoryid' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'groupid' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
|
@ -175,6 +184,10 @@ class event_exporter_base extends exporter {
|
|||
'icon' => [
|
||||
'type' => event_icon_exporter::read_properties_definition(),
|
||||
],
|
||||
'category' => [
|
||||
'type' => coursecat_summary_exporter::read_properties_definition(),
|
||||
'optional' => true,
|
||||
],
|
||||
'course' => [
|
||||
'type' => course_summary_exporter::read_properties_definition(),
|
||||
'optional' => true,
|
||||
|
@ -204,6 +217,9 @@ class event_exporter_base extends exporter {
|
|||
'iscourseevent' => [
|
||||
'type' => PARAM_BOOL
|
||||
],
|
||||
'iscategoryevent' => [
|
||||
'type' => PARAM_BOOL
|
||||
],
|
||||
'groupname' => [
|
||||
'type' => PARAM_RAW,
|
||||
'optional' => true,
|
||||
|
@ -226,10 +242,13 @@ class event_exporter_base extends exporter {
|
|||
$context = $this->related['context'];
|
||||
$values['isactionevent'] = false;
|
||||
$values['iscourseevent'] = false;
|
||||
$values['iscategoryevent'] = false;
|
||||
if ($moduleproxy = $event->get_course_module()) {
|
||||
$values['isactionevent'] = true;
|
||||
} else if ($event->get_type() == 'course') {
|
||||
$values['iscourseevent'] = true;
|
||||
} else if ($event->get_type() == 'category') {
|
||||
$values['iscategoryevent'] = true;
|
||||
}
|
||||
$timesort = $event->get_times()->get_sort_time()->getTimestamp();
|
||||
$iconexporter = new event_icon_exporter($event, ['context' => $context]);
|
||||
|
@ -239,6 +258,13 @@ class event_exporter_base extends exporter {
|
|||
$subscriptionexporter = new event_subscription_exporter($event);
|
||||
$values['subscription'] = $subscriptionexporter->export($output);
|
||||
|
||||
$proxy = $this->event->get_category();
|
||||
if ($proxy && $proxy->get('id')) {
|
||||
$category = $proxy->get_proxied_instance();
|
||||
$categorysummaryexporter = new coursecat_summary_exporter($category, ['context' => $context]);
|
||||
$values['category'] = $categorysummaryexporter->export($output);
|
||||
}
|
||||
|
||||
if ($course = $this->related['course']) {
|
||||
$coursesummaryexporter = new course_summary_exporter($course, ['context' => $context]);
|
||||
$values['course'] = $coursesummaryexporter->export($output);
|
||||
|
|
|
@ -46,6 +46,8 @@ class event_icon_exporter extends exporter {
|
|||
*/
|
||||
public function __construct(event_interface $event, $related = []) {
|
||||
$coursemodule = $event->get_course_module();
|
||||
$category = $event->get_category();
|
||||
$categoryid = $category ? $category->get('id') : null;
|
||||
$course = $event->get_course();
|
||||
$courseid = $course ? $course->get('id') : null;
|
||||
$group = $event->get_group();
|
||||
|
@ -54,6 +56,7 @@ class event_icon_exporter extends exporter {
|
|||
$userid = $user ? $user->get('id') : null;
|
||||
$isactivityevent = !empty($coursemodule);
|
||||
$isglobalevent = ($course && $courseid == SITEID);
|
||||
$iscategoryevent = ($category && !empty($categoryid));
|
||||
$iscourseevent = ($course && !empty($courseid) && $courseid != SITEID && empty($groupid));
|
||||
$isgroupevent = ($group && !empty($groupid));
|
||||
$isuserevent = ($user && !empty($userid));
|
||||
|
@ -70,24 +73,28 @@ class event_icon_exporter extends exporter {
|
|||
} else if ($isglobalevent) {
|
||||
$key = 'i/siteevent';
|
||||
$component = 'core';
|
||||
$alttext = get_string('globalevent', 'calendar');
|
||||
$alttext = get_string('typesite', 'calendar');
|
||||
} else if ($iscategoryevent) {
|
||||
$key = 'i/categoryevent';
|
||||
$component = 'core';
|
||||
$alttext = get_string('typecategory', 'calendar');
|
||||
} else if ($iscourseevent) {
|
||||
$key = 'i/courseevent';
|
||||
$component = 'core';
|
||||
$alttext = get_string('courseevent', 'calendar');
|
||||
$alttext = get_string('typecourse', 'calendar');
|
||||
} else if ($isgroupevent) {
|
||||
$key = 'i/groupevent';
|
||||
$component = 'core';
|
||||
$alttext = get_string('groupevent', 'calendar');
|
||||
$alttext = get_string('typegroup', 'calendar');
|
||||
} else if ($isuserevent) {
|
||||
$key = 'i/userevent';
|
||||
$component = 'core';
|
||||
$alttext = get_string('userevent', 'calendar');
|
||||
$alttext = get_string('typeuser', 'calendar');
|
||||
} else {
|
||||
// Default to site event icon?
|
||||
$key = 'i/siteevent';
|
||||
$component = 'core';
|
||||
$alttext = get_string('globalevent', 'calendar');
|
||||
$alttext = get_string('typesite', 'calendar');
|
||||
}
|
||||
|
||||
$data = new \stdClass();
|
||||
|
|
15
calendar/classes/external/month_exporter.php
vendored
15
calendar/classes/external/month_exporter.php
vendored
|
@ -75,8 +75,10 @@ class month_exporter extends exporter {
|
|||
'time' => $calendar->time,
|
||||
]);
|
||||
|
||||
if ($this->calendar->courseid) {
|
||||
$this->url->param('course', $this->calendar->courseid);
|
||||
if ($this->calendar->course && SITEID !== $this->calendar->course->id) {
|
||||
$this->url->param('course', $this->calendar->course->id);
|
||||
} else if ($this->calendar->categoryid) {
|
||||
$this->url->param('category', $this->calendar->categoryid);
|
||||
}
|
||||
|
||||
$related['type'] = $type;
|
||||
|
@ -106,6 +108,11 @@ class month_exporter extends exporter {
|
|||
'courseid' => [
|
||||
'type' => PARAM_INT,
|
||||
],
|
||||
'categoryid' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
'default' => 0,
|
||||
],
|
||||
'filter_selector' => [
|
||||
'type' => PARAM_RAW,
|
||||
],
|
||||
|
@ -209,6 +216,10 @@ class month_exporter extends exporter {
|
|||
$return['defaulteventcontext'] = $context->id;
|
||||
}
|
||||
|
||||
if ($this->calendar->categoryid) {
|
||||
$return['categoryid'] = $this->calendar->categoryid;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
|
10
calendar/classes/external/week_day_exporter.php
vendored
10
calendar/classes/external/week_day_exporter.php
vendored
|
@ -118,10 +118,16 @@ class week_day_exporter extends day_exporter {
|
|||
$url = new moodle_url('/calendar/view.php', [
|
||||
'view' => 'day',
|
||||
'time' => $timestamp,
|
||||
'course' => $this->calendar->course->id,
|
||||
]);
|
||||
]);
|
||||
|
||||
if ($this->calendar->course && SITEID !== $this->calendar->course->id) {
|
||||
$url->param('course', $this->calendar->course->id);
|
||||
} else if ($this->calendar->categoryid) {
|
||||
$url->param('category', $this->calendar->categoryid);
|
||||
}
|
||||
|
||||
$return['viewdaylink'] = $url->out(false);
|
||||
|
||||
if ($popovertitle = $this->get_popover_title()) {
|
||||
$return['popovertitle'] = $popovertitle;
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ class api {
|
|||
array $usersfilter = null,
|
||||
array $groupsfilter = null,
|
||||
array $coursesfilter = null,
|
||||
array $categoriesfilter = null,
|
||||
$withduration = true,
|
||||
$ignorehidden = true,
|
||||
callable $filter = null
|
||||
|
@ -102,6 +103,7 @@ class api {
|
|||
$usersfilter,
|
||||
$groupsfilter,
|
||||
$coursesfilter,
|
||||
$categoriesfilter,
|
||||
$withduration,
|
||||
$ignorehidden,
|
||||
$filter
|
||||
|
|
|
@ -117,6 +117,15 @@ class container {
|
|||
[self::class, 'apply_component_provide_event_action'],
|
||||
[self::class, 'apply_component_is_event_visible'],
|
||||
function ($dbrow) {
|
||||
if (!empty($dbrow->categoryid)) {
|
||||
// This is a category event. Check that the category is visible to this user.
|
||||
$category = \coursecat::get($dbrow->categoryid, IGNORE_MISSING, true);
|
||||
|
||||
if (empty($category) || !$category->is_uservisible()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// At present we only have a bail-out check for events in course modules.
|
||||
if (empty($dbrow->modulename)) {
|
||||
return false;
|
||||
|
|
|
@ -33,6 +33,8 @@ use core_calendar\local\event\factories\action_factory_interface;
|
|||
use core_calendar\local\event\factories\event_factory_interface;
|
||||
use core_calendar\local\event\strategies\raw_event_retrieval_strategy_interface;
|
||||
|
||||
require_once($CFG->libdir . '/coursecatlib.php');
|
||||
|
||||
/**
|
||||
* Event vault class.
|
||||
*
|
||||
|
@ -95,6 +97,7 @@ class event_vault implements event_vault_interface {
|
|||
array $usersfilter = null,
|
||||
array $groupsfilter = null,
|
||||
array $coursesfilter = null,
|
||||
array $categoriesfilter = null,
|
||||
$withduration = true,
|
||||
$ignorehidden = true,
|
||||
callable $filter = null
|
||||
|
@ -162,6 +165,7 @@ class event_vault implements event_vault_interface {
|
|||
$usersfilter,
|
||||
$groupsfilter,
|
||||
$coursesfilter,
|
||||
$categoriesfilter,
|
||||
$where,
|
||||
$params,
|
||||
"COALESCE(e.timesort, e.timestart) ASC, e.id ASC",
|
||||
|
@ -197,6 +201,10 @@ class event_vault implements event_vault_interface {
|
|||
event_interface $afterevent = null,
|
||||
$limitnum = 20
|
||||
) {
|
||||
$categoryids = array_map(function($category) {
|
||||
return $category->id;
|
||||
}, \coursecat::get_all());
|
||||
|
||||
$courseids = array_map(function($course) {
|
||||
return $course->id;
|
||||
}, enrol_get_all_users_courses($user->id));
|
||||
|
@ -219,6 +227,7 @@ class event_vault implements event_vault_interface {
|
|||
[$user->id],
|
||||
$groupids ? $groupids : null,
|
||||
$courseids ? $courseids : null,
|
||||
$categoryids ? $categoryids : null,
|
||||
true,
|
||||
true,
|
||||
function ($event) {
|
||||
|
@ -249,6 +258,7 @@ class event_vault implements event_vault_interface {
|
|||
[$user->id],
|
||||
$groupings[0] ? $groupings[0] : null,
|
||||
[$course->id],
|
||||
[],
|
||||
true,
|
||||
true,
|
||||
function ($event) use ($course) {
|
||||
|
@ -375,6 +385,7 @@ class event_vault implements event_vault_interface {
|
|||
[$userid],
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
$whereconditions,
|
||||
$whereparams,
|
||||
$ordersql,
|
||||
|
|
|
@ -76,6 +76,7 @@ interface event_vault_interface {
|
|||
array $usersfilter = null,
|
||||
array $groupsfilter = null,
|
||||
array $coursesfilter = null,
|
||||
array $categoriesfilter = null,
|
||||
$withduration = true,
|
||||
$ignorehidden = true,
|
||||
callable $filter = null
|
||||
|
|
|
@ -50,6 +50,11 @@ class action_event implements action_event_interface {
|
|||
*/
|
||||
protected $action;
|
||||
|
||||
/**
|
||||
* @var proxy_interface $category Category for this event.
|
||||
*/
|
||||
protected $category;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
|
@ -73,6 +78,10 @@ class action_event implements action_event_interface {
|
|||
return $this->event->get_description();
|
||||
}
|
||||
|
||||
public function get_category() {
|
||||
return $this->event->get_category();
|
||||
}
|
||||
|
||||
public function get_course() {
|
||||
return $this->event->get_course();
|
||||
}
|
||||
|
|
|
@ -52,6 +52,11 @@ class event implements event_interface {
|
|||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @var proxy_interface $category Category for this event.
|
||||
*/
|
||||
protected $category;
|
||||
|
||||
/**
|
||||
* @var proxy_interface $course Course for this event.
|
||||
*/
|
||||
|
@ -103,6 +108,7 @@ class event implements event_interface {
|
|||
* @param int $id The event's ID in the database.
|
||||
* @param string $name The event's name.
|
||||
* @param description_interface $description The event's description.
|
||||
* @param proxy_interface $category The category associated with the event.
|
||||
* @param proxy_interface $course The course associated with the event.
|
||||
* @param proxy_interface $group The group associated with the event.
|
||||
* @param proxy_interface $user The user associated with the event.
|
||||
|
@ -117,6 +123,7 @@ class event implements event_interface {
|
|||
$id,
|
||||
$name,
|
||||
description_interface $description,
|
||||
proxy_interface $category = null,
|
||||
proxy_interface $course = null,
|
||||
proxy_interface $group = null,
|
||||
proxy_interface $user = null,
|
||||
|
@ -130,6 +137,7 @@ class event implements event_interface {
|
|||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->description = $description;
|
||||
$this->category = $category;
|
||||
$this->course = $course;
|
||||
$this->group = $group;
|
||||
$this->user = $user;
|
||||
|
@ -153,6 +161,10 @@ class event implements event_interface {
|
|||
return $this->description;
|
||||
}
|
||||
|
||||
public function get_category() {
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
public function get_course() {
|
||||
return $this->course;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,13 @@ interface event_interface {
|
|||
*/
|
||||
public function get_description();
|
||||
|
||||
/**
|
||||
* Get the category object associated with the event.
|
||||
*
|
||||
* @return proxy_interface
|
||||
*/
|
||||
public function get_category();
|
||||
|
||||
/**
|
||||
* Get the course object associated with the event.
|
||||
*
|
||||
|
|
|
@ -30,11 +30,14 @@ use core_calendar\local\event\entities\event;
|
|||
use core_calendar\local\event\entities\repeat_event_collection;
|
||||
use core_calendar\local\event\exceptions\invalid_callback_exception;
|
||||
use core_calendar\local\event\proxies\cm_info_proxy;
|
||||
use core_calendar\local\event\proxies\coursecat_proxy;
|
||||
use core_calendar\local\event\proxies\std_proxy;
|
||||
use core_calendar\local\event\value_objects\event_description;
|
||||
use core_calendar\local\event\value_objects\event_times;
|
||||
use core_calendar\local\event\entities\event_interface;
|
||||
|
||||
require_once($CFG->libdir . '/coursecatlib.php');
|
||||
|
||||
/**
|
||||
* Abstract factory for creating calendar events.
|
||||
*
|
||||
|
@ -126,6 +129,7 @@ abstract class event_abstract_factory implements event_factory_interface {
|
|||
return null;
|
||||
}
|
||||
|
||||
$category = null;
|
||||
$course = null;
|
||||
$group = null;
|
||||
$user = null;
|
||||
|
@ -136,6 +140,8 @@ abstract class event_abstract_factory implements event_factory_interface {
|
|||
$module = new cm_info_proxy($dbrow->modulename, $dbrow->instance, $dbrow->courseid);
|
||||
}
|
||||
|
||||
$category = new coursecat_proxy($dbrow->categoryid);
|
||||
|
||||
$course = new std_proxy($dbrow->courseid, function($id) {
|
||||
return calendar_get_course_cached($this->coursecachereference, $id);
|
||||
});
|
||||
|
@ -163,6 +169,7 @@ abstract class event_abstract_factory implements event_factory_interface {
|
|||
$dbrow->id,
|
||||
$dbrow->name,
|
||||
new event_description($dbrow->description, $dbrow->format),
|
||||
$category,
|
||||
$course,
|
||||
$group,
|
||||
$user,
|
||||
|
|
|
@ -55,7 +55,7 @@ class create extends \moodleform {
|
|||
/**
|
||||
* The form definition
|
||||
*/
|
||||
public function definition () {
|
||||
public function definition() {
|
||||
global $PAGE;
|
||||
|
||||
$mform = $this->_form;
|
||||
|
@ -203,6 +203,9 @@ class create extends \moodleform {
|
|||
if (isset($eventtypes['course'])) {
|
||||
$options['course'] = get_string('course');
|
||||
}
|
||||
if (isset($eventtypes['category'])) {
|
||||
$options['category'] = get_string('category');
|
||||
}
|
||||
if (isset($eventtypes['site'])) {
|
||||
$options['site'] = get_string('site');
|
||||
}
|
||||
|
@ -223,6 +226,16 @@ class create extends \moodleform {
|
|||
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $options);
|
||||
}
|
||||
|
||||
if (isset($eventtypes['category'])) {
|
||||
$categoryoptions = [];
|
||||
foreach ($eventtypes['category'] as $id => $category) {
|
||||
$categoryoptions[$id] = $category;
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'categoryid', get_string('category'), $categoryoptions);
|
||||
$mform->hideIf('categoryid', 'eventtype', 'noteq', 'category');
|
||||
}
|
||||
|
||||
if (isset($eventtypes['course'])) {
|
||||
$courseoptions = [];
|
||||
foreach ($eventtypes['course'] as $course) {
|
||||
|
|
|
@ -69,6 +69,7 @@ class event_mapper implements event_mapper_interface {
|
|||
'name' => $coalesce('name'),
|
||||
'description' => $coalesce('description'),
|
||||
'format' => $coalesce('format'),
|
||||
'categoryid' => $coalesce('categoryid'),
|
||||
'courseid' => $coalesce('courseid'),
|
||||
'groupid' => $coalesce('groupid'),
|
||||
'userid' => $coalesce('userid'),
|
||||
|
|
92
calendar/classes/local/event/proxies/coursecat_proxy.php
Normal file
92
calendar/classes/local/event/proxies/coursecat_proxy.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Course category proxy.
|
||||
*
|
||||
* @package core_calendar
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_calendar\local\event\proxies;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir . '/coursecatlib.php');
|
||||
|
||||
/**
|
||||
* Course category proxy.
|
||||
*
|
||||
* This returns an instance of a coursecat rather than a stdClass.
|
||||
*
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class coursecat_proxy implements proxy_interface {
|
||||
/**
|
||||
* @var int $id The ID of the database record.
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var \stdClass $base Base class to get members from.
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* @var \coursecat $category The proxied instance.
|
||||
*/
|
||||
protected $category;
|
||||
|
||||
/**
|
||||
* coursecat_proxy constructor.
|
||||
*
|
||||
* @param int $id The ID of the record in the database.
|
||||
*/
|
||||
public function __construct($id) {
|
||||
$this->id = $id;
|
||||
$this->base = (object) [
|
||||
'id' => $id,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a member of the proxied class.
|
||||
*
|
||||
* @param string $member The name of the member to retrieve
|
||||
* @return mixed The member.
|
||||
*/
|
||||
public function get($member) {
|
||||
if ($this->base && property_exists($this->base, $member)) {
|
||||
return $this->base->{$member};
|
||||
}
|
||||
|
||||
return $this->get_proxied_instance()->{$member};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full instance of the proxied class.
|
||||
*
|
||||
* @return \coursecat
|
||||
*/
|
||||
public function get_proxied_instance() : \coursecat {
|
||||
if (!$this->category) {
|
||||
$this->category = \coursecat::get($this->id, IGNORE_MISSING, true);
|
||||
}
|
||||
return $this->category;
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
|
|||
array $usersfilter = null,
|
||||
array $groupsfilter = null,
|
||||
array $coursesfilter = null,
|
||||
array $categoriesfilter = null,
|
||||
array $whereconditions = null,
|
||||
array $whereparams = null,
|
||||
$ordersql = null,
|
||||
|
@ -51,6 +52,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
|
|||
!is_null($usersfilter) ? $usersfilter : true, // True means no filter in old implementation.
|
||||
!is_null($groupsfilter) ? $groupsfilter : true,
|
||||
!is_null($coursesfilter) ? $coursesfilter : true,
|
||||
!is_null($categoriesfilter) ? $categoriesfilter : true,
|
||||
$whereconditions,
|
||||
$whereparams,
|
||||
$ordersql,
|
||||
|
@ -78,6 +80,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
|
|||
$users,
|
||||
$groups,
|
||||
$courses,
|
||||
$categories,
|
||||
$whereconditions,
|
||||
$whereparams,
|
||||
$ordersql,
|
||||
|
@ -89,7 +92,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
|
|||
|
||||
$params = array();
|
||||
// Quick test.
|
||||
if (empty($users) && empty($groups) && empty($courses)) {
|
||||
if (empty($users) && empty($groups) && empty($courses) && empty($categories)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
|
@ -100,11 +103,11 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
|
|||
if ((is_array($users) && !empty($users)) or is_numeric($users)) {
|
||||
// Events from a number of users.
|
||||
list($insqlusers, $inparamsusers) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED);
|
||||
$filters[] = "(e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0)";
|
||||
$filters[] = "(e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)";
|
||||
$params = array_merge($params, $inparamsusers);
|
||||
} else if ($users === true) {
|
||||
// Events from ALL users.
|
||||
$filters[] = "(e.userid != 0 AND e.courseid = 0 AND e.groupid = 0)";
|
||||
$filters[] = "(e.userid != 0 AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)";
|
||||
}
|
||||
// Boolean false (no users at all): We don't need to do anything.
|
||||
|
||||
|
@ -130,6 +133,16 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
|
|||
$filters[] = "(e.groupid = 0 AND e.courseid != 0)";
|
||||
}
|
||||
|
||||
// Category filter.
|
||||
if ((is_array($categories) && !empty($categories)) or is_numeric($categories)) {
|
||||
list($insqlcategories, $inparamscategories) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
|
||||
$filters[] = "(e.groupid = 0 AND e.courseid = 0 AND e.categoryid $insqlcategories)";
|
||||
$params = array_merge($params, $inparamscategories);
|
||||
} else if ($categories === true) {
|
||||
// Events from ALL categories.
|
||||
$filters[] = "(e.groupid = 0 AND e.courseid = 0 AND e.categoryid != 0)";
|
||||
}
|
||||
|
||||
// Security check: if, by now, we have NOTHING in $whereclause, then it means
|
||||
// that NO event-selecting clauses were defined. Thus, we won't be returning ANY
|
||||
// events no matter what. Allowing the code to proceed might return a completely
|
||||
|
@ -168,7 +181,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
|
|||
|
||||
if ($user) {
|
||||
// Set filter condition for the user's events.
|
||||
$subqueryconditions[] = "(ev.userid = :user AND ev.courseid = 0 AND ev.groupid = 0)";
|
||||
$subqueryconditions[] = "(ev.userid = :user AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)";
|
||||
$subqueryparams['user'] = $user;
|
||||
|
||||
foreach ($usercourses as $courseid) {
|
||||
|
@ -210,10 +223,19 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
|
|||
// Set subquery filter condition for the courses.
|
||||
if (!empty($subquerycourses)) {
|
||||
list($incourses, $incoursesparams) = $DB->get_in_or_equal($subquerycourses, SQL_PARAMS_NAMED);
|
||||
$subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid $incourses)";
|
||||
$subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid $incourses AND ev.categoryid = 0)";
|
||||
$subqueryparams = array_merge($subqueryparams, $incoursesparams);
|
||||
}
|
||||
|
||||
// Set subquery filter condition for the categories.
|
||||
if ($categories === true) {
|
||||
$subqueryconditions[] = "(ev.categoryid != 0 AND ev.eventtype = 'category')";
|
||||
} else if (!empty($categories)) {
|
||||
list($incategories, $incategoriesparams) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
|
||||
$subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid = 0 AND ev.categoryid $incategories)";
|
||||
$subqueryparams = array_merge($subqueryparams, $incategoriesparams);
|
||||
}
|
||||
|
||||
// Build the WHERE condition for the sub-query.
|
||||
if (!empty($subqueryconditions)) {
|
||||
$subquerywhere = 'WHERE ' . implode(" OR ", $subqueryconditions);
|
||||
|
|
|
@ -39,6 +39,7 @@ interface raw_event_retrieval_strategy_interface {
|
|||
* @param array|null $usersfilter Array of users to retrieve events for.
|
||||
* @param array|null $groupsfilter Array of groups to retrieve events for.
|
||||
* @param array|null $coursesfilter Array of courses to retrieve events for.
|
||||
* @param array|null $categoriesfilter Array of categories to retrieve events for.
|
||||
* @param array|null $whereconditions Array of where conditions to restrict results.
|
||||
* @param array|null $whereparams Array of parameters for $whereconditions.
|
||||
* @param string|null $ordersql SQL to order results.
|
||||
|
@ -51,6 +52,7 @@ interface raw_event_retrieval_strategy_interface {
|
|||
array $usersfilter = null,
|
||||
array $groupsfilter = null,
|
||||
array $coursesfilter = null,
|
||||
array $categoriesfilter = null,
|
||||
array $whereconditions = null,
|
||||
array $whereparams = null,
|
||||
$ordersql = null,
|
||||
|
|
|
@ -108,7 +108,7 @@ if ($action === 'delete' && $eventid > 0) {
|
|||
}
|
||||
|
||||
$calendar = new calendar_information(0, 0, 0, $time);
|
||||
$calendar->prepare_for_view($course, $courses);
|
||||
$calendar->set_sources($course, $courses);
|
||||
|
||||
$formoptions = new stdClass;
|
||||
if ($eventid !== 0) {
|
||||
|
|
|
@ -100,7 +100,7 @@ if ($course !== NULL) {
|
|||
$PAGE->set_url($url);
|
||||
|
||||
$calendar = new calendar_information(0, 0, 0, $time);
|
||||
$calendar->prepare_for_view($course, $courses);
|
||||
$calendar->set_sources($course, $courses);
|
||||
|
||||
$pagetitle = get_string('export', 'calendar');
|
||||
|
||||
|
|
|
@ -882,10 +882,11 @@ class core_calendar_external extends external_api {
|
|||
* @param int $year The year to be shown
|
||||
* @param int $month The month to be shown
|
||||
* @param int $courseid The course to be included
|
||||
* @param int $categoryid The category to be included
|
||||
* @param bool $includenavigation Whether to include navigation
|
||||
* @return array
|
||||
*/
|
||||
public static function get_calendar_monthly_view($year, $month, $courseid, $includenavigation) {
|
||||
public static function get_calendar_monthly_view($year, $month, $courseid, $categoryid, $includenavigation) {
|
||||
global $CFG, $DB, $USER, $PAGE;
|
||||
require_once($CFG->dirroot."/calendar/lib.php");
|
||||
|
||||
|
@ -894,27 +895,45 @@ class core_calendar_external extends external_api {
|
|||
'year' => $year,
|
||||
'month' => $month,
|
||||
'courseid' => $courseid,
|
||||
'categoryid' => $categoryid,
|
||||
'includenavigation' => $includenavigation,
|
||||
]);
|
||||
|
||||
// TODO: Copy what we do in calendar/view.php.
|
||||
$context = \context_user::instance($USER->id);
|
||||
self::validate_context($context);
|
||||
$PAGE->set_url('/calendar/');
|
||||
|
||||
if ($courseid != SITEID && !empty($courseid)) {
|
||||
// Course ID must be valid and existing.
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
$courses = [$course->id => $course];
|
||||
|
||||
$coursecat = \coursecat::get($course->category);
|
||||
$category = $coursecat->get_db_record();
|
||||
} else {
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
}
|
||||
$category = null;
|
||||
|
||||
// TODO: Copy what we do in calendar/view.php.
|
||||
$context = \context_user::instance($USER->id);
|
||||
self::validate_context($context);
|
||||
if ($categoryid) {
|
||||
self::validate_context(context_coursecat::instance($categoryid));
|
||||
$ids = [$categoryid];
|
||||
$category = \coursecat::get($categoryid);
|
||||
$ids += $category->get_parents();
|
||||
$categories = \coursecat::get_many($ids);
|
||||
$courses = array_filter($courses, function($course) use ($categories) {
|
||||
return array_search($course->category, $categories) !== false;
|
||||
});
|
||||
$category = $category->get_db_record();
|
||||
}
|
||||
}
|
||||
|
||||
$type = \core_calendar\type_factory::get_calendar_instance();
|
||||
|
||||
$time = $type->convert_to_timestamp($year, $month, 1);
|
||||
$calendar = new calendar_information(0, 0, 0, $time);
|
||||
$calendar->prepare_for_view($course, $courses);
|
||||
$calendar->set_sources($course, $courses, $category);
|
||||
|
||||
list($data, $template) = calendar_get_view($calendar, 'month', $params['includenavigation']);
|
||||
|
||||
|
@ -932,6 +951,7 @@ class core_calendar_external extends external_api {
|
|||
'year' => new external_value(PARAM_INT, 'Month to be viewed', VALUE_REQUIRED),
|
||||
'month' => new external_value(PARAM_INT, 'Year to be viewed', VALUE_REQUIRED),
|
||||
'courseid' => new external_value(PARAM_INT, 'Course being viewed', VALUE_DEFAULT, SITEID, NULL_ALLOWED),
|
||||
'categoryid' => new external_value(PARAM_INT, 'Category being viewed', VALUE_DEFAULT, null, NULL_ALLOWED),
|
||||
'includenavigation' => new external_value(
|
||||
PARAM_BOOL,
|
||||
'Whether to show course navigation',
|
||||
|
|
212
calendar/lib.php
212
calendar/lib.php
|
@ -27,6 +27,8 @@ if (!defined('MOODLE_INTERNAL')) {
|
|||
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
|
||||
}
|
||||
|
||||
require_once($CFG->libdir . '/coursecatlib.php');
|
||||
|
||||
/**
|
||||
* These are read by the administration component to provide default values
|
||||
*/
|
||||
|
@ -89,6 +91,10 @@ define('CALENDAR_EVENT_GROUP', 4);
|
|||
*/
|
||||
define('CALENDAR_EVENT_USER', 8);
|
||||
|
||||
/**
|
||||
* CALENDAR_EVENT_COURSECAT - Course category calendar event types
|
||||
*/
|
||||
define('CALENDAR_EVENT_COURSECAT', 16);
|
||||
|
||||
/**
|
||||
* CALENDAR_IMPORT_FROM_FILE - import the calendar from a file
|
||||
|
@ -312,7 +318,9 @@ class calendar_event {
|
|||
global $USER, $DB;
|
||||
|
||||
$context = null;
|
||||
if (isset($this->properties->courseid) && $this->properties->courseid > 0) {
|
||||
if (isset($this->properties->categoryid) && $this->properties->categoryid > 0) {
|
||||
$context = \context_coursecat::instance($this->properties->categoryid);
|
||||
} else if (isset($this->properties->courseid) && $this->properties->courseid > 0) {
|
||||
$context = \context_course::instance($this->properties->courseid);
|
||||
} else if (isset($this->properties->course) && $this->properties->course > 0) {
|
||||
$context = \context_course::instance($this->properties->course);
|
||||
|
@ -360,8 +368,7 @@ class calendar_event {
|
|||
if ($this->editorcontext === null) {
|
||||
// Switch on the event type to decide upon the appropriate context to use for this event.
|
||||
$this->editorcontext = $this->properties->context;
|
||||
if ($this->properties->eventtype != 'user' && $this->properties->eventtype != 'course'
|
||||
&& $this->properties->eventtype != 'site' && $this->properties->eventtype != 'group') {
|
||||
if (!calendar_is_valid_eventtype($this->properties->eventtype)) {
|
||||
return clean_text($this->properties->description, $this->properties->format);
|
||||
}
|
||||
}
|
||||
|
@ -461,6 +468,11 @@ class calendar_event {
|
|||
$this->properties->groupid = 0;
|
||||
$this->properties->userid = $USER->id;
|
||||
break;
|
||||
case 'category':
|
||||
$this->properties->groupid = 0;
|
||||
$this->properties->category = 0;
|
||||
$this->properties->userid = $USER->id;
|
||||
break;
|
||||
case 'group':
|
||||
$this->properties->userid = $USER->id;
|
||||
break;
|
||||
|
@ -752,6 +764,14 @@ class calendar_event {
|
|||
// We have a course and are within the course context so we had
|
||||
// better use the courses max bytes value.
|
||||
$this->editoroptions['maxbytes'] = $course->maxbytes;
|
||||
} else if ($properties->eventtype === 'category') {
|
||||
// First check the course is valid.
|
||||
\coursecat::get($properties->categoryid, MUST_EXIST, true);
|
||||
// Course context.
|
||||
$this->editorcontext = $this->properties->context;
|
||||
// We have a course and are within the course context so we had
|
||||
// better use the courses max bytes value.
|
||||
$this->editoroptions['maxbytes'] = $course->maxbytes;
|
||||
} else {
|
||||
// If we get here we have a custom event type as used by some
|
||||
// modules. In this case the event will have been added by
|
||||
|
@ -896,8 +916,7 @@ class calendar_event {
|
|||
// Switch on the event type to decide upon the appropriate context to use for this event.
|
||||
$this->editorcontext = $this->properties->context;
|
||||
|
||||
if ($this->properties->eventtype != 'user' && $this->properties->eventtype != 'course'
|
||||
&& $this->properties->eventtype != 'site' && $this->properties->eventtype != 'group') {
|
||||
if (!calendar_is_valid_eventtype($this->properties->eventtype)) {
|
||||
// We don't have a context here, do a normal format_text.
|
||||
return external_format_text($this->properties->description, $this->properties->format, $this->editorcontext->id);
|
||||
}
|
||||
|
@ -939,6 +958,12 @@ class calendar_information {
|
|||
/** @var int A course id */
|
||||
public $courseid = null;
|
||||
|
||||
/** @var array An array of categories */
|
||||
public $categories = array();
|
||||
|
||||
/** @var int The current category */
|
||||
public $categoryid = null;
|
||||
|
||||
/** @var array An array of courses */
|
||||
public $courses = array();
|
||||
|
||||
|
@ -989,7 +1014,7 @@ class calendar_information {
|
|||
* @return $this
|
||||
*/
|
||||
public function set_time($time = null) {
|
||||
if ($time === null) {
|
||||
if (empty($time)) {
|
||||
$this->time = time();
|
||||
} else {
|
||||
$this->time = $time;
|
||||
|
@ -1001,17 +1026,71 @@ class calendar_information {
|
|||
/**
|
||||
* Initialize calendar information
|
||||
*
|
||||
* @deprecated 3.4
|
||||
* @param stdClass $course object
|
||||
* @param array $coursestoload An array of courses [$course->id => $course]
|
||||
* @param bool $ignorefilters options to use filter
|
||||
*/
|
||||
public function prepare_for_view(stdClass $course, array $coursestoload, $ignorefilters = false) {
|
||||
$this->courseid = $course->id;
|
||||
debugging('The prepare_for_view() function has been deprecated. Please update your code to use set_sources()',
|
||||
DEBUG_DEVELOPER);
|
||||
$this->set_sources($course, $coursestoload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sources for events within the calendar.
|
||||
*
|
||||
* If no category is provided, then the category path for the current
|
||||
* course will be used.
|
||||
*
|
||||
* @param stdClass $course The current course being viewed.
|
||||
* @param int[] $courses The list of all courses currently accessible.
|
||||
* @param stdClass $category The current category to show.
|
||||
*/
|
||||
public function set_sources(stdClass $course, array $courses, stdClass $category = null) {
|
||||
// A cousre must always be specified.
|
||||
$this->course = $course;
|
||||
list($courses, $group, $user) = calendar_set_filters($coursestoload, $ignorefilters);
|
||||
$this->courses = $courses;
|
||||
$this->courseid = $course->id;
|
||||
|
||||
list($courseids, $group, $user) = calendar_set_filters($courses);
|
||||
$this->courses = $courseids;
|
||||
$this->groups = $group;
|
||||
$this->users = $user;
|
||||
|
||||
// Do not show category events by default.
|
||||
$this->categoryid = null;
|
||||
$this->categories = null;
|
||||
|
||||
if (null !== $category && $category->id > 0) {
|
||||
// A specific category was requested - set the current category, and include all parents of that category.
|
||||
$category = \coursecat::get($category->id);
|
||||
$this->categoryid = $category->id;
|
||||
|
||||
$this->categories = $category->get_parents();
|
||||
$this->categories[] = $category->id;
|
||||
} else if (SITEID === $this->courseid) {
|
||||
// This is the site.
|
||||
// Show categories for all courses the user has access to.
|
||||
$this->categories = true;
|
||||
$categories = [];
|
||||
foreach ($courses as $course) {
|
||||
$category = \coursecat::get($course->category);
|
||||
$categories = array_merge($categories, $category->get_parents());
|
||||
$categories[] = $category->id;
|
||||
}
|
||||
|
||||
// And all categories that the user can manage.
|
||||
foreach (\coursecat::get_all() as $category) {
|
||||
if (!$category->has_manage_capability()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$categories = array_merge($categories, $category->get_parents());
|
||||
$categories[] = $category->id;
|
||||
}
|
||||
|
||||
$this->categories = array_unique($categories);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1087,15 +1166,17 @@ class calendar_information {
|
|||
* @param boolean $withduration whether only events starting within time range selected
|
||||
* or events in progress/already started selected as well
|
||||
* @param boolean $ignorehidden whether to select only visible events or all events
|
||||
* @param array|int|boolean $categories array of categories, category id or boolean for all/no course events
|
||||
* @return array $events of selected events or an empty array if there aren't any (or there was an error)
|
||||
*/
|
||||
function calendar_get_events($tstart, $tend, $users, $groups, $courses, $withduration=true, $ignorehidden=true) {
|
||||
function calendar_get_events($tstart, $tend, $users, $groups, $courses,
|
||||
$withduration = true, $ignorehidden = true, $categories = []) {
|
||||
global $DB;
|
||||
|
||||
$whereclause = '';
|
||||
$params = array();
|
||||
// Quick test.
|
||||
if (empty($users) && empty($groups) && empty($courses)) {
|
||||
if (empty($users) && empty($groups) && empty($courses) && empty($categories)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
|
@ -1103,12 +1184,12 @@ function calendar_get_events($tstart, $tend, $users, $groups, $courses, $withdur
|
|||
// Events from a number of users
|
||||
if(!empty($whereclause)) $whereclause .= ' OR';
|
||||
list($insqlusers, $inparamsusers) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED);
|
||||
$whereclause .= " (e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0)";
|
||||
$whereclause .= " (e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)";
|
||||
$params = array_merge($params, $inparamsusers);
|
||||
} else if($users === true) {
|
||||
// Events from ALL users
|
||||
if(!empty($whereclause)) $whereclause .= ' OR';
|
||||
$whereclause .= ' (e.userid != 0 AND e.courseid = 0 AND e.groupid = 0)';
|
||||
$whereclause .= ' (e.userid != 0 AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)';
|
||||
} else if($users === false) {
|
||||
// No user at all, do nothing
|
||||
}
|
||||
|
@ -1137,6 +1218,21 @@ function calendar_get_events($tstart, $tend, $users, $groups, $courses, $withdur
|
|||
$whereclause .= ' (e.groupid = 0 AND e.courseid != 0)';
|
||||
}
|
||||
|
||||
if ((is_array($categories) && !empty($categories)) || is_numeric($categories)) {
|
||||
if (!empty($whereclause)) {
|
||||
$whereclause .= ' OR';
|
||||
}
|
||||
list($insqlcategories, $inparamscategories) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
|
||||
$whereclause .= " (e.groupid = 0 AND e.courseid = 0 AND e.categoryid $insqlcategories)";
|
||||
$params = array_merge($params, $inparamscategories);
|
||||
} else if ($categories === true) {
|
||||
// Events from ALL categories.
|
||||
if (!empty($whereclause)) {
|
||||
$whereclause .= ' OR';
|
||||
}
|
||||
$whereclause .= ' (e.groupid = 0 AND e.courseid = 0 AND e.categoryid != 0)';
|
||||
}
|
||||
|
||||
// Security check: if, by now, we have NOTHING in $whereclause, then it means
|
||||
// that NO event-selecting clauses were defined. Thus, we won't be returning ANY
|
||||
// events no matter what. Allowing the code to proceed might return a completely
|
||||
|
@ -2050,19 +2146,27 @@ function calendar_edit_event_allowed($event, $manualedit = false) {
|
|||
// Allow users to add/edit group events if -
|
||||
// 1) They have manageentries for the course OR
|
||||
// 2) They have managegroupentries AND are in the group.
|
||||
$eventcontext = \context_course::instance($event->courseid);
|
||||
$group = $DB->get_record('groups', array('id' => $event->groupid));
|
||||
return $group && (
|
||||
has_capability('moodle/calendar:manageentries', $event->context) ||
|
||||
(has_capability('moodle/calendar:managegroupentries', $event->context)
|
||||
has_capability('moodle/calendar:manageentries', $eventcontext) ||
|
||||
(has_capability('moodle/calendar:managegroupentries', $eventcontext)
|
||||
&& groups_is_member($event->groupid)));
|
||||
} else if (!empty($event->courseid)) {
|
||||
// If groupid is not set, but course is set, it's definiely a course event.
|
||||
return has_capability('moodle/calendar:manageentries', $event->context);
|
||||
// If groupid is not set, but course is set, it's definitely a course event.
|
||||
$eventcontext = \context_course::instance($event->courseid);
|
||||
return has_capability('moodle/calendar:manageentries', $eventcontext);
|
||||
} else if (!empty($event->categoryid)) {
|
||||
// If groupid is not set, but category is set, it's definitely a category event.
|
||||
$eventcontext = \context_coursecat::instance($event->categoryid);
|
||||
return has_capability('moodle/calendar:manageentries', $eventcontext);
|
||||
} else if (!empty($event->userid) && $event->userid == $USER->id) {
|
||||
// If course is not set, but userid id set, it's a user event.
|
||||
return (has_capability('moodle/calendar:manageownentries', $event->context));
|
||||
$eventcontext = \context_user::instance($event->userid);
|
||||
return (has_capability('moodle/calendar:manageownentries', $eventcontext));
|
||||
} else if (!empty($event->userid)) {
|
||||
return (has_capability('moodle/calendar:manageentries', $event->context));
|
||||
$eventcontext = \context_user::instance($event->userid);
|
||||
return (has_capability('moodle/calendar:manageentries', $eventcontext));
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -2237,7 +2341,8 @@ function calendar_show_event_type($type, $user = null) {
|
|||
*/
|
||||
function calendar_set_event_type_display($type, $display = null, $user = null) {
|
||||
$persist = get_user_preferences('calendar_persistflt', 0, $user);
|
||||
$default = CALENDAR_EVENT_GLOBAL + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP + CALENDAR_EVENT_USER;
|
||||
$default = CALENDAR_EVENT_GLOBAL + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP
|
||||
+ CALENDAR_EVENT_USER + CALENDAR_EVENT_COURSECAT;
|
||||
if ($persist === 0) {
|
||||
global $SESSION;
|
||||
if (!isset($SESSION->calendarshoweventtype)) {
|
||||
|
@ -2273,14 +2378,16 @@ function calendar_set_event_type_display($type, $display = null, $user = null) {
|
|||
* @param stdClass $allowed list of allowed edit for event type
|
||||
* @param stdClass|int $course object of a course or course id
|
||||
* @param array $groups array of groups for the given course
|
||||
* @param stdClass|int $category object of a category
|
||||
*/
|
||||
function calendar_get_allowed_types(&$allowed, $course = null, $groups = null) {
|
||||
function calendar_get_allowed_types(&$allowed, $course = null, $groups = null, $category = null) {
|
||||
global $USER, $DB;
|
||||
|
||||
$allowed = new \stdClass();
|
||||
$allowed->user = has_capability('moodle/calendar:manageownentries', \context_system::instance());
|
||||
$allowed->groups = false;
|
||||
$allowed->courses = false;
|
||||
$allowed->categories = false;
|
||||
$allowed->site = has_capability('moodle/calendar:manageentries', \context_course::instance(SITEID));
|
||||
$getgroupsfunc = function($course, $context, $user) use ($groups) {
|
||||
if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
|
||||
|
@ -2316,6 +2423,13 @@ function calendar_get_allowed_types(&$allowed, $course = null, $groups = null) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($category)) {
|
||||
$catcontext = \context_coursecat::instance($category->id);
|
||||
if (has_capability('moodle/category:manage', $catcontext)) {
|
||||
$allowed->categories = [$category->id => 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2325,6 +2439,7 @@ function calendar_get_allowed_types(&$allowed, $course = null, $groups = null) {
|
|||
* The returned array will optionally have 5 keys:
|
||||
* 'user' : true if the logged in user can create user events
|
||||
* 'site' : true if the logged in user can create site events
|
||||
* 'category' : array of course categories that the user can create events for
|
||||
* 'course' : array of courses that the user can create events for
|
||||
* 'group': array of groups that the user can create events for
|
||||
* 'groupcourses' : array of courses that the groups belong to (can
|
||||
|
@ -2333,7 +2448,7 @@ function calendar_get_allowed_types(&$allowed, $course = null, $groups = null) {
|
|||
* @return array The array of allowed types.
|
||||
*/
|
||||
function calendar_get_all_allowed_types() {
|
||||
global $CFG, $USER;
|
||||
global $CFG, $USER, $DB;
|
||||
|
||||
require_once($CFG->libdir . '/enrollib.php');
|
||||
|
||||
|
@ -2349,6 +2464,10 @@ function calendar_get_all_allowed_types() {
|
|||
$types['site'] = true;
|
||||
}
|
||||
|
||||
if (coursecat::has_manage_capability_on_any()) {
|
||||
$types['category'] = coursecat::make_categories_list('moodle/category:manage');
|
||||
}
|
||||
|
||||
// This function warms the context cache for the course so the calls
|
||||
// to load the course context in calendar_get_allowed_types don't result
|
||||
// in additional DB queries.
|
||||
|
@ -2401,7 +2520,7 @@ function calendar_user_can_add_event($course) {
|
|||
|
||||
calendar_get_allowed_types($allowed, $course);
|
||||
|
||||
return (bool)($allowed->user || $allowed->groups || $allowed->courses || $allowed->site);
|
||||
return (bool)($allowed->user || $allowed->groups || $allowed->courses || $allowed->category || $allowed->site);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2426,6 +2545,8 @@ function calendar_add_event_allowed($event) {
|
|||
}
|
||||
|
||||
switch ($event->eventtype) {
|
||||
case 'category':
|
||||
return has_capability('moodle/category:manage', $event->context);
|
||||
case 'course':
|
||||
return has_capability('moodle/calendar:manageentries', $event->context);
|
||||
case 'group':
|
||||
|
@ -2485,6 +2606,9 @@ function calendar_get_eventtype_choices($courseid) {
|
|||
if (!empty($allowed->courses)) {
|
||||
$choices['course'] = get_string('courseevents', 'calendar');
|
||||
}
|
||||
if (!empty($allowed->categories)) {
|
||||
$choices['category'] = get_string('categoryevents', 'calendar');
|
||||
}
|
||||
if (!empty($allowed->groups) and is_array($allowed->groups)) {
|
||||
$choices['group'] = get_string('group');
|
||||
}
|
||||
|
@ -2505,6 +2629,8 @@ function calendar_add_subscription($sub) {
|
|||
$sub->courseid = $SITE->id;
|
||||
} else if ($sub->eventtype === 'group' || $sub->eventtype === 'course') {
|
||||
$sub->courseid = $sub->course;
|
||||
} else if ($sub->eventtype === 'category') {
|
||||
$sub->categoryid = $sub->category;
|
||||
} else {
|
||||
// User events.
|
||||
$sub->courseid = 0;
|
||||
|
@ -2956,11 +3082,12 @@ function core_calendar_user_preferences() {
|
|||
* @param boolean $ignorehidden whether to select only visible events or all events
|
||||
* @return array $events of selected events or an empty array if there aren't any (or there was an error)
|
||||
*/
|
||||
function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $withduration = true, $ignorehidden = true) {
|
||||
function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses,
|
||||
$withduration = true, $ignorehidden = true, $categories = []) {
|
||||
// Normalise the users, groups and courses parameters so that they are compliant with \core_calendar\local\api::get_events().
|
||||
// Existing functions that were using the old calendar_get_events() were passing a mixture of array, int, boolean for these
|
||||
// parameters, but with the new API method, only null and arrays are accepted.
|
||||
list($userparam, $groupparam, $courseparam) = array_map(function($param) {
|
||||
list($userparam, $groupparam, $courseparam, $categoryparam) = array_map(function($param) {
|
||||
// If parameter is true, return null.
|
||||
if ($param === true) {
|
||||
return null;
|
||||
|
@ -2978,7 +3105,7 @@ function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $
|
|||
|
||||
// No normalisation required.
|
||||
return $param;
|
||||
}, [$users, $groups, $courses]);
|
||||
}, [$users, $groups, $courses, $categories]);
|
||||
|
||||
$mapper = \core_calendar\local\event\container::get_event_mapper();
|
||||
$events = \core_calendar\local\api::get_events(
|
||||
|
@ -2993,6 +3120,7 @@ function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses, $
|
|||
$userparam,
|
||||
$groupparam,
|
||||
$courseparam,
|
||||
$categoryparam,
|
||||
$withduration,
|
||||
$ignorehidden
|
||||
);
|
||||
|
@ -3043,7 +3171,7 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
|
|||
}
|
||||
}
|
||||
|
||||
list($userparam, $groupparam, $courseparam) = array_map(function($param) {
|
||||
list($userparam, $groupparam, $courseparam, $categoryparam) = array_map(function($param) {
|
||||
// If parameter is true, return null.
|
||||
if ($param === true) {
|
||||
return null;
|
||||
|
@ -3061,7 +3189,7 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
|
|||
|
||||
// No normalisation required.
|
||||
return $param;
|
||||
}, [$calendar->users, $calendar->groups, $calendar->courses]);
|
||||
}, [$calendar->users, $calendar->groups, $calendar->courses, $calendar->categories]);
|
||||
|
||||
$events = \core_calendar\local\api::get_events(
|
||||
$tstart,
|
||||
|
@ -3075,13 +3203,19 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
|
|||
$userparam,
|
||||
$groupparam,
|
||||
$courseparam,
|
||||
$categoryparam,
|
||||
true,
|
||||
true,
|
||||
function ($event) {
|
||||
if ($proxy = $event->get_course_module()) {
|
||||
$cminfo = $proxy->get_proxied_instance();
|
||||
return $cminfo->uservisible;
|
||||
}
|
||||
|
||||
if ($proxy = $event->get_category()) {
|
||||
$category = $proxy->get_proxied_instance();
|
||||
|
||||
return $category->is_uservisible();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -3123,6 +3257,7 @@ function calendar_output_fragment_event_form($args) {
|
|||
$eventid = isset($args['eventid']) ? clean_param($args['eventid'], PARAM_INT) : null;
|
||||
$starttime = isset($args['starttime']) ? clean_param($args['starttime'], PARAM_INT) : null;
|
||||
$courseid = isset($args['courseid']) ? clean_param($args['courseid'], PARAM_INT) : null;
|
||||
$categoryid = isset($args['categoryid']) ? clean_param($args['categoryid'], PARAM_INT) : null;
|
||||
$event = null;
|
||||
$hasformdata = isset($args['formdata']) && !empty($args['formdata']);
|
||||
$context = \context_user::instance($USER->id);
|
||||
|
@ -3155,6 +3290,9 @@ function calendar_output_fragment_event_form($args) {
|
|||
$data['eventtype'] = 'course';
|
||||
$data['courseid'] = $courseid;
|
||||
$data['groupcourseid'] = $courseid;
|
||||
} else if (!empty($categoryid)) {
|
||||
$data['eventtype'] = 'category';
|
||||
$data['categoryid'] = $categoryid;
|
||||
}
|
||||
$mform->set_data($data);
|
||||
} else {
|
||||
|
@ -3262,6 +3400,7 @@ function calendar_get_footer_options($calendar) {
|
|||
function calendar_get_filter_types() {
|
||||
$types = [
|
||||
'site',
|
||||
'category',
|
||||
'course',
|
||||
'group',
|
||||
'user',
|
||||
|
@ -3274,3 +3413,20 @@ function calendar_get_filter_types() {
|
|||
];
|
||||
}, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the specified event type is valid.
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
function calendar_is_valid_eventtype($type) {
|
||||
$validtypes = [
|
||||
'user',
|
||||
'group',
|
||||
'course',
|
||||
'category',
|
||||
'site',
|
||||
];
|
||||
return in_array($type, $validtypes);
|
||||
}
|
||||
|
|
|
@ -46,6 +46,9 @@
|
|||
{{/description}}
|
||||
<h4>{{#str}} eventtype, core_calendar {{/str}}</h4>
|
||||
{{eventtype}}
|
||||
{{#iscategoryevent}}
|
||||
<div>{{{category.nestedname}}}</div>
|
||||
{{/iscategoryevent}}
|
||||
{{#iscourseevent}}
|
||||
<div><a href="{{url}}">{{course.fullname}}</a></div>
|
||||
{{/iscourseevent}}
|
||||
|
|
|
@ -33,7 +33,8 @@
|
|||
}}
|
||||
<div{{!
|
||||
}} class="calendarwrapper"{{!
|
||||
}} data-courseid="{{courseid}}"{{!
|
||||
}}{{#courseid}} data-courseid="{{courseid}}"{{/courseid}}{{!
|
||||
}}{{#categoryid}} data-categoryid="{{categoryid}}"{{/categoryid}}{{!
|
||||
}} data-month="{{date.mon}}"{{!
|
||||
}} data-year="{{date.year}}"{{!
|
||||
}}>
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
}} id="month-mini-{{date.year}}-{{date.month}}-{{uniqid}}"{{!
|
||||
}} class="calendarwrapper"{{!
|
||||
}} data-courseid="{{courseid}}"{{!
|
||||
}} data-categoryid="{{categoryid}}"{{!
|
||||
}} data-month="{{date.mon}}"{{!
|
||||
}} data-year="{{date.year}}"{{!
|
||||
}}>
|
||||
|
|
|
@ -108,6 +108,10 @@ class core_calendar_action_event_test_event implements event_interface {
|
|||
return new event_description('asdf', 1);
|
||||
}
|
||||
|
||||
public function get_category() {
|
||||
return new \stdClass();
|
||||
}
|
||||
|
||||
public function get_course() {
|
||||
return new \stdClass();
|
||||
}
|
||||
|
|
96
calendar/tests/behat/category_events.feature
Normal file
96
calendar/tests/behat/category_events.feature
Normal file
|
@ -0,0 +1,96 @@
|
|||
@core @core_calendar
|
||||
Feature: Course Category Events
|
||||
In order to inform multiple courses of shared events
|
||||
As a manager
|
||||
I need to create catgory events
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| managera | Manager | A | managera@example.com |
|
||||
| managera1 | Manager | A1 | managera1@example.com |
|
||||
| managera2 | Manager | A2 | managera2@example.com |
|
||||
| teachera1i | Teacher | A1i | teachera1i@example.com |
|
||||
| managerb | Manager | B | managerb@example.com |
|
||||
| managerb1 | Manager | B1 | managerb1@example.com |
|
||||
| managerb2 | Manager | B2 | managerb2@example.com |
|
||||
| teacherb1i | Teacher | B1i | teacherb1i@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
| student2 | Student | 2 | student2@example.com |
|
||||
And the following "categories" exist:
|
||||
| name | idnumber | category |
|
||||
| Year | year | |
|
||||
| Faculty A | faculty-a | year |
|
||||
| Faculty B | faculty-b | year |
|
||||
| Department A1 | department-a1 | faculty-a |
|
||||
| Department A2 | department-a2 | faculty-a |
|
||||
| Department B1 | department-b1 | faculty-b |
|
||||
| Department B2 | department-b2 | faculty-b |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | idnumber | format | category |
|
||||
| Course A1i | A1i | A1i | topics | department-a1 |
|
||||
| Course A2i | A2i | A2i | topics | department-a2 |
|
||||
| Course B1i | B1i | B1i | topics | department-b1 |
|
||||
| Course B2i | B2i | B2i | topics | department-b2 |
|
||||
And the following "role assigns" exist:
|
||||
| user | role | contextlevel | reference |
|
||||
| managera | manager | Category | faculty-a |
|
||||
| managera1 | manager | Category | department-a1 |
|
||||
| managerb | manager | Category | faculty-b |
|
||||
| managerb1 | manager | Category | department-b1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teachera1i | A1i | editingteacher |
|
||||
| teacherb1i | B1i | editingteacher |
|
||||
| student1 | A1i | student |
|
||||
| student1 | A2i | student |
|
||||
| student2 | B1i | student |
|
||||
| student2 | B2i | student |
|
||||
And the following "events" exist:
|
||||
| name | eventtype | category | course |
|
||||
| Site event | global | | |
|
||||
| FA event | category | faculty-a | |
|
||||
| DA1 event | category | department-a1 | |
|
||||
| DA2 event | category | department-a1 | |
|
||||
| FB event | category | faculty-b | |
|
||||
| DB1 event | category | department-b1 | |
|
||||
| DB2 event | category | department-b1 | |
|
||||
| CA1i event | course | | A1i |
|
||||
| CA2i event | course | | A2i |
|
||||
| CB1i event | course | | B1i |
|
||||
| CB2i event | course | | B2i |
|
||||
|
||||
@javascript
|
||||
Scenario: Manager of a Category can see all child and parent events in their category
|
||||
Given I log in as "managera"
|
||||
When I navigate to "Calendar" node in "Site pages"
|
||||
Then I should see "FA event"
|
||||
And I should see "DA1 event"
|
||||
And I should see "DA2 event"
|
||||
And I should not see "FB event"
|
||||
And I should not see "DB1 event"
|
||||
And I should not see "DB2 event"
|
||||
And I log out
|
||||
Given I log in as "managerb"
|
||||
When I navigate to "Calendar" node in "Site pages"
|
||||
Then I should see "FB event"
|
||||
And I should see "DB1 event"
|
||||
And I should see "DB2 event"
|
||||
And I should not see "FA event"
|
||||
And I should not see "DA1 event"
|
||||
And I should not see "DA2 event"
|
||||
|
||||
@javascript
|
||||
Scenario: Users enrolled in a course can see all child and parent events in their category
|
||||
Given I log in as "student1"
|
||||
When I navigate to "Calendar" node in "Site pages"
|
||||
Then I should see "FA event"
|
||||
And I should see "DA1 event"
|
||||
And I should see "DA2 event"
|
||||
And I should see "CA1i event"
|
||||
And I should see "CA2i event"
|
||||
And I should not see "FB event"
|
||||
And I should not see "DB1 event"
|
||||
And I should not see "DB2 event"
|
||||
And I should not see "CB1i event"
|
||||
And I should not see "CB2i event"
|
|
@ -240,6 +240,84 @@ class core_calendar_container_testcase extends advanced_testcase {
|
|||
$this->assertNull($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the event factory deals with invisible categorys as an admin.
|
||||
*/
|
||||
public function test_event_factory_when_category_visibility_is_toggled_as_admin() {
|
||||
// Create a hidden category.
|
||||
$category = $this->getDataGenerator()->create_category(['visible' => 0]);
|
||||
|
||||
$eventdata = [
|
||||
'categoryid' => $category->id,
|
||||
'eventtype' => 'category',
|
||||
];
|
||||
$legacyevent = $this->create_event($eventdata);
|
||||
|
||||
$dbrow = $this->get_dbrow_from_skeleton((object) $eventdata);
|
||||
$dbrow->id = $legacyevent->id;
|
||||
|
||||
$factory = \core_calendar\local\event\container::get_event_factory();
|
||||
$event = $factory->create_instance($dbrow);
|
||||
|
||||
// Module is still visible to admins even if the category is invisible.
|
||||
$this->assertInstanceOf(event_interface::class, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the event factory deals with invisible categorys as an user.
|
||||
*/
|
||||
public function test_event_factory_when_category_visibility_is_toggled_as_user() {
|
||||
// Create a hidden category.
|
||||
$category = $this->getDataGenerator()->create_category(['visible' => 0]);
|
||||
|
||||
$eventdata = [
|
||||
'categoryid' => $category->id,
|
||||
'eventtype' => 'category',
|
||||
];
|
||||
$legacyevent = $this->create_event($eventdata);
|
||||
|
||||
$dbrow = $this->get_dbrow_from_skeleton((object) $eventdata);
|
||||
$dbrow->id = $legacyevent->id;
|
||||
|
||||
// Use a standard user.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
// Set the user to the student.
|
||||
$this->setUser($user);
|
||||
|
||||
$factory = \core_calendar\local\event\container::get_event_factory();
|
||||
$event = $factory->create_instance($dbrow);
|
||||
|
||||
// Module is invisible to non-privileged users.
|
||||
$this->assertNull($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the event factory deals with invisible categorys as an guest.
|
||||
*/
|
||||
public function test_event_factory_when_category_visibility_is_toggled_as_guest() {
|
||||
// Create a hidden category.
|
||||
$category = $this->getDataGenerator()->create_category(['visible' => 0]);
|
||||
|
||||
$eventdata = [
|
||||
'categoryid' => $category->id,
|
||||
'eventtype' => 'category',
|
||||
];
|
||||
$legacyevent = $this->create_event($eventdata);
|
||||
|
||||
$dbrow = $this->get_dbrow_from_skeleton((object) $eventdata);
|
||||
$dbrow->id = $legacyevent->id;
|
||||
|
||||
// Set the user to the student.
|
||||
$this->setGuestUser();
|
||||
|
||||
$factory = \core_calendar\local\event\container::get_event_factory();
|
||||
$event = $factory->create_instance($dbrow);
|
||||
|
||||
// Module is invisible to guests.
|
||||
$this->assertNull($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the event factory deals with completion related events properly.
|
||||
*/
|
||||
|
@ -264,6 +342,7 @@ class core_calendar_container_testcase extends advanced_testcase {
|
|||
$event->userid = 1;
|
||||
$event->modulename = 'assign';
|
||||
$event->instance = $assign->id;
|
||||
$event->categoryid = 0;
|
||||
$event->courseid = $course->id;
|
||||
$event->groupid = 0;
|
||||
$event->timestart = time();
|
||||
|
@ -312,6 +391,7 @@ class core_calendar_container_testcase extends advanced_testcase {
|
|||
$event->userid = $user->id;
|
||||
$event->modulename = 'lesson';
|
||||
$event->instance = $lesson->id;
|
||||
$event->categoryid = 0;
|
||||
$event->courseid = $course->id;
|
||||
$event->groupid = 0;
|
||||
$event->timestart = time();
|
||||
|
@ -397,6 +477,7 @@ class core_calendar_container_testcase extends advanced_testcase {
|
|||
'name' => 'Test event',
|
||||
'description' => 'Hello',
|
||||
'format' => 1,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 1,
|
||||
'groupid' => 0,
|
||||
'userid' => 1,
|
||||
|
@ -418,6 +499,7 @@ class core_calendar_container_testcase extends advanced_testcase {
|
|||
'name' => 'Test event',
|
||||
'description' => 'Hello',
|
||||
'format' => 1,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 1,
|
||||
'groupid' => 1,
|
||||
'userid' => 1,
|
||||
|
@ -459,4 +541,38 @@ class core_calendar_container_testcase extends advanced_testcase {
|
|||
$event = new calendar_event($record);
|
||||
return $event->create($record, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad out a basic DB row with basic information.
|
||||
*
|
||||
* @param \stdClass $skeleton the current skeleton
|
||||
* @return \stdClass
|
||||
*/
|
||||
protected function get_dbrow_from_skeleton($skeleton) {
|
||||
$dbrow = (object) [
|
||||
'name' => 'Name',
|
||||
'description' => 'Description',
|
||||
'format' => 1,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 0,
|
||||
'groupid' => 0,
|
||||
'userid' => 0,
|
||||
'repeatid' => 0,
|
||||
'modulename' => '',
|
||||
'instance' => 0,
|
||||
'eventtype' => 'user',
|
||||
'timestart' => 1486396800,
|
||||
'timeduration' => 0,
|
||||
'timesort' => 1486396800,
|
||||
'visible' => 1,
|
||||
'timemodified' => 1485793098,
|
||||
'subscriptionid' => null
|
||||
];
|
||||
|
||||
foreach ((array) $skeleton as $key => $value) {
|
||||
$dbrow->$key = $value;
|
||||
}
|
||||
|
||||
return $dbrow;
|
||||
}
|
||||
}
|
||||
|
|
64
calendar/tests/coursecat_proxy_test.php
Normal file
64
calendar/tests/coursecat_proxy_test.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* coursecat_proxy tests.
|
||||
*
|
||||
* @package core_calendar
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_calendar\local\event\proxies\coursecat_proxy;
|
||||
|
||||
/**
|
||||
* coursecat_proxy testcase.
|
||||
*
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_calendar_coursecat_proxy_testcase extends advanced_testcase {
|
||||
|
||||
public function test_valid_coursecat() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$name = '2027-2028 Academic Year';
|
||||
$generator = $this->getDataGenerator();
|
||||
$category = $generator->create_category([
|
||||
'name' => $name,
|
||||
]);
|
||||
cache_helper::purge_by_event('changesincoursecat');
|
||||
|
||||
// Fetch the proxy.
|
||||
$startreads = $DB->perf_get_reads();
|
||||
$proxy = new coursecat_proxy($category->id);
|
||||
$this->assertInstanceOf(coursecat_proxy::class, $proxy);
|
||||
$this->assertEquals(0, $DB->perf_get_reads() - $startreads);
|
||||
|
||||
// Fetch the ID - this is known and doesn't require a cache read.
|
||||
$this->assertEquals($category->id, $proxy->get('id'));
|
||||
$this->assertEquals(0, $DB->perf_get_reads() - $startreads);
|
||||
|
||||
// Fetch the name - not known, and requires a read.
|
||||
$this->assertEquals($name, $proxy->get('name'));
|
||||
$this->assertEquals(1, $DB->perf_get_reads() - $startreads);
|
||||
|
||||
$this->assertInstanceOf('coursecat', $proxy->get_proxied_instance());
|
||||
}
|
||||
}
|
|
@ -114,6 +114,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
|
|||
'name' => 'test',
|
||||
'description' => 'Test description',
|
||||
'format' => 2,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 1,
|
||||
'groupid' => 1,
|
||||
'userid' => 1,
|
||||
|
@ -162,6 +163,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
|
|||
'name' => 'test',
|
||||
'description' => 'Test description',
|
||||
'format' => 2,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 1,
|
||||
'groupid' => 1,
|
||||
'userid' => 1,
|
||||
|
@ -210,6 +212,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
|
|||
'name' => 'test',
|
||||
'description' => 'Test description',
|
||||
'format' => 2,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 1,
|
||||
'groupid' => 1,
|
||||
'userid' => 1,
|
||||
|
@ -258,6 +261,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
|
|||
'name' => 'test',
|
||||
'description' => 'Test description',
|
||||
'format' => 2,
|
||||
'categoryid' => 0,
|
||||
'courseid' => $course->id,
|
||||
'groupid' => 1,
|
||||
'userid' => 1,
|
||||
|
@ -312,6 +316,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
|
|||
'name' => 'test',
|
||||
'description' => 'Test description',
|
||||
'format' => 2,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 0,
|
||||
'groupid' => 1,
|
||||
'userid' => 1,
|
||||
|
@ -345,6 +350,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
|
|||
'name' => 'Test event',
|
||||
'description' => 'Hello',
|
||||
'format' => 1,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 1,
|
||||
'groupid' => 1,
|
||||
'userid' => 1,
|
||||
|
@ -378,6 +384,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
|
|||
'name' => 'Test event',
|
||||
'description' => 'Hello',
|
||||
'format' => 1,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 1,
|
||||
'groupid' => 1,
|
||||
'userid' => 1,
|
||||
|
@ -411,6 +418,7 @@ class core_calendar_event_factory_testcase extends advanced_testcase {
|
|||
'name' => 'Test event',
|
||||
'description' => 'Hello',
|
||||
'format' => 1,
|
||||
'categoryid' => 0,
|
||||
'courseid' => 1,
|
||||
'groupid' => 1,
|
||||
'userid' => 1,
|
||||
|
|
|
@ -202,6 +202,10 @@ class event_mapper_test_action_event implements action_event_interface {
|
|||
return $this->event->get_description();
|
||||
}
|
||||
|
||||
public function get_category() {
|
||||
return $this->event->get_category();
|
||||
}
|
||||
|
||||
public function get_course() {
|
||||
return $this->event->get_course();
|
||||
}
|
||||
|
@ -255,6 +259,11 @@ class event_mapper_test_action_event implements action_event_interface {
|
|||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class event_mapper_test_event implements event_interface {
|
||||
/**
|
||||
* @var proxy_interface $categoryproxy Category proxy.
|
||||
*/
|
||||
protected $categoryproxy;
|
||||
|
||||
/**
|
||||
* @var proxy_interface $courseproxy Course proxy.
|
||||
*/
|
||||
|
@ -312,6 +321,10 @@ class event_mapper_test_event implements event_interface {
|
|||
return new event_description('asdf', 1);
|
||||
}
|
||||
|
||||
public function get_category() {
|
||||
return $this->categoryproxy;
|
||||
}
|
||||
|
||||
public function get_course() {
|
||||
return $this->courseproxy;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die();
|
|||
|
||||
use core_calendar\local\event\entities\event;
|
||||
use core_calendar\local\event\proxies\std_proxy;
|
||||
use core_calendar\local\event\proxies\coursecat_proxy;
|
||||
use core_calendar\local\event\value_objects\event_description;
|
||||
use core_calendar\local\event\value_objects\event_times;
|
||||
use core_calendar\local\event\entities\event_collection_interface;
|
||||
|
@ -48,6 +49,7 @@ class core_calendar_event_testcase extends advanced_testcase {
|
|||
$constructorparams['id'],
|
||||
$constructorparams['name'],
|
||||
$constructorparams['description'],
|
||||
$constructorparams['category'],
|
||||
$constructorparams['course'],
|
||||
$constructorparams['group'],
|
||||
$constructorparams['user'],
|
||||
|
@ -82,6 +84,7 @@ class core_calendar_event_testcase extends advanced_testcase {
|
|||
'id' => 1,
|
||||
'name' => 'Test event 1',
|
||||
'description' => new event_description('asdf', 1),
|
||||
'category' => new coursecat_proxy(0),
|
||||
'course' => new std_proxy(1, $lamecallable),
|
||||
'group' => new std_proxy(1, $lamecallable),
|
||||
'user' => new std_proxy(1, $lamecallable),
|
||||
|
|
|
@ -1600,7 +1600,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
|||
public function test_submit_create_update_form_create_site_event() {
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$context = context_course::instance(SITEID);
|
||||
$context = context_system::instance();
|
||||
$roleid = $generator->create_role();
|
||||
$timestart = new DateTime();
|
||||
$interval = new DateInterval("P1D"); // One day.
|
||||
|
|
|
@ -32,6 +32,7 @@ use core_calendar\local\event\entities\action_event;
|
|||
use core_calendar\local\event\entities\event;
|
||||
use core_calendar\local\event\entities\repeat_event_collection;
|
||||
use core_calendar\local\event\proxies\std_proxy;
|
||||
use core_calendar\local\event\proxies\coursecat_proxy;
|
||||
use core_calendar\local\event\proxies\cm_info_proxy;
|
||||
use core_calendar\local\event\value_objects\action;
|
||||
use core_calendar\local\event\value_objects\event_description;
|
||||
|
@ -108,6 +109,7 @@ class action_event_test_factory implements event_factory_interface {
|
|||
$record->id,
|
||||
$record->name,
|
||||
new event_description($record->description, $record->format),
|
||||
new coursecat_proxy($record->categoryid),
|
||||
new std_proxy($record->courseid, function($id) {
|
||||
$course = new \stdClass();
|
||||
$course->id = $id;
|
||||
|
|
|
@ -277,4 +277,69 @@ class core_calendar_raw_event_retrieval_strategy_testcase extends advanced_testc
|
|||
$events = $retrievalstrategy->get_raw_events();
|
||||
$this->assertCount(3, $events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test retrieval strategy with category specifications.
|
||||
*/
|
||||
public function test_get_raw_events_category() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$retrievalstrategy = new raw_event_retrieval_strategy();
|
||||
$generator = $this->getDataGenerator();
|
||||
$category1 = $generator->create_category();
|
||||
$category2 = $generator->create_category();
|
||||
$events = [
|
||||
[
|
||||
'name' => 'E1',
|
||||
'eventtype' => 'category',
|
||||
'description' => '',
|
||||
'format' => 1,
|
||||
'categoryid' => $category1->id,
|
||||
'userid' => 2,
|
||||
'timestart' => time(),
|
||||
],
|
||||
[
|
||||
'name' => 'E2',
|
||||
'eventtype' => 'category',
|
||||
'description' => '',
|
||||
'format' => 1,
|
||||
'categoryid' => $category2->id,
|
||||
'userid' => 2,
|
||||
'timestart' => time() + 1,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($events as $event) {
|
||||
calendar_event::create($event, false);
|
||||
}
|
||||
|
||||
// Get all events.
|
||||
$events = $retrievalstrategy->get_raw_events(null, null, null, null);
|
||||
$this->assertCount(2, $events);
|
||||
|
||||
$event = array_shift($events);
|
||||
$this->assertEquals('E1', $event->name);
|
||||
$event = array_shift($events);
|
||||
$this->assertEquals('E2', $event->name);
|
||||
|
||||
// Get events for C1 events.
|
||||
$events = $retrievalstrategy->get_raw_events(null, null, null, [$category1->id]);
|
||||
$this->assertCount(1, $events);
|
||||
|
||||
$event = array_shift($events);
|
||||
$this->assertEquals('E1', $event->name);
|
||||
|
||||
// Get events for C2 events.
|
||||
$events = $retrievalstrategy->get_raw_events(null, null, null, [$category2->id]);
|
||||
$this->assertCount(1, $events);
|
||||
|
||||
$event = array_shift($events);
|
||||
$this->assertEquals('E2', $event->name);
|
||||
|
||||
// Get events for several categories.
|
||||
$events = $retrievalstrategy->get_raw_events(null, null, null, [$category1->id, $category2->id]);
|
||||
$this->assertCount(2, $events);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ require_once($CFG->dirroot . '/calendar/lib.php');
|
|||
|
||||
use core_calendar\local\event\entities\event;
|
||||
use core_calendar\local\event\entities\repeat_event_collection;
|
||||
use core_calendar\local\event\proxies\coursecat_proxy;
|
||||
use core_calendar\local\event\proxies\std_proxy;
|
||||
use core_calendar\local\event\value_objects\event_description;
|
||||
use core_calendar\local\event\value_objects\event_times;
|
||||
|
@ -161,6 +162,7 @@ class core_calendar_repeat_event_collection_event_test_factory implements event_
|
|||
$dbrow->id,
|
||||
$dbrow->name,
|
||||
new event_description($dbrow->description, $dbrow->format),
|
||||
new coursecat_proxy($dbrow->categoryid),
|
||||
new std_proxy($dbrow->courseid, $identity),
|
||||
new std_proxy($dbrow->groupid, $identity),
|
||||
new std_proxy($dbrow->userid, $identity),
|
||||
|
|
|
@ -49,6 +49,7 @@ require_once('../config.php');
|
|||
require_once($CFG->dirroot.'/course/lib.php');
|
||||
require_once($CFG->dirroot.'/calendar/lib.php');
|
||||
|
||||
$categoryid = optional_param('category', null, PARAM_INT);
|
||||
$courseid = optional_param('course', SITEID, PARAM_INT);
|
||||
$view = optional_param('view', 'upcoming', PARAM_ALPHA);
|
||||
$time = optional_param('time', 0, PARAM_INT);
|
||||
|
@ -63,6 +64,10 @@ if ($courseid != SITEID) {
|
|||
$url->param('course', $courseid);
|
||||
}
|
||||
|
||||
if ($categoryid) {
|
||||
$url->param('categoryid', $categoryid);
|
||||
}
|
||||
|
||||
if ($view !== 'upcoming') {
|
||||
$time = usergetmidnight($time);
|
||||
$url->param('view', $view);
|
||||
|
@ -76,18 +81,30 @@ if ($courseid != SITEID && !empty($courseid)) {
|
|||
// Course ID must be valid and existing.
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
$courses = array($course->id => $course);
|
||||
$issite = false;
|
||||
navigation_node::override_active_url(new moodle_url('/course/view.php', array('id' => $course->id)));
|
||||
} else {
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
$issite = true;
|
||||
if ($categoryid) {
|
||||
$PAGE->set_category_by_id($categoryid);
|
||||
} else {
|
||||
$PAGE->set_context(context_system::instance());
|
||||
}
|
||||
if ($PAGE->context->contextlevel === CONTEXT_COURSECAT) {
|
||||
// Restrict to categories, and their parents, and the courses that the user is enrolled in within those
|
||||
// categories.
|
||||
$categories = array_keys($PAGE->categories);
|
||||
$courses = array_filter($courses, function($course) use ($categories) {
|
||||
return array_search($course->category, $categories) !== false;
|
||||
});
|
||||
navigation_node::override_active_url(new moodle_url('/course/index.php', array('categoryid' => $categoryid)));
|
||||
}
|
||||
}
|
||||
|
||||
require_login($course, false);
|
||||
|
||||
$calendar = new calendar_information(0, 0, 0, $time);
|
||||
$calendar->prepare_for_view($course, $courses);
|
||||
$calendar->set_sources($course, $courses, $PAGE->category);
|
||||
|
||||
$pagetitle = '';
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ $string['calendarheading'] = '{$a} Calendar';
|
|||
$string['calendarpreferences'] = 'Calendar preferences';
|
||||
$string['calendartypes'] = 'Calendar types';
|
||||
$string['calendarurl'] = 'Calendar URL: {$a}';
|
||||
$string['categoryevent'] = 'Category event';
|
||||
$string['clickhide'] = 'click to hide';
|
||||
$string['clickshow'] = 'click to show';
|
||||
$string['colcalendar'] = 'Calendar';
|
||||
|
@ -44,6 +45,7 @@ $string['confirmeventseriesdelete'] = 'The "{$a->name}" event is part of a serie
|
|||
$string['course'] = 'Course';
|
||||
$string['courseevent'] = 'Course event';
|
||||
$string['courseevents'] = 'Course events';
|
||||
$string['categoryevents'] = 'Category events';
|
||||
$string['courses'] = 'Courses';
|
||||
$string['customexport'] = 'Custom range ({$a->timestart} - {$a->timeend})';
|
||||
$string['daily'] = 'Daily';
|
||||
|
@ -97,6 +99,7 @@ $string['eventendtimewrapped'] = '{$a} (End time)';
|
|||
$string['eventinstanttime'] = 'Time';
|
||||
$string['eventkind'] = 'Type of event';
|
||||
$string['eventname'] = 'Event title';
|
||||
$string['eventnameandcategory'] = '{$a->category}: {$a->name}';
|
||||
$string['eventnameandcourse'] = '{$a->course}: {$a->name}';
|
||||
$string['eventnone'] = 'No events';
|
||||
$string['eventrepeat'] = 'Repeats';
|
||||
|
@ -141,6 +144,7 @@ $string['groupevent'] = 'Group event';
|
|||
$string['groupevents'] = 'Group events';
|
||||
$string['eventtypeglobal'] = 'global';
|
||||
$string['eventtypesite'] = 'global';
|
||||
$string['eventtypecategory'] = 'category';
|
||||
$string['eventtypecourse'] = 'course';
|
||||
$string['eventtypemodule'] = 'module';
|
||||
$string['eventtypegroup'] = 'group';
|
||||
|
@ -227,6 +231,7 @@ $string['tue'] = 'Tue';
|
|||
$string['tuesday'] = 'Tuesday';
|
||||
$string['typeclose'] = 'Close event';
|
||||
$string['typecourse'] = 'Course event';
|
||||
$string['typecategory'] = 'Category event';
|
||||
$string['typedue'] = 'Due event';
|
||||
$string['typegradingdue'] = 'Grading due event';
|
||||
$string['typegroup'] = 'Group event';
|
||||
|
|
133
lib/classes/external/coursecat_summary_exporter.php
vendored
Normal file
133
lib/classes/external/coursecat_summary_exporter.php
vendored
Normal file
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Class for exporting summary information for a course category.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core\external;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use renderer_base;
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* Class for exporting a course summary from an stdClass.
|
||||
*
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class coursecat_summary_exporter extends \core\external\exporter {
|
||||
|
||||
/**
|
||||
* @var \coursecat $category
|
||||
*/
|
||||
protected $category;
|
||||
|
||||
public function __construct(\coursecat $category, $related) {
|
||||
$this->category = $category;
|
||||
|
||||
$data = [];
|
||||
// Specify some defaults.
|
||||
foreach ($category as $key => $value) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
|
||||
return parent::__construct($data, $related);
|
||||
}
|
||||
|
||||
protected static function define_related() {
|
||||
return [
|
||||
'context' => 'context',
|
||||
];
|
||||
}
|
||||
|
||||
public static function define_other_properties() {
|
||||
return [
|
||||
'nestedname' => [
|
||||
'type' => PARAM_RAW,
|
||||
],
|
||||
'url' => [
|
||||
'type' => PARAM_URL,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
$return = [
|
||||
'nestedname' => $this->category->get_nested_name(),
|
||||
'url' => (new moodle_url('/course/index.php', [
|
||||
'categoryid' => $this->category->id,
|
||||
]))->out(false),
|
||||
];
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public static function define_properties() {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => PARAM_INT,
|
||||
],
|
||||
'name' => [
|
||||
'type' => PARAM_TEXT,
|
||||
'default' => '',
|
||||
],
|
||||
'idnumber' => [
|
||||
'type' => PARAM_RAW,
|
||||
'null' => NULL_ALLOWED,
|
||||
],
|
||||
'description' => [
|
||||
'type' => PARAM_RAW,
|
||||
'optional' => true,
|
||||
],
|
||||
'parent' => [
|
||||
'type' => PARAM_INT,
|
||||
],
|
||||
'coursecount' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => 0,
|
||||
],
|
||||
'visible' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => 1,
|
||||
],
|
||||
'timemodified' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => 0,
|
||||
],
|
||||
'depth' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatting parameters for the summary.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_format_parameters_for_description() {
|
||||
return [
|
||||
'component' => 'coursecat',
|
||||
'filearea' => 'description',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -203,6 +203,7 @@ class icon_system_fontawesome extends icon_system_font {
|
|||
'core:i/completion_self' => 'fa-user-o',
|
||||
'core:i/dashboard' => 'fa-tachometer',
|
||||
'core:i/lock' => 'fa-lock',
|
||||
'core:i/categoryevent' => 'fa-users',
|
||||
'core:i/courseevent' => 'fa-calendar',
|
||||
'core:i/db' => 'fa-database',
|
||||
'core:i/delete' => 'fa-trash',
|
||||
|
|
|
@ -291,6 +291,48 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
|
|||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all coursecat objects.
|
||||
*
|
||||
* @param array $options Options:
|
||||
* @param bool $options.returnhidden Return categories even if they are hidden
|
||||
* @return coursecat[]
|
||||
*/
|
||||
public static function get_all($options = []) {
|
||||
global $DB;
|
||||
|
||||
$coursecatrecordcache = cache::make('core', 'coursecatrecords');
|
||||
|
||||
$catcontextsql = \context_helper::get_preload_record_columns_sql('ctx');
|
||||
$catsql = "SELECT cc.*, {$catcontextsql}
|
||||
FROM {course_categories} cc
|
||||
JOIN {context} ctx ON cc.id = ctx.instanceid";
|
||||
$catsqlwhere = "WHERE ctx.contextlevel = :contextlevel";
|
||||
$catsqlorder = "ORDER BY cc.depth ASC, cc.sortorder ASC";
|
||||
|
||||
$catrs = $DB->get_recordset_sql("{$catsql} {$catsqlwhere} {$catsqlorder}", [
|
||||
'contextlevel' => CONTEXT_COURSECAT,
|
||||
]);
|
||||
|
||||
$types['categories'] = [];
|
||||
$categories = [];
|
||||
$toset = [];
|
||||
foreach ($catrs as $record) {
|
||||
$category = new coursecat($record);
|
||||
$toset[$category->id] = $category;
|
||||
|
||||
if (!empty($options['returnhidden']) || $category->is_uservisible()) {
|
||||
$categories[$record->id] = $category;
|
||||
}
|
||||
}
|
||||
$catrs->close();
|
||||
|
||||
$coursecatrecordcache->set_many($toset);
|
||||
|
||||
return $categories;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first found category
|
||||
*
|
||||
|
@ -1258,6 +1300,17 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
|
|||
array($this->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link used to view this course category.
|
||||
*
|
||||
* @return \moodle_url
|
||||
*/
|
||||
public function get_view_link() {
|
||||
return new \moodle_url('/course/index.php', [
|
||||
'categoryid' => $this->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches courses
|
||||
*
|
||||
|
@ -1703,6 +1756,9 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
|
|||
throw new moodle_exception('cannotdeletecategoryquestions', '', '', $this->get_formatted_name());
|
||||
}
|
||||
|
||||
// Delete all events in the category.
|
||||
$DB->delete_records('event', array('categoryid' => $this->id));
|
||||
|
||||
// Finally delete the category and it's context.
|
||||
$DB->delete_records('course_categories', array('id' => $this->id));
|
||||
|
||||
|
@ -2158,6 +2214,32 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the nested name of this category, with all of it's parents.
|
||||
*
|
||||
* @param bool $includelinks Whether to wrap each name in the view link for that category.
|
||||
* @param string $separator The string between each name.
|
||||
* @param array $options Formatting options.
|
||||
* @return string
|
||||
*/
|
||||
public function get_nested_name($includelinks = true, $separator = ' / ', $options = []) {
|
||||
// Get the name of hierarchical name of this category.
|
||||
$parents = $this->get_parents();
|
||||
$categories = static::get_many($parents);
|
||||
$categories[] = $this;
|
||||
|
||||
$names = array_map(function($category) use ($options, $includelinks) {
|
||||
if ($includelinks) {
|
||||
return html_writer::link($category->get_view_link(), $category->get_formatted_name($options));
|
||||
} else {
|
||||
return $category->get_formatted_name($options);
|
||||
}
|
||||
|
||||
}, $categories);
|
||||
|
||||
return implode($separator, $names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ids of all parents of the category. Last element in the return array is the direct parent
|
||||
*
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20170904" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20170921" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
|
@ -426,6 +426,7 @@
|
|||
<FIELD NAME="name" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="description" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="format" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="categoryid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="groupid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
|
@ -446,6 +447,7 @@
|
|||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="categoryid" TYPE="foreign" FIELDS="categoryid" REFTABLE="course_categories" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="courseid" UNIQUE="false" FIELDS="courseid"/>
|
||||
|
@ -453,7 +455,7 @@
|
|||
<INDEX NAME="timestart" UNIQUE="false" FIELDS="timestart"/>
|
||||
<INDEX NAME="timeduration" UNIQUE="false" FIELDS="timeduration"/>
|
||||
<INDEX NAME="type-timesort" UNIQUE="false" FIELDS="type, timesort"/>
|
||||
<INDEX NAME="groupid-courseid-visible-userid" UNIQUE="false" FIELDS="groupid, courseid, visible, userid" COMMENT="used for calendar view"/>
|
||||
<INDEX NAME="groupid-courseid-categoryid-visible-userid" UNIQUE="false" FIELDS="groupid, courseid, categoryid, visible, userid" COMMENT="used for calendar view"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="cache_filters" COMMENT="For keeping information about cached data">
|
||||
|
@ -3719,4 +3721,4 @@
|
|||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
|
@ -2570,5 +2570,36 @@ function xmldb_main_upgrade($oldversion) {
|
|||
upgrade_main_savepoint(true, 2017092700.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2017092900.00) {
|
||||
// Define field categoryid to be added to event.
|
||||
$table = new xmldb_table('event');
|
||||
$field = new xmldb_field('categoryid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'format');
|
||||
|
||||
// Conditionally launch add field categoryid.
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Add the categoryid key.
|
||||
$key = new xmldb_key('categoryid', XMLDB_KEY_FOREIGN, array('categoryid'), 'course_categories', array('id'));
|
||||
$dbman->add_key($table, $key);
|
||||
|
||||
// Add a new index for groupid/courseid/categoryid/visible/userid.
|
||||
// Do this before we remove the old index.
|
||||
$index = new xmldb_index('groupid-courseid-categoryid-visible-userid', XMLDB_INDEX_NOTUNIQUE, array('groupid', 'courseid', 'categoryid', 'visible', 'userid'));
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Drop the old index.
|
||||
$index = new xmldb_index('groupid-courseid-visible-userid', XMLDB_INDEX_NOTUNIQUE, array('groupid', 'courseid', 'visible', 'userid'));
|
||||
if ($dbman->index_exists($table, $index)) {
|
||||
$dbman->drop_index($table, $index);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2017092900.00);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1103,4 +1103,59 @@ EOD;
|
|||
// Get the tool associated with this instance.
|
||||
return $DB->get_record('enrol_lti_tools', array('enrolid' => $instanceid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used to create an event.
|
||||
*
|
||||
* @param array $data
|
||||
* @return stdClass
|
||||
*/
|
||||
public function create_event($data = []) {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$record = new \stdClass();
|
||||
$record->name = 'event name';
|
||||
$record->eventtype = 'global';
|
||||
$record->repeat = 0;
|
||||
$record->repeats = 0;
|
||||
$record->timestart = time();
|
||||
$record->timeduration = 0;
|
||||
$record->timesort = 0;
|
||||
$record->eventtype = 'user';
|
||||
$record->courseid = 0;
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$record->$key = $value;
|
||||
}
|
||||
|
||||
switch ($record->eventtype) {
|
||||
case 'user':
|
||||
unset($record->categoryid);
|
||||
unset($record->courseid);
|
||||
unset($record->groupid);
|
||||
break;
|
||||
case 'group':
|
||||
unset($record->categoryid);
|
||||
break;
|
||||
case 'course':
|
||||
unset($record->categoryid);
|
||||
unset($record->groupid);
|
||||
break;
|
||||
case 'category':
|
||||
unset($record->courseid);
|
||||
unset($record->groupid);
|
||||
break;
|
||||
case 'global':
|
||||
unset($record->categoryid);
|
||||
unset($record->courseid);
|
||||
unset($record->groupid);
|
||||
break;
|
||||
}
|
||||
|
||||
$event = new calendar_event($record);
|
||||
$event->create($record);
|
||||
|
||||
return $event->properties();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -174,6 +174,15 @@ class behat_data_generators extends behat_base {
|
|||
'datagenerator' => 'tag',
|
||||
'required' => array('name')
|
||||
),
|
||||
'events' => array(
|
||||
'datagenerator' => 'event',
|
||||
'required' => array('name', 'eventtype'),
|
||||
'switchids' => array(
|
||||
'user' => 'userid',
|
||||
'course' => 'courseid',
|
||||
'category' => 'categoryid',
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -218,7 +227,7 @@ class behat_data_generators extends behat_base {
|
|||
$methodname = 'get_' . $element . '_id';
|
||||
|
||||
// Not all the switch fields are required, default vars will be assigned by data generators.
|
||||
if (isset($elementdata[$element])) {
|
||||
if (!empty($elementdata[$element])) {
|
||||
// Temp $id var to avoid problems when $element == $field.
|
||||
$id = $this->{$methodname}($elementdata[$element]);
|
||||
unset($elementdata[$element]);
|
||||
|
|
|
@ -750,6 +750,22 @@ class core_coursecatlib_testcase extends advanced_testcase {
|
|||
$this->assertEquals(1, count($courses[$c5->id]->get_course_overviewfiles()));
|
||||
}
|
||||
|
||||
public function test_get_nested_name() {
|
||||
$cat1name = 'Cat1';
|
||||
$cat2name = 'Cat2';
|
||||
$cat3name = 'Cat3';
|
||||
$cat4name = 'Cat4';
|
||||
$category1 = coursecat::create(array('name' => $cat1name));
|
||||
$category2 = coursecat::create(array('name' => $cat2name, 'parent' => $category1->id));
|
||||
$category3 = coursecat::create(array('name' => $cat3name, 'parent' => $category2->id));
|
||||
$category4 = coursecat::create(array('name' => $cat4name, 'parent' => $category2->id));
|
||||
|
||||
$this->assertEquals($cat1name, $category1->get_nested_name(false));
|
||||
$this->assertEquals("{$cat1name} / {$cat2name}", $category2->get_nested_name(false));
|
||||
$this->assertEquals("{$cat1name} / {$cat2name} / {$cat3name}", $category3->get_nested_name(false));
|
||||
$this->assertEquals("{$cat1name} / {$cat2name} / {$cat4name}", $category4->get_nested_name(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a draft area for current user and fills it with fake files
|
||||
*
|
||||
|
|
3
pix/i/categoryevent.svg
Normal file
3
pix/i/categoryevent.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
|
||||
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M8 4c1.6 0 2.9 1.7 2.9 3.8 0 1.4-.9 2.5-.9 2.5-.3.4-.2 1 .3 1.2l3.6 1.7c.5.3 1.1.9 1.1 1.5V16H1v-1.2c0-.5.6-1.2 1.1-1.5l3.6-1.8c.5-.3.6-.8.3-1.3 0 0-.9-1.1-.9-2.5C5.1 5.7 6.4 4 8 4zm3.9 3.8c0 1.1-.4 2.1-.4 2.1-.2.5 0 1.1.5 1.4l1.5.7H16V8.4l-1.7-.8c-.5-.3-.6-.8-.3-1.2 0 0 .9-1.1.9-2.5C14.9 1.7 13.6 0 12 0c-1.5 0-2.6 1.4-2.8 3.2 1.5.7 2.7 2.4 2.7 4.6zM1.7 7.6L0 8.4V12h2.6l1.4-.7c.5-.3.8-.9.5-1.4 0 0-.4-1-.4-2.1 0-2.1 1.2-3.9 2.8-4.6C6.6 1.4 5.5 0 4 0 2.4 0 1.1 1.7 1.1 3.8c0 1.4.9 2.5.9 2.5.3.4.2 1-.3 1.3z" fill="#999"/></svg>
|
After Width: | Height: | Size: 833 B |
|
@ -1,12 +1,16 @@
|
|||
/* calendar.less */
|
||||
|
||||
// Calendar colour variables defined.
|
||||
$calendarEventCategoryColor: #d8bfd8 !default; // Pale purple.
|
||||
$calendarEventCourseColor: #ffd3bd !default; // Pale red.
|
||||
$calendarEventGlobalColor: #d6f8cd !default; // Pale green.
|
||||
$calendarEventGroupColor: #fee7ae !default; // Pale yellow.
|
||||
$calendarEventUserColor: #dce7ec !default; // Pale blue.
|
||||
|
||||
// Calendar event background colours defined.
|
||||
.calendar_event_category {
|
||||
background-color: $calendarEventCategoryColor;
|
||||
}
|
||||
.calendar_event_course {
|
||||
background-color: $calendarEventCourseColor;
|
||||
}
|
||||
|
@ -129,6 +133,7 @@ $calendarEventUserColor: #dce7ec !default; // Pale blue.
|
|||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.calendar_event_category,
|
||||
.calendar_event_course,
|
||||
.calendar_event_site,
|
||||
.calendar_event_group,
|
||||
|
@ -141,6 +146,9 @@ $calendarEventUserColor: #dce7ec !default; // Pale blue.
|
|||
}
|
||||
}
|
||||
|
||||
.calendar_event_category {
|
||||
border-color: $calendarEventCategoryColor;
|
||||
}
|
||||
.calendar_event_course {
|
||||
border-color: $calendarEventCourseColor;
|
||||
}
|
||||
|
@ -231,6 +239,15 @@ $calendarEventUserColor: #dce7ec !default; // Pale blue.
|
|||
}
|
||||
}
|
||||
|
||||
&.duration_category {
|
||||
border-top: 1px solid $calendarEventCategoryColor;
|
||||
border-bottom: 1px solid $calendarEventCategoryColor;
|
||||
|
||||
&.duration_finish {
|
||||
background-color: $calendarEventCategoryColor;
|
||||
}
|
||||
}
|
||||
|
||||
&.duration_course {
|
||||
border-top: 1px solid $calendarEventCourseColor;
|
||||
border-bottom: 1px solid $calendarEventCourseColor;
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
/* calendar.less */
|
||||
|
||||
// Calendar colour variables defined.
|
||||
@calendarEventCategoryColor: #d8bfd8; // Pale purple.
|
||||
@calendarEventCourseColor: #ffd3bd; // Pale red.
|
||||
@calendarEventGlobalColor: #d6f8cd; // Pale green.
|
||||
@calendarEventGroupColor: #fee7ae; // Pale yellow.
|
||||
@calendarEventUserColor: #dce7ec; // Pale blue.
|
||||
|
||||
// Calendar event background colours defined.
|
||||
.calendar_event_category {
|
||||
background-color: @calendarEventCategoryColor;
|
||||
}
|
||||
.calendar_event_course {
|
||||
background-color: @calendarEventCourseColor;
|
||||
}
|
||||
|
@ -110,6 +114,7 @@
|
|||
width: 98%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
.calendar_event_category,
|
||||
.calendar_event_course,
|
||||
.calendar_event_site,
|
||||
.calendar_event_group,
|
||||
|
@ -121,6 +126,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.calendar_event_category {
|
||||
border-color: @calendarEventCategoryColor;
|
||||
}
|
||||
.calendar_event_course {
|
||||
border-color: @calendarEventCourseColor;
|
||||
}
|
||||
|
@ -244,6 +252,13 @@
|
|||
background-color: @calendarEventGlobalColor;
|
||||
}
|
||||
}
|
||||
&.duration_category {
|
||||
border-top: 1px solid @calendarEventCategoryColor;
|
||||
border-bottom: 1px solid @calendarEventCategoryColor;
|
||||
&.duration_finish {
|
||||
background-color: @calendarEventCategoryColor;
|
||||
}
|
||||
}
|
||||
&.duration_course {
|
||||
border-top: 1px solid @calendarEventCourseColor;
|
||||
border-bottom: 1px solid @calendarEventCourseColor;
|
||||
|
|
|
@ -5563,6 +5563,9 @@ img.iconsmall {
|
|||
background-color: #fcf8e3;
|
||||
}
|
||||
/* calendar.less */
|
||||
.calendar_event_category {
|
||||
background-color: #d8bfd8;
|
||||
}
|
||||
.calendar_event_course {
|
||||
background-color: #ffd3bd;
|
||||
}
|
||||
|
@ -5655,6 +5658,7 @@ img.iconsmall {
|
|||
width: 98%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
.path-calendar .maincalendar .calendar_event_category:hover a,
|
||||
.path-calendar .maincalendar .calendar_event_course:hover a,
|
||||
.path-calendar .maincalendar .calendar_event_site:hover a,
|
||||
.path-calendar .maincalendar .calendar_event_group:hover a,
|
||||
|
@ -5662,6 +5666,9 @@ img.iconsmall {
|
|||
color: #003d5c;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.path-calendar .maincalendar .calendar_event_category {
|
||||
border-color: #d8bfd8;
|
||||
}
|
||||
.path-calendar .maincalendar .calendar_event_course {
|
||||
border-color: #ffd3bd;
|
||||
}
|
||||
|
@ -5768,6 +5775,13 @@ img.iconsmall {
|
|||
.block .minicalendar td.duration_global.duration_finish {
|
||||
background-color: #d6f8cd;
|
||||
}
|
||||
.block .minicalendar td.duration_category {
|
||||
border-top: 1px solid #d8bfd8;
|
||||
border-bottom: 1px solid #d8bfd8;
|
||||
}
|
||||
.block .minicalendar td.duration_category.duration_finish {
|
||||
background-color: #d8bfd8;
|
||||
}
|
||||
.block .minicalendar td.duration_course {
|
||||
border-top: 1px solid #ffd3bd;
|
||||
border-bottom: 1px solid #ffd3bd;
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2017092800.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2017092900.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue