Merge branch 'MDL-72295-master' of git://github.com/HuongNV13/moodle

This commit is contained in:
Ilya Tregubov
2021-10-11 11:51:59 +07:00
24 changed files with 514 additions and 44 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
define ("block_timeline/event_list",["jquery","core/notification","core/templates","core/paged_content_factory","core/str","core/user_date","block_timeline/calendar_events_repository"],function(a,b,c,d,e,f,g){var h=!1,i={EMPTY_MESSAGE:"[data-region=\"empty-message\"]",ROOT:"[data-region=\"event-list-container\"]",EVENT_LIST_CONTENT:"[data-region=\"event-list-content\"]",EVENT_LIST_LOADING_PLACEHOLDER:"[data-region=\"event-list-loading-placeholder\"]"},j={EVENT_LIST_CONTENT:"block_timeline/event-list-content"},k={ignoreControlWhileLoading:!0,controlPlacementBottom:!0,ariaLabels:{itemsperpagecomponents:"ariaeventlistpagelimit, block_timeline"}},l=function(a){a.find(i.EVENT_LIST_CONTENT).addClass("hidden");a.find(i.EMPTY_MESSAGE).removeClass("hidden")},m=function(a){a.find(i.EVENT_LIST_CONTENT).removeClass("hidden");a.find(i.EMPTY_MESSAGE).addClass("hidden")},n=function(a){a.find(i.EVENT_LIST_CONTENT).empty()},o=function(a,b){var c={},d={courseview:h,eventsbyday:[]};a.forEach(function(a){var b=a.timeusermidnight;if(c[b]){c[b].push(a)}else{c[b]=[a]}});Object.keys(c).forEach(function(a){var e=c[a];d.eventsbyday.push({past:a<b,dayTimestamp:a,events:e})});return d},p=function(a,b){var d=o(a,b),e=j.EVENT_LIST_CONTENT;return c.render(e,d)},q=function(a,b,c,d,e,f){var h=d!=void 0?a+d*86400:!1,i={starttime:a+c*86400,limit:b};if(e){i.aftereventid=e}if(h){i.endtime=h}if(f){i.courseid=f;return g.queryByCourse(i)}else{return g.queryByTime(i)}},r=function(a,b,c,d,e,g,h,i){var j=a.pageNumber,k=a.limit,l=j;while(!d.hasOwnProperty(l)){l--}var m=d[l],n=null;if(e&&e.hasOwnProperty(j)){n=e[j]}else{n=q(c,k+1,h,i,m,g)}return n.then(function(a){if(!a.events.length){b.allItemsLoaded(j);return[]}var d=a.events.filter(function(a){if("open"==a.eventtype||"opensubmission"==a.eventtype){var b=f.getUserMidnightForTimestamp(a.timesort,c);return b>c}return!0}),e=d.length<=k;if(e){b.allItemsLoaded(j)}else{d.pop()}return d})},s=function(c,f,g,h,i,j,l,m,n){var o={1:0},q=!1,s=a.extend({},k,n);return e.get_string("ariaeventlistpagelimit","block_timeline",a.isArray(c)?c[0].value:c).then(function(a){s.ariaLabels.itemsperpage=a;s.ariaLabels.paginationnav=m;return a}).then(function(){return d.createWithLimit(c,function(c,d){var e=[];c.forEach(function(a){var c=a.pageNumber,h=r(a,d,g,o,f,i,j,l).then(function(a){if(a.length){q=!0;var b=a[a.length-1].id;o[c+1]=b;return p(a,g)}else{return a}}).catch(b.exception);e.push(h)});a.when.apply(a,e).then(function(){h.resolve(q)}).catch(function(){h.resolve(q)});return e},s)})};return{init:function init(d,e,f,g){var j=4<arguments.length&&arguments[4]!==void 0?arguments[4]:{};d=a(d);h=!!j.courseview;var k=a.Deferred(),o=d.find(i.EVENT_LIST_CONTENT),p=d.find(i.EVENT_LIST_LOADING_PLACEHOLDER),q=d.attr("data-course-id"),r=parseInt(d.attr("data-days-offset"),10),t=d.attr("data-days-limit"),u=parseInt(d.attr("data-midnight"),10);n(d);m(d);p.removeClass("hidden");if(t!=void 0){t=parseInt(t,10)}return s(e,f,u,k,q,r,t,g,j).then(function(b,e){b=a(b);b.addClass("hidden");c.replaceNodeContents(o,b,e);k.then(function(a){b.removeClass("hidden");p.addClass("hidden");if(!a){l(d)}return a}).catch(function(){return!1});return b}).catch(b.exception)},rootSelector:i.ROOT}});
define ("block_timeline/event_list",["jquery","core/notification","core/templates","core/paged_content_factory","core/str","core/user_date","block_timeline/calendar_events_repository"],function(a,b,c,d,e,f,g){var h=!1,i={EMPTY_MESSAGE:"[data-region=\"empty-message\"]",ROOT:"[data-region=\"event-list-container\"]",EVENT_LIST_CONTENT:"[data-region=\"event-list-content\"]",EVENT_LIST_LOADING_PLACEHOLDER:"[data-region=\"event-list-loading-placeholder\"]",TIMELINE_BLOCK:"[data-region=\"timeline\"]",TIMELINE_SEARCH:"[data-region=\"search-input\"]"},j={EVENT_LIST_CONTENT:"block_timeline/event-list-content"},k={ignoreControlWhileLoading:!0,controlPlacementBottom:!0,ariaLabels:{itemsperpagecomponents:"ariaeventlistpagelimit, block_timeline"}},l=function(a){a.find(i.EVENT_LIST_CONTENT).addClass("hidden");a.find(i.EMPTY_MESSAGE).removeClass("hidden")},m=function(a){a.find(i.EVENT_LIST_CONTENT).removeClass("hidden");a.find(i.EMPTY_MESSAGE).addClass("hidden")},n=function(a){a.find(i.EVENT_LIST_CONTENT).empty()},o=function(a,b){var c={},d={courseview:h,eventsbyday:[]};a.forEach(function(a){var b=a.timeusermidnight;if(c[b]){c[b].push(a)}else{c[b]=[a]}});Object.keys(c).forEach(function(a){var e=c[a];d.eventsbyday.push({past:a<b,dayTimestamp:a,events:e})});return d},p=function(a,b){var d=o(a,b),e=j.EVENT_LIST_CONTENT;return c.render(e,d)},q=function(a,b,c,d,e,f,h){var i=d!=void 0?a+d*86400:!1,j={starttime:a+c*86400,limit:b};if(e){j.aftereventid=e}if(i){j.endtime=i}if(h){j.searchvalue=h}if(f){j.courseid=f;return g.queryByCourse(j)}else{return g.queryByTime(j)}},r=function(a,b,c,d,e,g,h,i,j){var k=a.pageNumber,l=a.limit,m=k;while(!d.hasOwnProperty(m)){m--}var n=d[m],o=null;if(e&&e.hasOwnProperty(k)){o=e[k]}else{o=q(c,l+1,h,i,n,g,j)}return o.then(function(a){if(!a.events.length){b.allItemsLoaded(k);return[]}var d=a.events.filter(function(a){if("open"==a.eventtype||"opensubmission"==a.eventtype){var b=f.getUserMidnightForTimestamp(a.timesort,c);return b>c}return!0}),e=d.length<=l;if(e){b.allItemsLoaded(k)}else{d.pop()}return d})},s=function(c,f,g,h,i,j,l,m,n,o){var q={1:0},s=!1,t=a.extend({},k,n);return e.get_string("ariaeventlistpagelimit","block_timeline",a.isArray(c)?c[0].value:c).then(function(a){t.ariaLabels.itemsperpage=a;t.ariaLabels.paginationnav=m;return a}).then(function(){return d.createWithLimit(c,function(c,d){var e=[];c.forEach(function(a){var c=a.pageNumber,h=r(a,d,g,q,f,i,j,l,o).then(function(a){if(a.length){s=!0;var b=a[a.length-1].id;q[c+1]=b;return p(a,g)}else{return a}}).catch(b.exception);e.push(h)});a.when.apply(a,e).then(function(){h.resolve(s)}).catch(function(){h.resolve(s)});return e},t)})};return{init:function init(d,e,f,g){var j=4<arguments.length&&arguments[4]!==void 0?arguments[4]:{};d=a(d);h=!!j.courseview;var k=a.Deferred(),o=d.find(i.EVENT_LIST_CONTENT),p=d.find(i.EVENT_LIST_LOADING_PLACEHOLDER),q=d.attr("data-course-id"),r=parseInt(d.attr("data-days-offset"),10),t=d.attr("data-days-limit"),u=parseInt(d.attr("data-midnight"),10),v=d.closest(i.TIMELINE_BLOCK).find(i.TIMELINE_SEARCH).val();n(d);m(d);p.removeClass("hidden");if(t!=void 0){t=parseInt(t,10)}return s(e,f,u,k,q,r,t,g,j,v).then(function(b,e){b=a(b);b.addClass("hidden");c.replaceNodeContents(o,b,e);k.then(function(a){b.removeClass("hidden");p.addClass("hidden");if(!a){l(d)}return a}).catch(function(){return!1});return b}).catch(b.exception)},rootSelector:i.ROOT}});
//# sourceMappingURL=event_list.min.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define ("block_timeline/view_courses",["jquery","core/notification","core/custom_interaction_events","core/str","core/templates","block_timeline/event_list","core_course/repository","block_timeline/calendar_events_repository"],function(a,b,c,d,e,f,g,h){var i={MORE_COURSES_BUTTON:"[data-action=\"more-courses\"]",MORE_COURSES_BUTTON_CONTAINER:"[data-region=\"more-courses-button-container\"]",NO_COURSES_EMPTY_MESSAGE:"[data-region=\"no-courses-empty-message\"]",COURSES_LIST:"[data-region=\"courses-list\"]",COURSE_ITEMS_LOADING_PLACEHOLDER:"[data-region=\"course-items-loading-placeholder\"]",COURSE_EVENTS_CONTAINER:"[data-region=\"course-events-container\"]",COURSE_NAME:"[data-region=\"course-name\"]",LOADING_ICON:".loading-icon"},j={COURSE_ITEMS:"block_timeline/course-items",LOADING_ICON:"core/loading"},k=5,l=86400,m=function(a){a.find(i.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass("hidden")},n=function(a){a.find(i.MORE_COURSES_BUTTON_CONTAINER).addClass("hidden")},o=function(a){a.find(i.MORE_COURSES_BUTTON_CONTAINER).removeClass("hidden")},p=function(a){var b=a.find(i.MORE_COURSES_BUTTON);b.prop("disabled",!0);e.render(j.LOADING_ICON,{}).then(function(a){b.append(a);return a}).catch(function(){return!1})},q=function(a){var b=a.find(i.MORE_COURSES_BUTTON);b.prop("disabled",!1);b.find(i.LOADING_ICON).remove()},r=function(a){a.find(i.NO_COURSES_EMPTY_MESSAGE).removeClass("hidden")},s=function(a,b){var c=a.find(i.COURSES_LIST);e.appendNodeContents(c,b,"")},t=function(a){return 0<a.find(i.COURSE_EVENTS_CONTAINER).length},u=function(a){return parseInt(a.attr("data-offset"),10)},v=function(a,b){a.attr("data-offset",b)},w=function(a){return parseInt(a.attr("data-limit"),10)},x=function(a){return parseInt(a.attr("data-days-offset"),10)},y=function(a){var b=a.attr("data-days-limit");return b!=void 0?parseInt(b,10):void 0},z=function(a){return parseInt(a.attr("data-midnight"),10)},A=function(a){var b=z(a),c=x(a);return b+c*l},B=function(a){var b=z(a),c=y(a);return c!=void 0?b+c*l:!1},C=function(a,b,c,d){var e={courseids:a,starttime:b,limit:c};if(d){e.endtime=d}return h.queryByCourses(e)},D=function(a){return a.data("last-event-load-time")},E=function(a,b){a.data("last-event-load-time",b)},F=function(a,b){return D(a)>b},G=function(a,b,c){var d=a.map(function(a){return a.id});return C(d,b,k+1,c)},H=function(a,b,c,d,f){return e.render(j.COURSE_ITEMS,{courses:a,midnight:c,hasdaysoffset:!0,hasdayslimit:f!=void 0,daysoffset:d,dayslimit:f,nodayslimit:f==void 0}).then(function(a){m(b);if(a){s(b,a)}else{if(!t(b)){r(b)}}return a}).then(function(c){if(a.length<2){n(b)}else{o(b)}return c}).catch(function(){m(b)})},I=function(c){var e=u(c),h=w(c);return g.getEnrolledCoursesByTimelineClassification("inprogress",h,e,"fullname asc").then(function(b){var e=Date.now(),g=b.courses,h=b.nextoffset,i=x(c),j=y(c),l=z(c),m=A(c),n=B(c);v(c,h);var o=G(g,m,n),p=H(g,c,l,i,j);return a.when(o,p).then(function(b){if(F(c,e)){return b}g.forEach(function(e){var g=e.id,h=[],i=c.find("[data-region=\"course-events-container\"][data-course-id=\""+g+"\"]"),j=i.find(f.rootSelector),l=b.groupedbycourse.filter(function(a){return a.courseid==g});if(l.length){h=l[0].events}var m=a.Deferred().resolve({events:h}).promise();d.get_string("ariaeventlistpaginationnavcourses","block_timeline",e.fullnamedisplay).then(function(a){f.init(j,k,{1:m},a);return a}).catch(function(){f.init(j,k,{1:m})})});return b})}).catch(b.exception)},J=function(c){var e=Date.now(),g=A(c),h=B(c),j=c.find(i.COURSE_EVENTS_CONTAINER),l=j.map(function(){return a(this).attr("data-course-id")}).get();E(c,e);return C(l,g,k+1,h).then(function(b){if(F(c,e)){return b}j.each(function(c,e){e=a(e);var g=e.attr("data-course-id"),h=e.find(i.COURSE_NAME).text(),j=e.find(f.rootSelector),l=a.Deferred(),m=[],n=b.groupedbycourse.filter(function(a){return a.courseid==g});if(n.length){m=n[0].events}l.resolve({events:m});var o={courseview:!0};d.get_string("ariaeventlistpaginationnavcourses","block_timeline",h).then(function(a){f.init(j,k,{1:l.promise()},a,o);return a}).catch(function(){f.init(j,k,{1:l.promise()},void 0,o)})});return b}).catch(b.exception)},K=function(a){c.define(a,[c.events.activate]);a.on(c.events.activate,i.MORE_COURSES_BUTTON,function(b,c){p(a);I(a).then(function(){q(a)}).catch(function(){q(a)});if(c){c.originalEvent.preventDefault();c.originalEvent.stopPropagation()}b.stopPropagation()})},L=function(a){if(!a.attr("data-seen")){if(t(a)){J(a)}else{I(a)}a.attr("data-seen",!0)}};return{init:function init(b){b=a(b);E(b,Date.now());if(b.hasClass("active")){I(b);b.attr("data-seen",!0)}K(b)},reset:function reset(a){a.removeAttr("data-seen");if(a.hasClass("active")){L(a)}},shown:L}});
define ("block_timeline/view_courses",["jquery","core/notification","core/custom_interaction_events","core/str","core/templates","block_timeline/event_list","core_course/repository","block_timeline/calendar_events_repository"],function(a,b,c,d,e,f,g,h){var i={MORE_COURSES_BUTTON:"[data-action=\"more-courses\"]",MORE_COURSES_BUTTON_CONTAINER:"[data-region=\"more-courses-button-container\"]",NO_COURSES_EMPTY_MESSAGE:"[data-region=\"no-courses-empty-message\"]",COURSES_LIST:"[data-region=\"courses-list\"]",COURSE_ITEMS_LOADING_PLACEHOLDER:"[data-region=\"course-items-loading-placeholder\"]",COURSE_EVENTS_CONTAINER:"[data-region=\"course-events-container\"]",COURSE_NAME:"[data-region=\"course-name\"]",LOADING_ICON:".loading-icon",TIMELINE_BLOCK:"[data-region=\"timeline\"]",TIMELINE_SEARCH:"[data-region=\"search-input\"]"},j={COURSE_ITEMS:"block_timeline/course-items",LOADING_ICON:"core/loading"},k=5,l=86400,m=function(a){a.find(i.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass("hidden")},n=function(a){a.find(i.MORE_COURSES_BUTTON_CONTAINER).addClass("hidden")},o=function(a){a.find(i.MORE_COURSES_BUTTON_CONTAINER).removeClass("hidden")},p=function(a){var b=a.find(i.MORE_COURSES_BUTTON);b.prop("disabled",!0);e.render(j.LOADING_ICON,{}).then(function(a){b.append(a);return a}).catch(function(){return!1})},q=function(a){var b=a.find(i.MORE_COURSES_BUTTON);b.prop("disabled",!1);b.find(i.LOADING_ICON).remove()},r=function(a){a.find(i.NO_COURSES_EMPTY_MESSAGE).removeClass("hidden")},s=function(a,b){var c=a.find(i.COURSES_LIST);e.appendNodeContents(c,b,"")},t=function(a){return 0<a.find(i.COURSE_EVENTS_CONTAINER).length},u=function(a){return parseInt(a.attr("data-offset"),10)},v=function(a,b){a.attr("data-offset",b)},w=function(a){return parseInt(a.attr("data-limit"),10)},x=function(a){return parseInt(a.attr("data-days-offset"),10)},y=function(a){var b=a.attr("data-days-limit");return b!=void 0?parseInt(b,10):void 0},z=function(a){return parseInt(a.attr("data-midnight"),10)},A=function(a){var b=z(a),c=x(a);return b+c*l},B=function(a){var b=z(a),c=y(a);return c!=void 0?b+c*l:!1},C=function(a,b,c,d,e){var f={courseids:a,starttime:b,limit:c};if(d){f.endtime=d}if(e){f.searchvalue=e}return h.queryByCourses(f)},D=function(a){return a.data("last-event-load-time")},E=function(a,b){a.data("last-event-load-time",b)},F=function(a,b){return D(a)>b},G=function(a,b,c,d){var e=a.map(function(a){return a.id});return C(e,b,k+1,c,d)},H=function(a,b,c,d,f){return e.render(j.COURSE_ITEMS,{courses:a,midnight:c,hasdaysoffset:!0,hasdayslimit:f!=void 0,daysoffset:d,dayslimit:f,nodayslimit:f==void 0}).then(function(a){m(b);if(a){s(b,a)}else{if(!t(b)){r(b)}}return a}).then(function(c){if(a.length<2){n(b)}else{o(b)}return c}).catch(function(){m(b)})},I=function(c){var e=u(c),h=w(c);return g.getEnrolledCoursesByTimelineClassification("inprogress",h,e,"fullname asc").then(function(b){var e=Date.now(),g=b.courses,h=b.nextoffset,j=x(c),l=y(c),m=z(c),n=A(c),o=B(c),p=c.closest(i.TIMELINE_BLOCK).find(i.TIMELINE_SEARCH).val();v(c,h);var q=G(g,n,o,p),r=H(g,c,m,j,l);return a.when(q,r).then(function(b){if(F(c,e)){return b}g.forEach(function(e){var g=e.id,h=[],i=c.find("[data-region=\"course-events-container\"][data-course-id=\""+g+"\"]"),j=i.find(f.rootSelector),l=b.groupedbycourse.filter(function(a){return a.courseid==g});if(l.length){h=l[0].events}var m=a.Deferred().resolve({events:h}).promise();d.get_string("ariaeventlistpaginationnavcourses","block_timeline",e.fullnamedisplay).then(function(a){f.init(j,k,{1:m},a);return a}).catch(function(){f.init(j,k,{1:m})})});return b})}).catch(b.exception)},J=function(c){var e=Date.now(),g=A(c),h=B(c),j=c.find(i.COURSE_EVENTS_CONTAINER),l=j.map(function(){return a(this).attr("data-course-id")}).get(),m=c.closest(i.TIMELINE_BLOCK).find(i.TIMELINE_SEARCH).val();E(c,e);return C(l,g,k+1,h,m).then(function(b){if(F(c,e)){return b}j.each(function(c,e){e=a(e);var g=e.attr("data-course-id"),h=e.find(i.COURSE_NAME).text(),j=e.find(f.rootSelector),l=a.Deferred(),m=[],n=b.groupedbycourse.filter(function(a){return a.courseid==g});if(n.length){m=n[0].events}l.resolve({events:m});var o={courseview:!0};d.get_string("ariaeventlistpaginationnavcourses","block_timeline",h).then(function(a){f.init(j,k,{1:l.promise()},a,o);return a}).catch(function(){f.init(j,k,{1:l.promise()},void 0,o)})});return b}).catch(b.exception)},K=function(a){c.define(a,[c.events.activate]);a.on(c.events.activate,i.MORE_COURSES_BUTTON,function(b,c){p(a);I(a).then(function(){q(a)}).catch(function(){q(a)});if(c){c.originalEvent.preventDefault();c.originalEvent.stopPropagation()}b.stopPropagation()})},L=function(a){if(!a.attr("data-seen")){if(t(a)){J(a)}else{I(a)}a.attr("data-seen",!0)}};return{init:function init(b){b=a(b);E(b,Date.now());if(b.hasClass("active")){I(b);b.attr("data-seen",!0)}K(b)},reset:function reset(a){a.removeAttr("data-seen");if(a.hasClass("active")){L(a)}},shown:L}});
//# sourceMappingURL=view_courses.min.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define ("block_timeline/view_nav",["jquery","core/custom_interaction_events","block_timeline/view","core/ajax","core/notification"],function(a,b,c,d,e){var f={TIMELINE_DAY_FILTER:"[data-region=\"day-filter\"]",TIMELINE_DAY_FILTER_OPTION:"[data-from]",TIMELINE_VIEW_SELECTOR:"[data-region=\"view-selector\"]",DATA_DAYS_OFFSET:"[data-days-offset]",DATA_DAYS_LIMIT:"[data-days-limit]"},g=function(a,b){d.call([{methodname:"core_user_update_user_preferences",args:{preferences:[{type:a,value:b}]}}])[0].fail(e.exception)},h=function(d,h){var i=d.find(f.TIMELINE_DAY_FILTER);b.define(i,[b.events.activate]);i.on(b.events.activate,f.TIMELINE_DAY_FILTER_OPTION,function(b,e){var i=a(b.currentTarget).data("filtername");g("block_timeline_user_filter_preference",i);var j=a(b.target).closest(f.TIMELINE_DAY_FILTER_OPTION);if("true"==j.attr("aria-current")){return}var k=j.attr("data-from"),l=j.attr("data-to"),m=d.find(f.DATA_DAYS_OFFSET);m.attr("data-days-offset",k);if(l!=void 0){m.attr("data-days-limit",l)}else{m.removeAttr("data-days-limit")}c.reset(h);e.originalEvent.preventDefault()})},i=function(d,h){var i=d.find(f.TIMELINE_VIEW_SELECTOR);i.on("shown shown.bs.tab",function(b){c.shown(h);a(b.target).removeClass("active")});b.define(i,[b.events.activate]);i.on(b.events.activate,"[data-toggle='tab']",function(b){var c=a(b.currentTarget).data("filtername");g("block_timeline_user_sort_preference",c)})};return{init:function init(b,c){b=a(b);h(b,c);i(b,c)}}});
define ("block_timeline/view_nav",["jquery","core/custom_interaction_events","block_timeline/view","core/ajax","core/notification","core/utils"],function(a,b,c,d,e,f){var g={TIMELINE_DAY_FILTER:"[data-region=\"day-filter\"]",TIMELINE_DAY_FILTER_OPTION:"[data-from]",TIMELINE_VIEW_SELECTOR:"[data-region=\"view-selector\"]",DATA_DAYS_OFFSET:"[data-days-offset]",DATA_DAYS_LIMIT:"[data-days-limit]",TIMELINE_SEARCH_INPUT:"[data-region=\"search-input\"]",TIMELINE_SEARCH_CLEAR_ICON:"[data-region=\"clear-icon\"]",TIMELINE_SEARCH_SEARCH_ICON:"[data-region=\"search-icon\"]"},h=function(a,b){d.call([{methodname:"core_user_update_user_preferences",args:{preferences:[{type:a,value:b}]}}])[0].fail(e.exception)},i=function(d,f){var i=d.find(g.TIMELINE_DAY_FILTER);b.define(i,[b.events.activate]);i.on(b.events.activate,g.TIMELINE_DAY_FILTER_OPTION,function(b,e){var i=a(b.currentTarget).data("filtername");h("block_timeline_user_filter_preference",i);var j=a(b.target).closest(g.TIMELINE_DAY_FILTER_OPTION);if("true"==j.attr("aria-current")){return}var k=j.attr("data-from"),l=j.attr("data-to"),m=d.find(g.DATA_DAYS_OFFSET);m.attr("data-days-offset",k);if(l!=void 0){m.attr("data-days-limit",l)}else{m.removeAttr("data-days-limit")}c.reset(f);e.originalEvent.preventDefault()})},j=function(d,f){var i=d.find(g.TIMELINE_VIEW_SELECTOR);i.on("shown shown.bs.tab",function(b){c.shown(f);a(b.target).removeClass("active")});b.define(i,[b.events.activate]);i.on(b.events.activate,"[data-toggle='tab']",function(b){var c=a(b.currentTarget).data("filtername");h("block_timeline_user_sort_preference",c)})},k=function(a,b){var c=a.find(g.TIMELINE_SEARCH_INPUT),d=a.find(g.TIMELINE_SEARCH_SEARCH_ICON),e=a.find(g.TIMELINE_SEARCH_CLEAR_ICON);c.on("input",f.debounce(function(){if(""!==c.val()){l(d,e,b)}else{m(d,e,b)}},300));e.on("click",function(){c.val("");m(d,e,b)})},l=function(a,b,d){a.addClass("d-none");b.parent().removeClass("d-none");c.reset(d)},m=function(a,b,d){a.removeClass("d-none");b.parent().addClass("d-none");c.reset(d)};return{init:function init(b,c){b=a(b);i(b,c);j(b,c);k(b,c)}}});
//# sourceMappingURL=view_nav.min.js.map
File diff suppressed because one or more lines are too long
+19 -6
View File
@@ -49,6 +49,8 @@ function(
ROOT: '[data-region="event-list-container"]',
EVENT_LIST_CONTENT: '[data-region="event-list-content"]',
EVENT_LIST_LOADING_PLACEHOLDER: '[data-region="event-list-loading-placeholder"]',
TIMELINE_BLOCK: '[data-region="timeline"]',
TIMELINE_SEARCH: '[data-region="search-input"]'
};
var TEMPLATES = {
@@ -177,9 +179,10 @@ function(
* @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to
* @param {int|false} lastId The ID of the last seen event (if any)
* @param {int|undefined} courseId Course ID to restrict events to
* @param {string|undefined} searchValue Search value
* @return {Promise} A jquery promise
*/
var load = function(midnight, limit, daysOffset, daysLimit, lastId, courseId) {
var load = function(midnight, limit, daysOffset, daysLimit, lastId, courseId, searchValue) {
var startTime = midnight + (daysOffset * SECONDS_IN_DAY);
var endTime = daysLimit != undefined ? midnight + (daysLimit * SECONDS_IN_DAY) : false;
@@ -196,6 +199,10 @@ function(
args.endtime = endTime;
}
if (searchValue) {
args.searchvalue = searchValue;
}
if (courseId) {
// If we have a course id then we only want events from that course.
args.courseid = courseId;
@@ -221,6 +228,7 @@ function(
* @param {int|undefined} courseId Course ID to restrict events to
* @param {Number} daysOffset How many days (from midnight) to offset the results from
* @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to
* @param {string|undefined} searchValue Search value
* @return {object} jQuery promise resolved with calendar events.
*/
var loadEventsFromPageData = function(
@@ -231,7 +239,8 @@ function(
preloadedPages,
courseId,
daysOffset,
daysLimit
daysLimit,
searchValue
) {
var pageNumber = pageData.pageNumber;
var limit = pageData.limit;
@@ -255,7 +264,7 @@ function(
} else {
// Load one more than the given limit so that we can tell if there
// is more content to load after this.
eventsPromise = load(midnight, limit + 1, daysOffset, daysLimit, lastId, courseId);
eventsPromise = load(midnight, limit + 1, daysOffset, daysLimit, lastId, courseId, searchValue);
}
return eventsPromise.then(function(result) {
@@ -308,6 +317,7 @@ function(
* @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to
* @param {string} paginationAriaLabel String to set as the aria label for the pagination bar.
* @param {object} additionalConfig Additional config options to pass to pagedContentFactory
* @param {string|undefined} searchValue Search value
* @return {object} jQuery promise.
*/
var createPagedContent = function(
@@ -319,7 +329,8 @@ function(
daysOffset,
daysLimit,
paginationAriaLabel,
additionalConfig
additionalConfig,
searchValue
) {
// Remember the last event id we loaded on each page because we can't
// use the offset value since the backend can skip events if the user doesn't
@@ -356,7 +367,8 @@ function(
preloadedPages,
courseId,
daysOffset,
daysLimit
daysLimit,
searchValue
).then(function(calendarEvents) {
if (calendarEvents.length) {
// Remember that we've loaded content.
@@ -423,6 +435,7 @@ function(
var daysOffset = parseInt(root.attr('data-days-offset'), 10);
var daysLimit = root.attr('data-days-limit');
var midnight = parseInt(root.attr('data-midnight'), 10);
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
// Make sure the content area and loading placeholder is visible.
// This is because the init function can be called to re-initialise
@@ -438,7 +451,7 @@ function(
// Created the paged content element.
return createPagedContent(pageLimit, preloadedPages, midnight, firstLoad, courseId, daysOffset, daysLimit,
paginationAriaLabel, additionalConfig)
paginationAriaLabel, additionalConfig, searchValue)
.then(function(html, js) {
html = $(html);
// Hide the content for now.
+16 -6
View File
@@ -50,7 +50,9 @@ function(
COURSE_ITEMS_LOADING_PLACEHOLDER: '[data-region="course-items-loading-placeholder"]',
COURSE_EVENTS_CONTAINER: '[data-region="course-events-container"]',
COURSE_NAME: '[data-region="course-name"]',
LOADING_ICON: '.loading-icon'
LOADING_ICON: '.loading-icon',
TIMELINE_BLOCK: '[data-region="timeline"]',
TIMELINE_SEARCH: '[data-region="search-input"]'
};
var TEMPLATES = {
@@ -250,9 +252,10 @@ function(
* @param {Number} startTime Timestamp to fetch events from.
* @param {Number} limit Limit to the number of events (this applies per course, not total)
* @param {Number} endTime Timestamp to fetch events to.
* @param {string|undefined} searchValue Search value
* @return {object} jQuery promise.
*/
var getEventsForCourseIds = function(courseIds, startTime, limit, endTime) {
var getEventsForCourseIds = function(courseIds, startTime, limit, endTime, searchValue) {
var args = {
courseids: courseIds,
starttime: startTime,
@@ -263,6 +266,10 @@ function(
args.endtime = endTime;
}
if (searchValue) {
args.searchvalue = searchValue;
}
return EventsRepository.queryByCourses(args);
};
@@ -304,14 +311,15 @@ function(
* @param {array} courses List of course objects.
* @param {Number} startTime Timestamp to load events after.
* @param {int|undefined} endTime Timestamp to load events up until.
* @param {string|undefined} searchValue Search value
* @return {object} jQuery promise resolved with the events.
*/
var loadEventsForCourses = function(courses, startTime, endTime) {
var loadEventsForCourses = function(courses, startTime, endTime, searchValue) {
var courseIds = courses.map(function(course) {
return course.id;
});
return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime);
return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime, searchValue);
};
/**
@@ -394,11 +402,12 @@ function(
var midnight = getMidnight(root);
var startTime = getStartTime(root);
var endTime = getEndTime(root);
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
// Record the next offset if we want to request more courses.
setOffset(root, nextOffset);
// Load the events for these courses.
var eventsPromise = loadEventsForCourses(courses, startTime, endTime);
var eventsPromise = loadEventsForCourses(courses, startTime, endTime, searchValue);
// Render the courses in the DOM.
var renderPromise = updateDisplayFromCourses(courses, root, midnight, daysOffset, daysLimit);
@@ -461,12 +470,13 @@ function(
var courseIds = courseEventsContainers.map(function() {
return $(this).attr('data-course-id');
}).get();
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
// Record when we started our request.
setEventReloadTime(root, startReloadTime);
// Load all of the events for the given courses.
return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime)
return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime, searchValue)
.then(function(eventsByCourse) {
if (hasReloadedEventsSince(root, startReloadTime)) {
// A new reload has begun so ignore our results.
+58 -2
View File
@@ -26,14 +26,16 @@ define(
'core/custom_interaction_events',
'block_timeline/view',
'core/ajax',
'core/notification'
'core/notification',
'core/utils'
],
function(
$,
CustomEvents,
View,
Ajax,
Notification
Notification,
Utils
) {
var SELECTORS = {
@@ -42,6 +44,9 @@ function(
TIMELINE_VIEW_SELECTOR: '[data-region="view-selector"]',
DATA_DAYS_OFFSET: '[data-days-offset]',
DATA_DAYS_LIMIT: '[data-days-limit]',
TIMELINE_SEARCH_INPUT: '[data-region="search-input"]',
TIMELINE_SEARCH_CLEAR_ICON: '[data-region="clear-icon"]',
TIMELINE_SEARCH_SEARCH_ICON: '[data-region="search-icon"]'
};
/**
@@ -144,6 +149,56 @@ function(
});
};
/**
* Event listener for the "search" input field in the timeline navigation that allows for
* searching the activity name, course name and activity type.
*
* @param {object} root The root element for the timeline block
* @param {object} timelineViewRoot The root element for the timeline view
*/
const registerSearch = (root, timelineViewRoot) => {
const searchInput = root.find(SELECTORS.TIMELINE_SEARCH_INPUT);
const searchIcon = root.find(SELECTORS.TIMELINE_SEARCH_SEARCH_ICON);
const clearSearchIcon = root.find(SELECTORS.TIMELINE_SEARCH_CLEAR_ICON);
searchInput.on('input', Utils.debounce(() => {
if (searchInput.val() !== '') {
activeSearchState(searchIcon, clearSearchIcon, timelineViewRoot);
} else {
clearSearchState(searchIcon, clearSearchIcon, timelineViewRoot);
}
}, 300));
clearSearchIcon.on('click', () => {
searchInput.val('');
clearSearchState(searchIcon, clearSearchIcon, timelineViewRoot);
});
};
/**
* Change the search icon to clear icon.
*
* @param {object} searchIcon Search icon element.
* @param {object} clearSearchIcon Clear search icon element.
* @param {object} timelineViewRoot The root element for the timeline view
*/
const activeSearchState = (searchIcon, clearSearchIcon, timelineViewRoot) => {
searchIcon.addClass('d-none');
clearSearchIcon.parent().removeClass('d-none');
View.reset(timelineViewRoot);
};
/**
* Change the clear search icon to search icon.
*
* @param {object} searchIcon Search icon element.
* @param {object} clearSearchIcon Clear search icon element.
* @param {object} timelineViewRoot The root element for the timeline view
*/
const clearSearchState = (searchIcon, clearSearchIcon, timelineViewRoot) => {
searchIcon.removeClass('d-none');
clearSearchIcon.parent().addClass('d-none');
View.reset(timelineViewRoot);
};
/**
* Initialise the timeline view navigation by adding event listeners to
* the navigation elements.
@@ -155,6 +210,7 @@ function(
root = $(root);
registerTimelineDaySelector(root, timelineViewRoot);
registerViewSelector(root, timelineViewRoot);
registerSearch(root, timelineViewRoot);
};
return {
@@ -30,6 +30,7 @@ $string['ariaeventlistpaginationnavdates'] = 'Timeline activities pagination';
$string['ariaeventlistpaginationnavcourses'] = 'Timeline activities for course {$a} pagination';
$string['ariaviewselector'] = 'Sort timeline items';
$string['ariaviewselectoroption'] = '{$a} sort option';
$string['clearsearch'] = 'Clear search';
$string['duedate'] = 'Due date';
$string['morecourses'] = 'More courses';
$string['timeline:myaddinstance'] = 'Add a new timeline block to Dashboard';
@@ -42,6 +43,7 @@ $string['next6months'] = 'Next 6 months';
$string['overdue'] = 'Overdue';
$string['all'] = 'All';
$string['pluginname'] = 'Timeline';
$string['searchevents'] = 'Search by activity type or name';
$string['sortbycourses'] = 'Sort by courses';
$string['sortbydates'] = 'Sort by dates';
$string['timeline'] = 'Timeline';
+5 -2
View File
@@ -26,12 +26,15 @@
<div id="block-timeline-{{uniqid}}" class="block-timeline" data-region="timeline">
<div class="container p-0 pb-3 border-bottom">
<div class="row no-gutters">
<div class="mr-2">
<div class="mr-2 mb-1">
{{> block_timeline/nav-day-filter }}
</div>
<div>
<div class="mr-auto mb-1">
{{> block_timeline/nav-view-selector }}
</div>
<div class="col-md-6 col-sm-8 col-12 mb-1 d-flex justify-content-end">
{{> block_timeline/nav-search }}
</div>
</div>
</div>
<div class="container p-0">
@@ -0,0 +1,47 @@
{{!
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/>.
}}
{{!
@template block_timeline/nav-search
This template renders the main content area for the timeline block.
Example context (json):
{}
}}
<div class="searchbar input-group mb-3" role='search'>
<label for='timelinesearchinput'>
<span class='sr-only'>{{#str}} searchevents, block_timeline {{/str}}</span>
</label>
<input type="text"
data-region="search-input"
id="timelinesearchinput"
class="form-control"
placeholder="{{#str}} searchevents, block_timeline {{/str}}"
name="search"
autocomplete="off">
<div class="input-group-append">
<div data-region="search-icon" class="input-group-text">
{{#pix}} a/search, core {{/pix}}
</div>
<div class="clear input-group d-none">
<button class="btn btn-outline-secondary" type="button" data-region="clear-icon">
<span class="d-flex" aria-hidden="true">{{#pix}} e/cancel, core {{/pix}}</span>
<span class="sr-only">{{#str}} clearsearch, block_timeline {{/str}}</span>
</button>
</div>
</div>
</div>
@@ -0,0 +1,95 @@
@block @block_timeline @javascript
Feature: The timeline block allows users to search for upcoming activities
As a student
I can search for the upcoming activities in the timeline block
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| student1 | Student | 1 | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category | startdate | enddate |
| Course 1 | C1 | 0 | ##1 month ago## | ##15 days ago## |
| Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## |
| Course 3 | C3 | 0 | ##first day of next month## | ##last day of next month## |
And the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | timeclose |
| choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## |
| choice | C1 | choice2 | Test choice 2 | Test choice description | ##first day of +5 months## | ##last day of +5 months## |
| choice | C3 | choice3 | Test choice 3 | Test choice description | ##first day of +5 months## | ##last day of +10 months## |
| choice | C2 | choice4 | Test choice 4 | Test choice description | ##first day of +5 months## | ##last day of +15 months## |
| choice | C1 | choice5 | Test choice 5 | Test choice description | ##first day of +5 months## | ##last day of +20 months## |
| choice | C3 | choice6 | Test choice 6 | Test choice description | ##first day of +5 months## | ##last day of +25 months## |
| feedback | C2 | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow## |
| feedback | C1 | feedback2 | Test feedback 2 | Test feedback description | ##first day of +5 months## | ##last day of +5 months## |
| feedback | C3 | feedback3 | Test feedback 3 | Test feedback description | ##first day of +5 months## | ##last day of +10 months## |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
Scenario: The search should return no events if I enter the wrong value
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search by activity type or name" to "Fake example"
Then I should see "No activities require action" in the "Timeline" "block"
Scenario: Search for Course name
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search by activity type or name" to "Course 1"
Then I should see "Test choice 2" in the "Timeline" "block"
And I should see "Test choice 5" in the "Timeline" "block"
And I should see "Test feedback 2" in the "Timeline" "block"
And I should not see "Test choice 1" in the "Timeline" "block"
And I should not see "Test choice 3" in the "Timeline" "block"
And I should not see "Test choice 4" in the "Timeline" "block"
And I should not see "Test feedback 1" in the "Timeline" "block"
And I should not see "Test feedback 3" in the "Timeline" "block"
Scenario: Search for Activity name
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search by activity type or name" to "Test choice 1"
And I wait until "Test choice 2" "text" does not exist
Then I should see "Test choice 1" in the "Timeline" "block"
And I should not see "Test choice 2" in the "Timeline" "block"
And I should not see "Test choice 3" in the "Timeline" "block"
And I should not see "Test choice 4" in the "Timeline" "block"
And I should not see "Test choice 5" in the "Timeline" "block"
And I should not see "Test choice 6" in the "Timeline" "block"
And I should not see "Test feedback 1" in the "Timeline" "block"
And I should not see "Test feedback 2" in the "Timeline" "block"
And I should not see "Test feedback 3" in the "Timeline" "block"
Scenario: Search for Activity type
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search by activity type or name" to "feedback"
Then I should see "Test feedback 1" in the "Timeline" "block"
And I should see "Test feedback 2" in the "Timeline" "block"
And I should see "Test feedback 3" in the "Timeline" "block"
And I should not see "Test choice 1" in the "Timeline" "block"
And I should not see "Test choice 2" in the "Timeline" "block"
And I should not see "Test choice 3" in the "Timeline" "block"
And I should not see "Test choice 4" in the "Timeline" "block"
And I should not see "Test choice 5" in the "Timeline" "block"
Scenario: Timeline paginated search
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search by activity type or name" to "choice"
Then I should see "Test choice 1" in the "Timeline" "block"
And I should see "Test choice 2" in the "Timeline" "block"
And I should see "Test choice 3" in the "Timeline" "block"
And I should see "Test choice 4" in the "Timeline" "block"
And I should see "Test choice 5" in the "Timeline" "block"
And I should not see "Test choice 6" in the "Timeline" "block"
And I click on "[data-control='next']" "css_element" in the "Timeline" "block"
And I should see "Test choice 6" in the "Timeline" "block"
+13 -6
View File
@@ -120,6 +120,7 @@ class api {
* @param int $limitnum Limit results to this amount (between 1 and 50)
* @param bool $lmittononsuspendedevents Limit course events to courses the user is active in (not suspended).
* @param \stdClass|null $user The user id or false for $USER
* @param string|null $searchvalue The value a user wishes to search against
* @return array A list of action_event_interface objects
* @throws \moodle_exception
*/
@@ -129,7 +130,8 @@ class api {
$aftereventid = null,
$limitnum = 20,
$limittononsuspendedevents = false,
?\stdClass $user = null
?\stdClass $user = null,
?string $searchvalue = null
) {
global $USER;
@@ -154,7 +156,7 @@ class api {
}
return $vault->get_action_events_by_timesort($user, $timesortfrom, $timesortto, $afterevent, $limitnum,
$limittononsuspendedevents);
$limittononsuspendedevents, $searchvalue);
}
/**
@@ -166,6 +168,7 @@ class api {
* @param int|null $timesortto The end timesort value (inclusive)
* @param int|null $aftereventid Only return events after this one
* @param int $limitnum Limit results to this amount (between 1 and 50)
* @param string|null $searchvalue The value a user wishes to search against
* @return array A list of action_event_interface objects
* @throws limit_invalid_parameter_exception
*/
@@ -174,7 +177,8 @@ class api {
$timesortfrom = null,
$timesortto = null,
$aftereventid = null,
$limitnum = 20
$limitnum = 20,
?string $searchvalue = null
) {
global $USER;
@@ -191,7 +195,7 @@ class api {
}
return $vault->get_action_events_by_course(
$USER, $course, $timesortfrom, $timesortto, $afterevent, $limitnum);
$USER, $course, $timesortfrom, $timesortto, $afterevent, $limitnum, $searchvalue);
}
/**
@@ -206,13 +210,15 @@ class api {
* @param int|null $timesortfrom The start timesort value (inclusive)
* @param int|null $timesortto The end timesort value (inclusive)
* @param int $limitnum Limit results per course to this amount (between 1 and 50)
* @param string|null $searchvalue The value a user wishes to search against
* @return array A list of action_event_interface objects indexed by course id
*/
public static function get_action_events_by_courses(
$courses = [],
$timesortfrom = null,
$timesortto = null,
$limitnum = 20
$limitnum = 20,
?string $searchvalue = null
) {
$return = [];
@@ -222,7 +228,8 @@ class api {
$timesortfrom,
$timesortto,
null,
$limitnum
$limitnum,
$searchvalue
);
}
@@ -98,7 +98,8 @@ class event_vault implements event_vault_interface {
array $categoriesfilter = null,
$withduration = true,
$ignorehidden = true,
callable $filter = null
callable $filter = null,
?string $searchvalue = null
) {
$fromquery = function($field, $timefrom, $lastseenmethod, $afterevent, $withduration) {
@@ -137,12 +138,15 @@ class event_vault implements event_vault_interface {
return [];
}
$searchquery = $this->generate_search_subquery($searchvalue);
$params = array_merge(
$type ? ['type' => $type] : [],
$timesortfromquery ? $timesortfromquery['params'] : [],
$timesorttoquery ? $timesorttoquery['params'] : [],
$timestartfromquery ? $timestartfromquery['params'] : [],
$timestarttoquery ? $timestarttoquery['params'] : []
$timestarttoquery ? $timestarttoquery['params'] : [],
$searchquery ? $searchquery['params'] : [],
);
$where = array_merge(
@@ -150,7 +154,8 @@ class event_vault implements event_vault_interface {
$timesortfromquery ? $timesortfromquery['where'] : [],
$timesorttoquery ? $timesorttoquery['where'] : [],
$timestartfromquery ? $timestartfromquery['where'] : [],
$timestarttoquery ? $timestarttoquery['where'] : []
$timestarttoquery ? $timestarttoquery['where'] : [],
$searchquery ? [$searchquery['where']] : [],
);
$offset = 0;
@@ -199,7 +204,8 @@ class event_vault implements event_vault_interface {
$timesortto = null,
event_interface $afterevent = null,
$limitnum = 20,
$limittononsuspendedevents = false
$limittononsuspendedevents = false,
?string $searchvalue = null
) {
$courseids = array_map(function($course) {
return $course->id;
@@ -231,7 +237,8 @@ class event_vault implements event_vault_interface {
true,
function ($event) {
return $event instanceof action_event_interface;
}
},
$searchvalue
);
}
@@ -241,7 +248,8 @@ class event_vault implements event_vault_interface {
$timesortfrom = null,
$timesortto = null,
event_interface $afterevent = null,
$limitnum = 20
$limitnum = 20,
?string $searchvalue = null
) {
$groupings = groups_get_user_groups($course->id, $user->id);
return array_values(
@@ -262,7 +270,8 @@ class event_vault implements event_vault_interface {
true,
function ($event) use ($course) {
return $event instanceof action_event_interface && $event->get_course()->get('id') == $course->id;
}
},
$searchvalue
)
);
}
@@ -393,4 +402,33 @@ class event_vault implements event_vault_interface {
)
);
}
/**
* Generates SQL subquery and parameters for event searching.
*
* @param string|null $searchvalue Search value.
* @return array|null
*/
protected function generate_search_subquery(?string $searchvalue): ?array {
global $DB;
if (!$searchvalue) {
return null;
}
// Course searching.
$whereconditions[] = $DB->sql_like('c.fullname', ':cfullname', false);
$params['cfullname'] = '%' . $DB->sql_like_escape($searchvalue) . '%';
// Activity name searching.
$whereconditions[] = $DB->sql_like('e.name', ':eventname', false);
$params['eventname'] = '%' . $DB->sql_like_escape($searchvalue) . '%';
// Activity type searching.
$whereconditions[] = $DB->sql_like('e.modulename', ':modulename', false);
$params['modulename'] = '%' . $DB->sql_like_escape($searchvalue) . '%';
$whereclause = '(' . implode(' OR ', $whereconditions) . ')';
return ['where' => $whereclause, 'params' => $params];
}
}
@@ -62,6 +62,7 @@ interface event_vault_interface {
* @param bool $ignorehidden If true don't return hidden events.
* @param callable|null $filter Additional logic to filter out unwanted events.
* Must return true to keep the event, false to discard it.
* @param string|null $searchvalue The value a user wishes to search against
* @return event_interface[] Array of event_interfaces.
*/
public function get_events(
@@ -79,7 +80,8 @@ interface event_vault_interface {
array $categoriesfilter = null,
$withduration = true,
$ignorehidden = true,
callable $filter = null
callable $filter = null,
?string $searchvalue = null
);
/**
@@ -94,6 +96,7 @@ interface event_vault_interface {
* @param event_interface $afterevent Only return events after this one
* @param int $limitnum Return at most this number of events
* @param bool $lmittononsuspendedevents Limit course events to courses the user is active in (not suspended).
* @param string|null $searchvalue The value a user wishes to search against
* @return event_interface
*/
public function get_action_events_by_timesort(
@@ -102,7 +105,8 @@ interface event_vault_interface {
$timesortto,
event_interface $afterevent,
$limitnum,
$limittononsuspendedevents
$limittononsuspendedevents,
?string $searchvalue = null
);
/**
@@ -118,6 +122,7 @@ interface event_vault_interface {
* @param int $timesortto Events with timesort until this value (inclusive)
* @param event_interface $afterevent Only return events after this one
* @param int $limitnum Return at most this number of events
* @param string|null $searchvalue The value a user wishes to search against
* @return action_event_interface
*/
public function get_action_events_by_course(
@@ -126,6 +131,7 @@ interface event_vault_interface {
$timesortfrom,
$timesortto,
event_interface $afterevent,
$limitnum
$limitnum,
?string $searchvalue = null
);
}
@@ -300,7 +300,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
GROUP BY ev.modulename, ev.instance, ev.eventtype";
// Build the main query.
$sql = "SELECT e.*
$sql = "SELECT e.*, c.fullname AS coursefullname, c.shortname AS courseshortname
FROM {event} e
INNER JOIN ($subquery) fe
ON e.modulename = fe.modulename
@@ -309,6 +309,8 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
AND (e.priority = fe.priority OR (e.priority IS NULL AND fe.priority IS NULL))
LEFT JOIN {modules} m
ON e.modulename = m.name
LEFT JOIN {course} c
ON c.id = e.courseid
WHERE (m.visible = 1 OR m.visible IS NULL) AND $whereclause
ORDER BY " . ($ordersql ? $ordersql : "e.timestart");
+13 -5
View File
@@ -408,6 +408,7 @@ class core_calendar_external extends external_api {
'limittononsuspendedevents' => new external_value(PARAM_BOOL,
'Limit the events to courses the user is not suspended in', VALUE_DEFAULT, false),
'userid' => new external_value(PARAM_INT, 'The user id', VALUE_DEFAULT, null),
'searchvalue' => new external_value(PARAM_TEXT, 'The value a user wishes to search against', VALUE_DEFAULT, null)
)
);
}
@@ -421,11 +422,12 @@ class core_calendar_external extends external_api {
* @param null|int $aftereventid Get events with ids greater than this one
* @param int $limitnum Limit the number of results to this value
* @param null|int $userid The user id
* @param string|null $searchvalue The value a user wishes to search against
* @return array
*/
public static function get_calendar_action_events_by_timesort($timesortfrom = 0, $timesortto = null,
$aftereventid = 0, $limitnum = 20, $limittononsuspendedevents = false,
$userid = null) {
$userid = null, ?string $searchvalue = null) {
global $PAGE, $USER;
$params = self::validate_parameters(
@@ -437,6 +439,7 @@ class core_calendar_external extends external_api {
'limitnum' => $limitnum,
'limittononsuspendedevents' => $limittononsuspendedevents,
'userid' => $userid,
'searchvalue' => $searchvalue
]
);
if ($params['userid']) {
@@ -459,7 +462,8 @@ class core_calendar_external extends external_api {
$params['aftereventid'],
$params['limitnum'],
$params['limittononsuspendedevents'],
$user
$user,
$params['searchvalue']
);
$exportercache = new events_related_objects_cache($events);
@@ -574,7 +578,8 @@ class core_calendar_external extends external_api {
),
'timesortfrom' => new external_value(PARAM_INT, 'Time sort from', VALUE_DEFAULT, null),
'timesortto' => new external_value(PARAM_INT, 'Time sort to', VALUE_DEFAULT, null),
'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 10)
'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 10),
'searchvalue' => new external_value(PARAM_TEXT, 'The value a user wishes to search against', VALUE_DEFAULT, null)
)
);
}
@@ -587,10 +592,11 @@ class core_calendar_external extends external_api {
* @param null|int $timesortfrom Events after this time (inclusive)
* @param null|int $timesortto Events before this time (inclusive)
* @param int $limitnum Limit the number of results per course to this value
* @param string|null $searchvalue The value a user wishes to search against
* @return array
*/
public static function get_calendar_action_events_by_courses(
array $courseids, $timesortfrom = null, $timesortto = null, $limitnum = 10) {
array $courseids, $timesortfrom = null, $timesortto = null, $limitnum = 10, ?string $searchvalue = null) {
global $PAGE, $USER;
@@ -602,6 +608,7 @@ class core_calendar_external extends external_api {
'timesortfrom' => $timesortfrom,
'timesortto' => $timesortto,
'limitnum' => $limitnum,
'searchvalue' => $searchvalue
]
);
$context = \context_user::instance($USER->id);
@@ -623,7 +630,8 @@ class core_calendar_external extends external_api {
$courses,
$params['timesortfrom'],
$params['timesortto'],
$params['limitnum']
$params['limitnum'],
$params['searchvalue']
);
if (empty($events)) {
+106
View File
@@ -1438,6 +1438,112 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
$this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
}
/**
* Test get_action_events_by_courses with search feature
*/
public function test_get_action_events_by_courses_with_search() {
// Generate data.
$user = $this->getDataGenerator()->create_user();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course3 = $this->getDataGenerator()->create_course();
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$instance1 = $generator->create_instance(['course' => $course1->id]);
$instance2 = $generator->create_instance(['course' => $course2->id]);
$instance3 = $generator->create_instance(['course' => $course3->id]);
$this->getDataGenerator()->enrol_user($user->id, $course1->id);
$this->getDataGenerator()->enrol_user($user->id, $course2->id);
$this->resetAfterTest(true);
$this->setUser($user);
$mapresult = function($result) {
$groupedbycourse = [];
foreach ($result['groupedbycourse'] as $group) {
$events = $group['events'];
$courseid = $group['courseid'];
$groupedbycourse[$courseid] = $events;
}
return $groupedbycourse;
};
for ($i = 1; $i < 10; $i++) {
if ($i < 3) {
$courseid = $course1->id;
$instance = $instance1->id;
} else if ($i < 6) {
$courseid = $course2->id;
$instance = $instance2->id;
} else {
$courseid = $course3->id;
$instance = $instance3->id;
}
$records[] = $this->create_calendar_event(
sprintf('Event %d', $i),
$user->id,
'user',
0,
1,
[
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $courseid,
'timesort' => $i,
'modulename' => 'assign',
'instance' => $instance,
]
);
}
// No result found for fake search.
$result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id, $course2->id, $course3->id],
1, null, 20, 'Fake search');
$result = external_api::clean_returnvalue(
core_calendar_external::get_calendar_action_events_by_courses_returns(),
$result
);
$groupedbycourse = $mapresult($result);
$this->assertEmpty($groupedbycourse[$course1->id]);
$this->assertEmpty($groupedbycourse[$course2->id]);
$this->assertArrayNotHasKey($course3->id, $groupedbycourse);
// Search for event name called 'Event 1'.
$result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id, $course2->id, $course3->id],
1, null, 20, 'Event 1');
$result = external_api::clean_returnvalue(
core_calendar_external::get_calendar_action_events_by_courses_returns(),
$result
);
$groupedbycourse = $mapresult($result);
$this->assertArrayNotHasKey($course3->id, $groupedbycourse);
$this->assertCount(2, $groupedbycourse);
$this->assertCount(1, $groupedbycourse[$course1->id]);
$this->assertCount(0, $groupedbycourse[$course2->id]);
$this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
// Search for activity type called 'assign'.
$result = core_calendar_external::get_calendar_action_events_by_courses([$course1->id, $course2->id, $course3->id],
1, null, 20, 'assign');
$result = external_api::clean_returnvalue(
core_calendar_external::get_calendar_action_events_by_courses_returns(),
$result
);
$groupedbycourse = $mapresult($result);
$this->assertArrayNotHasKey($course3->id, $groupedbycourse);
$this->assertCount(2, $groupedbycourse);
$this->assertCount(2, $groupedbycourse[$course1->id]);
$this->assertCount(3, $groupedbycourse[$course2->id]);
$this->assertEquals('Event 1', $groupedbycourse[$course1->id][0]['name']);
$this->assertEquals('Event 2', $groupedbycourse[$course1->id][1]['name']);
$this->assertEquals('Event 3', $groupedbycourse[$course2->id][0]['name']);
$this->assertEquals('Event 4', $groupedbycourse[$course2->id][1]['name']);
$this->assertEquals('Event 5', $groupedbycourse[$course2->id][2]['name']);
}
/**
* Test for deleting module events.
*/
+58
View File
@@ -290,6 +290,64 @@ class core_calendar_local_api_testcase extends advanced_testcase {
$this->assertEmpty($result);
}
/**
* Test get_calendar_action_events_by_timesort with search feature.
*/
public function test_get_calendar_action_events_by_timesort_with_search() {
// Generate data.
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$moduleinstance = $generator->create_instance(['course' => $course->id]);
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$this->resetAfterTest(true);
$this->setAdminUser();
$params = [
'type' => CALENDAR_EVENT_TYPE_ACTION,
'courseid' => $course->id,
'modulename' => 'assign',
'instance' => $moduleinstance->id,
'userid' => 1,
'eventtype' => 'user',
'repeats' => 0,
'timestart' => 1,
];
$event1 = create_event(array_merge($params, ['name' => 'Event 1', 'timesort' => 1]));
$event2 = create_event(array_merge($params, ['name' => 'Event 2', 'timesort' => 2]));
$event3 = create_event(array_merge($params, ['name' => 'Event 3', 'timesort' => 3]));
$event4 = create_event(array_merge($params, ['name' => 'Event 4', 'timesort' => 4]));
$event5 = create_event(array_merge($params, ['name' => 'Event 5', 'timesort' => 5]));
$event6 = create_event(array_merge($params, ['name' => 'Event 6', 'timesort' => 6]));
$event7 = create_event(array_merge($params, ['name' => 'Event 7', 'timesort' => 7]));
$event8 = create_event(array_merge($params, ['name' => 'Event 8', 'timesort' => 8]));
$this->setUser($user);
// No result found for fake search.
$result = \core_calendar\local\api::get_action_events_by_timesort(0, null, null, 6, false, null, 'Fake search');
$this->assertEmpty($result);
// Search for event name called 'Event 1'.
$result = \core_calendar\local\api::get_action_events_by_timesort(0, 8, null, 20, false, null, 'Event 1');
$this->assertCount(1, $result);
$this->assertEquals('Event 1', $result[0]->get_name());
// Search for activity type called 'assign'.
$result = \core_calendar\local\api::get_action_events_by_timesort(0, 8, null, 20, false, null, 'assign');
$this->assertCount(8, $result);
$this->assertEquals('Event 1', $result[0]->get_name());
$this->assertEquals('Event 2', $result[1]->get_name());
$this->assertEquals('Event 3', $result[2]->get_name());
$this->assertEquals('Event 4', $result[3]->get_name());
$this->assertEquals('Event 5', $result[4]->get_name());
$this->assertEquals('Event 6', $result[5]->get_name());
$this->assertEquals('Event 7', $result[6]->get_name());
$this->assertEquals('Event 8', $result[7]->get_name());
}
/**
* Requesting calendar events from a given course and time should return all
* events with a sort time at or after the requested time. All events prior
+5
View File
@@ -1,6 +1,11 @@
This files describes API changes in /calendar/* ,
information provided here is intended especially for developers.
=== 4.0 ===
* The following external functions now accepts an optional parameter 'searchvalue' to search the events:
- core_calendar_external::get_calendar_action_events_by_timesort
- core_calendar_external::get_calendar_action_events_by_courses
=== 3.10 ===
* The core_calendar\local\event\value_objects\times_interface class now has new method get_usermidnight_time() which
returns the user midnight time for a given event.
+8
View File
@@ -105,6 +105,14 @@ $card-gutter : $card-deck-margin * 2;
height: 5rem;
}
.block {
.searchbar {
.icon {
margin-right: 0;
}
}
}
.block .block-cards {
.course-info-container {
padding: 0.8rem;
+3
View File
@@ -12868,6 +12868,9 @@ body.dragging .dragging {
.block .empty-placeholder-image-lg {
height: 5rem; }
.block .searchbar .icon {
margin-right: 0; }
.block .block-cards .course-info-container {
padding: 0.8rem; }
+3
View File
@@ -12868,6 +12868,9 @@ body.dragging .dragging {
.block .empty-placeholder-image-lg {
height: 5rem; }
.block .searchbar .icon {
margin-right: 0; }
.block .block-cards .course-info-container {
padding: 0.8rem; }