Implemented self enrol buttons

This commit is contained in:
PMKuipers 2024-03-04 22:39:29 +01:00
parent 61256207be
commit 8382c4a117
26 changed files with 738 additions and 106 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/date-helper",["exports"],(function(_exports){function format_date(d,short){d instanceof Date||(d=new Date(d));let monthformat="short";return!0===short?monthformat="numeric":!1===short&&(monthformat="long"),d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})}function studyplanDates(plan){let earliestStart=null,latestEnd=null,openEnded=!1;for(const ix in plan.pages){const page=plan.pages[ix],s=new Date(page.startdate);if(page.enddate||(openEnded=!0),(!earliestStart||s<earliestStart)&&(earliestStart=s),page.enddate){const e=new Date(page.enddate);(!latestEnd||e>latestEnd)&&(latestEnd=e)}}return{start:earliestStart,end:openEnded?null:latestEnd}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.add_days=function(datestr,days){const date=new Date(datestr);return function(date){const d=new Date(date);let month=""+(d.getMonth()+1),day=""+d.getDate();const year=d.getFullYear();month.length<2&&(month="0"+month);day.length<2&&(day="0"+day);return[year,month,day].join("-")}(new Date(date.getTime()+864e5*days))},_exports.datespaninfo=function(first,last){first instanceof Date||(first=new Date(first));last instanceof Date||(last=new Date(last));first.setHours(0),first.setMinutes(0),first.setSeconds(0),first.setMilliseconds(0),last.setHours(23),last.setMinutes(59),last.setSeconds(59),last.setMilliseconds(999);const dayspan=Math.round((last-first+1)/864e5),years=Math.floor(dayspan/365),ydaysleft=dayspan%365,weeks=Math.floor(ydaysleft/7);return{first:first,last:last,totaldays:dayspan,years:years,weeks:weeks,days:ydaysleft%7,formatted:{first:format_date(first),last:format_date(last)}}},_exports.format_date=format_date,_exports.format_datetime=function(d,short){d instanceof Date||(d=new Date(d));let monthformat="short";!0===short?monthformat="numeric":!1===short&&(monthformat="long");return d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})+" "+d.toLocaleTimeString(document.documentElement.lang,{timeStyle:"short"})},_exports.studyplanDates=studyplanDates,_exports.studyplanPageTiming=function(page){const now=(new Date).getTime(),start=new Date(page.startdate),end=page.enddate?new Date(page.enddate):null;return start<now?end&&now>end?"past":"present":"future"},_exports.studyplanTiming=function(plan){const now=(new Date).getTime(),dates=studyplanDates(plan);return dates.start<now?dates.end&&now>dates.end?"past":"present":"future"}}));
define("local_treestudyplan/util/date-helper",["exports"],(function(_exports){function format_date(d,short){d instanceof Date||("number"==typeof d&&(d*=1e3),d=new Date(d));let monthformat="short";return!0===short?monthformat="numeric":!1===short&&(monthformat="long"),d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})}function studyplanDates(plan){let earliestStart=null,latestEnd=null,openEnded=!1;for(const ix in plan.pages){const page=plan.pages[ix],s=new Date(page.startdate);if(page.enddate||(openEnded=!0),(!earliestStart||s<earliestStart)&&(earliestStart=s),page.enddate){const e=new Date(page.enddate);(!latestEnd||e>latestEnd)&&(latestEnd=e)}}return{start:earliestStart,end:openEnded?null:latestEnd}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.add_days=function(datestr,days){const date=new Date(datestr);return function(date){const d=new Date(date);let month=""+(d.getMonth()+1),day=""+d.getDate();const year=d.getFullYear();month.length<2&&(month="0"+month);day.length<2&&(day="0"+day);return[year,month,day].join("-")}(new Date(date.getTime()+864e5*days))},_exports.datespaninfo=function(first,last){first instanceof Date||(first=new Date(first));last instanceof Date||(last=new Date(last));first.setHours(0),first.setMinutes(0),first.setSeconds(0),first.setMilliseconds(0),last.setHours(23),last.setMinutes(59),last.setSeconds(59),last.setMilliseconds(999);const dayspan=Math.round((last-first+1)/864e5),years=Math.floor(dayspan/365),ydaysleft=dayspan%365,weeks=Math.floor(ydaysleft/7);return{first:first,last:last,totaldays:dayspan,years:years,weeks:weeks,days:ydaysleft%7,formatted:{first:format_date(first),last:format_date(last)}}},_exports.format_date=format_date,_exports.format_datetime=function(d,short){d instanceof Date||("number"==typeof d&&(d*=1e3),d=new Date(d));let monthformat="short";!0===short?monthformat="numeric":!1===short&&(monthformat="long");return d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})+" "+d.toLocaleTimeString(document.documentElement.lang,{timeStyle:"short"})},_exports.studyplanDates=studyplanDates,_exports.studyplanPageTiming=function(page){const now=(new Date).getTime(),start=new Date(page.startdate),end=page.enddate?new Date(page.enddate):null;return start<now?end&&now>end?"past":"present":"future"},_exports.studyplanTiming=function(plan){const now=(new Date).getTime(),dates=studyplanDates(plan);return dates.start<now?dates.end&&now>dates.end?"past":"present":"future"}}));
//# sourceMappingURL=date-helper.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/svgarc",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.svgarcpath=_exports.svgarc=void 0;const cos=Math.cos,sin=Math.sin,π=Math.PI,f_matrix_times=(_ref,_ref2)=>{let[[a,b],[c,d]]=_ref,[x,y]=_ref2;return[a*x+b*y,c*x+d*y]},f_vec_add=(_ref3,_ref4)=>{let[a1,a2]=_ref3,[b1,b2]=_ref4;return[a1+b1,a2+b2]},svgarcpath=(_ref5,_ref6,_ref7,φ)=>{let[cx,cy]=_ref5,[rx,ry]=_ref6,[t1,Δ]=_ref7;Δ%=2*π;const rotMatrix=[[cos(x=φ),-sin(x)],[sin(x),cos(x)]];var x;const[sX,sY]=f_vec_add(f_matrix_times(rotMatrix,[rx*cos(t1),ry*sin(t1)]),[cx,cy]),[eX,eY]=f_vec_add(f_matrix_times(rotMatrix,[rx*cos(t1+Δ),ry*sin(t1+Δ)]),[cx,cy]);return"M "+sX+" "+sY+" A "+[rx,ry,φ/(2*π)*360,Δ>π?1:0,Δ>0?1:0,eX,eY].join(" ")};_exports.svgarcpath=svgarcpath;_exports.svgarc=(_ref8,_ref9,_ref10,φ)=>{let[cx,cy]=_ref8,[rx,ry]=_ref9,[t1,Δ]=_ref10;const path_2wk2r=document.createElementNS("http://www.w3.org/2000/svg","path"),d=svgarcpath([cx,cy],[rx,ry],[t1,Δ],φ);return path_2wk2r.setAttribute("d",d),path_2wk2r}}));
define("local_treestudyplan/util/svgarc",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.svgarcpath=_exports.svgarc=void 0;const cos=Math.cos,sin=Math.sin,π=Math.PI,f_matrix_times=(_ref,_ref2)=>{let[[a,b],[c,d]]=_ref,[x,y]=_ref2;return[a*x+b*y,c*x+d*y]},f_vec_add=(_ref3,_ref4)=>{let[a1,a2]=_ref3,[b1,b2]=_ref4;return[a1+b1,a2+b2]},svgarcpath=(_ref5,_ref6,_ref7,φ)=>{let[cx,cy]=_ref5,[rx,ry]=_ref6,[t1,Δ]=_ref7;Δ%=2*π;const rotMatrix=[[cos(x=φ),-sin(x)],[sin(x),cos(x)]];var x;const[sX,sY]=f_vec_add(f_matrix_times(rotMatrix,[rx*cos(t1),ry*sin(t1)]),[cx,cy]),[eX,eY]=f_vec_add(f_matrix_times(rotMatrix,[rx*cos(t1+Δ),ry*sin(t1+Δ)]),[cx,cy]),fA=Δ>π?1:0,fS=Δ>0?1:0;return isNaN(eY)||isNaN(eX)?"":"M "+sX+" "+sY+" A "+[rx,ry,φ/(2*π)*360,fA,fS,eX,eY].join(" ")};_exports.svgarcpath=svgarcpath;_exports.svgarc=(_ref8,_ref9,_ref10,φ)=>{let[cx,cy]=_ref8,[rx,ry]=_ref9,[t1,Δ]=_ref10;const path_2wk2r=document.createElementNS("http://www.w3.org/2000/svg","path"),d=svgarcpath([cx,cy],[rx,ry],[t1,Δ],φ);return path_2wk2r.setAttribute("d",d),path_2wk2r}}));
//# sourceMappingURL=svgarc.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"svgarc.min.js","sources":["../../src/util/svgarc.js"],"sourcesContent":["/*\nCopyright © 2020 Xah Lee, © 2023 P.M Kuipers\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the “Software”),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\nTHE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\nURL: SVG Circle Arc http://xahlee.info/js/svg_circle_arc.html\n*/\n\nconst cos = Math.cos;\nconst sin = Math.sin;\nconst π = Math.PI;\n\nconst f_matrix_times = (( [[a,b], [c,d]], [x,y]) => [ a * x + b * y, c * x + d * y]);\nconst f_rotate_matrix = (x => [[cos(x),-sin(x)], [sin(x), cos(x)]]);\nconst f_vec_add = (([a1, a2], [b1, b2]) => [a1 + b1, a2 + b2]);\n\n// function modified by pmkuipers for text params\n/**\n * Create svg path text for an arc\n * @param {*} center [cx,cy] center of ellipse\n * @param {*} radius [rx,ry] major minor radius\n * @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.\n * @param {*} φ rotation on the whole, in radian\n * @returns a SVG path element that represent a ellipse. Text describing the arc path in an svg path element\n */\nconst svgarcpath = (([cx,cy],[rx,ry], [t1, Δ], φ ) => {\n Δ = Δ % (2*π);\n const rotMatrix = f_rotate_matrix (φ);\n const [sX, sY] = ( f_vec_add ( f_matrix_times ( rotMatrix, [rx * cos(t1), ry * sin(t1)] ), [cx,cy] ) );\n const [eX, eY] = ( f_vec_add ( f_matrix_times ( rotMatrix, [rx * cos(t1+Δ), ry * sin(t1+Δ)] ), [cx,cy] ) );\n const fA = ( ( Δ > π ) ? 1 : 0 );\n const fS = ( ( Δ > 0 ) ? 1 : 0 );\n return \"M \" + sX + \" \" + sY + \" A \" + [ rx , ry , φ / (2*π) *360, fA, fS, eX, eY ].join(\" \");\n});\n\n/**\n * Create an svg arc element\n * @param {*} center [cx,cy] center of ellipse\n * @param {*} radius [rx,ry] major minor radius\n * @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.\n * @param {*} φ rotation on the whole, in radian\n * @returns a SVG path element that represent a ellipse.\n */\nconst svgarc = (([cx,cy],[rx,ry], [t1, Δ], φ ) => {\n const path_2wk2r = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\n const d = svgarcpath([cx,cy],[rx,ry], [t1, Δ], φ );\n path_2wk2r.setAttribute(\"d\", d);\n return path_2wk2r;\n});\n\nexport {svgarc, svgarcpath};"],"names":["cos","Math","sin","π","PI","f_matrix_times","a","b","c","d","x","y","f_vec_add","a1","a2","b1","b2","svgarcpath","φ","cx","cy","rx","ry","t1","Δ","rotMatrix","sX","sY","eX","eY","join","path_2wk2r","document","createElementNS","setAttribute"],"mappings":"kLAoBMA,IAAMC,KAAKD,IACXE,IAAMD,KAAKC,IACXC,EAAIF,KAAKG,GAETC,eAAkB,oBAAIC,EAAEC,IAAKC,EAAEC,UAAMC,EAAEC,eAAO,CAAEL,EAAII,EAAIH,EAAII,EAAGH,EAAIE,EAAID,EAAIE,EAAzD,EAElBC,UAAa,oBAAEC,GAAIC,WAAMC,GAAIC,gBAAQ,CAACH,GAAKE,GAAID,GAAKE,GAAvC,EAWbC,WAAc,mBAA2BC,SAAzBC,GAAGC,WAAKC,GAAGC,WAAMC,GAAIC,SACvCA,GAAS,EAAErB,QACLsB,UAdoB,CAAC,CAACzB,IAAPU,EAccQ,IAdChB,IAAIQ,IAAK,CAACR,IAAIQ,GAAIV,IAAIU,KAArCA,YAedgB,GAAIC,IAAQf,UAAYP,eAAiBoB,UAAW,CAACJ,GAAKrB,IAAIuB,IAAKD,GAAKpB,IAAIqB,MAAQ,CAACJ,GAAGC,MACxFQ,GAAIC,IAAQjB,UAAYP,eAAiBoB,UAAW,CAACJ,GAAKrB,IAAIuB,GAAGC,GAAIF,GAAKpB,IAAIqB,GAAGC,KAAO,CAACL,GAAGC,WAG5F,KAAOM,GAAK,IAAMC,GAAK,MAAQ,CAAEN,GAAKC,GAAKJ,GAAK,EAAEf,GAAI,IAF7CqB,EAAIrB,EAAM,EAAI,EACdqB,EAAI,EAAM,EAAI,EAC4CI,GAAIC,IAAKC,KAAK,IAAxF,iDAWY,oBAA2BZ,SAAzBC,GAAGC,WAAKC,GAAGC,WAAMC,GAAIC,gBAC7BO,WAAaC,SAASC,gBAAgB,6BAA8B,QACpExB,EAAIQ,WAAW,CAACE,GAAGC,IAAI,CAACC,GAAGC,IAAK,CAACC,GAAIC,GAAIN,UAC/Ca,WAAWG,aAAa,IAAKzB,GACtBsB,UAAP"}
{"version":3,"file":"svgarc.min.js","sources":["../../src/util/svgarc.js"],"sourcesContent":["/*\nCopyright © 2020 Xah Lee, © 2023 P.M Kuipers\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the “Software”),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\nTHE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\nURL: SVG Circle Arc http://xahlee.info/js/svg_circle_arc.html\n*/\n\nconst cos = Math.cos;\nconst sin = Math.sin;\nconst π = Math.PI;\n\nconst f_matrix_times = (( [[a,b], [c,d]], [x,y]) => [ a * x + b * y, c * x + d * y]);\nconst f_rotate_matrix = (x => [[cos(x),-sin(x)], [sin(x), cos(x)]]);\nconst f_vec_add = (([a1, a2], [b1, b2]) => [a1 + b1, a2 + b2]);\n\n// function modified by pmkuipers for text params\n/**\n * Create svg path text for an arc\n * @param {*} center [cx,cy] center of ellipse\n * @param {*} radius [rx,ry] major minor radius\n * @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.\n * @param {*} φ rotation on the whole, in radian\n * @returns a SVG path element that represent a ellipse. Text describing the arc path in an svg path element\n */\nconst svgarcpath = (([cx,cy],[rx,ry], [t1, Δ], φ ) => {\n Δ = Δ % (2*π);\n const rotMatrix = f_rotate_matrix (φ);\n const [sX, sY] = ( f_vec_add ( f_matrix_times ( rotMatrix, [rx * cos(t1), ry * sin(t1)] ), [cx,cy] ) );\n const [eX, eY] = ( f_vec_add ( f_matrix_times ( rotMatrix, [rx * cos(t1+Δ), ry * sin(t1+Δ)] ), [cx,cy] ) );\n const fA = ( ( Δ > π ) ? 1 : 0 );\n const fS = ( ( Δ > 0 ) ? 1 : 0 );\n if ( isNaN(eY) || isNaN(eX) ) {\n return \"\";\n } else {\n return \"M \" + sX + \" \" + sY + \" A \" + [ rx , ry , φ / (2*π) *360, fA, fS, eX, eY ].join(\" \");\n }\n});\n\n/**\n * Create an svg arc element\n * @param {*} center [cx,cy] center of ellipse\n * @param {*} radius [rx,ry] major minor radius\n * @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.\n * @param {*} φ rotation on the whole, in radian\n * @returns a SVG path element that represent a ellipse.\n */\nconst svgarc = (([cx,cy],[rx,ry], [t1, Δ], φ ) => {\n const path_2wk2r = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\n const d = svgarcpath([cx,cy],[rx,ry], [t1, Δ], φ );\n path_2wk2r.setAttribute(\"d\", d);\n return path_2wk2r;\n});\n\nexport {svgarc, svgarcpath};"],"names":["cos","Math","sin","π","PI","f_matrix_times","a","b","c","d","x","y","f_vec_add","a1","a2","b1","b2","svgarcpath","φ","cx","cy","rx","ry","t1","Δ","rotMatrix","sX","sY","eX","eY","fA","fS","isNaN","join","path_2wk2r","document","createElementNS","setAttribute"],"mappings":"kLAoBMA,IAAMC,KAAKD,IACXE,IAAMD,KAAKC,IACXC,EAAIF,KAAKG,GAETC,eAAkB,oBAAIC,EAAEC,IAAKC,EAAEC,UAAMC,EAAEC,eAAO,CAAEL,EAAII,EAAIH,EAAII,EAAGH,EAAIE,EAAID,EAAIE,EAAzD,EAElBC,UAAa,oBAAEC,GAAIC,WAAMC,GAAIC,gBAAQ,CAACH,GAAKE,GAAID,GAAKE,GAAvC,EAWbC,WAAc,mBAA2BC,SAAzBC,GAAGC,WAAKC,GAAGC,WAAMC,GAAIC,SACvCA,GAAS,EAAErB,QACLsB,UAdoB,CAAC,CAACzB,IAAPU,EAccQ,IAdChB,IAAIQ,IAAK,CAACR,IAAIQ,GAAIV,IAAIU,KAArCA,YAedgB,GAAIC,IAAQf,UAAYP,eAAiBoB,UAAW,CAACJ,GAAKrB,IAAIuB,IAAKD,GAAKpB,IAAIqB,MAAQ,CAACJ,GAAGC,MACxFQ,GAAIC,IAAQjB,UAAYP,eAAiBoB,UAAW,CAACJ,GAAKrB,IAAIuB,GAAGC,GAAIF,GAAKpB,IAAIqB,GAAGC,KAAO,CAACL,GAAGC,KAC7FU,GAAUN,EAAIrB,EAAM,EAAI,EACxB4B,GAAUP,EAAI,EAAM,EAAI,SACzBQ,MAAMH,KAAOG,MAAMJ,IACb,GAEA,KAAOF,GAAK,IAAMC,GAAK,MAAQ,CAAEN,GAAKC,GAAKJ,GAAK,EAAEf,GAAI,IAAK2B,GAAIC,GAAIH,GAAIC,IAAKI,KAAK,qDAYhF,oBAA2Bf,SAAzBC,GAAGC,WAAKC,GAAGC,WAAMC,GAAIC,gBAC7BU,WAAaC,SAASC,gBAAgB,6BAA8B,QACpE3B,EAAIQ,WAAW,CAACE,GAAGC,IAAI,CAACC,GAAGC,IAAK,CAACC,GAAIC,GAAIN,UAC/CgB,WAAWG,aAAa,IAAK5B,GACtByB,UAAP"}

