diff --git a/README.md b/README.md index a98fcba9..d001320d 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,30 @@ 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 this program. If not, see . + +## Version 2024040300 refactor ## + +Post version `2024040300` this plugin was completely refactored to support more reports and modules. +In addition the report now loads in a tabbed format instead of in different locations. + +Each report is now a subplugin within the `report` directory +The subplugins report class should extend from the \local_assessfreq\report_base class + +Capability checks were reworked to be relative to the location that they are being loading from. The initial version +has the following capabilities: + +- local/assessfreq:view +- assessfreqreport/activity_dashboard:view +- assessfreqreport/activities_in_progress:view +- assessfreqreport/heatmap:view +- assessfreqreport/summary_graphs:view + +however each future subplugin can define their own access checks by using the abstract `has_access` method. +Accessing the reports from a course (link now added to the course context menu under reports) will do the capability check at the +course context level, otherwise system level will be used. + +The reports themselves should also be restricted based on the $PAGE->course if it is not the SITEID as this is set +during the intial load of the index.php file. + +Each module is now a subplugin within the `source` directory +The subplugins source class should extend from the \local_assessfreq\source_base class diff --git a/amd/build/calendar.min.js b/amd/build/calendar.min.js deleted file mode 100644 index 45d125d1..00000000 --- a/amd/build/calendar.min.js +++ /dev/null @@ -1,2 +0,0 @@ -function _slicedToArray(a,b){return _arrayWithHoles(a)||_iterableToArrayLimit(a,b)||_unsupportedIterableToArray(a,b)||_nonIterableRest()}function _nonIterableRest(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(a,b){if(!a)return;if("string"==typeof a)return _arrayLikeToArray(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return _arrayLikeToArray(a,b)}function _arrayLikeToArray(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);cg;g++){if("undefined"!=typeof f[g]){for(var l=f[g],m=0;32>m;m++){if("undefined"!=typeof l[m]){e.push(l[m].number)}}}}h=Math.max.apply(Math,e);k=Math.min.apply(Math,e)}else{h=0;k=0}c(a)})},t=function(a){if(a==k){return 1}var b=h-k,c=(a-k)/b,d=Math.round(c*5+1);if(1>d){d=1}if(6s;s++){j=document.createElement("th");j.innerHTML=g[s];p.appendChild(j)}o.appendChild(q);m.appendChild(o);m.appendChild(p);r.appendChild(m);r.appendChild(n);l.appendChild(r);f.appendChild(l);h++}if("undefined"==typeof b||"undefined"==typeof c||"undefined"==typeof d){e(Error("Failed to create calendar tables."))}else{a({calendarContainer:f,year:b,startMonth:c})}})},x=function(a){for(var b="",c=0,d=Object.entries(a);c"+m[f]+": "+g+"
"}return b},y=function(a,b,c){for(var d=new Date(b,c).getDay(),e=v(b,c+1),f=1,g=0,h;6>g;g++){h=document.createElement("tr");for(var q=0;7>q;q++){if(0===g&&qp(c,b)){break}else{k=document.createElement("td");m=document.createTextNode(f);if("undefined"!=typeof e&&e.hasOwnProperty(f)){var j=t(e[f].number);if(0==n[j]||n[j]>e[f].number){n[j]=e[f].number}k.style.backgroundColor=l[j];k.style.color=o(l[j]);k.dataset.toggle="tooltip";k.dataset.html="true";k.dataset.event="true";k.dataset.date=b+"-"+(c+1)+"-"+f;k.title=x(e[f]);k.style.cursor="pointer"}f++}k.appendChild(m);h.appendChild(k)}a.appendChild(h)}},z=function(a){var b=a.calendarContainer,c=a.year,d=a.startMonth;return new Promise(function(a,e){for(var f=b.getElementsByTagName("tbody"),g=d,h=0,j;he;e++){if(0!==n[e]){var f=document.createElement("td"),g=document.createTextNode(n[e]+"+");f.appendChild(g);f.style.backgroundColor=l[e];f.style.color=o(l[e]);d.appendChild(f)}}c.appendChild(d);b.appendChild(c);n={1:0,2:0,3:0,4:0,5:0,6:0};a(b)})};d.generate=function(c,d,e,h,i){return new Promise(function(j,k){var l={year:c,startMonth:d,endMonth:e},m={year:c,metric:h,modules:i};a.get_strings(f).catch(function(){b.exception(new Error("Failed to load strings"))}).then(function(a){g=a;return m}).then(u).then(function(a){s(a,l)}).then(q).then(r).then(function(){return l}).then(w).then(z).then(function(a){if("undefined"!=typeof a){j(a)}else{k(Error("Could not generate calendar"))}})})};return d}); -//# sourceMappingURL=calendar.min.js.map diff --git a/amd/build/calendar.min.js.map b/amd/build/calendar.min.js.map deleted file mode 100644 index 3bac26b8..00000000 --- a/amd/build/calendar.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/calendar.js"],"names":["define","Str","Notification","Ajax","Calendar","eventArray","stringArr","key","component","stringResult","heatRangeMax","heatRangeMin","colorArray","processModules","heatRangeScale","getContrast","hexcolor","slice","r","parseInt","substr","g","b","daysInMonth","month","year","Date","getDate","getHeatColors","Promise","resolve","reject","call","methodname","args","done","response","JSON","parse","fail","Error","getProcessModules","calcHeatRange","dateObj","eventArrayLength","Object","keys","length","eventcount","i","j","push","number","Math","max","min","getHeat","eventCount","localRange","localPercent","heat","round","getEvents","metric","modules","jsonArgs","stringify","jsondata","getMonthEvents","monthevents","createTables","startMonth","endMonth","calendarContainer","document","createElement","container","classList","add","table","thead","tbody","id","monthRow","dayrow","monthHeader","colSpan","innerHTML","dayHeader","appendChild","getTooltip","dayArray","tipHTML","entries","value","populateCalendarDays","firstDay","getDay","monthEvents","date","row","cell","cellText","createTextNode","dataset","event","hasOwnProperty","style","backgroundColor","color","toggle","html","title","cursor","populateCalendar","tables","getElementsByTagName","createHeatScale","trow","generate","eventObj","get_strings","catch","exception","then","stringReturn","calendarHTML"],"mappings":"+qCAuBAA,OAAM,6BAAC,CAAC,UAAD,CAAa,mBAAb,CAAkC,WAAlC,CAAD,CAAiD,SAASC,CAAT,CAAcC,CAAd,CAA4BC,CAA5B,CAAkC,IAKjFC,CAAAA,CAAQ,CAAG,EALsE,CAMjFC,CAAU,CAAG,EANoE,CAO/EC,CAAS,CAAG,CACd,CAACC,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,UAAxB,CADc,CAEd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,UAAxB,CAFc,CAGd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,UAAxB,CAHc,CAId,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,UAAxB,CAJc,CAKd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,UAAxB,CALc,CAMd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,UAAxB,CANc,CAOd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,UAAxB,CAPc,CAQd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CARc,CASd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CATc,CAUd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAVc,CAWd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAXc,CAYd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAZc,CAad,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAbc,CAcd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAdc,CAed,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAfc,CAgBd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAhBc,CAiBd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAjBc,CAkBd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAlBc,CAmBd,CAACD,GAAG,CAAE,KAAN,CAAaC,SAAS,CAAE,kBAAxB,CAnBc,CAPmE,CA4BjFC,CA5BiF,CA6BjFC,CA7BiF,CA8BjFC,CA9BiF,CA+BjFC,CA/BiF,CAgCjFC,CAhCiF,CAiCjFC,CAAc,CAAG,CAAC,EAAK,CAAN,CAAS,EAAK,CAAd,CAAiB,EAAK,CAAtB,CAAyB,EAAK,CAA9B,CAAiC,EAAK,CAAtC,CAAyC,EAAK,CAA9C,CAjCgE,CAyC/EC,CAAW,CAAG,SAAUC,CAAV,CAAoB,CAEpC,GAA0B,WAAtB,QAAQA,CAAAA,CAAZ,CAAuC,CACnC,MAAO,SACV,CAGD,GAA6B,GAAzB,GAAAA,CAAQ,CAACC,KAAT,CAAe,CAAf,CAAkB,CAAlB,CAAJ,CAAkC,CAC9BD,CAAQ,CAAGA,CAAQ,CAACC,KAAT,CAAe,CAAf,CACd,CATmC,GAYhCC,CAAAA,CAAC,CAAGC,QAAQ,CAACH,CAAQ,CAACI,MAAT,CAAgB,CAAhB,CAAkB,CAAlB,CAAD,CAAsB,EAAtB,CAZoB,CAahCC,CAAC,CAAGF,QAAQ,CAACH,CAAQ,CAACI,MAAT,CAAgB,CAAhB,CAAkB,CAAlB,CAAD,CAAsB,EAAtB,CAboB,CAchCE,CAAC,CAAGH,QAAQ,CAACH,CAAQ,CAACI,MAAT,CAAgB,CAAhB,CAAkB,CAAlB,CAAD,CAAsB,EAAtB,CAdoB,CAoBpC,MAAe,IAAP,EAHE,CAAM,GAAJ,CAAAF,CAAD,CAAiB,GAAJ,CAAAG,CAAb,CAA6B,GAAJ,CAAAC,CAA1B,EAAsC,GAGzC,CAAe,SAAf,CAA2B,SACrC,CA9DoF,CAwE/EC,CAAW,CAAG,SAASC,CAAT,CAAgBC,CAAhB,CAAsB,CACtC,MAAO,IAAK,GAAIC,CAAAA,IAAJ,CAASD,CAAT,CAAeD,CAAf,CAAsB,EAAtB,EAA0BG,OAA1B,EACf,CA1EoF,CAiF/EC,CAAa,CAAG,UAAW,CAC7B,MAAO,IAAIC,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAUC,CAAV,CAAqB,CACpC5B,CAAI,CAAC6B,IAAL,CAAU,CAAC,CACPC,UAAU,CAAE,kCADL,CAEPC,IAAI,CAAE,EAFC,CAAD,CAAV,QAGiB,CAHjB,EAGoBC,IAHpB,CAGyB,SAASC,CAAT,CAAmB,CACxCxB,CAAU,CAAGyB,IAAI,CAACC,KAAL,CAAWF,CAAX,CAAb,CACAN,CAAO,CAAClB,CAAD,CACV,CAND,EAMG2B,IANH,CAMQ,UAAW,CACfR,CAAM,CAAC,GAAIS,CAAAA,KAAJ,CAAU,2BAAV,CAAD,CACT,CARD,CASH,CAVM,CAWV,CA7FoF,CAoG/EC,CAAiB,CAAG,UAAW,CACjC,MAAO,IAAIZ,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAUC,CAAV,CAAqB,CACpC5B,CAAI,CAAC6B,IAAL,CAAU,CAAC,CACPC,UAAU,CAAE,sCADL,CAEPC,IAAI,CAAE,EAFC,CAAD,CAAV,QAGiB,CAHjB,EAGoBC,IAHpB,CAGyB,SAASC,CAAT,CAAmB,CACxCvB,CAAc,CAAGwB,IAAI,CAACC,KAAL,CAAWF,CAAX,CAAjB,CACAN,CAAO,CAACjB,CAAD,CACV,CAND,EAMG0B,IANH,CAMQ,UAAW,CACfR,CAAM,CAAC,GAAIS,CAAAA,KAAJ,CAAU,8BAAV,CAAD,CACT,CARD,CASH,CAVM,CAWV,CAhHoF,CAyH/EE,CAAa,CAAG,SAASrC,CAAT,CAAqBsC,CAArB,CAA8B,CAChD,MAAO,IAAId,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAa,CAG5B,GAA4B,WAAxB,QAAQzB,CAAAA,CAAZ,CAAyC,CACrCK,CAAY,CAAG,CAAf,CACAC,CAAY,CAAG,CAAf,CAEAmB,CAAO,CAACzB,CAAD,CACV,CAED,GAAIuC,CAAAA,CAAgB,CAAGC,MAAM,CAACC,IAAP,CAAYzC,CAAZ,EAAwB0C,MAA/C,CACA,GAAwB,CAAnB,CAAAH,CAAD,EAAwD,WAA7B,GAAAvC,CAAU,CAACsC,CAAO,CAAClB,IAAT,CAAzC,CAA0E,CAOtE,OALIuB,CAAAA,CAAU,GAKd,CAJIvB,CAAI,CAAGpB,CAAU,CAACsC,CAAO,CAAClB,IAAT,CAIrB,CAASwB,CAAC,CAAG,CAAb,CAAoB,EAAJ,CAAAA,CAAhB,CAAwBA,CAAC,EAAzB,CAA6B,CACzB,GAAuB,WAAnB,QAAOxB,CAAAA,CAAI,CAACwB,CAAD,CAAf,CAAoC,CAEhC,OADIzB,CAAAA,CAAK,CAAGC,CAAI,CAACwB,CAAD,CAChB,CAASC,CAAC,CAAG,CAAb,CAAoB,EAAJ,CAAAA,CAAhB,CAAwBA,CAAC,EAAzB,CAA6B,CACzB,GAAwB,WAApB,QAAO1B,CAAAA,CAAK,CAAC0B,CAAD,CAAhB,CAAqC,CACjCF,CAAU,CAACG,IAAX,CAAgB3B,CAAK,CAAC0B,CAAD,CAAL,CAASE,MAAzB,CACH,CACJ,CACJ,CACJ,CAGD1C,CAAY,CAAG2C,IAAI,CAACC,GAAL,OAAAD,IAAI,CAAQL,CAAR,CAAnB,CACArC,CAAY,CAAG0C,IAAI,CAACE,GAAL,OAAAF,IAAI,CAAQL,CAAR,CACtB,CArBD,IAqBO,CACHtC,CAAY,CAAG,CAAf,CACAC,CAAY,CAAG,CAClB,CAEDmB,CAAO,CAACzB,CAAD,CACV,CAtCM,CAuCV,CAjKoF,CA0K/EmD,CAAO,CAAG,SAASC,CAAT,CAAqB,CAGjC,GAAIA,CAAU,EAAI9C,CAAlB,CAAgC,CAC5B,QACH,CALgC,GAQ3B+C,CAAAA,CAAU,CAAGhD,CAAY,CAAGC,CARD,CAS3BgD,CAAY,CAAG,CAACF,CAAU,CAAG9C,CAAd,EAA8B+C,CATlB,CAU7BE,CAAI,CAAGP,IAAI,CAACQ,KAAL,CAAYF,CAAY,EAAb,CAA8B,CAAzC,CAVsB,CAajC,GAAW,CAAP,CAAAC,CAAJ,CAAc,CACVA,CAAI,CAAG,CACV,CAED,GAAW,CAAP,CAAAA,CAAJ,CAAc,CACVA,CAAI,CAAG,CACV,CAED,MAAOA,CAAAA,CACV,CAhMoF,CA2M/EE,CAAS,CAAG,WAAkC,IAAxBrC,CAAAA,CAAwB,GAAxBA,IAAwB,CAAlBsC,CAAkB,GAAlBA,MAAkB,CAAVC,CAAU,GAAVA,OAAU,CAChD,MAAO,IAAInC,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAUC,CAAV,CAAqB,IAMhCkC,CAAAA,CAAQ,CAAG5B,IAAI,CAAC6B,SAAL,CALJ,CACPzC,IAAI,CAAEA,CADC,CAEPsC,MAAM,CAAEA,CAFD,CAGPC,OAAO,CAAEA,CAHF,CAKI,CANqB,CASpC7D,CAAI,CAAC6B,IAAL,CAAU,CAAC,CACPC,UAAU,CAAE,gCADL,CAEPC,IAAI,CAAE,CACFiC,QAAQ,CAAEF,CADR,CAFC,CAAD,CAAV,EAKI,CALJ,EAKO9B,IALP,CAKY,SAACC,CAAD,CAAc,CACtB/B,CAAU,CAAGgC,IAAI,CAACC,KAAL,CAAWF,CAAX,CAAb,CACAN,CAAO,CAACzB,CAAD,CACV,CARD,EAQGkC,IARH,CAQQ,UAAM,CACVR,CAAM,CAAC,GAAIS,CAAAA,KAAJ,CAAU,sBAAV,CAAD,CACT,CAVD,CAWH,CApBM,CAqBV,CAjOoF,CA0O/E4B,CAAc,CAAG,SAAS3C,CAAT,CAAeD,CAAf,CAAsB,CACzC,GAAI6C,CAAAA,CAAJ,CAEA,GAAiC,WAA5B,QAAOhE,CAAAA,CAAU,CAACoB,CAAD,CAAlB,EAAiF,WAAnC,QAAOpB,CAAAA,CAAU,CAACoB,CAAD,CAAV,CAAiBD,CAAjB,CAAzD,CAAmG,CAC/F6C,CAAW,CAAGhE,CAAU,CAACoB,CAAD,CAAV,CAAiBD,CAAjB,CACjB,CAED,MAAO6C,CAAAA,CACV,CAlPoF,CA4P/EC,CAAY,CAAG,WAAuC,IAA7B7C,CAAAA,CAA6B,GAA7BA,IAA6B,CAAvB8C,CAAuB,GAAvBA,UAAuB,CAAXC,CAAW,GAAXA,QAAW,CACxD,MAAO,IAAI3C,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAUC,CAAV,CAAqB,CAKpC,OAJI0C,CAAAA,CAAiB,CAAGC,QAAQ,CAACC,aAAT,CAAuB,KAAvB,CAIxB,CAHInD,CAAK,CAAG+C,CAGZ,CAAStB,CAAC,CAAGsB,CAAb,CAEQK,CAFR,CAAyB3B,CAAC,EAAIuB,CAA9B,CAAwCvB,CAAC,EAAzC,CAA6C,CAErC2B,CAFqC,CAEzBF,QAAQ,CAACC,aAAT,CAAuB,KAAvB,CAFyB,CAGzCC,CAAS,CAACC,SAAV,CAAoBC,GAApB,CAAwB,wBAAxB,EACA,GAAIC,CAAAA,CAAK,CAAGL,QAAQ,CAACC,aAAT,CAAuB,OAAvB,CAAZ,CACAI,CAAK,CAACF,SAAN,CAAgBC,GAAhB,CAAoB,eAApB,EALyC,GAMrCE,CAAAA,CAAK,CAAGN,QAAQ,CAACC,aAAT,CAAuB,OAAvB,CAN6B,CAOrCM,CAAK,CAAGP,QAAQ,CAACC,aAAT,CAAuB,OAAvB,CAP6B,CAQzCM,CAAK,CAACC,EAAN,CAAW,iBAAmBjC,CAA9B,CARyC,GASrCkC,CAAAA,CAAQ,CAAGT,QAAQ,CAACC,aAAT,CAAuB,IAAvB,CAT0B,CAUrCS,CAAM,CAAGV,QAAQ,CAACC,aAAT,CAAuB,IAAvB,CAV4B,CAWrCU,CAAW,CAAGX,QAAQ,CAACC,aAAT,CAAuB,IAAvB,CAXuB,CAYzCU,CAAW,CAACC,OAAZ,CAAsB,CAAtB,CACAD,CAAW,CAACE,SAAZ,CAAwB9E,CAAY,CAAE,EAAIe,CAAN,CAApC,CAEA,IAAK,GAAI0B,CAAAA,CAAC,CAAG,CAAR,CACGsC,CADR,CAAoB,CAAJ,CAAAtC,CAAhB,CAAuBA,CAAC,EAAxB,CAA4B,CACpBsC,CADoB,CACRd,QAAQ,CAACC,aAAT,CAAuB,IAAvB,CADQ,CAExBa,CAAS,CAACD,SAAV,CAAsB9E,CAAY,CAACyC,CAAD,CAAlC,CACAkC,CAAM,CAACK,WAAP,CAAmBD,CAAnB,CACH,CAGDL,CAAQ,CAACM,WAAT,CAAqBJ,CAArB,EAEAL,CAAK,CAACS,WAAN,CAAkBN,CAAlB,EACAH,CAAK,CAACS,WAAN,CAAkBL,CAAlB,EAEAL,CAAK,CAACU,WAAN,CAAkBT,CAAlB,EACAD,CAAK,CAACU,WAAN,CAAkBR,CAAlB,EAEAL,CAAS,CAACa,WAAV,CAAsBV,CAAtB,EAGAN,CAAiB,CAACgB,WAAlB,CAA8Bb,CAA9B,EAGApD,CAAK,EACR,CAED,GAAqB,WAAhB,QAAOC,CAAAA,CAAR,EAAwD,WAAtB,QAAO8C,CAAAA,CAAzC,EAA6F,WAApB,QAAOC,CAAAA,CAApF,CAA+G,CAC3GzC,CAAM,CAACS,KAAK,CAAC,mCAAD,CAAN,CACT,CAFD,IAEO,CAMHV,CAAO,CALW,CACd2C,iBAAiB,CAAGA,CADN,CAEdhD,IAAI,CAAGA,CAFO,CAGd8C,UAAU,CAAGA,CAHC,CAKX,CACV,CACJ,CAtDM,CAuDV,CApToF,CA4T/EmB,CAAU,CAAG,SAASC,CAAT,CAAmB,CAGlC,OAFIC,CAAAA,CAAO,CAAG,EAEd,OAAyB/C,MAAM,CAACgD,OAAP,CAAeF,CAAf,CAAzB,gBAAmD,8BAAzCpF,CAAyC,MAApCuF,CAAoC,MAC/CF,CAAO,EAAI,WAAa/E,CAAc,CAACN,CAAD,CAA3B,CAAmC,aAAnC,CAAmDuF,CAAnD,CAA2D,OACzE,CAED,MAAOF,CAAAA,CACV,CApUoF,CA6U/EG,CAAoB,CAAG,SAAShB,CAAT,CAAgBtD,CAAhB,CAAsBD,CAAtB,CAA6B,CAKtD,OAJIwE,CAAAA,CAAQ,CAAI,GAAItE,CAAAA,IAAJ,CAASD,CAAT,CAAeD,CAAf,CAAD,CAAwByE,MAAxB,EAIf,CAHIC,CAAW,CAAG9B,CAAc,CAAC3C,CAAD,CAAQD,CAAK,CAAG,CAAhB,CAGhC,CAFI2E,CAAI,CAAG,CAEX,CAASlD,CAAC,CAAG,CAAb,CACQmD,CADR,CAAoB,CAAJ,CAAAnD,CAAhB,CAAuBA,CAAC,EAAxB,CAA4B,CACpBmD,CADoB,CACd1B,QAAQ,CAACC,aAAT,CAAuB,IAAvB,CADc,CAIxB,IAAK,GAAIzB,CAAAA,CAAC,CAAG,CAAb,CAAoB,CAAJ,CAAAA,CAAhB,CAAuBA,CAAC,EAAxB,CAA4B,CACxB,GAAU,CAAN,GAAAD,CAAC,EAAUC,CAAC,CAAG8C,CAAnB,CAA6B,IACrBK,CAAAA,CAAI,CAAG3B,QAAQ,CAACC,aAAT,CAAuB,IAAvB,CADc,CAErB2B,CAAQ,CAAG5B,QAAQ,CAAC6B,cAAT,CAAwB,EAAxB,CAFU,CAGzBF,CAAI,CAACG,OAAL,CAAaC,KAAb,CAAqB,OAExB,CALD,IAKO,IAAIN,CAAI,CAAG5E,CAAW,CAACC,CAAD,CAAQC,CAAR,CAAtB,CAAqC,CACxC,KACH,CAFM,IAEA,CACH4E,CAAI,CAAG3B,QAAQ,CAACC,aAAT,CAAuB,IAAvB,CAAP,CACA2B,CAAQ,CAAG5B,QAAQ,CAAC6B,cAAT,CAAwBJ,CAAxB,CAAX,CACA,GAA4B,WAAvB,QAAOD,CAAAA,CAAR,EAAyCA,CAAW,CAACQ,cAAZ,CAA2BP,CAA3B,CAA7C,CAAgF,CAC5E,GAAIvC,CAAAA,CAAI,CAAGJ,CAAO,CAAC0C,CAAW,CAACC,CAAD,CAAX,OAAD,CAAlB,CAEA,GAA4B,CAAxB,EAAArF,CAAc,CAAC8C,CAAD,CAAd,EAA6B9C,CAAc,CAAC8C,CAAD,CAAd,CAAuBsC,CAAW,CAACC,CAAD,CAAX,OAAxD,CAAqF,CACjFrF,CAAc,CAAC8C,CAAD,CAAd,CAAuBsC,CAAW,CAACC,CAAD,CAAX,OAC1B,CAEDE,CAAI,CAACM,KAAL,CAAWC,eAAX,CAA6BhG,CAAU,CAACgD,CAAD,CAAvC,CACAyC,CAAI,CAACM,KAAL,CAAWE,KAAX,CAAmB9F,CAAW,CAACH,CAAU,CAACgD,CAAD,CAAX,CAA9B,CAGAyC,CAAI,CAACG,OAAL,CAAaM,MAAb,CAAsB,SAAtB,CACAT,CAAI,CAACG,OAAL,CAAaO,IAAb,CAAoB,MAApB,CACAV,CAAI,CAACG,OAAL,CAAaC,KAAb,CAAqB,MAArB,CACAJ,CAAI,CAACG,OAAL,CAAaL,IAAb,CAAoB1E,CAAI,CAAG,GAAP,EAAcD,CAAK,CAAG,CAAtB,EAA2B,GAA3B,CAAiC2E,CAArD,CACAE,CAAI,CAACW,KAAL,CAAatB,CAAU,CAACQ,CAAW,CAACC,CAAD,CAAZ,CAAvB,CACAE,CAAI,CAACM,KAAL,CAAWM,MAAX,CAAoB,SAEvB,CACDd,CAAI,EACP,CAEDE,CAAI,CAACZ,WAAL,CAAiBa,CAAjB,EACAF,CAAG,CAACX,WAAJ,CAAgBY,CAAhB,CACH,CACDtB,CAAK,CAACU,WAAN,CAAkBW,CAAlB,CACH,CACJ,CA5XoF,CAsY/Ec,CAAgB,CAAG,WAAgD,IAAtCzC,CAAAA,CAAsC,GAAtCA,iBAAsC,CAAnBhD,CAAmB,GAAnBA,IAAmB,CAAb8C,CAAa,GAAbA,UAAa,CACrE,MAAO,IAAI1C,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAUC,CAAV,CAAqB,CAMpC,OAJIoF,CAAAA,CAAM,CAAG1C,CAAiB,CAAC2C,oBAAlB,CAAuC,OAAvC,CAIb,CAHI5F,CAAK,CAAG+C,CAGZ,CAAStB,CAAC,CAAG,CAAb,CACQ8B,CADR,CAAgB9B,CAAC,CAAGkE,CAAM,CAACpE,MAA3B,CAAmCE,CAAC,EAApC,CAAwC,CAChC8B,CADgC,CACxBoC,CAAM,CAAClE,CAAD,CADkB,CAEpC8C,CAAoB,CAAChB,CAAD,CAAQtD,CAAR,CAAcD,CAAd,CAApB,CACAA,CAAK,EACR,CAED,GAAiC,WAA7B,QAAOiD,CAAAA,CAAX,CAA8C,CAC1C1C,CAAM,CAACS,KAAK,CAAC,qCAAD,CAAN,CACT,CAFD,IAEO,CACHV,CAAO,CAAC2C,CAAD,CACV,CACJ,CAjBM,CAkBV,CAzZoF,CAgarFrE,CAAQ,CAACiH,eAAT,CAA2B,UAAW,CAClC,MAAO,IAAIxF,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAa,CAK5B,OAJIiD,CAAAA,CAAK,CAAGL,QAAQ,CAACC,aAAT,CAAuB,OAAvB,CAIZ,CAHIM,CAAK,CAAGP,QAAQ,CAACC,aAAT,CAAuB,OAAvB,CAGZ,CAFI2C,CAAI,CAAG5C,QAAQ,CAACC,aAAT,CAAuB,IAAvB,CAEX,CAAS1B,CAAC,CAAG,CAAb,CAAoB,CAAJ,CAAAA,CAAhB,CAAuBA,CAAC,EAAxB,CAA4B,CACxB,GAA0B,CAAtB,GAAAnC,CAAc,CAACmC,CAAD,CAAlB,CAA6B,IACrBoD,CAAAA,CAAI,CAAG3B,QAAQ,CAACC,aAAT,CAAuB,IAAvB,CADc,CAErB2B,CAAQ,CAAG5B,QAAQ,CAAC6B,cAAT,CAAwBzF,CAAc,CAACmC,CAAD,CAAd,CAAoB,GAA5C,CAFU,CAIzBoD,CAAI,CAACZ,WAAL,CAAiBa,CAAjB,EACAD,CAAI,CAACM,KAAL,CAAWC,eAAX,CAA6BhG,CAAU,CAACqC,CAAD,CAAvC,CACAoD,CAAI,CAACM,KAAL,CAAWE,KAAX,CAAmB9F,CAAW,CAACH,CAAU,CAACqC,CAAD,CAAX,CAA9B,CAEAqE,CAAI,CAAC7B,WAAL,CAAiBY,CAAjB,CACH,CAEJ,CAEDpB,CAAK,CAACQ,WAAN,CAAkB6B,CAAlB,EACAvC,CAAK,CAACU,WAAN,CAAkBR,CAAlB,EAGAnE,CAAc,CAAG,CAAC,EAAK,CAAN,CAAS,EAAK,CAAd,CAAiB,EAAK,CAAtB,CAAyB,EAAK,CAA9B,CAAiC,EAAK,CAAtC,CAAyC,EAAK,CAA9C,CAAjB,CAEAgB,CAAO,CAACiD,CAAD,CACV,CA1BM,CA2BV,CA5BD,CAwCA3E,CAAQ,CAACmH,QAAT,CAAoB,SAAS9F,CAAT,CAAe8C,CAAf,CAA2BC,CAA3B,CAAqCT,CAArC,CAA6CC,CAA7C,CAAsD,CACtE,MAAO,IAAInC,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAUC,CAAV,CAAqB,IAC9BY,CAAAA,CAAO,CAAG,CACZlB,IAAI,CAAGA,CADK,CAEZ8C,UAAU,CAAGA,CAFD,CAGZC,QAAQ,CAAGA,CAHC,CADoB,CAO9BgD,CAAQ,CAAG,CACb/F,IAAI,CAAGA,CADM,CAEbsC,MAAM,CAAGA,CAFI,CAGbC,OAAO,CAAGA,CAHG,CAPmB,CAapC/D,CAAG,CAACwH,WAAJ,CAAgBnH,CAAhB,EAA2BoH,KAA3B,CAAiC,UAAM,CACnCxH,CAAY,CAACyH,SAAb,CAAuB,GAAInF,CAAAA,KAAJ,CAAU,wBAAV,CAAvB,CAEH,CAHD,EAGGoF,IAHH,CAGQ,SAAAC,CAAY,CAAI,CACpBpH,CAAY,CAAGoH,CAAf,CACA,MAAOL,CAAAA,CACV,CAND,EAOCI,IAPD,CAOM9D,CAPN,EAQC8D,IARD,CAQM,SAACvH,CAAD,CAAgB,CAClBqC,CAAa,CAACrC,CAAD,CAAasC,CAAb,CAChB,CAVD,EAWCiF,IAXD,CAWMhG,CAXN,EAYCgG,IAZD,CAYMnF,CAZN,EAaCmF,IAbD,CAaM,UAAM,CACR,MAAOjF,CAAAA,CACV,CAfD,EAgBCiF,IAhBD,CAgBMtD,CAhBN,EAiBCsD,IAjBD,CAiBMV,CAjBN,EAkBCU,IAlBD,CAkBM,SAACE,CAAD,CAAkB,CACpB,GAA4B,WAAxB,QAAOA,CAAAA,CAAX,CAAyC,CACrChG,CAAO,CAACgG,CAAD,CACV,CAFD,IAEO,CACH/F,CAAM,CAACS,KAAK,CAAC,6BAAD,CAAN,CACT,CACJ,CAxBD,CAyBH,CAtCM,CAwCV,CAzCD,CA2CA,MAAOpC,CAAAA,CACV,CApfK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for heatmap calendar generation and display.\n *\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/str', 'core/notification', 'core/ajax'], function(Str, Notification, Ajax) {\n\n /**\n * Module level variables.\n */\n var Calendar = {};\n var eventArray = [];\n const stringArr = [\n {key: 'sun', component: 'calendar'},\n {key: 'mon', component: 'calendar'},\n {key: 'tue', component: 'calendar'},\n {key: 'wed', component: 'calendar'},\n {key: 'thu', component: 'calendar'},\n {key: 'fri', component: 'calendar'},\n {key: 'sat', component: 'calendar'},\n {key: 'jan', component: 'local_assessfreq'},\n {key: 'feb', component: 'local_assessfreq'},\n {key: 'mar', component: 'local_assessfreq'},\n {key: 'apr', component: 'local_assessfreq'},\n {key: 'may', component: 'local_assessfreq'},\n {key: 'jun', component: 'local_assessfreq'},\n {key: 'jul', component: 'local_assessfreq'},\n {key: 'aug', component: 'local_assessfreq'},\n {key: 'sep', component: 'local_assessfreq'},\n {key: 'oct', component: 'local_assessfreq'},\n {key: 'nov', component: 'local_assessfreq'},\n {key: 'dec', component: 'local_assessfreq'},\n ];\n var stringResult;\n var heatRangeMax;\n var heatRangeMin;\n var colorArray;\n var processModules;\n var heatRangeScale = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0};\n\n /**\n * Pick a contrasting text color based on the background color.\n *\n * @param {String} A hexcolor value.\n * @return {String} The contrasting color (black or white).\n */\n const getContrast = function (hexcolor) {\n\n if (typeof (hexcolor) === \"undefined\") {\n return '#000000';\n }\n\n // If a leading # is provided, remove it.\n if (hexcolor.slice(0, 1) === '#') {\n hexcolor = hexcolor.slice(1);\n }\n\n // Convert to RGB value.\n var r = parseInt(hexcolor.substr(0,2),16);\n var g = parseInt(hexcolor.substr(2,2),16);\n var b = parseInt(hexcolor.substr(4,2),16);\n\n // Get YIQ ratio.\n var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;\n\n // Check contrast.\n return (yiq >= 128) ? '#000000' : '#FFFFFF';\n };\n\n /**\n * Check how many days in a month code.\n * from https://dzone.com/articles/determining-number-days-month.\n *\n * @method daysInMonth\n * @param {Number} month The month to get the number of days for.\n * @param {Number} year The year to get the number of days for.\n */\n const daysInMonth = function(month, year) {\n return 32 - new Date(year, month, 32).getDate();\n };\n\n /**\n * Get the heat colors to use in the heat map via Ajax.\n *\n * @method getHeatColors\n */\n const getHeatColors = function() {\n return new Promise((resolve, reject) => {\n Ajax.call([{\n methodname: 'local_assessfreq_get_heat_colors',\n args: {},\n }], true, false)[0].done(function(response) {\n colorArray = JSON.parse(response);\n resolve(colorArray);\n }).fail(function() {\n reject(new Error('Failed to get heat colors'));\n });\n });\n };\n\n /**\n * Get the event names that we are processing.\n *\n * @method getProcessEvents\n */\n const getProcessModules = function() {\n return new Promise((resolve, reject) => {\n Ajax.call([{\n methodname: 'local_assessfreq_get_process_modules',\n args: {},\n }], true, false)[0].done(function(response) {\n processModules = JSON.parse(response);\n resolve(processModules);\n }).fail(function() {\n reject(new Error('Failed to get process events'));\n });\n });\n };\n\n /**\n * Calculate the min and max values to use in the heatmap.\n *\n * @method daysInMonth\n * @param {Object} eventArray All the event count for the heatmap.\n * @param {Object} dateObj Date details.\n */\n const calcHeatRange = function(eventArray, dateObj) {\n return new Promise((resolve) => {\n\n // Resolve early if there are no events.\n if (typeof (eventArray) === \"undefined\") {\n heatRangeMax = 0;\n heatRangeMin = 0;\n\n resolve(eventArray);\n }\n // If scheduled tasks have not run yet we may not have any data.\n let eventArrayLength = Object.keys(eventArray).length;\n if ((eventArrayLength > 0) && (eventArray[dateObj.year] !== \"undefined\")) {\n\n let eventcount = new Array;\n let year = eventArray[dateObj.year];\n\n // Iterate through all the event counts.\n // This code looks nasty but there is only 366 days in a year.\n for (let i = 0; i < 12; i++) {\n if (typeof year[i] !== \"undefined\") {\n let month = year[i];\n for (let j = 0; j < 32; j++) {\n if (typeof month[j] !== \"undefined\") {\n eventcount.push(month[j].number);\n }\n }\n }\n }\n\n // Get min and max values to calculate heat spread.\n heatRangeMax = Math.max(...eventcount);\n heatRangeMin = Math.min(...eventcount);\n } else {\n heatRangeMax = 0;\n heatRangeMin = 0;\n }\n\n resolve(eventArray);\n });\n };\n\n /**\n * Translate assessment frequency to a heat value.\n *\n * @method getHeat\n * @param {Number} eventCount The count to get the heat value.\n * @return {Number} heat The heat value.\n */\n const getHeat = function(eventCount) {\n let scaleMin = 1;\n\n if (eventCount == heatRangeMin) {\n return scaleMin;\n }\n\n const scaleRange = 5; // 0 - 5 steps.\n const localRange = heatRangeMax - heatRangeMin;\n const localPercent = (eventCount - heatRangeMin) / localRange;\n let heat = Math.round((localPercent * scaleRange) + 1);\n\n // Clamp values.\n if (heat < 1) {\n heat = 1;\n }\n\n if (heat > 6) {\n heat = 6;\n }\n\n return heat;\n };\n\n /**\n * Get the events to display in the calendar via ajax call.\n *\n * @method getEvents\n * @param {Number} year The year to get the events for.\n * @param {String} metric The type of metric to get, 'students' or 'assess'.\n * @param {Array} modules Array of the modules to get.\n * @return {Promise}\n */\n const getEvents = function({year, metric, modules}) {\n return new Promise((resolve, reject) => {\n let args = {\n year: year,\n metric: metric,\n modules: modules\n };\n let jsonArgs = JSON.stringify(args);\n\n // Get the events to use in the mapping.\n Ajax.call([{\n methodname: 'local_assessfreq_get_frequency',\n args: {\n jsondata: jsonArgs\n },\n }])[0].done((response) => {\n eventArray = JSON.parse(response);\n resolve(eventArray);\n }).fail(() => {\n reject(new Error('Failed to get events'));\n });\n });\n };\n\n /**\n * Get the events for a particular month and year.\n *\n * @param {Number} year The year to get the number of days for.\n * @param {Number} month The month to get the number of days for.\n * @return {Array} monthevents The events for the supplied month.\n */\n const getMonthEvents = function(year, month) {\n let monthevents;\n\n if ((typeof eventArray[year] !== \"undefined\") && (typeof eventArray[year][month] !== \"undefined\")) {\n monthevents = eventArray[year][month];\n }\n\n return monthevents;\n };\n\n /**\n * Create the table structure for the calendar months.\n *\n * @oaram {Number} year The year to generate the tables for.\n * @param {Number} startMonth The month to start table generation from.\n * @param {Number} endMonth The month to generate the tables to.\n * @return {Promise}\n */\n const createTables = function({year, startMonth, endMonth}) {\n return new Promise((resolve, reject) => {\n let calendarContainer = document.createElement('div');\n let month = startMonth;\n\n // Itterate through and build are tables.\n for (let i = startMonth; i <= endMonth; i++) {\n // Setup some elements.\n let container = document.createElement('div');\n container.classList.add('local-assessfreq-month');\n let table = document.createElement('table');\n table.classList.add('table-striped');\n let thead = document.createElement('thead');\n let tbody = document.createElement('tbody');\n tbody.id = 'calendar-body-' + i;\n let monthRow = document.createElement('tr');\n let dayrow = document.createElement('tr');\n let monthHeader = document.createElement('th');\n monthHeader.colSpan = 7;\n monthHeader.innerHTML = stringResult[(7 + month)];\n\n for (let j = 0; j < 7; j++) {\n let dayHeader = document.createElement('th');\n dayHeader.innerHTML = stringResult[j];\n dayrow.appendChild(dayHeader);\n }\n\n // Construct the table.\n monthRow.appendChild(monthHeader);\n\n thead.appendChild(monthRow);\n thead.appendChild(dayrow);\n\n table.appendChild(thead);\n table.appendChild(tbody);\n\n container.appendChild(table);\n\n // Add to parent.\n calendarContainer.appendChild(container);\n\n // Increment variables.\n month++;\n }\n\n if ((typeof year === 'undefined') || (typeof startMonth === 'undefined') || (typeof endMonth === 'undefined')) {\n reject(Error('Failed to create calendar tables.'));\n } else {\n const resultObj = {\n calendarContainer : calendarContainer,\n year : year,\n startMonth : startMonth\n };\n resolve(resultObj);\n }\n });\n };\n\n /**\n * Generate the tooltip HTML.\n *\n * @param {Object} dayArray The details of the events for that day/\n * @return {String} tipHTML The HTML for the tooltip.\n */\n const getTooltip = function(dayArray) {\n let tipHTML = '';\n\n for (let [key, value] of Object.entries(dayArray)) {\n tipHTML += '' + processModules[key] + ': ' + value + '
';\n }\n\n return tipHTML;\n };\n\n /**\n * Generate calendar markup for the month.\n *\n * @param {Object} table The base table to populate.\n * @param {Number} year The year to generate calendar for.\n * @param {Number} month The monthe to generate calendar for.\n */\n const populateCalendarDays = function(table, year, month) {\n let firstDay = (new Date(year, month)).getDay(); // Get the starting day of the month.\n let monthEvents = getMonthEvents(year, (month + 1)); // We add one due to month diferences between PHP and JS.\n let date = 1; // Creating all cells.\n\n for (let i = 0; i < 6; i++) {\n let row = document.createElement(\"tr\"); // Creates a table row.\n\n // Creating individual cells, filing them up with data.\n for (let j = 0; j < 7; j++) {\n if (i === 0 && j < firstDay) {\n var cell = document.createElement(\"td\");\n var cellText = document.createTextNode(\"\");\n cell.dataset.event = 'false';\n\n } else if (date > daysInMonth(month, year)) { // Break if we have generated all the days for this month.\n break;\n } else {\n cell = document.createElement(\"td\");\n cellText = document.createTextNode(date);\n if ((typeof monthEvents !== \"undefined\") && (monthEvents.hasOwnProperty(date))) {\n let heat = getHeat(monthEvents[date]['number']);\n\n if (heatRangeScale[heat] == 0 || heatRangeScale[heat] > monthEvents[date]['number']) {\n heatRangeScale[heat] = monthEvents[date]['number'];\n }\n\n cell.style.backgroundColor = colorArray[heat];\n cell.style.color = getContrast(colorArray[heat]);\n\n // Add tooltip to cell.\n cell.dataset.toggle = 'tooltip';\n cell.dataset.html = 'true';\n cell.dataset.event = 'true';\n cell.dataset.date = year + '-' + (month + 1) + '-' + date;\n cell.title = getTooltip(monthEvents[date]);\n cell.style.cursor = \"pointer\";\n\n }\n date++;\n }\n\n cell.appendChild(cellText);\n row.appendChild(cell);\n }\n table.appendChild(row); // Appending each row into calendar body.\n }\n };\n\n /**\n * Controls the population of the calendar in to the base tables.\n *\n * @param {Object} calendarContainer the container to populate.\n * @param {Number} year The year to generate calendar for.\n * @param {Number} startMonth The month to start generation from.\n * @return {Promise}\n */\n const populateCalendar = function({calendarContainer, year, startMonth}) {\n return new Promise((resolve, reject) => {\n // Get the table boodies.\n let tables = calendarContainer.getElementsByTagName(\"tbody\");\n let month = startMonth;\n\n // For each table body populate with calendar.\n for (var i = 0; i < tables.length; i++) {\n let table = tables[i];\n populateCalendarDays(table, year, month);\n month++;\n }\n\n if (typeof calendarContainer === 'undefined') {\n reject(Error('Failed to populate calendar tables.'));\n } else {\n resolve(calendarContainer);\n }\n });\n };\n\n /**\n * Create the heatmap scale for the calendar.\n *\n * @method createHeatScale\n */\n Calendar.createHeatScale = function() {\n return new Promise((resolve) => {\n let table = document.createElement('table');\n let tbody = document.createElement('tbody');\n let trow = document.createElement('tr');\n\n for (var i = 1; i < 7; i++) {\n if (heatRangeScale[i] !== 0) {\n let cell = document.createElement('td');\n let cellText = document.createTextNode(heatRangeScale[i] + '+');\n\n cell.appendChild(cellText);\n cell.style.backgroundColor = colorArray[i];\n cell.style.color = getContrast(colorArray[i]);\n\n trow.appendChild(cell);\n }\n\n }\n\n tbody.appendChild(trow);\n table.appendChild(tbody);\n\n // Reset heat range scale.\n heatRangeScale = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0};\n\n resolve(table);\n });\n };\n\n /**\n * Initialise method for report calendar heatmap creation.\n *\n * @param {Number} year The year to generate the heatmap for.\n * @param {Number} startMonth The month to start with for the heatmap calendar.\n * @param {Number} endMonth The month to end with for the heatmap calendar.\n * @param {String} metric The type of metric to display, 'students' or 'aseess'.\n * @param {Array} modules The modules to display in the heatamp.\n * @return {Promise}\n */\n Calendar.generate = function(year, startMonth, endMonth, metric, modules) {\n return new Promise((resolve, reject) => {\n const dateObj = {\n year : year,\n startMonth : startMonth,\n endMonth : endMonth\n };\n\n const eventObj = {\n year : year,\n metric : metric,\n modules : modules\n };\n\n Str.get_strings(stringArr).catch(() => { // Get required strings.\n Notification.exception(new Error('Failed to load strings'));\n return;\n }).then(stringReturn => { // Save string to global to be used later.\n stringResult = stringReturn;\n return eventObj;\n })\n .then(getEvents)\n .then((eventArray) => {\n calcHeatRange(eventArray, dateObj);\n })\n .then(getHeatColors)\n .then(getProcessModules)\n .then(() => {\n return dateObj;\n })\n .then(createTables) // Create tables for calendar.\n .then(populateCalendar)\n .then((calendarHTML) => { // Return the result of the generate function.\n if (typeof calendarHTML !== 'undefined') {\n resolve(calendarHTML);\n } else {\n reject(Error('Could not generate calendar'));\n }\n });\n });\n\n };\n\n return Calendar;\n});\n"],"file":"calendar.min.js"} \ No newline at end of file diff --git a/amd/build/chart_data.min.js b/amd/build/chart_data.min.js deleted file mode 100644 index d9f4d651..00000000 --- a/amd/build/chart_data.min.js +++ /dev/null @@ -1,2 +0,0 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("local_assessfreq/chart_data",["exports","core/fragment","core/notification","core/str","core/templates"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=a.getCardCharts=void 0;b=h(b);c=h(c);d=g(d);e=h(e);function f(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;f=function(){return a};return a}function g(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=f();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var g=d?Object.getOwnPropertyDescriptor(a,e):null;if(g&&(g.get||g.set)){Object.defineProperty(c,e,g)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function h(a){return a&&a.__esModule?a:{default:a}}var i,j,k,l,m=function(a,f,g){i.forEach(function(h){var i=document.getElementById(h.cardId),m=i.getElementsByClassName("overlay-icon-container")[0],n=i.getElementsByClassName("chart-body")[0],o={call:h.call};if(f){o.hoursahead=f[0];o.hoursbehind=f[1]}if(a){o.quiz=a}if(g){o.year=g}var p={data:JSON.stringify(o)};m.classList.remove("hide");b.default.loadFragment("local_assessfreq",k,j,p).done(function(a){var b=JSON.parse(a);if(!0===b.hasdata){var f={withtable:!0,chartdata:JSON.stringify(b.chart)};if("undefined"!=typeof h.aspect){f.aspect=h.aspect}e.default.render(l,f).done(function(a,b){m.classList.add("hide");e.default.replaceNodeContents(n,a,b)}).fail(function(){c.default.exception(new Error("Failed to load chart template."))})}else{d.get_string("nodata","local_assessfreq").then(function(a){var b=document.createElement("h3");b.innerHTML=a;n.innerHTML=b.outerHTML;m.classList.add("hide")}).catch(function(){c.default.exception(new Error("Failed to load string: nodata"))})}}).fail(function(){c.default.exception(new Error("Failed to load card."))})})};a.getCardCharts=m;a.init=function init(a,b,c,d){i=a;j=b;k=c;l=d}}); -//# sourceMappingURL=chart_data.min.js.map diff --git a/amd/build/chart_data.min.js.map b/amd/build/chart_data.min.js.map deleted file mode 100644 index 352ad826..00000000 --- a/amd/build/chart_data.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/chart_data.js"],"names":["cards","contextId","fragment","template","getCardCharts","quizId","hoursFilter","yearSelect","forEach","cardData","cardElement","document","getElementById","cardId","spinner","getElementsByClassName","chartBody","values","call","hoursahead","hoursbehind","quiz","year","params","JSON","stringify","classList","remove","Fragment","loadFragment","done","response","resObj","parse","hasdata","context","chart","aspect","Templates","render","html","js","add","replaceNodeContents","fail","Notification","exception","Error","Str","get_string","then","str","noDatastr","createElement","innerHTML","outerHTML","catch","init","cardsArray","contextIdChart","fragmentChart","templateChart"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Chart data JS module.\n *\n * @module local_assessfreq/char_data\n * @package local_assessfreq\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Fragment from 'core/fragment';\nimport Notification from 'core/notification';\nimport * as Str from 'core/str';\nimport Templates from 'core/templates';\n\n/**\n * Module level variables.\n */\nlet cards;\nlet contextId;\nlet fragment;\nlet template;\n\n/**\n * For each of the cards on the dashboard get their corresponding chart data.\n * Data is based on the year variable from the corresponding dropdown.\n * Chart data is loaded via ajax.\n *\n * @param {int|null} quizId The quiz Id.\n * @param {array|null} hoursFilter Array with hour ahead or behind preference.\n * @param {int|null} yearSelect Year selected.\n */\nexport const getCardCharts = (quizId, hoursFilter, yearSelect) => {\n cards.forEach((cardData) => {\n let cardElement = document.getElementById(cardData.cardId);\n let spinner = cardElement.getElementsByClassName('overlay-icon-container')[0];\n let chartBody = cardElement.getElementsByClassName('chart-body')[0];\n let values = {'call': cardData.call};\n // Add values to Object depending on dashboard type.\n if (hoursFilter) {\n values.hoursahead = hoursFilter[0];\n values.hoursbehind = hoursFilter[1];\n }\n if (quizId) {\n values.quiz = quizId;\n }\n if (yearSelect) {\n values.year = yearSelect;\n }\n let params = {'data': JSON.stringify(values)};\n\n spinner.classList.remove('hide'); // Show sinner if not already shown.\n Fragment.loadFragment('local_assessfreq', fragment, contextId, params)\n .done((response) => {\n let resObj = JSON.parse(response);\n if (resObj.hasdata === true) {\n let context = {\n 'withtable': true, 'chartdata': JSON.stringify(resObj.chart)\n };\n if (typeof cardData.aspect !== 'undefined') {\n context.aspect = cardData.aspect;\n }\n Templates.render(template, context).done((html, js) => {\n spinner.classList.add('hide'); // Hide spinner if not already hidden.\n // Load card body.\n Templates.replaceNodeContents(chartBody, html, js);\n }).fail(() => {\n Notification.exception(new Error('Failed to load chart template.'));\n return;\n });\n return;\n } else {\n Str.get_string('nodata', 'local_assessfreq').then((str) => {\n const noDatastr = document.createElement('h3');\n noDatastr.innerHTML = str;\n chartBody.innerHTML = noDatastr.outerHTML;\n spinner.classList.add('hide'); // Hide spinner if not already hidden.\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: nodata'));\n });\n }\n }).fail(() => {\n Notification.exception(new Error('Failed to load card.'));\n return;\n });\n });\n};\n\n/**\n * Initialise method for table handler.\n *\n * @param {array} cardsArray Cards array.\n * @param {int} contextIdChart The context id.\n * @param {string} fragmentChart Fragment name.\n * @param {string} templateChart Template name.\n */\nexport const init = (cardsArray, contextIdChart, fragmentChart, templateChart) => {\n cards = cardsArray;\n contextId = contextIdChart;\n fragment = fragmentChart;\n template = templateChart;\n};\n"],"file":"chart_data.min.js"} \ No newline at end of file diff --git a/amd/build/chart_output_chartjs.min.js b/amd/build/chart_output_chartjs.min.js deleted file mode 100644 index bbf31b5f..00000000 --- a/amd/build/chart_output_chartjs.min.js +++ /dev/null @@ -1,2 +0,0 @@ -define ("local_assessfreq/chart_output_chartjs",["core/chart_output_chartjs"],function(a){var b={},c=!1,d=!1;a.prototype._makeConfig=function(){var a={type:this._getChartType(),data:{labels:this._cleanData(this._chart.getLabels()),datasets:this._makeDatasetsConfig()},options:{title:{display:null!==this._chart.getTitle(),text:this._cleanData(this._chart.getTitle())}}},b=this._chart.getLegendOptions();if(b){a.options.legend=b}if(d){a.options.legend=d}this._chart.getXAxes().forEach(function(b,c){var d=b.getLabels();a.options.scales=a.options.scales||{};a.options.scales.xAxes=a.options.scales.xAxes||[];a.options.scales.xAxes[c]=this._makeAxisConfig(b,"x",c);if(null!==d){a.options.scales.xAxes[c].ticks.callback=function(a,b){return d[b]||""}}a.options.scales.xAxes[c].stacked=this._isStacked()}.bind(this));this._chart.getYAxes().forEach(function(b,c){var d=b.getLabels();a.options.scales=a.options.scales||{};a.options.scales.yAxes=a.options.scales.yAxes||[];a.options.scales.yAxes[c]=this._makeAxisConfig(b,"y",c);if(null!==d){a.options.scales.yAxes[c].ticks.callback=function(a){return d[parseInt(a,10)]||""}}a.options.scales.yAxes[c].stacked=this._isStacked()}.bind(this));a.options.tooltips={callbacks:{label:this._makeTooltip.bind(this)}};a.options.maintainAspectRatio=c;return a};b.init=function(b,e,f,g){c=f;d=g;new a(b,e)};return b}); -//# sourceMappingURL=chart_output_chartjs.min.js.map diff --git a/amd/build/chart_output_chartjs.min.js.map b/amd/build/chart_output_chartjs.min.js.map deleted file mode 100644 index 37919818..00000000 --- a/amd/build/chart_output_chartjs.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/chart_output_chartjs.js"],"names":["define","Output","ChartOutput","aspectRatio","rtLegendoptions","prototype","_makeConfig","config","type","_getChartType","data","labels","_cleanData","_chart","getLabels","datasets","_makeDatasetsConfig","options","title","display","getTitle","text","legendOptions","getLegendOptions","legend","getXAxes","forEach","axis","i","axisLabels","scales","xAxes","_makeAxisConfig","ticks","callback","value","index","stacked","_isStacked","bind","getYAxes","yAxes","parseInt","tooltips","callbacks","label","_makeTooltip","maintainAspectRatio","init","chartImage","ChartInst","aspect"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Chart output for chart.js with custom override for aspect config.\n *\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['core/chart_output_chartjs'], function(Output) {\n\n /**\n * Module level variables.\n */\n var ChartOutput = {};\n var aspectRatio = false;\n var rtLegendoptions = false;\n\n /**\n * Overrride the config.\n *\n * @protected\n * @param {module:core/chart_axis} axis The axis.\n * @return {Object} The axis config.\n */\n Output.prototype._makeConfig = function() {\n var config = {\n type: this._getChartType(),\n data: {\n labels: this._cleanData(this._chart.getLabels()),\n datasets: this._makeDatasetsConfig()\n },\n options: {\n title: {\n display: this._chart.getTitle() !== null,\n text: this._cleanData(this._chart.getTitle())\n }\n }\n };\n var legendOptions = this._chart.getLegendOptions();\n if (legendOptions) {\n config.options.legend = legendOptions;\n }\n\n // Override legend options with those provided at run time.\n if (rtLegendoptions) {\n config.options.legend = rtLegendoptions;\n }\n\n this._chart.getXAxes().forEach(function(axis, i) {\n var axisLabels = axis.getLabels();\n\n config.options.scales = config.options.scales || {};\n config.options.scales.xAxes = config.options.scales.xAxes || [];\n config.options.scales.xAxes[i] = this._makeAxisConfig(axis, 'x', i);\n\n if (axisLabels !== null) {\n config.options.scales.xAxes[i].ticks.callback = function(value, index) {\n return axisLabels[index] || '';\n };\n }\n config.options.scales.xAxes[i].stacked = this._isStacked();\n }.bind(this));\n\n this._chart.getYAxes().forEach(function(axis, i) {\n var axisLabels = axis.getLabels();\n\n config.options.scales = config.options.scales || {};\n config.options.scales.yAxes = config.options.scales.yAxes || [];\n config.options.scales.yAxes[i] = this._makeAxisConfig(axis, 'y', i);\n\n if (axisLabels !== null) {\n config.options.scales.yAxes[i].ticks.callback = function(value) {\n return axisLabels[parseInt(value, 10)] || '';\n };\n }\n config.options.scales.yAxes[i].stacked = this._isStacked();\n }.bind(this));\n\n config.options.tooltips = {\n callbacks: {\n label: this._makeTooltip.bind(this)\n }\n };\n\n config.options.maintainAspectRatio = aspectRatio;\n\n return config;\n };\n\n /**\n * Get the aspect ratio setting and initialise the chart.\n */\n ChartOutput.init = function(chartImage, ChartInst, aspect, legend) {\n aspectRatio = aspect;\n rtLegendoptions = legend;\n new Output(chartImage, ChartInst);\n };\n\n return ChartOutput;\n\n});\n"],"file":"chart_output_chartjs.min.js"} \ No newline at end of file diff --git a/amd/build/course_selector.min.js b/amd/build/course_selector.min.js index 396c4f30..a69f54f1 100644 --- a/amd/build/course_selector.min.js +++ b/amd/build/course_selector.min.js @@ -1,2 +1,12 @@ -define ("local_assessfreq/course_selector",["core/ajax","core/notification"],function(a,b){return{transport:function(c,d,e){a.call([{methodname:"local_assessfreq_get_courses",args:{query:d}}])[0].then(function(a){var b=JSON.parse(a);e(b)}).fail(function(){b.exception(new Error("Failed to get events"))})},processResults:function(a,b){var c=[];b.forEach(function(a){c.push({value:a.id,label:a.fullname})});return c}}}); -//# sourceMappingURL=course_selector.min.js.map +/** + * Frameworks datasource. + * + * This module is compatible with core/form-autocomplete. + * + * @packagetool_lpmigrate + * @copyright2016 Frédéric Massart - FMCorz.net + * @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define("local_assessfreq/course_selector",["core/ajax","core/notification"],(function(Ajax,Notification){let CourseSelector={transport:function(selector,query,callback){Ajax.call([{methodname:"local_assessfreq_get_courses",args:{query:query}}])[0].then((response=>{let courseArray=JSON.parse(response);callback(courseArray)})).fail((()=>{Notification.exception(new Error("Failed to get events"))}))},processResults:function(selector,results){let options=[];return results.forEach((element=>{options.push({value:element.id,label:element.fullname})})),options}};return CourseSelector})); + +//# sourceMappingURL=course_selector.min.js.map \ No newline at end of file diff --git a/amd/build/course_selector.min.js.map b/amd/build/course_selector.min.js.map index 935ac513..6db776df 100644 --- a/amd/build/course_selector.min.js.map +++ b/amd/build/course_selector.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/course_selector.js"],"names":["define","Ajax","Notification","transport","selector","query","callback","call","methodname","args","then","response","courseArray","JSON","parse","fail","exception","Error","processResults","results","options","forEach","element","push","value","id","label","fullname"],"mappings":"AAyBAA,OAAM,oCAAC,CAAC,WAAD,CAAc,mBAAd,CAAD,CAAqC,SAASC,CAAT,CAAeC,CAAf,CAA6B,CAgDpE,MA3CqB,CAUNC,SAVM,CAUM,SAASC,CAAT,CAAmBC,CAAnB,CAA0BC,CAA1B,CAAoC,CAC3DL,CAAI,CAACM,IAAL,CAAU,CAAC,CACPC,UAAU,CAAE,8BADL,CAEPC,IAAI,CAAE,CACFJ,KAAK,CAAEA,CADL,CAFC,CAAD,CAAV,EAKI,CALJ,EAKOK,IALP,CAKY,SAACC,CAAD,CAAc,CACtB,GAAIC,CAAAA,CAAW,CAAGC,IAAI,CAACC,KAAL,CAAWH,CAAX,CAAlB,CACAL,CAAQ,CAACM,CAAD,CACX,CARD,EAQGG,IARH,CAQQ,UAAM,CACVb,CAAY,CAACc,SAAb,CAAuB,GAAIC,CAAAA,KAAJ,CAAU,sBAAV,CAAvB,CACH,CAVD,CAWH,CAtBoB,CA+BNC,cA/BM,CA+BW,SAASd,CAAT,CAAmBe,CAAnB,CAA4B,CACxD,GAAIC,CAAAA,CAAO,CAAG,EAAd,CACAD,CAAO,CAACE,OAAR,CAAgB,SAACC,CAAD,CAAa,CACzBF,CAAO,CAACG,IAAR,CAAa,CACTC,KAAK,CAAEF,CAAO,CAACG,EADN,CAETC,KAAK,CAAEJ,CAAO,CAACK,QAFN,CAAb,CAIH,CALD,EAOA,MAAOP,CAAAA,CACV,CAzCoB,CA4CxB,CAjDK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.If not, see .\n\n/**\n * Frameworks datasource.\n *\n * This module is compatible with core/form-autocomplete.\n *\n * @packagetool_lpmigrate\n * @copyright2016 Frédéric Massart - FMCorz.net\n * @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/ajax', 'core/notification'], function(Ajax, Notification) {\n\n /**\n * Module level variables.\n */\n var CourseSelector = {};\n\n /**\n * Source of data for Ajax element.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {String} query The query string.\n * @param {Function} callback A callback function receiving an array of results.\n * @return {Void}\n */\n CourseSelector.transport = function(selector, query, callback) {\n Ajax.call([{\n methodname: 'local_assessfreq_get_courses',\n args: {\n query: query\n },\n }])[0].then((response) => {\n let courseArray = JSON.parse(response);\n callback(courseArray);\n }).fail(() => {\n Notification.exception(new Error('Failed to get events'));\n });\n };\n\n /**\n * Process the results for auto complete elements.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {Array} results An array or results.\n * @return {Array} New array of results.\n */\n CourseSelector.processResults = function(selector, results) {\n let options = [];\n results.forEach((element) => {\n options.push({\n value: element.id,\n label: element.fullname\n });\n });\n\n return options;\n };\n\n return CourseSelector;\n});\n"],"file":"course_selector.min.js"} \ No newline at end of file +{"version":3,"file":"course_selector.min.js","sources":["../src/course_selector.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.If not, see .\n\n/**\n * Frameworks datasource.\n *\n * This module is compatible with core/form-autocomplete.\n *\n * @packagetool_lpmigrate\n * @copyright2016 Frédéric Massart - FMCorz.net\n * @licensehttp://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/ajax', 'core/notification'], function (Ajax, Notification) {\n\n /**\n * Module level variables.\n */\n let CourseSelector = {};\n\n /**\n * Source of data for Ajax element.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {String} query The query string.\n * @param {Function} callback A callback function receiving an array of results.\n */\n CourseSelector.transport = function(selector, query, callback) {\n Ajax.call([{\n methodname: 'local_assessfreq_get_courses',\n args: {\n query: query\n },\n }])[0].then((response) => {\n let courseArray = JSON.parse(response);\n // eslint-disable-next-line promise/no-callback-in-promise\n callback(courseArray);\n }).fail(() => {\n Notification.exception(new Error('Failed to get events'));\n });\n };\n\n /**\n * Process the results for auto complete elements.\n *\n * @param {String} selector The selector of the auto complete element.\n * @param {Array} results An array or results.\n * @return {Array} New array of results.\n */\n CourseSelector.processResults = function (selector, results) {\n let options = [];\n results.forEach((element) => {\n options.push({\n value: element.id,\n label: element.fullname\n });\n });\n\n return options;\n };\n\n return CourseSelector;\n});\n"],"names":["define","Ajax","Notification","CourseSelector","selector","query","callback","call","methodname","args","then","response","courseArray","JSON","parse","fail","exception","Error","results","options","forEach","element","push","value","id","label","fullname"],"mappings":";;;;;;;;;AAyBAA,0CAAO,CAAC,YAAa,sBAAsB,SAAUC,KAAMC,kBAKnDC,eAAiB,CASrBA,UAA2B,SAASC,SAAUC,MAAOC,UACjDL,KAAKM,KAAK,CAAC,CACPC,WAAY,+BACZC,KAAM,CACFJ,MAAOA,UAEX,GAAGK,MAAMC,eACLC,YAAcC,KAAKC,MAAMH,UAE7BL,SAASM,gBACVG,MAAK,KACJb,aAAac,UAAU,IAAIC,MAAM,6BAWzCd,eAAgC,SAAUC,SAAUc,aAC5CC,QAAU,UACdD,QAAQE,SAASC,UACbF,QAAQG,KAAK,CACTC,MAAOF,QAAQG,GACfC,MAAOJ,QAAQK,cAIhBP,iBAGJhB"} \ No newline at end of file diff --git a/amd/build/dashboard.min.js b/amd/build/dashboard.min.js new file mode 100644 index 00000000..45562ff9 --- /dev/null +++ b/amd/build/dashboard.min.js @@ -0,0 +1,3 @@ +define("local_assessfreq/dashboard",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;_exports.init=()=>{tabs()};const tabs=()=>{document.getElementsByClassName("tablinks").forEach((el=>el.addEventListener("click",(event=>{let target=event.target.dataset.target,tabcontent=document.getElementsByClassName("tabcontent");for(let i=0;i1?urlParts[1]:null;anchor&&null!==document.querySelector('[data-target="tab-'+anchor+'"]')?document.querySelector('[data-target="tab-'+anchor+'"]').click():document.querySelector('[data-target="tab-heatmap"]').click()}})); + +//# sourceMappingURL=dashboard.min.js.map \ No newline at end of file diff --git a/amd/build/dashboard.min.js.map b/amd/build/dashboard.min.js.map new file mode 100644 index 00000000..2f8e0917 --- /dev/null +++ b/amd/build/dashboard.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"dashboard.min.js","sources":["../src/dashboard.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Chart data JS module.\n *\n * @module local_assessfreq/dashboard\n * @package\n * @copyright Simon Thornett \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const init = () => {\n\n // Load the tab cuntionality.\n tabs();\n\n};\n\nconst tabs = () => {\n\n const tabcontent = document.getElementsByClassName(\"tablinks\");\n\n tabcontent.forEach(el => el.addEventListener('click', event => {\n let target = event.target.dataset.target;\n\n let tabcontent = document.getElementsByClassName(\"tabcontent\");\n for (let i = 0; i < tabcontent.length; i++) {\n tabcontent[i].style.display = \"none\";\n }\n\n // Get all elements with class=\"tablinks\" and remove the class \"active\"\n let tablinks = document.getElementsByClassName(\"tablinks\");\n for (let i = 0; i < tablinks.length; i++) {\n tablinks[i].className = tablinks[i].className.replace(\" active\", \"\");\n }\n\n // Show the current tab, and add an \"active\" class to the button that opened the tab\n document.getElementById(target).style.display = \"block\";\n event.currentTarget.className += \" active\";\n }));\n\n const currentUrl = document.URL;\n const urlParts = currentUrl.split('#');\n\n const anchor = (urlParts.length > 1) ? urlParts[1] : null;\n // First tab should be open by default unless we have an anchor.\n if (!anchor || document.querySelector('[data-target=\"tab-' + anchor + '\"]') === null) {\n document.querySelector('[data-target=\"tab-heatmap\"]').click();\n } else {\n document.querySelector('[data-target=\"tab-' + anchor + '\"]').click();\n }\n};\n"],"names":["tabs","document","getElementsByClassName","forEach","el","addEventListener","event","target","dataset","tabcontent","i","length","style","display","tablinks","className","replace","getElementById","currentTarget","urlParts","URL","split","anchor","querySelector","click"],"mappings":"+JAwBoB,KAGhBA,cAIEA,KAAO,KAEUC,SAASC,uBAAuB,YAExCC,SAAQC,IAAMA,GAAGC,iBAAiB,SAASC,YAC9CC,OAASD,MAAMC,OAAOC,QAAQD,OAE9BE,WAAaR,SAASC,uBAAuB,kBAC5C,IAAIQ,EAAI,EAAGA,EAAID,WAAWE,OAAQD,IACnCD,WAAWC,GAAGE,MAAMC,QAAU,WAI9BC,SAAWb,SAASC,uBAAuB,gBAC1C,IAAIQ,EAAI,EAAGA,EAAII,SAASH,OAAQD,IACjCI,SAASJ,GAAGK,UAAYD,SAASJ,GAAGK,UAAUC,QAAQ,UAAW,IAIrEf,SAASgB,eAAeV,QAAQK,MAAMC,QAAU,QAChDP,MAAMY,cAAcH,WAAa,qBAI/BI,SADalB,SAASmB,IACAC,MAAM,KAE5BC,OAAUH,SAASR,OAAS,EAAKQ,SAAS,GAAK,KAEhDG,QAA2E,OAAjErB,SAASsB,cAAc,qBAAuBD,OAAS,MAGlErB,SAASsB,cAAc,qBAAuBD,OAAS,MAAME,QAF7DvB,SAASsB,cAAc,+BAA+BC"} \ No newline at end of file diff --git a/amd/build/dashboard_assessment.min.js b/amd/build/dashboard_assessment.min.js deleted file mode 100644 index 5747a610..00000000 --- a/amd/build/dashboard_assessment.min.js +++ /dev/null @@ -1,2 +0,0 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("local_assessfreq/dashboard_assessment",["exports","core/notification","local_assessfreq/calendar","local_assessfreq/chart_data","local_assessfreq/dayview","local_assessfreq/user_preferences","local_assessfreq/zoom_modal"],function(a,b,c,d,e,f,g){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=j(b);c=j(c);d=i(d);e=j(e);f=i(f);g=j(g);function h(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;h=function(){return a};return a}function i(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=h();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function j(a){return a&&a.__esModule?a:{default:a}}var k,l,m,n,o,p="",q="",r=[{cardId:"local-assessfreq-assess-due-month",call:"assess_by_month"},{cardId:"local-assessfreq-assess-by-activity",call:"assess_by_activity"},{cardId:"local-assessfreq-assess-due-month-student",call:"assess_by_month_student"}],s=function(a){a.preventDefault();var b=a.target;if("a"===b.tagName.toLowerCase()&&b.dataset.year!==l){l=b.dataset.year;f.setUserPreference("local_assessfreq_overview_year_preference",l);var c=document.getElementById("local-assessfreq-report-overview").getElementsByClassName("local-assessfreq-year")[0];c.innerHTML=l;d.getCardCharts(0,null,l)}},t=function(){clearTimeout(o);o=setTimeout(x(),750)},u=function(a){var b=a.target;if("td"===b.tagName.toLowerCase()&&"true"===b.dataset.event){e.default.display(b.dataset.date)}},v=function(){var a=JSON.parse(q),d=parseInt(a.year),e=a.metric,f=a.modules,g=document.getElementById("local-assessfreq-report-heatmap"),h=g.getElementsByClassName("overlay-icon-container")[0];h.classList.remove("hide");c.default.generate(d,0,11,e,f).then(function(a){var b=document.getElementById("local-assessfreq-report-heatmap-months");b.innerHTML=a.innerHTML;b.addEventListener("click",u)}).then(c.default.createHeatScale).then(function(a){var b=document.getElementById("local-assessfreq-report-heatmap-scale");b.innerHTML=a.outerHTML;h.classList.add("hide")}).catch(function(){b.default.exception(new Error("Failed to calendar."))})},w=function(a){var b=a.year,c=a.metric,d=a.modules,e=document.getElementById("local-assessfreq-heatmap-form"),f=e.elements,g=[];if(0===d.length){d=["all"]}for(var l=0;l.\n\n/**\n * Javascript for report card display and processing.\n *\n * @module local_assessfreq/dashboard_assessment\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Notification from 'core/notification';\nimport Calendar from 'local_assessfreq/calendar';\nimport * as ChartData from 'local_assessfreq/chart_data';\nimport Dayview from 'local_assessfreq/dayview';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\nimport ZoomModal from 'local_assessfreq/zoom_modal';\n\n/**\n * Module level variables.\n */\nvar contextid;\nvar yearselect;\nvar yearselectheatmap;\nvar metricselectheatmap;\nvar timeout;\nvar modulesJson = '';\nvar heatmapOptionsJson = '';\n\nconst cards = [\n {cardId: 'local-assessfreq-assess-due-month', call: 'assess_by_month'},\n {cardId: 'local-assessfreq-assess-by-activity', call: 'assess_by_activity'},\n {cardId: 'local-assessfreq-assess-due-month-student', call: 'assess_by_month_student'}\n];\n\n/**\n * Get and process the selected year from the dropdown,\n * and update the corresponding user perference.\n *\n * @param {event} event The triggered event for the element.\n */\nconst yearButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.year !== yearselect) { // Only act on certain elements.\n yearselect = element.dataset.year;\n\n // Save selection as a user preference.\n UserPreference.setUserPreference('local_assessfreq_overview_year_preference', yearselect);\n\n // Update card data based on selected year.\n var yeartitle = document.getElementById('local-assessfreq-report-overview')\n .getElementsByClassName('local-assessfreq-year')[0];\n yeartitle.innerHTML = yearselect;\n\n ChartData.getCardCharts(0, null, yearselect); // Process loading for the assessment cards.\n }\n};\n\n/**\n * Quick and dirty debounce method for the heatmap settings menu.\n * This stops the ajax method that updates the heatmap from being updated\n * while the user is still checking options.\n *\n */\nconst updateHeatmapDebounce = () => {\n clearTimeout(timeout);\n timeout = setTimeout(updateHeatmap(), 750);\n};\n\n/**\n * Display heatmap calendar.\n *\n * @param {event} event The triggered event for the element.\n */\nconst detailView = (event) => {\n let element = event.target;\n if (element.tagName.toLowerCase() === 'td' && element.dataset.event === 'true') { // Only act on certain elements.\n Dayview.display(element.dataset.date);\n }\n};\n\n/**\n * Start heatmap generation.\n *\n */\nconst generateHeatmap = () => {\n let heatmapOptions = JSON.parse(heatmapOptionsJson);\n let year = parseInt(heatmapOptions.year);\n let metric = heatmapOptions.metric;\n let modules = heatmapOptions.modules;\n let heatmapContainer = document.getElementById('local-assessfreq-report-heatmap');\n let spinner = heatmapContainer.getElementsByClassName('overlay-icon-container')[0];\n\n spinner.classList.remove('hide'); // Show spinner if not already shown.\n\n Calendar.generate(year, 0, 11, metric, modules)\n .then(calendar => {\n let calendarContainer = document.getElementById('local-assessfreq-report-heatmap-months');\n calendarContainer.innerHTML = calendar.innerHTML;\n calendarContainer.addEventListener('click', detailView);\n })\n .then(Calendar.createHeatScale)\n .then((heatScale) => {\n let heatScaleContainer = document.getElementById('local-assessfreq-report-heatmap-scale');\n heatScaleContainer.innerHTML = heatScale.outerHTML;\n spinner.classList.add('hide'); // Hide sinner if not already hidden.\n })\n .catch(() => {\n Notification.exception(new Error('Failed to calendar.'));\n return;\n });\n};\n\nconst updateDownload = ({year, metric, modules}) => {\n let downloadForm = document.getElementById('local-assessfreq-heatmap-form');\n let formElements = downloadForm.elements;\n let toRemove = new Array();\n\n if (modules.length === 0) {\n modules = ['all'];\n }\n\n for (let i = 0; i < formElements.length; i++) {\n if (formElements[i] === undefined) {\n continue;\n }\n // Update year field.\n if ((formElements[i].type === 'hidden') && (formElements[i].name === 'year')) {\n formElements[i].value = year;\n continue;\n }\n\n // Update metric field.\n if ((formElements[i].type === 'hidden') && (formElements[i].name === 'metric')) {\n formElements[i].value = metric;\n continue;\n }\n\n // Update module fields.\n if ((formElements[i].type === 'hidden') && (formElements[i].name.startsWith('modules'))) {\n toRemove.push(formElements[i]);\n continue;\n }\n }\n\n for (const element of toRemove) {\n element.remove();\n }\n\n for (let i = 0; i < modules.length; i++) {\n let input = document.createElement('input');\n input.type = 'hidden';\n input.name = 'modules[' + modules[i] + ']';\n input.value = modules[i];\n\n downloadForm.appendChild(input);\n }\n};\n\n/**\n * Update the heatmap based on the current filter settings.\n *\n */\nconst updateHeatmap = () => {\n // Get current state of select menu items.\n var cardsModulesSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-modules');\n var links = cardsModulesSelectHeatmapElement.getElementsByTagName('a');\n var modules = [];\n\n for (var i = 0; i < links.length; i++) {\n if (links[i].classList.contains('active')) {\n let module = links[i].dataset.module;\n modules.push(module);\n }\n }\n\n // Save selection as a user preference.\n if (modulesJson !== JSON.stringify(modules)) {\n modulesJson = JSON.stringify(modules);\n UserPreference.setUserPreference('local_assessfreq_heatmap_modules_preference', modulesJson);\n }\n\n // Build settings object.\n var optionsObj = {\n 'year': yearselectheatmap,\n 'metric': metricselectheatmap,\n 'modules': modules\n };\n\n var optionsJson = JSON.stringify(optionsObj);\n\n if (optionsJson !== heatmapOptionsJson) { // Compare to global to see if there are any changes.\n // If list has changed fetch heatmap and update user preference.\n heatmapOptionsJson = optionsJson;\n generateHeatmap();\n\n // Update the download options.\n updateDownload(optionsObj);\n }\n};\n\n/**\n * Get and process the selected year from the dropdown for the heatmap display,\n * and update the corresponding user preference.\n *\n * @param {event} event The triggered event for the element.\n */\nconst yearHeatmapButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.year !== yearselectheatmap) { // Only act on certain elements.\n yearselectheatmap = element.dataset.year;\n\n // Save selection as a user preference.\n UserPreference.setUserPreference('local_assessfreq_heatmap_year_preference', yearselectheatmap);\n\n // Update card data based on selected year.\n var yeartitle = document.getElementById('local-assessfreq-report-heatmap')\n .getElementsByClassName('local-assessfreq-year')[0];\n yeartitle.innerHTML = yearselectheatmap;\n\n updateHeatmapDebounce(); // Call function to update heatmap.\n }\n};\n\n/**\n * Get and process the selected assessment metric from the dropdown for the heatmap display,\n * and update the corresponding user preference.\n *\n * @param {event} event The triggered event for the element.\n */\nconst metricHeatmapButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.metric !== metricselectheatmap) {\n metricselectheatmap = element.dataset.metric;\n\n // Save selection as a user preference.\n UserPreference.setUserPreference('local_assessfreq_heatmap_metric_preference', metricselectheatmap);\n\n updateHeatmapDebounce(); // Call function to update heatmap.\n }\n};\n\n/**\n * Add the event listeners to the modules in the module select dropdown.\n *\n * @param {Object} element The dropdown HTML element that contains the list of modules as links.\n */\nconst moduleListChildrenEvents = (element) => {\n var links = element.getElementsByTagName('a');\n var all = links[0];\n\n for (var i = 0; i < links.length; i++) {\n let module = links[i].dataset.module;\n\n if (module.toLowerCase() === 'all') {\n links[i].addEventListener('click', function(event){\n event.preventDefault();\n // Remove active class from all other links.\n for (var j = 0; j < links.length; j++) {\n links[j].classList.remove('active');\n }\n updateHeatmapDebounce(); // Call function to update heatmap.\n });\n } else if (module.toLowerCase() === 'close') {\n links[i].addEventListener('click', function(event){\n event.preventDefault();\n event.stopPropagation();\n\n var dropdownmenu = document.getElementById('local-assessfreq-heatmap-modules-filter');\n dropdownmenu.classList.remove('show');\n\n updateHeatmapDebounce(); // Call function to update heatmap.\n });\n\n } else {\n links[i].addEventListener('click', function(event){\n event.preventDefault();\n event.stopPropagation();\n\n all.classList.remove('active');\n\n event.target.classList.toggle('active');\n updateHeatmapDebounce();\n });\n }\n\n }\n};\n\n/**\n * Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst triggerZoomGraph = (event) => {\n let call = event.target.closest('div').dataset.call;\n let params = {'data': JSON.stringify({'year': yearselect, 'call': call})};\n let method = 'get_chart';\n\n ZoomModal.zoomGraph(event, params, method);\n};\n\n/**\n * Initialise method for report card rendering.\n *\n * @param {integer} context The current context id.\n */\nexport const init = (context) => {\n contextid = context;\n\n // Set up event listener and related actions for year dropdown on report cards.\n let cardsYearSelectElement = document.getElementById('local-assessfreq-cards-year');\n yearselect = cardsYearSelectElement.getElementsByClassName('active')[0].dataset.year;\n cardsYearSelectElement.addEventListener('click', yearButtonAction);\n\n // Set up event listener and related actions for year dropdown on heatmp.\n let cardsYearSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-year');\n yearselectheatmap = cardsYearSelectHeatmapElement.getElementsByClassName('active')[0].dataset.year;\n cardsYearSelectHeatmapElement.addEventListener('click', yearHeatmapButtonAction);\n\n // Set up event listener and related actions for metric dropdown on heatmp.\n let cardsMetricSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-metrics');\n metricselectheatmap = cardsMetricSelectHeatmapElement.getElementsByClassName('active')[0].dataset.metric;\n cardsMetricSelectHeatmapElement.addEventListener('click', metricHeatmapButtonAction);\n\n // Set up event listener and related actions for module dropdown on heatmp.\n let cardsModulesSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-modules');\n moduleListChildrenEvents(cardsModulesSelectHeatmapElement);\n\n // Set up zoom event listeners.\n let dueMonthZoom = document.getElementById('local-assessfreq-assess-due-month-zoom');\n dueMonthZoom.addEventListener('click', triggerZoomGraph);\n\n let dueActivityZoom = document.getElementById('local-assessfreq-assess-by-activity-zoom');\n dueActivityZoom.addEventListener('click', triggerZoomGraph);\n\n let dueStudentZoom = document.getElementById('local-assessfreq-assess-due-month-student-zoom');\n dueStudentZoom.addEventListener('click', triggerZoomGraph);\n\n // Create the zoom modal.\n ZoomModal.init(context);\n\n // Setup the dayview modal.\n Dayview.init();\n\n // Setup the chart data for each card.\n ChartData.init(cards, contextid, 'get_chart', 'core/chart');\n\n // Process loading for the assessment cards.\n ChartData.getCardCharts(0, null, yearselect);\n\n // Get the data for the heatmap.\n updateHeatmap();\n\n};\n"],"file":"dashboard_assessment.min.js"} \ No newline at end of file diff --git a/amd/build/dashboard_quiz.min.js b/amd/build/dashboard_quiz.min.js deleted file mode 100644 index 544d244b..00000000 --- a/amd/build/dashboard_quiz.min.js +++ /dev/null @@ -1,2 +0,0 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("local_assessfreq/dashboard_quiz",["exports","core/ajax","core/notification","core/str","core/templates","local_assessfreq/chart_data","local_assessfreq/form_modal","local_assessfreq/override_modal","local_assessfreq/table_handler","local_assessfreq/user_preferences","local_assessfreq/zoom_modal"],function(a,b,c,d,e,f,g,h,i,j,k){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=n(b);c=n(c);d=m(d);e=n(e);f=m(f);g=m(g);h=n(h);i=m(i);j=m(j);k=m(k);function l(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;l=function(){return a};return a}function m(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=l();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function n(a){return a&&a.__esModule?a:{default:a}}var o="",p,q=0,r=60,s,t=[{cardId:"local-assessfreq-quiz-summary-graph",call:"participant_summary",aspect:!0},{cardId:"local-assessfreq-quiz-summary-trend",call:"participant_trend",aspect:!1}],u=function(){var a=0";g.innerHTML=b.name+" ";g.appendChild(s);var t=new URL(window.location.href),v=t.origin+t.pathname+"?id="+q;history.pushState({},"",v);d.get_string("dashboard:quiztitle","local_assessfreq",{quiz:b.name,course:b.courseshortname}).then(function(a){document.title=a}).catch(function(){c.default.exception(new Error("Failed to load string: dashboard:quiztitle"))});e.default.render("local_assessfreq/quiz-summary-card-content",b).done(function(a){l.classList.add("hide");var b=document.getElementById("local-assessfreq-quiz-summary-card-content");e.default.replaceNodeContents(b,a,"")}).fail(function(){c.default.exception(new Error("Failed to load quiz summary template."))});h.classList.remove("hide");j.classList.remove("hide");m.classList.remove("hide");n.classList.remove("hide");f.getCardCharts(q);i.getTable(q);u();o.addEventListener("keyup",i.tableSearch);o.addEventListener("paste",i.tableSearch);p.addEventListener("click",i.tableSearchReset);r.addEventListener("click",i.tableSearchRowSet)}).fail(function(){c.default.exception(new Error("Failed to get quiz data"))})},w=function(a){a.preventDefault();var b=a.target;if(null!==b.closest("button")&&"local-assessfreq-refresh-quiz-dashboard"===b.closest("button").id){u(!0);v(q)}else if("a"===b.tagName.toLowerCase()){r=b.dataset.period;u(!0);j.setUserPreference("local_assessfreq_quiz_refresh_preference",r)}},x=function(a){var b=a.target.closest("div").dataset.call,c={data:JSON.stringify({quiz:q,call:b})};k.zoomGraph(a,c,"get_quiz_chart")},y=function(a,b){p=a;g.init(a,v);k.init(a);h.default.init(a,v);i.init(q,p,"local-assessfreq-quiz-student-table","local-assessfreq-quiz-table","get_student_table","local_assessfreq_quiz_table_rows_preference","local-assessfreq-quiz-student-table-search","local_assessfreq_student_table","local_assessfreq_set_table_preference");f.init(t,a,"get_quiz_chart","local_assessfreq/chart");d.get_string("loadingquiztitle","local_assessfreq").then(function(a){o=a}).catch(function(){c.default.exception(new Error("Failed to load string: loadingquiz"))}).then(function(){if(0.\n\n/**\n * Javascript for report card display and processing.\n *\n * @module local_assessfreq/dashboard_quiz\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport * as Str from 'core/str';\nimport Templates from 'core/templates';\nimport * as ChartData from 'local_assessfreq/chart_data';\nimport * as FormModal from 'local_assessfreq/form_modal';\nimport OverrideModal from 'local_assessfreq/override_modal';\nimport * as TableHandler from 'local_assessfreq/table_handler';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\nimport * as ZoomModal from 'local_assessfreq/zoom_modal';\n\n/**\n * Module level variables.\n */\nvar selectQuizStr = '';\nvar contextid;\nvar quizId = 0;\nvar refreshPeriod = 60;\nvar counterid;\n\nconst cards = [\n {cardId: 'local-assessfreq-quiz-summary-graph', call: 'participant_summary', aspect: true},\n {cardId: 'local-assessfreq-quiz-summary-trend', call: 'participant_trend', aspect: false}\n];\n\n/**\n * Function for refreshing the counter.\n *\n * @param {boolean} reset the current count process.\n */\nconst refreshCounter = (reset = true) => {\n let progressElement = document.getElementById('local-assessfreq-period-progress');\n\n // Reset the current count process.\n if (reset === true) {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n }\n\n // Exit early if there is already a counter running.\n if (counterid) {\n return;\n }\n\n counterid = setInterval(() => {\n let progressWidthAria = progressElement.getAttribute('aria-valuenow');\n const progressStep = 100 / refreshPeriod;\n\n if ((progressWidthAria - progressStep) > 0) {\n progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%');\n progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep));\n } else {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n processDashboard(quizId);\n refreshCounter();\n }\n }, (1000));\n};\n\n/**\n * Callback function that is called when a quiz is selected from the form.\n * Starts the processing of the dashboard.\n *\n * @param {int} quiz The quiz Id.\n */\nconst processDashboard = (quiz) => {\n quizId = quiz;\n let titleElement = document.getElementById('local-assessfreq-quiz-title');\n titleElement.innerHTML = selectQuizStr;\n // Get quiz data.\n Ajax.call([{\n methodname: 'local_assessfreq_get_quiz_data',\n args: {\n quizid: quiz\n },\n }])[0].then((response) => {\n\n let quizArray = JSON.parse(response);\n let cardsElement = document.getElementById('local-assessfreq-quiz-dashboard-cards-deck');\n let trendElement = document.getElementById('local-assessfreq-quiz-dashboard-participant-trend-deck');\n let summaryElement = document.getElementById('local-assessfreq-quiz-summary-card');\n let summarySpinner = summaryElement.getElementsByClassName('overlay-icon-container')[0];\n let tableElement = document.getElementById('local-assessfreq-quiz-table');\n let periodElement = document.getElementById('local-assessfreq-period-container');\n let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-student-table-search');\n let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-student-table-search-reset');\n let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-student-table-rows');\n\n let quizLink = document.createElement('a');\n quizLink.href = quizArray.url;\n quizLink.innerHTML = '';\n titleElement.innerHTML = quizArray.name + ' ';\n titleElement.appendChild(quizLink);\n\n // Update page URL with quiz ID, without reloading page so that page navigation and bookmarking works.\n const currentdUrl = new URL(window.location.href);\n const newUrl = currentdUrl.origin + currentdUrl.pathname + '?id=' + quizId;\n history.pushState({}, '', newUrl);\n\n // Update page title with quiz name.\n Str.get_string('dashboard:quiztitle', 'local_assessfreq', {'quiz': quizArray.name, 'course': quizArray.courseshortname})\n .then((str) => {\n document.title = str;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: dashboard:quiztitle'));\n });\n\n // Populate quiz summary card with details.\n Templates.render('local_assessfreq/quiz-summary-card-content', quizArray).done((html) => {\n summarySpinner.classList.add('hide');\n let contentcontainer = document.getElementById('local-assessfreq-quiz-summary-card-content');\n Templates.replaceNodeContents(contentcontainer, html, '');\n }).fail(() => {\n Notification.exception(new Error('Failed to load quiz summary template.'));\n return;\n });\n\n // Show the cards.\n cardsElement.classList.remove('hide');\n trendElement.classList.remove('hide');\n tableElement.classList.remove('hide');\n periodElement.classList.remove('hide');\n\n ChartData.getCardCharts(quizId);\n TableHandler.getTable(quizId);\n refreshCounter();\n\n tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch);\n tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch);\n tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset);\n tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet);\n\n return;\n }).fail(() => {\n Notification.exception(new Error('Failed to get quiz data'));\n });\n};\n\n/**\n * Handle processing of refresh and period button actions.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst refreshAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') {\n refreshCounter(true);\n processDashboard(quizId);\n } else if (element.tagName.toLowerCase() === 'a') {\n refreshPeriod = element.dataset.period;\n refreshCounter(true);\n UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod);\n }\n};\n\n/**\n * Trigger the zoom graph. Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst triggerZoomGraph = (event) => {\n let call = event.target.closest('div').dataset.call;\n let params = {'data': JSON.stringify({'quiz': quizId, 'call': call})};\n let method = 'get_quiz_chart';\n\n ZoomModal.zoomGraph(event, params, method);\n};\n\n/**\n * Initialise method for quiz dashboard rendering.\n *\n * @param {int} context The context id.\n * @param {int} quiz The quiz id.\n */\nexport const init = (context, quiz) => {\n contextid = context;\n FormModal.init(context, processDashboard); // Create modal for quiz selection modal.\n ZoomModal.init(context); // Create the zoom modal.\n OverrideModal.init(context, processDashboard);\n TableHandler.init(\n quizId,\n contextid,\n 'local-assessfreq-quiz-student-table',\n 'local-assessfreq-quiz-table',\n 'get_student_table',\n 'local_assessfreq_quiz_table_rows_preference',\n 'local-assessfreq-quiz-student-table-search',\n 'local_assessfreq_student_table',\n 'local_assessfreq_set_table_preference'\n );\n ChartData.init(cards, context, 'get_quiz_chart', 'local_assessfreq/chart');\n Str.get_string('loadingquiztitle', 'local_assessfreq').then((str) => {\n selectQuizStr = str;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: loadingquiz'));\n }).then(() => {\n if (quiz > 0) {\n quizId = quiz;\n processDashboard(quiz);\n }\n });\n\n UserPreference.getUserPreference('local_assessfreq_quiz_refresh_preference')\n .then((response) => {\n refreshPeriod = response.preferences[0].value ? response.preferences[0].value : 60;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: refresh'));\n });\n\n // Event handling for refresh and period buttons.\n let refreshElement = document.getElementById('local-assessfreq-period-container');\n refreshElement.addEventListener('click', refreshAction);\n\n // Set up zoom event listeners.\n let summaryZoom = document.getElementById('local-assessfreq-quiz-summary-graph-zoom');\n summaryZoom.addEventListener('click', triggerZoomGraph);\n\n let trendZoom = document.getElementById('local-assessfreq-quiz-summary-trend-zoom');\n trendZoom.addEventListener('click', triggerZoomGraph);\n\n};\n"],"file":"dashboard_quiz.min.js"} \ No newline at end of file diff --git a/amd/build/dashboard_quiz_inprogress.min.js b/amd/build/dashboard_quiz_inprogress.min.js deleted file mode 100644 index 394c4239..00000000 --- a/amd/build/dashboard_quiz_inprogress.min.js +++ /dev/null @@ -1,2 +0,0 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("local_assessfreq/dashboard_quiz_inprogress",["exports","core/ajax","core/notification","core/templates","local_assessfreq/chart_data","local_assessfreq/table_handler","local_assessfreq/user_preferences","local_assessfreq/zoom_modal"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=k(b);c=k(c);d=k(d);e=j(e);f=j(f);g=j(g);h=j(h);function i(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;i=function(){return a};return a}function j(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=i();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function k(a){return a&&a.__esModule?a:{default:a}}var l,m=60,n,o="name_asc",p=0,q=0,r,s=[{cardId:"local-assessfreq-quiz-summary-upcomming-graph",call:"upcomming_quizzes",aspect:!0},{cardId:"local-assessfreq-quiz-summary-inprogress-graph",call:"all_participants_inprogress",aspect:!0}],t=function(){var a=0.\n\n/**\n * Javascript for quizzes in progress display and processing.\n *\n * @module local_assessfreq/dashboard_quiz_inprogress\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport * as ChartData from 'local_assessfreq/chart_data';\nimport * as TableHandler from 'local_assessfreq/table_handler';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\nimport * as ZoomModal from 'local_assessfreq/zoom_modal';\n\n/**\n * Module level variables.\n */\nvar contextid;\nvar refreshPeriod = 60;\nvar counterid;\nvar tablesort = 'name_asc';\nvar hoursAhead = 0;\nvar hoursBehind = 0;\n\n/**\n * Hours filter array.\n *\n * @type {array} Title to display on modal.\n */\nvar hoursFilter;\n\nconst cards = [\n {cardId: 'local-assessfreq-quiz-summary-upcomming-graph', call: 'upcomming_quizzes', aspect: true},\n {cardId: 'local-assessfreq-quiz-summary-inprogress-graph', call: 'all_participants_inprogress', aspect: true}\n];\n\n/**\n * Function for refreshing the counter.\n *\n * @param {boolean} reset the current count process.\n */\nconst refreshCounter = (reset = true) => {\n let progressElement = document.getElementById('local-assessfreq-period-progress');\n\n // Reset the current count process.\n if (reset === true) {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n }\n\n // Exit early if there is already a counter running.\n if (counterid) {\n return;\n }\n\n counterid = setInterval(() => {\n let progressWidthAria = progressElement.getAttribute('aria-valuenow');\n const progressStep = 100 / refreshPeriod;\n\n if ((progressWidthAria - progressStep) > 0) {\n progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%');\n progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep));\n } else {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n processDashboard();\n refreshCounter();\n }\n }, (1000));\n};\n\n/**\n * Starts the processing of the dashboard.\n */\nconst processDashboard = () => {\n // Get summary quiz data.\n Ajax.call([{\n methodname: 'local_assessfreq_get_inprogress_counts',\n args: {},\n }])[0].then((response) => {\n let quizSummary = JSON.parse(response);\n let summaryElement = document.getElementById('local-assessfreq-quiz-dashboard-inprogress-summary-card');\n let summarySpinner = summaryElement.getElementsByClassName('overlay-icon-container')[0];\n let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-inprogress-table-search');\n let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-inprogress-table-search-reset');\n let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-inprogress-table-rows');\n let tableSortElement = document.getElementById('local-assessfreq-inprogress-table-sort');\n\n summaryElement.classList.remove('hide'); // Show the card.\n\n // Populate summary card with details.\n Templates.render('local_assessfreq/quiz-dashboard-inprogress-summary-card-content', quizSummary)\n .done((html) => {\n summarySpinner.classList.add('hide');\n\n let contentcontainer = document.getElementById('local-assessfreq-quiz-dashboard-inprogress-summary-card-content');\n Templates.replaceNodeContents(contentcontainer, html, '');\n }).fail(() => {\n Notification.exception(new Error('Failed to load quiz counts template.'));\n return;\n });\n\n hoursFilter = [hoursAhead, hoursBehind];\n ChartData.getCardCharts(0, hoursFilter);\n TableHandler.getTable(0, hoursFilter, tablesort);\n refreshCounter();\n\n // Table event listeners.\n tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch);\n tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch);\n tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset);\n tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet);\n tableSortElement.addEventListener('click', TableHandler.tableSortButtonAction);\n\n return;\n }).fail(() => {\n Notification.exception(new Error('Failed to get quiz summary counts'));\n });\n};\n\n/**\n * Handle processing of refresh and period button actions.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst refreshAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') {\n refreshCounter(true);\n processDashboard();\n } else if (element.tagName.toLowerCase() === 'a') {\n refreshPeriod = element.dataset.period;\n refreshCounter(true);\n UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod);\n }\n};\n\n/**\n * Trigger the zoom graph. Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst triggerZoomGraph = (event) => {\n let call = event.target.closest('div').dataset.call;\n let params = {'data': JSON.stringify({'call': call, 'hoursahead': hoursAhead, 'hoursbehind': hoursBehind})};\n let method = 'get_quiz_inprogress_chart';\n\n ZoomModal.zoomGraph(event, params, method);\n};\n\n/**\n * Process the hours ahead event from the in progress quizzes table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst quizzesAheadSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_quizzes_inprogress_table_hoursahead_preference', hours)\n .then(() => {\n hoursAhead = hours;\n processDashboard(); // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours ahead'));\n });\n }\n};\n\n/**\n * Process the hours behind event from the in progress quizzes table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst quizzesBehindSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference', hours)\n .then(() => {\n hoursBehind = hours;\n processDashboard(); // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours behind'));\n });\n }\n};\n\n/**\n * Initialise method for quizzes in progress dashboard rendering.\n *\n * @param {int} context The context id.\n */\nexport const init = (context) => {\n contextid = context;\n ZoomModal.init(context); // Create the zoom modal.\n TableHandler.init(\n 0,\n contextid,\n null,\n 'local-assessfreq-quiz-inprogress-table',\n 'get_quizzes_inprogress_table',\n 'local_assessfreq_quiz_table_inprogress_preference',\n 'local-assessfreq-quiz-inprogress-table-search'\n );\n ChartData.init(cards, context, 'get_quiz_inprogress_chart', 'local_assessfreq/chart');\n\n UserPreference.getUserPreference('local_assessfreq_quiz_refresh_preference')\n .then((response) => {\n refreshPeriod = response.preferences[0].value ? response.preferences[0].value : 60;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: refresh'));\n });\n\n UserPreference.getUserPreference('local_assessfreq_quiz_table_inprogress_sort_preference')\n .then((response) => {\n tablesort = response.preferences[0].value ? response.preferences[0].value : 'name_asc';\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: tablesort'));\n });\n\n UserPreference.getUserPreference('local_assessfreq_quizzes_inprogress_table_hoursahead_preference')\n .then((response) => {\n hoursAhead = response.preferences[0].value ? response.preferences[0].value : 0;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursahead'));\n });\n\n UserPreference.getUserPreference('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference')\n .then((response) => {\n hoursBehind = response.preferences[0].value ? response.preferences[0].value : 0;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursbehind'));\n });\n\n // Event handling for refresh and period buttons.\n let refreshElement = document.getElementById('local-assessfreq-period-container');\n refreshElement.addEventListener('click', refreshAction);\n\n // Set up zoom event listeners.\n let summaryZoom = document.getElementById('local-assessfreq-quiz-summary-inprogress-graph-zoom');\n summaryZoom.addEventListener('click', triggerZoomGraph);\n\n let upcommingZoom = document.getElementById('local-assessfreq-quiz-summary-upcomming-graph-zoom');\n upcommingZoom.addEventListener('click', triggerZoomGraph);\n\n // Set up behind and ahead quizzes event listeners.\n let quizzesAheadElement = document.getElementById('local-assessfreq-quiz-student-table-hoursahead');\n quizzesAheadElement.addEventListener('click', quizzesAheadSet);\n\n let quizzesBehindElement = document.getElementById('local-assessfreq-quiz-student-table-hoursbehind');\n quizzesBehindElement.addEventListener('click', quizzesBehindSet);\n\n processDashboard();\n\n};\n"],"file":"dashboard_quiz_inprogress.min.js"} \ No newline at end of file diff --git a/amd/build/dayview.min.js b/amd/build/dayview.min.js deleted file mode 100644 index 7538d451..00000000 --- a/amd/build/dayview.min.js +++ /dev/null @@ -1,2 +0,0 @@ -function asyncGeneratorStep(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function _asyncToGenerator(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){asyncGeneratorStep(h,d,e,f,g,"next",a)}function g(a){asyncGeneratorStep(h,d,e,f,g,"throw",a)}f(void 0)})}}define ("local_assessfreq/dayview",["core/str","core/notification","core/modal_factory","local_assessfreq/modal_large","core/templates","core/ajax"],function(a,b,c,d,e,f){var g={},h,i="

",j=[{key:"sun",component:"calendar"},{key:"mon",component:"calendar"},{key:"tue",component:"calendar"},{key:"wed",component:"calendar"},{key:"thu",component:"calendar"},{key:"fri",component:"calendar"},{key:"sat",component:"calendar"},{key:"jan",component:"local_assessfreq"},{key:"feb",component:"local_assessfreq"},{key:"mar",component:"local_assessfreq"},{key:"apr",component:"local_assessfreq"},{key:"may",component:"local_assessfreq"},{key:"jun",component:"local_assessfreq"},{key:"jul",component:"local_assessfreq"},{key:"aug",component:"local_assessfreq"},{key:"sep",component:"local_assessfreq"},{key:"oct",component:"local_assessfreq"},{key:"nov",component:"local_assessfreq"},{key:"dec",component:"local_assessfreq"}],k,l="Australia/Melbourne",m="",n=function(a,b){return new Promise(function(c){var d=new Date(1e3*a).toLocaleString("en-US",{timeZone:l}),e=new Date(d),f=e.getFullYear(),g=k[7+e.getMonth()],h=e.getDate(),i=e.getHours(),j="0"+e.getMinutes(),m=i+":"+j.substr(-2);if("strftimetime"===b){c(m)}else{c(h+" "+g+" "+f+", "+m)}})},o=function(){var a=_asyncToGenerator(regeneratorRuntime.mark(function a(b){var c,d,e,f,g,h,j,k,m,o,p,q,r,s;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=JSON.parse(b);d=5/72;e=0;case 3:if(!(e=q)){a.next=23;break}q=0;s=(p-j)/60*d;a.next=20;return n(c[e].timestart,"strftimedatetime");case 20:c[e].start=a.sent;a.next=28;break;case 23:r=q/60*d;s=(p-m)/60*d;a.next=27;return n(c[e].timestart,"strftimetime");case 27:c[e].start=a.sent;case 28:if(100.\n\n/**\n * Javascript for heatmap calendar generation and display.\n *\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/str', 'core/notification', 'core/modal_factory', 'local_assessfreq/modal_large', 'core/templates', 'core/ajax'],\nfunction(Str, Notification, ModalFactory, ModalLarge, Templates, Ajax) {\n\n /**\n * Module level variables.\n */\n var Dayview = {};\n var modalObj;\n const spinner = '

'\n + ''\n + '

';\n\n const stringArr = [\n {key: 'sun', component: 'calendar'},\n {key: 'mon', component: 'calendar'},\n {key: 'tue', component: 'calendar'},\n {key: 'wed', component: 'calendar'},\n {key: 'thu', component: 'calendar'},\n {key: 'fri', component: 'calendar'},\n {key: 'sat', component: 'calendar'},\n {key: 'jan', component: 'local_assessfreq'},\n {key: 'feb', component: 'local_assessfreq'},\n {key: 'mar', component: 'local_assessfreq'},\n {key: 'apr', component: 'local_assessfreq'},\n {key: 'may', component: 'local_assessfreq'},\n {key: 'jun', component: 'local_assessfreq'},\n {key: 'jul', component: 'local_assessfreq'},\n {key: 'aug', component: 'local_assessfreq'},\n {key: 'sep', component: 'local_assessfreq'},\n {key: 'oct', component: 'local_assessfreq'},\n {key: 'nov', component: 'local_assessfreq'},\n {key: 'dec', component: 'local_assessfreq'},\n ];\n var stringResult;\n var systemTimezone = 'Australia/Melbourne';\n var dayViewTitle = '';\n\n const getUserDate = function (timestamp, format) {\n return new Promise((resolve) => {\n const systemTimezoneTime = new Date(timestamp * 1000).toLocaleString('en-US', {timeZone: systemTimezone});\n let date = new Date(systemTimezoneTime);\n const year = date.getFullYear();\n const month = stringResult[(7 + date.getMonth())];\n const day = date.getDate();\n const hours = date.getHours();\n const minutes = '0' + date.getMinutes();\n\n const strftimetime = hours + ':' + minutes.substr(-2); // Will display time in 10:30 format.\n const strftimedatetime = day + ' ' + month + ' ' + year + ', ' + strftimetime;\n\n if (format === 'strftimetime') {\n resolve(strftimetime);\n } else {\n resolve(strftimedatetime);\n }\n\n });\n };\n\n const formatData = async function(response) {\n let responseArr = JSON.parse(response);\n\n // We are displaying the event as a bar whose width represents the start and end time of the event.\n // We need to scale the width of the bar to match the width of the container. Therefore 100% width of the container\n // equals 24 hours (one day).\n // There are 1440 mins per day. 1440 mins equals 100%, therefore 1 min = (100/1440)%. 5/72 == 100/1440.\n let scaler = 5 / 72;\n\n for (let i = 0; i < responseArr.length; i++) {\n const year = responseArr[i].endyear;\n const month = (responseArr[i].endmonth) - 1; // Minus 1 for difference between months in PHP and JS.\n const day = responseArr[i].endday;\n const dayStart = (new Date(year, month, day).getTime()) / 1000;\n const timeStart = new Date(responseArr[i].timestart * 1000).toLocaleString('en-US', {timeZone: systemTimezone});\n const timeStartTimestamp = (new Date(timeStart).getTime()) / 1000;\n const timeEnd = new Date(responseArr[i].timeend * 1000).toLocaleString('en-US', {timeZone: systemTimezone});\n const timeEndTimestamp = (new Date(timeEnd).getTime()) / 1000;\n let secondsSinceDayStart = timeStartTimestamp - dayStart;\n let leftMargin = 0;\n let width = 0;\n\n if (secondsSinceDayStart <= 0) {\n secondsSinceDayStart = 0;\n width = ((timeEndTimestamp - dayStart) / 60) * scaler;\n responseArr[i].start = await getUserDate(responseArr[i].timestart, 'strftimedatetime');\n } else {\n leftMargin = (secondsSinceDayStart / 60) * scaler;\n width = ((timeEndTimestamp - timeStartTimestamp) / 60) * scaler;\n responseArr[i].start = await getUserDate(responseArr[i].timestart, 'strftimetime');\n }\n\n if (leftMargin + width > 100) {\n width = 100 - leftMargin;\n }\n\n responseArr[i].leftmargin = leftMargin;\n responseArr[i].width = width;\n responseArr[i].end = await getUserDate(responseArr[i].timeend, 'strftimetime');\n }\n\n return new Promise((resolve) => {\n resolve(responseArr);\n });\n };\n\n /**\n * Initialise the base modal to be used.\n *\n */\n Dayview.display = function(date) {\n modalObj.setBody(spinner);\n modalObj.show();\n let args = {\n date: date,\n modules: ['all']\n };\n let jsonArgs = JSON.stringify(args);\n Ajax.call([{\n methodname: 'local_assessfreq_get_day_events',\n args: {jsondata: jsonArgs},\n }])[0]\n .then(formatData)\n .then((responseArr) => {\n\n let context = {rows: responseArr};\n const year = responseArr[0].endyear;\n const day = responseArr[0].endday;\n const month = stringResult[(6 + parseInt(responseArr[0].endmonth))];\n const dayDate = day + ' ' + month + ' ' + year;\n\n modalObj.setTitle(dayViewTitle + ' ' + dayDate);\n modalObj.setBody(Templates.render('local_assessfreq/dayview', context));\n\n }).fail(() => {\n Notification.exception(new Error('Failed to load day view'));\n });\n };\n\n /**\n * Initialise the base modal to be used.\n *\n * @param {integer} context The current context id.\n */\n Dayview.init = function() {\n // Load the strings we'll need later.\n Str.get_strings(stringArr).catch(() => { // Get required strings.\n Notification.exception(new Error('Failed to load strings'));\n return;\n }).then(stringReturn => { // Save string to global to be used later.\n stringResult = stringReturn;\n });\n\n // Get the system timzone.\n Ajax.call([{\n methodname: 'local_assessfreq_get_system_timezone',\n args: {},\n }], true, false)[0].then((response) => {\n systemTimezone = response;\n return;\n }).fail(() => {\n Notification.exception(new Error('Failed to get system timezone'));\n });\n\n Str.get_string('schedule', 'local_assessfreq').then((title) => {\n dayViewTitle = title;\n\n // Create the Modal.\n ModalFactory.create({\n type: ModalLarge.TYPE,\n title: title,\n body: spinner\n })\n .done((modal) => {\n modalObj = modal;\n\n });\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: loading'));\n });\n\n };\n\n return Dayview;\n});\n"],"file":"dayview.min.js"} \ No newline at end of file diff --git a/amd/build/debouncer.min.js b/amd/build/debouncer.min.js index 20d1b83f..1fca9c82 100644 --- a/amd/build/debouncer.min.js +++ b/amd/build/debouncer.min.js @@ -1,2 +1,3 @@ -define ("local_assessfreq/debouncer",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.debouncer=void 0;a.debouncer=function debouncer(a,b){var c;return function(){for(var d=arguments.length,e=Array(d),f=0;f{let timeout;return function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];const later=()=>{clearTimeout(timeout),func(...args)};clearTimeout(timeout),timeout=setTimeout(later,wait)}}})); + +//# sourceMappingURL=debouncer.min.js.map \ No newline at end of file diff --git a/amd/build/debouncer.min.js.map b/amd/build/debouncer.min.js.map index 7668fcb3..ceb85d0a 100644 --- a/amd/build/debouncer.min.js.map +++ b/amd/build/debouncer.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/debouncer.js"],"names":["debouncer","func","wait","timeout","args","later","clearTimeout","setTimeout"],"mappings":"0JAmCyB,QAAZA,CAAAA,SAAY,CAACC,CAAD,CAAOC,CAAP,CAAgB,CACrC,GAAIC,CAAAA,CAAJ,CAEA,MAAO,WAAmC,4BAANC,CAAM,uBAANA,CAAM,iBACtC,GAAMC,CAAAA,CAAK,CAAG,UAAM,CAChBC,YAAY,CAACH,CAAD,CAAZ,CACAF,CAAI,MAAJ,QAAQG,CAAR,CACH,CAHD,CAKAE,YAAY,CAACH,CAAD,CAAZ,CACAA,CAAO,CAAGI,UAAU,CAACF,CAAD,CAAQH,CAAR,CACvB,CACJ,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Debounce JS module.\n *\n * @module local_assessfreq/debouncer\n * @package local_assessfreq\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n *\n */\n\n/**\n * Quick and dirty debounce method for the settings.\n * This stops the ajax method that updates the table from being updated\n * while the user is still checking options.\n *\n * @method debouncer\n * @param {function} func The function we want to keep calling.\n * @param {number} wait Our timeout.\n * @return {function}\n */\nexport const debouncer = (func, wait) => {\n let timeout;\n\n return function executedFunction(...args) {\n const later = () => {\n clearTimeout(timeout);\n func(...args);\n };\n\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n};\n"],"file":"debouncer.min.js"} \ No newline at end of file +{"version":3,"file":"debouncer.min.js","sources":["../src/debouncer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Debounce JS module.\n *\n * @module local_assessfreq/debouncer\n * @package\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n *\n */\n\n/**\n * Quick and dirty debounce method for the settings.\n * This stops the ajax method that updates the table from being updated\n * while the user is still checking options.\n *\n * @method debouncer\n * @param {function} func The function we want to keep calling.\n * @param {number} wait Our timeout.\n * @return {function}\n */\nexport const debouncer = (func, wait) => {\n let timeout;\n\n return function executedFunction(...args) {\n const later = () => {\n clearTimeout(timeout);\n func(...args);\n };\n\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n};\n"],"names":["func","wait","timeout","args","later","clearTimeout","setTimeout"],"mappings":"yKAmCyB,CAACA,KAAMC,YACxBC,eAEG,yCAA6BC,6CAAAA,iCAC1BC,MAAQ,KACVC,aAAaH,SACbF,QAAQG,OAGZE,aAAaH,SACbA,QAAUI,WAAWF,MAAOH"} \ No newline at end of file diff --git a/amd/build/form_modal.min.js b/amd/build/form_modal.min.js deleted file mode 100644 index 63bcb0f9..00000000 --- a/amd/build/form_modal.min.js +++ /dev/null @@ -1,2 +0,0 @@ -define ("local_assessfreq/form_modal",["core/str","core/modal_factory","core/fragment","core/ajax"],function(a,b,c,d){var e={},f,g,h=[],i,j="

",k={attributes:!0,childList:!1,subtree:!0},l=new MutationObserver(function ObserverCallback(a){for(var b=0,c;bd){if(null===document.getElementById("noquizwarning")){a.get_string("noquizselected","local_assessfreq").then(function(a){var b=document.createElement("div");b.innerHTML=a;b.id="noquizwarning";b.classList.add("alert","alert-danger");g.getBody().prepend(b)}).catch(function(){Notification.exception(new Error("Failed to load string: searchquiz"))})}}else{g.hide();g.setBody("");l.disconnect();i(d,e)}},q=function(){o();g.show()};e.init=function(a,b){f=a;i=b;m();var c=document.getElementById("local-assessfreq-find-quiz");c.addEventListener("click",q)};return e}); -//# sourceMappingURL=form_modal.min.js.map diff --git a/amd/build/form_modal.min.js.map b/amd/build/form_modal.min.js.map deleted file mode 100644 index 7411e0b4..00000000 --- a/amd/build/form_modal.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/form_modal.js"],"names":["define","Str","ModalFactory","Fragment","Ajax","FormModal","contextid","modalObj","resetOptions","callback","spinner","observerConfig","attributes","childList","subtree","observer","MutationObserver","ObserverCallback","mutationsList","i","element","length","target","tagName","toLowerCase","classList","contains","addEventListener","updateModalBody","document","getElementById","dataset","course","value","call","methodname","args","query","done","response","quizArray","JSON","parse","selectElement","selectElementLength","options","remove","j","k","opt","el","createElement","textContent","name","id","appendChild","removeAttribute","forEach","option","disabled","fail","Notification","exception","Error","createModal","get_string","then","title","create","type","types","DEFAULT","body","large","modal","getRoot","on","processModalForm","e","preventDefault","setBody","hide","catch","getOptionPlaceholders","Promise","resolve","reject","get_strings","key","component","stringReturn","push","formdata","params","stringify","setTitle","loadFragment","modalContainer","querySelectorAll","observe","quizElement","quizId","selectedIndex","courseId","warning","innerHTML","add","getBody","prepend","disconnect","displayModalForm","show","init","context","processDashboard","createBroadcastButton"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for report card display and processing.\n *\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/str', 'core/modal_factory', 'core/fragment', 'core/ajax'],\nfunction(Str, ModalFactory, Fragment, Ajax) {\n\n /**\n * Module level variables.\n */\n var FormModal = {};\n var contextid;\n var modalObj;\n var resetOptions = [];\n var callback;\n\n const spinner = '

'\n + ''\n + '

';\n\n const observerConfig = { attributes: true, childList: false, subtree: true };\n\n const ObserverCallback = function(mutationsList) {\n for (let i = 0; i < mutationsList.length; i++) {\n let element = mutationsList[i].target;\n if(element.tagName.toLowerCase() === 'span' && element.classList.contains('badge')) {\n element.addEventListener('click', updateModalBody);\n document.getElementById('id_courses').dataset.course = element.dataset.value;\n\n document.getElementById('id_quiz').value = -1;\n Ajax.call([{\n methodname: 'local_assessfreq_get_quizzes',\n args: {\n query: mutationsList[i].target.dataset.value\n },\n }])[0].done((response) => {\n let quizArray = JSON.parse(response);\n let selectElement = document.getElementById('id_quiz');\n let selectElementLength = selectElement.options.length;\n if (document.getElementById('noquizwarning') !== null) {\n document.getElementById('noquizwarning').remove();\n }\n // Clear exisitng options.\n for (let j = selectElementLength - 1; j >= 0; j--) {\n selectElement.options[j] = null;\n }\n\n if (quizArray.length > 0) {\n\n // Add new options.\n for (let k = 0; k < quizArray.length; k++) {\n let opt = quizArray[k];\n let el = document.createElement('option');\n el.textContent = opt.name;\n el.value = opt.id;\n selectElement.appendChild(el);\n }\n selectElement.removeAttribute('disabled');\n if (document.getElementById('noquizwarning') !== null) {\n document.getElementById('noquizwarning').remove();\n }\n } else {\n resetOptions.forEach((option) => {\n selectElement.appendChild(option);\n });\n document.getElementById('id_quiz').value = 0;\n selectElement.disabled = true;\n }\n\n }).fail(() => {\n Notification.exception(new Error('Failed to get quizzes'));\n });\n\n break;\n }\n\n }\n };\n\n const observer = new MutationObserver(ObserverCallback);\n\n /**\n * Create the modal window.\n *\n * @private\n */\n const createModal = function() {\n Str.get_string('loading', 'local_assessfreq').then((title) => {\n // Create the Modal.\n ModalFactory.create({\n type: ModalFactory.types.DEFAULT,\n title: title,\n body: spinner,\n large: true\n })\n .done((modal) => {\n modalObj = modal;\n\n // Explicitly handle form click events.\n modalObj.getRoot().on('click', '#id_submitbutton', processModalForm);\n modalObj.getRoot().on('click', '#id_cancel', (e) => {\n e.preventDefault();\n modalObj.setBody(spinner);\n modalObj.hide();\n });\n });\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: loading'));\n });\n };\n\n const getOptionPlaceholders = function() {\n return new Promise((resolve, reject) => {\n const stringArr = [\n {key: 'selectcourse', component: 'local_assessfreq'},\n {key: 'loadingquiz', component: 'local_assessfreq'},\n ];\n\n Str.get_strings(stringArr).catch(() => { // Get required strings.\n reject(new Error('Failed to load strings'));\n return;\n }).then(stringReturn => { // Save string to global to be used later.\n for (let i = 0; i < stringReturn.length; i++) {\n let el = document.createElement('option');\n el.textContent = stringReturn[i];\n el.value = 0 - i;\n resetOptions.push(el);\n }\n resolve();\n });\n });\n };\n\n /**\n * Updates the body of the modal window.\n *\n * @param {Object} formdata\n * @private\n */\n const updateModalBody = function(formdata) {\n if (typeof formdata === \"undefined\") {\n formdata = {};\n }\n\n let params = {\n 'jsonformdata': JSON.stringify(formdata)\n };\n\n getOptionPlaceholders()\n .then(() => {\n Str.get_string('searchquiz', 'local_assessfreq').then((title) => {\n modalObj.setTitle(title);\n modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_base_form', contextid, params));\n let modalContainer = document.querySelectorAll('[data-region*=\"modal-container\"]')[0];\n observer.observe(modalContainer, observerConfig);\n\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: searchquiz'));\n });\n });\n };\n\n /**\n * Updates Moodle form with selected information.\n *\n * @param {Object} e\n * @private\n */\n const processModalForm = function(e) {\n e.preventDefault(); // Stop modal from closing.\n\n let quizElement = document.getElementById('id_quiz');\n let quizId = quizElement.options[quizElement.selectedIndex].value;\n let courseId = document.getElementById('id_courses').dataset.course;\n\n if (courseId === undefined || quizId < 1) {\n if (document.getElementById('noquizwarning') === null) {\n Str.get_string('noquizselected', 'local_assessfreq').then((warning) => {\n let element = document.createElement('div');\n element.innerHTML = warning;\n element.id = 'noquizwarning';\n element.classList.add('alert', 'alert-danger');\n modalObj.getBody().prepend(element);\n\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: searchquiz'));\n });\n\n }\n\n } else {\n modalObj.hide(); // Close modal.\n modalObj.setBody(''); // Cleaer form.\n observer.disconnect(); // Remove observer.\n callback(quizId, courseId); // Trigger dashboard update.\n }\n\n };\n\n /**\n * Display the Modal form.\n */\n const displayModalForm = function() {\n updateModalBody();\n modalObj.show();\n };\n\n /**\n * Initialise method for quiz dashboard rendering.\n */\n FormModal.init = function(context, processDashboard) {\n contextid = context;\n callback = processDashboard;\n createModal();\n\n let createBroadcastButton = document.getElementById('local-assessfreq-find-quiz');\n createBroadcastButton.addEventListener('click', displayModalForm);\n };\n\n return FormModal;\n});\n"],"file":"form_modal.min.js"} \ No newline at end of file diff --git a/amd/build/modal_large.min.js b/amd/build/modal_large.min.js index 122e2488..19b59b2b 100644 --- a/amd/build/modal_large.min.js +++ b/amd/build/modal_large.min.js @@ -1,2 +1,11 @@ -define ("local_assessfreq/modal_large",["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry"],function(a,b,c,d,e){var f=!1,g=function(a){d.call(this,a)};g.TYPE="local_assesfreq-large_modal";g.prototype=Object.create(d.prototype);g.prototype.constructor=g;g.prototype.registerEventListeners=function(){d.prototype.registerEventListeners.call(this)};if(!f){e.register(g.TYPE,g,"local_assessfreq/modal_large");f=!0}return g}); -//# sourceMappingURL=modal_large.min.js.map +/** + * Javascript for large modal . + * + * @module local_assessfreq/modal_large + * @package + * @copyright 2020 Matt Porritt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define("local_assessfreq/modal_large",["jquery","core/notification","core/custom_interaction_events","core/modal","core/modal_registry"],(function($,Notification,CustomEvents,Modal,ModalRegistry){let registered=!1,ModalLarge=function(root){Modal.call(this,root)};return ModalLarge.TYPE="local_assesfreq-large_modal",(ModalLarge.prototype=Object.create(Modal.prototype)).constructor=ModalLarge,ModalLarge.prototype.registerEventListeners=function(){Modal.prototype.registerEventListeners.call(this)},registered||(ModalRegistry.register(ModalLarge.TYPE,ModalLarge,"local_assessfreq/modal_large"),registered=!0),ModalLarge})); + +//# sourceMappingURL=modal_large.min.js.map \ No newline at end of file diff --git a/amd/build/modal_large.min.js.map b/amd/build/modal_large.min.js.map index 60ee9107..dfdc2028 100644 --- a/amd/build/modal_large.min.js.map +++ b/amd/build/modal_large.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/modal_large.js"],"names":["define","$","Notification","CustomEvents","Modal","ModalRegistry","registered","ModalLarge","root","call","TYPE","prototype","Object","create","constructor","registerEventListeners","register"],"mappings":"AAuBAA,OAAM,gCAAC,CAAC,QAAD,CAAW,mBAAX,CAAgC,gCAAhC,CAAkE,YAAlE,CAAgF,qBAAhF,CAAD,CACN,SAASC,CAAT,CAAYC,CAAZ,CAA0BC,CAA1B,CAAwCC,CAAxC,CAA+CC,CAA/C,CAA8D,IAEtDC,CAAAA,CAAU,GAF4C,CAStDC,CAAU,CAAG,SAASC,CAAT,CAAe,CAC5BJ,CAAK,CAACK,IAAN,CAAW,IAAX,CAAiBD,CAAjB,CACH,CAXyD,CAa1DD,CAAU,CAACG,IAAX,CAAkB,6BAAlB,CACAH,CAAU,CAACI,SAAX,CAAuBC,MAAM,CAACC,MAAP,CAAcT,CAAK,CAACO,SAApB,CAAvB,CACAJ,CAAU,CAACI,SAAX,CAAqBG,WAArB,CAAmCP,CAAnC,CAOAA,CAAU,CAACI,SAAX,CAAqBI,sBAArB,CAA8C,UAAW,CAErDX,CAAK,CAACO,SAAN,CAAgBI,sBAAhB,CAAuCN,IAAvC,CAA4C,IAA5C,CACH,CAHD,CAOA,GAAI,CAACH,CAAL,CAAiB,CACbD,CAAa,CAACW,QAAd,CAAuBT,CAAU,CAACG,IAAlC,CAAwCH,CAAxC,CAAoD,8BAApD,EACAD,CAAU,GACb,CAED,MAAOC,CAAAA,CACV,CApCK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for large modal .\n *\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_registry'],\nfunction($, Notification, CustomEvents, Modal, ModalRegistry) {\n\n var registered = false;\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n var ModalLarge = function(root) {\n Modal.call(this, root);\n };\n\n ModalLarge.TYPE = 'local_assesfreq-large_modal';\n ModalLarge.prototype = Object.create(Modal.prototype);\n ModalLarge.prototype.constructor = ModalLarge;\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n ModalLarge.prototype.registerEventListeners = function() {\n // Apply parent event listeners.\n Modal.prototype.registerEventListeners.call(this);\n };\n\n // Automatically register with the modal registry the first time this module is imported so that you can create modals\n // of this type using the modal factory.\n if (!registered) {\n ModalRegistry.register(ModalLarge.TYPE, ModalLarge, 'local_assessfreq/modal_large');\n registered = true;\n }\n\n return ModalLarge;\n});"],"file":"modal_large.min.js"} \ No newline at end of file +{"version":3,"file":"modal_large.min.js","sources":["../src/modal_large.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for large modal .\n *\n * @module local_assessfreq/modal_large\n * @package\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_registry'],\n function($, Notification, CustomEvents, Modal, ModalRegistry) {\n\n let registered = false;\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n let ModalLarge = function(root) {\n Modal.call(this, root);\n };\n\n ModalLarge.TYPE = 'local_assesfreq-large_modal';\n ModalLarge.prototype = Object.create(Modal.prototype);\n ModalLarge.prototype.constructor = ModalLarge;\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n ModalLarge.prototype.registerEventListeners = function () {\n // Apply parent event listeners.\n Modal.prototype.registerEventListeners.call(this);\n };\n\n // Automatically register with the modal registry the first time this module is imported so that you can create modals\n // of this type using the modal factory.\n if (!registered) {\n ModalRegistry.register(ModalLarge.TYPE, ModalLarge, 'local_assessfreq/modal_large');\n registered = true;\n }\n\n return ModalLarge;\n }\n);\n"],"names":["define","$","Notification","CustomEvents","Modal","ModalRegistry","registered","ModalLarge","root","call","this","TYPE","prototype","Object","create","constructor","registerEventListeners","register"],"mappings":";;;;;;;;AAwBAA,sCACI,CAAC,SAAU,oBAAqB,iCAAkC,aAAc,wBAChF,SAASC,EAAGC,aAAcC,aAAcC,MAAOC,mBAEvCC,YAAa,EAObC,WAAa,SAASC,MACtBJ,MAAMK,KAAKC,KAAMF,cAGrBD,WAAWI,KAAO,+BAClBJ,WAAWK,UAAYC,OAAOC,OAAOV,MAAMQ,YACtBG,YAAcR,WAOnCA,WAAWK,UAAUI,uBAAyB,WAE1CZ,MAAMQ,UAAUI,uBAAuBP,KAAKC,OAK3CJ,aACDD,cAAcY,SAASV,WAAWI,KAAMJ,WAAY,gCACpDD,YAAa,GAGVC"} \ No newline at end of file diff --git a/amd/build/override_modal.min.js b/amd/build/override_modal.min.js index 421b5acf..f643d4c9 100644 --- a/amd/build/override_modal.min.js +++ b/amd/build/override_modal.min.js @@ -1,2 +1,10 @@ -define ("local_assessfreq/override_modal",["jquery","core/str","core/modal_factory","core/modal_events","core/fragment","core/ajax"],function(a,b,c,d,e,f){var h={},i,j,k,l,m,n,o="

",p=function(){b.get_string("loading","local_assessfreq").then(function(a){c.create({type:c.types.DEFAULT,title:a,body:o,large:!0}).done(function(a){j=a;j.getRoot().on("click","#id_submitbutton",g);j.getRoot().on("click","#id_cancel",function(a){a.preventDefault();j.setBody(o);j.hide()})})}).catch(function(){Notification.exception(new Error("Failed to load string: loading"))})},q=function(a,c,d){if("undefined"==typeof d){d={}}var f={jsonformdata:JSON.stringify(d),quizid:a,userid:c};j.setBody(o);b.get_string("useroverride","local_assessfreq").then(function(a){j.setTitle(a);j.setBody(e.loadFragment("local_assessfreq","new_override_form",i,f))}).catch(function(){Notification.exception(new Error("Failed to load string: useroverride"))})};function g(b){b.preventDefault();var c=j.getRoot().find("form").serialize(),d=JSON.stringify(c),e=a.merge(j.getRoot().find("[aria-invalid=\"true\"]"),j.getRoot().find(".error"));if(e.length){e.first().focus();return}f.call([{methodname:"local_assessfreq_process_override_form",args:{jsonformdata:d,quizid:l}}])[0].done(function(){j.setBody(o);j.hide();if(n){k(l,n)}else{k(l)}}).fail(function(){q(l,m,c)})}h.displayModalForm=function(a,b){var c=2 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define("local_assessfreq/override_modal",["jquery","core/str","core/modal","core/modal_factory","core/modal_events","core/fragment","core/ajax"],(function($,Str,Modal,ModalFactory,ModalEvents,Fragment,Ajax){let contextid,activitytype,modalObj,activityid,userid,tableHandler,OverrideModal={};const spinner='

',createModal=function(){Str.get_string("loading").then((title=>{ModalFactory.create({type:ModalFactory.types.DEFAULT,title:title,body:spinner,large:!0}).done((modal=>{modalObj=modal,modalObj.getRoot().on("click","#id_submitbutton",processModalForm),modalObj.getRoot().on("click","#id_cancel",(function(e){e.preventDefault(),modalObj.setBody(spinner),modalObj.hide()}))}))}))},updateModalBody=function(activity,user,formdata){void 0===formdata&&(formdata={});let params={jsonformdata:JSON.stringify(formdata),activitytype:activitytype,activityid:activity,userid:user};modalObj.setBody(spinner),Str.get_string("modal:useroverride","local_assessfreq").then((title=>{modalObj.setTitle(title),modalObj.setBody(Fragment.loadFragment("local_assessfreq","new_override_form",contextid,params))}))};function processModalForm(e){e.preventDefault();let overrideform=modalObj.getRoot().find("form").serialize(),formjson=JSON.stringify(overrideform),invalid=$.merge(modalObj.getRoot().find('[aria-invalid="true"]'),modalObj.getRoot().find(".error"));invalid.length?invalid.first().focus():Ajax.call([{methodname:"local_assessfreq_process_override_form",args:{jsonformdata:formjson,activityid:activityid,activitytype:activitytype}}])[0].done((()=>{modalObj.setBody(spinner),modalObj.hide(),void 0!==tableHandler&&tableHandler.getTable()})).fail((()=>{updateModalBody(activityid,userid,overrideform)}))}return OverrideModal.displayModalForm=function(activity,user){activityid=activity,userid=user,updateModalBody(activityid,user),modalObj.show()},OverrideModal.init=function(context,module){let tablehandler=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0;activitytype=module,contextid=context,tableHandler=tablehandler,createModal()},OverrideModal})); + +//# sourceMappingURL=override_modal.min.js.map \ No newline at end of file diff --git a/amd/build/override_modal.min.js.map b/amd/build/override_modal.min.js.map index 70e2a09b..738f8ef4 100644 --- a/amd/build/override_modal.min.js.map +++ b/amd/build/override_modal.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/override_modal.js"],"names":["define","$","Str","ModalFactory","ModalEvents","Fragment","Ajax","OverrideModal","contextid","modalObj","callback","quizid","userid","hoursFilter","spinner","createModal","get_string","then","title","create","type","types","DEFAULT","body","large","done","modal","getRoot","on","processModalForm","e","preventDefault","setBody","hide","catch","Notification","exception","Error","updateModalBody","quiz","user","formdata","params","JSON","stringify","setTitle","loadFragment","overrideform","find","serialize","formjson","invalid","merge","length","first","focus","call","methodname","args","fail","displayModalForm","hours","show","init","context","callbackFunction"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for report card display and processing.\n *\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax'],\nfunction($,Str, ModalFactory, ModalEvents, Fragment, Ajax) {\n\n /**\n * Module level variables.\n */\n var OverrideModal = {};\n var contextid;\n var modalObj;\n var callback;\n var quizid;\n var userid;\n var hoursFilter;\n\n const spinner = '

'\n + ''\n + '

';\n\n /**\n * Create the modal window.\n *\n * @private\n */\n const createModal = function() {\n Str.get_string('loading', 'local_assessfreq').then((title) => {\n // Create the Modal.\n ModalFactory.create({\n type: ModalFactory.types.DEFAULT,\n title: title,\n body: spinner,\n large: true\n })\n .done((modal) => {\n modalObj = modal;\n // Explicitly handle form click events.\n modalObj.getRoot().on('click', '#id_submitbutton', processModalForm);\n modalObj.getRoot().on('click', '#id_cancel', function(e) {\n e.preventDefault();\n modalObj.setBody(spinner);\n modalObj.hide();\n });\n });\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: loading'));\n });\n };\n\n /**\n * Updates the body of the modal window.\n *\n * @param {Object} formdata\n * @private\n */\n const updateModalBody = function(quiz, user, formdata) {\n if (typeof formdata === \"undefined\") {\n formdata = {};\n }\n\n let params = {\n 'jsonformdata': JSON.stringify(formdata),\n 'quizid': quiz,\n 'userid': user\n };\n\n modalObj.setBody(spinner);\n Str.get_string('useroverride', 'local_assessfreq').then((title) => {\n modalObj.setTitle(title);\n modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_override_form', contextid, params));\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: useroverride'));\n });\n };\n\n /**\n * Updates Moodle form with selected information.\n *\n * @param {Object} e\n * @private\n */\n function processModalForm(e) {\n e.preventDefault(); // Stop modal from closing.\n\n // Form data.\n let overrideform = modalObj.getRoot().find('form').serialize();\n let formjson = JSON.stringify(overrideform);\n\n // Handle invalid form fields for better UX.\n // I hate that I had to use JQuery for this.\n var invalid = $.merge(\n modalObj.getRoot().find('[aria-invalid=\"true\"]'),\n modalObj.getRoot().find('.error')\n );\n\n if (invalid.length) {\n invalid.first().focus();\n return;\n }\n\n // Submit form via ajax.\n Ajax.call([{\n methodname: 'local_assessfreq_process_override_form',\n args: {\n 'jsonformdata': formjson,\n 'quizid': quizid\n },\n }])[0].done(() => {\n // For submission succeeded.\n modalObj.setBody(spinner);\n modalObj.hide();\n if (hoursFilter) {\n callback(quizid, hoursFilter);\n } else {\n callback(quizid);\n }\n }).fail(() => {\n // Form submission failed server side, redisplay with errors.\n updateModalBody(quizid, userid, overrideform);\n });\n }\n\n /**\n * Display the Modal form.\n */\n OverrideModal.displayModalForm = function(quiz, user, hours = null) {\n quizid = quiz;\n userid = user;\n hoursFilter = hours;\n updateModalBody(quiz, user);\n modalObj.show();\n };\n\n /**\n * Initialise method for quiz dashboard rendering.\n */\n OverrideModal.init = function(context, callbackFunction, hours = null) {\n contextid = context;\n callback = callbackFunction;\n hoursFilter = hours;\n createModal();\n };\n\n return OverrideModal;\n});\n"],"file":"override_modal.min.js"} \ No newline at end of file +{"version":3,"file":"override_modal.min.js","sources":["../src/override_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for report card display and processing.\n *\n * @package\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n ['jquery', 'core/str', 'core/modal', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax'],\n function($, Str, Modal, ModalFactory, ModalEvents, Fragment, Ajax) {\n\n /**\n * Module level variables.\n */\n let OverrideModal = {};\n let contextid;\n let activitytype;\n let modalObj;\n let activityid;\n let userid;\n let tableHandler;\n\n const spinner = '

'\n + ''\n + '

';\n\n /**\n * Create the modal window.\n *\n * @private\n */\n const createModal = function() {\n Str.get_string('loading').then((title) => {\n // Create the Modal.\n ModalFactory.create({\n type: ModalFactory.types.DEFAULT,\n title: title,\n body: spinner,\n large: true\n })\n .done((modal) => {\n modalObj = modal;\n // Explicitly handle form click events.\n modalObj.getRoot().on('click', '#id_submitbutton', processModalForm);\n modalObj.getRoot().on('click', '#id_cancel', function(e) {\n e.preventDefault();\n modalObj.setBody(spinner);\n modalObj.hide();\n });\n });\n });\n };\n\n /**\n * Updates the body of the modal window.\n *\n * @param {Integer} activity\n * @param {Integer} user\n * @param {Object} formdata\n * @private\n */\n const updateModalBody = function(activity, user, formdata) {\n if (typeof formdata === \"undefined\") {\n formdata = {};\n }\n\n let params = {\n 'jsonformdata': JSON.stringify(formdata),\n 'activitytype': activitytype,\n 'activityid': activity,\n 'userid': user\n };\n\n modalObj.setBody(spinner);\n Str.get_string('modal:useroverride', 'local_assessfreq').then((title) => {\n modalObj.setTitle(title);\n modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_override_form', contextid, params));\n });\n };\n\n /**\n * Updates Moodle form with selected information.\n *\n * @param {Object} e\n * @private\n */\n function processModalForm(e) {\n e.preventDefault(); // Stop modal from closing.\n\n // Form data.\n let overrideform = modalObj.getRoot().find('form').serialize();\n let formjson = JSON.stringify(overrideform);\n\n // Handle invalid form fields for better UX.\n // I hate that I had to use JQuery for this.\n let invalid = $.merge(\n modalObj.getRoot().find('[aria-invalid=\"true\"]'),\n modalObj.getRoot().find('.error')\n );\n\n if (invalid.length) {\n invalid.first().focus();\n return;\n }\n\n // Submit form via ajax.\n Ajax.call([{\n methodname: 'local_assessfreq_process_override_form',\n args: {\n 'jsonformdata': formjson,\n 'activityid': activityid,\n 'activitytype': activitytype,\n },\n }])[0].done(() => {\n // For submission succeeded.\n modalObj.setBody(spinner);\n modalObj.hide();\n if (tableHandler !== undefined) {\n tableHandler.getTable();\n }\n }).fail(() => {\n // Form submission failed server side, redisplay with errors.\n updateModalBody(activityid, userid, overrideform);\n });\n }\n\n /**\n * Display the Modal form.\n * @param {Integer} activity\n * @param {Integer} user\n */\n OverrideModal.displayModalForm = function(activity, user) {\n activityid = activity;\n userid = user;\n updateModalBody(activityid, user);\n modalObj.show();\n };\n\n /**\n * Initialise method for dashboard rendering.\n * @param {Integer} context\n * @param {String} module\n * @param {TableHandler} tablehandler If defined will trigger a table refresh on form save.\n */\n OverrideModal.init = function(context, module, tablehandler = undefined) {\n activitytype = module;\n contextid = context;\n tableHandler = tablehandler;\n createModal();\n };\n\n return OverrideModal;\n }\n);\n"],"names":["define","$","Str","Modal","ModalFactory","ModalEvents","Fragment","Ajax","contextid","activitytype","modalObj","activityid","userid","tableHandler","OverrideModal","spinner","createModal","get_string","then","title","create","type","types","DEFAULT","body","large","done","modal","getRoot","on","processModalForm","e","preventDefault","setBody","hide","updateModalBody","activity","user","formdata","params","JSON","stringify","setTitle","loadFragment","overrideform","find","serialize","formjson","invalid","merge","length","first","focus","call","methodname","args","undefined","getTable","fail","displayModalForm","show","init","context","module","tablehandler"],"mappings":";;;;;;;AAuBAA,yCACI,CAAC,SAAU,WAAY,aAAc,qBAAsB,oBAAqB,gBAAiB,cACjG,SAASC,EAAGC,IAAKC,MAAOC,aAAcC,YAAaC,SAAUC,UAMrDC,UACAC,aACAC,SACAC,WACAC,OACAC,aANAC,cAAgB,SAQdC,QAAU,sFASVC,YAAc,WAChBd,IAAIe,WAAW,WAAWC,MAAMC,QAE5Bf,aAAagB,OAAO,CAChBC,KAAMjB,aAAakB,MAAMC,QACzBJ,MAAOA,MACPK,KAAMT,QACNU,OAAO,IAENC,MAAMC,QACHjB,SAAWiB,MAEXjB,SAASkB,UAAUC,GAAG,QAAS,mBAAoBC,kBACnDpB,SAASkB,UAAUC,GAAG,QAAS,cAAc,SAASE,GAClDA,EAAEC,iBACFtB,SAASuB,QAAQlB,SACjBL,SAASwB,iBAcvBC,gBAAkB,SAASC,SAAUC,KAAMC,eACrB,IAAbA,WACPA,SAAW,QAGXC,OAAS,cACOC,KAAKC,UAAUH,uBACf7B,wBACF2B,gBACJC,MAGd3B,SAASuB,QAAQlB,SACjBb,IAAIe,WAAW,qBAAsB,oBAAoBC,MAAMC,QAC3DT,SAASgC,SAASvB,OAClBT,SAASuB,QAAQ3B,SAASqC,aAAa,mBAAoB,oBAAqBnC,UAAW+B,sBAU1FT,iBAAiBC,GACtBA,EAAEC,qBAGEY,aAAelC,SAASkB,UAAUiB,KAAK,QAAQC,YAC/CC,SAAWP,KAAKC,UAAUG,cAI1BI,QAAU/C,EAAEgD,MACZvC,SAASkB,UAAUiB,KAAK,yBACxBnC,SAASkB,UAAUiB,KAAK,WAGxBG,QAAQE,OACRF,QAAQG,QAAQC,QAKpB7C,KAAK8C,KAAK,CAAC,CACPC,WAAY,yCACZC,KAAM,cACcR,oBACFpC,wBACEF,iBAEpB,GAAGiB,MAAK,KAERhB,SAASuB,QAAQlB,SACjBL,SAASwB,YACYsB,IAAjB3C,cACAA,aAAa4C,cAElBC,MAAK,KAEJvB,gBAAgBxB,WAAYC,OAAQgC,wBAS5C9B,cAAc6C,iBAAmB,SAASvB,SAAUC,MAChD1B,WAAayB,SACbxB,OAASyB,KACTF,gBAAgBxB,WAAY0B,MAC5B3B,SAASkD,QASb9C,cAAc+C,KAAO,SAASC,QAASC,YAAQC,yEAAeR,EAC1D/C,aAAesD,OACfvD,UAAYsD,QACZjD,aAAemD,aACfhD,eAGGF"} \ No newline at end of file diff --git a/amd/build/student_search.min.js b/amd/build/student_search.min.js deleted file mode 100644 index 5e70d3fd..00000000 --- a/amd/build/student_search.min.js +++ /dev/null @@ -1,2 +0,0 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("local_assessfreq/student_search",["exports","jquery","core/notification","local_assessfreq/override_modal","local_assessfreq/table_handler","local_assessfreq/user_preferences"],function(a,b,c,d,e,f){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=i(b);c=i(c);d=i(d);e=h(e);f=h(f);function g(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;g=function(){return a};return a}function h(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=g();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function i(a){return a&&a.__esModule?a:{default:a}}var j,k=4,l=1,m=60,n,o=function(){var a=0.\n\n/**\n * Javascript for student search display and processing.\n *\n * @module local_assessfreq/student_search\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Notification from 'core/notification';\nimport OverrideModal from 'local_assessfreq/override_modal';\nimport * as TableHandler from 'local_assessfreq/table_handler';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\n\n/**\n * Module level variables.\n */\nvar contextid;\nvar hoursAhead = 4;\nvar hoursBehind = 1;\nvar refreshPeriod = 60;\nvar counterid;\n\n/**\n * Function for refreshing the counter.\n *\n * @param {boolean} reset the current count process.\n */\nconst refreshCounter = (reset = true) => {\n let progressElement = document.getElementById('local-assessfreq-period-progress');\n\n // Reset the current count process.\n if (reset === true) {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n }\n\n // Exit early if there is already a counter running.\n if (counterid) {\n return;\n }\n\n counterid = setInterval(() => {\n let progressWidthAria = progressElement.getAttribute('aria-valuenow');\n const progressStep = 100 / refreshPeriod;\n\n if ((progressWidthAria - progressStep) > 0) {\n progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%');\n progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep));\n } else {\n clearInterval(counterid);\n counterid = null;\n progressElement.setAttribute('style', 'width: 100%');\n progressElement.setAttribute('aria-valuenow', 100);\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null);\n refreshCounter();\n }\n }, (1000));\n};\n\n/**\n * Process the hours ahead event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchAheadSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursahead_preference', hours)\n .then(() => {\n hoursAhead = hours;\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours ahead'));\n });\n }\n};\n\n/**\n * Process the hours behind event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchBehindSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursbehind_preference', hours)\n .then(() => {\n hoursBehind = hours;\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: hours behind'));\n });\n }\n};\n\n/**\n * Handle processing of refresh and period button actions.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst refreshAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') {\n refreshCounter(true);\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null);\n } else if (element.tagName.toLowerCase() === 'a') {\n refreshPeriod = element.dataset.period;\n refreshCounter(true);\n UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod);\n }\n};\n\n/**\n * Initialise method for student search.\n *\n * @param {integer} context The current context id.\n */\nexport const init = (context) => {\n contextid = context;\n TableHandler.init(\n 0,\n contextid,\n 'local-assessfreq-student-search-table',\n 'local-assessfreq-student-search',\n 'get_student_search_table',\n 'local_assessfreq_student_search_table_rows_preference',\n 'local-assessfreq-quiz-student-table-search',\n 'local_assessfreq_student_search_table',\n 'local_assessfreq_set_table_preference'\n );\n\n // Add required initial event listeners.\n let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-student-table-search');\n let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-student-table-search-reset');\n let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-student-table-rows');\n let tableSearchAheadElement = document.getElementById('local-assessfreq-quiz-student-table-hoursahead');\n let tableSearchBehindElement = document.getElementById('local-assessfreq-quiz-student-table-hoursbehind');\n let refreshElement = document.getElementById('local-assessfreq-period-container');\n\n tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch);\n tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch);\n tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset);\n tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet);\n tableSearchAheadElement.addEventListener('click', tableSearchAheadSet);\n tableSearchBehindElement.addEventListener('click', tableSearchBehindSet);\n refreshElement.addEventListener('click', refreshAction);\n\n $.when(\n UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursahead_preference')\n .then((response) => {\n hoursAhead = response.preferences[0].value ? response.preferences[0].value : 4;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursahead'));\n }),\n UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursbehind_preference')\n .then((response) => {\n hoursBehind = response.preferences[0].value ? response.preferences[0].value : 1;\n })\n .fail(() => {\n Notification.exception(new Error('Failed to get use preference: hoursahead'));\n })\n ).done(function() {\n TableHandler.getTable(0, [hoursAhead, hoursBehind], null);\n OverrideModal.init(context, TableHandler.getTable, [hoursAhead, hoursBehind]);\n });\n};\n"],"file":"student_search.min.js"} \ No newline at end of file diff --git a/amd/build/summary_participants.min.js b/amd/build/summary_participants.min.js deleted file mode 100644 index 9106e762..00000000 --- a/amd/build/summary_participants.min.js +++ /dev/null @@ -1,2 +0,0 @@ -define ("local_assessfreq/summary_participants",["core/fragment","core/templates","core/str","core/notification"],function(a,b,c,d){return{chart:function(e,f){e.forEach(function(e){var g=document.getElementById(e+"-summary-graph"),h={data:JSON.stringify({quiz:e,call:"participant_summary"})};a.loadFragment("local_assessfreq","get_quiz_chart",f,h).done(function(a){var e=JSON.parse(a);if(!0==e.hasdata){var f={withtable:!1,chartdata:JSON.stringify(e.chart),aspect:!1,legend:JSON.stringify({position:"left"})};b.render("local_assessfreq/chart",f).done(function(a,c){b.replaceNodeContents(g,a,c)}).fail(function(){d.exception(new Error("Failed to load chart template."))})}else{c.get_string("nodata","local_assessfreq").then(function(a){var b=document.createElement("h3");b.innerHTML=a;g.innerHTML=b.outerHTML}).catch(function(){d.exception(new Error("Failed to load string: nodata"))})}}).fail(function(){d.exception(new Error("Failed to load card."))})})}}}); -//# sourceMappingURL=summary_participants.min.js.map diff --git a/amd/build/summary_participants.min.js.map b/amd/build/summary_participants.min.js.map deleted file mode 100644 index 491ab4e4..00000000 --- a/amd/build/summary_participants.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/summary_participants.js"],"names":["define","Fragment","Templates","Str","Notification","chart","assessids","contextid","forEach","assessid","chartElement","document","getElementById","params","JSON","stringify","loadFragment","done","response","resObj","parse","hasdata","context","position","render","html","js","replaceNodeContents","fail","exception","Error","get_string","then","str","noDatastr","createElement","innerHTML","outerHTML","catch"],"mappings":"AAuBAA,OAAM,yCAAC,CAAC,eAAD,CAAkB,gBAAlB,CAAoC,UAApC,CAAgD,mBAAhD,CAAD,CACN,SAASC,CAAT,CAAmBC,CAAnB,CAA8BC,CAA9B,CAAmCC,CAAnC,CAAiD,CAgD7C,MA3Cc,CAENC,KAFM,CAEE,SAASC,CAAT,CAAoBC,CAApB,CAA+B,CAC3CD,CAAS,CAACE,OAAV,CAAkB,SAACC,CAAD,CAAc,IACxBC,CAAAA,CAAY,CAAGC,QAAQ,CAACC,cAAT,CAAwBH,CAAQ,CAAG,gBAAnC,CADS,CAExBI,CAAM,CAAG,CAAC,KAAQC,IAAI,CAACC,SAAL,CAAe,CAAC,KAASN,CAAV,CAAoB,KAAQ,qBAA5B,CAAf,CAAT,CAFe,CAI5BR,CAAQ,CAACe,YAAT,CAAsB,kBAAtB,CAA0C,gBAA1C,CAA4DT,CAA5D,CAAuEM,CAAvE,EACCI,IADD,CACM,SAACC,CAAD,CAAc,CAChB,GAAIC,CAAAA,CAAM,CAAGL,IAAI,CAACM,KAAL,CAAWF,CAAX,CAAb,CACA,GAAI,IAAAC,CAAM,CAACE,OAAX,CAA4B,IAEpBC,CAAAA,CAAO,CAAG,CACV,YADU,CAEV,UAAcR,IAAI,CAACC,SAAL,CAAeI,CAAM,CAACd,KAAtB,CAFJ,CAGV,SAHU,CAIV,OAAWS,IAAI,CAACC,SAAL,CALF,CAACQ,QAAQ,CAAE,MAAX,CAKE,CAJD,CAFU,CAQxBrB,CAAS,CAACsB,MAAV,CAAiB,wBAAjB,CAA2CF,CAA3C,EAAoDL,IAApD,CAAyD,SAACQ,CAAD,CAAOC,CAAP,CAAc,CAEnExB,CAAS,CAACyB,mBAAV,CAA8BjB,CAA9B,CAA4Ce,CAA5C,CAAkDC,CAAlD,CACH,CAHD,EAGGE,IAHH,CAGQ,UAAM,CACVxB,CAAY,CAACyB,SAAb,CAAuB,GAAIC,CAAAA,KAAJ,CAAU,gCAAV,CAAvB,CAEH,CAND,CAQH,CAhBD,IAgBO,CACH3B,CAAG,CAAC4B,UAAJ,CAAe,QAAf,CAAyB,kBAAzB,EAA6CC,IAA7C,CAAkD,SAACC,CAAD,CAAS,CACvD,GAAMC,CAAAA,CAAS,CAAGvB,QAAQ,CAACwB,aAAT,CAAuB,IAAvB,CAAlB,CACAD,CAAS,CAACE,SAAV,CAAsBH,CAAtB,CACAvB,CAAY,CAAC0B,SAAb,CAAyBF,CAAS,CAACG,SAEtC,CALD,EAKGC,KALH,CAKS,UAAM,CACXlC,CAAY,CAACyB,SAAb,CAAuB,GAAIC,CAAAA,KAAJ,CAAU,+BAAV,CAAvB,CACH,CAPD,CAQH,CACJ,CA7BD,EA6BGF,IA7BH,CA6BQ,UAAM,CACVxB,CAAY,CAACyB,SAAb,CAAuB,GAAIC,CAAAA,KAAJ,CAAU,sBAAV,CAAvB,CAEH,CAhCD,CAiCH,CArCD,CAsCH,CAzCa,CA4CjB,CAlDK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for summary participants graph.\n *\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/fragment', 'core/templates', 'core/str', 'core/notification'],\nfunction(Fragment, Templates, Str, Notification) {\n\n /**\n * Module level variables.\n */\n var Summary = {};\n\n Summary.chart = function(assessids, contextid) {\n assessids.forEach((assessid) => {\n let chartElement = document.getElementById(assessid + '-summary-graph');\n let params = {'data': JSON.stringify({'quiz' : assessid, 'call': 'participant_summary'})};\n\n Fragment.loadFragment('local_assessfreq', 'get_quiz_chart', contextid, params)\n .done((response) => {\n let resObj = JSON.parse(response);\n if (resObj.hasdata == true) {\n let legend = {position: 'left'};\n let context = {\n 'withtable' : false,\n 'chartdata' : JSON.stringify(resObj.chart),\n 'aspect' : false,\n 'legend' : JSON.stringify(legend)\n };\n Templates.render('local_assessfreq/chart', context).done((html, js) => {\n // Load card body.\n Templates.replaceNodeContents(chartElement, html, js);\n }).fail(() => {\n Notification.exception(new Error('Failed to load chart template.'));\n return;\n });\n return;\n } else {\n Str.get_string('nodata', 'local_assessfreq').then((str) => {\n const noDatastr = document.createElement('h3');\n noDatastr.innerHTML = str;\n chartElement.innerHTML = noDatastr.outerHTML;\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: nodata'));\n });\n }\n }).fail(() => {\n Notification.exception(new Error('Failed to load card.'));\n return;\n });\n });\n };\n\n return Summary;\n});"],"file":"summary_participants.min.js"} \ No newline at end of file diff --git a/amd/build/table_handler.min.js b/amd/build/table_handler.min.js index 59bb5900..674d05ff 100644 --- a/amd/build/table_handler.min.js +++ b/amd/build/table_handler.min.js @@ -1,2 +1,11 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("local_assessfreq/table_handler",["exports","core/ajax","core/fragment","core/notification","core/templates","local_assessfreq/debouncer","local_assessfreq/override_modal","local_assessfreq/user_preferences"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=a.tableSortButtonAction=a.tableSearchRowSet=a.tableSearchReset=a.tableSearch=a.getTable=void 0;b=k(b);c=k(c);d=k(d);e=k(e);f=j(f);g=k(g);h=j(h);function i(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;i=function(){return a};return a}function j(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=i();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function k(a){return a&&a.__esModule?a:{default:a}}var l,m,n,o,p,q=0,r=!1,s,t,u,v,w,x=function(a){var b=1 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_fragment=_interopRequireDefault(_fragment),_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates),Debouncer=_interopRequireWildcard(Debouncer),_override_modal=_interopRequireDefault(_override_modal),UserPreference=_interopRequireWildcard(UserPreference);return _exports.default=class{constructor(activity,context,tableElementId,tableFragmentComponent,tableFragmentValue,tableRowPreference,tableSortPreference,tableSearchElement){let tableId=arguments.length>8&&void 0!==arguments[8]?arguments[8]:null,tableMethodName=arguments.length>9&&void 0!==arguments[9]?arguments[9]:null;this.activityId=activity,this.contextId=context,this.elementId=tableElementId,this.fragmentComponent=tableFragmentComponent,this.fragmentValue=tableFragmentValue,this.rowPreference=tableRowPreference,this.sortPreference=tableSortPreference,this.searchElement=tableSearchElement,this.id=tableId,this.methodName=tableMethodName,this.overridden=!1}getTable=(()=>{var _this=this;return function(){let page=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;_this.overridden=!1;let search=document.getElementById(_this.searchElement).value.trim(),tableElement=document.getElementById(_this.elementId),spinner=tableElement.getElementsByClassName("overlay-icon-container")[0],tableBody=tableElement.getElementsByClassName("table-body")[0],values={search:search,page:page};_this.activityId>0&&(values.activityid=_this.activityId);let params={data:JSON.stringify(values)};spinner.classList.remove("hide"),_fragment.default.loadFragment(_this.fragmentComponent,_this.fragmentValue,_this.contextId,params).done(((response,js)=>{tableBody.innerHTML=response,js&&_templates.default.runTemplateJS(js),spinner.classList.add("hide"),_this.tableEventListeners()})).fail((()=>{_notification.default.exception(new Error("Failed to update table."))}))}})();debounceTable=Debouncer.debouncer((()=>{this.getTable()}),750);tableSort=event=>{event.preventDefault();let sortArray={};const linkUrl=new URL(event.target.closest("a").href),targetSortBy=linkUrl.searchParams.get("tsort");let targetSortOrder=linkUrl.searchParams.get("tdir");""===targetSortOrder&&(targetSortOrder="4"),sortArray[targetSortBy]=targetSortOrder,_ajax.default.call([{methodname:this.methodName,args:{tableid:this.id,preference:"sortby",values:JSON.stringify(sortArray)}}])[0].then((()=>{this.getTable()}))};tableHide=event=>{event.preventDefault();let hideArray={};const linkUrl=new URL(event.target.closest("a").href),links=document.getElementById(this.elementId).querySelectorAll("a");let targetAction,targetColumn,action,column;-1!==linkUrl.search.indexOf("thide")?(targetAction="hide",targetColumn=linkUrl.searchParams.get("thide")):(targetAction="show",targetColumn=linkUrl.searchParams.get("tshow"));for(let i=0;i{this.getTable()}))};tableReset=event=>{event.preventDefault(),_ajax.default.call([{methodname:this.methodName,args:{tableid:this.id,preference:"reset",values:JSON.stringify({})}}])[0].then((()=>{this.getTable()}))};tableSearch=event=>"Meta"!==event.key&&!event.ctrlKey&&((0===event.target.value.length||event.target.value.length>2)&&this.debounceTable(),!0);tableSearchReset=()=>{let tableSearchInputElement=document.getElementById(this.searchElement);tableSearchInputElement.value="",tableSearchInputElement.focus(),this.getTable()};tableSearchRowSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){let rows=event.target.dataset.metric;UserPreference.setUserPreference(this.rowPreference,rows).then((()=>{this.getTable()})).fail((()=>{_notification.default.exception(new Error("Failed to update user preference: rows"))}))}};tableNav=event=>{event.preventDefault();const page=new URL(event.target.closest("a").href).searchParams.get("page");page&&this.getTable(page)};tableSortButtonAction=event=>{event.preventDefault();var element=event.target;if("a"===element.tagName.toLowerCase()&&element.dataset.sort!==this.sortValue){this.sortValue=element.dataset.sort;let links=element.parentNode.getElementsByTagName("a");for(let i=0;i{const tableElement=document.getElementById(this.elementId),links=tableElement.querySelectorAll("a"),resetLink=tableElement.getElementsByClassName("resettable"),overrideLinks=tableElement.getElementsByClassName("action-icon override"),disabledLinks=tableElement.getElementsByClassName("action-icon disabled"),tableNavElement=tableElement.querySelectorAll("nav");for(let i=0;i0&&resetLink[0].addEventListener("click",this.tableReset);for(let i=0;i{event.preventDefault()}));tableNavElement.forEach((navElement=>{navElement.addEventListener("click",this.tableNav)}))};triggerOverrideModal=event=>{event.preventDefault();let userid=event.target.closest("a").id.substring(25);if(userid.includes("-")){let elements=userid.split("-");this.activityId=elements.pop(),userid=elements.pop()}_override_modal.default.displayModalForm(this.activityId,userid,this.hoursFilter)}},_exports.default})); + +//# sourceMappingURL=table_handler.min.js.map \ No newline at end of file diff --git a/amd/build/table_handler.min.js.map b/amd/build/table_handler.min.js.map index 4b50485e..3cb88dd6 100644 --- a/amd/build/table_handler.min.js.map +++ b/amd/build/table_handler.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/table_handler.js"],"names":["cardElement","contextId","elementId","fragmentValue","hoursFilter","quizId","overridden","rowPreference","sortValue","searchElement","id","methodName","getTable","quiz","hours","sortValueTable","page","search","document","getElementById","value","trim","tableElement","spinner","getElementsByClassName","tableBody","values","hoursahead","hoursbehind","sortArray","split","sortOn","direction","sorton","params","JSON","stringify","classList","remove","Fragment","loadFragment","done","response","js","innerHTML","Templates","runTemplateJS","add","tableEventListeners","fail","Notification","exception","Error","debounceTable","Debouncer","debouncer","tableSort","event","preventDefault","linkUrl","URL","target","closest","href","targetSortBy","searchParams","get","targetSortOrder","Ajax","call","methodname","args","tableid","preference","then","tableHide","hideArray","links","querySelectorAll","targetAction","targetColumn","action","column","indexOf","i","hideLinkUrl","length","tableReset","tableSearch","key","ctrlKey","tableSearchReset","tableSearchInputElement","focus","tableSearchRowSet","tagName","toLowerCase","rows","dataset","metric","UserPreference","setUserPreference","tableNav","tableSortButtonAction","element","sort","parentNode","getElementsByTagName","tableNavElement","tableCardElement","resetLink","overrideLinks","disabledLinks","addEventListener","triggerOverrideModal","forEach","navElement","userid","substring","includes","elements","pop","OverrideModal","displayModalForm","init","context","tableElementId","tableFragmentValue","tableRowPreference","tableSearchElement","tableId","tableMethodName"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Table handler JS module.\n *\n * @module local_assessfreq/table_handler\n * @package local_assessfreq\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Fragment from 'core/fragment';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport * as Debouncer from 'local_assessfreq/debouncer';\nimport OverrideModal from 'local_assessfreq/override_modal';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\n\n/**\n * Module level variables.\n */\nlet cardElement;\nlet contextId;\nlet elementId;\nlet fragmentValue;\nlet hoursFilter;\nlet quizId = 0;\nlet overridden = false;\nlet rowPreference;\nlet sortValue;\nlet searchElement;\n\n/**\n * Table id variable.\n *\n * @type {string}\n */\nlet id;\n\n/**\n * Table method name variable.\n *\n * @type {string}\n */\nlet methodName;\n\n/**\n * Display the table that contains all the students in the exam as well as their attempts.\n *\n * @param {int} quiz The Quiz Id.\n * @param {array|null} hours Array with hour ahead or behind preference.\n * @param {string|null} sortValueTable Sort preference.\n * @param {int|string|null} page Page number.\n */\nexport const getTable = (quiz, hours = null, sortValueTable = null, page) => {\n if (typeof page === \"undefined\" || overridden === true) {\n page = 0;\n }\n\n overridden = false;\n\n let search = document.getElementById(searchElement).value.trim();\n let tableElement = document.getElementById(elementId);\n let spinner = tableElement.getElementsByClassName('overlay-icon-container')[0];\n let tableBody = tableElement.getElementsByClassName('table-body')[0];\n let values = {'search': search, 'page': page};\n\n // Add values to Object depending on dashboard type.\n if (quiz > 0) {\n quizId = quiz;\n values.quiz = quizId;\n }\n if (hours) {\n hoursFilter = hours;\n values.hoursahead = hoursFilter[0];\n values.hoursbehind = hoursFilter[1];\n }\n if (sortValueTable) {\n sortValue = sortValueTable;\n let sortArray = sortValue.split('_');\n let sortOn = sortArray[0];\n let direction = sortArray[1];\n values.sorton = sortOn;\n values.direction = direction;\n }\n\n let params = {'data': JSON.stringify(values)};\n\n spinner.classList.remove('hide'); // Show spinner if not already shown.\n Fragment.loadFragment('local_assessfreq', fragmentValue, contextId, params)\n .done((response, js) => {\n tableBody.innerHTML = response;\n if (js) {\n Templates.runTemplateJS(js); // Magic call the initialises JS from template included in response template HTML.\n }\n spinner.classList.add('hide');\n tableEventListeners(); // Re-add table event listeners.\n\n }).fail(() => {\n Notification.exception(new Error('Failed to update table.'));\n });\n};\n\n/**\n * This stops the ajax method that updates the table from being updated\n * while the user is still checking options.\n *\n */\nconst debounceTable = Debouncer.debouncer(() => {\n getTable(quizId, hoursFilter, sortValue);\n}, 750);\n\n/**\n * Process the sort click events from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSort = (event) => {\n event.preventDefault();\n\n let sortArray = {};\n const linkUrl = new URL(event.target.closest('a').href);\n const targetSortBy = linkUrl.searchParams.get('tsort');\n let targetSortOrder = linkUrl.searchParams.get('tdir');\n\n // We want to flip the clicked column.\n if (targetSortOrder === '') {\n targetSortOrder = \"4\";\n }\n\n sortArray[targetSortBy] = targetSortOrder;\n\n // Set option via ajax.\n Ajax.call([{\n methodname: methodName,\n args: {\n tableid: id,\n preference: 'sortby',\n values: JSON.stringify(sortArray)\n },\n }])[0].then(() => {\n getTable(quizId, hoursFilter, sortValue); // Reload the table.\n });\n\n};\n\n/**\n * Process the sort click events from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableHide = (event) => {\n event.preventDefault();\n\n let hideArray = {};\n const linkUrl = new URL(event.target.closest('a').href);\n const tableElement = document.getElementById(elementId);\n const links = tableElement.querySelectorAll('a');\n let targetAction;\n let targetColumn;\n let action;\n let column;\n\n if (linkUrl.search.indexOf('thide') !== -1) {\n targetAction = 'hide';\n targetColumn = linkUrl.searchParams.get('thide');\n } else {\n targetAction = 'show';\n targetColumn = linkUrl.searchParams.get('tshow');\n }\n\n for (let i = 0; i < links.length; i++) {\n let hideLinkUrl = new URL(links[i].href);\n if (hideLinkUrl.search.indexOf('thide') !== -1) {\n action = 'hide';\n column = hideLinkUrl.searchParams.get('thide');\n } else {\n action = 'show';\n column = hideLinkUrl.searchParams.get('tshow');\n }\n\n if (action === 'show') {\n hideArray[column] = 1;\n }\n\n }\n\n hideArray[targetColumn] = (targetAction === 'hide') ? 1 : 0; // We want to flip the clicked column.\n\n // Set option via ajax.\n Ajax.call([{\n methodname: methodName,\n args: {\n tableid: id,\n preference: 'collapse',\n values: JSON.stringify(hideArray)\n },\n }])[0].then(() => {\n getTable(quizId, hoursFilter, sortValue); // Reload the table.\n });\n\n};\n\n/**\n * Process the reset click event from the table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableReset = (event) => {\n event.preventDefault();\n\n // Set option via ajax.\n Ajax.call([{\n methodname: methodName,\n args: {\n tableid: id,\n preference: 'reset',\n values: JSON.stringify({})\n },\n }])[0].then(() => {\n getTable(quizId, hoursFilter, sortValue); // Reload the table.\n });\n\n};\n\n/**\n * Process the search events from the student table.\n *\n */\nexport const tableSearch = (event) => {\n if (event.key === 'Meta' || event.ctrlKey) {\n return false;\n }\n\n if (event.target.value.length === 0 || event.target.value.length > 2) {\n debounceTable();\n }\n};\n\n/**\n * Process the search reset click event from the student table.\n *\n */\nexport const tableSearchReset = () => {\n let tableSearchInputElement = document.getElementById(searchElement);\n tableSearchInputElement.value = '';\n tableSearchInputElement.focus();\n getTable(quizId, hoursFilter, sortValue);\n};\n\n/**\n * Process the row set event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nexport const tableSearchRowSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let rows = event.target.dataset.metric;\n UserPreference.setUserPreference(rowPreference, rows)\n .then(() => {\n getTable(quizId, hoursFilter, sortValue); // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: rows'));\n });\n }\n};\n\n/**\n * Process the nav event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableNav = (event) => {\n event.preventDefault();\n\n const linkUrl = new URL(event.target.closest('a').href);\n const page = linkUrl.searchParams.get('page');\n\n if (page) {\n getTable(quizId, hoursFilter, sortValue, page);\n }\n};\n\n/**\n * Get and process the selected assessment metric from the dropdown for the heatmap display,\n * and update the corresponding user preference.\n *\n * @param {Event} event The triggered event for the element.\n */\nexport const tableSortButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.sort !== sortValue) {\n sortValue = element.dataset.sort;\n\n let links = element.parentNode.getElementsByTagName('a');\n for (let i = 0; i < links.length; i++) {\n links[i].classList.remove('active');\n }\n\n element.classList.add('active');\n\n // Save selection as a user preference.\n UserPreference.setUserPreference('local_assessfreq_quiz_table_inprogress_sort_preference', sortValue);\n\n debounceTable(); // Call function to update table.\n\n }\n};\n\n/**\n * Re-add event listeners when the student table is updated.\n */\nconst tableEventListeners = () => {\n const tableElement = document.getElementById(elementId);\n let tableNavElement;\n if (cardElement) {\n const tableCardElement = document.getElementById(cardElement);\n const links = tableElement.querySelectorAll('a');\n const resetLink = tableElement.getElementsByClassName('resettable');\n const overrideLinks = tableElement.getElementsByClassName('action-icon override');\n const disabledLinks = tableElement.getElementsByClassName('action-icon disabled');\n tableNavElement = tableCardElement.querySelectorAll('nav'); // There are two nav paging elements per table.\n\n for (let i = 0; i < links.length; i++) {\n let linkUrl = new URL(links[i].href);\n if (linkUrl.search.indexOf('thide') !== -1 || linkUrl.search.indexOf('tshow') !== -1) {\n links[i].addEventListener('click', tableHide);\n } else if (linkUrl.search.indexOf('tsort') !== -1) {\n links[i].addEventListener('click', tableSort);\n }\n\n }\n\n if (resetLink.length > 0) {\n resetLink[0].addEventListener('click', tableReset);\n }\n\n for (let i = 0; i < overrideLinks.length; i++) {\n overrideLinks[i].addEventListener('click', triggerOverrideModal);\n }\n\n for (let i = 0; i < disabledLinks.length; i++) {\n disabledLinks[i].addEventListener('click', (event) => {\n event.preventDefault();\n });\n }\n } else {\n tableNavElement = tableElement.querySelectorAll('nav');\n }\n\n tableNavElement.forEach((navElement) => {\n navElement.addEventListener('click', tableNav);\n });\n};\n\n/**\n * Trigger the override modal form. Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst triggerOverrideModal = (event) => {\n event.preventDefault();\n let userid = event.target.closest('a').id.substring(25);\n if (userid.includes('-')) {\n let elements = userid.split('-');\n quizId = elements.pop();\n userid = elements.pop();\n }\n\n OverrideModal.displayModalForm(quizId, userid, hoursFilter);\n};\n\n/**\n * Initialise method for table handler.\n *\n * @param {int} quiz The quiz id.\n * @param {int} context The context id.\n * @param {string} tableCardElement The table card element.\n * @param {string} tableElementId The table element id.\n * @param {string} tableFragmentValue The table fragment value.\n * @param {string} tableRowPreference The table row preference.\n * @param {string} tableSearchElement The table search element.\n * @param {string|null} tableId The table id.\n * @param {string|null} tableMethodName The table method name.\n */\nexport const init = (quiz,\n context,\n tableCardElement,\n tableElementId,\n tableFragmentValue,\n tableRowPreference,\n tableSearchElement,\n tableId = null,\n tableMethodName = null) => {\n quizId = quiz;\n contextId = context;\n cardElement = tableCardElement;\n elementId = tableElementId;\n fragmentValue = tableFragmentValue;\n rowPreference = tableRowPreference;\n searchElement = tableSearchElement;\n id = tableId;\n methodName = tableMethodName;\n};\n"],"file":"table_handler.min.js"} \ No newline at end of file +{"version":3,"file":"table_handler.min.js","sources":["../src/table_handler.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Table handler JS module.\n *\n * @module local_assessfreq/table_handler\n * @package\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Fragment from 'core/fragment';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport * as Debouncer from 'local_assessfreq/debouncer';\nimport OverrideModal from 'local_assessfreq/override_modal';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\n\nexport default class TableHandler {\n\n constructor(activity,\n context,\n tableElementId,\n tableFragmentComponent,\n tableFragmentValue,\n tableRowPreference,\n tableSortPreference,\n tableSearchElement,\n tableId = null,\n tableMethodName = null) {\n this.activityId = activity;\n this.contextId = context;\n this.elementId = tableElementId;\n this.fragmentComponent = tableFragmentComponent;\n this.fragmentValue = tableFragmentValue;\n this.rowPreference = tableRowPreference;\n this.sortPreference = tableSortPreference;\n this.searchElement = tableSearchElement;\n this.id = tableId;\n this.methodName = tableMethodName;\n this.overridden = false;\n }\n\n /**\n * Display the table that contains all the students in the exam as well as their attempts.\n *\n * @param {int|string|null} page Page number.\n */\n getTable = (page = 0) => {\n this.overridden = false;\n\n let search = document.getElementById(this.searchElement).value.trim();\n let tableElement = document.getElementById(this.elementId);\n let spinner = tableElement.getElementsByClassName('overlay-icon-container')[0];\n let tableBody = tableElement.getElementsByClassName('table-body')[0];\n let values = {'search': search, 'page': page};\n\n // Add values to Object depending on dashboard type.\n if (this.activityId > 0) {\n values.activityid = this.activityId;\n }\n\n let params = {'data': JSON.stringify(values)};\n\n spinner.classList.remove('hide'); // Show spinner if not already shown.\n Fragment.loadFragment(this.fragmentComponent, this.fragmentValue, this.contextId, params)\n .done((response, js) => {\n tableBody.innerHTML = response;\n if (js) {\n Templates.runTemplateJS(js); // Magic call the initialises JS from template included in response template HTML.\n }\n spinner.classList.add('hide');\n this.tableEventListeners(); // Re-add table event listeners.\n\n }).fail(() => {\n Notification.exception(new Error('Failed to update table.'));\n });\n };\n\n /**\n * This stops the ajax method that updates the table from being updated\n * while the user is still checking options.\n *\n */\n debounceTable = Debouncer.debouncer(() => {\n this.getTable();\n }, 750);\n\n /**\n * Process the sort click events from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableSort = (event) => {\n event.preventDefault();\n\n let sortArray = {};\n const linkUrl = new URL(event.target.closest('a').href);\n const targetSortBy = linkUrl.searchParams.get('tsort');\n let targetSortOrder = linkUrl.searchParams.get('tdir');\n\n // We want to flip the clicked column.\n if (targetSortOrder === '') {\n targetSortOrder = \"4\";\n }\n\n sortArray[targetSortBy] = targetSortOrder;\n\n // Set option via ajax.\n // eslint-disable-next-line promise/catch-or-return\n Ajax.call([{\n methodname: this.methodName,\n args: {\n tableid: this.id,\n preference: 'sortby',\n values: JSON.stringify(sortArray)\n },\n // eslint-disable-next-line promise/always-return\n }])[0].then(() => {\n this.getTable(); // Reload the table.\n });\n\n };\n\n /**\n * Process the sort click events from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableHide = (event) => {\n event.preventDefault();\n\n let hideArray = {};\n const linkUrl = new URL(event.target.closest('a').href);\n const tableElement = document.getElementById(this.elementId);\n const links = tableElement.querySelectorAll('a');\n let targetAction;\n let targetColumn;\n let action;\n let column;\n\n if (linkUrl.search.indexOf('thide') !== -1) {\n targetAction = 'hide';\n targetColumn = linkUrl.searchParams.get('thide');\n } else {\n targetAction = 'show';\n targetColumn = linkUrl.searchParams.get('tshow');\n }\n\n for (let i = 0; i < links.length; i++) {\n let hideLinkUrl = new URL(links[i].href);\n if (hideLinkUrl.search.indexOf('thide') !== -1) {\n action = 'hide';\n column = hideLinkUrl.searchParams.get('thide');\n } else {\n action = 'show';\n column = hideLinkUrl.searchParams.get('tshow');\n }\n\n if (action === 'show') {\n hideArray[column] = 1;\n }\n }\n\n hideArray[targetColumn] = (targetAction === 'hide') ? 1 : 0; // We want to flip the clicked column.\n\n // Set option via ajax.\n // eslint-disable-next-line promise/catch-or-return\n Ajax.call([{\n methodname: this.methodName,\n args: {\n tableid: this.id,\n preference: 'collapse',\n values: JSON.stringify(hideArray)\n },\n // eslint-disable-next-line promise/always-return\n }])[0].then(() => {\n this.getTable(); // Reload the table.\n });\n\n };\n\n /**\n * Process the reset click event from the table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableReset = (event) => {\n event.preventDefault();\n\n // Set option via ajax.\n // eslint-disable-next-line promise/catch-or-return\n Ajax.call([{\n methodname: this.methodName,\n args: {\n tableid: this.id,\n preference: 'reset',\n values: JSON.stringify({})\n },\n // eslint-disable-next-line promise/always-return\n }])[0].then(() => {\n this.getTable(); // Reload the table.\n });\n\n };\n\n /**\n * Process the search events from the student table.\n *\n * @param {Event} event\n * @return {Boolean}\n */\n tableSearch = (event) => {\n if (event.key === 'Meta' || event.ctrlKey) {\n return false;\n }\n\n if (event.target.value.length === 0 || event.target.value.length > 2) {\n this.debounceTable();\n }\n return true;\n };\n\n /**\n * Process the search reset click event from the student table.\n *\n */\n tableSearchReset = () => {\n let tableSearchInputElement = document.getElementById(this.searchElement);\n tableSearchInputElement.value = '';\n tableSearchInputElement.focus();\n this.getTable();\n };\n\n /**\n * Process the row set event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableSearchRowSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n let rows = event.target.dataset.metric;\n UserPreference.setUserPreference(this.rowPreference, rows)\n // eslint-disable-next-line promise/always-return\n .then(() => {\n this.getTable(); // Reload the table.\n })\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference: rows'));\n });\n }\n };\n\n /**\n * Process the nav event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableNav = (event) => {\n event.preventDefault();\n\n const linkUrl = new URL(event.target.closest('a').href);\n const page = linkUrl.searchParams.get('page');\n\n if (page) {\n this.getTable(page);\n }\n };\n\n /**\n * Get and process the selected assessment metric from the dropdown for the heatmap display,\n * and update the corresponding user preference.\n *\n * @param {Event} event The triggered event for the element.\n */\n tableSortButtonAction = (event) => {\n event.preventDefault();\n var element = event.target;\n\n if (element.tagName.toLowerCase() === 'a' && element.dataset.sort !== this.sortValue) {\n this.sortValue = element.dataset.sort;\n\n let links = element.parentNode.getElementsByTagName('a');\n for (let i = 0; i < links.length; i++) {\n links[i].classList.remove('active');\n }\n\n element.classList.add('active');\n\n // Save selection as a user preference.\n UserPreference.setUserPreference(this.sortPreference, this.sortValue);\n\n this.debounceTable(); // Call function to update table.\n }\n };\n\n /**\n * Re-add event listeners when the student table is updated.\n */\n tableEventListeners = () => {\n const tableElement = document.getElementById(this.elementId);\n const links = tableElement.querySelectorAll('a');\n const resetLink = tableElement.getElementsByClassName('resettable');\n const overrideLinks = tableElement.getElementsByClassName('action-icon override');\n const disabledLinks = tableElement.getElementsByClassName('action-icon disabled');\n const tableNavElement = tableElement.querySelectorAll('nav'); // There are two nav paging elements per table.\n\n for (let i = 0; i < links.length; i++) {\n let linkUrl = new URL(links[i].href);\n if (linkUrl.search.indexOf('thide') !== -1 || linkUrl.search.indexOf('tshow') !== -1) {\n links[i].addEventListener('click', this.tableHide);\n } else if (linkUrl.search.indexOf('tsort') !== -1) {\n links[i].addEventListener('click', this.tableSort);\n }\n }\n\n if (resetLink.length > 0) {\n resetLink[0].addEventListener('click', this.tableReset);\n }\n\n for (let i = 0; i < overrideLinks.length; i++) {\n overrideLinks[i].addEventListener('click', this.triggerOverrideModal);\n }\n\n for (let i = 0; i < disabledLinks.length; i++) {\n disabledLinks[i].addEventListener('click', (event) => {\n event.preventDefault();\n });\n }\n\n tableNavElement.forEach((navElement) => {\n navElement.addEventListener('click', this.tableNav);\n });\n };\n\n /**\n * Trigger the override modal form. Thin wrapper to add extra data to click event.\n *\n * @param {Event} event The triggered event for the element.\n */\n triggerOverrideModal = (event) => {\n event.preventDefault();\n let userid = event.target.closest('a').id.substring(25);\n if (userid.includes('-')) {\n let elements = userid.split('-');\n this.activityId = elements.pop();\n userid = elements.pop();\n }\n\n OverrideModal.displayModalForm(this.activityId, userid, this.hoursFilter);\n };\n}\n"],"names":["constructor","activity","context","tableElementId","tableFragmentComponent","tableFragmentValue","tableRowPreference","tableSortPreference","tableSearchElement","tableId","tableMethodName","activityId","contextId","elementId","fragmentComponent","fragmentValue","rowPreference","sortPreference","searchElement","id","methodName","overridden","getTable","page","_this","search","document","getElementById","value","trim","tableElement","spinner","getElementsByClassName","tableBody","values","activityid","params","JSON","stringify","classList","remove","loadFragment","done","response","js","innerHTML","runTemplateJS","add","tableEventListeners","fail","exception","Error","debounceTable","Debouncer","debouncer","tableSort","event","preventDefault","sortArray","linkUrl","URL","target","closest","href","targetSortBy","searchParams","get","targetSortOrder","call","methodname","this","args","tableid","preference","then","tableHide","hideArray","links","querySelectorAll","targetAction","targetColumn","action","column","indexOf","i","length","hideLinkUrl","tableReset","tableSearch","key","ctrlKey","tableSearchReset","tableSearchInputElement","focus","tableSearchRowSet","tagName","toLowerCase","rows","dataset","metric","UserPreference","setUserPreference","tableNav","tableSortButtonAction","element","sort","sortValue","parentNode","getElementsByTagName","resetLink","overrideLinks","disabledLinks","tableNavElement","addEventListener","triggerOverrideModal","forEach","navElement","userid","substring","includes","elements","split","pop","displayModalForm","hoursFilter"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/user_preferences.min.js b/amd/build/user_preferences.min.js index 6eb7bdc3..2540e4f3 100644 --- a/amd/build/user_preferences.min.js +++ b/amd/build/user_preferences.min.js @@ -1,2 +1,11 @@ -define ("local_assessfreq/user_preferences",["exports","core/ajax","core/notification"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getUserPreference=a.setUserPreference=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}var e=function(a,d){return b.default.call([{methodname:"core_user_update_user_preferences",args:{preferences:[{type:a,value:d}]}}])[0].fail(function(){c.default.exception(new Error("Failed to update user preference"))})};a.setUserPreference=e;var f=function(a){return b.default.call([{methodname:"core_user_get_user_preferences",args:{name:a}}])[0]};a.getUserPreference=f}); -//# sourceMappingURL=user_preferences.min.js.map +define("local_assessfreq/user_preferences",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * User preferences JS module. + * + * @module local_assessfreq/user_preferences + * @package + * @copyright 2020 Guillermo Gomez + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setUserPreference=_exports.getUserPreference=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);_exports.setUserPreference=(type,value)=>{const request={methodname:"core_user_update_user_preferences",args:{preferences:[{type:type,value:value}]}};return _ajax.default.call([request])[0].fail((()=>{_notification.default.exception(new Error("Failed to update user preference"))}))};_exports.getUserPreference=name=>{const request={methodname:"core_user_get_user_preferences",args:{name:name}};return _ajax.default.call([request])[0]}})); + +//# sourceMappingURL=user_preferences.min.js.map \ No newline at end of file diff --git a/amd/build/user_preferences.min.js.map b/amd/build/user_preferences.min.js.map index 18f98efe..d0054997 100644 --- a/amd/build/user_preferences.min.js.map +++ b/amd/build/user_preferences.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/user_preferences.js"],"names":["setUserPreference","type","value","Ajax","call","methodname","args","preferences","fail","Notification","exception","Error","getUserPreference","name"],"mappings":"qNAwBA,OACA,O,mDAUO,GAAMA,CAAAA,CAAiB,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CAQ9C,MAAOC,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,mCADA,CAEZC,IAAI,CAAE,CACFC,WAAW,CAAE,CAAC,CAACN,IAAI,CAAEA,CAAP,CAAaC,KAAK,CAAEA,CAApB,CAAD,CADX,CAFM,CAOC,CAAV,EAAqB,CAArB,EACNM,IADM,CACD,UAAM,CACRC,UAAaC,SAAb,CAAuB,GAAIC,CAAAA,KAAJ,CAAU,kCAAV,CAAvB,CACH,CAHM,CAIV,CAZM,C,sBAqBA,GAAMC,CAAAA,CAAiB,CAAG,SAACC,CAAD,CAAU,CAQvC,MAAOV,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,gCADA,CAEZC,IAAI,CAAE,CACF,KAAQO,CADN,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * User preferences JS module.\n *\n * @module local_assessfreq/user_preferences\n * @package local_assessfreq\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Generic handler to persist user preferences.\n *\n * @method setUserPreference\n * @param {string} type The name of the attribute you're updating\n * @param {string} value The value of the attribute you're updating\n * @return {promise} jQuery promise\n */\nexport const setUserPreference = (type, value) => {\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [{type: type, value: value}]\n }\n };\n\n return Ajax.call([request])[0]\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference'));\n });\n};\n\n/**\n * Generic handler to get user preference.\n *\n * @method getUserPreference\n * @param {string} name The name of the attribute you're getting.\n * @return {promise} jQuery promise\n */\nexport const getUserPreference = (name) => {\n const request = {\n methodname: 'core_user_get_user_preferences',\n args: {\n 'name': name\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"user_preferences.min.js"} \ No newline at end of file +{"version":3,"file":"user_preferences.min.js","sources":["../src/user_preferences.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * User preferences JS module.\n *\n * @module local_assessfreq/user_preferences\n * @package\n * @copyright 2020 Guillermo Gomez \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Generic handler to persist user preferences.\n *\n * @method setUserPreference\n * @param {string} type The name of the attribute you're updating\n * @param {string} value The value of the attribute you're updating\n * @return {promise} jQuery promise\n */\nexport const setUserPreference = (type, value) => {\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n preferences: [{type: type, value: value}]\n }\n };\n\n return Ajax.call([request])[0]\n .fail(() => {\n Notification.exception(new Error('Failed to update user preference'));\n });\n};\n\n/**\n * Generic handler to get user preference.\n *\n * @method getUserPreference\n * @param {string} name The name of the attribute you're getting.\n * @return {promise} jQuery promise\n */\nexport const getUserPreference = (name) => {\n const request = {\n methodname: 'core_user_get_user_preferences',\n args: {\n 'name': name\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["type","value","request","methodname","args","preferences","Ajax","call","fail","exception","Error","name"],"mappings":";;;;;;;;6OAmCiC,CAACA,KAAMC,eAC9BC,QAAU,CACZC,WAAY,oCACZC,KAAM,CACFC,YAAa,CAAC,CAACL,KAAMA,KAAMC,MAAOA,iBAInCK,cAAKC,KAAK,CAACL,UAAU,GAC3BM,MAAK,2BACWC,UAAU,IAAIC,MAAM,oEAWPC,aACxBT,QAAU,CACZC,WAAY,iCACZC,KAAM,MACMO,cAITL,cAAKC,KAAK,CAACL,UAAU"} \ No newline at end of file diff --git a/amd/build/zoom_modal.min.js b/amd/build/zoom_modal.min.js deleted file mode 100644 index 209a8b66..00000000 --- a/amd/build/zoom_modal.min.js +++ /dev/null @@ -1,2 +0,0 @@ -define ("local_assessfreq/zoom_modal",["core/str","core/modal_factory","core/fragment","core/ajax","core/templates","local_assessfreq/modal_large","core/notification"],function(a,b,c,d,e,f,g){var h={},i,j;h.zoomGraph=function(b,d,f){var h=b.target.parentElement.dataset.title;c.loadFragment("local_assessfreq",f,i,d).done(function(b){var c=JSON.parse(b);if(!0==c.hasdata){var d={withtable:!1,chartdata:JSON.stringify(c.chart),aspect:!1};j.setTitle(h);j.setBody(e.render("local_assessfreq/chart",d));j.show()}else{a.get_string("nodata","local_assessfreq").then(function(a){var b=document.createElement("h3");b.innerHTML=a;j.setTitle(h);j.setBody(b.outerHTML);j.show()}).catch(function(){g.exception(new Error("Failed to load string: nodata"))})}}).fail(function(){g.exception(new Error("Failed to load zoomed graph"))})};var k=function(){return new Promise(function(c,d){a.get_string("loading","core").then(function(a){b.create({type:f.TYPE,title:a,body:"

"}).done(function(a){j=a;c()})}).catch(function(){d(new Error("Failed to load string: loading"))})})};h.init=function(a){i=a;k()};return h}); -//# sourceMappingURL=zoom_modal.min.js.map diff --git a/amd/build/zoom_modal.min.js.map b/amd/build/zoom_modal.min.js.map deleted file mode 100644 index 6f16f9bd..00000000 --- a/amd/build/zoom_modal.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/zoom_modal.js"],"names":["define","Str","ModalFactory","Fragment","Ajax","Templates","ModalLarge","Notification","ZoomModal","contextid","modalObj","zoomGraph","event","params","method","title","target","parentElement","dataset","loadFragment","done","response","resObj","JSON","parse","hasdata","context","stringify","chart","aspect","setTitle","setBody","render","show","get_string","then","str","noDatastr","document","createElement","innerHTML","outerHTML","catch","exception","Error","fail","createModal","Promise","resolve","reject","create","type","TYPE","body","modal","init"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for report card display and processing.\n *\n * @package local_assessfreq\n * @copyright 2020 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['core/str', 'core/modal_factory', 'core/fragment', 'core/ajax', 'core/templates', 'local_assessfreq/modal_large',\n 'core/notification'],\nfunction(Str, ModalFactory, Fragment, Ajax, Templates, ModalLarge, Notification) {\n\n /**\n * Module level variables.\n */\n var ZoomModal = {};\n var contextid;\n var modalObj;\n const spinner = '

'\n + ''\n + '

';\n\n /**\n * Provides zoom functionality for card graphs.\n */\n ZoomModal.zoomGraph = function(event, params, method) {\n let title = event.target.parentElement.dataset.title;\n\n Fragment.loadFragment('local_assessfreq', method, contextid, params)\n .done((response) => {\n let resObj = JSON.parse(response);\n if (resObj.hasdata == true) {\n var context = { 'withtable' : false, 'chartdata' : JSON.stringify(resObj.chart), aspect: false};\n modalObj.setTitle(title);\n modalObj.setBody(Templates.render('local_assessfreq/chart', context));\n modalObj.show();\n return;\n } else {\n Str.get_string('nodata', 'local_assessfreq').then((str) => {\n const noDatastr = document.createElement('h3');\n noDatastr.innerHTML = str;\n modalObj.setTitle(title);\n modalObj.setBody(noDatastr.outerHTML);\n modalObj.show();\n return;\n }).catch(() => {\n Notification.exception(new Error('Failed to load string: nodata'));\n });\n }\n }).fail(() => {\n Notification.exception(new Error('Failed to load zoomed graph'));\n return;\n });\n\n };\n\n /**\n * Create the modal window for graph zooming.\n *\n * @private\n */\n const createModal = function() {\n return new Promise((resolve, reject) => {\n Str.get_string('loading', 'core').then((title) => {\n // Create the Modal.\n ModalFactory.create({\n type: ModalLarge.TYPE,\n title: title,\n body: spinner\n })\n .done((modal) => {\n modalObj = modal;\n resolve();\n });\n }).catch(() => {\n reject(new Error('Failed to load string: loading'));\n });\n });\n };\n\n /**\n * Initialise method for quiz dashboard rendering.\n */\n ZoomModal.init = function(context) {\n contextid = context;\n createModal();\n };\n\n return ZoomModal;\n});\n"],"file":"zoom_modal.min.js"} \ No newline at end of file diff --git a/amd/src/calendar.js b/amd/src/calendar.js deleted file mode 100644 index 5c0de108..00000000 --- a/amd/src/calendar.js +++ /dev/null @@ -1,520 +0,0 @@ -// 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 . - -/** - * Javascript for heatmap calendar generation and display. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define(['core/str', 'core/notification', 'core/ajax'], function (Str, Notification, Ajax) { - - /** - * Module level variables. - */ - var Calendar = {}; - var eventArray = []; - const stringArr = [ - {key: 'sun', component: 'calendar'}, - {key: 'mon', component: 'calendar'}, - {key: 'tue', component: 'calendar'}, - {key: 'wed', component: 'calendar'}, - {key: 'thu', component: 'calendar'}, - {key: 'fri', component: 'calendar'}, - {key: 'sat', component: 'calendar'}, - {key: 'jan', component: 'local_assessfreq'}, - {key: 'feb', component: 'local_assessfreq'}, - {key: 'mar', component: 'local_assessfreq'}, - {key: 'apr', component: 'local_assessfreq'}, - {key: 'may', component: 'local_assessfreq'}, - {key: 'jun', component: 'local_assessfreq'}, - {key: 'jul', component: 'local_assessfreq'}, - {key: 'aug', component: 'local_assessfreq'}, - {key: 'sep', component: 'local_assessfreq'}, - {key: 'oct', component: 'local_assessfreq'}, - {key: 'nov', component: 'local_assessfreq'}, - {key: 'dec', component: 'local_assessfreq'}, - ]; - var stringResult; - var heatRangeMax; - var heatRangeMin; - var colorArray; - var processModules; - var heatRangeScale = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0}; - - /** - * Pick a contrasting text color based on the background color. - * - * @param {String} A hexcolor value. - * @return {String} The contrasting color (black or white). - */ - const getContrast = function (hexcolor) { - - if (typeof (hexcolor) === "undefined") { - return '#000000'; - } - - // If a leading # is provided, remove it. - if (hexcolor.slice(0, 1) === '#') { - hexcolor = hexcolor.slice(1); - } - - // Convert to RGB value. - var r = parseInt(hexcolor.substr(0,2),16); - var g = parseInt(hexcolor.substr(2,2),16); - var b = parseInt(hexcolor.substr(4,2),16); - - // Get YIQ ratio. - var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; - - // Check contrast. - return (yiq >= 128) ? '#000000' : '#FFFFFF'; - }; - - /** - * Check how many days in a month code. - * from https://dzone.com/articles/determining-number-days-month. - * - * @method daysInMonth - * @param {Number} month The month to get the number of days for. - * @param {Number} year The year to get the number of days for. - */ - const daysInMonth = function (month, year) { - return 32 - new Date(year, month, 32).getDate(); - }; - - /** - * Get the heat colors to use in the heat map via Ajax. - * - * @method getHeatColors - */ - const getHeatColors = function () { - return new Promise((resolve, reject) => { - Ajax.call([{ - methodname: 'local_assessfreq_get_heat_colors', - args: {}, - }], true, false)[0].done(function (response) { - colorArray = JSON.parse(response); - resolve(colorArray); - }).fail(function () { - reject(new Error('Failed to get heat colors')); - }); - }); - }; - - /** - * Get the event names that we are processing. - * - * @method getProcessEvents - */ - const getProcessModules = function () { - return new Promise((resolve, reject) => { - Ajax.call([{ - methodname: 'local_assessfreq_get_process_modules', - args: {}, - }], true, false)[0].done(function (response) { - processModules = JSON.parse(response); - resolve(processModules); - }).fail(function () { - reject(new Error('Failed to get process events')); - }); - }); - }; - - /** - * Calculate the min and max values to use in the heatmap. - * - * @method daysInMonth - * @param {Object} eventArray All the event count for the heatmap. - * @param {Object} dateObj Date details. - */ - const calcHeatRange = function (eventArray, dateObj) { - return new Promise((resolve) => { - - // Resolve early if there are no events. - if (typeof (eventArray) === "undefined") { - heatRangeMax = 0; - heatRangeMin = 0; - - resolve(eventArray); - } - // If scheduled tasks have not run yet we may not have any data. - let eventArrayLength = Object.keys(eventArray).length; - if ((eventArrayLength > 0) && (eventArray[dateObj.year] !== "undefined")) { - let eventcount = new Array; - let year = eventArray[dateObj.year]; - - // Iterate through all the event counts. - // This code looks nasty but there is only 366 days in a year. - for (let i = 0; i < 12; i++) { - if (typeof year[i] !== "undefined") { - let month = year[i]; - for (let j = 0; j < 32; j++) { - if (typeof month[j] !== "undefined") { - eventcount.push(month[j].number); - } - } - } - } - - // Get min and max values to calculate heat spread. - heatRangeMax = Math.max(...eventcount); - heatRangeMin = Math.min(...eventcount); - } else { - heatRangeMax = 0; - heatRangeMin = 0; - } - - resolve(eventArray); - }); - }; - - /** - * Translate assessment frequency to a heat value. - * - * @method getHeat - * @param {Number} eventCount The count to get the heat value. - * @return {Number} heat The heat value. - */ - const getHeat = function (eventCount) { - let scaleMin = 1; - - if (eventCount == heatRangeMin) { - return scaleMin; - } - - const scaleRange = 5; // 0 - 5 steps. - const localRange = heatRangeMax - heatRangeMin; - const localPercent = (eventCount - heatRangeMin) / localRange; - let heat = Math.round((localPercent * scaleRange) + 1); - - // Clamp values. - if (heat < 1) { - heat = 1; - } - - if (heat > 6) { - heat = 6; - } - - return heat; - }; - - /** - * Get the events to display in the calendar via ajax call. - * - * @method getEvents - * @param {Number} year The year to get the events for. - * @param {String} metric The type of metric to get, 'students' or 'assess'. - * @param {Array} modules Array of the modules to get. - * @return {Promise} - */ - const getEvents = function ({year, metric, modules}) { - return new Promise((resolve, reject) => { - let args = { - year: year, - metric: metric, - modules: modules - }; - let jsonArgs = JSON.stringify(args); - - // Get the events to use in the mapping. - Ajax.call([{ - methodname: 'local_assessfreq_get_frequency', - args: { - jsondata: jsonArgs - }, - }])[0].done((response) => { - eventArray = JSON.parse(response); - resolve(eventArray); - }).fail(() => { - reject(new Error('Failed to get events')); - }); - }); - }; - - /** - * Get the events for a particular month and year. - * - * @param {Number} year The year to get the number of days for. - * @param {Number} month The month to get the number of days for. - * @return {Array} monthevents The events for the supplied month. - */ - const getMonthEvents = function (year, month) { - let monthevents; - - if ((typeof eventArray[year] !== "undefined") && (typeof eventArray[year][month] !== "undefined")) { - monthevents = eventArray[year][month]; - } - - return monthevents; - }; - - /** - * Create the table structure for the calendar months. - * - * @oaram {Number} year The year to generate the tables for. - * @param {Number} startMonth The month to start table generation from. - * @param {Number} endMonth The month to generate the tables to. - * @return {Promise} - */ - const createTables = function ({year, startMonth, endMonth}) { - return new Promise((resolve, reject) => { - let calendarContainer = document.createElement('div'); - let month = startMonth; - - // Itterate through and build are tables. - for (let i = startMonth; i <= endMonth; i++) { - // Setup some elements. - let container = document.createElement('div'); - container.classList.add('local-assessfreq-month'); - let table = document.createElement('table'); - table.classList.add('table-striped'); - let thead = document.createElement('thead'); - let tbody = document.createElement('tbody'); - tbody.id = 'calendar-body-' + i; - let monthRow = document.createElement('tr'); - let dayrow = document.createElement('tr'); - let monthHeader = document.createElement('th'); - monthHeader.colSpan = 7; - monthHeader.innerHTML = stringResult[(7 + month)]; - - for (let j = 0; j < 7; j++) { - let dayHeader = document.createElement('th'); - dayHeader.innerHTML = stringResult[j]; - dayrow.appendChild(dayHeader); - } - - // Construct the table. - monthRow.appendChild(monthHeader); - - thead.appendChild(monthRow); - thead.appendChild(dayrow); - - table.appendChild(thead); - table.appendChild(tbody); - - container.appendChild(table); - - // Add to parent. - calendarContainer.appendChild(container); - - // Increment variables. - month++; - } - - if ((typeof year === 'undefined') || (typeof startMonth === 'undefined') || (typeof endMonth === 'undefined')) { - reject(Error('Failed to create calendar tables.')); - } else { - const resultObj = { - calendarContainer : calendarContainer, - year : year, - startMonth : startMonth - }; - resolve(resultObj); - } - }); - }; - - /** - * Generate the tooltip HTML. - * - * @param {Object} dayArray The details of the events for that day/ - * @return {String} tipHTML The HTML for the tooltip. - */ - const getTooltip = function (dayArray) { - let tipHTML = ''; - - for (let [key, value] of Object.entries(dayArray)) { - tipHTML += '' + processModules[key] + ': ' + value + '
'; - } - - return tipHTML; - }; - - /** - * Generate calendar markup for the month. - * - * @param {Object} table The base table to populate. - * @param {Number} year The year to generate calendar for. - * @param {Number} month The monthe to generate calendar for. - */ - const populateCalendarDays = function (table, year, month) { - let firstDay = (new Date(year, month)).getDay(); // Get the starting day of the month. - let monthEvents = getMonthEvents(year, (month + 1)); // We add one due to month diferences between PHP and JS. - let date = 1; // Creating all cells. - - for (let i = 0; i < 6; i++) { - let row = document.createElement("tr"); // Creates a table row. - - // Creating individual cells, filing them up with data. - for (let j = 0; j < 7; j++) { - if (i === 0 && j < firstDay) { - var cell = document.createElement("td"); - var cellText = document.createTextNode(""); - cell.dataset.event = 'false'; - } else if (date > daysInMonth(month, year)) { // Break if we have generated all the days for this month. - break; - } else { - cell = document.createElement("td"); - cellText = document.createTextNode(date); - if ((typeof monthEvents !== "undefined") && (monthEvents.hasOwnProperty(date))) { - let heat = getHeat(monthEvents[date]['number']); - - if (heatRangeScale[heat] == 0 || heatRangeScale[heat] > monthEvents[date]['number']) { - heatRangeScale[heat] = monthEvents[date]['number']; - } - - cell.style.backgroundColor = colorArray[heat]; - cell.style.color = getContrast(colorArray[heat]); - - // Add tooltip to cell. - cell.dataset.toggle = 'tooltip'; - cell.dataset.html = 'true'; - cell.dataset.event = 'true'; - cell.dataset.date = year + '-' + (month + 1) + '-' + date; - cell.title = getTooltip(monthEvents[date]); - cell.style.cursor = "pointer"; - } - date++; - } - - cell.appendChild(cellText); - row.appendChild(cell); - } - table.appendChild(row); // Appending each row into calendar body. - } - }; - - /** - * Controls the population of the calendar in to the base tables. - * - * @param {Object} calendarContainer the container to populate. - * @param {Number} year The year to generate calendar for. - * @param {Number} startMonth The month to start generation from. - * @return {Promise} - */ - const populateCalendar = function ({calendarContainer, year, startMonth}) { - return new Promise((resolve, reject) => { - // Get the table boodies. - let tables = calendarContainer.getElementsByTagName("tbody"); - let month = startMonth; - - // For each table body populate with calendar. - for (var i = 0; i < tables.length; i++) { - let table = tables[i]; - populateCalendarDays(table, year, month); - month++; - } - - if (typeof calendarContainer === 'undefined') { - reject(Error('Failed to populate calendar tables.')); - } else { - resolve(calendarContainer); - } - }); - }; - - /** - * Create the heatmap scale for the calendar. - * - * @method createHeatScale - */ - Calendar.createHeatScale = function () { - return new Promise((resolve) => { - let table = document.createElement('table'); - let tbody = document.createElement('tbody'); - let trow = document.createElement('tr'); - - for (var i = 1; i < 7; i++) { - if (heatRangeScale[i] !== 0) { - let cell = document.createElement('td'); - let cellText = document.createTextNode(heatRangeScale[i] + '+'); - - cell.appendChild(cellText); - cell.style.backgroundColor = colorArray[i]; - cell.style.color = getContrast(colorArray[i]); - - trow.appendChild(cell); - } - } - - tbody.appendChild(trow); - table.appendChild(tbody); - - // Reset heat range scale. - heatRangeScale = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0}; - - resolve(table); - }); - }; - - /** - * Initialise method for report calendar heatmap creation. - * - * @param {Number} year The year to generate the heatmap for. - * @param {Number} startMonth The month to start with for the heatmap calendar. - * @param {Number} endMonth The month to end with for the heatmap calendar. - * @param {String} metric The type of metric to display, 'students' or 'aseess'. - * @param {Array} modules The modules to display in the heatamp. - * @return {Promise} - */ - Calendar.generate = function (year, startMonth, endMonth, metric, modules) { - return new Promise((resolve, reject) => { - const dateObj = { - year : year, - startMonth : startMonth, - endMonth : endMonth - }; - - const eventObj = { - year : year, - metric : metric, - modules : modules - }; - - Str.get_strings(stringArr).catch(() => { // Get required strings. - Notification.exception(new Error('Failed to load strings')); - return; - }).then(stringReturn => { // Save string to global to be used later. - stringResult = stringReturn; - return eventObj; - }) - .then(getEvents) - .then((eventArray) => { - calcHeatRange(eventArray, dateObj); - }) - .then(getHeatColors) - .then(getProcessModules) - .then(() => { - return dateObj; - }) - .then(createTables) // Create tables for calendar. - .then(populateCalendar) - .then((calendarHTML) => { // Return the result of the generate function. - if (typeof calendarHTML !== 'undefined') { - resolve(calendarHTML); - } else { - reject(Error('Could not generate calendar')); - } - }); - }); - - }; - - return Calendar; -}); diff --git a/amd/src/chart_data.js b/amd/src/chart_data.js deleted file mode 100644 index c66a818d..00000000 --- a/amd/src/chart_data.js +++ /dev/null @@ -1,117 +0,0 @@ -// 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 . - -/** - * Chart data JS module. - * - * @module local_assessfreq/char_data - * @package local_assessfreq - * @copyright 2020 Guillermo Gomez - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import Fragment from 'core/fragment'; -import Notification from 'core/notification'; -import * as Str from 'core/str'; -import Templates from 'core/templates'; - -/** - * Module level variables. - */ -let cards; -let contextId; -let fragment; -let template; - -/** - * For each of the cards on the dashboard get their corresponding chart data. - * Data is based on the year variable from the corresponding dropdown. - * Chart data is loaded via ajax. - * - * @param {int|null} quizId The quiz Id. - * @param {array|null} hoursFilter Array with hour ahead or behind preference. - * @param {int|null} yearSelect Year selected. - */ -export const getCardCharts = (quizId, hoursFilter, yearSelect) => { - cards.forEach((cardData) => { - let cardElement = document.getElementById(cardData.cardId); - let spinner = cardElement.getElementsByClassName('overlay-icon-container')[0]; - let chartBody = cardElement.getElementsByClassName('chart-body')[0]; - let values = {'call': cardData.call}; - // Add values to Object depending on dashboard type. - if (hoursFilter) { - values.hoursahead = hoursFilter[0]; - values.hoursbehind = hoursFilter[1]; - } - if (quizId) { - values.quiz = quizId; - } - if (yearSelect) { - values.year = yearSelect; - } - let params = {'data': JSON.stringify(values)}; - - spinner.classList.remove('hide'); // Show sinner if not already shown. - Fragment.loadFragment('local_assessfreq', fragment, contextId, params) - .done((response) => { - let resObj = JSON.parse(response); - if (resObj.hasdata === true) { - let context = { - 'withtable': true, 'chartdata': JSON.stringify(resObj.chart) - }; - if (typeof cardData.aspect !== 'undefined') { - context.aspect = cardData.aspect; - } - Templates.render(template, context).done((html, js) => { - spinner.classList.add('hide'); // Hide spinner if not already hidden. - // Load card body. - Templates.replaceNodeContents(chartBody, html, js); - }).fail(() => { - Notification.exception(new Error('Failed to load chart template.')); - return; - }); - return; - } else { - Str.get_string('nodata', 'local_assessfreq').then((str) => { - const noDatastr = document.createElement('h3'); - noDatastr.innerHTML = str; - chartBody.innerHTML = noDatastr.outerHTML; - spinner.classList.add('hide'); // Hide spinner if not already hidden. - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: nodata')); - }); - } - }).fail(() => { - Notification.exception(new Error('Failed to load card.')); - return; - }); - }); -}; - -/** - * Initialise method for table handler. - * - * @param {array} cardsArray Cards array. - * @param {int} contextIdChart The context id. - * @param {string} fragmentChart Fragment name. - * @param {string} templateChart Template name. - */ -export const init = (cardsArray, contextIdChart, fragmentChart, templateChart) => { - cards = cardsArray; - contextId = contextIdChart; - fragment = fragmentChart; - template = templateChart; -}; diff --git a/amd/src/chart_output_chartjs.js b/amd/src/chart_output_chartjs.js deleted file mode 100644 index 4cc3039c..00000000 --- a/amd/src/chart_output_chartjs.js +++ /dev/null @@ -1,115 +0,0 @@ -// 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 . - -/** - * Chart output for chart.js with custom override for aspect config. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define(['core/chart_output_chartjs'], function (Output) { - - /** - * Module level variables. - */ - var ChartOutput = {}; - var aspectRatio = false; - var rtLegendoptions = false; - - /** - * Overrride the config. - * - * @protected - * @param {module:core/chart_axis} axis The axis. - * @return {Object} The axis config. - */ - Output.prototype._makeConfig = function () { - var config = { - type: this._getChartType(), - data: { - labels: this._cleanData(this._chart.getLabels()), - datasets: this._makeDatasetsConfig() - }, - options: { - title: { - display: this._chart.getTitle() !== null, - text: this._cleanData(this._chart.getTitle()) - } - } - }; - var legendOptions = this._chart.getLegendOptions(); - if (legendOptions) { - config.options.legend = legendOptions; - } - - // Override legend options with those provided at run time. - if (rtLegendoptions) { - config.options.legend = rtLegendoptions; - } - - this._chart.getXAxes().forEach(function (axis, i) { - var axisLabels = axis.getLabels(); - - config.options.scales = config.options.scales || {}; - config.options.scales.xAxes = config.options.scales.xAxes || []; - config.options.scales.xAxes[i] = this._makeAxisConfig(axis, 'x', i); - - if (axisLabels !== null) { - config.options.scales.xAxes[i].ticks.callback = function (value, index) { - return axisLabels[index] || ''; - }; - } - config.options.scales.xAxes[i].stacked = this._isStacked(); - }.bind(this)); - - this._chart.getYAxes().forEach(function (axis, i) { - var axisLabels = axis.getLabels(); - - config.options.scales = config.options.scales || {}; - config.options.scales.yAxes = config.options.scales.yAxes || []; - config.options.scales.yAxes[i] = this._makeAxisConfig(axis, 'y', i); - - if (axisLabels !== null) { - config.options.scales.yAxes[i].ticks.callback = function (value) { - return axisLabels[parseInt(value, 10)] || ''; - }; - } - config.options.scales.yAxes[i].stacked = this._isStacked(); - }.bind(this)); - - config.options.tooltips = { - callbacks: { - label: this._makeTooltip.bind(this) - } - }; - - config.options.maintainAspectRatio = aspectRatio; - - return config; - }; - - /** - * Get the aspect ratio setting and initialise the chart. - */ - ChartOutput.init = function (chartImage, ChartInst, aspect, legend) { - aspectRatio = aspect; - rtLegendoptions = legend; - new Output(chartImage, ChartInst); - }; - - return ChartOutput; - -}); diff --git a/amd/src/course_selector.js b/amd/src/course_selector.js index 472cc8e5..fe958cf1 100644 --- a/amd/src/course_selector.js +++ b/amd/src/course_selector.js @@ -28,7 +28,7 @@ define(['core/ajax', 'core/notification'], function (Ajax, Notification) { /** * Module level variables. */ - var CourseSelector = {}; + let CourseSelector = {}; /** * Source of data for Ajax element. @@ -36,9 +36,8 @@ define(['core/ajax', 'core/notification'], function (Ajax, Notification) { * @param {String} selector The selector of the auto complete element. * @param {String} query The query string. * @param {Function} callback A callback function receiving an array of results. - * @return {Void} - */ - CourseSelector.transport = function (selector, query, callback) { + */ + CourseSelector.transport = function(selector, query, callback) { Ajax.call([{ methodname: 'local_assessfreq_get_courses', args: { @@ -46,6 +45,7 @@ define(['core/ajax', 'core/notification'], function (Ajax, Notification) { }, }])[0].then((response) => { let courseArray = JSON.parse(response); + // eslint-disable-next-line promise/no-callback-in-promise callback(courseArray); }).fail(() => { Notification.exception(new Error('Failed to get events')); diff --git a/amd/src/dashboard.js b/amd/src/dashboard.js new file mode 100644 index 00000000..9d4241c6 --- /dev/null +++ b/amd/src/dashboard.js @@ -0,0 +1,65 @@ +// 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 . + +/** + * Chart data JS module. + * + * @module local_assessfreq/dashboard + * @package + * @copyright Simon Thornett + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export const init = () => { + + // Load the tab cuntionality. + tabs(); + +}; + +const tabs = () => { + + const tabcontent = document.getElementsByClassName("tablinks"); + + tabcontent.forEach(el => el.addEventListener('click', event => { + let target = event.target.dataset.target; + + let tabcontent = document.getElementsByClassName("tabcontent"); + for (let i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + + // Get all elements with class="tablinks" and remove the class "active" + let tablinks = document.getElementsByClassName("tablinks"); + for (let i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + + // Show the current tab, and add an "active" class to the button that opened the tab + document.getElementById(target).style.display = "block"; + event.currentTarget.className += " active"; + })); + + const currentUrl = document.URL; + const urlParts = currentUrl.split('#'); + + const anchor = (urlParts.length > 1) ? urlParts[1] : null; + // First tab should be open by default unless we have an anchor. + if (!anchor || document.querySelector('[data-target="tab-' + anchor + '"]') === null) { + document.querySelector('[data-target="tab-heatmap"]').click(); + } else { + document.querySelector('[data-target="tab-' + anchor + '"]').click(); + } +}; diff --git a/amd/src/dashboard_assessment.js b/amd/src/dashboard_assessment.js deleted file mode 100644 index 6ecaacea..00000000 --- a/amd/src/dashboard_assessment.js +++ /dev/null @@ -1,372 +0,0 @@ -// 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 . - -/** - * Javascript for report card display and processing. - * - * @module local_assessfreq/dashboard_assessment - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import Notification from 'core/notification'; -import Calendar from 'local_assessfreq/calendar'; -import * as ChartData from 'local_assessfreq/chart_data'; -import Dayview from 'local_assessfreq/dayview'; -import * as UserPreference from 'local_assessfreq/user_preferences'; -import ZoomModal from 'local_assessfreq/zoom_modal'; - -/** - * Module level variables. - */ -var contextid; -var yearselect; -var yearselectheatmap; -var metricselectheatmap; -var timeout; -var modulesJson = ''; -var heatmapOptionsJson = ''; - -const cards = [ - {cardId: 'local-assessfreq-assess-due-month', call: 'assess_by_month'}, - {cardId: 'local-assessfreq-assess-by-activity', call: 'assess_by_activity'}, - {cardId: 'local-assessfreq-assess-due-month-student', call: 'assess_by_month_student'} -]; - -/** - * Get and process the selected year from the dropdown, - * and update the corresponding user perference. - * - * @param {event} event The triggered event for the element. - */ -const yearButtonAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.tagName.toLowerCase() === 'a' && element.dataset.year !== yearselect) { // Only act on certain elements. - yearselect = element.dataset.year; - - // Save selection as a user preference. - UserPreference.setUserPreference('local_assessfreq_overview_year_preference', yearselect); - - // Update card data based on selected year. - var yeartitle = document.getElementById('local-assessfreq-report-overview') - .getElementsByClassName('local-assessfreq-year')[0]; - yeartitle.innerHTML = yearselect; - - ChartData.getCardCharts(0, null, yearselect); // Process loading for the assessment cards. - } -}; - -/** - * Quick and dirty debounce method for the heatmap settings menu. - * This stops the ajax method that updates the heatmap from being updated - * while the user is still checking options. - * - */ -const updateHeatmapDebounce = () => { - clearTimeout(timeout); - timeout = setTimeout(updateHeatmap(), 750); -}; - -/** - * Display heatmap calendar. - * - * @param {event} event The triggered event for the element. - */ -const detailView = (event) => { - let element = event.target; - if (element.tagName.toLowerCase() === 'td' && element.dataset.event === 'true') { // Only act on certain elements. - Dayview.display(element.dataset.date); - } -}; - -/** - * Start heatmap generation. - * - */ -const generateHeatmap = () => { - let heatmapOptions = JSON.parse(heatmapOptionsJson); - let year = parseInt(heatmapOptions.year); - let metric = heatmapOptions.metric; - let modules = heatmapOptions.modules; - let heatmapContainer = document.getElementById('local-assessfreq-report-heatmap'); - let spinner = heatmapContainer.getElementsByClassName('overlay-icon-container')[0]; - - spinner.classList.remove('hide'); // Show spinner if not already shown. - - Calendar.generate(year, 0, 11, metric, modules) - .then(calendar => { - let calendarContainer = document.getElementById('local-assessfreq-report-heatmap-months'); - calendarContainer.innerHTML = calendar.innerHTML; - calendarContainer.addEventListener('click', detailView); - }) - .then(Calendar.createHeatScale) - .then((heatScale) => { - let heatScaleContainer = document.getElementById('local-assessfreq-report-heatmap-scale'); - heatScaleContainer.innerHTML = heatScale.outerHTML; - spinner.classList.add('hide'); // Hide sinner if not already hidden. - }) - .catch(() => { - Notification.exception(new Error('Failed to calendar.')); - return; - }); -}; - -const updateDownload = ({year, metric, modules}) => { - let downloadForm = document.getElementById('local-assessfreq-heatmap-form'); - let formElements = downloadForm.elements; - let toRemove = new Array(); - - if (modules.length === 0) { - modules = ['all']; - } - - for (let i = 0; i < formElements.length; i++) { - if (formElements[i] === undefined) { - continue; - } - // Update year field. - if ((formElements[i].type === 'hidden') && (formElements[i].name === 'year')) { - formElements[i].value = year; - continue; - } - - // Update metric field. - if ((formElements[i].type === 'hidden') && (formElements[i].name === 'metric')) { - formElements[i].value = metric; - continue; - } - - // Update module fields. - if ((formElements[i].type === 'hidden') && (formElements[i].name.startsWith('modules'))) { - toRemove.push(formElements[i]); - continue; - } - } - - for (const element of toRemove) { - element.remove(); - } - - for (let i = 0; i < modules.length; i++) { - let input = document.createElement('input'); - input.type = 'hidden'; - input.name = 'modules[' + modules[i] + ']'; - input.value = modules[i]; - - downloadForm.appendChild(input); - } -}; - -/** - * Update the heatmap based on the current filter settings. - * - */ -const updateHeatmap = () => { - // Get current state of select menu items. - var cardsModulesSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-modules'); - var links = cardsModulesSelectHeatmapElement.getElementsByTagName('a'); - var modules = []; - - for (var i = 0; i < links.length; i++) { - if (links[i].classList.contains('active')) { - let module = links[i].dataset.module; - modules.push(module); - } - } - - // Save selection as a user preference. - if (modulesJson !== JSON.stringify(modules)) { - modulesJson = JSON.stringify(modules); - UserPreference.setUserPreference('local_assessfreq_heatmap_modules_preference', modulesJson); - } - - // Build settings object. - var optionsObj = { - 'year': yearselectheatmap, - 'metric': metricselectheatmap, - 'modules': modules - }; - - var optionsJson = JSON.stringify(optionsObj); - - if (optionsJson !== heatmapOptionsJson) { // Compare to global to see if there are any changes. - // If list has changed fetch heatmap and update user preference. - heatmapOptionsJson = optionsJson; - generateHeatmap(); - - // Update the download options. - updateDownload(optionsObj); - } -}; - -/** - * Get and process the selected year from the dropdown for the heatmap display, - * and update the corresponding user preference. - * - * @param {event} event The triggered event for the element. - */ -const yearHeatmapButtonAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.tagName.toLowerCase() === 'a' && element.dataset.year !== yearselectheatmap) { // Only act on certain elements. - yearselectheatmap = element.dataset.year; - - // Save selection as a user preference. - UserPreference.setUserPreference('local_assessfreq_heatmap_year_preference', yearselectheatmap); - - // Update card data based on selected year. - var yeartitle = document.getElementById('local-assessfreq-report-heatmap') - .getElementsByClassName('local-assessfreq-year')[0]; - yeartitle.innerHTML = yearselectheatmap; - - updateHeatmapDebounce(); // Call function to update heatmap. - } -}; - -/** - * Get and process the selected assessment metric from the dropdown for the heatmap display, - * and update the corresponding user preference. - * - * @param {event} event The triggered event for the element. - */ -const metricHeatmapButtonAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.tagName.toLowerCase() === 'a' && element.dataset.metric !== metricselectheatmap) { - metricselectheatmap = element.dataset.metric; - - // Save selection as a user preference. - UserPreference.setUserPreference('local_assessfreq_heatmap_metric_preference', metricselectheatmap); - - updateHeatmapDebounce(); // Call function to update heatmap. - } -}; - -/** - * Add the event listeners to the modules in the module select dropdown. - * - * @param {Object} element The dropdown HTML element that contains the list of modules as links. - */ -const moduleListChildrenEvents = (element) => { - var links = element.getElementsByTagName('a'); - var all = links[0]; - - for (var i = 0; i < links.length; i++) { - let module = links[i].dataset.module; - - if (module.toLowerCase() === 'all') { - links[i].addEventListener('click', function (event) { - event.preventDefault(); - // Remove active class from all other links. - for (var j = 0; j < links.length; j++) { - links[j].classList.remove('active'); - } - updateHeatmapDebounce(); // Call function to update heatmap. - }); - } else if (module.toLowerCase() === 'close') { - links[i].addEventListener('click', function (event) { - event.preventDefault(); - event.stopPropagation(); - - var dropdownmenu = document.getElementById('local-assessfreq-heatmap-modules-filter'); - dropdownmenu.classList.remove('show'); - - updateHeatmapDebounce(); // Call function to update heatmap. - }); - } else { - links[i].addEventListener('click', function (event) { - event.preventDefault(); - event.stopPropagation(); - - all.classList.remove('active'); - - event.target.classList.toggle('active'); - updateHeatmapDebounce(); - }); - } - } -}; - -/** - * Thin wrapper to add extra data to click event. - * - * @param {Event} event The triggered event for the element. - */ -const triggerZoomGraph = (event) => { - let call = event.target.closest('div').dataset.call; - let params = {'data': JSON.stringify({'year': yearselect, 'call': call})}; - let method = 'get_chart'; - - ZoomModal.zoomGraph(event, params, method); -}; - -/** - * Initialise method for report card rendering. - * - * @param {integer} context The current context id. - */ -export const init = (context) => { - contextid = context; - - // Set up event listener and related actions for year dropdown on report cards. - let cardsYearSelectElement = document.getElementById('local-assessfreq-cards-year'); - yearselect = cardsYearSelectElement.getElementsByClassName('active')[0].dataset.year; - cardsYearSelectElement.addEventListener('click', yearButtonAction); - - // Set up event listener and related actions for year dropdown on heatmp. - let cardsYearSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-year'); - yearselectheatmap = cardsYearSelectHeatmapElement.getElementsByClassName('active')[0].dataset.year; - cardsYearSelectHeatmapElement.addEventListener('click', yearHeatmapButtonAction); - - // Set up event listener and related actions for metric dropdown on heatmp. - let cardsMetricSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-metrics'); - metricselectheatmap = cardsMetricSelectHeatmapElement.getElementsByClassName('active')[0].dataset.metric; - cardsMetricSelectHeatmapElement.addEventListener('click', metricHeatmapButtonAction); - - // Set up event listener and related actions for module dropdown on heatmp. - let cardsModulesSelectHeatmapElement = document.getElementById('local-assessfreq-heatmap-modules'); - moduleListChildrenEvents(cardsModulesSelectHeatmapElement); - - // Set up zoom event listeners. - let dueMonthZoom = document.getElementById('local-assessfreq-assess-due-month-zoom'); - dueMonthZoom.addEventListener('click', triggerZoomGraph); - - let dueActivityZoom = document.getElementById('local-assessfreq-assess-by-activity-zoom'); - dueActivityZoom.addEventListener('click', triggerZoomGraph); - - let dueStudentZoom = document.getElementById('local-assessfreq-assess-due-month-student-zoom'); - dueStudentZoom.addEventListener('click', triggerZoomGraph); - - // Create the zoom modal. - ZoomModal.init(context); - - // Setup the dayview modal. - Dayview.init(); - - // Setup the chart data for each card. - ChartData.init(cards, contextid, 'get_chart', 'core/chart'); - - // Process loading for the assessment cards. - ChartData.getCardCharts(0, null, yearselect); - - // Get the data for the heatmap. - updateHeatmap(); - -}; diff --git a/amd/src/dashboard_quiz.js b/amd/src/dashboard_quiz.js deleted file mode 100644 index 6a036bfb..00000000 --- a/amd/src/dashboard_quiz.js +++ /dev/null @@ -1,252 +0,0 @@ -// 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 . - -/** - * Javascript for report card display and processing. - * - * @module local_assessfreq/dashboard_quiz - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import Ajax from 'core/ajax'; -import Notification from 'core/notification'; -import * as Str from 'core/str'; -import Templates from 'core/templates'; -import * as ChartData from 'local_assessfreq/chart_data'; -import * as FormModal from 'local_assessfreq/form_modal'; -import OverrideModal from 'local_assessfreq/override_modal'; -import * as TableHandler from 'local_assessfreq/table_handler'; -import * as UserPreference from 'local_assessfreq/user_preferences'; -import * as ZoomModal from 'local_assessfreq/zoom_modal'; - -// Module level variables. - -var selectQuizStr = ''; -var contextid; -var quizId = 0; -var refreshPeriod = 60; -var counterid; - -const cards = [ - {cardId: 'local-assessfreq-quiz-summary-graph', call: 'participant_summary', aspect: true}, - {cardId: 'local-assessfreq-quiz-summary-trend', call: 'participant_trend', aspect: false} -]; - -/** - * Function for refreshing the counter. - * - * @param {boolean} reset the current count process. - */ -const refreshCounter = (reset = true) => { - let progressElement = document.getElementById('local-assessfreq-period-progress'); - - // Reset the current count process. - if (reset === true) { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - } - - // Exit early if there is already a counter running. - if (counterid) { - return; - } - - counterid = setInterval(() => { - let progressWidthAria = progressElement.getAttribute('aria-valuenow'); - const progressStep = 100 / refreshPeriod; - - if ((progressWidthAria - progressStep) > 0) { - progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%'); - progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep)); - } else { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - processDashboard(quizId); - refreshCounter(); - } - }, (1000)); -}; - -/** - * Callback function that is called when a quiz is selected from the form. - * Starts the processing of the dashboard. - * - * @param {int} quiz The quiz Id. - */ -const processDashboard = (quiz) => { - quizId = quiz; - let titleElement = document.getElementById('local-assessfreq-quiz-title'); - titleElement.innerHTML = selectQuizStr; - // Get quiz data. - Ajax.call([{ - methodname: 'local_assessfreq_get_quiz_data', - args: { - quizid: quiz - }, - }])[0].then((response) => { - - let quizArray = JSON.parse(response); - let cardsElement = document.getElementById('local-assessfreq-quiz-dashboard-cards-deck'); - let trendElement = document.getElementById('local-assessfreq-quiz-dashboard-participant-trend-deck'); - let summaryElement = document.getElementById('local-assessfreq-quiz-summary-card'); - let summarySpinner = summaryElement.getElementsByClassName('overlay-icon-container')[0]; - let tableElement = document.getElementById('local-assessfreq-quiz-table'); - let periodElement = document.getElementById('local-assessfreq-period-container'); - let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-student-table-search'); - let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-student-table-search-reset'); - let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-student-table-rows'); - - let quizLink = document.createElement('a'); - quizLink.href = quizArray.url; - quizLink.innerHTML = ''; - titleElement.innerHTML = quizArray.name + ' '; - titleElement.appendChild(quizLink); - - // Update page URL with quiz ID, without reloading page so that page navigation and bookmarking works. - const currentdUrl = new URL(window.location.href); - const newUrl = currentdUrl.origin + currentdUrl.pathname + '?id=' + quizId; - history.pushState({}, '', newUrl); - - // Update page title with quiz name. - Str.get_string('dashboard:quiztitle', 'local_assessfreq', {'quiz': quizArray.name, 'course': quizArray.courseshortname}) - .then((str) => { - document.title = str; - }).catch(() => { - Notification.exception(new Error('Failed to load string: dashboard:quiztitle')); - }); - - // Populate quiz summary card with details. - Templates.render('local_assessfreq/quiz-summary-card-content', quizArray).done((html) => { - summarySpinner.classList.add('hide'); - let contentcontainer = document.getElementById('local-assessfreq-quiz-summary-card-content'); - Templates.replaceNodeContents(contentcontainer, html, ''); - }).fail(() => { - Notification.exception(new Error('Failed to load quiz summary template.')); - return; - }); - - // Show the cards. - cardsElement.classList.remove('hide'); - trendElement.classList.remove('hide'); - tableElement.classList.remove('hide'); - periodElement.classList.remove('hide'); - - ChartData.getCardCharts(quizId); - TableHandler.getTable(quizId); - refreshCounter(); - - tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch); - tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch); - tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset); - tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet); - - return; - }).fail(() => { - Notification.exception(new Error('Failed to get quiz data')); - }); -}; - -/** - * Handle processing of refresh and period button actions. - * - * @param {Event} event The triggered event for the element. - */ -const refreshAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') { - refreshCounter(true); - processDashboard(quizId); - } else if (element.tagName.toLowerCase() === 'a') { - refreshPeriod = element.dataset.period; - refreshCounter(true); - UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod); - } -}; - -/** - * Trigger the zoom graph. Thin wrapper to add extra data to click event. - * - * @param {Event} event The triggered event for the element. - */ -const triggerZoomGraph = (event) => { - let call = event.target.closest('div').dataset.call; - let params = {'data': JSON.stringify({'quiz': quizId, 'call': call})}; - let method = 'get_quiz_chart'; - - ZoomModal.zoomGraph(event, params, method); -}; - -/** - * Initialise method for quiz dashboard rendering. - * - * @param {int} context The context id. - * @param {int} quiz The quiz id. - */ -export const init = (context, quiz) => { - contextid = context; - FormModal.init(context, processDashboard); // Create modal for quiz selection modal. - ZoomModal.init(context); // Create the zoom modal. - OverrideModal.init(context, processDashboard); - TableHandler.init( - quizId, - contextid, - 'local-assessfreq-quiz-student-table', - 'local-assessfreq-quiz-table', - 'get_student_table', - 'local_assessfreq_quiz_table_rows_preference', - 'local-assessfreq-quiz-student-table-search', - 'local_assessfreq_student_table', - 'local_assessfreq_set_table_preference' - ); - ChartData.init(cards, context, 'get_quiz_chart', 'local_assessfreq/chart'); - Str.get_string('loadingquiztitle', 'local_assessfreq').then((str) => { - selectQuizStr = str; - }).catch(() => { - Notification.exception(new Error('Failed to load string: loadingquiz')); - }).then(() => { - if (quiz > 0) { - quizId = quiz; - processDashboard(quiz); - } - }); - - UserPreference.getUserPreference('local_assessfreq_quiz_refresh_preference') - .then((response) => { - refreshPeriod = response.preferences[0].value ? response.preferences[0].value : 60; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: refresh')); - }); - - // Event handling for refresh and period buttons. - let refreshElement = document.getElementById('local-assessfreq-period-container'); - refreshElement.addEventListener('click', refreshAction); - - // Set up zoom event listeners. - let summaryZoom = document.getElementById('local-assessfreq-quiz-summary-graph-zoom'); - summaryZoom.addEventListener('click', triggerZoomGraph); - - let trendZoom = document.getElementById('local-assessfreq-quiz-summary-trend-zoom'); - trendZoom.addEventListener('click', triggerZoomGraph); - -}; diff --git a/amd/src/dashboard_quiz_inprogress.js b/amd/src/dashboard_quiz_inprogress.js deleted file mode 100644 index 4d465d89..00000000 --- a/amd/src/dashboard_quiz_inprogress.js +++ /dev/null @@ -1,286 +0,0 @@ -// 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 . - -/** - * Javascript for quizzes in progress display and processing. - * - * @module local_assessfreq/dashboard_quiz_inprogress - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import Ajax from 'core/ajax'; -import Notification from 'core/notification'; -import Templates from 'core/templates'; -import * as ChartData from 'local_assessfreq/chart_data'; -import * as TableHandler from 'local_assessfreq/table_handler'; -import * as UserPreference from 'local_assessfreq/user_preferences'; -import * as ZoomModal from 'local_assessfreq/zoom_modal'; - -/** - * Module level variables. - */ -var contextid; -var refreshPeriod = 60; -var counterid; -var tablesort = 'name_asc'; -var hoursAhead = 0; -var hoursBehind = 0; - -/** - * Hours filter array. - * - * @type {array} Title to display on modal. - */ -var hoursFilter; - -const cards = [ - {cardId: 'local-assessfreq-quiz-summary-upcomming-graph', call: 'upcomming_quizzes', aspect: true}, - {cardId: 'local-assessfreq-quiz-summary-inprogress-graph', call: 'all_participants_inprogress', aspect: true} -]; - -/** - * Function for refreshing the counter. - * - * @param {boolean} reset the current count process. - */ -const refreshCounter = (reset = true) => { - let progressElement = document.getElementById('local-assessfreq-period-progress'); - - // Reset the current count process. - if (reset === true) { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - } - - // Exit early if there is already a counter running. - if (counterid) { - return; - } - - counterid = setInterval(() => { - let progressWidthAria = progressElement.getAttribute('aria-valuenow'); - const progressStep = 100 / refreshPeriod; - - if ((progressWidthAria - progressStep) > 0) { - progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%'); - progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep)); - } else { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - processDashboard(); - refreshCounter(); - } - }, (1000)); -}; - -/** - * Starts the processing of the dashboard. - */ -const processDashboard = () => { - // Get summary quiz data. - Ajax.call([{ - methodname: 'local_assessfreq_get_inprogress_counts', - args: {}, - }])[0].then((response) => { - let quizSummary = JSON.parse(response); - let summaryElement = document.getElementById('local-assessfreq-quiz-dashboard-inprogress-summary-card'); - let summarySpinner = summaryElement.getElementsByClassName('overlay-icon-container')[0]; - let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-inprogress-table-search'); - let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-inprogress-table-search-reset'); - let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-inprogress-table-rows'); - let tableSortElement = document.getElementById('local-assessfreq-inprogress-table-sort'); - - summaryElement.classList.remove('hide'); // Show the card. - - // Populate summary card with details. - Templates.render('local_assessfreq/quiz-dashboard-inprogress-summary-card-content', quizSummary) - .done((html) => { - summarySpinner.classList.add('hide'); - - let contentcontainer = document.getElementById('local-assessfreq-quiz-dashboard-inprogress-summary-card-content'); - Templates.replaceNodeContents(contentcontainer, html, ''); - }).fail(() => { - Notification.exception(new Error('Failed to load quiz counts template.')); - return; - }); - - hoursFilter = [hoursAhead, hoursBehind]; - ChartData.getCardCharts(0, hoursFilter); - TableHandler.getTable(0, hoursFilter, tablesort); - refreshCounter(); - - // Table event listeners. - tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch); - tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch); - tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset); - tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet); - tableSortElement.addEventListener('click', TableHandler.tableSortButtonAction); - - return; - }).fail(() => { - Notification.exception(new Error('Failed to get quiz summary counts')); - }); -}; - -/** - * Handle processing of refresh and period button actions. - * - * @param {Event} event The triggered event for the element. - */ -const refreshAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') { - refreshCounter(true); - processDashboard(); - } else if (element.tagName.toLowerCase() === 'a') { - refreshPeriod = element.dataset.period; - refreshCounter(true); - UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod); - } -}; - -/** - * Trigger the zoom graph. Thin wrapper to add extra data to click event. - * - * @param {Event} event The triggered event for the element. - */ -const triggerZoomGraph = (event) => { - let call = event.target.closest('div').dataset.call; - let params = {'data': JSON.stringify({'call': call, 'hoursahead': hoursAhead, 'hoursbehind': hoursBehind})}; - let method = 'get_quiz_inprogress_chart'; - - ZoomModal.zoomGraph(event, params, method); -}; - -/** - * Process the hours ahead event from the in progress quizzes table. - * - * @param {Event} event The triggered event for the element. - */ -const quizzesAheadSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let hours = event.target.dataset.metric; - UserPreference.setUserPreference('local_assessfreq_quizzes_inprogress_table_hoursahead_preference', hours) - .then(() => { - hoursAhead = hours; - processDashboard(); // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: hours ahead')); - }); - } -}; - -/** - * Process the hours behind event from the in progress quizzes table. - * - * @param {Event} event The triggered event for the element. - */ -const quizzesBehindSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let hours = event.target.dataset.metric; - UserPreference.setUserPreference('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference', hours) - .then(() => { - hoursBehind = hours; - processDashboard(); // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: hours behind')); - }); - } -}; - -/** - * Initialise method for quizzes in progress dashboard rendering. - * - * @param {int} context The context id. - */ -export const init = (context) => { - contextid = context; - ZoomModal.init(context); // Create the zoom modal. - TableHandler.init( - 0, - contextid, - null, - 'local-assessfreq-quiz-inprogress-table', - 'get_quizzes_inprogress_table', - 'local_assessfreq_quiz_table_inprogress_preference', - 'local-assessfreq-quiz-inprogress-table-search' - ); - ChartData.init(cards, context, 'get_quiz_inprogress_chart', 'local_assessfreq/chart'); - - UserPreference.getUserPreference('local_assessfreq_quiz_refresh_preference') - .then((response) => { - refreshPeriod = response.preferences[0].value ? response.preferences[0].value : 60; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: refresh')); - }); - - UserPreference.getUserPreference('local_assessfreq_quiz_table_inprogress_sort_preference') - .then((response) => { - tablesort = response.preferences[0].value ? response.preferences[0].value : 'name_asc'; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: tablesort')); - }); - - UserPreference.getUserPreference('local_assessfreq_quizzes_inprogress_table_hoursahead_preference') - .then((response) => { - hoursAhead = response.preferences[0].value ? response.preferences[0].value : 0; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: hoursahead')); - }); - - UserPreference.getUserPreference('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference') - .then((response) => { - hoursBehind = response.preferences[0].value ? response.preferences[0].value : 0; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: hoursbehind')); - }); - - // Event handling for refresh and period buttons. - let refreshElement = document.getElementById('local-assessfreq-period-container'); - refreshElement.addEventListener('click', refreshAction); - - // Set up zoom event listeners. - let summaryZoom = document.getElementById('local-assessfreq-quiz-summary-inprogress-graph-zoom'); - summaryZoom.addEventListener('click', triggerZoomGraph); - - let upcommingZoom = document.getElementById('local-assessfreq-quiz-summary-upcomming-graph-zoom'); - upcommingZoom.addEventListener('click', triggerZoomGraph); - - // Set up behind and ahead quizzes event listeners. - let quizzesAheadElement = document.getElementById('local-assessfreq-quiz-student-table-hoursahead'); - quizzesAheadElement.addEventListener('click', quizzesAheadSet); - - let quizzesBehindElement = document.getElementById('local-assessfreq-quiz-student-table-hoursbehind'); - quizzesBehindElement.addEventListener('click', quizzesBehindSet); - - processDashboard(); - -}; diff --git a/amd/src/dayview.js b/amd/src/dayview.js deleted file mode 100644 index 4e3bdb08..00000000 --- a/amd/src/dayview.js +++ /dev/null @@ -1,209 +0,0 @@ -// 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 . - -/** - * Javascript for heatmap calendar generation and display. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define( - ['core/str', 'core/notification', 'core/modal_factory', 'local_assessfreq/modal_large', 'core/templates', 'core/ajax'], - function (Str, Notification, ModalFactory, ModalLarge, Templates, Ajax) { - - /** - * Module level variables. - */ - var Dayview = {}; - var modalObj; - const spinner = '

' - + '' - + '

'; - - const stringArr = [ - {key: 'sun', component: 'calendar'}, - {key: 'mon', component: 'calendar'}, - {key: 'tue', component: 'calendar'}, - {key: 'wed', component: 'calendar'}, - {key: 'thu', component: 'calendar'}, - {key: 'fri', component: 'calendar'}, - {key: 'sat', component: 'calendar'}, - {key: 'jan', component: 'local_assessfreq'}, - {key: 'feb', component: 'local_assessfreq'}, - {key: 'mar', component: 'local_assessfreq'}, - {key: 'apr', component: 'local_assessfreq'}, - {key: 'may', component: 'local_assessfreq'}, - {key: 'jun', component: 'local_assessfreq'}, - {key: 'jul', component: 'local_assessfreq'}, - {key: 'aug', component: 'local_assessfreq'}, - {key: 'sep', component: 'local_assessfreq'}, - {key: 'oct', component: 'local_assessfreq'}, - {key: 'nov', component: 'local_assessfreq'}, - {key: 'dec', component: 'local_assessfreq'}, - ]; - var stringResult; - var systemTimezone = 'Australia/Melbourne'; - var dayViewTitle = ''; - - const getUserDate = function (timestamp, format) { - return new Promise((resolve) => { - const systemTimezoneTime = new Date(timestamp * 1000).toLocaleString('en-US', {timeZone: systemTimezone}); - let date = new Date(systemTimezoneTime); - const year = date.getFullYear(); - const month = stringResult[(7 + date.getMonth())]; - const day = date.getDate(); - const hours = date.getHours(); - const minutes = '0' + date.getMinutes(); - - const strftimetime = hours + ':' + minutes.substr(-2); // Will display time in 10:30 format. - const strftimedatetime = day + ' ' + month + ' ' + year + ', ' + strftimetime; - - if (format === 'strftimetime') { - resolve(strftimetime); - } else { - resolve(strftimedatetime); - } - - }); - }; - - const formatData = async function (response) { - let responseArr = JSON.parse(response); - - // We are displaying the event as a bar whose width represents the start and end time of the event. - // We need to scale the width of the bar to match the width of the container. Therefore 100% width of the container - // equals 24 hours (one day). - // There are 1440 mins per day. 1440 mins equals 100%, therefore 1 min = (100/1440)%. 5/72 == 100/1440. - let scaler = 5 / 72; - - for (let i = 0; i < responseArr.length; i++) { - const year = responseArr[i].endyear; - const month = (responseArr[i].endmonth) - 1; // Minus 1 for difference between months in PHP and JS. - const day = responseArr[i].endday; - const dayStart = (new Date(year, month, day).getTime()) / 1000; - const timeStart = new Date(responseArr[i].timestart * 1000).toLocaleString('en-US', {timeZone: systemTimezone}); - const timeStartTimestamp = (new Date(timeStart).getTime()) / 1000; - const timeEnd = new Date(responseArr[i].timeend * 1000).toLocaleString('en-US', {timeZone: systemTimezone}); - const timeEndTimestamp = (new Date(timeEnd).getTime()) / 1000; - let secondsSinceDayStart = timeStartTimestamp - dayStart; - let leftMargin = 0; - let width = 0; - - if (secondsSinceDayStart <= 0) { - secondsSinceDayStart = 0; - width = ((timeEndTimestamp - dayStart) / 60) * scaler; - responseArr[i].start = await getUserDate(responseArr[i].timestart, 'strftimedatetime'); - } else { - leftMargin = (secondsSinceDayStart / 60) * scaler; - width = ((timeEndTimestamp - timeStartTimestamp) / 60) * scaler; - responseArr[i].start = await getUserDate(responseArr[i].timestart, 'strftimetime'); - } - - if (leftMargin + width > 100) { - width = 100 - leftMargin; - } - - responseArr[i].leftmargin = leftMargin; - responseArr[i].width = width; - responseArr[i].end = await getUserDate(responseArr[i].timeend, 'strftimetime'); - } - - return new Promise((resolve) => { - resolve(responseArr); - }); - }; - - /** - * Initialise the base modal to be used. - * - */ - Dayview.display = function (date) { - modalObj.setBody(spinner); - modalObj.show(); - let args = { - date: date, - modules: ['all'] - }; - let jsonArgs = JSON.stringify(args); - Ajax.call([{ - methodname: 'local_assessfreq_get_day_events', - args: {jsondata: jsonArgs}, - }])[0] - .then(formatData) - .then((responseArr) => { - - let context = {rows: responseArr}; - const year = responseArr[0].endyear; - const day = responseArr[0].endday; - const month = stringResult[(6 + parseInt(responseArr[0].endmonth))]; - const dayDate = day + ' ' + month + ' ' + year; - - modalObj.setTitle(dayViewTitle + ' ' + dayDate); - modalObj.setBody(Templates.render('local_assessfreq/dayview', context)); - - }).fail(() => { - Notification.exception(new Error('Failed to load day view')); - }); - }; - - /** - * Initialise the base modal to be used. - * - * @param {integer} context The current context id. - */ - Dayview.init = function () { - // Load the strings we'll need later. - Str.get_strings(stringArr).catch(() => { // Get required strings. - Notification.exception(new Error('Failed to load strings')); - return; - }).then(stringReturn => { // Save string to global to be used later. - stringResult = stringReturn; - }); - - // Get the system timzone. - Ajax.call([{ - methodname: 'local_assessfreq_get_system_timezone', - args: {}, - }], true, false)[0].then((response) => { - systemTimezone = response; - return; - }).fail(() => { - Notification.exception(new Error('Failed to get system timezone')); - }); - - Str.get_string('schedule', 'local_assessfreq').then((title) => { - dayViewTitle = title; - - // Create the Modal. - ModalFactory.create({ - type: ModalLarge.TYPE, - title: title, - body: spinner - }) - .done((modal) => { - modalObj = modal; - - }); - }).catch(() => { - Notification.exception(new Error('Failed to load string: loading')); - }); - - }; - - return Dayview; - } -); diff --git a/amd/src/debouncer.js b/amd/src/debouncer.js index b1874d6e..531d6050 100644 --- a/amd/src/debouncer.js +++ b/amd/src/debouncer.js @@ -17,7 +17,7 @@ * Debounce JS module. * * @module local_assessfreq/debouncer - * @package local_assessfreq + * @package * @copyright 2020 Guillermo Gomez * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * diff --git a/amd/src/form_modal.js b/amd/src/form_modal.js deleted file mode 100644 index 23117f27..00000000 --- a/amd/src/form_modal.js +++ /dev/null @@ -1,241 +0,0 @@ -// 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 . - -/** - * Javascript for report card display and processing. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define( - ['core/str', 'core/modal_factory', 'core/fragment', 'core/ajax'], - function (Str, ModalFactory, Fragment, Ajax) { - - /** - * Module level variables. - */ - var FormModal = {}; - var contextid; - var modalObj; - var resetOptions = []; - var callback; - - const spinner = '

' - + '' - + '

'; - - const observerConfig = { attributes: true, childList: false, subtree: true }; - - const ObserverCallback = function (mutationsList) { - for (let i = 0; i < mutationsList.length; i++) { - let element = mutationsList[i].target; - if (element.tagName.toLowerCase() === 'span' && element.classList.contains('badge')) { - element.addEventListener('click', updateModalBody); - document.getElementById('id_courses').dataset.course = element.dataset.value; - - document.getElementById('id_quiz').value = -1; - Ajax.call([{ - methodname: 'local_assessfreq_get_quizzes', - args: { - query: mutationsList[i].target.dataset.value - }, - }])[0].done((response) => { - let quizArray = JSON.parse(response); - let selectElement = document.getElementById('id_quiz'); - let selectElementLength = selectElement.options.length; - if (document.getElementById('noquizwarning') !== null) { - document.getElementById('noquizwarning').remove(); - } - // Clear exisitng options. - for (let j = selectElementLength - 1; j >= 0; j--) { - selectElement.options[j] = null; - } - - if (quizArray.length > 0) { - // Add new options. - for (let k = 0; k < quizArray.length; k++) { - let opt = quizArray[k]; - let el = document.createElement('option'); - el.textContent = opt.name; - el.value = opt.id; - selectElement.appendChild(el); - } - selectElement.removeAttribute('disabled'); - if (document.getElementById('noquizwarning') !== null) { - document.getElementById('noquizwarning').remove(); - } - } else { - resetOptions.forEach((option) => { - selectElement.appendChild(option); - }); - document.getElementById('id_quiz').value = 0; - selectElement.disabled = true; - } - - }).fail(() => { - Notification.exception(new Error('Failed to get quizzes')); - }); - - break; - } - } - }; - - const observer = new MutationObserver(ObserverCallback); - - /** - * Create the modal window. - * - * @private - */ - const createModal = function () { - Str.get_string('loading', 'local_assessfreq').then((title) => { - // Create the Modal. - ModalFactory.create({ - type: ModalFactory.types.DEFAULT, - title: title, - body: spinner, - large: true - }) - .done((modal) => { - modalObj = modal; - - // Explicitly handle form click events. - modalObj.getRoot().on('click', '#id_submitbutton', processModalForm); - modalObj.getRoot().on('click', '#id_cancel', (e) => { - e.preventDefault(); - modalObj.setBody(spinner); - modalObj.hide(); - }); - }); - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: loading')); - }); - }; - - const getOptionPlaceholders = function () { - return new Promise((resolve, reject) => { - const stringArr = [ - {key: 'selectcourse', component: 'local_assessfreq'}, - {key: 'loadingquiz', component: 'local_assessfreq'}, - ]; - - Str.get_strings(stringArr).catch(() => { // Get required strings. - reject(new Error('Failed to load strings')); - return; - }).then(stringReturn => { // Save string to global to be used later. - for (let i = 0; i < stringReturn.length; i++) { - let el = document.createElement('option'); - el.textContent = stringReturn[i]; - el.value = 0 - i; - resetOptions.push(el); - } - resolve(); - }); - }); - }; - - /** - * Updates the body of the modal window. - * - * @param {Object} formdata - * @private - */ - const updateModalBody = function (formdata) { - if (typeof formdata === "undefined") { - formdata = {}; - } - - let params = { - 'jsonformdata': JSON.stringify(formdata) - }; - - getOptionPlaceholders() - .then(() => { - Str.get_string('searchquiz', 'local_assessfreq').then((title) => { - modalObj.setTitle(title); - modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_base_form', contextid, params)); - let modalContainer = document.querySelectorAll('[data-region*="modal-container"]')[0]; - observer.observe(modalContainer, observerConfig); - - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: searchquiz')); - }); - }); - }; - - /** - * Updates Moodle form with selected information. - * - * @param {Object} e - * @private - */ - const processModalForm = function (e) { - e.preventDefault(); // Stop modal from closing. - - let quizElement = document.getElementById('id_quiz'); - let quizId = quizElement.options[quizElement.selectedIndex].value; - let courseId = document.getElementById('id_courses').dataset.course; - - if (courseId === undefined || quizId < 1) { - if (document.getElementById('noquizwarning') === null) { - Str.get_string('noquizselected', 'local_assessfreq').then((warning) => { - let element = document.createElement('div'); - element.innerHTML = warning; - element.id = 'noquizwarning'; - element.classList.add('alert', 'alert-danger'); - modalObj.getBody().prepend(element); - - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: searchquiz')); - }); - } - } else { - modalObj.hide(); // Close modal. - modalObj.setBody(''); // Cleaer form. - observer.disconnect(); // Remove observer. - callback(quizId, courseId); // Trigger dashboard update. - } - - }; - - /** - * Display the Modal form. - */ - const displayModalForm = function () { - updateModalBody(); - modalObj.show(); - }; - - /** - * Initialise method for quiz dashboard rendering. - */ - FormModal.init = function (context, processDashboard) { - contextid = context; - callback = processDashboard; - createModal(); - - let createBroadcastButton = document.getElementById('local-assessfreq-find-quiz'); - createBroadcastButton.addEventListener('click', displayModalForm); - }; - - return FormModal; - } -); diff --git a/amd/src/modal_large.js b/amd/src/modal_large.js index 4ba79b6f..16422da1 100644 --- a/amd/src/modal_large.js +++ b/amd/src/modal_large.js @@ -16,23 +16,24 @@ /** * Javascript for large modal . * - * @package local_assessfreq + * @module local_assessfreq/modal_large + * @package * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( ['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_registry'], - function ($, Notification, CustomEvents, Modal, ModalRegistry) { + function($, Notification, CustomEvents, Modal, ModalRegistry) { - var registered = false; + let registered = false; /** * Constructor for the Modal. * * @param {object} root The root jQuery element for the modal */ - var ModalLarge = function (root) { + let ModalLarge = function(root) { Modal.call(this, root); }; diff --git a/amd/src/override_modal.js b/amd/src/override_modal.js index 6409e511..bc839c06 100644 --- a/amd/src/override_modal.js +++ b/amd/src/override_modal.js @@ -16,25 +16,25 @@ /** * Javascript for report card display and processing. * - * @package local_assessfreq + * @package * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( - ['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax'], - function ($,Str, ModalFactory, ModalEvents, Fragment, Ajax) { + ['jquery', 'core/str', 'core/modal', 'core/modal_factory', 'core/modal_events', 'core/fragment', 'core/ajax'], + function($, Str, Modal, ModalFactory, ModalEvents, Fragment, Ajax) { /** * Module level variables. */ - var OverrideModal = {}; - var contextid; - var modalObj; - var callback; - var quizid; - var userid; - var hoursFilter; + let OverrideModal = {}; + let contextid; + let activitytype; + let modalObj; + let activityid; + let userid; + let tableHandler; const spinner = '

' + '' @@ -45,8 +45,8 @@ define( * * @private */ - const createModal = function () { - Str.get_string('loading', 'local_assessfreq').then((title) => { + const createModal = function() { + Str.get_string('loading').then((title) => { // Create the Modal. ModalFactory.create({ type: ModalFactory.types.DEFAULT, @@ -54,46 +54,43 @@ define( body: spinner, large: true }) - .done((modal) => { - modalObj = modal; - // Explicitly handle form click events. - modalObj.getRoot().on('click', '#id_submitbutton', processModalForm); - modalObj.getRoot().on('click', '#id_cancel', function (e) { - e.preventDefault(); - modalObj.setBody(spinner); - modalObj.hide(); + .done((modal) => { + modalObj = modal; + // Explicitly handle form click events. + modalObj.getRoot().on('click', '#id_submitbutton', processModalForm); + modalObj.getRoot().on('click', '#id_cancel', function(e) { + e.preventDefault(); + modalObj.setBody(spinner); + modalObj.hide(); + }); }); - }); - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: loading')); }); }; /** * Updates the body of the modal window. * + * @param {Integer} activity + * @param {Integer} user * @param {Object} formdata * @private */ - const updateModalBody = function (quiz, user, formdata) { + const updateModalBody = function(activity, user, formdata) { if (typeof formdata === "undefined") { formdata = {}; } let params = { 'jsonformdata': JSON.stringify(formdata), - 'quizid': quiz, + 'activitytype': activitytype, + 'activityid': activity, 'userid': user }; modalObj.setBody(spinner); - Str.get_string('useroverride', 'local_assessfreq').then((title) => { + Str.get_string('modal:useroverride', 'local_assessfreq').then((title) => { modalObj.setTitle(title); modalObj.setBody(Fragment.loadFragment('local_assessfreq', 'new_override_form', contextid, params)); - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: useroverride')); }); }; @@ -112,7 +109,7 @@ define( // Handle invalid form fields for better UX. // I hate that I had to use JQuery for this. - var invalid = $.merge( + let invalid = $.merge( modalObj.getRoot().find('[aria-invalid="true"]'), modalObj.getRoot().find('.error') ); @@ -127,41 +124,44 @@ define( methodname: 'local_assessfreq_process_override_form', args: { 'jsonformdata': formjson, - 'quizid': quizid + 'activityid': activityid, + 'activitytype': activitytype, }, }])[0].done(() => { // For submission succeeded. modalObj.setBody(spinner); modalObj.hide(); - if (hoursFilter) { - callback(quizid, hoursFilter); - } else { - callback(quizid); + if (tableHandler !== undefined) { + tableHandler.getTable(); } }).fail(() => { // Form submission failed server side, redisplay with errors. - updateModalBody(quizid, userid, overrideform); + updateModalBody(activityid, userid, overrideform); }); } /** * Display the Modal form. + * @param {Integer} activity + * @param {Integer} user */ - OverrideModal.displayModalForm = function (quiz, user, hours = null) { - quizid = quiz; + OverrideModal.displayModalForm = function(activity, user) { + activityid = activity; userid = user; - hoursFilter = hours; - updateModalBody(quiz, user); + updateModalBody(activityid, user); modalObj.show(); }; /** - * Initialise method for quiz dashboard rendering. + * Initialise method for dashboard rendering. + * @param {Integer} context + * @param {String} module + * @param {TableHandler} tablehandler If defined will trigger a table refresh on form save. */ - OverrideModal.init = function (context, callbackFunction, hours = null) { + OverrideModal.init = function(context, module, tablehandler = undefined) { + activitytype = module; contextid = context; - callback = callbackFunction; - hoursFilter = hours; + tableHandler = tablehandler; createModal(); }; diff --git a/amd/src/student_search.js b/amd/src/student_search.js deleted file mode 100644 index e0c80aa9..00000000 --- a/amd/src/student_search.js +++ /dev/null @@ -1,192 +0,0 @@ -// 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 . - -/** - * Javascript for student search display and processing. - * - * @module local_assessfreq/student_search - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -import $ from 'jquery'; -import Notification from 'core/notification'; -import OverrideModal from 'local_assessfreq/override_modal'; -import * as TableHandler from 'local_assessfreq/table_handler'; -import * as UserPreference from 'local_assessfreq/user_preferences'; - -/** - * Module level variables. - */ -var contextid; -var hoursAhead = 4; -var hoursBehind = 1; -var refreshPeriod = 60; -var counterid; - -/** - * Function for refreshing the counter. - * - * @param {boolean} reset the current count process. - */ -const refreshCounter = (reset = true) => { - let progressElement = document.getElementById('local-assessfreq-period-progress'); - - // Reset the current count process. - if (reset === true) { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - } - - // Exit early if there is already a counter running. - if (counterid) { - return; - } - - counterid = setInterval(() => { - let progressWidthAria = progressElement.getAttribute('aria-valuenow'); - const progressStep = 100 / refreshPeriod; - - if ((progressWidthAria - progressStep) > 0) { - progressElement.setAttribute('style', 'width: ' + (progressWidthAria - progressStep) + '%'); - progressElement.setAttribute('aria-valuenow', (progressWidthAria - progressStep)); - } else { - clearInterval(counterid); - counterid = null; - progressElement.setAttribute('style', 'width: 100%'); - progressElement.setAttribute('aria-valuenow', 100); - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); - refreshCounter(); - } - }, (1000)); -}; - -/** - * Process the hours ahead event from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableSearchAheadSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let hours = event.target.dataset.metric; - UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursahead_preference', hours) - .then(() => { - hoursAhead = hours; - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: hours ahead')); - }); - } -}; - -/** - * Process the hours behind event from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableSearchBehindSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let hours = event.target.dataset.metric; - UserPreference.setUserPreference('local_assessfreq_student_search_table_hoursbehind_preference', hours) - .then(() => { - hoursBehind = hours; - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); // Reload the table. // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: hours behind')); - }); - } -}; - -/** - * Handle processing of refresh and period button actions. - * - * @param {Event} event The triggered event for the element. - */ -const refreshAction = (event) => { - event.preventDefault(); - var element = event.target; - - if (element.closest('button') !== null && element.closest('button').id === 'local-assessfreq-refresh-quiz-dashboard') { - refreshCounter(true); - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); - } else if (element.tagName.toLowerCase() === 'a') { - refreshPeriod = element.dataset.period; - refreshCounter(true); - UserPreference.setUserPreference('local_assessfreq_quiz_refresh_preference', refreshPeriod); - } -}; - -/** - * Initialise method for student search. - * - * @param {integer} context The current context id. - */ -export const init = (context) => { - contextid = context; - TableHandler.init( - 0, - contextid, - 'local-assessfreq-student-search-table', - 'local-assessfreq-student-search', - 'get_student_search_table', - 'local_assessfreq_student_search_table_rows_preference', - 'local-assessfreq-quiz-student-table-search', - 'local_assessfreq_student_search_table', - 'local_assessfreq_set_table_preference' - ); - - // Add required initial event listeners. - let tableSearchInputElement = document.getElementById('local-assessfreq-quiz-student-table-search'); - let tableSearchResetElement = document.getElementById('local-assessfreq-quiz-student-table-search-reset'); - let tableSearchRowsElement = document.getElementById('local-assessfreq-quiz-student-table-rows'); - let tableSearchAheadElement = document.getElementById('local-assessfreq-quiz-student-table-hoursahead'); - let tableSearchBehindElement = document.getElementById('local-assessfreq-quiz-student-table-hoursbehind'); - let refreshElement = document.getElementById('local-assessfreq-period-container'); - - tableSearchInputElement.addEventListener('keyup', TableHandler.tableSearch); - tableSearchInputElement.addEventListener('paste', TableHandler.tableSearch); - tableSearchResetElement.addEventListener('click', TableHandler.tableSearchReset); - tableSearchRowsElement.addEventListener('click', TableHandler.tableSearchRowSet); - tableSearchAheadElement.addEventListener('click', tableSearchAheadSet); - tableSearchBehindElement.addEventListener('click', tableSearchBehindSet); - refreshElement.addEventListener('click', refreshAction); - - $.when( - UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursahead_preference') - .then((response) => { - hoursAhead = response.preferences[0].value ? response.preferences[0].value : 4; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: hoursahead')); - }), - UserPreference.getUserPreference('local_assessfreq_student_search_table_hoursbehind_preference') - .then((response) => { - hoursBehind = response.preferences[0].value ? response.preferences[0].value : 1; - }) - .fail(() => { - Notification.exception(new Error('Failed to get use preference: hoursahead')); - }) - ).done(function () { - TableHandler.getTable(0, [hoursAhead, hoursBehind], null); - OverrideModal.init(context, TableHandler.getTable, [hoursAhead, hoursBehind]); - }); -}; diff --git a/amd/src/summary_participants.js b/amd/src/summary_participants.js deleted file mode 100644 index 3182a805..00000000 --- a/amd/src/summary_participants.js +++ /dev/null @@ -1,76 +0,0 @@ -// 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 . - -/** - * Javascript for summary participants graph. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define( - ['core/fragment', 'core/templates', 'core/str', 'core/notification'], - function (Fragment, Templates, Str, Notification) { - - /** - * Module level variables. - */ - var Summary = {}; - - Summary.chart = function (assessids, contextid) { - assessids.forEach((assessid) => { - let chartElement = document.getElementById(assessid + '-summary-graph'); - let params = {'data': JSON.stringify({'quiz' : assessid, 'call': 'participant_summary'})}; - - Fragment.loadFragment('local_assessfreq', 'get_quiz_chart', contextid, params) - .done((response) => { - let resObj = JSON.parse(response); - if (resObj.hasdata == true) { - let legend = {position: 'left'}; - let context = { - 'withtable' : false, - 'chartdata' : JSON.stringify(resObj.chart), - 'aspect' : false, - 'legend' : JSON.stringify(legend) - }; - Templates.render('local_assessfreq/chart', context).done((html, js) => { - // Load card body. - Templates.replaceNodeContents(chartElement, html, js); - }).fail(() => { - Notification.exception(new Error('Failed to load chart template.')); - return; - }); - return; - } else { - Str.get_string('nodata', 'local_assessfreq').then((str) => { - const noDatastr = document.createElement('h3'); - noDatastr.innerHTML = str; - chartElement.innerHTML = noDatastr.outerHTML; - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: nodata')); - }); - } - }).fail(() => { - Notification.exception(new Error('Failed to load card.')); - return; - }); - }); - }; - - return Summary; - } -); diff --git a/amd/src/table_handler.js b/amd/src/table_handler.js index 3e713a7f..d0cc3a3f 100644 --- a/amd/src/table_handler.js +++ b/amd/src/table_handler.js @@ -17,7 +17,7 @@ * Table handler JS module. * * @module local_assessfreq/table_handler - * @package local_assessfreq + * @package * @copyright 2020 Guillermo Gomez * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -30,328 +30,311 @@ import * as Debouncer from 'local_assessfreq/debouncer'; import OverrideModal from 'local_assessfreq/override_modal'; import * as UserPreference from 'local_assessfreq/user_preferences'; -/** - * Module level variables. - */ -let cardElement; -let contextId; -let elementId; -let fragmentValue; -let hoursFilter; -let quizId = 0; -let overridden = false; -let rowPreference; -let sortValue; -let searchElement; - -/** - * Table id variable. - * - * @type {string} - */ -let id; - -/** - * Table method name variable. - * - * @type {string} - */ -let methodName; - -/** - * Display the table that contains all the students in the exam as well as their attempts. - * - * @param {int} quiz The Quiz Id. - * @param {array|null} hours Array with hour ahead or behind preference. - * @param {string|null} sortValueTable Sort preference. - * @param {int|string|null} page Page number. - */ -export const getTable = (quiz, hours = null, sortValueTable = null, page) => { - if (typeof page === "undefined" || overridden === true) { - page = 0; +export default class TableHandler { + + constructor(activity, + context, + tableElementId, + tableFragmentComponent, + tableFragmentValue, + tableRowPreference, + tableSortPreference, + tableSearchElement, + tableId = null, + tableMethodName = null) { + this.activityId = activity; + this.contextId = context; + this.elementId = tableElementId; + this.fragmentComponent = tableFragmentComponent; + this.fragmentValue = tableFragmentValue; + this.rowPreference = tableRowPreference; + this.sortPreference = tableSortPreference; + this.searchElement = tableSearchElement; + this.id = tableId; + this.methodName = tableMethodName; + this.overridden = false; } - overridden = false; - - let search = document.getElementById(searchElement).value.trim(); - let tableElement = document.getElementById(elementId); - let spinner = tableElement.getElementsByClassName('overlay-icon-container')[0]; - let tableBody = tableElement.getElementsByClassName('table-body')[0]; - let values = {'search': search, 'page': page}; - - // Add values to Object depending on dashboard type. - if (quiz > 0) { - quizId = quiz; - values.quiz = quizId; - } - if (hours) { - hoursFilter = hours; - values.hoursahead = hoursFilter[0]; - values.hoursbehind = hoursFilter[1]; - } - if (sortValueTable) { - sortValue = sortValueTable; - let sortArray = sortValue.split('_'); - let sortOn = sortArray[0]; - let direction = sortArray[1]; - values.sorton = sortOn; - values.direction = direction; - } + /** + * Display the table that contains all the students in the exam as well as their attempts. + * + * @param {int|string|null} page Page number. + */ + getTable = (page = 0) => { + this.overridden = false; + + let search = document.getElementById(this.searchElement).value.trim(); + let tableElement = document.getElementById(this.elementId); + let spinner = tableElement.getElementsByClassName('overlay-icon-container')[0]; + let tableBody = tableElement.getElementsByClassName('table-body')[0]; + let values = {'search': search, 'page': page}; + + // Add values to Object depending on dashboard type. + if (this.activityId > 0) { + values.activityid = this.activityId; + } - let params = {'data': JSON.stringify(values)}; + let params = {'data': JSON.stringify(values)}; - spinner.classList.remove('hide'); // Show spinner if not already shown. - Fragment.loadFragment('local_assessfreq', fragmentValue, contextId, params) - .done((response, js) => { - tableBody.innerHTML = response; - if (js) { - Templates.runTemplateJS(js); // Magic call the initialises JS from template included in response template HTML. - } - spinner.classList.add('hide'); - tableEventListeners(); // Re-add table event listeners. + spinner.classList.remove('hide'); // Show spinner if not already shown. + Fragment.loadFragment(this.fragmentComponent, this.fragmentValue, this.contextId, params) + .done((response, js) => { + tableBody.innerHTML = response; + if (js) { + Templates.runTemplateJS(js); // Magic call the initialises JS from template included in response template HTML. + } + spinner.classList.add('hide'); + this.tableEventListeners(); // Re-add table event listeners. - }).fail(() => { - Notification.exception(new Error('Failed to update table.')); + }).fail(() => { + Notification.exception(new Error('Failed to update table.')); }); -}; - -/** - * This stops the ajax method that updates the table from being updated - * while the user is still checking options. - * - */ -const debounceTable = Debouncer.debouncer(() => { - getTable(quizId, hoursFilter, sortValue); -}, 750); - -/** - * Process the sort click events from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableSort = (event) => { - event.preventDefault(); - - let sortArray = {}; - const linkUrl = new URL(event.target.closest('a').href); - const targetSortBy = linkUrl.searchParams.get('tsort'); - let targetSortOrder = linkUrl.searchParams.get('tdir'); - - // We want to flip the clicked column. - if (targetSortOrder === '') { - targetSortOrder = "4"; - } - - sortArray[targetSortBy] = targetSortOrder; + }; + + /** + * This stops the ajax method that updates the table from being updated + * while the user is still checking options. + * + */ + debounceTable = Debouncer.debouncer(() => { + this.getTable(); + }, 750); + + /** + * Process the sort click events from the student table. + * + * @param {Event} event The triggered event for the element. + */ + tableSort = (event) => { + event.preventDefault(); + + let sortArray = {}; + const linkUrl = new URL(event.target.closest('a').href); + const targetSortBy = linkUrl.searchParams.get('tsort'); + let targetSortOrder = linkUrl.searchParams.get('tdir'); + + // We want to flip the clicked column. + if (targetSortOrder === '') { + targetSortOrder = "4"; + } - // Set option via ajax. - Ajax.call([{ - methodname: methodName, - args: { - tableid: id, - preference: 'sortby', - values: JSON.stringify(sortArray) - }, - }])[0].then(() => { - getTable(quizId, hoursFilter, sortValue); // Reload the table. - }); + sortArray[targetSortBy] = targetSortOrder; + + // Set option via ajax. + // eslint-disable-next-line promise/catch-or-return + Ajax.call([{ + methodname: this.methodName, + args: { + tableid: this.id, + preference: 'sortby', + values: JSON.stringify(sortArray) + }, + // eslint-disable-next-line promise/always-return + }])[0].then(() => { + this.getTable(); // Reload the table. + }); -}; + }; -/** - * Process the sort click events from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableHide = (event) => { - event.preventDefault(); - - let hideArray = {}; - const linkUrl = new URL(event.target.closest('a').href); - const tableElement = document.getElementById(elementId); - const links = tableElement.querySelectorAll('a'); - let targetAction; - let targetColumn; - let action; - let column; - - if (linkUrl.search.indexOf('thide') !== -1) { - targetAction = 'hide'; - targetColumn = linkUrl.searchParams.get('thide'); - } else { - targetAction = 'show'; - targetColumn = linkUrl.searchParams.get('tshow'); - } + /** + * Process the sort click events from the student table. + * + * @param {Event} event The triggered event for the element. + */ + tableHide = (event) => { + event.preventDefault(); - for (let i = 0; i < links.length; i++) { - let hideLinkUrl = new URL(links[i].href); - if (hideLinkUrl.search.indexOf('thide') !== -1) { - action = 'hide'; - column = hideLinkUrl.searchParams.get('thide'); + let hideArray = {}; + const linkUrl = new URL(event.target.closest('a').href); + const tableElement = document.getElementById(this.elementId); + const links = tableElement.querySelectorAll('a'); + let targetAction; + let targetColumn; + let action; + let column; + + if (linkUrl.search.indexOf('thide') !== -1) { + targetAction = 'hide'; + targetColumn = linkUrl.searchParams.get('thide'); } else { - action = 'show'; - column = hideLinkUrl.searchParams.get('tshow'); + targetAction = 'show'; + targetColumn = linkUrl.searchParams.get('tshow'); } - if (action === 'show') { - hideArray[column] = 1; - } - } - - hideArray[targetColumn] = (targetAction === 'hide') ? 1 : 0; // We want to flip the clicked column. - - // Set option via ajax. - Ajax.call([{ - methodname: methodName, - args: { - tableid: id, - preference: 'collapse', - values: JSON.stringify(hideArray) - }, - }])[0].then(() => { - getTable(quizId, hoursFilter, sortValue); // Reload the table. - }); - -}; - -/** - * Process the reset click event from the table. - * - * @param {Event} event The triggered event for the element. - */ -const tableReset = (event) => { - event.preventDefault(); - - // Set option via ajax. - Ajax.call([{ - methodname: methodName, - args: { - tableid: id, - preference: 'reset', - values: JSON.stringify({}) - }, - }])[0].then(() => { - getTable(quizId, hoursFilter, sortValue); // Reload the table. - }); - -}; - -/** - * Process the search events from the student table. - * - */ -export const tableSearch = (event) => { - if (event.key === 'Meta' || event.ctrlKey) { - return false; - } - - if (event.target.value.length === 0 || event.target.value.length > 2) { - debounceTable(); - } -}; + for (let i = 0; i < links.length; i++) { + let hideLinkUrl = new URL(links[i].href); + if (hideLinkUrl.search.indexOf('thide') !== -1) { + action = 'hide'; + column = hideLinkUrl.searchParams.get('thide'); + } else { + action = 'show'; + column = hideLinkUrl.searchParams.get('tshow'); + } -/** - * Process the search reset click event from the student table. - * - */ -export const tableSearchReset = () => { - let tableSearchInputElement = document.getElementById(searchElement); - tableSearchInputElement.value = ''; - tableSearchInputElement.focus(); - getTable(quizId, hoursFilter, sortValue); -}; + if (action === 'show') { + hideArray[column] = 1; + } + } -/** - * Process the row set event from the student table. - * - * @param {Event} event The triggered event for the element. - */ -export const tableSearchRowSet = (event) => { - event.preventDefault(); - if (event.target.tagName.toLowerCase() === 'a') { - let rows = event.target.dataset.metric; - UserPreference.setUserPreference(rowPreference, rows) - .then(() => { - getTable(quizId, hoursFilter, sortValue); // Reload the table. - }) - .fail(() => { - Notification.exception(new Error('Failed to update user preference: rows')); - }); - } -}; + hideArray[targetColumn] = (targetAction === 'hide') ? 1 : 0; // We want to flip the clicked column. + + // Set option via ajax. + // eslint-disable-next-line promise/catch-or-return + Ajax.call([{ + methodname: this.methodName, + args: { + tableid: this.id, + preference: 'collapse', + values: JSON.stringify(hideArray) + }, + // eslint-disable-next-line promise/always-return + }])[0].then(() => { + this.getTable(); // Reload the table. + }); -/** - * Process the nav event from the student table. - * - * @param {Event} event The triggered event for the element. - */ -const tableNav = (event) => { - event.preventDefault(); + }; + + /** + * Process the reset click event from the table. + * + * @param {Event} event The triggered event for the element. + */ + tableReset = (event) => { + event.preventDefault(); + + // Set option via ajax. + // eslint-disable-next-line promise/catch-or-return + Ajax.call([{ + methodname: this.methodName, + args: { + tableid: this.id, + preference: 'reset', + values: JSON.stringify({}) + }, + // eslint-disable-next-line promise/always-return + }])[0].then(() => { + this.getTable(); // Reload the table. + }); - const linkUrl = new URL(event.target.closest('a').href); - const page = linkUrl.searchParams.get('page'); + }; + + /** + * Process the search events from the student table. + * + * @param {Event} event + * @return {Boolean} + */ + tableSearch = (event) => { + if (event.key === 'Meta' || event.ctrlKey) { + return false; + } - if (page) { - getTable(quizId, hoursFilter, sortValue, page); - } -}; + if (event.target.value.length === 0 || event.target.value.length > 2) { + this.debounceTable(); + } + return true; + }; + + /** + * Process the search reset click event from the student table. + * + */ + tableSearchReset = () => { + let tableSearchInputElement = document.getElementById(this.searchElement); + tableSearchInputElement.value = ''; + tableSearchInputElement.focus(); + this.getTable(); + }; + + /** + * Process the row set event from the student table. + * + * @param {Event} event The triggered event for the element. + */ + tableSearchRowSet = (event) => { + event.preventDefault(); + if (event.target.tagName.toLowerCase() === 'a') { + let rows = event.target.dataset.metric; + UserPreference.setUserPreference(this.rowPreference, rows) + // eslint-disable-next-line promise/always-return + .then(() => { + this.getTable(); // Reload the table. + }) + .fail(() => { + Notification.exception(new Error('Failed to update user preference: rows')); + }); + } + }; -/** - * Get and process the selected assessment metric from the dropdown for the heatmap display, - * and update the corresponding user preference. - * - * @param {Event} event The triggered event for the element. - */ -export const tableSortButtonAction = (event) => { - event.preventDefault(); - var element = event.target; + /** + * Process the nav event from the student table. + * + * @param {Event} event The triggered event for the element. + */ + tableNav = (event) => { + event.preventDefault(); - if (element.tagName.toLowerCase() === 'a' && element.dataset.sort !== sortValue) { - sortValue = element.dataset.sort; + const linkUrl = new URL(event.target.closest('a').href); + const page = linkUrl.searchParams.get('page'); - let links = element.parentNode.getElementsByTagName('a'); - for (let i = 0; i < links.length; i++) { - links[i].classList.remove('active'); + if (page) { + this.getTable(page); } + }; + + /** + * Get and process the selected assessment metric from the dropdown for the heatmap display, + * and update the corresponding user preference. + * + * @param {Event} event The triggered event for the element. + */ + tableSortButtonAction = (event) => { + event.preventDefault(); + var element = event.target; + + if (element.tagName.toLowerCase() === 'a' && element.dataset.sort !== this.sortValue) { + this.sortValue = element.dataset.sort; + + let links = element.parentNode.getElementsByTagName('a'); + for (let i = 0; i < links.length; i++) { + links[i].classList.remove('active'); + } - element.classList.add('active'); + element.classList.add('active'); - // Save selection as a user preference. - UserPreference.setUserPreference('local_assessfreq_quiz_table_inprogress_sort_preference', sortValue); + // Save selection as a user preference. + UserPreference.setUserPreference(this.sortPreference, this.sortValue); - debounceTable(); // Call function to update table. - } -}; + this.debounceTable(); // Call function to update table. + } + }; -/** - * Re-add event listeners when the student table is updated. - */ -const tableEventListeners = () => { - const tableElement = document.getElementById(elementId); - let tableNavElement; - if (cardElement) { - const tableCardElement = document.getElementById(cardElement); + /** + * Re-add event listeners when the student table is updated. + */ + tableEventListeners = () => { + const tableElement = document.getElementById(this.elementId); const links = tableElement.querySelectorAll('a'); const resetLink = tableElement.getElementsByClassName('resettable'); const overrideLinks = tableElement.getElementsByClassName('action-icon override'); const disabledLinks = tableElement.getElementsByClassName('action-icon disabled'); - tableNavElement = tableCardElement.querySelectorAll('nav'); // There are two nav paging elements per table. + const tableNavElement = tableElement.querySelectorAll('nav'); // There are two nav paging elements per table. for (let i = 0; i < links.length; i++) { let linkUrl = new URL(links[i].href); if (linkUrl.search.indexOf('thide') !== -1 || linkUrl.search.indexOf('tshow') !== -1) { - links[i].addEventListener('click', tableHide); + links[i].addEventListener('click', this.tableHide); } else if (linkUrl.search.indexOf('tsort') !== -1) { - links[i].addEventListener('click', tableSort); + links[i].addEventListener('click', this.tableSort); } } if (resetLink.length > 0) { - resetLink[0].addEventListener('click', tableReset); + resetLink[0].addEventListener('click', this.tableReset); } for (let i = 0; i < overrideLinks.length; i++) { - overrideLinks[i].addEventListener('click', triggerOverrideModal); + overrideLinks[i].addEventListener('click', this.triggerOverrideModal); } for (let i = 0; i < disabledLinks.length; i++) { @@ -359,61 +342,26 @@ const tableEventListeners = () => { event.preventDefault(); }); } - } else { - tableNavElement = tableElement.querySelectorAll('nav'); - } - - tableNavElement.forEach((navElement) => { - navElement.addEventListener('click', tableNav); - }); -}; - -/** - * Trigger the override modal form. Thin wrapper to add extra data to click event. - * - * @param {Event} event The triggered event for the element. - */ -const triggerOverrideModal = (event) => { - event.preventDefault(); - let userid = event.target.closest('a').id.substring(25); - if (userid.includes('-')) { - let elements = userid.split('-'); - quizId = elements.pop(); - userid = elements.pop(); - } - OverrideModal.displayModalForm(quizId, userid, hoursFilter); -}; + tableNavElement.forEach((navElement) => { + navElement.addEventListener('click', this.tableNav); + }); + }; + + /** + * Trigger the override modal form. Thin wrapper to add extra data to click event. + * + * @param {Event} event The triggered event for the element. + */ + triggerOverrideModal = (event) => { + event.preventDefault(); + let userid = event.target.closest('a').id.substring(25); + if (userid.includes('-')) { + let elements = userid.split('-'); + this.activityId = elements.pop(); + userid = elements.pop(); + } -/** - * Initialise method for table handler. - * - * @param {int} quiz The quiz id. - * @param {int} context The context id. - * @param {string} tableCardElement The table card element. - * @param {string} tableElementId The table element id. - * @param {string} tableFragmentValue The table fragment value. - * @param {string} tableRowPreference The table row preference. - * @param {string} tableSearchElement The table search element. - * @param {string|null} tableId The table id. - * @param {string|null} tableMethodName The table method name. - */ -export const init = (quiz, - context, - tableCardElement, - tableElementId, - tableFragmentValue, - tableRowPreference, - tableSearchElement, - tableId = null, - tableMethodName = null) => { - quizId = quiz; - contextId = context; - cardElement = tableCardElement; - elementId = tableElementId; - fragmentValue = tableFragmentValue; - rowPreference = tableRowPreference; - searchElement = tableSearchElement; - id = tableId; - methodName = tableMethodName; - }; + OverrideModal.displayModalForm(this.activityId, userid, this.hoursFilter); + }; +} diff --git a/amd/src/user_preferences.js b/amd/src/user_preferences.js index fe2ca89c..544408a0 100644 --- a/amd/src/user_preferences.js +++ b/amd/src/user_preferences.js @@ -17,7 +17,7 @@ * User preferences JS module. * * @module local_assessfreq/user_preferences - * @package local_assessfreq + * @package * @copyright 2020 Guillermo Gomez * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/amd/src/zoom_modal.js b/amd/src/zoom_modal.js deleted file mode 100644 index 06bbc1ed..00000000 --- a/amd/src/zoom_modal.js +++ /dev/null @@ -1,107 +0,0 @@ -// 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 . - -/** - * Javascript for report card display and processing. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define( - ['core/str', 'core/modal_factory', 'core/fragment', 'core/ajax', 'core/templates', 'local_assessfreq/modal_large', - 'core/notification'], - function (Str, ModalFactory, Fragment, Ajax, Templates, ModalLarge, Notification) { - - /** - * Module level variables. - */ - var ZoomModal = {}; - var contextid; - var modalObj; - const spinner = '

' - + '' - + '

'; - - /** - * Provides zoom functionality for card graphs. - */ - ZoomModal.zoomGraph = function (event, params, method) { - let title = event.target.parentElement.dataset.title; - - Fragment.loadFragment('local_assessfreq', method, contextid, params) - .done((response) => { - let resObj = JSON.parse(response); - if (resObj.hasdata == true) { - var context = { 'withtable' : false, 'chartdata' : JSON.stringify(resObj.chart), aspect: false}; - modalObj.setTitle(title); - modalObj.setBody(Templates.render('local_assessfreq/chart', context)); - modalObj.show(); - return; - } else { - Str.get_string('nodata', 'local_assessfreq').then((str) => { - const noDatastr = document.createElement('h3'); - noDatastr.innerHTML = str; - modalObj.setTitle(title); - modalObj.setBody(noDatastr.outerHTML); - modalObj.show(); - return; - }).catch(() => { - Notification.exception(new Error('Failed to load string: nodata')); - }); - } - }).fail(() => { - Notification.exception(new Error('Failed to load zoomed graph')); - return; - }); - - }; - - /** - * Create the modal window for graph zooming. - * - * @private - */ - const createModal = function () { - return new Promise((resolve, reject) => { - Str.get_string('loading', 'core').then((title) => { - // Create the Modal. - ModalFactory.create({ - type: ModalLarge.TYPE, - title: title, - body: spinner - }) - .done((modal) => { - modalObj = modal; - resolve(); - }); - }).catch(() => { - reject(new Error('Failed to load string: loading')); - }); - }); - }; - - /** - * Initialise method for quiz dashboard rendering. - */ - ZoomModal.init = function (context) { - contextid = context; - createModal(); - }; - - return ZoomModal; - } -); diff --git a/ci.yml b/ci.yml new file mode 100644 index 00000000..f1044690 --- /dev/null +++ b/ci.yml @@ -0,0 +1,13 @@ +# .github/workflows/ci.yml +name: ci + +on: [push, pull_request] + +jobs: + ci: + uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main + # Required if you plan to publish (uncomment the below) + # secrets: + # moodle_org_token: ${{ secrets.MOODLE_ORG_TOKEN }} + with: + disable_phpcpd: true diff --git a/classes/event/event_processed.php b/classes/event/event_processed.php index e42cdeab..13f38253 100644 --- a/classes/event/event_processed.php +++ b/classes/event/event_processed.php @@ -24,6 +24,8 @@ namespace local_assessfreq\event; +use core\event\base; + /** * Event class. * @@ -31,7 +33,8 @@ * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class event_processed extends \core\event\base { +class event_processed extends base { + /** * Init method. */ @@ -45,7 +48,7 @@ protected function init() { * * @return string */ - public static function get_name() { + public static function get_name() : string { return get_string('eventeventprocessed', 'local_assessfreq'); } @@ -54,7 +57,7 @@ public static function get_name() { * * @return string */ - public function get_description() { + public function get_description() : string { return get_string('eventeven_processed_desc', 'local_assessfreq'); } } diff --git a/classes/external.php b/classes/external.php index feebbc07..3938d2f0 100644 --- a/classes/external.php +++ b/classes/external.php @@ -21,9 +21,14 @@ * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + +use core\session\manager; +use local_assessfreq\source_base; + defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . "/externallib.php"); +require_once(dirname(__FILE__, 2) . '/lib.php'); /** * Local assessfreq Web Service. @@ -36,194 +41,23 @@ class local_assessfreq_external extends external_api { /** * Returns description of method parameters. * - * @return void - */ - public static function get_frequency_parameters() { - return new external_function_parameters([ - 'jsondata' => new external_value(PARAM_RAW, 'The data encoded as a json array'), - ]); - } - - /** - * Returns event frequency map for all users in site. - * - * @param string $jsondata JSON data. - * @return string JSON response. - */ - public static function get_frequency($jsondata) { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Parameter validation. - self::validate_parameters( - self::get_frequency_parameters(), - ['jsondata' => $jsondata] - ); - - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - - // Execute API call. - $data = json_decode($jsondata, true); - $frequency = new \local_assessfreq\frequency(); - $freqarr = $frequency->get_frequency_array($data['year'], $data['metric'], $data['modules']); - - return json_encode($freqarr); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_frequency_returns() { - return new external_value(PARAM_RAW, 'Event JSON'); - } - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_heat_colors_parameters() { - return new external_function_parameters([ - // If I had params they'd be here, but I don't, so they're not. - ]); - } - - /** - * Returns heat map colors. - * This method doesn't require login or user session update. - * It also doesn't need any capability check. - * - * @return string JSON response. - */ - public static function get_heat_colors() { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Execute API call. - $frequency = new \local_assessfreq\frequency(); - $heatarray = $frequency->get_heat_colors(); - - return json_encode($heatarray); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_heat_colors_returns() { - return new external_value(PARAM_RAW, 'Event JSON'); - } - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_process_modules_parameters() { - return new external_function_parameters([ - // If I had params they'd be here, but I don't, so they're not. - ]); - } - - /** - * Returns modules enabled for processing along with their module name string. - * - * @return string JSON response. - */ - public static function get_process_modules() { - \core\session\manager::write_close(); // Close session early this is a read op. - - $modulesandstrings = ['number' => get_string('numberevents', 'local_assessfreq')]; - - // Execute API call. - $frequency = new \local_assessfreq\frequency(); - $processmodules = $frequency->get_process_modules(); - - foreach ($processmodules as $module) { - $modulesandstrings[$module] = get_string('modulename', $module); - } - - return json_encode($modulesandstrings); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_process_modules_returns() { - return new external_value(PARAM_RAW, 'Event JSON'); - } - - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_day_events_parameters() { - return new external_function_parameters([ - 'jsondata' => new external_value(PARAM_RAW, 'The data encoded as a json array'), - ]); - } - - /** - * Returns event frequency map for all users in site. - * - * @param string $jsondata JSON data. - * @return string JSON response. - */ - public static function get_day_events($jsondata) { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Parameter validation. - self::validate_parameters( - self::get_day_events_parameters(), - ['jsondata' => $jsondata] - ); - - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - - // Execute API call. - $data = json_decode($jsondata, true); - $frequency = new \local_assessfreq\frequency(); - $freqarr = $frequency->get_day_events($data['date'], $data['modules']); - - return json_encode($freqarr); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_day_events_returns() { - return new external_value(PARAM_RAW, 'Event JSON'); - } - - /** - * Returns description of method parameters. - * - * @return void + * @return external_function_parameters */ - public static function get_courses_parameters() { + public static function get_courses_parameters() : external_function_parameters { return new external_function_parameters([ 'query' => new external_value(PARAM_TEXT, 'The query to find'), ]); } /** - * Returns courses and quizzes in that course that match search data. + * Returns courses that match search data. * * @param string $query The search query. * @return string JSON response. */ - public static function get_courses($query) { - global $DB; - \core\session\manager::write_close(); // Close session early this is a read op. + public static function get_courses(string $query) : string { + global $DB, $SITE, $COURSE; + manager::write_close(); // Close session early this is a read op. // Parameter validation. self::validate_parameters( @@ -231,23 +65,28 @@ public static function get_courses($query) { ['query' => $query] ); - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - // Execute API call. - $sql = 'SELECT id, fullname FROM {course} WHERE ' . $DB->sql_like('fullname', ':fullname', false) . ' AND id <> 1'; + $sql = 'SELECT id, fullname, category FROM {course} WHERE ' . $DB->sql_like('fullname', ':fullname', false) . ' AND id <> 1'; $params = ['fullname' => '%' . $DB->sql_like_escape($query) . '%']; - $courses = $DB->get_records_sql($sql, $params, 0, 11); + $courses = $DB->get_records_sql($sql, $params, 0, 30); $data = []; + if (has_capability('local/assessfreq:view', context_system::instance())) { + $data[SITEID] = [ + "id" => $SITE->id, + "fullname" => external_format_string($SITE->fullname, true, ["escape" => false]) + ]; + } + $categories = \core_course_category::make_categories_list(); foreach ($courses as $course) { - $data[$course->id] = ["id" => $course->id, "fullname" => format_string( - $course->fullname, - true, - ["context" => $context, "escape" => false] - ), ]; + $data[$course->id] = [ + "id" => $course->id, + "fullname" => $categories[$course->category] . ' / ' . external_format_string($course->fullname, true, ["escape" => false]) + ]; + } + + if (isset($data[$COURSE->id])) { + unset($data[$COURSE->id]); } return json_encode(array_values($data)); @@ -255,120 +94,80 @@ public static function get_courses($query) { /** * Returns description of method result value - * @return external_description + * @return external_value */ - public static function get_courses_returns() { + public static function get_courses_returns() : external_value { return new external_value(PARAM_RAW, 'Course result JSON'); } /** * Returns description of method parameters. * - * @return void + * @return external_function_parameters */ - public static function get_quizzes_parameters() { + public static function get_activities_parameters() : external_function_parameters { return new external_function_parameters([ - 'query' => new external_value(PARAM_INT, 'The query to find'), + 'courseid' => new external_value(PARAM_INT, 'The courseid to find'), ]); } /** - * Returns courses and quizzes in that course that match search data. + * Returns activities in the course that match search data. * - * @param string $query The search query. + * @param $courseid * @return string JSON response. */ - public static function get_quizzes($query) { + public static function get_activities($courseid) : string { global $DB; - \core\session\manager::write_close(); // Close session early this is a read op. + manager::write_close(); // Close session early this is a read op. // Parameter validation. self::validate_parameters( - self::get_quizzes_parameters(), - ['query' => $query] + self::get_activities_parameters(), + ['courseid' => $courseid] ); - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - // Execute API call. - $params = ['course' => $query]; - $quizzes = $DB->get_records('quiz', $params, 'name ASC', 'id, name'); + $modules = $DB->get_records('course_modules', ['course' => $courseid]); + + $sources = get_sources(); $data = []; - foreach ($quizzes as $quiz) { - $data[$quiz->id] = ["id" => $quiz->id, "name" => format_string( - $quiz->name, - true, - ["context" => $context, "escape" => false] - ), ]; + foreach ($modules as $module) { + $modinfo = get_fast_modinfo($courseid); + $cm = $modinfo->get_cm($module->id); + // Skip over if source is not enabled or if the source doesn't have an activity dashboard. + $moduletype = $cm->modname; + if (!isset($sources[$moduletype]) || !method_exists($sources[$moduletype], 'get_activity_dashboard')) { + continue; + } + + $data[$module->id] = [ + "id" => $module->id, + "name" => $cm->get_module_type_name() . " - " . $cm->get_name() + ]; } + usort($data, fn($a, $b) => $a['name'] <=> $b['name']); + return json_encode(array_values($data)); } /** * Returns description of method result value - * @return external_description + * @return external_value */ - public static function get_quizzes_returns() { - return new external_value(PARAM_RAW, 'Quiz result JSON'); + public static function get_activities_returns() : external_value { + return new external_value(PARAM_RAW, 'Result JSON'); } - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_quiz_data_parameters() { - return new external_function_parameters([ - 'quizid' => new external_value(PARAM_INT, 'The quiz id to get data for'), - ]); - } - - /** - * Returns quiz data. - * - * @param string $quizid The quiz id to get data for. - * @return string JSON response. - */ - public static function get_quiz_data($quizid) { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Parameter validation. - self::validate_parameters( - self::get_quiz_data_parameters(), - ['quizid' => $quizid] - ); - - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - - // Execute API call. - $quiz = new \local_assessfreq\quiz(); - $quizdata = $quiz->get_quiz_data($quizid); - - return json_encode($quizdata); - } - - /** - * Returns description of method result value - * @return external_description - */ - public static function get_quiz_data_returns() { - return new external_value(PARAM_RAW, 'Quiz data result JSON'); - } /** * Returns description of method parameters. * - * @return void + * @return external_function_parameters */ - public static function set_table_preference_parameters() { + public static function set_table_preference_parameters() : external_function_parameters { return new external_function_parameters([ 'tableid' => new external_value(PARAM_ALPHANUMEXT, 'The table id to set the preference for'), 'preference' => new external_value(PARAM_ALPHAEXT, 'The table preference to set'), @@ -377,15 +176,15 @@ public static function set_table_preference_parameters() { } /** - * Returns quiz data. + * Set table preferences. * * @param string $tableid The table id to set the preference for. * @param string $preference The name of the preference to set. * @param string $values The values to set for the preference, encoded as JSON. * @return string JSON response. */ - public static function set_table_preference($tableid, $preference, $values) { - global $SESSION; + public static function set_table_preference(string $tableid, string $preference, string $values) : string { + global $SESSION, $PAGE; // Parameter validation. self::validate_parameters( @@ -393,23 +192,14 @@ public static function set_table_preference($tableid, $preference, $values) { ['tableid' => $tableid, 'preference' => $preference, 'values' => $values] ); - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - // Set up the initial preference template. - if (isset($SESSION->flextable[$tableid])) { - $prefs = $SESSION->flextable[$tableid]; - } else { - $prefs = [ - 'collapse' => [], - 'sortby' => [], - 'i_first' => '', - 'i_last' => '', - 'textsort' => [], - ]; - } + $prefs = $SESSION->flextable[$tableid] ?? [ + 'collapse' => [], + 'sortby' => [], + 'i_first' => '', + 'i_last' => '', + 'textsort' => [], + ]; // Set or reset the preferences. if ($preference == 'reset') { @@ -437,38 +227,40 @@ public static function set_table_preference_returns() { return new external_value(PARAM_ALPHAEXT, 'Name of the updated preference'); } + /** * Returns description of method parameters * * @return external_function_parameters */ - public static function process_override_form_parameters() { + public static function process_override_form_parameters() : external_function_parameters { return new external_function_parameters( [ 'jsonformdata' => new external_value(PARAM_RAW, 'The data from the create copy form, encoded as a json array'), - 'quizid' => new external_value(PARAM_INT, 'The quiz id to processs the override for'), + 'activitytype' => new external_value(PARAM_ALPHANUMEXT, 'The activity to processs the override for'), + 'activityid' => new external_value(PARAM_INT, 'The activity id to processs the override for'), ] ); } /** - * Submit the quiz override form. + * Submit the override form. * * @param string $jsonformdata The data from the form, encoded as a json array. - * @param int $quizid The quiz id to add an override for. - * @throws moodle_exception + * @param string $activitytype The activity to add an override for. + * @param int $activityid The activity id to add an override for. * @return string */ - public static function process_override_form($jsonformdata, $quizid) { + public static function process_override_form(string $jsonformdata, string $activitytype, int $activityid) : string { global $DB; // Release session lock. - \core\session\manager::write_close(); + manager::write_close(); // We always must pass webservice params through validate_parameters. $params = self::validate_parameters( self::process_override_form_parameters(), - ['jsonformdata' => $jsonformdata, 'quizid' => $quizid] + ['jsonformdata' => $jsonformdata, 'activitytype' => $activitytype, 'activityid' => $activityid] ); $formdata = json_decode($params['jsonformdata']); @@ -476,56 +268,15 @@ public static function process_override_form($jsonformdata, $quizid) { $submitteddata = []; parse_str($formdata, $submitteddata); - // Check access. - $quizdata = new \local_assessfreq\quiz(); - $context = $quizdata->get_quiz_context($quizid); - self::validate_context($context); - has_capability('mod/quiz:manageoverrides', $context); - - // Check if we have an existing override for this user. - $override = $DB->get_record('quiz_overrides', ['quiz' => $quizid, 'userid' => $submitteddata['userid']]); - - // Submit the form data. - $quiz = $DB->get_record('quiz', ['id' => $quizid], '*', MUST_EXIST); - $cm = get_course_and_cm_from_cmid($context->instanceid, 'quiz')[1]; - $mform = new \local_assessfreq\form\quiz_override_form($cm, $quiz, $context, $override, $submitteddata); - - $mdata = $mform->get_data(); - - if ($mdata) { - $params = [ - 'context' => $context, - 'other' => [ - 'quizid' => $quizid, - ], - 'relateduserid' => $mdata->userid, - ]; - $mdata->quiz = $quizid; - - if (!empty($override->id)) { - $mdata->id = $override->id; - $DB->update_record('quiz_overrides', $mdata); - - // Determine which override updated event to fire. - $params['objectid'] = $override->id; - $event = \mod_quiz\event\user_override_updated::create($params); - // Trigger the override updated event. - $event->trigger(); - } else { - unset($mdata->id); - $mdata->id = $DB->insert_record('quiz_overrides', $mdata); - - // Determine which override created event to fire. - $params['objectid'] = $mdata->id; - $event = \mod_quiz\event\user_override_created::create($params); - // Trigger the override created event. - $event->trigger(); - } - } else { - throw new moodle_exception('submitoverridefail', 'local_assessfreq'); + $processid = 0; + $sources = get_sources(); + $source = $sources[$activitytype]; + /* @var $source source_base */ + if (method_exists($source, 'process_override_form')) { + $processid = $source->process_override_form($activityid, $submitteddata); } - return json_encode(['overrideid' => $mdata->id]); + return json_encode(['overrideid' => $processid]); } /** @@ -536,82 +287,4 @@ public static function process_override_form($jsonformdata, $quizid) { public static function process_override_form_returns() { return new external_value(PARAM_RAW, 'JSON response.'); } - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_system_timezone_parameters() { - return new external_function_parameters([ - // If I had params they'd be here, but I don't, so they're not. - ]); - } - - /** - * Returns system timezone. - * This method doesn't require login or user session update. - * It also doesn't need any capability check. - * - * @return string Timezone. - */ - public static function get_system_timezone() { - \core\session\manager::write_close(); // Close session early this is a read op. - global $DB; - - // Execute API call. - $timezone = $DB->get_field('config', 'value', ['name' => 'timezone'], MUST_EXIST); - - return $timezone; - } - - /** - * Returns description of method result value. - * - * @return external_description - */ - public static function get_system_timezone_returns() { - return new external_value(PARAM_TEXT, 'Timezone'); - } - - /** - * Returns description of method parameters. - * - * @return void - */ - public static function get_inprogress_counts_parameters() { - return new external_function_parameters([ - // If I had params they'd be here, but I don't, so they're not. - ]); - } - - /** - * Returns quiz summary data for upcomming and inprogress quizzes. - * - * @return string JSON response. - */ - public static function get_inprogress_counts() { - \core\session\manager::write_close(); // Close session early this is a read op. - - // Context validation and permission check. - $context = context_system::instance(); - self::validate_context($context); - has_capability('moodle/site:config', $context); - - // Execute API call. - $quiz = new \local_assessfreq\quiz(); - $now = time(); - $quizdata = $quiz->get_inprogress_counts($now); - - return json_encode($quizdata); - } - - /** - * Returns description of method result value. - * - * @return external_description - */ - public static function get_inprogress_counts_returns() { - return new external_value(PARAM_RAW, 'JSON quiz count data'); - } } diff --git a/classes/form/quiz_search_form.php b/classes/form/quiz_search_form.php deleted file mode 100644 index 385dc744..00000000 --- a/classes/form/quiz_search_form.php +++ /dev/null @@ -1,82 +0,0 @@ -. - -/** - * Form to search for quizzes. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\form; - -defined('MOODLE_INTERNAL') || die(); - -require_once("$CFG->libdir/formslib.php"); - -/** - * Form to search for quizzes. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class quiz_search_form extends \moodleform { - /** - * Build form for the broadcast message. - * - * {@inheritDoc} - * @see \moodleform::definition() - */ - public function definition() { - $mform = $this->_form; - $mform->disable_form_change_checker(); - - // Form heading. - $mform->addElement( - 'html', - \html_writer::div(get_string('searchquizform', 'local_assessfreq'), 'form-description mb-3') - ); - - $courseoptions = [ - 'multiple' => false, - 'placeholder' => get_string('entercourse', 'local_assessfreq'), - 'noselectionstring' => get_string('nocourse', 'local_assessfreq'), - 'ajax' => 'local_assessfreq/course_selector', - 'casesensitive' => false, - ]; - $mform->addElement('autocomplete', 'courses', get_string('course', 'local_assessfreq'), [], $courseoptions); - - $mform->addElement('hidden', 'coursechoice', '0'); - $mform->setType('coursechoice', PARAM_INT); - - $selectoptions = [ - 0 => get_string('selectcourse', 'local_assessfreq'), - -1 => get_string('loadingquiz', 'local_assessfreq'), - ]; - $mform->addElement( - 'select', - 'quiz', - get_string('quiz', 'local_assessfreq'), - $selectoptions - ); - $mform->disabledIf('quiz', 'coursechoice', 'eq', '0'); - - $btnstring = get_string('selectquiz', 'local_assessfreq'); - $this->add_action_buttons(true, $btnstring); - } -} diff --git a/classes/form/scheduler.php b/classes/form/scheduler.php deleted file mode 100644 index fa06bbf5..00000000 --- a/classes/form/scheduler.php +++ /dev/null @@ -1,55 +0,0 @@ -. - - -/** - * Text type form element - * - * Contains HTML class for a text type element - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -defined('MOODLE_INTERNAL') || die(); - -global $CFG; -require_once($CFG->libdir . '/form/static.php'); - -/** - * Text type element - * - * HTML class for a text type element - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class scheduler_form_element extends MoodleQuickForm_static implements templatable { - /** - * Form element scheduler. - * - * @param string $elementname (optional) Name of the text field. - * @param string $elementlabel (optional) text field label. - * @param string $text (optional) Text to put in text field. - */ - public function __construct($elementname = null, $elementlabel = null, $text = null) { - global $OUTPUT; - $text = $OUTPUT->render_from_template('local_assessfreq/scheduler_form_element', ['foo' => $text]); - - parent::__construct($elementname, $elementlabel, $text); - } -} diff --git a/classes/frequency.php b/classes/frequency.php index 7681cccb..a729e62f 100644 --- a/classes/frequency.php +++ b/classes/frequency.php @@ -25,10 +25,19 @@ namespace local_assessfreq; use cache; +use context; +use core\dml\sql_join; +use core\oauth2\service\microsoft; +use Exception; +use moodle_recordset; +use stdClass; defined('MOODLE_INTERNAL') || die(); +global $CFG; + require_once($CFG->dirroot . '/calendar/lib.php'); +require_once($CFG->dirroot . '/local/assessfreq/lib.php'); /** * Frequency class. @@ -41,74 +50,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class frequency { - /** - * The due date databse field differs between module types. - * This map provides the translation. - * - * @var array $modduefield - */ - private $moduleendfield = [ - 'assign' => 'duedate', - 'choice' => 'timeclose', - 'data' => 'timeavailableto', - 'feedback' => 'timeclose', - 'forum' => 'duedate', - 'lesson' => 'deadline', - 'quiz' => 'timeclose', - 'scorm' => 'timeclose', - 'workshop' => 'submissionend', - ]; - - /** - * The start date databse field differs between module types. - * This map provides the translation. - * - * @var array $modduefield - */ - private $modulestartfield = [ - 'assign' => 'allowsubmissionsfromdate', - 'choice' => 'timeopen', - 'data' => 'timeavailablefrom', - 'feedback' => 'timeopen', - 'forum' => null, - 'lesson' => 'available', - 'quiz' => 'timeopen', - 'scorm' => 'timeopen', - 'workshop' => 'submissionstart', - ]; - - /** - * The time limit databse field differs between module types and only some support it. - * This map provides the translation - * - * @var array $moduletimelimit - */ - private $moduletimelimit = [ - 'leesson' => 'timelimit', - 'quiz' => 'timelimit', - - ]; - - - /** - * Map of capabilities that users must have - * before that activity event applies to them. - * - * @var array $capabilitymap - */ - private $capabilitymap = [ - 'assign' => ['mod/assign:submit', 'mod/assign:view'], - 'choice' => ['mod/choice:choose', 'mod/choice:view'], - 'data' => ['mod/data:writeentry', 'mod/data:viewentry', 'mod/data:view'], - 'feedback' => ['mod/feedback:complete', 'mod/feedback:viewanalysepage', 'mod/feedback:view'], - 'forum' => [ - 'mod/forum:startdiscussion', 'mod/forum:createattachment', 'mod/forum:replypost', 'mod/forum:viewdiscussion', ], - 'lesson' => ['mod/lesson:view'], - 'quiz' => ['mod/quiz:attempt', 'mod/quiz:view'], - 'scorm' => ['mod/scorm:savetrack', 'mod/scorm:viewscores'], - 'workshop' => ['mod/workshop:submit', 'mod/workshop:view'], - ]; - /** * Expiry period for caches. * @@ -121,27 +62,12 @@ class frequency { * * @var integer $batchsize */ - private $batchsize = 100; + private int $batchsize = 100; /** - * Get the modules to use in data collection. - * This is based on plugin configuration. - * - * @return array $modules The enabled modules. + * Cache of event users. */ - public function get_modules(): array { - $version = get_config('moodle', 'version'); - - // Start with a hardcoded list of modules. As there is not a good way to get a list of suppoerted modules. - // Different versions of Moodle have different supported modules. This is an anti pattern, but yeah... - if ($version < 2019052000) { // Versions less than 3.7 don't support forum due dates. - $availablemodules = ['assign', 'choice', 'data', 'feedback', 'lesson', 'quiz', 'scorm', 'workshop']; - } else { - $availablemodules = ['assign', 'choice', 'data', 'feedback', 'forum', 'lesson', 'quiz', 'scorm', 'workshop']; - } - - return $availablemodules; - } + private array $eventuserscache = []; /** * Given a modle shortname get capabilities that users must have @@ -151,20 +77,8 @@ public function get_modules(): array { * @return array Capabilities relating to the module. */ public function get_module_capabilities(string $module): array { - return $this->capabilitymap[$module]; - } - - /** - * Get currently enabled modules from the Moodle DB. - * - * @return array $modules The enabled modules. - */ - public function get_enabled_modules(): array { - global $DB; - - $modules = $DB->get_records_menu('modules', [], '', 'name, visible'); - - return $modules; + $sources = get_sources(true); + return $sources[$module]->get_user_capabilities(); } /** @@ -177,17 +91,13 @@ public function get_enabled_modules(): array { * @return array $modules Lis of modules to process. */ public function get_process_modules(): array { - $config = get_config('local_assessfreq'); - $modules = explode(',', $config->modules); - $disabledmodules = $config->disabledmodules; - - if (!$disabledmodules) { - $enabledmodules = $this->get_enabled_modules(); + $sources = get_sources(); + $modules = []; - foreach ($modules as $index => $module) { - if (empty($enabledmodules[$module])) { - unset($modules[$index]); - } + if (!empty($sources)) { + /* @var $source source_base */ + foreach ($sources as $source) { + $modules[] = $source->get_module(); } } @@ -200,19 +110,15 @@ public function get_process_modules(): array { * @param string $module Activity module to get data for. * @return string $sql The generated SQL. */ - private function get_sql_query(string $module): string { + private function get_sql_query(string $module, $duedate, $startdate, $timelimit): string { $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); - - $duedate = $this->moduleendfield[$module]; $sql = 'SELECT cm.id, cm.course, m.name, cm.instance, c.id as contextid, a.' . $duedate . ' AS duedate '; - if (!empty($this->modulestartfield[$module])) { - $startdate = $this->modulestartfield[$module]; + if ($startdate) { $sql .= ', a.' . $startdate . ' AS startdate '; } - if (!empty($this->moduletimelimit[$module])) { - $timelimit = $this->moduletimelimit[$module]; + if ($timelimit) { $sql .= ', a.' . $timelimit . ' AS timelimit '; } @@ -239,14 +145,12 @@ private function get_sql_query(string $module): string { * * @param string $sql * @param array $params - * @return \moodle_recordset + * @return moodle_recordset */ - private function get_module_events(string $sql, array $params): \moodle_recordset { + private function get_module_events(string $sql, array $params): moodle_recordset { global $DB; - $recordset = $DB->get_recordset_sql($sql, $params); - - return $recordset; + return $DB->get_recordset_sql($sql, $params); } /** @@ -257,13 +161,11 @@ private function get_module_events(string $sql, array $params): \moodle_recordse * @return array $timeelements Array of split time. */ private function format_time(int $timestamp): array { - $timeelements = [ + return [ 'endyear' => date('Y', $timestamp), 'endmonth' => date('m', $timestamp), 'endday' => date('d', $timestamp), ]; - - return $timeelements; } /** @@ -271,9 +173,9 @@ private function format_time(int $timestamp): array { * The event date may have been changed from in the past to in the future. In this case it may * not have been picked up by the delete records process. This method removes it a processing time. * - * @param \stdClass $record The record to process. + * @param stdClass $record The record to process. */ - private function cleanup_record(\stdClass $record): void { + private function cleanup_record(stdClass $record): void { global $DB; $params = ['module' => $record->module, 'instanceid' => $record->instanceid]; @@ -289,10 +191,10 @@ private function cleanup_record(\stdClass $record): void { * Take a recordest of events process * and store in correct database table. * - * @param \moodle_recordset $recordset - * @return array + * @param moodle_recordset $recordset + * @return int */ - private function process_module_events(\moodle_recordset $recordset): int { + private function process_module_events(moodle_recordset $recordset): int { global $DB; $recordsprocessed = 0; $toinsert = []; @@ -308,7 +210,7 @@ private function process_module_events(\moodle_recordset $recordset): int { // Iterate through the records and insert to database in batches. $timeelements = $this->format_time($record->duedate); - $insertrecord = new \stdClass(); + $insertrecord = new stdClass(); $insertrecord->module = $record->name; $insertrecord->instanceid = $record->instance; $insertrecord->courseid = $record->course; @@ -328,7 +230,6 @@ private function process_module_events(\moodle_recordset $recordset): int { // Insert in database. $DB->insert_records('local_assessfreq_site', $toinsert); $toinsert = []; // Reset array. - $recordsprocessed += count($toinsert); } } @@ -352,17 +253,24 @@ private function process_module_events(\moodle_recordset $recordset): int { */ public function process_site_events(int $duedate): int { $recordsprocessed = 0; - $enabledmods = $this->get_process_modules(); + $sources = get_sources(true); $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); - if (!empty($enabledmods[0])) { - // Itterate through modules. - foreach ($enabledmods as $module) { - $sql = $this->get_sql_query($module); + if (!empty($sources)) { + // Itterate through sources. + foreach ($sources as $source) { + + /* @var $source source_base */ + $sql = $this->get_sql_query( + $source->get_module_table(), + $source->get_close_field(), + $source->get_open_field(), + $source->get_timelimit_field() + ); if ($includehiddencourses) { - $params = [$module, CONTEXT_MODULE, $duedate, 1]; + $params = [$source->get_module(), CONTEXT_MODULE, $duedate, 1]; } else { - $params = [$module, CONTEXT_MODULE, $duedate, 1, 1]; + $params = [$source->get_module(), CONTEXT_MODULE, $duedate, 1, 1]; } $moduleevents = $this->get_module_events($sql, $params); // Get all events for module. @@ -378,11 +286,11 @@ public function process_site_events(int $duedate): int { * get the enrolled users with given capabilities for a given context. * Used to generte SQL for getting users in assessments. * - * @param \context $context The context to get the enrolled users for. + * @param context $context The context to get the enrolled users for. * @param array $capabilities The capabilities that users need to have. * @return array */ - public function generate_enrolled_wheres_joins_params(\context $context, array $capabilities): array { + public function generate_enrolled_wheres_joins_params(context $context, array $capabilities): array { $uid = 'u.id'; $joins = []; $wheres = []; @@ -401,33 +309,30 @@ public function generate_enrolled_wheres_joins_params(\context $context, array $ $wheres[] = "u.deleted = 0"; $wheres = implode(" AND ", $wheres); - $wherejoin = [$joins, $wheres, $params]; - - return $wherejoin; + return [$joins, $wheres, $params]; } /** * Our own implementation of get_enrolled_users. Allows us to check multiple capabilities * in less database queries. * - * @param \context $context The context to get the enrolled users for. + * @param context $context The context to get the enrolled users for. * @param array $capabilities The capabilities that users need to have. * @return array Enrolled user records */ - private function get_enrolled_users(\context $context, array $capabilities): array { + private function get_enrolled_users(context $context, array $capabilities): array { global $DB; [$joins, $wheres, $params] = $this->generate_enrolled_wheres_joins_params($context, $capabilities); - $finaljoin = new \core\dml\sql_join($joins, $wheres, $params); + $finaljoin = new sql_join($joins, $wheres, $params); $sql = "SELECT DISTINCT u.id - FROM {user} u - $finaljoin->joins - WHERE $finaljoin->wheres"; - $params = $finaljoin->params; + FROM {user} u + $finaljoin->joins + WHERE $finaljoin->wheres"; - return $DB->get_records_sql($sql, $params); + return $DB->get_records_sql($sql, $finaljoin->params); } /** @@ -436,17 +341,33 @@ private function get_enrolled_users(\context $context, array $capabilities): arr * this can take a long time. Consider using the get_event_users method * if you don't need the most up to date data. * - * @param int $contextid The context ID in a course for the event to check. + * @param int $contextid The module context ID for the event to check. * @param string $module The type of module the event is for. * @return array $users An array of user IDs. */ public function get_event_users_raw(int $contextid, string $module): array { - $context = \context::instance_by_id($contextid); + + $context = context::instance_by_id($contextid); + $coursecontext = $context->get_parent_context(); + + $cachekey = "{$coursecontext->id}-{$module}"; + if (isset($this->eventuserscache[$cachekey])) { + return $this->eventuserscache[$cachekey]; + } + $capabilities = $this->get_module_capabilities($module); - $users = $this->get_enrolled_users($context, $capabilities); + $roles = []; + foreach ($capabilities as $capability) { + $roles = $roles + get_roles_with_capability($capability, CAP_ALLOW, $context); + } + $users = []; + foreach ($roles as $role) { + $users = $users + get_users_from_role_on_context($role, $coursecontext); + } - return $users; + $this->eventuserscache[$cachekey] = array_column($users, 'userid'); + return $this->eventuserscache[$cachekey]; } /** @@ -460,8 +381,7 @@ public function get_event_users_raw(int $contextid, string $module): array { */ public function get_event_users(int $contextid, string $module, bool $cache = true): array { global $DB; - $users = []; - $cachekey = (string)$contextid . '_' . $module; + $cachekey = $contextid . '_' . $module; // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'eventusers'); @@ -483,7 +403,7 @@ public function get_event_users(int $contextid, string $module, bool $cache = tr // Update cache. if (!empty($users)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->users = $users; $usercache->set($cachekey, $data); @@ -497,22 +417,20 @@ public function get_event_users(int $contextid, string $module, bool $cache = tr * Get stored events from a specified date. * * @param int $duedate The duedate to get events from. - * @return \moodle_recordset Recordset of event info. + * @return moodle_recordset Recordset of event info. */ - private function get_stored_events(int $duedate): \moodle_recordset { + private function get_stored_events(int $duedate): moodle_recordset { global $DB; $select = 'timeend >= ?'; $params = [$duedate]; - $recordset = $DB->get_recordset_select( + return $DB->get_recordset_select( 'local_assessfreq_site', $select, $params, 'timeend DESC', 'id, contextid, module' ); - - return $recordset; } /** @@ -526,8 +444,8 @@ private function get_stored_events(int $duedate): \moodle_recordset { private function prepare_user_event_records(array $users, int $eventid): array { $userrecords = []; foreach ($users as $user) { - $record = new \stdClass(); - $record->userid = $user->id; + $record = new stdClass(); + $record->userid = $user->userid; $record->eventid = $eventid; $userrecords[] = $record; @@ -574,8 +492,8 @@ public function delete_events(int $duedate): void { $select = 'timeend >= ?'; // We do the following in a transaction to maintain data consistency. + $transaction = $DB->start_delegated_transaction(); try { - $transaction = $DB->start_delegated_transaction(); $userevents = $DB->get_fieldset_select('local_assessfreq_site', 'id', $select, [$duedate]); // Delete site events. @@ -591,8 +509,19 @@ public function delete_events(int $duedate): void { } } + // Clear the caches to prevent desync between caches and database. + cache::make('local_assessfreq', 'siteevents')->purge(); + cache::make('local_assessfreq', 'userevents')->purge(); + cache::make('local_assessfreq', 'courseevents')->purge(); + cache::make('local_assessfreq', 'eventsduemonth')->purge(); + cache::make('local_assessfreq', 'monthlyuser')->purge(); + cache::make('local_assessfreq', 'eventsdueactivity')->purge(); + cache::make('local_assessfreq', 'yearevents')->purge(); + cache::make('local_assessfreq', 'usereventsallfrequencyarray')->purge(); + cache::make('local_assessfreq', 'eventusers')->purge(); + $transaction->allow_commit(); - } catch (\Exception $e) { + } catch (Exception $e) { $transaction->rollback($e); } } @@ -600,21 +529,20 @@ public function delete_events(int $duedate): void { /** * Delete processed event. * - * @param \stdClass $event The event to delete. + * @param stdClass $event The event to delete. */ - public function delete_event(\stdClass $event): void { + public function delete_event(stdClass $event): void { global $DB; // We do the following in a transaction to maintain data consistency. + $transaction = $DB->start_delegated_transaction(); try { - $transaction = $DB->start_delegated_transaction(); - // Delete site events. $DB->delete_records('local_assessfreq_site', ['id' => $event->id]); $DB->delete_records('local_assessfreq_user', ['eventid' => $event->id]); $transaction->allow_commit(); - } catch (\Exception $e) { + } catch (Exception $e) { $transaction->rollback($e); } } @@ -628,7 +556,7 @@ public function delete_event(\stdClass $event): void { * @param int $to Timestamp to fiter to. * @return array $filteredevents The list of filtered events. */ - private function filter_event_data($events, int $from, int $to = 0): array { + private function filter_event_data(array $events, int $from, int $to = 0): array { $filteredevents = []; // If an explicit to date was not defined default to a year from now. @@ -650,15 +578,15 @@ private function filter_event_data($events, int $from, int $to = 0): array { * Get site events. * This is events across all courses. * + * @param int $courseid The course to get events for or all events. This is not used here but kept for function mapping. * @param string $module The module to get events for or all events. * @param int $from The timestamp to get events from. * @param int $to The timestamp to get events to. * @param bool $cache If false cache won't be used fresh data will be retrieved from DB. * @return array $events An array of site events */ - public function get_site_events(string $module = 'all', int $from = 0, int $to = 0, bool $cache = true): array { + public function get_site_events(int $courseid, string $module = 'all', int $from = 0, int $to = 0, bool $cache = true): array { global $DB; - $events = []; // Try to get value from cache. $sitecache = cache::make('local_assessfreq', 'siteevents'); @@ -694,7 +622,7 @@ public function get_site_events(string $module = 'all', int $from = 0, int $to = // Update cache. if (!empty($rawevents)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $rawevents; $sitecache->set($module, $data); @@ -708,14 +636,14 @@ public function get_site_events(string $module = 'all', int $from = 0, int $to = /** * Get all events that are ending on a given date. * + * @param int $courseid The course to get events for. * @param string $date The end date for the event. * @param string $module The module to get events for or all events. * * @return array $events An array of site events */ - public function get_day_ending_events(string $date, string $module = 'all'): array { + public function get_day_ending_events(int $courseid , string $date, string $module = 'all'): array { global $DB; - $events = []; // TODO: Think about some caching here. // TODO: Improve unit test coverage for this. @@ -755,9 +683,13 @@ public function get_day_ending_events(string $date, string $module = 'all'): arr $params[] = $tostart; $params[] = $toend; - $events = $DB->get_records_sql($sql, $params); + // Add the courseid restrictions. + if ($courseid != SITEID) { + $params[] = $courseid; + $sql .= " AND c.id = ?"; + } - return $events; + return $DB->get_records_sql($sql, $params); } /** @@ -778,8 +710,8 @@ public function get_course_events( bool $cache = true ): array { global $DB; - $events = []; - $cachekey = (string)$courseid . '_' . $module; + + $cachekey = $courseid . '_' . $module . '_' . $from . '_' . $to; // Try to get value from cache. $coursecache = cache::make('local_assessfreq', 'courseevents'); @@ -801,7 +733,7 @@ public function get_course_events( // Update cache. if (!empty($rawevents)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $rawevents; $coursecache->set($cachekey, $data); @@ -823,8 +755,8 @@ public function get_course_events( */ public function get_user_events(int $userid, string $module = 'all', int $from = 0, int $to = 0, bool $cache = true): array { global $DB; - $events = []; - $cachekey = (string)$userid . '_' . $module; + + $cachekey = $userid . '_' . $module; // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'userevents'); @@ -859,7 +791,7 @@ public function get_user_events(int $userid, string $module = 'all', int $from = // Update cache. if (!empty($rawevents)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $rawevents; $usercache->set($cachekey, $data); @@ -872,12 +804,13 @@ public function get_user_events(int $userid, string $module = 'all', int $from = /** * Return events for all users. * + * @param int $courseid The course to get events from. * @param string $module The module to get events for or all events. * @param int $from The timestamp to get events from. * @param int $to The timestamp to get events to. * @return array $events An array of site events */ - public function get_user_events_all(string $module = 'all', int $from = 0, int $to = 0): iterable { + public function get_user_events_all(int $courseid, string $module = 'all', int $from = 0, int $to = 0): iterable { global $DB; $rowkey = $DB->sql_concat('s.id', "'_'", 'u.userid'); @@ -896,12 +829,19 @@ public function get_user_events_all(string $module = 'all', int $from = 0, int $ $sql .= ' WHERE s.module = ?'; } + // Should we include hidden courses. $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); if (!$includehiddencourses) { $params[] = 1; $sql .= " AND c.visible = ?"; } + // Add the courseid restriction. + if ($courseid != SITEID) { + $params[] = $courseid; + $sql .= " AND c.id = ?"; + } + // If an explicit to date was not defined default to a year from now. if ($to === 0) { $to = time() + YEARSECS; @@ -911,9 +851,7 @@ public function get_user_events_all(string $module = 'all', int $from = 0, int $ $params[] = $to; $sql .= " AND s.timeend >= ? AND s.timeend < ?"; - $events = $DB->get_recordset_sql($sql, $params); - - return $events; + return $DB->get_records_sql($sql, $params); } /** @@ -923,10 +861,15 @@ public function get_user_events_all(string $module = 'all', int $from = 0, int $ * @param bool $cache Fetch events from cache. * @return array $events The events. */ - public function get_events_due_by_month(int $year, bool $cache = true): array { - global $DB; - $events = []; - $cachekey = (string)$year; + public function get_events_due_by_month(int $year, int $month = 0, bool $cache = true): array { + global $DB, $PAGE; + + // Adjust the cache key based on course. + if ($PAGE->course->id != SITEID) { + $cachekey = $PAGE->course->id . '_' . $year . '_' . $month; + } else { + $cachekey = $year . '_' . $month; + } // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'eventsduemonth'); @@ -937,12 +880,10 @@ public function get_events_due_by_month(int $year, bool $cache = true): array { } else { // Not valid cache data. $modules = $this->get_process_modules(); [$insql, $params] = $DB->get_in_or_equal($modules); - $params[] = $year; $sql = "SELECT s.endmonth, COUNT(s.id) as count FROM {local_assessfreq_site} s LEFT JOIN {course} c ON s.courseid = c.id - WHERE s.module $insql - AND s.endyear = ?"; + WHERE s.module $insql "; $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); if (!$includehiddencourses) { @@ -950,7 +891,29 @@ public function get_events_due_by_month(int $year, bool $cache = true): array { $sql .= " AND c.visible = ? "; } - $sql .= 'GROUP BY s.endmonth + // Add the courseid restriction. + if ($PAGE->course->id != SITEID) { + $params[] = $PAGE->course->id; + $sql .= " AND c.id = ? "; + } + + // Add month restrictions. + if ($month && $month > 1) { + $params[] = $month; + $params[] = $year; + $params[] = $month; + $params[] = $year + 1; + $sql .= " AND (s.endmonth >= ? AND s.endyear = ? OR s.endmonth < ? AND s.endyear = ?) "; + } else if ($month == 1) { + $params[] = $month; + $params[] = $year; + $sql .= " AND s.endmonth >= ? AND s.endyear = ? "; + } else { + $params[] = $year; + $sql .= " AND s.endyear = ? "; + } + + $sql .= ' GROUP BY s.endmonth ORDER BY s.endmonth ASC'; $events = $DB->get_records_sql($sql, $params); @@ -959,7 +922,7 @@ public function get_events_due_by_month(int $year, bool $cache = true): array { // Update cache. if (!empty($events)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $events; $usercache->set($cachekey, $data); @@ -975,10 +938,15 @@ public function get_events_due_by_month(int $year, bool $cache = true): array { * @param bool $cache Fetch events from cache. * @return array $events The events. */ - public function get_events_due_monthly_by_user(int $year, bool $cache = true): array { - global $DB; - $events = []; - $cachekey = (string)$year; + public function get_events_due_monthly_by_user(int $year, int $month = 0, bool $cache = true): array { + global $DB, $PAGE; + + // Adjust the cache key based on course. + if ($PAGE->course->id != SITEID) { + $cachekey = $PAGE->course->id . '_' . $year . '_' . $month; + } else { + $cachekey = $year . '_' . $month; + } // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'monthlyuser'); @@ -989,13 +957,11 @@ public function get_events_due_monthly_by_user(int $year, bool $cache = true): a } else { // Not valid cache data. $modules = $this->get_process_modules(); [$insql, $params] = $DB->get_in_or_equal($modules); - $params[] = $year; $sql = "SELECT s.endmonth, COUNT(u.id) as count FROM {local_assessfreq_site} s INNER JOIN {local_assessfreq_user} u ON s.id = u.eventid INNER JOIN {course} c ON s.courseid = c.id - WHERE s.module $insql - AND s.endyear = ?"; + WHERE s.module $insql "; $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); if (!$includehiddencourses) { @@ -1003,7 +969,29 @@ public function get_events_due_monthly_by_user(int $year, bool $cache = true): a $sql .= " AND c.visible = ? "; } - $sql .= 'GROUP BY s.endmonth + // Add the courseid restriction. + if ($PAGE->course->id != SITEID) { + $params[] = $PAGE->course->id; + $sql .= " AND c.id = ? "; + } + + // Add month restrictions. + if ($month && $month > 1) { + $params[] = $month; + $params[] = $year; + $params[] = $month; + $params[] = $year + 1; + $sql .= " AND (s.endmonth >= ? AND s.endyear = ? OR s.endmonth < ? AND s.endyear = ?) "; + } else if ($month == 1) { + $params[] = $month; + $params[] = $year; + $sql .= " AND s.endmonth >= ? AND s.endyear = ? "; + } else { + $params[] = $year; + $sql .= " AND s.endyear = ? "; + } + + $sql .= ' GROUP BY s.endmonth ORDER BY s.endmonth ASC'; $events = $DB->get_records_sql($sql, $params); @@ -1012,7 +1000,7 @@ public function get_events_due_monthly_by_user(int $year, bool $cache = true): a // Update cache. if (!empty($events)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $events; $usercache->set($cachekey, $data); @@ -1028,10 +1016,15 @@ public function get_events_due_monthly_by_user(int $year, bool $cache = true): a * @param bool $cache Fetch events from cache. * @return array $events The events. */ - public function get_events_due_by_activity(int $year, bool $cache = true): array { - global $DB; - $events = []; - $cachekey = (string)$year . '_activity'; + public function get_events_due_by_activity(int $year, int $month = 0, bool $cache = true): array { + global $DB, $PAGE; + + // Adjust the cache key based on course. + if ($PAGE->course->id != SITEID) { + $cachekey = $PAGE->course->id . '_' . $year . '_' . $month; + } else { + $cachekey = $year . '_' . $month; + } // Try to get value from cache. $usercache = cache::make('local_assessfreq', 'eventsdueactivity'); @@ -1040,11 +1033,10 @@ public function get_events_due_by_activity(int $year, bool $cache = true): array if ($data && (time() < $data->expiry) && $cache) { // Valid cache data. $events = $data->events; } else { // Not valid cache data. - $params = [$year]; + $params = []; $sql = 'SELECT s.module, COUNT(s.id) as count FROM {local_assessfreq_site} s - LEFT JOIN {course} c ON s.courseid = c.id - WHERE s.endyear = ?'; + LEFT JOIN {course} c ON s.courseid = c.id '; $includehiddencourses = get_config('local_assessfreq', 'hiddencourses'); if (!$includehiddencourses) { @@ -1052,7 +1044,29 @@ public function get_events_due_by_activity(int $year, bool $cache = true): array $sql .= " AND c.visible = ? "; } - $sql .= 'GROUP BY s.module + // Add the courseid restriction. + if ($PAGE->course->id != SITEID) { + $params[] = $PAGE->course->id; + $sql .= " AND c.id = ? "; + } + + // Add month restrictions. + if ($month && $month > 1) { + $params[] = $month; + $params[] = $year; + $params[] = $month; + $params[] = $year + 1; + $sql .= " AND (s.endmonth >= ? AND s.endyear = ? OR s.endmonth < ? AND s.endyear = ?) "; + } else if ($month == 1) { + $params[] = $month; + $params[] = $year; + $sql .= " AND s.endmonth >= ? AND s.endyear = ? "; + } else { + $params[] = $year; + $sql .= " AND s.endyear = ? "; + } + + $sql .= ' GROUP BY s.module ORDER BY s.module ASC'; $events = $DB->get_records_sql($sql, $params); @@ -1061,7 +1075,7 @@ public function get_events_due_by_activity(int $year, bool $cache = true): array // Update cache. if (!empty($events)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $events; $usercache->set($cachekey, $data); @@ -1078,7 +1092,6 @@ public function get_events_due_by_activity(int $year, bool $cache = true): array */ public function get_years_has_events(bool $cache = true): array { global $DB; - $years = []; $cachekey = 'yearevents'; // Try to get value from cache. @@ -1097,7 +1110,7 @@ public function get_years_has_events(bool $cache = true): array { // Update cache. if (!empty($years)) { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->events = $years; $usercache->set($cachekey, $data); @@ -1109,12 +1122,14 @@ public function get_years_has_events(bool $cache = true): array { /** * Get all events on a particular day. * + * @param int $courseid The course to get events for. * @param string $date A string representations of the date to get events for. * @param array $modules The modules to get events for. * @return array $dayevents The list of events that day. */ - public function get_day_events(string $date, array $modules): array { + public function get_day_events(int $courseid, string $date, array $modules): array { $dayevents = []; + $events = []; if (empty($modules)) { $modules = ['all']; @@ -1122,21 +1137,23 @@ public function get_day_events(string $date, array $modules): array { // Get the raw events. if (in_array('all', $modules)) { - $events = $this->get_day_ending_events($date, 'all'); + $events = $this->get_day_ending_events($courseid, $date); } else { // Work through the event array. foreach ($modules as $module) { if ($module == 'all') { continue; } else { - $events = array_merge($events, $this->get_day_ending_events($date, $module)); + $events = array_merge($events, $this->get_day_ending_events($courseid, $date, $module)); } } } + $sources = get_sources(); + // Get additional information and format the event data. foreach ($events as $event) { - $context = \context::instance_by_id($event->contextid, IGNORE_MISSING); + $context = context::instance_by_id($event->contextid, IGNORE_MISSING); $course = get_course($event->courseid); if ($context) { @@ -1144,13 +1161,15 @@ public function get_day_events(string $date, array $modules): array { $event->url = $context->get_url()->out(); $event->usercount = count($this->get_event_users($event->contextid, $event->module)); $event->timelimit = - ($event->timelimit == 0) ? get_string('na', 'local_assessfreq') : round(($event->timelimit / 60)); + ($event->timelimit == 0) ? '-' : round(($event->timelimit / 60)); + $event->dashurl = ''; - if ($event->module == 'quiz') { - $dashurl = new \moodle_url('/local/assessfreq/dashboard_quiz.php', ['id' => $event->instanceid]); + /* @var $source source_base */ + $source = $sources[$event->module]; + if (method_exists($source, 'get_activity_dashboard')) { + $dashurl = new \moodle_url('/local/assessfreq/', ['activityid' => $context->instanceid], 'activity_dashboard'); $event->dashurl = $dashurl->out(); } - $event->courseshortname = $course->shortname; $dayevents[] = $event; @@ -1186,24 +1205,28 @@ public function get_day_events(string $date, array $modules): array { * @param array $modules List of modules to get events for. * @return array $freqarray The array of even frequencies. */ - public function get_frequency_array(int $year, string $metric, array $modules): array { + public function get_frequency_array(int $year = 0, int $month = 0, string $metric = 'assess', array $modules = []): array { + global $PAGE; + $freqarray = []; $events = []; - $from = mktime(0, 0, 0, 1, 1, $year); - $to = mktime(23, 59, 59, 12, 31, $year); + $from = mktime(0, 0, 0, $month, 1, $year); + $to = strtotime("+1 year", $from) - 1; $userfreqarraycache = cache::make('local_assessfreq', 'usereventsallfrequencyarray'); sort($modules); - $cachekey = implode("_", $modules) . '_' . (string)$from . '_' . (string)$to; - - if ($metric == 'assess') { + $cachekey = $PAGE->course->id . '_' . implode("_", $modules) . '_' . $from . '_' . $to; + if ($PAGE->course->id == SITEID) { $functionname = 'get_site_events'; - } else if ($metric == 'students') { + } else { + $functionname = 'get_course_events'; + } + + if ($metric == 'students') { + $functionname = 'get_user_events_all'; $data = $userfreqarraycache->get($cachekey); - if ($data && $metric == 'students' && (time() < $data->expiry)) { + if ($data && (time() < $data->expiry)) { return $data->freqarray; } - - $functionname = 'get_user_events_all'; } if (empty($modules)) { @@ -1211,17 +1234,21 @@ public function get_frequency_array(int $year, string $metric, array $modules): } // Get the raw events. - if (in_array('all', $modules)) { - $events = $this->$functionname('all', $from, $to); - } else { - // Work through the event array. - foreach ($modules as $module) { - $events = array_merge($events, $this->$functionname($module, $from, $to)); + if (method_exists($this, $functionname)) { + if (in_array('all', $modules)) { + $events = $this->$functionname($PAGE->course->id, 'all', $from, $to); + } else { + // Work through the event array. + foreach ($modules as $module) { + $events = array_merge($events, $this->$functionname($PAGE->course->id, $module, $from, $to)); + } } } // Iterate through the events, building the frequency array. + raise_memory_limit(MEMORY_EXTRA); foreach ($events as $event) { + $year = $event->endyear; $month = $event->endmonth; $day = $event->endday; $module = $event->module; @@ -1249,7 +1276,7 @@ public function get_frequency_array(int $year, string $metric, array $modules): */ if ($functionname == 'get_user_events_all') { $expiry = time() + $this->expiryperiod; - $data = new \stdClass(); + $data = new stdClass(); $data->expiry = $expiry; $data->freqarray = $freqarray; $userfreqarraycache->set($cachekey, $data); @@ -1266,17 +1293,21 @@ public function get_frequency_array(int $year, string $metric, array $modules): * @param array $modules The modules to get. * @return array $data The data for the download file. */ - public function get_download_data(int $year, string $metric, array $modules): array { - global $DB; + public function get_download_data(int $year, int $month, string $metric, array $modules): array { + global $DB, $PAGE; $data = []; $events = []; - $from = mktime(0, 0, 0, 1, 1, $year); - $to = mktime(23, 59, 59, 12, 31, $year); + $from = mktime(0, 0, 0, $month, 1, $year); + $to = strtotime("+1 year", $from) - 1; if ($metric == 'assess') { - $functionname = 'get_site_events'; - } else if ($metric == 'students') { + if ($PAGE->course->id == SITEID) { + $functionname = 'get_site_events'; + } else { + $functionname = 'get_course_events'; + } + } else { $functionname = 'get_user_events_all'; } @@ -1286,28 +1317,29 @@ public function get_download_data(int $year, string $metric, array $modules): ar // Get the raw events. if (in_array('all', $modules)) { - $events = $this->$functionname('all', $from, $to); + $events = $this->$functionname($PAGE->course->id, 'all', $from, $to); } else { // Work through the event array. foreach ($modules as $module) { if ($module == 'all') { continue; } else { - $events = array_merge($events, $this->$functionname($module, $from, $to)); + $events = array_merge($events, $this->$functionname($PAGE->course->id, $module, $from, $to)); } } } + // Soert the data by timeend. + usort($events, function($a, $b) { + return strcmp($a->timeend, $b->timeend); + }); + // Format the data ready for download. foreach ($events as $event) { $row = []; // Catch exception when context does not exist because assessfreq tables are out of sync. - try { - $context = \context::instance_by_id($event->contextid); - } catch (\dml_missing_record_exception $ex) { - continue; - } + $context = context::instance_by_id($event->contextid); $activity = get_string('modulename', $event->module); $startdate = userdate($event->timestart, get_string('strftimedatetimeshort', 'langconfig')); @@ -1336,116 +1368,6 @@ public function get_download_data(int $year, string $metric, array $modules): ar } $data[] = $row; } - return $data; } - - /** - * Get heat colors to use id nheatmap display from plugin configuration. - * - * @return array - */ - public function get_heat_colors(): array { - $config = get_config('local_assessfreq'); - - $heatcolors = [ - 1 => $config->heat1, - 2 => $config->heat2, - 3 => $config->heat3, - 4 => $config->heat4, - 5 => $config->heat5, - 6 => $config->heat6, - ]; - - return $heatcolors; - } - - /** - * Purge all plugin caches. - * This is invoked when a plugin setting is changed. - * - * @param string $name Name of the setting change that invoked the purge. - */ - public static function purge_caches($name): void { - global $CFG; - - // Get plugin cache definitions. - $definitions = []; - include($CFG->dirroot . '/local/assessfreq/db/caches.php'); - $definitionnames = array_keys($definitions); - - // Clear each cache. - foreach ($definitionnames as $definitionname) { - $cache = cache::make('local_assessfreq', $definitionname); - $cache->purge(); - } - } - - /** - * Get assessment conflicts. - * - * @param int $now The timestamp to get the conflicts for. - * @return array $conflicts The conflict data. - */ - private function get_conflicts(int $now): array { - global $DB; - $conflicts = []; - - // A conflict is an overlapping date range for two or more quizzes where the quiz has at least one common student. - $eventsql = 'SELECT lasa.id as eventid, lasb.id as conflictid - FROM {local_assessfreq_site} lasa - INNER JOIN {local_assessfreq_site} lasb ON (lasa.timestart > lasb.timestart AND lasa.timestart < lasb.timeend) - OR (lasa.timeend > lasb.timestart AND lasa.timeend < lasb.timeend) - OR (lasa.timeend > lasb.timeend AND lasa.timestart < lasb.timestart) - WHERE lasa.module = ? - AND lasb.module = ? - AND lasa.timestart > ?'; - $eventparams = ['quiz', 'quiz', $now, $now]; - $recordset = $DB->get_recordset_sql($eventsql, $eventparams); - - foreach ($recordset as $record) { - $usersql = 'SELECT DISTINCT laua.userid - FROM {local_assessfreq_user} laua - INNER JOIN {local_assessfreq_user} laub on laua.userid = laub.userid - WHERE laua.eventid = ? - AND laub.eventid = ?'; - - $userparams = [$record->eventid, $record->conflictid]; - $users = $DB->get_fieldset_sql($usersql, $userparams); - - if (!empty($users)) { - $conflict = new \stdClass(); - $conflict->eventid = $record->eventid; - $conflict->conflictid = $record->conflictid; - $conflict->users = $users; - - $conflicts[] = $conflict; - } - } - $recordset->close(); - - return $conflicts; - } - - /** - * Process the conflicts. - * - * @return array $conflicts Conflict data. - */ - public function process_conflicts(): array { - - // Final result should look like this. - $conflicts['eventid'] = [ - [ - 'conflicteventid' => 123, - 'effecteduserids' => [1, 2, 3], - ], - [ - 'conflicteventid' => 456, - 'effecteduserids' => [4, 5, 6], - ], - ]; - - return $conflicts; - } } diff --git a/classes/output/all_participants_inprogress.php b/classes/output/all_participants_inprogress.php deleted file mode 100644 index 6c1d746f..00000000 --- a/classes/output/all_participants_inprogress.php +++ /dev/null @@ -1,124 +0,0 @@ -. - -/** - * Renderable for all participant summary card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\output; - -use local_assessfreq\quiz; - -/** - * Renderable for all participant summary card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class all_participants_inprogress { - /** - * Generate the markup for the summary chart, - * used in the in progress quizzes dashboard. - * - * @param int $now Timestamp to get chart data for. - * @param int $hoursahead Amount of time in hours to look ahead for quizzes starting. - * @param int $hoursbehind Amount of time in hours to look behind for quizzes starting. - * @return array With Generated chart object and chart data status. - */ - public function get_all_participants_inprogress_chart(int $now, int $hoursahead = 0, int $hoursbehind = 0): array { - - // Get quizzes for the supplied timestamp. - $quiz = new quiz($hoursahead, $hoursbehind); - $quizzes = $quiz->get_quiz_summaries($now); - - $inprogressquizzes = $quizzes['inprogress']; - $upcommingquizzes = $quizzes['upcomming']; - $finishedquizzes = $quizzes['finished']; - - foreach ($upcommingquizzes as $timestamp => $upcommingquiz) { - foreach ($upcommingquiz as $timestampupcomming => $upcomming) { - $inprogressquizzes[$timestampupcomming] = $upcomming; - } - } - - foreach ($finishedquizzes as $timestamp => $finishedquiz) { - foreach ($finishedquiz as $timestampfinished => $finished) { - $inprogressquizzes[$timestampfinished] = $finished; - } - } - - $notloggedin = 0; - $loggedin = 0; - $inprogress = 0; - $finished = 0; - - foreach ($inprogressquizzes as $quizobj) { - if (!empty($quizobj->tracking)) { - $notloggedin += $quizobj->tracking->notloggedin; - $loggedin += $quizobj->tracking->loggedin; - $inprogress += $quizobj->tracking->inprogress; - $finished += $quizobj->tracking->finished; - } - } - - $result = []; - - if (($notloggedin == 0) && ($loggedin == 0) && ($inprogress == 0) && ($finished == 0)) { - $result['hasdata'] = false; - $result['chart'] = false; - } else { - $result['hasdata'] = true; - - $seriesdata = [ - $notloggedin, - $loggedin, - $inprogress, - $finished, - ]; - - $labels = [ - get_string('notloggedin', 'local_assessfreq'), - get_string('loggedin', 'local_assessfreq'), - get_string('inprogress', 'local_assessfreq'), - get_string('finished', 'local_assessfreq'), - ]; - - $colors = [ - get_config('local_assessfreq', 'notloggedincolor'), - get_config('local_assessfreq', 'loggedincolor'), - get_config('local_assessfreq', 'inprogresscolor'), - get_config('local_assessfreq', 'finishedcolor'), - ]; - - // Create chart object. - $chart = new \core\chart_pie(); - $chart->set_doughnut(true); - $participants = new \core\chart_series(get_string('participants', 'local_assessfreq'), $seriesdata); - $participants->set_colors($colors); - $chart->add_series($participants); - $chart->set_labels($labels); - - $result['chart'] = $chart; - } - - return $result; - } -} diff --git a/classes/output/dashboard_table.php b/classes/output/dashboard_table.php deleted file mode 100644 index 4eaa7ffb..00000000 --- a/classes/output/dashboard_table.php +++ /dev/null @@ -1,201 +0,0 @@ -. -namespace local_assessfreq\output; - -/** - * Common code for outputting dashboard tables - * - * @package local_assessfreq - * @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net} - * @author Mark Johnson - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -trait dashboard_table { - /** - * Get content for title column. - * - * @param \stdClass $row - * @return string html used to display the video field. - * @throws \moodle_exception - */ - public function col_fullname($row): string { - global $OUTPUT; - - return $OUTPUT->user_picture($row, ['size' => 35, 'includefullname' => true]); - } - - /** - * Get content for time start column. - * Displays the user attempt start time. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timestart($row) { - if ($row->timestart == 0) { - $content = \html_writer::span(get_string('na', 'local_assessfreq')); - } else { - $datetime = userdate($row->timestart, get_string('trenddatetime', 'local_assessfreq')); - $content = \html_writer::span($datetime); - } - - return $content; - } - - /** - * Get content for time finish column. - * Displays the user attempt finish time. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timefinish($row) { - if ($row->timefinish == 0 && $row->timestart == 0) { - $content = \html_writer::span(get_string('na', 'local_assessfreq')); - } else if ($row->timefinish == 0 && $row->timestart > 0) { - $time = $row->timestart + $row->timelimit; - $datetime = userdate($time, get_string('trenddatetime', 'local_assessfreq')); - $content = \html_writer::span($datetime, 'local-assessfreq-disabled'); - } else { - $datetime = userdate($row->timefinish, get_string('trenddatetime', 'local_assessfreq')); - $content = \html_writer::span($datetime); - } - - return $content; - } - - /** - * Get content for state column. - * Displays the users state in the quiz. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_state($row) { - if ($row->state == 'notloggedin') { - $color = 'background: ' . get_config('local_assessfreq', 'notloggedincolor'); - } else if ($row->state == 'loggedin') { - $color = 'background: ' . get_config('local_assessfreq', 'loggedincolor'); - } else if ($row->state == 'inprogress') { - $color = 'background: ' . get_config('local_assessfreq', 'inprogresscolor'); - } else if ($row->state == 'uploadpending') { - $color = 'background: ' . get_config('local_assessfreq', 'inprogresscolor'); - } else if ($row->state == 'finished') { - $color = 'background: ' . get_config('local_assessfreq', 'finishedcolor'); - } else if ($row->state == 'abandoned') { - $color = 'background: ' . get_config('local_assessfreq', 'finishedcolor'); - } else if ($row->state == 'overdue') { - $color = 'background: ' . get_config('local_assessfreq', 'finishedcolor'); - } - - $content = \html_writer::span('', 'local-assessfreq-status-icon', ['style' => $color]); - $content .= get_string($row->state, 'local_assessfreq'); - - return $content; - } - - /** - * Return an array of headers common across dashboard tables. - * - * @return array - */ - protected function get_common_headers(): array { - return [ - get_string('quiztimeopen', 'local_assessfreq'), - get_string('quiztimeclose', 'local_assessfreq'), - get_string('quiztimelimit', 'local_assessfreq'), - get_string('quiztimestart', 'local_assessfreq'), - get_string('quiztimefinish', 'local_assessfreq'), - get_string('status', 'local_assessfreq'), - get_string('actions', 'local_assessfreq'), - ]; - } - - /** - * Return an array of columns common across dashboard tables. - * - * @return array - */ - protected function get_common_columns(): array { - return [ - 'timeopen', - 'timeclose', - 'timelimit', - 'timestart', - 'timefinish', - 'state', - 'actions', - ]; - } - - /** - * Return HTML for common column actions. - * - * @param \stdClass $row - * @return string - */ - protected function get_common_column_actions(\stdClass $row): string { - global $OUTPUT; - $actions = ''; - if ( - $row->state == 'finished' - || $row->state == 'inprogress' - || $row->state == 'uploadpending' - || $row->state == 'abandoned' - || $row->state == 'overdue' - ) { - $classes = 'action-icon'; - $attempturl = new \moodle_url('/mod/quiz/review.php', ['attempt' => $row->attemptid]); - $attributes = [ - 'class' => $classes, - 'id' => 'tool-assessfreq-attempt-' . $row->id, - 'data-toggle' => 'tooltip', - 'data-placement' => 'top', - 'title' => get_string('userattempt', 'local_assessfreq'), - ]; - } else { - $classes = 'action-icon disabled'; - $attempturl = '#'; - $attributes = [ - 'class' => $classes, - 'id' => 'tool-assessfreq-attempt-' . $row->id, - ]; - } - $icon = $OUTPUT->render(new \pix_icon('i/search', '')); - $actions .= \html_writer::link($attempturl, $icon, $attributes); - - $profileurl = new \moodle_url('/user/profile.php', ['id' => $row->id]); - $icon = $OUTPUT->render(new \pix_icon('i/completion_self', '')); - $actions .= \html_writer::link($profileurl, $icon, [ - 'class' => 'action-icon', - 'id' => 'tool-assessfreq-profile-' . $row->id, - 'data-toggle' => 'tooltip', - 'data-placement' => 'top', - 'title' => get_string('userprofile', 'local_assessfreq'), - ]); - - $logurl = new \moodle_url('/report/log/user.php', ['id' => $row->id, 'course' => 1, 'mode' => 'all']); - $icon = $OUTPUT->render(new \pix_icon('i/report', '')); - $actions .= \html_writer::link($logurl, $icon, [ - 'class' => 'action-icon', - 'id' => 'tool-assessfreq-log-' . $row->id, - 'data-toggle' => 'tooltip', - 'data-placement' => 'top', - 'title' => get_string('userlogs', 'local_assessfreq'), - ]); - return $actions; - } -} diff --git a/classes/output/inprogress_participant_summary.php b/classes/output/inprogress_participant_summary.php deleted file mode 100644 index c27ec8b1..00000000 --- a/classes/output/inprogress_participant_summary.php +++ /dev/null @@ -1,76 +0,0 @@ -. - -/** - * Renderable for participant summary card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\output; - -/** - * Renderable for participant summary card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class inprogress_participant_summary { - /** - * Generate the markup for the summary chart, - * used in the quiz dashboard. - * - * @param \stdClass $participants The particpiant data. - * @return \core\chart_pie $chart Generated chart object and chart data status. - */ - public function get_inprogress_participant_summary_chart(\stdClass $participants): \core\chart_pie { - - $seriesdata = [ - $participants->notloggedin, - $participants->loggedin, - $participants->inprogress, - $participants->finished, - ]; - - $labels = [ - get_string('notloggedin', 'local_assessfreq'), - get_string('loggedin', 'local_assessfreq'), - get_string('inprogress', 'local_assessfreq'), - get_string('finished', 'local_assessfreq'), - ]; - - $colors = [ - get_config('local_assessfreq', 'notloggedincolor'), - get_config('local_assessfreq', 'loggedincolor'), - get_config('local_assessfreq', 'inprogresscolor'), - get_config('local_assessfreq', 'finishedcolor'), - ]; - - // Create chart object. - $chart = new \core\chart_pie(); - $chart->set_doughnut(true); - $participants = new \core\chart_series(get_string('participants', 'local_assessfreq'), $seriesdata); - $participants->set_colors($colors); - $chart->add_series($participants); - $chart->set_labels($labels); - $chart->set_legend_options(['display' => false]); - - return $chart; - } -} diff --git a/classes/output/quiz_user_table.php b/classes/output/quiz_user_table.php deleted file mode 100644 index 9c8f44b5..00000000 --- a/classes/output/quiz_user_table.php +++ /dev/null @@ -1,329 +0,0 @@ -. - -/** - * Renderable table for quiz dashboard users. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\output; - -defined('MOODLE_INTERNAL') || die; - -require_once($CFG->libdir . '/tablelib.php'); - -use table_sql; -use renderable; - -/** - * Renderable table for quiz dashboard users. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class quiz_user_table extends table_sql implements renderable { - use dashboard_table; - - /** - * @var integer $quizid The ID of the braodcast to get the acknowledgements for. - */ - private $quizid; - - /** - * - * @var integer $contextid The context id. - */ - private $contextid; - - /** - * - * @var string $search The string to search for in the table data. - */ - private $search; - - /** - * @var string[] Extra fields to display. - */ - protected $extrafields; - - /** - * report_table constructor. - * - * @param string $baseurl Base URL of the page that contains the table. - * @param int $quizid The id from the quiz table to get data for. - * @param int $contextid The context id for the context the table is being displayed in. - * @param string $search The string to search for in the table. - * @param int $page the page number for pagination. - * - * @throws \coding_exception - */ - public function __construct(string $baseurl, int $quizid, int $contextid, string $search, int $page = 0) { - parent::__construct('local_assessfreq_student_table'); - global $DB; - - $this->quizid = $quizid; - $this->contextid = $contextid; - $this->search = $search; - $this->set_attribute('id', 'local_assessfreq_ackreport_table'); - $this->set_attribute('class', 'generaltable generalbox'); - $this->downloadable = false; - $this->define_baseurl($baseurl); - - $quizrecord = $DB->get_record('quiz', ['id' => $this->quizid], 'timeopen, timeclose, timelimit'); - $this->timeopen = $quizrecord->timeopen; - $this->timeclose = $quizrecord->timeclose; - $this->timelimit = $quizrecord->timelimit; - - $context = \context::instance_by_id($contextid); - - // Define the headers and columns. - $headers = []; - $columns = []; - - $headers[] = get_string('fullname'); - $columns[] = 'fullname'; - - $extrafields = \core_user\fields::get_identity_fields($context, false); - foreach ($extrafields as $field) { - $headers[] = \core_user\fields::get_display_name($field); - $columns[] = $field; - } - - $this->define_columns(array_merge($columns, $this->get_common_columns())); - $this->define_headers(array_merge($headers, $this->get_common_headers())); - $this->extrafields = $extrafields; - - // Setup pagination. - $this->currpage = $page; - $this->sortable(true); - $this->column_nosort = ['actions']; - } - - /** - * This function is used for the extra user fields. - * - * These are being dynamically added to the table so there are no functions 'col_' as - * the list has the potential to increase in the future and we don't want to have to remember to add - * a new method to this class. We also don't want to pollute this class with unnecessary methods. - * - * @param string $colname The column name - * @param \stdClass $data - * @return string - */ - public function other_cols($colname, $data) { - // Do not process if it is not a part of the extra fields. - if (!in_array($colname, $this->extrafields)) { - return ''; - } - - return s($data->{$colname}); - } - - /** - * Get content for time open column. - * Displays when the user attempt opens. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timeopen($row) { - $datetime = userdate($row->timeopen, get_string('trenddatetime', 'local_assessfreq')); - - if ($row->timeopen != $this->timeopen) { - $content = \html_writer::span($datetime, 'local-assessfreq-override-status'); - } else { - $content = \html_writer::span($datetime); - } - - return $content; - } - - /** - * Get content for time close column. - * Displays when the user attempt closes. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timeclose($row) { - $datetime = userdate($row->timeclose, get_string('trenddatetime', 'local_assessfreq')); - - if ($row->timeclose != $this->timeclose) { - $content = \html_writer::span($datetime, 'local-assessfreq-override-status'); - } else { - $content = \html_writer::span($datetime); - } - - return $content; - } - - /** - * Get content for time limit column. - * Displays the time the user has to finsih the quiz. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_timelimit($row) { - $timelimit = format_time($row->timelimit); - - if ($row->timelimit != $this->timelimit) { - $content = \html_writer::span($timelimit, 'local-assessfreq-override-status'); - } else { - $content = \html_writer::span($timelimit); - } - - return $content; - } - - /** - * Get content for actions column. - * Displays the actions for the user. - * - * @param \stdClass $row - * @return string html used to display the field. - */ - public function col_actions($row) { - global $OUTPUT; - - $manage = ''; - - $icon = $OUTPUT->render(new \pix_icon('i/duration', '')); - $manage .= \html_writer::link('#', $icon, [ - 'class' => 'action-icon override', - 'id' => 'tool-assessfreq-override-' . $row->id, - 'data-toggle' => 'tooltip', - 'data-placement' => 'top', - 'title' => get_string('useroverride', 'local_assessfreq'), - ]); - - $manage .= $this->get_common_column_actions($row); - - return $manage; - } - - - /** - * Query the database for results to display in the table. - * - * @param int $pagesize size of page for paginated displayed table. - * @param bool $useinitialsbar do you want to use the initials bar. - */ - public function query_db($pagesize, $useinitialsbar = false) { - global $CFG, $DB; - - $maxlifetime = $CFG->sessiontimeout; - $timedout = time() - $maxlifetime; - $sort = $this->get_sql_sort(); - - // We never want initial bars. We are using a custom search. - $this->initialbars(false); - - $frequency = new \local_assessfreq\frequency(); - $quiz = new \local_assessfreq\quiz(); - $capabilities = $frequency->get_module_capabilities('quiz'); - $context = $quiz->get_quiz_context($this->quizid); - - [$joins, $wheres, $params] = $frequency->generate_enrolled_wheres_joins_params($context, $capabilities); - $attemptsql = 'SELECT qa_a.userid, qa_a.state, qa_a.quiz, qa_a.id as attemptid, - qa_a.timestart as timestart, qa_a.timefinish as timefinish - FROM {quiz_attempts} qa_a - INNER JOIN (SELECT userid, MAX(timestart) as timestart - FROM {quiz_attempts} - GROUP BY userid) qa_b ON qa_a.userid = qa_b.userid - AND qa_a.timestart = qa_b.timestart - WHERE qa_a.quiz = :qaquiz'; - - $sessionsql = 'SELECT DISTINCT (userid) - FROM {sessions} - WHERE timemodified >= :stm'; - - $joins .= ' LEFT JOIN {quiz_overrides} qo ON u.id = qo.userid AND qo.quiz = :qoquiz'; - $joins .= " LEFT JOIN ($attemptsql) qa ON u.id = qa.userid"; - $joins .= " LEFT JOIN ($sessionsql) us ON u.id = us.userid"; - - $params['qaquiz'] = $this->quizid; - $params['qoquiz'] = $this->quizid; - $params['stm'] = $timedout; - - $finaljoin = new \core\dml\sql_join($joins, $wheres, $params); - $params = $finaljoin->params; - - $sql = "SELECT u.*, - COALESCE(qo.timeopen, $this->timeopen) AS timeopen, - COALESCE(qo.timeclose, $this->timeclose) AS timeclose, - COALESCE(qo.timelimit, $this->timelimit) AS timelimit, - COALESCE(qa.state, (CASE - WHEN us.userid > 0 THEN 'loggedin' - ELSE 'notloggedin' - END)) AS state, - qa.attemptid, - qa.timestart, - qa.timefinish - FROM {user} u - $finaljoin->joins - WHERE $finaljoin->wheres"; - - $pagesize = get_user_preferences('local_assessfreq_quiz_table_rows_preference', 20); - - if (!empty($sort)) { - $sql .= " ORDER BY $sort"; - } - - $records = $DB->get_recordset_sql($sql, $params); - $data = []; - $offset = $this->currpage * $pagesize; - $offsetcount = 0; - $recordcount = 0; - - foreach ($records as $record) { - $searchcount = 0; - if ($this->search != '') { - // Because we are using COALESE and CASE for state we can't use SQL WHERE so we need to filter in PHP land. - // Also because we need to do some filtering in PHP land, we'll do it all here. - $searchcount = -1; - $searchfields = array_merge($this->extrafields, ['firstname', 'lastname', 'state']); - - foreach ($searchfields as $searchfield) { - if (stripos($record->{$searchfield}, $this->search) !== false) { - $searchcount++; - } - } - } - - if ($searchcount > -1 && $offsetcount >= $offset && $recordcount < $pagesize) { - $data[$record->id] = $record; - } - - if ($searchcount > -1 && $offsetcount >= $offset) { - $recordcount++; - } - - if ($searchcount > -1) { - $offsetcount++; - } - } - - $records->close(); - - $this->pagesize($pagesize, $offsetcount); - $this->rawdata = $data; - } -} diff --git a/classes/output/renderer.php b/classes/output/renderer.php index a89d0365..270c04d6 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -15,463 +15,70 @@ // along with Moodle. If not, see . /** - * Assessment Frequency block rendrer. + * Renderer. * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + namespace local_assessfreq\output; -use local_assessfreq\quiz; +use local_assessfreq\form\course_search; +use local_assessfreq\report_base; use plugin_renderer_base; -use local_assessfreq\frequency; -/** - * Assessment Frequency block rendrer. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ class renderer extends plugin_renderer_base { /** - * Render the html for the report cards. - * Most content is loaded by ajax - * - * @return string html to display. - */ - public function render_report_cards(): string { - $currentyear = date('Y'); - $preferenceyear = get_user_preferences('local_assessfreq_overview_year_preference', $currentyear); - $frequency = new frequency(); - - // Get years that have events and load into context. - $years = $frequency->get_years_has_events(); - - if (empty($years)) { - $years = [$currentyear]; - } - - // Add current year to the selection of years if missing. - if (!in_array($currentyear, $years)) { - $years[] = $currentyear; - } - - $context = ['years' => [], 'currentyear' => $preferenceyear]; - - if (!empty($years)) { - foreach ($years as $year) { - if ($year == $preferenceyear) { - $context['years'][] = ['year' => ['val' => $year, 'active' => 'true']]; - } else { - $context['years'][] = ['year' => ['val' => $year]]; - } - } - } else { - $context['years'][] = ['year' => ['val' => $preferenceyear, 'active' => 'true']]; - } - - return $this->render_from_template('local_assessfreq/report-cards', $context); - } - - /** - * Render the HTML for the student quiz table. - * - * @param string $baseurl the base url to render the table on. - * @param int $quizid the id of the quiz in the quiz table. - * @param int $contextid the id of the context the table is being called in. - * @param string $search The string to search for. - * @param int $page the page number for pagination. - * @return string $output HTML for the table. - */ - public function render_student_table(string $baseurl, int $quizid, int $contextid, string $search = '', int $page = 0): string { - $renderable = new quiz_user_table($baseurl, $quizid, $contextid, $search, $page); - $perpage = 50; - ob_start(); - $renderable->out($perpage, true); - $output = ob_get_contents(); - ob_end_clean(); - - return $output; - } - - /** - * Render the HTML for the student search table. - * - * @param string $baseurl the base url to render the table on. - * @param int $contextid the id of the context the table is being called in. - * @param string $search The string to search for. - * @param int $hoursahead Ammount of time in hours to look ahead for quizzes starting. - * @param int $hoursbehind Ammount of time in hours to look behind for quizzes starting. - * @param int $now The timestamp to use for the current time. - * @param int $page the page number for pagination. - * @return string $output HTML for the table. - */ - public function render_student_search_table( - string $baseurl, - int $contextid, - string $search, - int $hoursahead, - int $hoursbehind, - int $now, - int $page = 0 - ): string { - - $renderable = new student_search_table($baseurl, $contextid, $search, $hoursahead, $hoursbehind, $now, $page); - $perpage = 50; - - ob_start(); - $renderable->out($perpage, true); - $output = ob_get_contents(); - ob_end_clean(); - - return $output; - } - - /** - * Renders the quizzes in progress "table" on the quiz dashboard screen. - * We update the table via ajax. - * The table isn't a real table it's a collection of divs. - * - * @param string $search The search string for the table. - * @param int $page The page number of results. - * @param string $sorton The value to sort the quizzes by. - * @param string $direction The direction to sort the quizzes. - * @param int $hoursahead Amount of time in hours to look ahead for quizzes starting. - * @param int $hoursbehind Amount of time in hours to look behind for quizzes starting. - * @return string $output HTML for the table. - */ - public function render_quizzes_inprogress_table( - string $search, - int $page, - string $sorton, - string $direction, - int $hoursahead = 0, - int $hoursbehind = 0 - ): string { - $context = \context_system::instance(); // TODO: pass the actual context in from the caller. - $now = time(); - $quiz = new quiz($hoursahead, $hoursbehind); - $quizzes = $quiz->get_quiz_summaries($now); - $pagesize = get_user_preferences('local_assessfreq_quiz_table_inprogress_preference', 5); - - $inprogressquizzes = $quizzes['inprogress']; - $upcommingquizzes = $quizzes['upcomming']; - $finishedquizzes = $quizzes['finished']; - - foreach ($upcommingquizzes as $key => $upcommingquiz) { - foreach ($upcommingquiz as $keyupcomming => $upcomming) { - $inprogressquizzes[$keyupcomming] = $upcomming; - } - } - - foreach ($finishedquizzes as $key => $finishedquiz) { - foreach ($finishedquiz as $keyfinished => $finished) { - $inprogressquizzes[$keyfinished] = $finished; - } - } - - [$filtered, $totalrows] = $quiz->filter_quizzes($inprogressquizzes, $search, $page, $pagesize); - $sortedquizzes = \local_assessfreq\utils::sort($filtered, $sorton, $direction); - - $pagingbar = new \paging_bar($totalrows, $page, $pagesize, '/'); - $pagingoutput = $this->render($pagingbar); - - $context = [ - 'quizzes' => array_values($sortedquizzes), - 'quizids' => json_encode(array_keys($sortedquizzes)), - 'context' => $context->id, - 'pagingbar' => $pagingoutput, - ]; - - $output = $this->render_from_template('local_assessfreq/quiz-inprogress-summary', $context); - - return $output; - } - - /** - * Return heatmap HTML. + * Render each of the assessfreqreport subplugins as tabs to display. * - * @return string The heatmap HTML. + * @return void */ - public function render_report_heatmap(): string { - $currentyear = date('Y'); - $preferenceyear = get_user_preferences('local_assessfreq_heatmap_year_preference', $currentyear); - $preferencemetric = get_user_preferences('local_assessfreq_heatmap_metric_preference', 'assess'); - $preferencemodules = json_decode(get_user_preferences('local_assessfreq_heatmap_modules_preference', '["all"]'), true); - - $frequency = new frequency(); - - // Initial context setup. - $context = [ - 'years' => [], - 'currentyear' => $preferenceyear, - 'modules' => [], - 'metrics' => [], - 'sesskey' => sesskey(), - 'downloadmetric' => $preferencemetric, - ]; - - // Get years that have events and load into context. - $years = $frequency->get_years_has_events(); - - if (empty($years)) { - $years = [$currentyear]; - } - - // Add current year to the selection of years if missing. - if (!in_array($currentyear, $years)) { - $years[] = $currentyear; + public function render_reports() : void { + global $PAGE; + $reports = get_reports(); + $reportoutputs = []; + foreach ($reports as $report) { + /* @var $report report_base */ + $reportoutputs[] = [ + 'tablink' => $report->get_tablink(), // Plugin name. + 'tabname' => $report->get_name(), // Display name. + 'report' => $report->get_contents(), + 'weight' => $report->get_tab_weight(), + ]; } - - if (!empty($years)) { - foreach ($years as $year) { - if ($year == $preferenceyear) { - $context['years'][] = ['year' => ['val' => $year, 'active' => 'true']]; - $context['downloadyear'] = $year; - } else { - $context['years'][] = ['year' => ['val' => $year]]; - } + usort($reportoutputs, function($a, $b) { + return $a['weight'] <=> $b['weight']; + }); + $courseselectoptions = []; + $courseselect = ''; + $categories = \core_course_category::make_categories_list(); + + foreach ($categories as $categoryid => $category) { + $courses = get_courses($categoryid); + foreach ($courses as $course) { + $courseselectoptions[$course->id] = $category . ' - ' . $course->fullname; } - } else { - $context['years'][] = ['year' => ['val' => $preferenceyear, 'active' => 'true']]; - $context['downloadyear'] = $preferenceyear; - } - - // Get modules for filters and load into context. - $modules = $frequency->get_process_modules(); - if (empty($preferencemodules) || $preferencemodules === ['all']) { - $context['modules'][] = ['module' => ['val' => 'all', 'name' => get_string('all'), 'active' => 'true']]; - } else { - $context['modules'][] = ['module' => ['val' => 'all', 'name' => get_string('all')]]; } - - if (!empty($modules[0])) { - foreach ($modules as $module) { - $modulename = get_string('modulename', $module); - if (in_array($module, $preferencemodules)) { - $context['modules'][] = ['module' => ['val' => $module, 'name' => $modulename, 'active' => 'true']]; - } else { - $context['modules'][] = ['module' => ['val' => $module, 'name' => $modulename]]; - } - } + if (!empty($courseselectoptions)) { + $courseselect = $this->single_select( + '#', + 'courseid', + $courseselectoptions, + $PAGE->course->id != SITEID ? $PAGE->course->id : '', + ['' => get_string('courseselect', 'local_assessfreq')], + ); } - - // Get metric details and load into context. - $context['metrics'] = [$preferencemetric => 'true']; - - return $this->render_from_template('local_assessfreq/report-heatmap', $context); - } - - /** - * Get the html to render the assessment dashboard. - * - * @param string $baseurl the base url to render this report on. - * @return string $html the html to display. - */ - public function render_dashboard_assessment(string $baseurl): string { - $html = ''; - $html .= $this->header(); - $html .= $this->render_report_cards(); - $html .= $this->render_report_heatmap(); - $html .= $this->footer(); - - return $html; - } - - /** - * Add HTML for quiz selection and quiz refresh buttons. - * - * @return string html for the button. - */ - private function render_quiz_select_refresh_button(): string { - $preferencerefresh = get_user_preferences('local_assessfreq_quiz_refresh_preference', 60); - $refreshminutes = [ - 60 => 'minuteone', - 120 => 'minutetwo', - 300 => 'minutefive', - 600 => 'minuteten', - ]; - - $context = [ - 'refreshinitial' => get_string($refreshminutes[$preferencerefresh], 'local_assessfreq'), - 'refresh' => [$refreshminutes[$preferencerefresh] => 'true'], - 'hide' => true, - ]; - - return $this->render_from_template('local_assessfreq/quiz-dashboard-controls', $context); - } - - /** - * Add HTML for quiz refresh button. - * - * @return string html for the button. - */ - private function render_quiz_refresh_button(): string { - $preferencerefresh = get_user_preferences('local_assessfreq_quiz_refresh_preference', 60); - $preferencehoursahead = get_user_preferences('local_assessfreq_quizzes_inprogress_table_hoursahead_preference', 0); - $preferencehoursbehind = get_user_preferences('local_assessfreq_quizzes_inprogress_table_hoursbehind_preference', 0); - - $refreshminutes = [ - 60 => 'minuteone', - 120 => 'minutetwo', - 300 => 'minutefive', - 600 => 'minuteten', - ]; - - $hours = [ - 0 => 'hours0', - 1 => 'hours1', - 4 => 'hours4', - 8 => 'hours8', - ]; - - $context = [ - 'refreshinitial' => get_string($refreshminutes[$preferencerefresh], 'local_assessfreq'), - 'refresh' => [$refreshminutes[$preferencerefresh] => 'true'], - 'hoursahead' => [$hours[$preferencehoursahead] => 'true'], - 'hoursbehind' => [$hours[$preferencehoursbehind] => 'true'], - ]; - - return $this->render_from_template('local_assessfreq/quiz-dashboard-inprogress-controls', $context); - } - - /** - * Render the cards on the quiz dashboard. - * - * @return string - */ - private function render_quiz_dashboard_cards(): string { - $preferencerows = get_user_preferences('local_assessfreq_quiz_table_rows_preference', 20); - $rows = [ - 20 => 'rows20', - 50 => 'rows50', - 100 => 'rows100', - ]; - - $context = [ - 'rows' => [$rows[$preferencerows] => 'true'], - ]; - - return $this->render_from_template('local_assessfreq/quiz-dashboard-cards', $context); - } - - /** - * Render the cards on the quiz dashboard. - * - * @return string - */ - private function render_quiz_dashboard_inprogress_cards(): string { - $preferencerows = get_user_preferences('local_assessfreq_quiz_table_inprogress_preference', 10); - $preferencesort = get_user_preferences('local_assessfreq_quiz_table_inprogress_sort_preference', 'name_asc'); - $rows = [ - 5 => 'rows5', - 10 => 'rows10', - 20 => 'rows20', - ]; - - $context = [ - 'rows' => [$rows[$preferencerows] => 'true'], - 'sort' => [$preferencesort => 'true'], - ]; - - return $this->render_from_template('local_assessfreq/quiz-dashboard-inprogress-cards', $context); - } - - /** - * Render the cards on the quiz dashboard. - * - * @return string - */ - private function render_student_table_cards(): string { - $preferencerows = get_user_preferences('local_assessfreq_student_search_table_rows_preference', 20); - $preferencehoursahead = get_user_preferences('local_assessfreq_student_search_table_hoursahead_preference', 4); - $preferencehoursbehind = get_user_preferences('local_assessfreq_student_search_table_hoursbehind_preference', 1); - $preferencerefresh = get_user_preferences('local_assessfreq_quiz_refresh_preference', 60); - - $refreshminutes = [ - 60 => 'minuteone', - 120 => 'minutetwo', - 300 => 'minutefive', - 600 => 'minuteten', - ]; - - $rows = [ - 20 => 'rows20', - 50 => 'rows50', - 100 => 'rows100', - ]; - - $hours = [ - 0 => 'hours0', - 1 => 'hours1', - 4 => 'hours4', - 8 => 'hours8', - ]; - - $preferencerefresh = get_user_preferences('local_assessfreq_quiz_refresh_preference', 60); - $refreshminutes = [ - 60 => 'minuteone', - 120 => 'minutetwo', - 300 => 'minutefive', - 600 => 'minuteten', - ]; - - $context = [ - 'rows' => [$rows[$preferencerows] => 'true'], - 'hoursahead' => [$hours[$preferencehoursahead] => 'true'], - 'hoursbehind' => [$hours[$preferencehoursbehind] => 'true'], - 'refreshinitial' => get_string($refreshminutes[$preferencerefresh], 'local_assessfreq'), - 'refresh' => [$refreshminutes[$preferencerefresh] => 'true'], - ]; - - return $this->render_from_template('local_assessfreq/student-search', $context); - } - - /** - * Get the html to render the quiz dashboard. - * - * @param string $baseurl the base url to render this report on. - * @return string $html the html to display. - */ - public function render_dashboard_quiz(string $baseurl): string { - $html = ''; - $html .= $this->header(); - $html .= $this->render_quiz_select_refresh_button(); - $html .= $this->render_quiz_dashboard_cards(); - $html .= $this->footer(); - - return $html; - } - - /** - * Get the html to render the quizzes in porgress dashboard. - * - * @param string $baseurl the base url to render this report on. - * @return string $html the html to display. - */ - public function render_dashboard_quiz_inprogress(string $baseurl): string { - $html = ''; - $html .= $this->header(); - $html .= $this->render_quiz_refresh_button(); - $html .= $this->render_quiz_dashboard_inprogress_cards(); - $html .= $this->footer(); - - return $html; - } - - /** - * Get the html to render the student search. - * - * @return string $html the html to display. - */ - public function render_student_search(): string { - $html = ''; - $html .= $this->header(); - $html .= $this->render_student_table_cards(); - $html .= $this->footer(); - - return $html; + $output = $this->output->header(); + $output .= $this->render_from_template( + 'local_assessfreq/index', + [ + 'reports' => $reportoutputs, + 'courseselect' => $courseselect, + ] + ); + $output .= $this->output->footer(); + echo $output; } } diff --git a/classes/output/upcomming_quizzes.php b/classes/output/upcomming_quizzes.php deleted file mode 100644 index d8925917..00000000 --- a/classes/output/upcomming_quizzes.php +++ /dev/null @@ -1,93 +0,0 @@ -. - -/** - * Renderable for upcomming quizzes card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq\output; - -use local_assessfreq\quiz; - -/** - * Renderable for upcomming quizzes card. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class upcomming_quizzes { - /** - * Generate the markup for the upcomming quizzes chart, - * used in the in progress quizzes dashboard. - * - * @param int $now Timestamp to get chart data for. - * @return array With Generated chart object and chart data status. - */ - public function get_upcomming_quizzes_chart(int $now): array { - - // Get quizzes for the supplied timestamp. - $quiz = new quiz(); - $quizzes = $quiz->get_quiz_summaries($now); - - $labels = []; - $quizseriestitle = get_string('quizzes', 'local_assessfreq'); - $participantseries = get_string('students', 'local_assessfreq'); - $result = []; - $result['hasdata'] = true; - - $quizseriesdata = []; - $participantseriesdata = []; - - foreach ($quizzes['upcomming'] as $timestamp => $upcomming) { - $quizcount = 0; - $participantcount = 0; - - foreach ($upcomming as $quiz) { - $quizcount++; - $participantcount += $quiz->participants; - } - - // Check if inprogress quizzes are upcomming quizzes with overrides. - foreach ($quizzes['inprogress'] as $inprogress) { - if ($inprogress->timestampopen >= $timestamp && $inprogress->timestampopen < $timestamp + HOURSECS) { - $quizcount++; - $participantcount += $inprogress->participants; - } - } - - $quizseriesdata[] = $quizcount; - $participantseriesdata[] = $participantcount; - $labels[] = userdate($timestamp + HOURSECS, get_string('inprogressdatetime', 'local_assessfreq')); - } - - // Create chart object. - $quizseries = new \core\chart_series($quizseriestitle, $quizseriesdata); - $participantseries = new \core\chart_series($participantseries, $participantseriesdata); - - $chart = new \core\chart_bar(); - $chart->add_series($quizseries); - $chart->add_series($participantseries); - $chart->set_labels($labels); - $result['chart'] = $chart; - - return $result; - } -} diff --git a/classes/plugininfo/assessfreqreport.php b/classes/plugininfo/assessfreqreport.php new file mode 100644 index 00000000..f61aaf31 --- /dev/null +++ b/classes/plugininfo/assessfreqreport.php @@ -0,0 +1,100 @@ +. + +/** + * Report plugininfo. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_assessfreq\plugininfo; + +use admin_settingpage; +use core\plugininfo\base; +use moodle_url; +use part_of_admin_tree; + +class assessfreqreport extends base { + + /** + * Finds all enabled plugin names, the result may include missing plugins. + * @return array of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() : array { + $pluginmanager = \core_plugin_manager::instance(); + $plugins = $pluginmanager->get_plugins_of_type('assessfreqreport'); + + if (empty($plugins)) { + return array(); + } + + $enabled = []; + foreach ($plugins as $name => $plugin) { + if ($plugin->is_enabled()) { + $enabled[$name] = $name; + } + } + return $enabled; + } + + /** + * Whether the subplugin is enabled. + * + * @return bool Whether enabled. + */ + public function is_enabled() : bool { + return get_config('assessfreqreport_' . $this->name, 'enabled'); + } + + /** + * Returns the node name used in admin settings menu for this plugin settings (if applicable) + * + * @return string node name or null if plugin does not create settings node (default) + */ + public function get_settings_section_name(): string { + return 'assessfreqreport_' . $this->name; + } + + /** + * Include the settings.php file from sub plugins if they provide it. + * This is a copy of very similar implementations from various other subplugin areas. + */ + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig || !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } +} + diff --git a/classes/plugininfo/assessfreqsource.php b/classes/plugininfo/assessfreqsource.php new file mode 100644 index 00000000..f1c4e448 --- /dev/null +++ b/classes/plugininfo/assessfreqsource.php @@ -0,0 +1,99 @@ +. + +/** + * Source plugininfo. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_assessfreq\plugininfo; + +use admin_settingpage; +use core\plugininfo\base; +use part_of_admin_tree; + +class assessfreqsource extends base { + + /** + * Finds all enabled plugin names, the result may include missing plugins. + * @return array of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() : array { + $pluginmanager = \core_plugin_manager::instance(); + $plugins = $pluginmanager->get_plugins_of_type('assessfreqsource'); + + if (empty($plugins)) { + return array(); + } + + $enabled = []; + foreach ($plugins as $name => $plugin) { + if ($plugin->is_enabled()) { + $enabled[$name] = $name; + } + } + return $enabled; + } + + /** + * Whether the subplugin is enabled. + * + * @return bool Whether enabled. + */ + public function is_enabled() : bool { + return get_config('assessfreqsource_' . $this->name, 'enabled'); + } + + /** + * Returns the node name used in admin settings menu for this plugin settings (if applicable) + * + * @return string node name or null if plugin does not create settings node (default) + */ + public function get_settings_section_name() : string { + return 'assessfreqsource_' . $this->name; + } + + /** + * Include the settings.php file from sub plugins if they provide it. + * This is a copy of very similar implementations from various other subplugin areas. + */ + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig || !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } +} + diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 3d44b2cd..917da0ec 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -24,10 +24,12 @@ namespace local_assessfreq\privacy; +use context; use core_privacy\local\metadata\collection; use core_privacy\local\request\contextlist; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; +use core_privacy\local\request\data_provider; use core_privacy\local\request\writer; use core_privacy\local\request\userlist; @@ -38,7 +40,7 @@ * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider implements \core_privacy\local\request\data_provider, \core_privacy\local\metadata\provider { +class provider implements data_provider, \core_privacy\local\metadata\provider { /** * Returns metadata about this plugin's privacy policy. * @@ -75,7 +77,7 @@ public static function get_metadata(collection $collection): collection { * @return contextlist the contexts in which data is contained. */ public static function get_contexts_for_userid(int $userid): contextlist { - $contextlist = new \core_privacy\local\request\contextlist(); + $contextlist = new contextlist(); $contextlist->add_user_context($userid); $contextlist->add_system_context(); return $contextlist; @@ -118,8 +120,8 @@ public static function export_user_data(approved_contextlist $contextlist) { // Get records for user ID. $rows = $DB->get_records('local_assessfreq_user', ['userid' => $userid]); + $i = 0; if (count($rows) > 0) { - $i = 0; foreach ($rows as $row) { $parentclass[$i]['userid'] = $row->userid; $parentclass[$i]['eventid'] = $row->eventid; @@ -150,7 +152,7 @@ public static function export_user_data(approved_contextlist $contextlist) { * * @param context $context The context to delete for. */ - public static function delete_data_for_all_users_in_context(\context $context) { + public static function delete_data_for_all_users_in_context(context $context) { global $DB; // All data contained in system context. if ($context->contextlevel == CONTEXT_SYSTEM) { diff --git a/classes/quiz.php b/classes/quiz.php deleted file mode 100644 index b85488a8..00000000 --- a/classes/quiz.php +++ /dev/null @@ -1,773 +0,0 @@ -. - -/** - * Quiz data class. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace local_assessfreq; - -use mod_quiz\question\bank\qbank_helper; - -/** - * Quiz data class. - * - * This class handles data processing to get quiz data. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class quiz { - /** - * Ammount of time in hours for lookahead values. - * Defaults to 12. - * - * @var int $hoursahead. - */ - private $hoursahead = 12; - - /** - * Ammount of time in hours for lookbehind values. - * Defaults to 1. - * - * @var int $hoursahead. - */ - private $hoursbehind = 1; - - /** - * The direction used in sorting. - * - * @var string $sortdirection - */ - private $sortdirection; - - /** - * The quiz details to sort by. - * - * @var string $sorton - */ - private $sorton; - - /** - * Class constructor. - * - * @param int $hoursahead - * @param int $hoursbehind - */ - public function __construct(int $hoursahead = 12, int $hoursbehind = 1) { - $this->hoursahead = $hoursahead; - $this->hoursbehind = $hoursbehind; - } - - /** - * Given a quiz id get the module context. - * - * @param int $quizid The quiz ID of the context to get. - * @return \context_module $context The quiz module context. - */ - public function get_quiz_context(int $quizid): \context_module { - global $DB; - - $params = ['module' => 'quiz', 'quiz' => $quizid]; - $sql = 'SELECT cm.id - FROM {course_modules} cm - INNER JOIN {modules} m ON cm.module = m.id - INNER JOIN {quiz} q ON cm.instance = q.id AND cm.course = q.course - WHERE m.name = :module - AND q.id = :quiz'; - $cmid = $DB->get_field_sql($sql, $params); - $context = \context_module::instance($cmid); - - return $context; - } - - /** - * Get override info for a paricular quiz. - * Data returned is: - * Number of users with overrides in Quiz, - * Ealiest override start, - * Latest override end. - * - * @param int $quizid The ID of the quiz to get override data for. - * @param \context_module $context The context object of the quiz. - * @return \stdClass $overrideinfo Information about quiz overrides. - */ - private function get_quiz_override_info(int $quizid, \context_module $context): \stdClass { - global $DB; - - $capabilities = ['mod/quiz:attempt', 'mod/quiz:view']; - $overrideinfo = new \stdClass(); - $users = []; - $start = 0; - $end = 0; - - $sql = 'SELECT id, userid, COALESCE(timeopen, 0) AS timeopen, COALESCE(timeclose, 0) AS timeclose - FROM {quiz_overrides} - WHERE quiz = ?'; - $params = [$quizid]; - $overrides = $DB->get_records_sql($sql, $params); - - foreach ($overrides as $override) { - if (!has_all_capabilities($capabilities, $context, $override->userid)) { - continue; // Don't count users who can't access the quiz. - } - - $users[] = $override->userid; - - if ($override->timeclose > $end) { - $end = $override->timeclose; - } - - if ($start == 0) { - $start = $override->timeopen; - } else if ($override->timeopen < $start) { - $start = $override->timeopen; - } - } - - $users = count(array_unique($users)); - - $overrideinfo->start = $start; - $overrideinfo->end = $end; - $overrideinfo->users = $users; - - return $overrideinfo; - } - - /** - * Get quiz question infromation. - * Data returned is: - * List of individual question types, - * Count of questions in quiz, - * Count of question types. - * - * @param int $quizid The ID of the quiz to get override data for. - * @return \stdClass $questions The question data for the quiz. - */ - private function get_quiz_questions(int $quizid): \stdClass { - global $DB; - $questions = new \stdClass(); - $types = []; - $questioncount = 0; - $context = $this->get_quiz_context($quizid); - - $questionsrecords = qbank_helper::get_question_structure($quizid, $context); - - foreach ($questionsrecords as $questionrecord) { - $types[] = get_string('pluginname', 'qtype_' . $questionrecord->qtype); - $questioncount++; - } - - $typeswithcounts = []; - foreach (array_count_values($types) as $type => $count) { - $typeswithcounts[] = ['type' => $type, 'count' => $count]; - } - - $questions->types = $typeswithcounts; - $questions->typecount = count($typeswithcounts); - $questions->questioncount = $questioncount; - - return $questions; - } - - /** - * Method returns data about a quiz. - * Data returned is: - * Quiz name, - * Quiz start time, - * Quiz end time, - * Earliest participant start time (override), - * Latest participant end time (override), - * Total participants taking the quiz, - * Number participants with overrides in quiz, - * Quiz link, - * Number of questions, - * Number of question types, - * List of question types. - * - * @param int $quizid ID of the quiz to get data for. - * @return \stdClass $quizdata The retrieved quiz data. - */ - public function get_quiz_data(int $quizid): \stdClass { - global $DB; - $quizdata = new \stdClass(); - $context = $this->get_quiz_context($quizid); - - $quizrecord = $DB->get_record('quiz', ['id' => $quizid], 'name, timeopen, timeclose, timelimit, course'); - $course = get_course($quizrecord->course); - $courseurl = new \moodle_url('/course/view.php', ['id' => $quizrecord->course]); - - $overrideinfo = $this->get_quiz_override_info($quizid, $context); - $questions = $this->get_quiz_questions($quizid); - $frequency = new frequency(); - if (!empty($quizrecord->timeopen)) { - $timesopen = userdate($quizrecord->timeopen, get_string('strftimedatetime', 'langconfig')); - } else { - $timesopen = get_string('na', 'local_assessfreq'); - } - if (!empty($quizrecord->timeclose)) { - $timeclose = userdate($quizrecord->timeclose, get_string('strftimedatetime', 'langconfig')); - } else { - $timeclose = get_string('na', 'local_assessfreq'); - } - if (!empty($overrideinfo->start)) { - $overrideinfostart = userdate($overrideinfo->start, get_string('strftimedatetime', 'langconfig')); - } else { - $overrideinfostart = get_string('na', 'local_assessfreq'); - } - if (!empty($overrideinfo->end)) { - $overrideinfoend = userdate($overrideinfo->end, get_string('strftimedatetime', 'langconfig')); - } else { - $overrideinfoend = get_string('na', 'local_assessfreq'); - } - - // Handle override start. - if ($overrideinfo->start != 0 && $overrideinfo->start < $quizrecord->timeopen) { - $earlyopen = $overrideinfostart; - $earlyopenstamp = $overrideinfo->start; - } else { - $earlyopen = $timesopen; - $earlyopenstamp = $quizrecord->timeopen; - } - - // Handle override end. - if ($overrideinfo->end != 0 && $overrideinfo->end > $quizrecord->timeclose) { - $lateclose = $overrideinfoend; - $lateclosestamp = $overrideinfo->end; - } else { - $lateclose = $timeclose; - $lateclosestamp = $quizrecord->timeclose; - } - - // Quiz result link. - $resultlink = new \moodle_url('/mod/quiz/report.php', ['id' => $context->instanceid, 'mode' => 'overview']); - // Override link. - $overrridelink = new \moodle_url('/mod/quiz/overrides.php', ['cmid' => $context->instanceid, 'mode' => 'user']); - // Participant link. - $participantlink = new \moodle_url('/user/index.php', ['id' => $quizrecord->course]); - // Dashboard link. - $dashboardlink = new \moodle_url('/local/assessfreq/dashboard_quiz.php', ['id' => $quizid]); - - $quizdata->name = format_string($quizrecord->name, true, ["context" => $context, "escape" => true]); - $quizdata->timeopen = $timesopen; - $quizdata->timeclose = $timeclose; - $quizdata->timelimit = format_time($quizrecord->timelimit); - $quizdata->earlyopen = $earlyopen; - $quizdata->earlyopenstamp = $earlyopenstamp; - $quizdata->lateclose = $lateclose; - $quizdata->lateclosestamp = $lateclosestamp; - $quizdata->participants = count($frequency->get_event_users_raw($context->id, 'quiz')); - $quizdata->overrideparticipants = $overrideinfo->users; - $quizdata->url = $context->get_url()->out(false); - $quizdata->types = $questions->types; - $quizdata->typecount = $questions->typecount; - $quizdata->questioncount = $questions->questioncount; - $quizdata->resultlink = $resultlink->out(false); - $quizdata->overridelink = $overrridelink->out(false); - $quizdata->coursefullname = format_string($course->fullname, true, ["context" => $context, "escape" => true]); - $quizdata->courseshortname = $course->shortname; - $quizdata->courselink = $courseurl->out(false); - $quizdata->participantlink = $participantlink->out(false); - $quizdata->dashboardlink = $dashboardlink->out(false); - $quizdata->assessid = $quizid; - - return $quizdata; - } - - /** - * Get a list of all quiz overrides that have a start date less than now + 1 hour - * AND end date is in the future OR end date is less then 1 hour in the past. - * And startdate != 0. - * - * @param int $now Timestamp to use for reference for time. - * @param int $lookahead The number of seconds from the provided now value to look ahead when getting overrides. - * @param int $lookbehind The number of seconds from the provided now value to look behind when getting overrides. - * @return array $quizzes The quizzes with applicable overrides. - */ - private function get_tracked_overrides(int $now, int $lookahead, int $lookbehind): array { - global $DB; - - $starttime = $now + $lookahead; - $endtime = $now - $lookbehind; - - $sql = 'SELECT id, quiz, userid, timeopen, timeclose - FROM {quiz_overrides} - WHERE (timeopen > 0 AND timeopen < :starttime) - AND (timeclose > :endtime OR timeclose > :now)'; - $params = [ - 'starttime' => $starttime, - 'endtime' => $endtime, - 'now' => $now, - ]; - - $quizzes = $DB->get_records_sql($sql, $params); - - return $quizzes; - } - - /** - * Get a list of all quizzes that have a start date less than now + 1 hour - * AND end date is in the future OR end date is less then 1 hour in the past. - * And startdate != 0. - * - * @param int $now Timestamp to use for reference for time. - * @param int $lookahead The number of seconds from the provided now value to look ahead when getting quizzes. - * @param int $lookbehind The number of seconds from the provided now value to look behind when getting quizzes. - * @return array $quizzes The quizzes. - */ - private function get_tracked_quizzes(int $now, int $lookahead, int $lookbehind): array { - global $DB; - - $starttime = $now + $lookahead; - $endtime = $now - $lookbehind; - - $sql = 'SELECT id, timeopen, timeclose, timelimit, 0 AS isoverride - FROM {quiz} - WHERE (timeopen > 0 AND timeopen < :starttime) - AND (timeclose > :endtime OR timeclose > :now)'; - $params = [ - 'starttime' => $starttime, - 'endtime' => $endtime, - 'now' => $now, - ]; - - $quizzes = $DB->get_records_sql($sql, $params); - - return $quizzes; - } - - /** - * Get a list of all quizzes that have a start date less than now + 1 hour - * AND end date is in the future OR end date is less then 1 hour in the past. - * And startdate != 0. With quiz start and end times adjusted to take into account users with overrides. - * - * @param int $now Timestamp to use for reference for time. - * @param int $lookahead The number of seconds from the provided now value to look ahead when getting quizzes. - * @param int $lookbehind The number of seconds from the provided now value to look behind when getting quizzes. - * @return array $quizzes The quizzes. - */ - private function get_tracked_quizzes_with_overrides(int $now, int $lookahead = HOURSECS, int $lookbehind = HOURSECS): array { - global $DB; - - $quizzes = $this->get_tracked_quizzes($now, $lookahead, $lookbehind); - $overrides = $this->get_tracked_overrides($now, $lookahead, $lookbehind); - - // Add override data to each quiz in the array. - foreach ($overrides as $override) { - $sql = 'SELECT id, timeopen, timeclose, timelimit - FROM {quiz} - WHERE id = :id'; - $params = [ - 'id' => $override->quiz, - ]; - - $quizzesoverride = $DB->get_record_sql($sql, $params); - - if ($quizzesoverride) { - if (array_key_exists($quizzesoverride->id, $quizzes)) { - $quizzesoverride->isoverride = $quizzes[$quizzesoverride->id]->isoverride; - if (isset($quizzes[$quizzesoverride->id]->overrides)) { - $quizzesoverride->overrides = $quizzes[$quizzesoverride->id]->overrides; - } - $quizzesoverride->overrides[] = $override; - $quizzes[$quizzesoverride->id] = $quizzesoverride; - } else { - $quizzesoverride->isoverride = 1; - $quizzesoverride->overrides[] = $override; - $quizzes[$quizzesoverride->id] = $quizzesoverride; - } - } - } - - return $quizzes; - } - - /** - * Get counts for inprogress assessments, both total in progress quiz activities - * and total participants in progress. - * - * @param int $now Timestamp to use for reference for time. - * @return array $quizzes Array of counts of inprogress assessments and participants. - */ - public function get_inprogress_counts(int $now): array { - // Get tracked quizzes. - $trackedquizzes = $this->get_tracked_quizzes_with_overrides($now, 0, 0); - - $counts = [ - 'assessments' => 0, - 'participants' => 0, - ]; - - foreach ($trackedquizzes as $quiz) { - $counts['assessments']++; - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - if (!empty($trackedrecords)) { - $tracking = array_pop($trackedrecords); - $counts['participants'] += $tracking->inprogress; - } - } - - return $counts; - } - - /** - * Get finished, in progress and upcomming quizzes and their associated data. - * - * @param int $now Timestamp to use for reference for time. - * @return array $quizzes Array of finished, inprogress and upcomming quizzes with associated data. - */ - public function get_quiz_summaries(int $now): array { - // Get tracked quizzes. - $lookahead = HOURSECS * $this->hoursahead; - $lookbehind = HOURSECS * $this->hoursbehind; - $trackedquizzes = $this->get_tracked_quizzes_with_overrides($now, $lookahead, $lookbehind); - - // Set up array to hold quizzes and data. - $quizzes = [ - 'finished' => [], - 'inprogress' => [], - 'upcomming' => [], - ]; - - // Itterate through the hours, processing in progress and upcomming quizzes. - for ($hour = 0; $hour <= $this->hoursahead; $hour++) { - $time = $now + (HOURSECS * $hour); - - if ($hour == 0) { - $quizzes['inprogress'] = []; - } - - $quizzes['upcomming'][$time] = []; - - // Seperate out inprogress and upcomming quizzes, then get data for each quiz. - foreach ($trackedquizzes as $quiz) { - if ($quiz->timeopen < $time && $quiz->timeclose > $time && $hour === 0) { // Get inprogress quizzes. - $quizdata = $this->get_quiz_data($quiz->id); - $quizdata->timestampopen = $quiz->timeopen; - $quizdata->timestampclose = $quiz->timeclose; - $quizdata->timestamplimit = $quiz->timelimit; - $quizdata->isoverride = $quiz->isoverride; - - if (isset($quiz->overrides)) { - $quizdata->overrides = $quiz->overrides; - } - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - $quizdata->tracking = array_pop($trackedrecords); - - $quizzes['inprogress'][$quiz->id] = $quizdata; - unset($trackedquizzes[$quiz->id]); // Remove quiz from array to help with performance. - } else if (($quiz->timeopen >= $time) && ($quiz->timeopen < ($time + HOURSECS))) { // Get upcomming quizzes. - $quizdata = $this->get_quiz_data($quiz->id); - $quizdata->timestampopen = $quiz->timeopen; - $quizdata->timestampclose = $quiz->timeclose; - $quizdata->timestamplimit = $quiz->timelimit; - $quizdata->isoverride = $quiz->isoverride; - - if (isset($quiz->overrides)) { - $quizdata->overrides = $quiz->overrides; - } - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - $quizdata->tracking = array_pop($trackedrecords); - - $quizzes['upcomming'][$time][$quiz->id] = $quizdata; - unset($trackedquizzes[$quiz->id]); - } else { - if (isset($quiz->overrides)) { - $quizdata = $this->get_quiz_data($quiz->id); - $quizdata->timestampopen = $quiz->timeopen; - $quizdata->timestampclose = $quiz->timeclose; - $quizdata->timestamplimit = $quiz->timelimit; - $quizdata->isoverride = $quiz->isoverride; - - if (isset($quiz->overrides)) { - $quizdata->overrides = $quiz->overrides; - } - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - $quizdata->tracking = array_pop($trackedrecords); - - $quizzes['inprogress'][$quiz->id] = $quizdata; - unset($trackedquizzes[$quiz->id]); - } - } - } - } - - // Iterate through the hours, processing finished quizzes. - for ($hour = 1; $hour <= $this->hoursbehind; $hour++) { - $time = $now - (HOURSECS * $hour); - - $quizzes['finished'][$time] = []; - - // Get data for each finished quiz. - foreach ($trackedquizzes as $quiz) { - if (($quiz->timeclose >= $time) && ($quiz->timeclose < ($time + HOURSECS))) { // Get finished quizzes. - $quizdata = $this->get_quiz_data($quiz->id); - $quizdata->timestampopen = $quiz->timeopen; - $quizdata->timestampclose = $quiz->timeclose; - $quizdata->timestamplimit = $quiz->timelimit; - $quizdata->isoverride = $quiz->isoverride; - - if (isset($quiz->overrides)) { - $quizdata->overrides = $quiz->overrides; - } - - // Get tracked users for quiz. - $trackedrecords = $this->get_quiz_tracking($quiz->id); - $quizdata->tracking = array_pop($trackedrecords); - - $quizzes['finished'][$time][$quiz->id] = $quizdata; - unset($trackedquizzes[$quiz->id]); - } - } - } - - return $quizzes; - } - - /** - * Given a list of user ids, check if the user is logged in our not - * and return summary counts of logged in and not logged in users. - * - * @param array $userids User ids to get logged in status. - * @return \stdClass $usercounts Object with coutns of users logged in and not logged in. - */ - private function get_loggedin_users(array $userids): \stdClass { - global $CFG, $DB; - - $maxlifetime = $CFG->sessiontimeout; - $timedout = time() - $maxlifetime; - $userchunks = array_chunk($userids, 250); // Break list of users into chunks so we don't exceed DB IN limits. - - $loggedin = 0; // Count of logged in users. - $loggedout = 0; // Count of not loggedin users. - $loggedinusers = []; - $loggedoutusers = []; - - foreach ($userchunks as $userchunk) { - [$insql, $inparams] = $DB->get_in_or_equal($userchunk); - $inparams[] = $timedout; - - $sql = "SELECT DISTINCT(userid) - FROM {sessions} - WHERE userid $insql - AND timemodified >= ?"; - $users = $DB->get_fieldset_sql($sql, $inparams); - $loggedinusers = array_merge($loggedinusers, $users); - } - - $loggedoutusers = array_diff($userids, $loggedinusers); - - $loggedin = count($loggedinusers); - $loggedout = count($loggedoutusers); - - $usercounts = new \stdClass(); - $usercounts->loggedin = $loggedin; - $usercounts->loggedout = $loggedout; - $usercounts->loggedinusers = $loggedinusers; - $usercounts->loggedoutusers = $loggedoutusers; - - return $usercounts; - } - - /** - * Get count of in porgress and finished attempts for a quiz. - * - * @param int $quizid The id of the quiz to get the counts for. - * @return \stdClass $attemptcounts The found counts. - */ - private function get_quiz_attempts(int $quizid): \stdClass { - global $DB; - - $inprogress = 0; - $finished = 0; - $inprogressusers = []; - $finishedusers = []; - - $sql = 'SELECT userid, state - FROM {quiz_attempts} qa - JOIN ( - SELECT MAX(id) id - FROM {quiz_attempts} - WHERE quiz = ? - GROUP BY userid) - AS qb - ON qa.id = qb.id'; - - $params = [$quizid]; - - $usersattempts = $DB->get_records_sql($sql, $params); - - foreach ($usersattempts as $usersattempt) { - if ($usersattempt->state == 'inprogress' || $usersattempt->state == 'overdue') { - $inprogress++; - $inprogressusers[] = $usersattempt->userid; - } else if ($usersattempt->state == 'finished' || $usersattempt->state == 'abandoned') { - $finished++; - $finishedusers[] = $usersattempt->userid; - } - } - - $attemptcounts = new \stdClass(); - $attemptcounts->inprogress = $inprogress; - $attemptcounts->finished = $finished; - $attemptcounts->inprogressusers = $inprogressusers; - $attemptcounts->finishedusers = $finishedusers; - - return $attemptcounts; - } - - /** - * Process and store user tracking information for a quiz. - * - * @param int $now Timestamp to use for reference for time. - * @return int $count Count of processed quizzes - */ - public function process_quiz_tracking(int $now): int { - global $DB; - - $frequency = new frequency(); - $quizzes = $this->get_tracked_quizzes_with_overrides($now); - $quizusersbyquizid = []; - $contextsbyquizid = []; - $count = 0; - - foreach ($quizzes as $quiz) { - $contextid = $this->get_quiz_context($quiz->id)->id; - $quizusersbyquizid[$quiz->id] = array_column($frequency->get_event_users_raw( - $contextid, - 'quiz' - ), 'id'); - - $contextsbyquizid[$quiz->id] = $contextid; - } - - $loggedinusers = $this->get_loggedin_users( - array_unique(array_reduce($quizusersbyquizid, 'array_merge', [])) - ); - - // For each quiz get the list of users who are elligble to do the quiz. - foreach ($quizzes as $quiz) { - $context = $contextsbyquizid[$quiz->id]; - $quizusers = $quizusersbyquizid[$quiz->id]; - $attemptusers = $this->get_quiz_attempts($quiz->id); - $loggedout = 0; - $loggedin = 0; - $inprogress = 0; - $finished = 0; - - foreach ($quizusers as $user) { - if (in_array($user, $attemptusers->finishedusers)) { - $finished++; - continue; - } else if (in_array($user, $attemptusers->inprogressusers)) { - $inprogress++; - continue; - } else if (in_array($user, $loggedinusers->loggedinusers)) { - $loggedin++; - continue; - } else if (in_array($user, $loggedinusers->loggedoutusers)) { - $loggedout++; - continue; - } - } - - $record = new \stdClass(); - $record->assessid = $quiz->id; - $record->notloggedin = $loggedout; - $record->loggedin = $loggedin; - $record->inprogress = $inprogress; - $record->finished = $finished; - $record->timecreated = time(); - - $DB->insert_record('local_assessfreq_trend', $record); - $count++; - } - - return $count; - } - - /** - * Given a quiz ID get its tracking information. - * - * @param int $quizid The ID of the quiz. - * @return array $tracking Tracking reocrds for the quiz. - */ - public function get_quiz_tracking(int $quizid): array { - global $DB; - - $tracking = $DB->get_records('local_assessfreq_trend', ['assessid' => $quizid], 'timecreated ASC'); - - return $tracking; - } - - /** - * Given an array of quizzes, filter based on a provided search string and apply pagination. - * - * @param array $quizzes Array of quizzes to search. - * @param string $search The string to search by. - * @param int $page The page number of results. - * @param int $pagesize The page size for results. - * @return array $result Array containing list of filtered quizzes and total of how many quizzes matched the filter. - */ - public function filter_quizzes(array $quizzes, string $search, int $page, int $pagesize): array { - $filtered = []; - $searchfields = ['name', 'coursefullname']; - $offset = $page * $pagesize; - $offsetcount = 0; - $recordcount = 0; - - foreach ($quizzes as $id => $quiz) { - $searchcount = 0; - if ($search != '') { - $searchcount = -1; - foreach ($searchfields as $searchfield) { - if (stripos($quiz->{$searchfield}, $search) !== false) { - $searchcount++; - } - } - } - - if ($searchcount > -1 && $offsetcount >= $offset && $recordcount < $pagesize) { - $filtered[$id] = $quiz; - } - - if ($searchcount > -1 && $offsetcount >= $offset) { - $recordcount++; - } - - if ($searchcount > -1) { - $offsetcount++; - } - } - - $result = [$filtered, $offsetcount]; - - return $result; - } -} diff --git a/classes/report_base.php b/classes/report_base.php new file mode 100644 index 00000000..c7327818 --- /dev/null +++ b/classes/report_base.php @@ -0,0 +1,103 @@ +. + +/** + * Base report class. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_assessfreq; + +/** + * Abstract class that each report subplugin primary class will extend from to determine consistent factors. + */ +abstract class report_base { + + private static array $instances = []; + + public function __construct() { + $this->get_required_js(); + $this->get_required_css(); + } + + /** + * Get the instance of the report class. + * + * @return report_base + */ + public static function get_instance() : report_base { + $class = static::class; + if (!isset(self::$instances[$class])) { + self::$instances[$class] = new static(); + } + + return self::$instances[$class]; + } + + /** + * Return the name of the tab being rendered. + * @return string + */ + abstract public function get_name() : string; + + /** + * Return the weight of the tab which is used to determine the loading order with the highest first. + * @return int + */ + abstract public function get_tab_weight() : int; + + /** + * Get the contents of the page as a string of HTML (template). + * + * @return object + */ + + abstract public function get_contents() : string; + + /** + * Get the anchor link to use for the tabs. + * + * @return string + */ + abstract public function get_tablink() : string; + + /** + * Check if the report is visible to the user. + * + * @return bool + */ + public function has_access() : bool { + return false; + } + + /** + * Set up the required JS in the global $PAGE object. + * @return void + */ + protected function get_required_js() { + } + + /** + * Set up the required CSS in the global $PAGE object. + * @return void + */ + protected function get_required_css() { + } +} diff --git a/classes/source_base.php b/classes/source_base.php new file mode 100644 index 00000000..fc2f0ed0 --- /dev/null +++ b/classes/source_base.php @@ -0,0 +1,176 @@ +. + +/** + * Base source class. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_assessfreq; + +/** + * Abstract class that each source subplugin primary class will extend from to determine consistent factors. + */ +abstract class source_base { + + private static array $instances = []; + + public function __construct() { + $this->get_required_js(); + $this->get_required_css(); + } + + /** + * Get the instance of the source class. + * + * @return source_base + */ + public static function get_instance() : source_base { + $class = static::class; + if (!isset(self::$instances[$class])) { + self::$instances[$class] = new static(); + } + + return self::$instances[$class]; + } + + /** + * Return the name of the module the source refers to. + * @return string + */ + abstract public function get_module() : string; + + /** + * Return the module table. By default, this is the module name, however some mods use a different table. + * @return string + */ + public function get_module_table() : string { + return $this->get_module(); + } + + /** + * Return the timelimit field used in the module table. + * @return string + */ + public function get_timelimit_field() : string { + return ''; + } + + /** + * Return the available/timeopen field used in the module table. + * @return string + */ + public function get_open_field() : string { + return ''; + } + + /** + * Return the duedate/timeclose field used in the module table. + * @return string + */ + public function get_close_field() : string { + return ''; + } + + /** + * Return the capability map for the module that users must have before the activity applies to them. + * @return array + */ + public function get_user_capabilities() : array { + return []; + } + + /** + * Return the name of the source being rendered. + * @return string + */ + abstract public function get_name() : string; + + /** + * Set up the required JS in the global $PAGE object. + * @return void + */ + protected function get_required_js() { + } + + /** + * Set up the required CSS in the global $PAGE object. + * @return void + */ + protected function get_required_css() { + } + + /** + * Given an assess ID and module get its tracking information. + * + * @param int $assessid The ID of the assessment. + * @param bool $limited If limited, only return a subset of data. Otherwise reports can try and render thousands of data points. + * @return array $tracking Tracking reocrds for the quiz. + */ + protected function get_tracking(int $assessid, bool $limited = false) : array { + global $DB; + + $trendlimit = get_config('assessfreqreport_activity_dashboard', 'trendcount'); + $return = []; + + $trends = $DB->get_records( + 'local_assessfreq_trend', + ['assessid' => $assessid, 'module' => $this->get_module()], + 'timecreated ASC' + ); + if (!$limited) { + return $trends; + } + $modulus = round(count($trends) / $trendlimit); + $i = 0; + if (count($trends) < $trendlimit) { + return $trends; + } + foreach ($trends as $trend) { + if ($i % $modulus == 0) { + $return[] = $trend; + } + $i++; + } + + return $return; + } + + /** + * Given an assess ID and module get its most recent tracking information. + * + * @param int $assessid The ID of the assessment. + * @return mixed $tracking Tracking record. + */ + protected function get_recent_tracking(int $assessid) { + global $DB; + + return $DB->get_record_sql(" + SELECT * + FROM {local_assessfreq_trend} + WHERE assessid = ? + AND module = ? + ORDER BY id DESC + LIMIT 1 + ", + [$assessid, $this->get_module()] + ); + } +} diff --git a/classes/task/data_process.php b/classes/task/data_process.php index 73eb05ab..0ac5fc71 100644 --- a/classes/task/data_process.php +++ b/classes/task/data_process.php @@ -21,9 +21,14 @@ * @copyright 2020 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + namespace local_assessfreq\task; +use context_system; +use core\task\manager; use core\task\scheduled_task; +use local_assessfreq\event\event_processed; +use local_assessfreq\frequency; /** * A scheduled task to generate data used in plugin reports. @@ -38,7 +43,7 @@ class data_process extends scheduled_task { * * @return string */ - public function get_name() { + public function get_name() : string { return get_string('task:dataprocess', 'local_assessfreq'); } @@ -49,11 +54,11 @@ public function get_name() { public function execute() { mtrace('local_assessfreq: Processing event data'); $now = time(); - $frequency = new \local_assessfreq\frequency(); - $context = \context_system::instance(); + $frequency = new frequency(); + $context = context_system::instance(); // Only run scheduled task if there is not an ad-hoc task pending or processing historic data. - $adhoctask = \core\task\manager::get_adhoc_tasks(\local_assessfreq\task\history_process::class); + $adhoctask = manager::get_adhoc_tasks(history_process::class); if (!empty($adhoctask)) { mtrace('local_assessfreq: Stopping early historic processing task pending'); return; @@ -62,9 +67,9 @@ public function execute() { // Due dates may have changed since we last ran report. So delete all events in DB later than now and replace them. mtrace('local_assessfreq: Deleting old event data'); $actionstart = time(); - $frequency->delete_events($now); // Delete event records greaer than now. + $frequency->delete_events($now); // Delete event records greater than now. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'delete', 'duration' => $actionduration], ]); @@ -75,7 +80,7 @@ public function execute() { $actionstart = time(); $frequency->process_site_events($now); // Process records in the future. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'site', 'duration' => $actionduration], ]); @@ -86,11 +91,21 @@ public function execute() { $actionstart = time(); $frequency->process_user_events($now); // Process user events. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'user', 'duration' => $actionduration], ]); $event->trigger(); mtrace('local_assessfreq: Processing user events finished in: ' . $actionduration . ' seconds'); + + //mtrace('local_assessfreq: Clearing legacy tracking data'); + //$actionstart = time(); + //$actionduration = time() - $actionstart; + //$event = event_processed::create([ + // 'context' => $context, + // 'other' => ['action' => 'user', 'duration' => $actionduration], + //]); + //$event->trigger(); + //mtrace('local_assessfreq: Processing user events finished in: ' . $actionduration . ' seconds'); } } diff --git a/classes/task/history_process.php b/classes/task/history_process.php index 5cbcb513..7e2b797f 100644 --- a/classes/task/history_process.php +++ b/classes/task/history_process.php @@ -23,7 +23,12 @@ */ namespace local_assessfreq\task; +use context_system; use core\task\adhoc_task; +use core\task\manager; +use local_assessfreq\event\event_processed; +use local_assessfreq\frequency; +use moodle_exception; /** * Adhoc task to process historical data used in plugin. @@ -43,18 +48,18 @@ public function execute() { // Only run if scheduled task is not running. // Throw an error if it is and this task will be retried after a delay. // The scheduled task won't start while this job is pending. - $schedtask = \core\task\manager::get_scheduled_task(\local_assessfreq\task\data_process::class); + $schedtask = manager::get_scheduled_task(data_process::class); if ($schedtask->get_lock()) { - throw new \moodle_exception('local_assessfreq_scheduled_task_running'); + throw new moodle_exception('local_assessfreq_scheduled_task_running'); } - $frequency = new \local_assessfreq\frequency(); - $context = \context_system::instance(); + $frequency = new frequency(); + $context = context_system::instance(); $actionstart = time(); $frequency->delete_events(0); // Delete ALL event records. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'delete', 'duration' => $actionduration], ]); @@ -65,7 +70,7 @@ public function execute() { $actionstart = time(); $frequency->process_site_events(1); // Process ALL records. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'site', 'duration' => $actionduration], ]); @@ -76,7 +81,7 @@ public function execute() { $actionstart = time(); $frequency->process_user_events(1); // Process ALL user events. $actionduration = time() - $actionstart; - $event = \local_assessfreq\event\event_processed::create([ + $event = event_processed::create([ 'context' => $context, 'other' => ['action' => 'user', 'duration' => $actionduration], ]); diff --git a/classes/task/quiz_tracking.php b/classes/task/quiz_tracking.php deleted file mode 100644 index 0c7a21f3..00000000 --- a/classes/task/quiz_tracking.php +++ /dev/null @@ -1,59 +0,0 @@ -. - -/** - * A scheduled task to track the process of quizzes in the system. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -namespace local_assessfreq\task; - -use core\task\scheduled_task; - -/** - * A scheduled task to track the process of quizzes in the system. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class quiz_tracking extends scheduled_task { - /** - * Get a descriptive name for this task (shown to admins). - * - * @return string - */ - public function get_name() { - return get_string('task:quiztracking', 'local_assessfreq'); - } - - /** - * Do the job. - * Throw exceptions on errors (the job will be retried). - */ - public function execute() { - mtrace('local_assessfreq: Processing quiz trcking'); - $quiz = new \local_assessfreq\quiz(); - - $actionstart = time(); - $quiz->process_quiz_tracking($actionstart); // Process user events. - $actionduration = time() - $actionstart; - - mtrace('local_assessfreq: Processing quiz tracking finished in: ' . $actionduration . ' seconds'); - } -} diff --git a/classes/utils.php b/classes/utils.php index 11d096ae..461f072a 100644 --- a/classes/utils.php +++ b/classes/utils.php @@ -70,7 +70,7 @@ public static function sort(array $inputarray, string $sorton, string $direction /** * Sort an array of arrays/objects by multiple values. * - * @param array $inputarray Array of quizzes to sort. + * @param array $inputarray Array of activities to sort. * @param array $sorton Associative array to sort by in the format field => direction. * @return array $inputarray the sorted array. */ diff --git a/dashboard_assessment.php b/dashboard_assessment.php deleted file mode 100644 index 7c211437..00000000 --- a/dashboard_assessment.php +++ /dev/null @@ -1,50 +0,0 @@ -. - -/** - * Assessment dashboard. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); - -$baseurl = $CFG->wwwroot . "/local/assessfreq/dashboard_assessment.php"; - -// Calls require_login and performs permissions checks for admin pages. -admin_externalpage_setup( - 'local_assessfreq_assessment', - '', - null, - '', - ['pagelayout' => 'admin'] -); - -$title = get_string('dashboard:assessment', 'local_assessfreq'); -$url = new moodle_url($baseurl); -$context = context_system::instance(); - -$PAGE->set_url($url); -$PAGE->set_context($context); -$PAGE->set_title($title); -$PAGE->set_heading($title); -$PAGE->requires->js_call_amd('local_assessfreq/dashboard_assessment', 'init', [$context->id]); - -$output = $PAGE->get_renderer('local_assessfreq'); - -echo $output->render_dashboard_assessment($baseurl); diff --git a/dashboard_quiz.php b/dashboard_quiz.php deleted file mode 100644 index 66a62d2a..00000000 --- a/dashboard_quiz.php +++ /dev/null @@ -1,52 +0,0 @@ -. - -/** - * Quiz dashboard. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); - -$quizid = optional_param('id', 0, PARAM_INT); - -$baseurl = $CFG->wwwroot . "/local/assessfreq/dashboard_quiz.php"; - -// Calls require_login and performs permissions checks for admin pages. -admin_externalpage_setup( - 'local_assessfreq_quiz', - '', - null, - '', - ['pagelayout' => 'admin'] -); - -$title = get_string('dashboard:quiz', 'local_assessfreq'); -$url = new moodle_url($baseurl); -$context = context_system::instance(); - -$PAGE->set_url($url); -$PAGE->set_context($context); -$PAGE->set_title($title); -$PAGE->set_heading($title); -$PAGE->requires->js_call_amd('local_assessfreq/dashboard_quiz', 'init', [$context->id, $quizid]); - -$output = $PAGE->get_renderer('local_assessfreq'); - -echo $output->render_dashboard_quiz($baseurl); diff --git a/dashboard_quiz_inprogress.php b/dashboard_quiz_inprogress.php deleted file mode 100644 index 18cdacd4..00000000 --- a/dashboard_quiz_inprogress.php +++ /dev/null @@ -1,50 +0,0 @@ -. - -/** - * Quiz dashboard. - * - * @package local_assessfreq - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -require_once('../../config.php'); -require_once($CFG->libdir . '/adminlib.php'); - -$baseurl = $CFG->wwwroot . "/local/assessfreq/dashboard_quiz_inprogress.php"; - -// Calls require_login and performs permissions checks for admin pages. -admin_externalpage_setup( - 'local_assessfreq_quiz', - '', - null, - '', - ['pagelayout' => 'admin'] -); - -$title = get_string('dashboard:quiz_inprogress', 'local_assessfreq'); -$url = new moodle_url($baseurl); -$context = context_system::instance(); - -$PAGE->set_url($url); -$PAGE->set_context($context); -$PAGE->set_title($title); -$PAGE->set_heading($title); -$PAGE->requires->js_call_amd('local_assessfreq/dashboard_quiz_inprogress', 'init', [$context->id]); - -$output = $PAGE->get_renderer('local_assessfreq'); - -echo $output->render_dashboard_quiz_inprogress($baseurl); diff --git a/db/access.php b/db/access.php new file mode 100644 index 00000000..2359261f --- /dev/null +++ b/db/access.php @@ -0,0 +1,34 @@ +. + +/** + * Access file. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = [ + 'local/assessfreq:view' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [], + ], +]; diff --git a/db/install.php b/db/install.php index a81c96ab..2ccdcc66 100644 --- a/db/install.php +++ b/db/install.php @@ -22,13 +22,16 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core\task\manager; +use local_assessfreq\task\history_process; + /** * Generate ad-hoc task on install. */ function xmldb_local_assessfreq_install() { if (!PHPUNIT_TEST) { // I hate this anti-pattern. // Create an adhoc task that will process all historical event data. - $task = new \local_assessfreq\task\history_process(); - \core\task\manager::queue_adhoc_task($task, true); + $task = new history_process(); + manager::queue_adhoc_task($task, true); } } diff --git a/db/install.xml b/db/install.xml index ca69cca6..a660c3ae 100644 --- a/db/install.xml +++ b/db/install.xml @@ -69,6 +69,7 @@ + @@ -81,6 +82,7 @@ +
diff --git a/db/services.php b/db/services.php index afac4f83..ce50c597 100644 --- a/db/services.php +++ b/db/services.php @@ -26,40 +26,6 @@ // Define the web service functions to install. $functions = [ - 'local_assessfreq_get_frequency' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_frequency', - 'classpath' => '', - 'description' => 'Returns event frequency map.', - 'type' => 'read', - 'ajax' => true, - ], - 'local_assessfreq_get_heat_colors' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_heat_colors', - 'classpath' => '', - 'description' => 'Returns event heat map colors.', - 'type' => 'read', - 'loginrequired' => false, - 'ajax' => true, - ], - 'local_assessfreq_get_process_modules' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_process_modules', - 'classpath' => '', - 'description' => 'Returns modules we are processing .', - 'type' => 'read', - 'loginrequired' => false, - 'ajax' => true, - ], - 'local_assessfreq_get_day_events' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_day_events', - 'classpath' => '', - 'description' => 'Gets day event info for use in heatmap.', - 'type' => 'read', - 'ajax' => true, - ], 'local_assessfreq_get_courses' => [ 'classname' => 'local_assessfreq_external', 'methodname' => 'get_courses', @@ -68,19 +34,11 @@ 'type' => 'read', 'ajax' => true, ], - 'local_assessfreq_get_quizzes' => [ + 'local_assessfreq_get_activities' => [ 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_quizzes', + 'methodname' => 'get_activities', 'classpath' => '', - 'description' => 'Gets quizzes.', - 'type' => 'read', - 'ajax' => true, - ], - 'local_assessfreq_get_quiz_data' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_quiz_data', - 'classpath' => '', - 'description' => 'Gets quiz data.', + 'description' => 'Gets activities.', 'type' => 'read', 'ajax' => true, ], @@ -100,21 +58,4 @@ 'type' => 'write', 'ajax' => true, ], - 'local_assessfreq_get_system_timezone' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_system_timezone', - 'classpath' => '', - 'description' => 'Returns system (not user) timezone.', - 'type' => 'read', - 'loginrequired' => false, - 'ajax' => true, - ], - 'local_assessfreq_get_inprogress_counts' => [ - 'classname' => 'local_assessfreq_external', - 'methodname' => 'get_inprogress_counts', - 'classpath' => '', - 'description' => 'Get counts for inprogress assessments.', - 'type' => 'read', - 'ajax' => true, - ], ]; diff --git a/db/subplugins.json b/db/subplugins.json new file mode 100644 index 00000000..e37298eb --- /dev/null +++ b/db/subplugins.json @@ -0,0 +1,6 @@ +{ + "plugintypes": { + "assessfreqreport": "local/assessfreq/report", + "assessfreqsource": "local/assessfreq/source" + } +} \ No newline at end of file diff --git a/db/tasks.php b/db/tasks.php index 6e9893c3..3648ed85 100644 --- a/db/tasks.php +++ b/db/tasks.php @@ -36,14 +36,5 @@ 'day' => '*', 'dayofweek' => '*', 'month' => '*', - ], - [ - 'classname' => 'local_assessfreq\task\quiz_tracking', - 'blocking' => 0, - 'minute' => '*', - 'hour' => '*', - 'day' => '*', - 'dayofweek' => '*', - 'month' => '*', - ], + ] ]; diff --git a/db/upgrade.php b/db/upgrade.php new file mode 100644 index 00000000..daa35493 --- /dev/null +++ b/db/upgrade.php @@ -0,0 +1,57 @@ +. + +/** + * Upgrade file. + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Function to upgrade local_assessfreq. + * @param int $oldversion the version we are upgrading from + * @return bool result + */ +function xmldb_local_assessfreq_upgrade($oldversion) { + global $DB; + + $dbman = $DB->get_manager(); + + if ($oldversion < 2024040302) { + + $table = new xmldb_table('local_assessfreq_trend'); + /* + * Previously we only used this table for quiz, so all existing modules will be quiz modules, hence the default. + */ + $field = new xmldb_field('module', XMLDB_TYPE_CHAR, '20', true, true, null, 'quiz'); + $index = new xmldb_index('module', XMLDB_INDEX_NOTUNIQUE, ['assessid', 'module']); + + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + if (!$dbman->index_exists($table, $index)) { + $dbman->add_index($table, $index); + } + + upgrade_plugin_savepoint(true, 2024040302, 'local', 'assessfreq'); + } + + return true; +} diff --git a/history.php b/history.php index ce873ffa..cb3cd9fb 100644 --- a/history.php +++ b/history.php @@ -34,21 +34,21 @@ // Build the page output. echo $OUTPUT->header(); -echo $OUTPUT->heading(get_string('clearhistory', 'local_assessfreq')); +echo $OUTPUT->heading(get_string('settings:clearhistory', 'local_assessfreq')); // Page content. (This feels like the lazy way to do things). $url = new \moodle_url('/local/assessfreq/history.php', ['action' => 'deleteall']); if ($action === null) { echo $OUTPUT->box_start(); - echo $OUTPUT->container(get_string('reprocessall_desc', 'local_assessfreq')); - echo $OUTPUT->single_button($url, get_string('reprocessall', 'local_assessfreq'), 'get'); + echo $OUTPUT->container(get_string('history:reprocessall_desc', 'local_assessfreq')); + echo $OUTPUT->single_button($url, get_string('history:reprocessall', 'local_assessfreq'), 'get'); echo $OUTPUT->box_end(); } else if ($action == 'deleteall') { $actionurl = new moodle_url('/local/assessfreq/history.php', ['action' => 'confirmed']); $cancelurl = new moodle_url('/local/assessfreq/history.php'); echo $OUTPUT->confirm( - get_string('confirmreprocess', 'local_assessfreq'), + get_string('history:confirmreprocess', 'local_assessfreq'), new single_button($actionurl, get_string('continue'), 'post', true), new single_button($cancelurl, get_string('cancel'), 'get') ); @@ -57,8 +57,8 @@ $task = new \local_assessfreq\task\history_process(); \core\task\manager::queue_adhoc_task($task, true); echo $OUTPUT->box_start(); - echo $OUTPUT->container(get_string('reprocessall_desc', 'local_assessfreq')); - echo $OUTPUT->single_button($url, get_string('reprocessall', 'local_assessfreq'), 'get'); + echo $OUTPUT->container(get_string('history:reprocessall_desc', 'local_assessfreq')); + echo $OUTPUT->single_button($url, get_string('history:reprocessall', 'local_assessfreq'), 'get'); echo $OUTPUT->box_end(); } diff --git a/index.php b/index.php new file mode 100644 index 00000000..1cc018e3 --- /dev/null +++ b/index.php @@ -0,0 +1,64 @@ +. + +/** + * Main landing page for the reports + * + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__, 3) . '/config.php'); + +require_login(); + +require_once('lib.php'); + +// Capability requirements. +$context = context_system::instance(); +$course = get_course(SITEID); + +// If we have a course selected, update the PAGE object accordinging. +if ($courseid = optional_param('courseid', 0, PARAM_INT)) { + // If we've been given the side id redirect without the param. + if ($courseid == SITEID) { + redirect('/local/assessfreq/'); + } + $context = context_course::instance($courseid); + $PAGE->set_pagelayout('incourse'); + $course = get_course($courseid); +} + +// Capability check. +require_capability('local/assessfreq:view', $context); + +$PAGE->set_url('/local/assessfreq'); +$PAGE->set_context($context); +// Set the course to use in subsequent checks. +$PAGE->set_course($course); + +if ($course->id != SITEID) { + $PAGE->set_heading($course->fullname); +} +$PAGE->set_title(get_string('pluginname', 'local_assessfreq')); + +$output = $PAGE->get_renderer('local_assessfreq'); +$PAGE->requires->js_call_amd('local_assessfreq/dashboard', 'init'); + +/* @var $output local_assessfreq\output\renderer */ +$output->render_reports(); diff --git a/lang/en/local_assessfreq.php b/lang/en/local_assessfreq.php index 4ada491d..36ab3616 100644 --- a/lang/en/local_assessfreq.php +++ b/lang/en/local_assessfreq.php @@ -15,207 +15,49 @@ // along with Moodle. If not, see . /** - * Plugin strings are defined here. + * Lang file. * - * @package local_assessfreq - * @category string - * @copyright 2020 Matt Porritt - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package local_assessfreq + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -defined('MOODLE_INTERNAL') || die(); +$string['pluginname'] = 'Assessment Frequency Report'; +$string['subplugintype_assessfreqreport_plural'] = 'Assessment Frequency Reports'; +$string['subplugintype_assessfreqsource_plural'] = 'Assessment Frequency Sources'; -$string['pluginname'] = 'Assessment Frequency'; -$string['title'] = 'Assessment Frequency'; +$string['privacy:metadata'] = 'The assessment frequency reports only display data'; + +$string['assessfreq:view'] = 'Ability to load the inital view. Report subplugins will also need to be allowed.'; -$string['abandoned'] = 'Abandoned'; -$string['activity'] = 'Activity'; -$string['actions'] = 'Actions'; -$string['assessbyactivity'] = 'Assessments by activity'; -$string['assessbymonth'] = 'Assessments due by month'; -$string['assessbymonthstudents'] = 'Students with assessments due by month'; -$string['assessheatmap'] = 'Assessment heatmap for year:'; -$string['assessoverview'] = 'Assessment overviews for year:'; -$string['cachedef_eventsdueactivity'] = 'Events due by activity cache'; -$string['cachedef_eventsduemonth'] = 'Events due by month cache'; -$string['cachedef_eventusers'] = 'Users for month cache'; -$string['cachedef_monthlyuser'] = 'User events due by month cache'; -$string['cachedef_courseevents'] = 'Assessment frequency course event cache'; -$string['cachedef_siteevents'] = 'Assessment frequency site event cache'; -$string['cachedef_userevents'] = 'Assessment frequency user event cache'; -$string['cachedef_usereventsallfrequencyarray'] = 'Assessment frequency all user event cache'; -$string['cachedef_yearevents'] = 'Years that have events'; -$string['clearhistory'] = 'Clear history'; -$string['close'] = 'Close'; -$string['closeapply'] = 'Close and apply'; -$string['confirmreprocess'] = 'Delete ALL history and reprocess?'; -$string['course'] = 'Course'; -$string['courseasc'] = 'Course Asc'; -$string['coursedesc'] = 'Course Desc'; -$string['dashboard'] = 'View activity dashboard'; -$string['dashboard:assessment'] = 'Assessment dashboard'; -$string['dashboard:quiz'] = 'Quiz dashboard'; -$string['dashboard:quiz_inprogress'] = 'Quizzes in progress dashboard'; -$string['dashboard:quiztitle'] = '{$a->quiz} - {$a->course} - Dashboard'; -$string['duedate'] = 'Due date'; -$string['eventeventprocessed'] = 'event_processed'; -$string['eventeven_processed_desc'] = 'local assessfreq task event processing'; -$string['entercourse'] = 'Enter course name'; -$string['entersearch'] = 'Enter search text'; -$string['entersearchquiz'] = 'Search by quiz or course name'; -$string['findcourse'] = 'Find course'; -$string['finished'] = 'Finished'; -$string['hours0'] = 'Now'; -$string['hours1'] = '1 Hour'; -$string['hours4'] = '4 Hours'; -$string['hours8'] = '8 Hours'; -$string['hoursahead'] = 'Hours ahead'; -$string['hoursbehind'] = 'Hours behind'; -$string['inprogress'] = 'In progress'; -$string['inprogressdatetime'] = '%H:00'; -$string['inprogressparticpants'] = 'Participants in progress: {$a}'; -$string['inprogressquiz'] = 'Quizzes in progress: {$a}'; -$string['loading'] = 'Loading...'; -$string['loadingquiz'] = 'Loading quizzes'; -$string['loadingquiztitle'] = 'Loading quiz'; -$string['loggedin'] = 'Logged in'; -$string['na'] = 'N/A'; -$string['minuteone'] = '1 Minute'; -$string['minutetwo'] = '2 Minutes'; -$string['minutefive'] = '5 Minutes'; -$string['minuteten'] = '10 Minutes'; -$string['nocourse'] = 'No course selected'; -$string['nodata'] = 'No data found'; -$string['noquiz'] = 'No quiz selected...'; -$string['noquizselected'] = 'No quiz selected. Select quiz or cancel'; -$string['notloggedin'] = 'Not logged in'; -$string['numberassessments'] = 'By number of assessments'; -$string['numberevents'] = 'Event Count'; -$string['numberstudents'] = 'By number of students with assessments'; -$string['open'] = 'Open'; -$string['overdue'] = 'Overdue'; -$string['overrides'] = 'Overrides'; -$string['participantsummary'] = 'Participant summary'; -$string['participanttrend'] = 'Participant trend'; -$string['participants'] = 'Participants'; -$string['period'] = 'Period'; -$string['privacy:metadata:local_assessfreq'] = 'Data relating users for the local assessfreq plugin'; -$string['privacy:metadata:local_assessfreq_user'] = 'Data relating users with assessment events'; -$string['privacy:metadata:local_assessfreq_user:id'] = 'Record ID'; -$string['privacy:metadata:local_assessfreq_user:userid'] = 'The ID of the user that is effected by the assessment event'; -$string['privacy:metadata:local_assessfreq_user:eventid'] = 'The ID that relates to the assessment event'; -$string['privacy:metadata:local_assessfreq_conf_user'] = 'Data relating users with assessment conflicts'; -$string['privacy:metadata:local_assessfreq_conf_user:id'] = 'Record ID'; -$string['privacy:metadata:local_assessfreq_conf_user:userid'] = 'The ID of the user that is effected by the assessment conflict'; -$string['privacy:metadata:local_assessfreq_conf_user:conflictid'] = 'The ID that relates to the assessment conflict'; -$string['pluginsettings'] = 'Plugin settings'; -$string['quiz'] = 'Quiz'; -$string['quizasc'] = 'Quiz Asc'; -$string['quizdesc'] = 'Quiz Desc'; -$string['quizdetails'] = 'Quiz details'; -$string['quiztparticipantsoverride'] = 'Participants with an override:'; -$string['quiztquestionnumber'] = 'Questions in quiz:'; -$string['quizquestiontypes'] = 'Question types in quiz:'; -$string['quiztimeclose'] = 'Close time'; -$string['quiztimeearlyopen'] = 'First participant starts:'; -$string['quiztimefinish'] = 'Finish'; -$string['quiztimelateclose'] = 'Last participant finishes:'; -$string['quiztimelimit'] = 'Time limit'; -$string['quiztimeopen'] = 'Open time'; -$string['quiztimestart'] = 'Start'; -$string['quizparticipants'] = 'Participant count:'; -$string['quizresults'] = 'Quiz results:'; -$string['quizresultsview'] = 'View quiz results'; -$string['quizzes'] = 'Quizzes'; -$string['quizzesinprogress'] = 'Quizzes in progress'; -$string['reports'] = 'Assessment reports'; -$string['reset'] = 'Clear search'; -$string['reprocessall'] = 'Reprocess all events'; -$string['reprocessall_desc'] = 'This will delete ALL existing event records from the database and start a process to reprocess all events. This will happen in the background.'; -$string['rows5'] = '5 Rows'; -$string['rows10'] = '10 Rows'; -$string['rows20'] = '20 Rows'; -$string['rows50'] = '50 Rows'; -$string['rows100'] = '100 Rows'; -$string['scale'] = 'Scale:'; -$string['schedule'] = 'Daily schedule'; -$string['selectassessment'] = 'Select assessment type'; -$string['selectcourse'] = 'Select course first'; -$string['selectquiz'] = 'Select quiz'; -$string['searchquiz'] = 'Search for quiz'; -$string['searchquizform'] = 'Search and select the quiz to display on the dashboard'; -$string['selectmetric'] = 'Select metric'; -$string['selectyear'] = 'Select year'; -$string['settings:chartheading'] = 'Chart colors'; -$string['settings:chartheading_desc'] = 'These settings allow you to configure the colors used in the charts and graphs'; -$string['settings:finishedcolor'] = 'Finished color'; -$string['settings:finishedcolor_desc'] = 'Select color to display for finished users in charts'; -$string['settings:heat1'] = 'First heat color'; -$string['settings:heat1_desc'] = 'Select color for the first level of the frequency heatmap'; -$string['settings:heat1'] = 'First heat color'; -$string['settings:heat1_desc'] = 'Select color for the first level of the frequency heatmap'; -$string['settings:heat2'] = 'Second heat color'; -$string['settings:heat2_desc'] = 'Select color for the second level of the frequency heatmap'; -$string['settings:heat3'] = 'Third heat color'; -$string['settings:heat3_desc'] = 'Select color for the third level of the frequency heatmap'; -$string['settings:heat4'] = 'Fourth heat color'; -$string['settings:heat4_desc'] = 'Select color for the fourth level of the frequency heatmap'; -$string['settings:heat5'] = 'Fifth heat color'; -$string['settings:heat5_desc'] = 'Select color for the fifth level of the frequency heatmap'; -$string['settings:heat6'] = 'Sixth heat color'; -$string['settings:heat6_desc'] = 'Select color for the sixth level of the frequency heatmap'; -$string['settings:heatheading'] = 'Heatmap colors'; -$string['settings:heatheading_desc'] = 'These settings allow you to configure the colors used in the heatmap'; -$string['settings:hiddencourses'] = 'Include hidden courses'; -$string['settings:hiddencourses_desc'] = 'Included hidden courses in the heatmap calculations'; -$string['settings:inprogresscolor'] = 'In progress color'; -$string['settings:inprogresscolor_desc'] = 'Select color to display for in progress users in charts'; -$string['settings:loggedincolor'] = 'Logged in color'; -$string['settings:loggedincolor_desc'] = 'Select color to display for logged in users in charts'; -$string['settings:modules'] = 'Enabled modules'; -$string['settings:modules_desc'] = 'Select the modules that you want to appear in the heatmap calculations'; -$string['settings:moduleheading'] = 'Modules and courses'; -$string['settings:moduleheading_desc'] = 'These settings control how modules and courses are used in processing'; -$string['settings:notloggedincolor'] = 'Not logged in color'; -$string['settings:notloggedincolor_desc'] = 'Select color to display for not logged in users in charts'; -$string['settings:disabledmodules'] = 'Include disabled modules'; -$string['settings:disabledmodules_desc'] = 'Include modules that have been disabled in calculations'; -$string['showrows'] = 'Show rows'; -$string['sorttable'] = 'Sort table'; -$string['status'] = 'Status'; -$string['student_search'] = 'Student Search'; -$string['students'] = 'Students'; -$string['studenttable'] = 'Student attempt status'; -$string['submitoverridefail'] = 'Ajax override form submission failed'; -$string['systemdisabled'] = ' (module disabled)'; $string['task:dataprocess'] = 'Data collection task'; $string['task:quiztracking'] = 'Quiz tracking task'; -$string['time'] = 'Time'; -$string['timelimit'] = 'Time limit (minutes)'; -$string['timeendasc'] = 'End time Asc'; -$string['timeenddesc'] = 'End time Desc'; -$string['timestartasc'] = 'Start time Asc'; -$string['timestartdesc'] = 'Start time Desc'; -$string['title'] = 'Title'; -$string['toggleoverview'] = 'Toggle overview graphs'; -$string['trenddatetime'] = '%H:%M, %d-%m-%y'; -$string['userattempt'] = 'View user attempt'; -$string['upcommingquizes'] = 'Upcomming quizzes starting'; -$string['uploadpending'] = 'Upload pending'; -$string['userlogs'] = 'View user logs'; -$string['useroverride'] = 'Add user override'; -$string['userprofile'] = 'View user profile'; -$string['url'] = 'URL'; -$string['zoom'] = 'Zoom in'; -$string['jan'] = 'January'; -$string['feb'] = 'February'; -$string['mar'] = 'March'; -$string['apr'] = 'April'; -$string['may'] = 'May'; -$string['jun'] = 'June'; -$string['jul'] = 'July'; -$string['aug'] = 'August'; -$string['sep'] = 'September'; -$string['oct'] = 'October'; -$string['nov'] = 'November'; -$string['dec'] = 'December'; + +$string['noreports'] = 'No reports have been configured for you. +If you believe this is an error please contact your site administrator.'; + +$string['history:confirmreprocess'] = 'Delete ALL history and reprocess?'; +$string['history:reprocessall'] = 'Reprocess all events'; +$string['history:reprocessall_desc'] = 'This will delete ALL existing event records from the database and start a process to reprocess all events. This will happen in the background.'; + +$string['settings:clearhistory'] = 'Assessment Frequency Clear History'; +$string['settings:head'] = 'Assessment Frequency Reports'; +$string['settings:local_assessfreq'] = 'Global Settings'; +$string['settings:start_month'] = 'Start month'; +$string['settings:start_month_desc'] = 'Specify the month that the heatmap year should start from.'; +$string['settings:hiddencourses'] = 'Include hidden courses'; +$string['settings:hiddencourses_desc'] = 'Included hidden courses in the reports'; +$string['settings:enablesource'] = 'Enable: {$a}'; +$string['settings:enablesource_help'] = 'Check this control to allow the source to be used for the dashboard.'; +$string['settings:enablereport'] = 'Enable: {$a}'; +$string['settings:enablereport_help'] = 'Check this control to allow the report to be used for the dashboard.'; + +$string['filter:entersearch'] = 'Enter search'; +$string['filter:reset'] = 'Reset'; +$string['filter:showrows'] = 'Show rows'; +$string['filter:rows20'] = '20 rows'; +$string['filter:rows50'] = '50 rows'; +$string['filter:rows100'] = '100 rows'; + +$string['modal:useroverride'] = 'User override'; diff --git a/lib.php b/lib.php index 1258ad3a..484ef139 100644 --- a/lib.php +++ b/lib.php @@ -13,6 +13,9 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +use local_assessfreq\frequency; +use local_assessfreq\source_base; +use local_assessfreq\report_base; /** * This page contains callbacks. @@ -23,297 +26,214 @@ */ /** - * Returns the name of the user preferences as well as the details this plugin uses. + * This function extends the navigation with the report link. * - * @return array + * @param navigation_node $navigation The navigation node to extend + * @param stdClass $course The course to object for the report + * @param context $context The context of the course */ -function local_assessfreq_user_preferences() { - - $preferences['local_assessfreq_overview_year_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => date('Y'), - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_heatmap_year_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => date('Y'), - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_heatmap_metric_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 'assess', - 'type' => PARAM_ALPHA, - ]; - - $preferences['local_assessfreq_heatmap_modules_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => '[]', - 'type' => PARAM_RAW, - ]; - - $preferences['local_assessfreq_quiz_refresh_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 60, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quiz_table_rows_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 20, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_student_search_table_rows_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 20, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_student_search_table_hoursahead_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 4, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_student_search_table_hoursbehind_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 1, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quizzes_inprogress_table_hoursahead_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 0, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quizzes_inprogress_table_hoursbehind_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 0, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quiz_table_inprogress_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 20, - 'type' => PARAM_INT, - ]; - - $preferences['local_assessfreq_quiz_table_inprogress_sort_preference'] = [ - 'null' => NULL_NOT_ALLOWED, - 'default' => 'name_asc', - 'type' => PARAM_ALPHAEXT, - ]; - - return $preferences; +function local_assessfreq_extend_navigation_course(navigation_node $navigation, stdClass $course, context $context) { + if (has_capability('local/assessfreq:view', $context)) { + $url = new moodle_url('/local/assessfreq/', ['courseid' => $course->id]); + $settingsnode = navigation_node::create(get_string('pluginname', 'local_assessfreq'), $url); + $reportnode = $navigation->get('coursereports'); + if (isset($settingsnode) && !empty($reportnode)) { + $reportnode->add_node($settingsnode); + } + } } /** - * Return the HTML for the given chart. + * Get all of the subplugin reports that are enabled and instantiate the class. * - * @param string $args JSON from the calling AJAX function. - * @return string $chartdata The generated chart. + * @param $ignoreenabled + * @return array */ -function local_assessfreq_output_fragment_get_chart($args): string { - $allowedcalls = [ - 'assess_by_month', - 'assess_by_activity', - 'assess_by_month_student', - ]; - - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); - - if (in_array($data->call, $allowedcalls)) { - $classname = '\\local_assessfreq\\output\\' . $data->call; - $methodname = 'get_' . $data->call . '_chart'; - } else { - throw new moodle_exception('Call not allowed'); +function get_reports($ignoreenabled = false) : array { + $reports = []; + $pluginmanager = core_plugin_manager::instance(); + foreach ($pluginmanager->get_plugins_of_type('assessfreqreport') as $subplugin) { + /* @var $class report_base */ + if ($subplugin->is_enabled() || $ignoreenabled) { + $class = "assessfreqreport_{$subplugin->name}\\report"; + $report = $class::get_instance(); + if ($report->has_access()) { + $reports[$subplugin->name] = $report; + } + } } - - $assesschart = new $classname(); - $chart = $assesschart->$methodname($data->year); - - $chartdata = json_encode($chart); - return $chartdata; + return $reports; } /** - * Return the HTML for the given chart. + * Get all of the subplugin sources that are enabled and instantiate the class. * - * @param string $args JSON from the calling AJAX function. - * @return string $chartdata The generated chart. + * @param $ignoreenabled + * @return array */ -function local_assessfreq_output_fragment_get_quiz_chart($args): string { - $allowedcalls = [ - 'participant_summary', - 'participant_trend', - ]; - - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); - - if (in_array($data->call, $allowedcalls)) { - $classname = '\\local_assessfreq\\output\\' . $data->call; - $methodname = 'get_' . $data->call . '_chart'; - } else { - throw new moodle_exception('Call not allowed'); +function get_sources($ignoreenabled = false, $requiredmethod = '') : array { + $sources = []; + $pluginmanager = core_plugin_manager::instance(); + foreach ($pluginmanager->get_plugins_of_type('assessfreqsource') as $subplugin) { + if ($subplugin->is_enabled() || $ignoreenabled) { + /* @var $class source_base */ + $class = "assessfreqsource_{$subplugin->name}\\source"; + $source = $class::get_instance(); + if (!empty($requiredmethod)) { + if (!method_exists($source, $requiredmethod)) { + continue; + } + } + $sources[$subplugin->name] = $source; + } } - - $assesschart = new $classname(); - $chart = $assesschart->$methodname($data->quiz); - - $chartdata = json_encode($chart); - return $chartdata; + return $sources; } /** - * Return the HTML for the given chart. + * Using the start month defined in config get an ordered year of month names. * - * @param string $args JSON from the calling AJAX function. - * @return string $chartdata The generated chart. + * @return array */ -function local_assessfreq_output_fragment_get_quiz_inprogress_chart($args): string { - $allowedcalls = [ - 'upcomming_quizzes', - 'all_participants_inprogress', - ]; - - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); - - if (in_array($data->call, $allowedcalls)) { - $classname = '\\local_assessfreq\\output\\' . $data->call; - $methodname = 'get_' . $data->call . '_chart'; - } else { - throw new moodle_exception('Call not allowed'); - } +function get_months_ordered() : array { - $assesschart = new $classname(); - $now = time(); + $months = []; + $startmonth = get_config('local_assessfreq', 'start_month'); - if ($methodname == 'get_all_participants_inprogress_chart') { - $chart = $assesschart->$methodname($now, $data->hoursahead, $data->hoursbehind); - } else { - $chart = $assesschart->$methodname($now); + for ($i = $startmonth; $i < $startmonth + 12; $i++) { + $month = $i - 12 > 0 ? $i - 12 : $i; + + $date = DateTime::createFromFormat('!m', $month); + $monthname = $date->format('F'); + + $months[$month] = $monthname; } - $chartdata = json_encode($chart); - return $chartdata; + return $months; } /** - * Renders the quiz search form for the modal on the quiz dashboard. + * Get the years that have events with the preferred year active. * - * @param array $args - * @return string $o Form HTML. + * @param $preference + * @return array */ -function local_assessfreq_output_fragment_new_base_form($args): string { +function get_years($preference) : array { - $context = $args['context']; - has_capability('moodle/site:config', $context); + $currentyear = date('Y'); - $mform = new \local_assessfreq\form\quiz_search_form(null, null, 'post', '', ['class' => 'ignoredirty']); + // Get years that have events and load into context. + $frequency = new frequency(); + $yearlist = $frequency->get_years_has_events(); - ob_start(); - $mform->display(); - $o = ob_get_contents(); - ob_end_clean(); + if (empty($yearlist)) { + $yearlist = [$currentyear]; + } - return $o; -} + // Add current year to the selection of years if missing. + if (!in_array($currentyear, $yearlist)) { + $yearlist[] = $currentyear; + } -/** - * Renders the student table on the quiz dashboard screen. - * We update the table via ajax. - * - * @param array $args - * @return string $o Form HTML. - */ -function local_assessfreq_output_fragment_get_student_table($args): string { - global $CFG, $PAGE; + $years = []; - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); + foreach ($yearlist as $year) { + $years[$year] = ['year' => ['val' => $year]]; + } - $baseurl = $CFG->wwwroot . '/local/assessfreq/dashboard_quiz.php'; - $output = $PAGE->get_renderer('local_assessfreq'); + if (!$preference) { + $preference = date('Y'); + } - $o = $output->render_student_table($baseurl, $data->quiz, $context->id, $data->search, $data->page); + $years[$preference]['year']['active'] = true; - return $o; + return array_values($years); } /** - * Renders the student table on the student search screen. - * We update the table via ajax. + * Get the modules to use in data collection. + * This is based on which sources have been enabled. * - * @param array $args - * @return string $o Form HTML. + * @return array $modules The enabled modules. */ -function local_assessfreq_output_fragment_get_student_search_table($args): string { - global $CFG, $PAGE; +function get_modules($preferences, $requiredmethod= '') : array { - $context = $args['context']; - has_capability('moodle/site:config', $context); - $data = json_decode($args['data']); - $search = is_null($data->search) ? '' : $data->search; - $now = time(); - $hoursahead = (int)$data->hoursahead; - $hoursbehind = (int)$data->hoursbehind; + $sources = get_sources(false, $requiredmethod); - $baseurl = $CFG->wwwroot . '/local/assessfreq/student_search.php'; - $output = $PAGE->get_renderer('local_assessfreq'); + // Get modules for filters and load into context. + $modules = []; + $modules['all'] = ['module' => ['val' => 'all', 'name' => get_string('all')]]; - $o = $output->render_student_search_table($baseurl, $context->id, $search, $hoursahead, $hoursbehind, $now, $data->page); + foreach ($sources as $source) { + $modulename = get_string('modulename', $source->get_module()); + $modules[$source->get_module()] = ['module' => ['val' => $source->get_module(), 'name' => $modulename]]; + } - return $o; + if (!$preferences) { + $preferences = ["all"]; + } + + foreach ($preferences as $preference) { + if (isset($modules[$preference])) { + $modules[$preference]['module']['active'] = true; + } + } + + return array_values($modules); } /** - * Renders the quizzes in progress "table" on the quiz dashboard screen. - * We update the table via ajax. - * The table isn't a real table it's a collection of divs. + * Given a list of user ids, check if the user is logged in our not + * and return summary counts of logged in and not logged in users. * - * @param array $args - * @return string $o Form HTML. + * @param array $userids User ids to get logged in status. + * @return stdClass $usercounts Object with coutns of users logged in and not logged in. */ -function local_assessfreq_output_fragment_get_quizzes_inprogress_table($args): string { - global $PAGE; +function get_loggedin_users(array $userids): stdClass { + global $CFG, $DB; - $context = $args['context']; - has_capability('moodle/site:config', $context); + $maxlifetime = $CFG->sessiontimeout; + $timedout = time() - $maxlifetime; + $userchunks = array_chunk($userids, 250); // Break list of users into chunks so we don't exceed DB IN limits. - $data = json_decode($args['data']); - $search = is_null($data->search) ? '' : $data->search; - $sorton = is_null($data->sorton) ? 'name' : $data->sorton; - $direction = is_null($data->direction) ? 'asc' : $data->direction; - $hoursahead = (int)$data->hoursahead; - $hoursbehind = (int)$data->hoursbehind; + $loggedinusers = []; - $output = $PAGE->get_renderer('local_assessfreq'); - $o = $output->render_quizzes_inprogress_table($search, $data->page, $sorton, $direction, $hoursahead, $hoursbehind); + foreach ($userchunks as $userchunk) { + [$insql, $inparams] = $DB->get_in_or_equal($userchunk); + $inparams[] = $timedout; - return $o; + $sql = "SELECT DISTINCT(userid) + FROM {sessions} + WHERE userid $insql + AND timemodified >= ?"; + $users = $DB->get_fieldset_sql($sql, $inparams); + $loggedinusers = array_merge($loggedinusers, $users); + } + + $loggedoutusers = array_diff($userids, $loggedinusers); + + $loggedin = count($loggedinusers); + $loggedout = count($loggedoutusers); + + $usercounts = new stdClass(); + $usercounts->loggedin = $loggedin; + $usercounts->loggedout = $loggedout; + $usercounts->loggedinusers = $loggedinusers; + $usercounts->loggedoutusers = $loggedoutusers; + + return $usercounts; } /** - * Renders the quiz user override form for the modal on the quiz dashboard. + * Renders the user override form for the modal. * * @param array $args * @return string $o Form HTML. */ function local_assessfreq_output_fragment_new_override_form($args): string { - global $DB; + global $DB, $CFG; - $context = $args['context']; - has_capability('mod/quiz:manageoverrides', $context); + $module = $args['activitytype']; $serialiseddata = json_decode($args['jsonformdata'], true); @@ -323,36 +243,17 @@ function local_assessfreq_output_fragment_new_override_form($args): string { parse_str($serialiseddata, $formdata); } - // Get some data needed to generate the form. - $quizid = $args['quizid']; - $quizdata = new \local_assessfreq\quiz(); - $quizcontext = $quizdata->get_quiz_context($quizid); - $quiz = $DB->get_record('quiz', ['id' => $quizid], '*', MUST_EXIST); - - $cm = get_course_and_cm_from_cmid($quizcontext->instanceid, 'quiz')[1]; - - // Check if we have an existing override for this user. - $override = $DB->get_record('quiz_overrides', ['quiz' => $quiz->id, 'userid' => $args['userid']]); - - if ($override) { - $data = clone $override; - } else { - $data = new \stdClass(); - $data->userid = $args['userid']; - } - - $mform = new \local_assessfreq\form\quiz_override_form($cm, $quiz, $quizcontext, $override, $formdata); - $mform->set_data($data); - - if (!empty($serialiseddata)) { - // If we were passed non-empty form data we want the mform to call validation functions and show errors. - $mform->is_validated(); + $sources = get_sources(); + $source = $sources[$module]; + $o = ''; + /* @var $source source_base */ + if (method_exists($source, 'get_override_form')) { + $mform = $source->get_override_form($args['activityid'], $args['context'], $args['userid'], $serialiseddata); + ob_start(); + $mform->display(); + $o = ob_get_contents(); + ob_end_clean(); } - ob_start(); - $mform->display(); - $o = ob_get_contents(); - ob_end_clean(); - return $o; } diff --git a/report/activities_in_progress/amd/build/activities_in_progress.min.js b/report/activities_in_progress/amd/build/activities_in_progress.min.js new file mode 100644 index 00000000..4df6e71e --- /dev/null +++ b/report/activities_in_progress/amd/build/activities_in_progress.min.js @@ -0,0 +1,11 @@ +define("assessfreqreport_activities_in_progress/activities_in_progress",["exports","local_assessfreq/table_handler","local_assessfreq/user_preferences"],(function(_exports,_table_handler,UserPreference){var obj; +/** + * Chart data JS module. + * + * @module assessfreqreport/activities_in_progress + * @package + * @copyright Simon Thornett + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_table_handler=(obj=_table_handler)&&obj.__esModule?obj:{default:obj},UserPreference=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(UserPreference);_exports.init=context=>{moduleDropdown();let table=new _table_handler.default(0,context,"assessfreqreport-activities-in-progress-table","assessfreqreport_activities_in_progress","get_in_progress_table","assessfreqreport_activities_in_progress_table_rows_preference","assessfreqreport_activities_in_progress_table_sort_preference","assessfreqreport-activities-in-progress-table-search","assessfreqreport-activities-in-progress-table","local_assessfreq_set_table_preference");table.getTable();let tableSearchInputElement=document.getElementById("assessfreqreport-activities-in-progress-table-search"),tableSearchResetElement=document.getElementById("assessfreqreport-activities-in-progress-table-search-reset"),tableSearchRowsElement=document.getElementById("assessfreqreport-activities-in-progress-table-rows"),tableSearchAheadElement=document.getElementById("assessfreqreport-activities-in-progress-hoursahead"),tableSearchBehindElement=document.getElementById("assessfreqreport-activities-in-progress-hoursbehind");tableSearchInputElement.addEventListener("keyup",table.tableSearch),tableSearchInputElement.addEventListener("paste",table.tableSearch),tableSearchResetElement.addEventListener("click",table.tableSearchReset),tableSearchRowsElement.addEventListener("click",table.tableSearchRowSet),tableSearchAheadElement.addEventListener("click",tableSearchAheadSet),tableSearchBehindElement.addEventListener("click",tableSearchBehindSet)};const moduleDropdown=()=>{let links=document.getElementById("local-assessfreq-report-activities-in-progress-filter-type").getElementsByTagName("a"),all=links[0],modules=[];for(let i=0;i{event.preventDefault(),event.stopPropagation();for(let j=0;j{event.preventDefault(),event.stopPropagation();document.getElementById("local-assessfreq-report-activities-in-progress-filter-type-filters").classList.remove("show");for(let i=0;i{event.preventDefault(),event.stopPropagation(),all.classList.remove("active"),event.target.classList.toggle("active")}))}},tableSearchAheadSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){if(event.target.classList.contains("active"))return;let hours=event.target.dataset.metric;UserPreference.setUserPreference("assessfreqreport_activities_in_progress_hoursahead_preference",hours),location.reload()}},tableSearchBehindSet=event=>{if(event.preventDefault(),"a"===event.target.tagName.toLowerCase()){if(event.target.classList.contains("active"))return;let hours=event.target.dataset.metric;UserPreference.setUserPreference("assessfreqreport_activities_in_progress_hoursbehind_preference",hours),location.reload()}}})); + +//# sourceMappingURL=activities_in_progress.min.js.map \ No newline at end of file diff --git a/report/activities_in_progress/amd/build/activities_in_progress.min.js.map b/report/activities_in_progress/amd/build/activities_in_progress.min.js.map new file mode 100644 index 00000000..ad3ef18a --- /dev/null +++ b/report/activities_in_progress/amd/build/activities_in_progress.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"activities_in_progress.min.js","sources":["../src/activities_in_progress.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Chart data JS module.\n *\n * @module assessfreqreport/activities_in_progress\n * @package\n * @copyright Simon Thornett \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport TableHandler from 'local_assessfreq/table_handler';\nimport * as UserPreference from 'local_assessfreq/user_preferences';\n\n/**\n * Init function.\n * @param {Integer} context\n */\nexport const init = (context) => {\n\n // Set up event listener and related actions for module dropdown on heatmp.\n moduleDropdown();\n\n let table = new TableHandler(\n 0,\n context,\n 'assessfreqreport-activities-in-progress-table',\n 'assessfreqreport_activities_in_progress',\n 'get_in_progress_table',\n 'assessfreqreport_activities_in_progress_table_rows_preference',\n 'assessfreqreport_activities_in_progress_table_sort_preference',\n 'assessfreqreport-activities-in-progress-table-search',\n 'assessfreqreport-activities-in-progress-table',\n 'local_assessfreq_set_table_preference'\n );\n\n table.getTable();\n\n let tableSearchInputElement = document.getElementById('assessfreqreport-activities-in-progress-table-search');\n let tableSearchResetElement = document.getElementById('assessfreqreport-activities-in-progress-table-search-reset');\n let tableSearchRowsElement = document.getElementById('assessfreqreport-activities-in-progress-table-rows');\n let tableSearchAheadElement = document.getElementById('assessfreqreport-activities-in-progress-hoursahead');\n let tableSearchBehindElement = document.getElementById('assessfreqreport-activities-in-progress-hoursbehind');\n\n tableSearchInputElement.addEventListener('keyup', table.tableSearch);\n tableSearchInputElement.addEventListener('paste', table.tableSearch);\n tableSearchResetElement.addEventListener('click', table.tableSearchReset);\n tableSearchRowsElement.addEventListener('click', table.tableSearchRowSet);\n tableSearchAheadElement.addEventListener('click', tableSearchAheadSet);\n tableSearchBehindElement.addEventListener('click', tableSearchBehindSet);\n\n};\n\n/**\n * Add the event listeners to the modules in the module select dropdown.\n */\nconst moduleDropdown = () => {\n let links = document.getElementById('local-assessfreq-report-activities-in-progress-filter-type').getElementsByTagName('a');\n let all = links[0];\n let modules = [];\n\n for (let i = 0; i < links.length; i++) {\n let module = links[i].dataset.module;\n\n if (module.toLowerCase() === 'all') {\n links[i].addEventListener('click', event => {\n event.preventDefault();\n event.stopPropagation();\n // Remove active class from all other links.\n for (let j = 0; j < links.length; j++) {\n links[j].classList.remove('active');\n }\n event.target.classList.toggle('active');\n });\n } else if (module.toLowerCase() === 'close') {\n links[i].addEventListener('click', event => {\n event.preventDefault();\n event.stopPropagation();\n\n const dropdownmenu = document.getElementById('local-assessfreq-report-activities-in-progress-filter-type-filters');\n dropdownmenu.classList.remove('show');\n\n for (let i = 0; i < links.length; i++) {\n if (links[i].classList.contains('active')) {\n let module = links[i].dataset.module;\n modules.push(module);\n }\n }\n\n // Save selection as a user preference.\n UserPreference.setUserPreference(\n 'assessfreqreport_activities_in_progress_modules_preference',\n JSON.stringify(modules)\n );\n\n // Reload based on selected year.\n location.reload();\n });\n } else {\n links[i].addEventListener('click', event => {\n event.preventDefault();\n event.stopPropagation();\n\n all.classList.remove('active');\n\n event.target.classList.toggle('active');\n });\n }\n }\n};\n\n/**\n * Process the hours ahead event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchAheadSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n // Don't process already selected links.\n if (event.target.classList.contains('active')) {\n return;\n }\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('assessfreqreport_activities_in_progress_hoursahead_preference', hours);\n // Reload based on selected year.\n location.reload();\n }\n};\n\n/**\n * Process the hours behind event from the student table.\n *\n * @param {Event} event The triggered event for the element.\n */\nconst tableSearchBehindSet = (event) => {\n event.preventDefault();\n if (event.target.tagName.toLowerCase() === 'a') {\n // Don't process already selected links.\n if (event.target.classList.contains('active')) {\n return;\n }\n let hours = event.target.dataset.metric;\n UserPreference.setUserPreference('assessfreqreport_activities_in_progress_hoursbehind_preference', hours);\n // Reload based on selected year.\n location.reload();\n }\n};\n"],"names":["context","moduleDropdown","table","TableHandler","getTable","tableSearchInputElement","document","getElementById","tableSearchResetElement","tableSearchRowsElement","tableSearchAheadElement","tableSearchBehindElement","addEventListener","tableSearch","tableSearchReset","tableSearchRowSet","tableSearchAheadSet","tableSearchBehindSet","links","getElementsByTagName","all","modules","i","length","module","dataset","toLowerCase","event","preventDefault","stopPropagation","j","classList","remove","target","toggle","contains","push","UserPreference","setUserPreference","JSON","stringify","location","reload","tagName","hours","metric"],"mappings":";;;;;;;;qmCA+BqBA,UAGjBC,qBAEIC,MAAQ,IAAIC,uBACZ,EACAH,QACA,gDACA,0CACA,wBACA,gEACA,gEACA,uDACA,gDACA,yCAGJE,MAAME,eAEFC,wBAA0BC,SAASC,eAAe,wDAClDC,wBAA0BF,SAASC,eAAe,8DAClDE,uBAAyBH,SAASC,eAAe,sDACjDG,wBAA0BJ,SAASC,eAAe,sDAClDI,yBAA2BL,SAASC,eAAe,uDAEvDF,wBAAwBO,iBAAiB,QAASV,MAAMW,aACxDR,wBAAwBO,iBAAiB,QAASV,MAAMW,aACxDL,wBAAwBI,iBAAiB,QAASV,MAAMY,kBACxDL,uBAAuBG,iBAAiB,QAASV,MAAMa,mBACvDL,wBAAwBE,iBAAiB,QAASI,qBAClDL,yBAAyBC,iBAAiB,QAASK,6BAOjDhB,eAAiB,SACfiB,MAAQZ,SAASC,eAAe,8DAA8DY,qBAAqB,KACnHC,IAAMF,MAAM,GACZG,QAAU,OAET,IAAIC,EAAI,EAAGA,EAAIJ,MAAMK,OAAQD,IAAK,KAC/BE,OAASN,MAAMI,GAAGG,QAAQD,OAED,QAAzBA,OAAOE,cACPR,MAAMI,GAAGV,iBAAiB,SAASe,QAC/BA,MAAMC,iBACND,MAAME,sBAED,IAAIC,EAAI,EAAGA,EAAIZ,MAAMK,OAAQO,IAC9BZ,MAAMY,GAAGC,UAAUC,OAAO,UAE9BL,MAAMM,OAAOF,UAAUG,OAAO,aAEF,UAAzBV,OAAOE,cACdR,MAAMI,GAAGV,iBAAiB,SAASe,QAC/BA,MAAMC,iBACND,MAAME,kBAEevB,SAASC,eAAe,sEAChCwB,UAAUC,OAAO,YAEzB,IAAIV,EAAI,EAAGA,EAAIJ,MAAMK,OAAQD,OAC1BJ,MAAMI,GAAGS,UAAUI,SAAS,UAAW,KACnCX,OAASN,MAAMI,GAAGG,QAAQD,OAC9BH,QAAQe,KAAKZ,QAKrBa,eAAeC,kBACX,6DACAC,KAAKC,UAAUnB,UAInBoB,SAASC,YAGbxB,MAAMI,GAAGV,iBAAiB,SAASe,QAC/BA,MAAMC,iBACND,MAAME,kBAENT,IAAIW,UAAUC,OAAO,UAErBL,MAAMM,OAAOF,UAAUG,OAAO,eAWxClB,oBAAuBW,WACzBA,MAAMC,iBACqC,MAAvCD,MAAMM,OAAOU,QAAQjB,cAAuB,IAExCC,MAAMM,OAAOF,UAAUI,SAAS,qBAGhCS,MAAQjB,MAAMM,OAAOR,QAAQoB,OACjCR,eAAeC,kBAAkB,gEAAiEM,OAElGH,SAASC,WASXzB,qBAAwBU,WAC1BA,MAAMC,iBACqC,MAAvCD,MAAMM,OAAOU,QAAQjB,cAAuB,IAExCC,MAAMM,OAAOF,UAAUI,SAAS,qBAGhCS,MAAQjB,MAAMM,OAAOR,QAAQoB,OACjCR,eAAeC,kBAAkB,iEAAkEM,OAEnGH,SAASC"} \ No newline at end of file diff --git a/report/activities_in_progress/amd/src/activities_in_progress.js b/report/activities_in_progress/amd/src/activities_in_progress.js new file mode 100644 index 00000000..4a7d0884 --- /dev/null +++ b/report/activities_in_progress/amd/src/activities_in_progress.js @@ -0,0 +1,161 @@ +// 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 . + +/** + * Chart data JS module. + * + * @module assessfreqreport/activities_in_progress + * @package + * @copyright Simon Thornett + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import TableHandler from 'local_assessfreq/table_handler'; +import * as UserPreference from 'local_assessfreq/user_preferences'; + +/** + * Init function. + * @param {Integer} context + */ +export const init = (context) => { + + // Set up event listener and related actions for module dropdown on heatmp. + moduleDropdown(); + + let table = new TableHandler( + 0, + context, + 'assessfreqreport-activities-in-progress-table', + 'assessfreqreport_activities_in_progress', + 'get_in_progress_table', + 'assessfreqreport_activities_in_progress_table_rows_preference', + 'assessfreqreport_activities_in_progress_table_sort_preference', + 'assessfreqreport-activities-in-progress-table-search', + 'assessfreqreport-activities-in-progress-table', + 'local_assessfreq_set_table_preference' + ); + + table.getTable(); + + let tableSearchInputElement = document.getElementById('assessfreqreport-activities-in-progress-table-search'); + let tableSearchResetElement = document.getElementById('assessfreqreport-activities-in-progress-table-search-reset'); + let tableSearchRowsElement = document.getElementById('assessfreqreport-activities-in-progress-table-rows'); + let tableSearchAheadElement = document.getElementById('assessfreqreport-activities-in-progress-hoursahead'); + let tableSearchBehindElement = document.getElementById('assessfreqreport-activities-in-progress-hoursbehind'); + + tableSearchInputElement.addEventListener('keyup', table.tableSearch); + tableSearchInputElement.addEventListener('paste', table.tableSearch); + tableSearchResetElement.addEventListener('click', table.tableSearchReset); + tableSearchRowsElement.addEventListener('click', table.tableSearchRowSet); + tableSearchAheadElement.addEventListener('click', tableSearchAheadSet); + tableSearchBehindElement.addEventListener('click', tableSearchBehindSet); + +}; + +/** + * Add the event listeners to the modules in the module select dropdown. + */ +const moduleDropdown = () => { + let links = document.getElementById('local-assessfreq-report-activities-in-progress-filter-type').getElementsByTagName('a'); + let all = links[0]; + let modules = []; + + for (let i = 0; i < links.length; i++) { + let module = links[i].dataset.module; + + if (module.toLowerCase() === 'all') { + links[i].addEventListener('click', event => { + event.preventDefault(); + event.stopPropagation(); + // Remove active class from all other links. + for (let j = 0; j < links.length; j++) { + links[j].classList.remove('active'); + } + event.target.classList.toggle('active'); + }); + } else if (module.toLowerCase() === 'close') { + links[i].addEventListener('click', event => { + event.preventDefault(); + event.stopPropagation(); + + const dropdownmenu = document.getElementById('local-assessfreq-report-activities-in-progress-filter-type-filters'); + dropdownmenu.classList.remove('show'); + + for (let i = 0; i < links.length; i++) { + if (links[i].classList.contains('active')) { + let module = links[i].dataset.module; + modules.push(module); + } + } + + // Save selection as a user preference. + UserPreference.setUserPreference( + 'assessfreqreport_activities_in_progress_modules_preference', + JSON.stringify(modules) + ); + + // Reload based on selected year. + location.reload(); + }); + } else { + links[i].addEventListener('click', event => { + event.preventDefault(); + event.stopPropagation(); + + all.classList.remove('active'); + + event.target.classList.toggle('active'); + }); + } + } +}; + +/** + * Process the hours ahead event from the student table. + * + * @param {Event} event The triggered event for the element. + */ +const tableSearchAheadSet = (event) => { + event.preventDefault(); + if (event.target.tagName.toLowerCase() === 'a') { + // Don't process already selected links. + if (event.target.classList.contains('active')) { + return; + } + let hours = event.target.dataset.metric; + UserPreference.setUserPreference('assessfreqreport_activities_in_progress_hoursahead_preference', hours); + // Reload based on selected year. + location.reload(); + } +}; + +/** + * Process the hours behind event from the student table. + * + * @param {Event} event The triggered event for the element. + */ +const tableSearchBehindSet = (event) => { + event.preventDefault(); + if (event.target.tagName.toLowerCase() === 'a') { + // Don't process already selected links. + if (event.target.classList.contains('active')) { + return; + } + let hours = event.target.dataset.metric; + UserPreference.setUserPreference('assessfreqreport_activities_in_progress_hoursbehind_preference', hours); + // Reload based on selected year. + location.reload(); + } +}; diff --git a/report/activities_in_progress/classes/output/renderer.php b/report/activities_in_progress/classes/output/renderer.php new file mode 100644 index 00000000..5548e3cb --- /dev/null +++ b/report/activities_in_progress/classes/output/renderer.php @@ -0,0 +1,352 @@ +. + +/** + * Renderer. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace assessfreqreport_activities_in_progress\output; + +use context_system; +use core\chart_bar; +use core\chart_pie; +use core\chart_series; +use html_writer; +use local_assessfreq\source_base; +use local_assessfreq\utils; +use paging_bar; +use plugin_renderer_base; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/local/assessfreq/lib.php'); + +class renderer extends plugin_renderer_base { + + public function render_report($data) { + + // In progress counts. + $contents = ''; + foreach ($data['inprogress'] as $count) { + $contents .= html_writer::div($count); + } + + $progresssummarycontainer = $this->render_from_template( + 'local_assessfreq/card', + [ + 'header' => get_string('inprogress:head', 'assessfreqreport_activities_in_progress'), + 'contents' => $contents + ] + ); + + // Upcoming activities starting. + $labels = []; + $seriestitle = get_string('upcomingchart:activities', 'assessfreqreport_activities_in_progress'); + $participantseries = get_string('upcomingchart:participants', 'assessfreqreport_activities_in_progress'); + + $seriesdata = []; + $participantseriesdata = []; + + foreach ($data['upcoming'] as $sourceupcoming) { + foreach ($sourceupcoming['upcoming'] as $timestamp => $upcoming) { + $count = 0; + $participantcount = 0; + + foreach ($upcoming as $activity) { + $count++; + $participantcount += $activity->participants; + } + + foreach ($sourceupcoming['inprogress'] as $inprogress) { + if ($inprogress->timestampopen >= $timestamp && $inprogress->timestampopen < $timestamp + HOURSECS) { + $count++; + $participantcount += $inprogress->participants; + } + } + + if (!isset($seriesdata[$timestamp])) { + $seriesdata[$timestamp] = 0; + } + $seriesdata[$timestamp] += $count; + if (!isset($participantseriesdata[$timestamp])) { + $participantseriesdata[$timestamp] = 0; + } + $participantseriesdata[$timestamp] += $participantcount; + $labels[$timestamp] = userdate( + $timestamp + HOURSECS, + get_string('upcomingchart:inprogressdatetime', 'assessfreqreport_activities_in_progress') + ); + } + } + $seriesdata = array_values($seriesdata); + $participantseriesdata = array_values($participantseriesdata); + $labels = array_values($labels); + + if ($seriesdata) { + $series = new chart_series($seriestitle, $seriesdata); + $participantseries = new chart_series($participantseries, $participantseriesdata); + + $chart = new chart_bar(); + $chart->add_series($series); + $chart->add_series($participantseries); + $chart->set_labels($labels); + + $contents = $this->render($chart); + } else { + $contents = ''; + } + $upcomingcontainer = $this->render_from_template( + 'local_assessfreq/card', + [ + 'header' => get_string('upcomingchart:head', 'assessfreqreport_activities_in_progress'), + 'contents' => $contents, + ] + ); + + // Participant summary container. + $seriesdata = [ + 'notloggedin' => 0, + 'loggedin' => 0, + 'inprogress' => 0, + 'finished' => 0, + ]; + + foreach ($data['participants'] as $sourceparticipants) { + foreach ($sourceparticipants as $status => $value) { + $seriesdata[$status] = $value + ($seriesdata[$status] ?? 0); + } + } + + $seriesdata = array_values($seriesdata); + + $labels = [ + get_string('summarychart:notloggedin', 'assessfreqreport_activities_in_progress'), + get_string('summarychart:loggedin', 'assessfreqreport_activities_in_progress'), + get_string('summarychart:inprogress', 'assessfreqreport_activities_in_progress'), + get_string('summarychart:finished', 'assessfreqreport_activities_in_progress'), + ]; + + $colors = [ + get_config('assessfreqreport_activities_in_progress', 'notloggedincolor'), + get_config('assessfreqreport_activities_in_progress', 'loggedincolor'), + get_config('assessfreqreport_activities_in_progress', 'inprogresscolor'), + get_config('assessfreqreport_activities_in_progress', 'finishedcolor'), + ]; + + if ($participants) { + $chart = new chart_pie(); + $chart->set_doughnut(true); + $participants = new chart_series( + get_string('summarychart:participants', 'assessfreqreport_activities_in_progress'), + $seriesdata + ); + $participants->set_colors($colors); + $chart->add_series($participants); + $chart->set_labels($labels); + + $contents = $this->render($chart); + } else { + $contents = ''; + } + + $summarycontainer = $this->render_from_template( + 'local_assessfreq/card', + [ + 'header' => get_string('summarychart:head', 'assessfreqreport_activities_in_progress'), + 'contents' => $contents + ] + ); + + // Activies in progress container. + $progresscontainer = $this->render_from_template( + 'local_assessfreq/card', + [ + 'header' => get_string('inprogresstable:head', 'assessfreqreport_activities_in_progress'), + 'contents' => 'No data' + ] + ); + + $preferencerows = get_user_preferences('assessfreqreport_activities_in_progress_table_rows_preference', 20); + $rows = [ + 20 => 'rows20', + 50 => 'rows50', + 100 => 'rows100', + ]; + + $preferencehoursahead = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursahead_preference', 8); + $preferencehoursbehind = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursbehind_preference', 1); + + $hours = [ + 0 => 'hours0', + 1 => 'hours1', + 4 => 'hours4', + 8 => 'hours8', + ]; + + $preferencemodule = json_decode( + get_user_preferences('assessfreqreport_activities_in_progress_modules_preference', '["all"]'), + true + ); + // Only get modules with the "get_inprogress_count" method as only these display on the report. + $modules = get_modules($preferencemodule, 'get_inprogress_count'); + + return $this->render_from_template( + 'assessfreqreport_activities_in_progress/activities-in-progress', + [ + 'filters' => [ + 'modules' => $modules, + 'hoursahead' => [$hours[$preferencehoursahead] => 'true'], + 'hoursbehind' => [$hours[$preferencehoursbehind] => 'true'], + ], + 'progresssummary' => $progresssummarycontainer, + 'upcoming' => $upcomingcontainer, + 'summary' => $summarycontainer, + 'progress' => $progresscontainer, + 'table' => [ + 'id' => 'assessfreqreport-activities-in-progress', + 'name' => get_string('inprogresstable:head', 'assessfreqreport_activities_in_progress'), + 'rows' => [$rows[$preferencerows] => 'true'], + ] + ] + ); + } + + /** + * Renders the activities in progress "table" on the dashboard screen. + * We update the table via ajax. + * The table isn't a real table it's a collection of divs. + * + * @param string $search The search string for the table. + * @param int $page The page number of results. + * @param string $sorton The value to sort by. + * @param string $direction The direction to sort. + * @param int $hoursahead Amount of time in hours to look ahead for activity starting. + * @param int $hoursbehind Amount of time in hours to look behind for activity starting. + * @return string $output HTML for the table. + */ + public function render_activities_inprogress_table( + string $search, + int $page, + string $sorton, + string $direction + ): string { + $now = time(); + $hoursahead = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursahead_preference', 8); + $hoursbehind = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursbehind_preference', 1); + $sources = get_sources(); + $inprogress = []; + $modulepreference = json_decode( + get_user_preferences('assessfreqreport_activities_in_progress_modules_preference', '["all"]') + ); + /* @var $source source_base */ + foreach ($sources as $source) { + if (!in_array('all', $modulepreference) && !in_array($source->get_module(), $modulepreference)) { + continue; + } + if (method_exists($source, 'get_inprogress_data')) { + $inprogress[] = $source->get_inprogress_data($now, $hoursahead, $hoursbehind); + } + } + $pagesize = get_user_preferences('assessfreqreport_activities_in_progress_table_rows_preference', 20); + + $activities = []; + foreach ($inprogress as $activity) { + array_push($activities, ...$activity['inprogress']); + $upcomingactivities = $activity['upcoming']; + $finishedactivities = $activity['finished']; + + foreach ($upcomingactivities as $upcomingactivity) { + foreach ($upcomingactivity as $key => $upcoming) { + $activities[$key] = $upcoming; + } + } + + foreach ($finishedactivities as $finishedactivity) { + foreach ($finishedactivity as $key => $finished) { + $activities[$key] = $finished; + } + } + } + + if (empty($activities)) { + return ''; + } + + [$filtered, $totalrows] = $this->filter($activities, $search, $page, $pagesize); + $sortedactivities = utils::sort($filtered, $sorton, $direction); + + $pagingbar = new paging_bar($totalrows, $page, $pagesize, '/'); + $pagingoutput = $this->render($pagingbar); + + $context = [ + 'activities' => array_values($sortedactivities), + 'pagingbar' => $pagingoutput, + 'iscourse' => $this->page->course->id !== SITEID, + ]; + + return $this->render_from_template('assessfreqreport_activities_in_progress/activities-in-progress-table', $context); + } + + + /** + * Given an array of activities, filter based on a provided search string and apply pagination. + * + * @param array $activities Array of activities to search. + * @param string $search The string to search by. + * @param int $page The page number of results. + * @param int $pagesize The page size for results. + * @return array $result Array containing list of filtered activities and total of how many activities matched the filter. + */ + private function filter(array $activities, string $search, int $page, int $pagesize): array { + $filtered = []; + $searchfields = ['name', 'coursefullname']; + $offset = $page * $pagesize; + $offsetcount = 0; + $recordcount = 0; + + foreach ($activities as $id => $activity) { + $searchcount = 0; + if ($search != '') { + $searchcount = -1; + foreach ($searchfields as $searchfield) { + if (stripos($activity->{$searchfield}, $search) !== false) { + $searchcount++; + } + } + } + + if ($searchcount > -1 && $offsetcount >= $offset && $recordcount < $pagesize) { + $filtered[$id] = $activity; + } + + if ($searchcount > -1 && $offsetcount >= $offset) { + $recordcount++; + } + + if ($searchcount > -1) { + $offsetcount++; + } + } + + return [$filtered, $offsetcount]; + } +} diff --git a/report/activities_in_progress/classes/report.php b/report/activities_in_progress/classes/report.php new file mode 100644 index 00000000..74c330b6 --- /dev/null +++ b/report/activities_in_progress/classes/report.php @@ -0,0 +1,127 @@ +. + +/** + * Main report class. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace assessfreqreport_activities_in_progress; + +use local_assessfreq\report_base; +use local_assessfreq\source_base; + +class report extends report_base { + const WEIGHT = 30; + + /** + * @inheritDoc + */ + public function get_name() : string { + return get_string("tab:name", "assessfreqreport_activities_in_progress"); + } + + /** + * @inheritDoc + */ + public function get_tab_weight() : int { + return self::WEIGHT; + } + + /** + * @inheritDoc + */ + public function get_tablink() : string { + return 'activities_in_progress'; + } + + /** + * @inheritDoc + */ + public function has_access() : bool { + global $PAGE; + + return has_capability('assessfreqreport/activities_in_progress:view', $PAGE->context); + } + + /** + * @inheritDoc + */ + public function get_contents() : string { + global $PAGE; + + $data = []; + $inprogress = []; + $upcoming = []; + $participants = []; + $now = time(); + $modulepreference = json_decode( + get_user_preferences('assessfreqreport_activities_in_progress_modules_preference', '["all"]') + ); + $sources = get_sources(); + $hoursahead = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursahead_preference', 8); + $hoursbehind = (int)get_user_preferences('assessfreqreport_activities_in_progress_hoursbehind_preference', 1); + + foreach ($sources as $source) { + /* @var $source source_base */ + if (!in_array('all', $modulepreference) && !in_array($source->get_module(), $modulepreference)) { + continue; + } + if (method_exists($source, 'get_inprogress_count')) { + $inprogress[] = $source->get_inprogress_count($now, $hoursahead, $hoursbehind); + } + if (method_exists($source, 'get_upcoming_data')) { + $upcoming[] = $source->get_upcoming_data($now, $hoursahead, $hoursbehind); + } + if (method_exists($source, 'get_all_participants_inprogress_data')) { + $participants[] = $source->get_all_participants_inprogress_data($now, $hoursahead, $hoursbehind); + } + } + $data['inprogress'] = $inprogress; + $data['upcoming'] = $upcoming; + $data['participants'] = $participants; + + $renderer = $PAGE->get_renderer("assessfreqreport_activities_in_progress"); + + return $renderer->render_report($data); + } + + /** + * @inheritDoc + */ + protected function get_required_js() : void { + global $PAGE; + + $PAGE->requires->js_call_amd( + 'assessfreqreport_activities_in_progress/activities_in_progress', + 'init', + [$PAGE->context->id] + ); + } + + /** + * @inheritDoc + */ + protected function get_required_css(): void { + global $PAGE; + + $PAGE->requires->css('/local/assessfreq/report/activities_in_progress/styles.css'); + } +} diff --git a/report/activities_in_progress/db/access.php b/report/activities_in_progress/db/access.php new file mode 100644 index 00000000..7e3d2ed6 --- /dev/null +++ b/report/activities_in_progress/db/access.php @@ -0,0 +1,34 @@ +. + +/** + * Access file. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = [ + 'assessfreqreport/activities_in_progress:view' => [ + 'captype' => 'read', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => [], + ], +]; diff --git a/report/activities_in_progress/lang/en/assessfreqreport_activities_in_progress.php b/report/activities_in_progress/lang/en/assessfreqreport_activities_in_progress.php new file mode 100644 index 00000000..775d996d --- /dev/null +++ b/report/activities_in_progress/lang/en/assessfreqreport_activities_in_progress.php @@ -0,0 +1,84 @@ +. + +/** + * Lang file. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['pluginname'] = 'Report - Activities in Progress'; + +$string['tab:name'] = 'Activities in Progress'; + +$string['activities_in_progress:view'] = 'Ability to view the activities in progress report.'; + +$string['settings:chartheading'] = 'Chart settings'; +$string['settings:chartheading_desc'] = 'These settings allow you to configure the the settings used in the charts and graphs'; +$string['settings:notloggedincolor'] = 'Not logged in color'; +$string['settings:notloggedincolor_desc'] = 'Select color to display for not logged in users in charts'; +$string['settings:loggedincolor'] = 'Logged in color'; +$string['settings:loggedincolor_desc'] = 'Select color to display for logged in users in charts'; +$string['settings:inprogresscolor'] = 'In progress color'; +$string['settings:inprogresscolor_desc'] = 'Select color to display for in progress users in charts'; +$string['settings:finishedcolor'] = 'Finished color'; +$string['settings:finishedcolor_desc'] = 'Select color to display for finished users in charts'; +$string['settings:trendcount'] = 'Trend chart limit'; +$string['settings:trendcount_desc'] = 'The trend data is run every minute and can contain a lot of data. +For example an assessment running for 5 days can have 7200 points that can be mapped which can overwhelm the chart. +This setting specifies the number of points that will be evenly plotted on the graph'; +$string['settings:graphsheading'] = 'Graph settings'; +$string['settings:graphsheading_desc'] = 'Specify the graph settings for each graph report'; + +$string['filter:selectassessment'] = 'Select assessment type'; +$string['filter:closeapply'] = 'Close and apply'; +$string['filter:header'] = 'Filters'; +$string['filter:submit'] = 'Filter'; +$string['filter:hours0'] = 'Now'; +$string['filter:hours1'] = '1 Hour'; +$string['filter:hours4'] = '4 Hours'; +$string['filter:hours8'] = '8 Hours'; +$string['filter:hoursahead'] = 'Hours ahead'; +$string['filter:hoursbehind'] = 'Hours behind'; + +$string['inprogress'] = 'In progress'; +$string['inprogress:head'] = 'In progress'; + +$string['upcomingchart:head'] = 'Upcoming activities starting'; +$string['upcomingchart:inprogressdatetime'] = '%H:00'; +$string['upcomingchart:activities'] = 'Activities'; +$string['upcomingchart:participants'] = 'Students'; + +$string['summarychart:head'] = 'Participant summary'; +$string['summarychart:participants'] = 'Students'; +$string['summarychart:notloggedin'] = 'Not logged in'; +$string['summarychart:loggedin'] = 'Logged in'; +$string['summarychart:inprogress'] = 'In progress'; +$string['summarychart:finished'] = 'Finished'; + +$string['inprogresstable:head'] = 'Activies in progress'; +$string['inprogresstable:activity'] = 'Activity'; +$string['inprogresstable:course'] = 'Course'; +$string['inprogresstable:timelimit'] = 'Time limit'; +$string['inprogresstable:timeopen'] = 'Time open'; +$string['inprogresstable:timeclose'] = 'Time close'; +$string['inprogresstable:participants'] = 'Participants (Overrides)'; +$string['inprogresstable:dashboard'] = 'Dashboard'; + +$string['report:usage_guidlines'] = ''; diff --git a/report/activities_in_progress/lib.php b/report/activities_in_progress/lib.php new file mode 100644 index 00000000..373555de --- /dev/null +++ b/report/activities_in_progress/lib.php @@ -0,0 +1,86 @@ +. + +/** + * @package assessfreqreport_activities_in_progress + * @copyright 2024 Simon Thornett + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Returns the name of the user preferences as well as the details this plugin uses. + * + * @return array + */ +function assessfreqreport_activities_in_progress_user_preferences() : array { + + $preferences['assessfreqreport_activities_in_progress_modules_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => '[]', + 'type' => PARAM_RAW, + ]; + + $preferences['assessfreqreport_activities_in_progress_table_rows_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => 20, + 'type' => PARAM_INT, + ]; + + $preferences['assessfreqreport_activities_in_progress_table_sort_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => 'name_asc', + 'type' => PARAM_ALPHAEXT, + ]; + + $preferences['assessfreqreport_activities_in_progress_hoursahead_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => 8, + 'type' => PARAM_INT, + ]; + + $preferences['assessfreqreport_activities_in_progress_hoursbehind_preference'] = [ + 'null' => NULL_NOT_ALLOWED, + 'default' => 1, + 'type' => PARAM_INT, + ]; + + return $preferences; +} + +/** + * Renders the user table on the dashboard screen. + * We update the table via ajax. + * + * @param array $args + * @return string $o Form HTML. + */ +function assessfreqreport_activities_in_progress_output_fragment_get_in_progress_table(array $args) : string { + global $PAGE; + + require_capability('assessfreqreport/activities_in_progress:view', $PAGE->context); + + $sortpreference = explode( + '_', + get_user_preferences('assessfreqreport_activities_in_progress_table_sort_preference', 'name_asc') + ); + $data = json_decode($args['data']); + $search = is_null($data->search) ? '' : $data->search; + $sorton = $sortpreference[0]; + $direction = $sortpreference[1]; + + $output = $PAGE->get_renderer('assessfreqreport_activities_in_progress'); + return $output->render_activities_inprogress_table($search, $data->page, $sorton, $direction); +} diff --git a/report/activities_in_progress/settings.php b/report/activities_in_progress/settings.php new file mode 100644 index 00000000..6d1e0359 --- /dev/null +++ b/report/activities_in_progress/settings.php @@ -0,0 +1,65 @@ +. + +/** + * Settings file. + * + * @package assessfreqreport_activities_in_progress + * @author Simon Thornett + * @copyright Catalyst IT, 2024 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +if (!$hassiteconfig) { + return; +} + +// Graph settings. +$settings->add(new admin_setting_heading( + 'assessfreqreport_activities_in_progress/graphsheading', + get_string('settings:graphsheading', 'assessfreqreport_activities_in_progress'), + get_string('settings:graphsheading_desc', 'assessfreqreport_activities_in_progress') +)); + +$settings->add(new admin_setting_configcolourpicker( + 'assessfreqreport_activities_in_progress/notloggedincolor', + get_string('settings:notloggedincolor', 'assessfreqreport_activities_in_progress'), + get_string('settings:notloggedincolor_desc', 'assessfreqreport_activities_in_progress'), + '#8C0010' +)); + +$settings->add(new admin_setting_configcolourpicker( + 'assessfreqreport_activities_in_progress/loggedincolor', + get_string('settings:loggedincolor', 'assessfreqreport_activities_in_progress'), + get_string('settings:loggedincolor_desc', 'assessfreqreport_activities_in_progress'), + '#FA8900' +)); + +$settings->add(new admin_setting_configcolourpicker( + 'assessfreqreport_activities_in_progress/inprogresscolor', + get_string('settings:inprogresscolor', 'assessfreqreport_activities_in_progress'), + get_string('settings:inprogresscolor_desc', 'assessfreqreport_activities_in_progress'), + '#875692' +)); + +$settings->add(new admin_setting_configcolourpicker( + 'assessfreqreport_activities_in_progress/finishedcolor', + get_string('settings:finishedcolor', 'assessfreqreport_activities_in_progress'), + get_string('settings:finishedcolor_desc', 'assessfreqreport_activities_in_progress'), + '#1B8700' +)); diff --git a/report/activities_in_progress/styles.css b/report/activities_in_progress/styles.css new file mode 100644 index 00000000..dd9125ba --- /dev/null +++ b/report/activities_in_progress/styles.css @@ -0,0 +1,3 @@ +#local-assessfreq-report-activities-in-progress .chart-area .chart-image { + width: 100% !important; +} \ No newline at end of file diff --git a/report/activities_in_progress/templates/activities-in-progress-table.mustache b/report/activities_in_progress/templates/activities-in-progress-table.mustache new file mode 100644 index 00000000..34d7bd2f --- /dev/null +++ b/report/activities_in_progress/templates/activities-in-progress-table.mustache @@ -0,0 +1,78 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template assessfreqreport_activities_in_progress/activities-in-progress-table + + Report Summary template. + + Example context (json): + { + "activities": 1, + "context": 1 + } +}} +
+ {{{pagingbar}}} +
+
+
+
+ + + + {{^iscourse}} + + {{/iscourse}} + + + + + + + + {{#activities}} + + + {{^iscourse}} + + {{/iscourse}} + + + + + + + {{/activities}} + +
{{#str}} inprogresstable:activity, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:course, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:timeopen, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:timeclose, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:timelimit, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:participants, assessfreqreport_activities_in_progress {{/str}}{{#str}} inprogresstable:dashboard, assessfreqreport_activities_in_progress {{/str}}
+ {{{name}}} + {{courseshortname}}{{earlyopen}}{{lateclose}}{{timelimit}} + {{participants}} ({{overrideparticipants}}) + + {{#dashboardlink}} + + {{#pix}} i/report, core{{/pix}} + + {{/dashboardlink}} +
+
+
+
+
+ {{{pagingbar}}} +
\ No newline at end of file diff --git a/templates/quiz-dashboard-cards.mustache b/report/activities_in_progress/templates/activities-in-progress.mustache similarity index 50% rename from templates/quiz-dashboard-cards.mustache rename to report/activities_in_progress/templates/activities-in-progress.mustache index 22da4c96..f2ce8b59 100644 --- a/templates/quiz-dashboard-cards.mustache +++ b/report/activities_in_progress/templates/activities-in-progress.mustache @@ -15,7 +15,7 @@ along with Moodle. If not, see . }} {{! - @template local_assessfreq/quiz-dashboard-cards + @template assessfreqreport_activities_in_progress/activities-in-progress Report Summary template. @@ -24,22 +24,26 @@ } }} +
+ + {{> assessfreqreport_activities_in_progress/filters}} -
-
-
-

{{# str }} noquiz, local_assessfreq {{/ str }}

+
+
+
{{{progresssummary}}}
+
+
+
+ {{{upcoming}}} +
+
+ {{{summary}}} +
+
+
+
+ {{> local_assessfreq/table}} +
-
-
- {{> local_assessfreq/quiz-summary-card}} - {{> local_assessfreq/quiz-summary-graph}} -
-
- {{> local_assessfreq/quiz-summary-trend-graph}} -
-
- {{> local_assessfreq/quiz-summary-student-table}}
- diff --git a/report/activities_in_progress/templates/filter-hoursahead.mustache b/report/activities_in_progress/templates/filter-hoursahead.mustache new file mode 100644 index 00000000..4f6ae0e8 --- /dev/null +++ b/report/activities_in_progress/templates/filter-hoursahead.mustache @@ -0,0 +1,76 @@ +{{! + 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 . +}} +{{! + @template local_assessfreq/nav-quiz-table-hoursahead-filter + + This template renders the day range selector for the timeline view. + + Example context (json): + {} +}} +
+ + +
diff --git a/report/activities_in_progress/templates/filter-hoursbehind.mustache b/report/activities_in_progress/templates/filter-hoursbehind.mustache new file mode 100644 index 00000000..147f0295 --- /dev/null +++ b/report/activities_in_progress/templates/filter-hoursbehind.mustache @@ -0,0 +1,76 @@ +{{! + 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 . +}} +{{! + @template local_assessfreq/nav-quiz-table-hoursbehind-filter + + This template renders the day range selector for the timeline view. + + Example context (json): + {} +}} +
+ + +
diff --git a/templates/nav-assess-type-filter.mustache b/report/activities_in_progress/templates/filter-type.mustache similarity index 62% rename from templates/nav-assess-type-filter.mustache rename to report/activities_in_progress/templates/filter-type.mustache index 448e9455..5c60ae18 100644 --- a/templates/nav-assess-type-filter.mustache +++ b/report/activities_in_progress/templates/filter-type.mustache @@ -15,27 +15,27 @@ along with Moodle. If not, see . }} {{! - @template local_assessfreq/nav-assess-type-filter + @template assessfreqreport_activities_in_progress/filter-type - This template renders the day range selector for the timeline view. + This template renders the type filter. Example context (json): {} }} -