View File

@ -9,7 +9,7 @@
import {SimpleLine} from './simpleline/simpleline';
import {get_strings} from 'core/str';
import {load_strings} from './util/string-helper';
import {format_date,studyplanPageTiming,studyplanTiming} from './util/date-helper';
import {format_date, format_datetime, studyplanPageTiming,studyplanTiming} from './util/date-helper';
import {call} from 'core/ajax';
import notification from 'core/notification';
import {svgarcpath} from './util/svgarc';
@ -18,12 +18,37 @@ import Config from 'core/config';
import {ProcessStudyplan, ProcessStudyplanPage, objCopy} from './studyplan-processor';
import TSComponents from './treestudyplan-components';
import {eventTypes as editSwEventTypes} from 'core/edit_switch';
import { premiumenabled, premiumstatus } from "./util/premium";
// Make π available as a constant
const π = Math.PI;
// Gravity value for arrow lines - determines how much a line is pulled in the direction of the start/end before changing direction
const LINE_GRAVITY = 1.3;
/**
* Studyline is not enrollable
* @var int
*/
const ENROLLABLE_NONE = 0;
/**
* Studyline can be enrolled into by the student
* @var int
*/
const ENROLLABLE_SELF = 1;
/**
* Studyline can be enrolled into by specific role(s)
* @var int
*/
const ENROLLABLE_ROLE = 2;
/**
* Studyline can be enrolled by user and/or role
* @var int
*/
const ENROLLABLE_SELF_ROLE = 3;
export default {
install(Vue/*,options*/){
@ -160,6 +185,25 @@ export default {
details: 'studyplan_details',
overview: 'overviewreport:all',
oveviewperiod: 'overviewreport:period'
},
lineheader: {
cannot_enrol: 'line_cannot_enrol',
can_enrol: 'line_can_enrol',
is_enrolled: 'line_is_enrolled',
enrol: 'line_enrol',
enrolled: 'line_enrolled',
notenrolled: 'line_notenrolled',
enrol_question: 'line_enrol_question',
enrollments: 'line_enrollments',
enrollment: 'line_enrollment',
info: 'info@core',
confirm: 'confirm@core',
yes: 'yes@core',
no: 'no@core',
enrolled_in: 'line_enrolled_in',
since: 'since@core',
byname: 'byname@core',
}
});
@ -459,11 +503,11 @@ export default {
},
guestmode: {
type: Boolean,
default() { return false;},
default: false,
},
teachermode: {
type: Boolean,
default() { return false;},
default: false,
},
},
data() {
@ -637,8 +681,12 @@ export default {
></s-studyline-header-heading>
<r-studyline-heading v-for="(line,lineindex) in page.studylines"
:key="line.id"
:teachermode="teachermode"
:guestmode="guestmode"
v-model="page.studylines[lineindex]"
:layers='countLineLayers(line,page)+1'
:studentid="value.userid"
@enrolupdate="page.studylines[lineindex].enrol = $event"
:class=" 't-studyline' + ((lineindex%2==0)?' odd ' :' even ' )
+ ((lineindex==0)?' first ':' ')
+ ((lineindex==page.studylines.length-1)?' last ':' ')"
@ -726,14 +774,28 @@ export default {
type: Object, // Studyline
default: function(){ return {};},
},
guestmode: {
type: Boolean,
default: false,
},
teachermode: {
type: Boolean,
default: false,
},
layers: {
type: Number,
default: 1,
},
studentid: {
type: Number,
},
},
data() {
return {
layerHeights: {}
layerHeights: {},
text: strings.lineheader,
students: null,
can_unenrol: false,
};
},
created() {
@ -742,9 +804,45 @@ export default {
ItemEventBus.$on('lineHeightChange', this.onLineHeightChange);
},
computed: {
enrollable() {
return this.value.enrol.enrollable > ENROLLABLE_NONE;
},
enrollable_self() {
return [ENROLLABLE_SELF,ENROLLABLE_SELF_ROLE].includes(this.value.enrol.enrollable);
},
enrollable_role() {
return [ENROLLABLE_ROLE,ENROLLABLE_SELF_ROLE].includes(this.value.enrol.enrollable);
},
enrolled() {
return this.value.enrol.enrolled?true:false;
},
can_enrol() {
return this.value.enrol.can_enrol?true:false;
},
enrol_question() {
return this.text.enrol_question.replace('{$a}',this.value.name);
},
enrolled_in() {
return this.text.enrolled_in.replace('{$a}',this.value.name);
},
by() {
return this.text.byname.replace('{$a}','');
},
enrolldate() {
return format_datetime(this.value.enrol.enrolled_time);
},
enrolled_students() {
const list = [];
for (const s of this.students) {
if (s.enrolled) {
}
}
return list;
}
},
methods: {
premiumenabled,
onLineHeightChange(lineid){
// All layers for this line have the first slot send an update message on layer height change.
// When one of those updates is received, record the height and recalculate the total height of the
@ -764,15 +862,125 @@ export default {
const heightStyle=`${heightSum}px`;
this.$refs.mainEl.style.height = heightStyle;
}
},
enrol_self() {
const self=this;
call([{
methodname: 'local_treestudyplan_line_enrol_self',
args: { id: self.value.id},
}])[0].then(function(response){
self.$emit('enrolupdate',response);
}).catch(notification.exception);
},
enrol_student(id) {
const self=this;
call([{
methodname: 'local_treestudyplan_line_enrol_students',
args: { id: self.value.id, users: [id] },
}])[0].then(function(response){
self.$emit('enrolupdate',response);
}).catch(notification.exception);
},
unenrol_student(id) {
const self=this;
call([{
methodname: 'local_treestudyplan_line_unenrol_students',
args: { id: self.value.id, users: [id] },
}])[0].then(function(response){
self.$emit('enrolupdate',response);
}).catch(notification.exception);
},
load_students() {
const self=this;
call([{
methodname: 'local_treestudyplan_list_line_enrolled_students',
args: { id: self.value.id },
}])[0].then(function(response){
self.students = response.userinfo;
self.can_unenrol = response.can_unenrol;
}).catch(notification.exception);
}
},
template: `
<div class="r-studyline r-studyline-heading "
:data-studyline="value.id" ref="mainEl"
><div class="r-studyline-handle" :style="'background-color: ' + value.color"></div>
<div class="r-studyline-title">
<div class="r-studyline-title"><div>
<abbr v-b-tooltip.hover :title="value.name">{{ value.shortname }}</abbr>
</div>
<template v-if="premiumenabled() && enrollable">
<template v-if="teachermode">
<a v-if="!can_enrol"
href='#' @click.prevent=""
v-b-modal="'r-enrollments-'+value.id"
:title="text.cannot_enrol"
><i class='fa fa-lock text-danger'></i>&nbsp;{{text.enrollments}}</a>
<a v-else
href='#' @click.prevent=""
v-b-modal="'r-enrollments-'+value.id"
:title="text.can_enrol"
><i class='fa fa-unlock-alt text-success'></i>&nbsp;{{text.enrollments}}</a>
<b-modal
:id="'r-enrollments-'+value.id"
ok-only
scrollable
>
<table>
<tr v-if="students == null"><td>
<div class="spinner-border spinner-border-sm text-info" role="status"></div>
</td></tr>
<template v-if="students && can_enrol">
<tr v-for="student in students">
</template>
<template v-else-if="students">
</template>
</table>
</b-modal>
</template>
<template v-else>
<a v-if="!enrolled && !can_enrol"
href='#' @click.prevent=""
v-b-tooltip.focus
:title="text.cannot_enrol"
><i class='fa fa-lock text-danger'></i>&nbsp;{{text.cannot_enrol}}</a>
<a v-else-if="!enrolled && can_enrol"
href='#' @click.prevent=""
v-b-modal="'r-enrol-'+value.id"
:title="text.can_enrol"
><i class='fa fa-unlock-alt text-success'></i>&nbsp;{{text.enrol}}</a>
<a v-else-if="enrolled"
href='#' @click.prevent=""
v-b-modal="'r-enrollment-'+value.id"
:title="text.enrolled"
><i class='fa fa-unlock text-success'></i>&nbsp;{{text.enrolled}}</a>
<b-modal
:id="'r-enrol-'+value.id"
:title="text.confirm"
:ok-title="text.yes"
:cancel-title="text.no"
ok-variant="success"
cancel-variant="danger"
@ok="enrol_self"
>
<p>{{enrol_question}}</p>
</b-modal>
<b-modal
:id="'r-enrollment-'+value.id"
ok-only
:title="text.enrollment"
>
<p>{{enrolled_in}}<p>
<p><b>{{text.since}}</b> {{enrolldate}}<br>
<b>{{by}}</b> {{this.value.enrol.enrolled_by}}</p>
</b-modal>
</template>
</template>
</div></div>
</div>
`,
});

View File

@ -490,14 +490,14 @@ export default {
<p>{{ text.advanced_cascade_cohortsync_desc}}</p>
<p class="mt-2"><b-button
variant="info"
@click="cascade_cohortsync"
@click.prevent="cascade_cohortsync"
>{{ text.advanced_cascade_cohortsync}}</b-button></p>
<h3>{{ text.advanced_bulk_course_timing}}</h3>
<p>{{ text.advanced_bulk_course_timing_desc}}</p>
<p>{{text.currentpage}} <i>{{selectedpage.fullname}}</i></p>
<p class="mt-2"><b-button
variant="info"
@click="bulk_course_timing"
@click.prevent="bulk_course_timing"
>{{ text.advanced_bulk_course_timing}}</b-button></p>
<template v-if="['bistate','tristate'].includes(value.aggregation)">
<h3>{{ text.advanced_force_scale_title}}</h3>
@ -508,7 +508,7 @@ export default {
<b-button
variant="danger"
:disabled="force_scales.selected_scale == null"
@click="force_scales_start"
@click.prevent="force_scales_start"
>{{ text.advanced_force_scale_button}}</b-button>
</p>
<p class="mt-2">
@ -535,26 +535,26 @@ export default {
<h3>{{ text.advanced_backup }}</h3>
<p><b-button
variant="primary"
@click="export_page('json')"
@click.prevent="export_page('json')"
>{{ text.advanced_backup_page }}</b-button>
{{text.currentpage}} <i>{{selectedpage.fullname}}</i></p>
<p><b-button
variant="primary"
@click="export_plan('json')"
@click.prevent="export_plan('json')"
>{{ text.advanced_backup_plan }}</b-button></p>
<h3>{{ text.advanced_restore }}</h3>
<p><b-button
variant="danger"
@click="import_studylines"
@click.prevent="import_studylines"
>{{ text.advanced_restore_lines}}</b-button></p>
<p><b-button
variant="danger"
@click="import_pages"
@click.prevent="import_pages"
>{{ text.advanced_restore_pages }}</b-button></p>
<h3>{{ text.advanced_export }}</h3>
<p><b-button
variant="primary"
@click="export_page('csv')"
@click.prevent="export_page('csv')"
>{{ text.advanced_export_csv_page }}</b-button>
{{text.currentpage}} <i>{{selectedpage.fullname}}</i></p>
</b-tab>
@ -563,12 +563,12 @@ export default {
<p>{{text.currentpage}} <i>{{selectedpage.fullname}}</i></p>
<p><b-button
variant="danger"
@click="purge_studyplanpage"
@click.prevent="purge_studyplanpage"
>{{ text.advanced_purge_page}}</b-button></p>
<p>{{text.advanced_purge_plan_expl}}</p>
<p><b-button
variant="danger"
@click="purge_studyplan"
@click.prevent="purge_studyplan"
>{{ text.advanced_purge_plan}}</b-button></p>
</b-tab>
</b-tabs>
@ -985,11 +985,11 @@ export default {
</b-row>
<b-row class='mt-2'>
<b-col>
<b-button variant='danger' @click="cohortDisassociate()"
<b-button variant='danger' @click.prevent="cohortDisassociate()"
><i class='fa fa-chain-broken'></i>&nbsp;{{text.delete_association}}</b-button>
</b-col>
<b-col>
<b-button variant='success' @click="cohortAssociate()"
<b-button variant='success' @click.prevent="cohortAssociate()"
><i class='fa fa-link'></i>&nbsp;{{text.add_association}}</b-button>
</b-col>
</b-row>
@ -1031,11 +1031,11 @@ export default {
</b-row>
<b-row class='mt-2'>
<b-col>
<b-button variant='danger' @click="userDisassociate()"
<b-button variant='danger' @click.prevent="userDisassociate()"
><i class='fa fa-chain-broken'></i>&nbsp;{{text.delete_association}}</b-button>
</b-col>
<b-col>
<b-button variant='success' @click="userAssociate()"
<b-button variant='success' @click.prevent="userAssociate()"
><i class='fa fa-link'></i>&nbsp;{{text.add_association}}</b-button>
</b-col>
</b-row>
@ -1226,8 +1226,10 @@ export default {
name: '',
shortname: '',
color: '#DDDDDD',
enrollable: 0,
enrolroles: [],
enrol: {
enrollable: 0,
enrolroles: [],
}
},
page: {
id: -1,
@ -1243,8 +1245,10 @@ export default {
name: '',
shortname: '',
color: '#DDDDDD',
enrollable: 0,
enrolroles: [],
enrol: {
enrollable: 0,
enrolroles: [],
}
},
original: {},
availableroles: [],
@ -1406,16 +1410,16 @@ export default {
'shortname': newlineinfo.shortname,
'color': newlineinfo.color,
'sequence': page.studylines.length,
'enrollable': newlineinfo.enrollable,
'enrolroles': newlineinfo.enrolroles
'enrollable': newlineinfo.enrol.enrollable,
'enrolroles': newlineinfo.enrol.enrolroles
}
}])[0].then(function(response){
page.studylines.push(response);
newlineinfo.name = '';
newlineinfo.shortname = '';
newlineinfo.color = "#dddddd";
newlineinfo.enrollable = 0;
newlineinfo.enrolroles = [];
newlineinfo.enrol.enrollable = 0;
newlineinfo.enrol.enrolroles = [];
}).catch(notification.exception);
},
editLineStart(line) {
@ -1434,15 +1438,15 @@ export default {
'name': editedline.name,
'shortname': editedline.shortname,
'color': editedline.color,
'enrollable': editedline.enrollable,
'enrolroles': editedline.enrolroles
'enrollable': editedline.enrol.enrollable,
'enrolroles': editedline.enrol.enrolroles
}
}])[0].then(function(response){
originalline['name'] = response['name'];
originalline['shortname'] = response['shortname'];
originalline['color'] = response['color'];
originalline['enrollable'] = response['enrollable'];
originalline['enrolroles'] = response['enrolroles'];
originalline.name = response.name;
originalline.shortname = response.shortname;
originalline.color = response.color;
originalline.enrol.enrollable = response.enrol.enrollable;
originalline.enrol.enrolroles = response.enrol.enrolroles;
}).catch(notification.exception);
},
deleteLine(page,line) {
@ -1615,7 +1619,7 @@ export default {
><i class='fa fa-gear'></i> {{text.edit$core}}</t-studyplan-edit>
</span>
<span class='control deletable'>
<a v-if='value.pages.length == 0' href='#' @click='deletePlan(value)'
<a v-if='value.pages.length == 0' href='#' @click.prevent='deletePlan(value)'
><i class='text-danger fa fa-trash'></i></a>
</span>
</div>
@ -1768,7 +1772,7 @@ export default {
</div>
</div>
<div v-if="edit.studyline.editmode" class='t-studyline-add ml-2 mt-1'>
<a href="#" v-b-modal="'modal-add-studyline-'+page.id" @click="false;"
<a href="#" v-b-modal="'modal-add-studyline-'+page.id" @click.prevent="false;"
><i class='fa fa-plus'></i>{{ text.studyline_add }}</a>
</div>
<b-modal
@ -1806,7 +1810,7 @@ export default {
<b-row>
<b-col cols="3">{{ text.studyline_enrollable}}</b-col>
<b-col>
<b-form-select v-model="create.studyline.enrollable">
<b-form-select v-model="create.studyline.enrol.enrollable">
<b-form-select-option
v-for="(nr,n) in 4"
:value="n"
@ -1814,11 +1818,11 @@ export default {
</b-form-select>
</b-col>
</b-row>
<b-row v-if='[2,3].includes(create.studyline.enrollable)'>
<b-row v-if='[2,3].includes(create.studyline.enrol.enrollable)'>
<b-col cols="3">{{ text.studyline_enrolroles}}</b-col>
<b-col>
<b-form-select
v-model="create.studyline.enrolroles"
v-model="create.studyline.enrol.enrolroles"
:options="availableroles"
multiple
value-field="id"
@ -1865,7 +1869,7 @@ export default {
<b-row>
<b-col cols="3">{{ text.studyline_enrollable}}</b-col>
<b-col>
<b-form-select v-model="edit.studyline.data.enrollable">
<b-form-select v-model="edit.studyline.data.enrol.enrollable">
<b-form-select-option
v-for="(nr,n) in 4"
:value="n"
@ -1873,11 +1877,11 @@ export default {
</b-form-select>
</b-col>
</b-row>
<b-row v-if='[2,3].includes(edit.studyline.data.enrollable)'>
<b-row v-if='[2,3].includes(edit.studyline.data.enrol.enrollable)'>
<b-col cols="3">{{ text.studyline_enrolroles}}</b-col>
<b-col>
<b-form-select
v-model="edit.studyline.data.enrolroles"
v-model="edit.studyline.data.enrol.enrolroles"
:options="availableroles"
multiple
value-field="id"
@ -2019,10 +2023,10 @@ export default {
<div class='controlbox'>
<template v-if='editable || deletable'>
<span class='control editable' v-if='editable'>
<a href='#' @click='onEdit'><i class='fa fa-pencil'></i></a>
<a href='#' @click.prevent='onEdit'><i class='fa fa-pencil'></i></a>
</span>
<span class='control deletable' v-if='deletable'>
<a v-if='deletable' href='#' @click='onDelete'><i class='text-danger fa fa-trash'></i></a>
<a v-if='deletable' href='#' @click.prevent='onDelete'><i class='text-danger fa fa-trash'></i></a>
</span>
</template>
</div>
@ -2584,7 +2588,7 @@ export default {
></i>
</span>
<span class="mr-1" v-else>
<a href='#' @click="validate_course_period()" class="text-warning"
<a href='#' @click.prevent="validate_course_period()" class="text-warning"
v-b-tooltip.hover.bottomleft :title="text.timing_off"
><i class="fa fa-calendar-times-o"
></i
@ -3070,7 +3074,7 @@ export default {
:data="value"
@dragstart="dragStart"
@dragend="dragEnd"
@click="deleteMode = (value.connections.out.length)?(!deleteMode):false"
@click.prevent="deleteMode = (value.connections.out.length)?(!deleteMode):false"
>
<svg width='5px' height='10px'><rect ry="1px" rx="1px" y="0px" x="0px" height="10px" width="5px"/></svg>
<template v-slot:drag-image="{data}"> <i :id="'t-item-cdrag-'+value.id" class="fa"></i>
@ -3079,7 +3083,7 @@ export default {
<div class="deletebox" v-if="deleteMode && value.connections.out.length > 0"
>
<a v-for="conn in value.connections.out"
@click="deleteLine(conn)"
@click.prevent="deleteLine(conn)"
@mouseenter="highlight(conn)"
@mouseleave="normalize(conn)"
class="t-connection-delete text-danger"
@ -3108,11 +3112,11 @@ export default {
</b-form-group>
<template #modal-footer="{ ok, cancel, hide }" >
<a href='#' @click='deleteItem()' class="text-danger"
<a href='#' @click.prevent='deleteItem()' class="text-danger"
><i class="fa fa-trash"></i>
{{ text.delete }}
</a>
<b-button size="sm" variant="primary" @click="ok()">
<b-button size="sm" variant="primary" @click.prevent="ok()">
{{ text.ok }}
</b-button>
</template>
@ -3146,7 +3150,7 @@ export default {
<b-col md="11">
<b-card-body class="align-items-center">
<i class="fa fa-exclamation"></i> {{text.error}}
<a href='#' @click='$emit("deleterq")' class="text-danger"
<a href='#' @click.prevent='$emit("deleterq")' class="text-danger"
><i class="fa fa-trash"></i></a>
</b-card-body>
</b-col>
@ -3372,11 +3376,11 @@ export default {
position="below"
></s-course-extrafields>
<template #modal-footer="{ ok, cancel, hide }" >
<a href='#' @click='$emit("deleterq")' class="text-danger"
<a href='#' @click.prevent='$emit("deleterq")' class="text-danger"
><i class="fa fa-trash"></i>
{{ text.delete }}
</a>
<b-button size="sm" variant="primary" @click="ok()">
<b-button size="sm" variant="primary" @click.prevent="ok()">
{{ text.ok }}
</b-button>
</template>
@ -3837,11 +3841,11 @@ export default {
</b-col></b-row>
</b-container>
<template #modal-footer="{ ok, cancel, hide }" >
<a href='#' @click='$emit("deleterq")' class="text-danger"
<a href='#' @click.prevent='$emit("deleterq")' class="text-danger"
><i class="fa fa-trash"></i>
{{ text.delete }}
</a>
<b-button size="sm" variant="primary" @click="ok()">
<b-button size="sm" variant="primary" @click.prevent="ok()">
{{ text.ok }}
</b-button>
</template>

View File

@ -693,7 +693,7 @@ export default {
computed: {
lastaccess() {
if (this.student.lastaccess) {
return format_datetime(this.student.lastaccess*1000); // Takes date in milliseconds
return format_datetime(this.student.lastaccess); // Takes date in milliseconds
} else {
return this.text.never;
}

View File

@ -6,6 +6,9 @@
*/
export function format_date(d,short){
if(!(d instanceof Date)){
if (typeof d == 'number') {
d *= 1000; // Convert from seconds to milliseconds.
}
d = new Date(d);
}
@ -28,6 +31,9 @@ export function format_date(d,short){
*/
export function format_datetime(d,short){
if(!(d instanceof Date)){
if (typeof d == 'number') {
d *= 1000; // Convert from seconds to milliseconds.
}
d = new Date(d);
}

View File

@ -42,7 +42,11 @@ const svgarcpath = (([cx,cy],[rx,ry], [t1, Δ], φ ) => {
const [eX, eY] = ( f_vec_add ( f_matrix_times ( rotMatrix, [rx * cos(t1+Δ), ry * sin(t1+Δ)] ), [cx,cy] ) );
const fA = ( ( Δ > π ) ? 1 : 0 );
const fS = ( ( Δ > 0 ) ? 1 : 0 );
return "M " + sX + " " + sY + " A " + [ rx , ry , φ / (2*π) *360, fA, fS, eX, eY ].join(" ");
if ( isNaN(eY) || isNaN(eX) ) {
return "";
} else {
return "M " + sX + " " + sY + " A " + [ rx , ry , φ / (2*π) *360, fA, fS, eX, eY ].join(" ");
}
});
/**

View File

@ -592,4 +592,43 @@ class studentstudyplanservice extends \external_api {
}
}
/***************************
* *
* line_enrol_self *
* *
***************************/
/**
* Parameter description for webservice function get_teaching_page
*/
public static function line_enrol_self_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"id" => new \external_value(PARAM_INT, 'id of specific studyline to enrol into'),
] );
}
/**
* Return value description for webservice function get_teaching_page
*/
public static function line_enrol_self_returns() : \external_description {
return studyline::enrol_info_structure();
}
/**
* Get all or one studyplan the current user is teaching in
* @param int $studyplanid ID of studyplan to retrieve
* @return array
*/
public static function line_enrol_self($id) {
global $USER;
$userid = $USER->id;
$o = studyline::find_by_id($id);
if ($o->can_enrol($userid)) {
$o->enrol($userid);
}
return $o->enrol_info_model($userid);
}
}

View File

@ -190,6 +190,41 @@ class studyline {
return ($this->r->enrollable == self::ENROLLABLE_ROLE || $this->r->enrollable == self::ENROLLABLE_SELF_ROLE);
}
/**
* Whether the current user can enrol a specific user in this line
* @param int|null $user ID or user object for user or null
*/
public function can_enrol($userid=null) : bool {
global $USER;
$plan = $this->studyplan();
if (!empty($userid)){
if ( $plan->has_linked_user(intval($userid))) {
if ( $this->self_enrollable() && $userid == $USER->id ) {
return true;
}
if ( $this->role_enrollable()) {
$context = $plan->context();
foreach ( $this->enrol_roleids() as $rid) {
if (\user_has_role_assignment($USER->id,$rid,$context->id)){
return true;
}
}
}
}
} else {
if ( $this->role_enrollable()) {
$context = $plan->context();
foreach ( $this->enrol_roleids() as $rid) {
if (\user_has_role_assignment($USER->id,$rid,$context->id)){
return true;
}
}
}
}
return false;
}
/**
* Whether this line is enrollable at all
@ -262,8 +297,7 @@ class studyline {
"shortname" => new \external_value(PARAM_TEXT, 'idnumber of studyline'),
"color" => new \external_value(PARAM_TEXT, 'description of studyline'),
"sequence" => new \external_value(PARAM_INT, 'order of studyline'),
"enrollable" => new \external_value(PARAM_INT, 'type of enrollable'),
"enrolroles" => new \external_multiple_structure(new \external_value(PARAM_INT, 'id of role')),
"enrol" => self::enrol_info_structure(),
"slots" => new \external_multiple_structure(
new \external_single_structure([
self::SLOTSET_COURSES => new \external_multiple_structure(
@ -275,6 +309,25 @@ class studyline {
],"Study line editor structure",$value);
}
/**
* Webservice structure for enrolment info
* @param int $value Webservice requirement constant
*/
public static function enrol_info_structure($value = VALUE_REQUIRED) {
return new \external_single_structure([
"enrollable" => new \external_value(PARAM_INT, 'enrol mode (raw)'),
"enrolroles" => new \external_multiple_structure(new \external_value(PARAM_INT, 'id of role')),
"allowedroles" => new \external_multiple_structure(new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of role'),
"name" => new \external_value(PARAM_TEXT, 'name of role'),
])),
"can_enrol" => new \external_value(PARAM_BOOL, 'enrollable by current user',VALUE_OPTIONAL),
"enrolled" => new \external_value(PARAM_BOOL, 'student is enrolled',VALUE_OPTIONAL),
"enrolled_time" => new \external_value(PARAM_INT, 'moment of enrollment',VALUE_OPTIONAL),
"enrolled_by" => new \external_value(PARAM_TEXT, 'Name of enrolling user',VALUE_OPTIONAL),
],"Enrollment info",$value);
}
/**
* Webservice structure for simple info
* @param int $value Webservice requirement constant
@ -286,12 +339,7 @@ class studyline {
"shortname" => new \external_value(PARAM_TEXT, 'idnumber of studyline'),
"color" => new \external_value(PARAM_TEXT, 'description of studyline'),
"sequence" => new \external_value(PARAM_INT, 'order of studyline'),
"enrollable_self" => new \external_value(PARAM_BOOL, 'enrollable by student'),
"enrollable_role" => new \external_value(PARAM_BOOL, 'enrollable by student'),
"enrolroles" => new \external_multiple_structure(new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of role'),
"name" => new \external_value(PARAM_TEXT, 'name of role'),
])),
"enrol" => self::enrol_info_structure(),
],"Study line simple structure",$value);
}
@ -306,9 +354,7 @@ class studyline {
'shortname' => $this->r->shortname,
'color' => $this->r->color,
'sequence' => $this->r->sequence,
'enrollable_self' => $this->self_enrollable(),
'enrollable_role' => $this->role_enrollable(),
'enrolroles' => $this->enrol_roles_model(),
'enrol' => $this->enrol_info_model(),
];
}
@ -335,8 +381,7 @@ class studyline {
'shortname' => $this->r->shortname,
'color' => $this->r->color,
'sequence' => $this->r->sequence,
'enrollable' => $this->r->enrollable,
'enrolroles' => $this->enrol_roleids(),
'enrol' => $this->enrol_info_model(),
'slots' => [],
];
if ($mode == "export") {
@ -345,7 +390,6 @@ class studyline {
unset($model["sequence"]);
}
// TODO: Make this a little nicer.
// Get the number of slots.
// As a safety data integrity measure, if there are any items in a higher slot than currently allowed, .
// Make sure there are enought slots to account for them.
@ -501,12 +545,7 @@ class studyline {
"shortname" => new \external_value(PARAM_TEXT, 'idnumber of studyline'),
"color" => new \external_value(PARAM_TEXT, 'description of studyline'),
"sequence" => new \external_value(PARAM_INT, 'order of studyline'),
"enrollable_self" => new \external_value(PARAM_BOOL, 'enrollable by student'),
"enrollable_role" => new \external_value(PARAM_BOOL, 'enrollable by student'),
"enrol_roles" => new \external_multiple_structure(new \external_single_structure([
"id" => new \external_value(PARAM_INT, 'id of studyline'),
"name" => new \external_value(PARAM_TEXT, 'shortname of studyline'),
])),
"enrol" => self::enrol_info_structure(),
"slots" => new \external_multiple_structure(
new \external_single_structure([
self::SLOTSET_COURSES => new \external_multiple_structure(
@ -518,13 +557,128 @@ class studyline {
], 'Studyline with user info', $value);
}
/**
* Webservice model for user enrolment info
* @param int $userid ID of user to check specific info for
* @return array Webservice data model
*/
public function enrol_info_model($userid=null) {
global $DB,$USER;
$model = [
'enrolroles' => $this->enrol_roleids(),
'allowedroles' => $this->enrol_roles_model(),
'enrollable' => $this->r->enrollable,
];
if (!empty($userid)) {
$r = $DB->get_record('local_treestudyplan_lineuser',[
'line_id' => $this->id(),
'user_id' => $userid,
]);
if (empty($r)) {
$enrolled = false;
$enrolled_time = 0;
$enrolled_by = "";
} else {
$enrolled = boolval($r->enrolled);
$enrolled_time = $r->timeenrolled;
$by = $DB->get_record('user',["id" => $r->enrolledby]);
if (empty($by)) {
$enrolled_by = \get_string("unknownuser","core");
} else {
$enrolled_by = "{$by->firstname} {$by->lastname}";
}
}
$usermodel = [
'can_enrol' => $this->can_enrol($userid),
"enrolled" => $enrolled,
"enrolled_time" => $enrolled_time,
"enrolled_by" => $enrolled_by,
];
$model = array_merge($model,$usermodel);
} else {
$model["can_enrol"] = $this->can_enrol();
}
return $model;
}
/**
* Enrol student from this line (if line enrollable)
* NOTE: This function does not check if the current user should be allowed to do this,
* the checks need to be done in the calling webservice / function
* @param int $userid of user to enrol
*/
public function enrol($userid) {
global $DB,$USER;
if ($this->r->enrollable > self::ENROLLABLE_NONE) {
$r = $DB->get_record("local_treestudyplan_lineuser",
["line_id" => $this->id(),
"user_id" => $userid]);
if ($r) {
// Registration already exists, check if enrol is false and update accordingly.
if (! boolval($r->enrolled)) {
$r->enrolled = 1;
$r->timeenrolled = time();
$r->enrolledby = $USER->id;
$DB->update_record("local_treestudyplan_lineuser",$r);
}
// Otherwise, ignore the request.
} else {
// Insert new record.
$r = new \stdClass;
$r->enrolled = 1;
$r->line_id = $this->id();
$r->user_id = $userid;
$r->timeenrolled = time();
$r->enrolledby = $USER->id;
$DB->insert_record("local_treestudyplan_lineuser",$r);
}
}
}
/**
* Unenrol student from this line (if line enrollable)
* NOTE: This function does not check if the current user should be allowed to do this,
* the checks need to be done in the calling webservice / function
* @param int $userid of user to unenrol
*/
public function unenrol($userid) {
global $DB,$USER;
if ($this->r->enrollable > self::ENROLLABLE_NONE) {
// Check if an enrollment line exist.
$r = $DB->get_record("local_treestudyplan_lineuser",
["line_id" => $this->id(),
"user_id" => $userid]);
if ($r) {
// Registration already exists, check if enrolled is true and update accordingly.
if (boolval($r->enrolled)) {
$r->enrolled = 0;
$r->timeenrolled = time(); // Regi
$r->enrolledby = $USER->id;
$DB->update_record("local_treestudyplan_lineuser",$r);
}
// Otherwise, ignore the request.
}
// Otherwise, no action is needed.
}
}
/**
* Webservice model for user info
* @param int $userid ID of user to check specific info for
* @return array Webservice data model
*/
public function user_model($userid) {
// TODO: Integrate this function into generate_model() for ease of maintenance.
global $DB;
@ -534,9 +688,7 @@ class studyline {
'shortname' => $this->r->shortname,
'color' => $this->r->color,
'sequence' => $this->r->sequence,
'enrollable_self' => $this->self_enrollable(),
'enrollable_role' => $this->role_enrollable(),
'enrol_roles' => $this->enrol_roles_model(),
'enrol' => $this->enrol_info_model($userid),
'slots' => [],
];

View File

@ -1944,5 +1944,147 @@ class studyplanservice extends \external_api {
}
/***************************
* *
* line_enrol_students *
* *
***************************/
/**
* Parameter description for webservice function get_teaching_page
*/
public static function line_enrol_students_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"id" => new \external_value(PARAM_INT, 'id of specific studyline to enrol into'),
"users" => new \external_multiple_structure(new \external_value(PARAM_INT),'list of user ids'),
] );
}
/**
* Return value description for webservice function get_teaching_page
*/
public static function line_enrol_students_returns() : \external_description {
return success::structure();
}
/**
* Get all or one studyplan the current user is teaching in
* @param int $studyplanid ID of studyplan to retrieve
* @return array
*/
public static function line_enrol_students($id,$users) {
global $USER;
$userid = $USER->id;
$o = studyline::find_by_id($id);
/* NOTE: Perhaps the additional check for the view permission is not needed
since there is already a check on roles going on....
*/
$context = $o->context();
webservicehelper::require_capabilities(self::CAP_VIEW, $context);
foreach ($users as $userid) {
if ($o->can_enrol($userid)) {
$o->enrol($userid);
}
}
return success::success()->model();
}
/***************************
* *
* line_unenrol_students *
* *
***************************/
/**
* Parameter description for webservice function get_teaching_page
*/
public static function line_unenrol_students_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"id" => new \external_value(PARAM_INT, 'id of specific studyline to enrol into'),
"users" => new \external_multiple_structure(new \external_value(PARAM_INT),'list of user ids'),
] );
}
/**
* Return value description for webservice function get_teaching_page
*/
public static function line_unenrol_students_returns() : \external_description {
return success::structure();
}
/**
* Get all or one studyplan the current user is teaching in
* @param int $studyplanid ID of studyplan to retrieve
* @return array
*/
public static function line_unenrol_students($id,$users) {
global $USER;
$userid = $USER->id;
$o = studyline::find_by_id($id);
/* NOTE: Perhaps the additional check for the view permission is not needed
since there is already a check on roles going on....
*/
$context = $o->context();
webservicehelper::require_capabilities('local/treestudyplan:lineunenrol', $context);
foreach ($users as $userid) {
$o->unenrol($userid);
}
return success::success()->model();
}
/***************************
* *
* line_enrol_students *
* *
***************************/
/**
* Parameter description for webservice function get_teaching_page
*/
public static function list_line_enrolled_students_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"id" => new \external_value(PARAM_INT, 'id of specific studyline to list for'),
] );
}
/**
* Return value description for webservice function get_teaching_page
*/
public static function list_line_enrolled_students_returns() : \external_description {
return new \external_single_structure([
"userinfo" => new \external_multiple_structure(studyline::enrol_info_structure()),
"can_unenrol" => new \external_value(PARAM_BOOL,"True if the requesting user can unenrol students"),
]);
}
/**
* Get all or one studyplan the current user is teaching in
* @param int $studyplanid ID of studyplan to retrieve
* @return array
*/
public static function list_line_enrolled_students($id) {
$o = studyline::find_by_id($id);
$context = $o->context();
webservicehelper::require_capabilities(self::CAP_VIEW, $context);
$list = [];
$p = $o->studyplan();
foreach( $p->find_linked_userids() as $userid) {
$list[] = $o->enrol_info_model($userid);
}
return [
"userinfo" => $list,
"can_unenrol" => \has_capability('local/treestudyplan:lineunenrol', $context),
];
}
}

View File

@ -891,8 +891,10 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.features-treestudyplan .t-item-course .card-body,
.features-treestudyplan .r-item-competency .card-body {
padding: 3px;
padding-left: 7px;
padding-right: 7px;
padding-left: 5px;
padding-right: 5px;
margin-top: 2px;
margin-bottom: 2px;
}
.path-local-treestudyplan .r-item-invalid .card-body,
.path-local-treestudyplan .t-item-invalid .card-body,
@ -1030,8 +1032,8 @@ body.path-local-treestudyplan .editmode-switch-form > * {
font-size: 21px;
vertical-align: middle;
float: right;
margin-right: 2px;
margin-top: 2px;
top: -1px;
position: relative;
}
.path-local-treestudyplan .r-progress-circle-popup,
.features-treestudyplan .r-progress-circle-popup {

View File

@ -68,4 +68,13 @@ $capabilities = [
),
],
'local/treestudyplan:lineunenrol' => [
'riskbitmask' => RISK_PERSONAL ,
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array(
'manager' => CAP_ALLOW
),
],
];

View File

@ -734,4 +734,40 @@ $functions = [
'capabilities' => 'local/treestudyplan:editstudyplan',
'loginrequired' => true,
],
'local_treestudyplan_line_enrol_self' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'line_enrol_self', // External function name.
'description' => 'Enroll yourself in a study line',
'type' => 'write', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_line_enrol_students' => [ // Web service function name.
'classname' => '\local_treestudyplan\studyplanservice', // Class containing the external function.
'methodname' => 'line_enrol_students', // External function name.
'description' => 'Enroll a student in a study line',
'type' => 'write', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_line_unenrol_students' => [ // Web service function name.
'classname' => '\local_treestudyplan\studyplanservice', // Class containing the external function.
'methodname' => 'line_unenrol_students', // External function name.
'description' => 'Enrol a student in a study line',
'type' => 'write', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:lineunenrol',
'loginrequired' => true,
],
'local_treestudyplan_list_line_enrolled_students' => [ // Web service function name.
'classname' => '\local_treestudyplan\studyplanservice', // Class containing the external function.
'methodname' => 'list_line_enrolled_students', // External function name.
'description' => 'Show students enrolled in an enrollable study line',
'type' => 'write', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
];

View File

@ -169,12 +169,14 @@ $string["studyline_edit"] = 'Edit study line';
$string["studyline_editmode"] = 'Edit study lines';
$string["toolbox_toggle"] = 'Insert components and courses';
$string["studyline_remove"] = 'Remove study line';
$string["studyline_confirm_remove"] = 'Are you sure you want to remove studyline {$a}?';
$string["studyline_confirm_remove"] = 'Are you sure you want to remove study line {$a}?';
$string["studyline_name"] = 'Full name';
$string["studyline_name_ph"] = '';
$string["studyline_shortname"] = 'Short name';
$string["studyline_shortname_ph"] = '';
$string["studyline_color"] = 'Background color';
$string["studyline_enrollable"] = 'Registration';
$string["studyline_enrolroles"] = 'Allowed roles';
$string["studyitem_confirm_remove"] = 'Are you sure you want to remove module {$a}?';
$string["editmode_modules_hidden"] = 'Modules hidden in edit mode';
@ -469,3 +471,14 @@ $string["line_enrollable:0"] = 'No registration needed';
$string["line_enrollable:1"] = 'Registration by students themselves.';
$string["line_enrollable:2"] = 'Registration by user with role';
$string["line_enrollable:3"] = 'Registration by students themeselves or user with role';
$string["line_enrol"] = 'Register';
$string["line_enrolled"] = 'Registered';
$string["line_notenrolled"] = 'Not registered';
$string["line_enrol_question"] = 'Do you want to register yourself for {$a}?';
$string["line_enrollments"] = 'Registrations';
$string["line_enrollment"] = 'Registration';
$string["line_cannot_enrol"] = 'You cannot register yourself for this line';
$string["line_can_enrol"] = 'You can register for this line';
$string["line_is_enrolled"] = 'You are registered for this line';
$string["line_enrolled_in"] = 'Registered in {$a}';

View File

@ -175,6 +175,8 @@ $string["studyline_name_ph"] = '';
$string["studyline_shortname"] = 'Korte naam';
$string["studyline_shortname_ph"] = '';
$string["studyline_color"] = 'Achtergrondkleur';
$string["studyline_enrollable"] = 'Inschrijven';
$string["studyline_enrolroles"] = 'Rollen mogen inschrijven';
$string["studyitem_confirm_remove"] = 'Weet je zeker dat je module {$a} wilt verwijderen?';
$string["editmode_modules_hidden"] = 'Modules verborgen tijdens bewerken';
@ -468,4 +470,14 @@ $string["overviewreport:period"] = 'Resultatenoverzicht voor deze periode';
$string["line_enrollable:0"] = 'Geen inschrijving nodig';
$string["line_enrollable:1"] = 'Inschrijving door student zelf';
$string["line_enrollable:2"] = 'Inschrijving door gebruiker met rol';
$string["line_enrollable:3"] = 'Inschrijving door student zelf of gebruiker met rol';
$string["line_enrollable:3"] = 'Inschrijving door student zelf of gebruiker met rol';
$string["line_enrol"] = 'Inschrijven';
$string["line_enrolled"] = 'Ingeschreven';
$string["line_notenrolled"] = 'Niet ingeschreven';
$string["line_enrol_quetsion"] = 'Wil je jezelf inschrijven voor {$a}?';
$string["line_enrollments"] = 'Inschrijvingen';
$string["line_enrollment"] = 'Inschrijving';
$string["line_cannot_enrol"] = 'Je kunt je niet zelf inschrijven voor deze leerlijn';
$string["line_can_enrol"] = 'Je kunt jezelf inschrijven voor deze leerlijn';
$string["line_is_enrolled"] = 'Je bent ingeschreven voor deze leerlijn';
$string["line_enrolled_in"] = 'Ingeschreven in {$a}';

View File

@ -756,8 +756,11 @@
.t-item-course .card-body,
.r-item-competency .card-body {
padding: 3px;
padding-left: 7px;
padding-right: 7px;
padding-left: 5px;
padding-right: 5px;
margin-top: 2px;
margin-bottom: 2px;
}
.r-item-invalid .card-body,
@ -878,8 +881,8 @@
font-size: 21px /*16pt*/;
vertical-align: middle;
float: right;
margin-right: 2px;
margin-top: 2px;
top: -1px;
position: relative;
}
.r-progress-circle-popup{

View File

@ -891,8 +891,10 @@ body.path-local-treestudyplan .editmode-switch-form > * {
.features-treestudyplan .t-item-course .card-body,
.features-treestudyplan .r-item-competency .card-body {
padding: 3px;
padding-left: 7px;
padding-right: 7px;
padding-left: 5px;
padding-right: 5px;
margin-top: 2px;
margin-bottom: 2px;
}
.path-local-treestudyplan .r-item-invalid .card-body,
.path-local-treestudyplan .t-item-invalid .card-body,
@ -1030,8 +1032,8 @@ body.path-local-treestudyplan .editmode-switch-form > * {
font-size: 21px;
vertical-align: middle;
float: right;
margin-right: 2px;
margin-top: 2px;
top: -1px;
position: relative;
}
.path-local-treestudyplan .r-progress-circle-popup,
.features-treestudyplan .r-progress-circle-popup {

View File

@ -22,7 +22,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494).
$plugin->version = 2024022505; // YYYYMMDDHH (year, month, day, iteration).
$plugin->version = 2024030200; // YYYYMMDDHH (year, month, day, iteration).
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11).
$plugin->release = "1.1.5";