Lot of work on student view with multiple pages, plans and plan progress

This commit is contained in:
PMKuipers 2023-11-12 23:49:50 +01:00
parent fe8d8d0a0f
commit a208d0023a
36 changed files with 985 additions and 430 deletions

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/page-myreport",["exports","core/ajax","core/notification","./vue/vue","./report-viewer-components","./treestudyplan-components","./util/debugger","./studyplan-processor","./portal-vue/portal-vue.esm","./bootstrap-vue/bootstrap-vue"],(function(_exports,_ajax,_notification,_vue,_reportViewerComponents,_treestudyplanComponents,_debugger,_studyplanProcessor,_portalVue,_bootstrapVue){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){var type=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"myreport",arg=arguments.length>1?arguments[1]:void 0,app=new _vue.default({el:"#root",data:{studyplans:[]},mounted:function(){var call_method,call_args;"invited"==type?(call_method="local_treestudyplan_get_invited_studyplan",call_args={invitekey:arg}):"other"==type?(call_method="local_treestudyplan_get_user_studyplans",call_args={userid:arg}):"teaching"==type?(call_method="local_treestudyplan_get_teaching_studyplans",call_args={}):(call_method="local_treestudyplan_get_own_studyplan",call_args={}),(0,_ajax.call)([{methodname:call_method,args:call_args}])[0].done((function(response){debug.info("Studyplans:",response);var timingval={future:0,present:1,past:2};response.sort((function(a,b){var timinga=_treestudyplanComponents.default.studyplanTiming(a),timingb=_treestudyplanComponents.default.studyplanTiming(b),t=timingval[timinga]-timingval[timingb];return 0==t&&0==(t=new Date(b.startdate).getTime()-new Date(a.startdate).getTime())&&(t=a.name.localeCompare(b.name)),t})),app.studyplans=(0,_studyplanProcessor.ProcessStudyplans)(response)})).fail(_notification.default.exception)},methods:{}})},_notification=_interopRequireDefault(_notification),_vue=_interopRequireDefault(_vue),_reportViewerComponents=_interopRequireDefault(_reportViewerComponents),_treestudyplanComponents=_interopRequireDefault(_treestudyplanComponents),_debugger=_interopRequireDefault(_debugger),_portalVue=_interopRequireDefault(_portalVue),_bootstrapVue=_interopRequireDefault(_bootstrapVue),_vue.default.use(_reportViewerComponents.default),_vue.default.use(_portalVue.default),_vue.default.use(_bootstrapVue.default);var debug=new _debugger.default("treestudyplan-report")})); define("local_treestudyplan/page-myreport",["exports","./vue/vue","./report-viewer-components","./util/debugger","./portal-vue/portal-vue.esm","./bootstrap-vue/bootstrap-vue"],(function(_exports,_vue,_reportViewerComponents,_debugger,_portalVue,_bootstrapVue){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){let type=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"own",arg1=arguments.length>1?arguments[1]:void 0;new _vue.default({el:"#root",data:{studyplans:[],type:type,invitekey:"invited"==type?arg1:null,userid:"other"==type?arg1:null},methods:{}})},_vue=_interopRequireDefault(_vue),_reportViewerComponents=_interopRequireDefault(_reportViewerComponents),_debugger=_interopRequireDefault(_debugger),_portalVue=_interopRequireDefault(_portalVue),_bootstrapVue=_interopRequireDefault(_bootstrapVue),_vue.default.use(_reportViewerComponents.default),_vue.default.use(_portalVue.default),_vue.default.use(_bootstrapVue.default);new _debugger.default("treestudyplan-report")}));
//# sourceMappingURL=page-myreport.min.js.map //# sourceMappingURL=page-myreport.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"page-myreport.min.js","sources":["../src/page-myreport.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"off\" */\n/*eslint no-trailing-spaces: \"off\" */\n/*eslint no-console: \"off\" */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n// You can call it anything you like\n\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\n\nimport Vue from './vue/vue';\n\nimport RVComponents from './report-viewer-components';\nVue.use(RVComponents);\nimport TSComponents from './treestudyplan-components';\n\nimport Debugger from './util/debugger';\n\nimport {ProcessStudyplans} from './studyplan-processor';\n\n\nimport PortalVue from './portal-vue/portal-vue.esm';\nVue.use(PortalVue);\nimport BootstrapVue from './bootstrap-vue/bootstrap-vue';\nVue.use(BootstrapVue);\n\nlet debug = new Debugger(\"treestudyplan-report\");\n\n/**\n * Initialize the Page\n * @param {string} type Type of page to show\n * @param {Object} arg Arguments passed\n */\n export function init(type=\"myreport\",arg) {\n let app = new Vue({\n el: '#root',\n data: {\n \"studyplans\": [],\n },\n mounted() {\n let call_method;\n let call_args;\n if(type == \"invited\"){\n call_method = 'local_treestudyplan_get_invited_studyplan';\n call_args = {\"invitekey\": arg};\n }\n else if(type == \"other\"){\n call_method = 'local_treestudyplan_get_user_studyplans';\n call_args = {\"userid\": arg};\n }\n else if(type == \"teaching\"){\n call_method = 'local_treestudyplan_get_teaching_studyplans';\n call_args = {};\n }\n else{\n call_method = 'local_treestudyplan_get_own_studyplan';\n call_args = {};\n }\n call([{\n methodname: call_method,\n args: call_args\n }])[0].done(function(response){\n debug.info(\"Studyplans:\",response);\n const timingval = { future: 0, present: 1, past: 2, };\n response.sort((a,b) => {\n const timinga = TSComponents.studyplanTiming(a);\n const timingb = TSComponents.studyplanTiming(b);\n\n let t = timingval[timinga] - timingval[timingb];\n if(t == 0){\n // sort by start date if timing is equal\n t = new Date(b.startdate).getTime() - new Date(a.startdate).getTime();\n\n if (t == 0) {\n // sort by name if timing is equal\n t = a.name.localeCompare(b.name);\n }\n }\n return t;\n });\n app.studyplans = ProcessStudyplans(response);\n }).fail(notification.exception);\n\n },\n\n methods: {\n\n },\n });\n\n}\n\n"],"names":["type","arg","app","Vue","el","data","mounted","call_method","call_args","methodname","args","done","response","debug","info","timingval","future","present","past","sort","a","b","timinga","TSComponents","studyplanTiming","timingb","t","Date","startdate","getTime","name","localeCompare","studyplans","fail","notification","exception","methods","use","RVComponents","PortalVue","BootstrapVue","Debugger"],"mappings":"ikBAmCsBA,4DAAK,WAAWC,2CAC9BC,IAAM,IAAIC,aAAI,CACdC,GAAI,QACJC,KAAM,YACY,IAElBC,uBACQC,YACAC,UACO,WAARR,MACCO,YAAc,4CACdC,UAAY,WAAcP,MAEd,SAARD,MACJO,YAAc,0CACdC,UAAY,QAAWP,MAEX,YAARD,MACJO,YAAc,8CACdC,UAAY,KAGZD,YAAc,wCACdC,UAAY,mBAEX,CAAC,CACFC,WAAYF,YACZG,KAAMF,aACN,GAAGG,MAAK,SAASC,UACjBC,MAAMC,KAAK,cAAcF,cACnBG,UAAY,CAAEC,OAAQ,EAAGC,QAAS,EAAGC,KAAM,GACjDN,SAASO,MAAK,SAACC,EAAEC,OACPC,QAAUC,iCAAaC,gBAAgBJ,GACvCK,QAAUF,iCAAaC,gBAAgBH,GAEzCK,EAAIX,UAAUO,SAAWP,UAAUU,gBAC/B,GAALC,GAIU,IAFTA,EAAI,IAAIC,KAAKN,EAAEO,WAAWC,UAAY,IAAIF,KAAKP,EAAEQ,WAAWC,aAIxDH,EAAIN,EAAEU,KAAKC,cAAcV,EAAES,OAG5BJ,KAEXxB,IAAI8B,YAAa,yCAAkBpB,aACpCqB,KAAKC,sBAAaC,YAIzBC,QAAS,yYAxEbC,IAAIC,8CASJD,IAAIE,iCAEJF,IAAIG,2BAEJ3B,MAAQ,IAAI4B,kBAAS"} {"version":3,"file":"page-myreport.min.js","sources":["../src/page-myreport.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"off\" */\n/*eslint no-trailing-spaces: \"off\" */\n/*eslint no-console: \"off\" */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n// You can call it anything you like\n\nimport Vue from './vue/vue';\n\nimport RVComponents from './report-viewer-components';\nVue.use(RVComponents);\n\nimport Debugger from './util/debugger';\n\nimport PortalVue from './portal-vue/portal-vue.esm';\nVue.use(PortalVue);\nimport BootstrapVue from './bootstrap-vue/bootstrap-vue';\nVue.use(BootstrapVue);\n\nlet debug = new Debugger(\"treestudyplan-report\");\n\n/**\n * Initialize the Page\n * @param {string} type Type of page to show\n * @param {Object} arg1 Argument1 as passed\n */\n export function init(type=\"own\",arg1) {\n let app = new Vue({\n el: '#root',\n data: {\n \"studyplans\": [],\n \"type\": type,\n \"invitekey\": (type==\"invited\")?arg1:null,\n \"userid\": (type==\"other\")?arg1:null,\n },\n methods: {\n\n },\n });\n\n}\n\n"],"names":["type","arg1","Vue","el","data","methods","use","RVComponents","PortalVue","BootstrapVue","Debugger"],"mappings":"2aA4BsBA,4DAAK,MAAMC,4CACnB,IAAIC,aAAI,CACdC,GAAI,QACJC,KAAM,YACY,QACNJ,eACY,WAANA,KAAiBC,KAAK,YACnB,SAAND,KAAeC,KAAK,MAEnCI,QAAS,2QAzBbC,IAAIC,8CAKJD,IAAIE,iCAEJF,IAAIG,uBAEI,IAAIC,kBAAS"}

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/studyplan-processor",["exports"],(function(_exports){function ProcessStudyplan(studyplan){for(var ip in studyplan.pages){ProcessStudyplanPage(studyplan.pages[ip])}return studyplan}function ProcessStudyplanPage(page){var connections={};for(var il in page.studylines){var line=page.studylines[il];for(var is in line.slots){var slot=line.slots[is];if(void 0!==slot.courses)for(var ic in slot.courses){var itm=slot.courses[ic];for(var idx in itm.connections.in){var conn=itm.connections.in[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}for(var _idx in itm.connections.out){var _conn=itm.connections.out[_idx];_conn.id in connections?itm.connections[_idx]=connections[_conn.id]:connections[_conn.id]=_conn}}if(void 0!==slot.filters)for(var ix in slot.filters){var _itm=slot.filters[ix];for(var _idx2 in _itm.connections.in){var _conn2=_itm.connections.in[_idx2];_conn2.id in connections?_itm.connections[_idx2]=connections[_conn2.id]:connections[_conn2.id]=_conn2}for(var _idx3 in _itm.connections.out){var _conn3=_itm.connections.out[_idx3];_conn3.id in connections?_itm.connections[_idx3]=connections[_conn3.id]:connections[_conn3.id]=_conn3}}}}return page}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ProcessStudyplan=ProcessStudyplan,_exports.ProcessStudyplanPage=ProcessStudyplanPage,_exports.ProcessStudyplans=function(studyplans){for(var isx in studyplans){ProcessStudyplan(studyplans[isx])}return studyplans},_exports.objCopy=function(target,source,fields){for(var ix in fields){var field=fields[ix];target[field]=source[field]}},_exports.transportItem=function(target,source,identifier,param){param||(param="value");var item,itemindex;for(var ix in source)if(source[ix][param]==identifier){item=source[ix],itemindex=ix;break}item&&(target.push(item),source.splice(itemindex,1))}})); define("local_treestudyplan/studyplan-processor",["exports"],(function(_exports){function ProcessStudyplan(studyplan){for(const ip in studyplan.pages){ProcessStudyplanPage(studyplan.pages[ip])}return studyplan}function ProcessStudyplanPage(page){let connections={};for(const il in page.studylines){const line=page.studylines[il];for(const is in line.slots){const slot=line.slots[is];if(void 0!==slot.courses)for(const ic in slot.courses){const itm=slot.courses[ic];for(const idx in itm.connections.in){const conn=itm.connections.in[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}for(const idx in itm.connections.out){const conn=itm.connections.out[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}}if(void 0!==slot.filters)for(const ix in slot.filters){const itm=slot.filters[ix];for(const idx in itm.connections.in){const conn=itm.connections.in[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}for(const idx in itm.connections.out){const conn=itm.connections.out[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}}}}return page}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ProcessStudyplan=ProcessStudyplan,_exports.ProcessStudyplanPage=ProcessStudyplanPage,_exports.ProcessStudyplans=function(studyplans){for(const isx in studyplans){ProcessStudyplan(studyplans[isx])}return studyplans},_exports.objCopy=function(target,source,fields){null==fields&&(fields=Object.getOwnPropertyNames(source));for(const ix in fields){const field=fields[ix];target[field]=source[field]}return target},_exports.transportItem=function(target,source,identifier,param){param||(param="value");let item,itemindex;for(const ix in source)if(source[ix][param]==identifier){item=source[ix],itemindex=ix;break}item&&(target.push(item),source.splice(itemindex,1))}}));
//# sourceMappingURL=studyplan-processor.min.js.map //# sourceMappingURL=studyplan-processor.min.js.map

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));var monthformat="short";return short&&(monthformat="numeric"),d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.add_days=function(datestr,days){var date=new Date(datestr);return function(date){var d=new Date(date),month=""+(d.getMonth()+1),day=""+d.getDate(),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);var 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})); 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 short&&(monthformat="numeric"),d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})}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.studyplanDates=function(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}}}));
//# sourceMappingURL=date-helper.min.js.map //# sourceMappingURL=date-helper.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -7,20 +7,13 @@
// Put this file in path/to/plugin/amd/src // Put this file in path/to/plugin/amd/src
// You can call it anything you like // You can call it anything you like
import {call} from 'core/ajax';
import notification from 'core/notification';
import Vue from './vue/vue'; import Vue from './vue/vue';
import RVComponents from './report-viewer-components'; import RVComponents from './report-viewer-components';
Vue.use(RVComponents); Vue.use(RVComponents);
import TSComponents from './treestudyplan-components';
import Debugger from './util/debugger'; import Debugger from './util/debugger';
import {ProcessStudyplans} from './studyplan-processor';
import PortalVue from './portal-vue/portal-vue.esm'; import PortalVue from './portal-vue/portal-vue.esm';
Vue.use(PortalVue); Vue.use(PortalVue);
import BootstrapVue from './bootstrap-vue/bootstrap-vue'; import BootstrapVue from './bootstrap-vue/bootstrap-vue';
@ -31,60 +24,17 @@ let debug = new Debugger("treestudyplan-report");
/** /**
* Initialize the Page * Initialize the Page
* @param {string} type Type of page to show * @param {string} type Type of page to show
* @param {Object} arg Arguments passed * @param {Object} arg1 Argument1 as passed
*/ */
export function init(type="myreport",arg) { export function init(type="own",arg1) {
let app = new Vue({ let app = new Vue({
el: '#root', el: '#root',
data: { data: {
"studyplans": [], "studyplans": [],
"type": type,
"invitekey": (type=="invited")?arg1:null,
"userid": (type=="other")?arg1:null,
}, },
mounted() {
let call_method;
let call_args;
if(type == "invited"){
call_method = 'local_treestudyplan_get_invited_studyplan';
call_args = {"invitekey": arg};
}
else if(type == "other"){
call_method = 'local_treestudyplan_get_user_studyplans';
call_args = {"userid": arg};
}
else if(type == "teaching"){
call_method = 'local_treestudyplan_get_teaching_studyplans';
call_args = {};
}
else{
call_method = 'local_treestudyplan_get_own_studyplan';
call_args = {};
}
call([{
methodname: call_method,
args: call_args
}])[0].done(function(response){
debug.info("Studyplans:",response);
const timingval = { future: 0, present: 1, past: 2, };
response.sort((a,b) => {
const timinga = TSComponents.studyplanTiming(a);
const timingb = TSComponents.studyplanTiming(b);
let t = timingval[timinga] - timingval[timingb];
if(t == 0){
// sort by start date if timing is equal
t = new Date(b.startdate).getTime() - new Date(a.startdate).getTime();
if (t == 0) {
// sort by name if timing is equal
t = a.name.localeCompare(b.name);
}
}
return t;
});
app.studyplans = ProcessStudyplans(response);
}).fail(notification.exception);
},
methods: { methods: {
}, },

View File

@ -15,6 +15,7 @@ import notification from 'core/notification';
import {svgarcpath} from './util/svgarc'; import {svgarcpath} from './util/svgarc';
import Debugger from './util/debugger'; import Debugger from './util/debugger';
import Config from 'core/config'; import Config from 'core/config';
import {ProcessStudyplan, ProcessStudyplanPage, objCopy} from './studyplan-processor';
import TSComponents from './treestudyplan-components'; import TSComponents from './treestudyplan-components';
@ -23,12 +24,11 @@ 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 // 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; const LINE_GRAVITY = 1.3;
export default { export default {
install(Vue/*,options*/){ install(Vue/*,options*/){
Vue.use(TSComponents); Vue.use(TSComponents);
let debug = new Debugger("treestudyplan-viewer"); let debug = new Debugger("treestudyplan-viewer");
debug.warn(Config);
let lastCaller = null; let lastCaller = null;
/** /**
* Scroll current period into view * Scroll current period into view
@ -49,6 +49,13 @@ export default {
} }
let strings = load_strings({ let strings = load_strings({
report: {
loading: "loadinghelp@core",
studyplan_past: "studyplan_past",
studyplan_present: "studyplan_present",
studyplan_future: "studyplan_future",
back: "back",
},
invalid: { invalid: {
error: 'error', error: 'error',
}, },
@ -136,7 +143,8 @@ export default {
startdate: 'studyplan_startdate', startdate: 'studyplan_startdate',
enddate: 'studyplan_enddate', enddate: 'studyplan_enddate',
description: 'studyplan_description', description: 'studyplan_description',
duration: 'studyplan_duration' duration: 'studyplan_duration',
details: 'studyplan_details',
} }
}); });
@ -247,51 +255,180 @@ export default {
Vue.component('r-report', { Vue.component('r-report', {
props: { props: {
value: { invitekey: {
type: Array, type: String,
default() { return null;},
}, },
guestmode: { userid: {
type: Boolean, type: Number,
default: false, default() { return 0;},
}, },
teachermode: { type: {
type: Boolean, type: String,
default: false, default() { return "own";},
} }
}, },
data() { data() {
return { return {
text: strings.report,
studyplans: {
past: [],
present: [],
future: [],
},
selectedstudyplan: null, selectedstudyplan: null,
loadingstudyplan: false,
loading: true,
}; };
}, },
computed: { computed: {
displayedstudyplan(){ teachermode() {
if(this.selectedstudyplan){ return (this.type =="teaching");
return this.selectedstudyplan; },
} else if(this.value && this.value.length > 0){ guestmode() {
return this.value[0]; return (this.type == "invited");
},
verified_type() {
if (! ["invited","other","teaching","own"].includes(this.type)) {
return "own";
} else { } else {
return null; return this.type;
}
} }
}, },
studyplancount() {
return this.studyplans.past.length + this.studyplans.present.length + this.studyplans.future.length;
}
},
updated() {
},
mounted() {
this.loadStudyplans();
},
methods: { methods: {
selectstudyplan(plan) { call_args(o) {
this.selectedstudyplan = plan; const args = {};
if (typeof o == 'object' && !Array.isArray(o) && o !== null) {
objCopy(args,o);
}
if(this.verified_type == "invited") {
args["invitekey"] = this.invitekey;
} else if(this.verified_type == "other") {
args["userid"] = this.userid;
}
return args;
}, },
loadStudyplans() {
const self = this;
this.loading = true;
call([{
methodname: `local_treestudyplan_list_${this.verified_type}_studyplans`,
args: this.call_args(),
}])[0].done(function(response){
debug.info("Studyplans:",response);
const plans = { future: [], present: [], past: [], };
for (const ix in response) {
const plan = response[ix];
const timing = TSComponents.studyplanTiming(plan);
plans[timing].push(plan);
}
for (const ix in plans) {
debug.info(ix, plans[ix]);
plans[ix].sort((a,b) => {
const t = new Date(b.startdate).getTime() - new Date(a.startdate).getTime();
if (t == 0) {
// sort by name if timing is equal
t = a.name.localeCompare(b.name);
}
return t;
});
}
self.studyplans = plans;
self.loading = false;
if (self.studyplans.present.length == 1) {
// Directly show the current study plan if it's the only one
self.selectStudyplan(self.studyplans.present[0]);
} else {
// If there is but a single studyplan, select it anyway, even if it is not current...
if (this.studyplancount == 1) {
if(self.studyplans.future.lengh > 0) {
self.selectStudyplan(self.studyplans.future[0]);
} else {
self.selectStudyplan(self.studyplans.past[0]);
}
}
}
}).fail(notification.exception);
},
selectStudyplan(plan) {
const self = this;
this.loadingstudyplan = true;
call([{
methodname: `local_treestudyplan_get_${this.verified_type}_studyplan`,
args: this.call_args({
studyplanid: plan.id,
}),
}])[0].done(function(response){
debug.info("Detailed studyplan:",response);
self.selectedstudyplan = ProcessStudyplan(response);
self.loadingstudyplan = false;
}).fail(notification.exception);
},
deselectStudyplan() {
this.selectedstudyplan = null;
this.loadStudyplans(); // Reload the list of studyplans.
}
}, },
template: ` template: `
<div class='t-studyplan-container r-report-tabs'> <div class='t-studyplan-container r-report-tabs'>
<b-list-group horizontal> <div v-if='loading' class="vue-loader spinner-border text-primary" role="status"></div>
<b-list-group-item <template v-else>
v-for="(studyplan,planindex) in value" <div v-if='!loadingstudyplan && selectedstudyplan'>
:key="studyplan.id" <div class="mb-2">
:active="displayedstudyplan && studyplan.id == displayedstudyplan.id" <a href='#' @click='deselectStudyplan'><h5 class="d-inline"><i class="fa fa-chevron-left"></i> {{ text.back }}</h5></a>
button <h4 class="d-inline ml-3">{{ selectedstudyplan.name}}
@click="selectstudyplan(studyplan)" <s-studyplan-details
>{{studyplan.name}}</b-list-group-item> class="mb-2"
</b-list-group> size="sm"
<r-studyplan v-if='displayedstudyplan' v-model='displayedstudyplan' :guestmode='guestmode' :teachermode='teachermode'></r-studyplan> v-model="selectedstudyplan"
v-if="selectedstudyplan.description"
><i class='fa fa-info-circle'></i></s-studyplan-details></h4>
</div>
<r-studyplan
v-model='selectedstudyplan'
:guestmode='guestmode'
:teachermode='teachermode'
></r-studyplan>
</div>
<div v-else-if='loadingstudyplan' class="vue-loader spinner-border text-primary" role="status">
<span class="sr-only">{{ text.loading }}</span>
</div>
<div v-else class='t-studyplan-notselected'>
<template v-for="timing in ['present', 'past', 'future']">
<template v-if="studyplans[timing].length > 0">
<h4>{{ text["studyplan_"+timing]}}:</h4>
<b-card-group deck>
<s-studyplan-card
v-for='(studyplan, planindex) in studyplans[timing]'
:key='studyplan.id'
v-model='studyplans[timing][planindex]'
open
@open='selectStudyplan(studyplan)'
></s-studyplan-card>
</b-card-group>
</template>
</template>
</div>
</template>
</div> </div>
`, `,
}); });
@ -415,7 +552,7 @@ export default {
:title-item-class="'s-studyplanpage-tab '+ page.timing" :title-item-class="'s-studyplanpage-tab '+ page.timing"
><template #title> ><template #title>
<span v-b-tooltip.hover :title='page.fullname'>{{page.shortname}}</span> <span v-b-tooltip.hover :title='page.fullname'>{{page.shortname}}</span>
<a href="#" v-b-modal="'studyplanpage-info-'+page.id" variant='info' <a href="#" v-b-modal="'studyplanpage-info-'+page.id" class='text-info'
v-if='pageindex == selectedpageindex' v-if='pageindex == selectedpageindex'
><i class='fa fa-info-circle'></i></a> ><i class='fa fa-info-circle'></i></a>
</template> </template>

View File

@ -1,16 +1,20 @@
/*eslint no-console: "off"*/ /*eslint no-console: "off"*/
/** /**
* Copy fields from one object to another * Copy fields from one object to another
* @param {Object} target The target to move to * @param {Object} target The target to copy to
* @param {Object} source The source to move from * @param {Object} source The source to copy from
* @param {Array} fields The field names to copy * @param {Array} fields The field names to copy
* @returns {Object} The map with strings loaded in * @returns {Object} Reference to target
*/ */
export function objCopy(target,source,fields){ export function objCopy(target,source,fields){
if ( fields === undefined || fields === null) {
fields = Object.getOwnPropertyNames(source);
}
for(const ix in fields) { for(const ix in fields) {
const field = fields[ix]; const field = fields[ix];
target[field] = source[field]; target[field] = source[field];
} }
return target;
} }
/** /**

View File

@ -5,20 +5,23 @@
// Put this file in path/to/plugin/amd/src // Put this file in path/to/plugin/amd/src
import {load_strings} from './util/string-helper'; import {load_strings} from './util/string-helper';
import {format_date} from './util/date-helper'; import {format_date, studyplanDates} from './util/date-helper';
export default { export default {
studyplanTiming(a) { studyplanTiming(plan) {
const now = new Date().getTime(); const now = new Date().getTime();
let timing = 'future';
if(new Date(a.startdate).getTime() < now){ const dates = studyplanDates(plan);
if(a.enddate && now > new Date(a.enddate).getTime()) {
timing = 'past'; if(dates.start < now){
if( dates.end && now > dates.end) {
return 'past';
} else { } else {
timing = 'present'; return 'present';
} }
} else {
return 'future';
} }
return timing;
}, },
install(Vue/*,options*/){ install(Vue/*,options*/){
@ -29,6 +32,10 @@ export default {
idnumber: "studyplan_idnumber", idnumber: "studyplan_idnumber",
description: "studyplan_description", description: "studyplan_description",
completed: "completed", completed: "completed",
details: "studyplan_details",
},
details: {
details: "studyplan_details",
} }
}); });
// Create new eventbus for interaction between item components // Create new eventbus for interaction between item components
@ -63,16 +70,12 @@ export default {
} }
return timing; return timing;
}, },
startdate(){ dates(){
return format_date(this.value.pages[0].startdate); const dates = studyplanDates(this.value);
}, return {
enddate(){ start: format_date(dates.start),
if(this.value.pages[0].enddate){ end: (dates.end)?format_date(dates.end):this.text.noenddate,
return format_date(this.value.pages[0].enddate); };
}
else {
return this.text.noenddate;
}
}, },
width_completed() { width_completed() {
if(this.value.progress) { if(this.value.progress) {
@ -139,22 +142,13 @@ export default {
</div> </div>
<slot></slot> <slot></slot>
<template #footer> <template #footer>
<span :class="'t-timing-'+timing" v-html="startdate + ' - '+ enddate"></span> <span :class="'t-timing-'+timing" v-html="dates.start + ' - '+ dates.end"></span>
<span class="s-studyplan-card-buttons"> <span class="s-studyplan-card-buttons">
<slot name='footer'></slot> <slot name='footer'></slot>
<template v-if='value.description'> <s-studyplan-details
<b-button variant="primary" v-b-modal="'modal-description-'+value.id" v-model="value"
><i class='fa fa-info-circle'></i> {{ text.description }}</b-button> v-if="value.description"
<b-modal ><i class='fa fa-info-circle'></i></s-studyplan-details>
:title="value.name"
scrollable
centered
ok-only
size="xl"
:id="'modal-description-'+value.id"
><span v-html="value.description"></span>
</b-modal>
</template>
<b-button style="float:right;" v-if='open' variant='primary' <b-button style="float:right;" v-if='open' variant='primary'
@click.prevent='onOpenClick($event)'>{{ text.open }}</b-button> @click.prevent='onOpenClick($event)'>{{ text.open }}</b-button>
</span> </span>
@ -271,5 +265,59 @@ export default {
}); });
Vue.component('s-studyplan-details', {
props: {
value: {
type: Object,
},
variant: {
type: String,
default() { return "info"; }
},
pill: {
type: Boolean,
default() { return false; }
},
size: {
type: String,
default() { return "";}
}
},
data() {
return {
text: strings.details,
};
},
template: `
<span>
<b-button
:size="size"
:pill="pill"
:variant="variant"
v-b-modal="'modal-description-'+value.id"
><slot><i class='fa fa-info-circle'></i>
{{ text.details}}</slot>
</b-button>
<b-modal
:title="value.name"
scrollable
centered
ok-only
content-class="s-studyplan-details"
:id="'modal-description-'+value.id"
>
<b-container>
<b-row>
<b-col cols="4"><img :src='value.icon'></b-col>
<b-col cols="8" class="pl-1">
<span v-html="value.description"></span>
</b-col>
</b-row>
</b-container>
</b-modal>
</span>
`,
});
} }
}; };

View File

@ -92,3 +92,36 @@ export function add_days(datestr,days) {
const newdate = new Date(date.getTime() + (days * 86400000) ); // Add n days in ms. const newdate = new Date(date.getTime() + (days * 86400000) ); // Add n days in ms.
return dateYYYYMMDD(newdate); return dateYYYYMMDD(newdate);
} }
/**
* Determine start and end dates of a studyplan
* @param {*} plan
* @returns
*/
export function studyplanDates(plan) {
let earliestStart = null;
let latestEnd = null;
let openEnded = false;
for(const ix in plan.pages) {
const page = plan.pages[ix];
const s = new Date(page.startdate);
if (! page.enddate) {
openEnded = true; // One of the pages has no end date set...
}
if (!earliestStart || s < earliestStart) {
earliestStart = s;
}
if ( page.enddate ) {
const e = new Date(page.enddate);
if (!latestEnd || e > latestEnd) {
latestEnd = e;
}
}
}
return {
start: earliestStart,
end: (openEnded)?null:latestEnd,
};
}

View File

@ -300,14 +300,12 @@ class courseinfo {
* @param bool $usecorecompletioninfo Whether to use corecompletion info instead of custom selected gradables * @param bool $usecorecompletioninfo Whether to use corecompletion info instead of custom selected gradables
* @return array Webservice data model * @return array Webservice data model
*/ */
public function editor_model(studyitem $studyitem = null, $usecorecompletioninfo = false) { public function editor_model($usecorecompletioninfo = false) {
global $DB;
$contextinfo = new contextinfo($this->context); $contextinfo = new contextinfo($this->context);
$timing = $this->timing(); $timing = $this->timing();
if (isset($studyitem)) { if (isset($this->studyitem)) {
$numenrolled = $this->count_enrolled_students($studyitem->studyline()->studyplan()->find_linked_userids()); $numenrolled = $this->count_enrolled_students($this->studyitem->studyline()->studyplan()->find_linked_userids());
} else { } else {
$numenrolled = 0; $numenrolled = 0;
} }
@ -330,18 +328,18 @@ class courseinfo {
'numenrolled' => $numenrolled, 'numenrolled' => $numenrolled,
]; ];
if (isset($this->studyitem)) {
if (!$usecorecompletioninfo) { if (!$usecorecompletioninfo) {
$gradables = gradeinfo::list_course_gradables($this->course, $studyitem ? $studyitem : $this->studyitem ); $gradables = gradeinfo::list_course_gradables($this->course, $this->studyitem );
foreach ($gradables as $gradable) { foreach ($gradables as $gradable) {
$info['grades'][] = $gradable->editor_model($studyitem ? $studyitem : $this->studyitem); $info['grades'][] = $gradable->editor_model($this->studyitem);
} }
} else if (isset($this->studyitem)) { } else {
$cc = new corecompletioninfo($this->course, $this->studyitem); $cc = new corecompletioninfo($this->course, $this->studyitem);
$studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids(); $studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
$info['completion'] = $cc->editor_model($studentlist); $info['completion'] = $cc->editor_model($studentlist);
} }
}
return $info; return $info;
} }
@ -441,6 +439,7 @@ class courseinfo {
'enrolled' => $this->is_enrolled_student($userid), 'enrolled' => $this->is_enrolled_student($userid),
]; ];
if (isset($this->studyitem)) {
if (!$usecorecompletioninfo) { if (!$usecorecompletioninfo) {
$gradables = gradeinfo::list_studyitem_gradables($this->studyitem); $gradables = gradeinfo::list_studyitem_gradables($this->studyitem);
foreach ($gradables as $gi) { foreach ($gradables as $gi) {
@ -450,7 +449,7 @@ class courseinfo {
$cc = new corecompletioninfo($this->course, $this->studyitem); $cc = new corecompletioninfo($this->course, $this->studyitem);
$info['completion'] = $cc->user_model($userid); $info['completion'] = $cc->user_model($userid);
} }
}
return $info; return $info;
} }

49
classes/debug.php Normal file
View File

@ -0,0 +1,49 @@
<?php
// This file is part of the Studyplan plugin for Moodle
//
// 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 <https://www.gnu.org/licenses/>.
/**
* Ease of debugging tools
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace local_treestudyplan;
defined('MOODLE_INTERNAL') || die();
class debug {
/**
* @param $object Object to dump
* @param $filename File to write to
* @return any The object
*/
public static function &dump(&$object,$filename="/tmp/debug.log") {
$f = fopen($filename,"a+");
fwrite($f,\json_encode($object,JSON_PRETTY_PRINT )."\n");
fclose($f);
return $object;
}
/**
* @param $object Object to dump
* @param $filename File to write to
* @return any The object
*/
public static function write($text,$filename="/tmp/debug.log") {
$f = fopen($filename,"a+");
fwrite($f,$text."\n");
fclose($f);
}
}

View File

@ -69,7 +69,6 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
*/ */
protected function initialize($configstr) { protected function initialize($configstr) {
// First initialize with the defaults. // First initialize with the defaults.
foreach (["thresh_excellent", "thresh_good", "thresh_completed", "thresh_progress", ] as $key) { foreach (["thresh_excellent", "thresh_good", "thresh_completed", "thresh_progress", ] as $key) {
$val = intval(get_config('local_treestudyplan', "bistate_{$key}")); $val = intval(get_config('local_treestudyplan', "bistate_{$key}"));
if ($val >= 0 && $val <= 100) { if ($val >= 0 && $val <= 100) {
@ -174,13 +173,22 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
$totalrequired = 0; $totalrequired = 0;
$requiredmet = 0; $requiredmet = 0;
$minprogress = ($this->accept_pending_as_submitted) ? completion::PENDING : completion::PROGRESS; $minprogress = ($this->cfg()->accept_pending_as_submitted) ? completion::PENDING : completion::PROGRESS;
foreach ($completions as $index => $c) { foreach ($completions as $index => $c) {
$completed += ($c >= completion::COMPLETED) ? 1 : 0; $completed += ($c >= completion::COMPLETED) ? 1 : 0;
$progress += ($c >= $minprogress) ? 1 : 0; $progress += ($c >= $minprogress) ? 1 : 0;
$failed += ($c <= completion::FAILED) ? 1 : 0; $failed += ($c <= completion::FAILED) ? 1 : 0;
if (in_array($index,$required)) {
// Not using count($required) to prevent nonexistant indices in the required list from messing things up.
$totalrequired += 1;
if ($c >= completion::COMPLETED) {
$requiredmet += 1;
}
}
} }
$started = $progress + $failed; $started = $progress + $failed;
$allrequiredmet = ($requiredmet >= $totalrequired); $allrequiredmet = ($requiredmet >= $totalrequired);
@ -193,11 +201,11 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
if ($total == 0) { if ($total == 0) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
} }
if ($fractioncompleted >= $this->thresh_excellent && $allrequiredmet) { if ($fractioncompleted >= $this->cfg()->thresh_excellent && $allrequiredmet) {
return completion::EXCELLENT; return completion::EXCELLENT;
} else if ($fractioncompleted >= $this->thresh_good && $allrequiredmet) { } else if ($fractioncompleted >= $this->cfg()->thresh_good && $allrequiredmet) {
return completion::GOOD; return completion::GOOD;
} else if ($fractioncompleted >= $this->thresh_completed && $allrequiredmet) { } else if ($fractioncompleted >= $this->cfg()->thresh_completed && $allrequiredmet) {
return completion::COMPLETED; return completion::COMPLETED;
} else if ($started == 0) { } else if ($started == 0) {
return completion::INCOMPLETE; return completion::INCOMPLETE;
@ -238,7 +246,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
// Combine the aquired completions into one. // Combine the aquired completions into one.
$result = self::aggregate_binary_goals($completions, $required); $result = self::aggregate_binary_goals($completions, $required);
if ($this->use_failed && $result == completion::PROGRESS && $coursefinished) { if ($this->cfg()->use_failed && $result == completion::PROGRESS && $coursefinished) {
return completion::FAILED; return completion::FAILED;
} else { } else {
return $result; return $result;

View File

@ -34,21 +34,7 @@ use \local_treestudyplan\completion;
class core_aggregator extends \local_treestudyplan\aggregator { class core_aggregator extends \local_treestudyplan\aggregator {
/** @var bool */ /** @var bool */
public const DEPRECATED = false; public const DEPRECATED = false;
/** @var array */
private $agcfg = null;
/**
* Retrieve or initialize current config object
* @return stdClass
*/
private function cfg() {
if (empty($this->agcfg)) {
$this->agcfg = (object)[
'accept_pending_as_submitted' => false, // Also count ungraded but submitted .
];
}
return $this->agcfg;
}
/** /**
* Create new instance of aggregation method * Create new instance of aggregation method
@ -64,22 +50,7 @@ class core_aggregator extends \local_treestudyplan\aggregator {
* @param string $configstr Aggregation configuration string * @param string $configstr Aggregation configuration string
*/ */
protected function initialize($configstr) { protected function initialize($configstr) {
// First initialize with the defaults.
foreach (["accept_pending_as_submitted"] as $key) {
$this->cfg()->$key = boolval(get_config('local_treestudyplan', "bistate_{$key}"));
}
// Next, decode json.
$config = \json_decode($configstr, true);
if (is_array($config)) {
// Copy all valid config settings to this item.
foreach (["accept_pending_as_submitted"] as $key) {
if (array_key_exists($key, $config)) {
$this->cfg()->$key = boolval($config[$key]);
}
}
}
} }
/** /**
@ -87,9 +58,7 @@ class core_aggregator extends \local_treestudyplan\aggregator {
* @return string Configuration string * @return string Configuration string
*/ */
public function config_string() { public function config_string() {
return json_encode([ return "";
"accept_pending_as_submitted" => $this->cfg()->accept_pending_as_submitted,
]);
} }
/** /**
@ -153,11 +122,7 @@ class core_aggregator extends \local_treestudyplan\aggregator {
if ($completion->is_enabled() && $completion->is_tracked_user($userid)) { if ($completion->is_enabled() && $completion->is_tracked_user($userid)) {
if ($completion->is_course_complete($userid)) { if ($completion->is_course_complete($userid)) {
// Now, the trick is to determine what constitutes excellent and good completion.... // Completed is completed
// TODO: Determine excellent and maybe good completion.
// Option: Use course end grade to determine that...
// Probably needs a config value in the aggregator....
return completion::COMPLETED; return completion::COMPLETED;
} else { } else {
// Check if the course is over or not, if it is over, display failed. // Check if the course is over or not, if it is over, display failed.

View File

@ -37,6 +37,7 @@ class studentstudyplanservice extends \external_api {
* @var string * @var string
*/ */
const CAP_VIEWOTHER = "local/treestudyplan:viewuserreports"; const CAP_VIEWOTHER = "local/treestudyplan:viewuserreports";
/************************ /************************
* * * *
* list_user_studyplans * * list_user_studyplans *
@ -74,57 +75,12 @@ class studentstudyplanservice extends \external_api {
foreach ($studyplans as $studyplan) { foreach ($studyplans as $studyplan) {
// Only include studyplans in the context the user has permissions for. // Only include studyplans in the context the user has permissions for.
if (webservicehelper::has_capabilities(self::CAP_VIEWOTHER, $studyplan->context(), false)) { if (webservicehelper::has_capabilities(self::CAP_VIEWOTHER, $studyplan->context(), false)) {
$list[] = $studyplan->simple_model(); $list[] = $studyplan->simple_model($userid);
} }
} }
return $list; return $list;
} }
/************************
* *
* get_user_studyplans *
* *
************************/
/**
* Parameter description for webservice function get_user_studyplans
*/
public static function get_user_studyplans_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"userid" => new \external_value(PARAM_INT, 'id of user'),
] );
}
/**
* Return value description for webservice function get_user_studyplans
*/
public static function get_user_studyplans_returns() : \external_description {
return new \external_multiple_structure(
studyplan::user_structure()
);
}
/**
* Get the full studyplans for a specific user
* @param int $userid ID of user to check specific info for
* @return array
*/
public static function get_user_studyplans($userid) {
global $CFG, $DB;
$studyplans = studyplan::find_for_user($userid);
$map = [];
foreach ($studyplans as $studyplan) {
// Only include studyplans in the context the user has permissions for.
if (webservicehelper::has_capabilities(self::CAP_VIEWOTHER, $studyplan->context(), false)) {
$map[] = $studyplan->user_model($userid);
}
}
return $map;
}
/************************ /************************
* * * *
* get_user_studyplan * * get_user_studyplan *
@ -160,7 +116,49 @@ class studentstudyplanservice extends \external_api {
$studyplan = studyplan::find_by_id($studyplanid); $studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEWOTHER, $studyplan->context()); webservicehelper::require_capabilities(self::CAP_VIEWOTHER, $studyplan->context());
if ($studyplan->has_linked_user($userid)) { if ($studyplan->exist_for_user($userid)) {
return $studyplan->user_model($userid);
} else {
return null;
}
}
/************************
* *
* get_user_page *
* *
************************/
/**
* Parameter description for webservice function get_user_page
*/
public static function get_user_page_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"userid" => new \external_value(PARAM_INT, 'id of user'),
"pageid" => new \external_value(PARAM_INT, 'id of specific studyplan to provide'),
] );
}
/**
* Return value description for webservice function get_user_page
*/
public static function get_user_page_returns() : \external_description {
return studyplan::user_structure();
}
/**
* Get a specific studyplan page for a given user
* @param int $userid ID of user to check specific info for
* @param int $pageid ID of studyplan to view
* @return array
*/
public static function get_user_page($userid, $pageid) {
global $CFG, $DB;
$studyplan = studyplan::find_by_id($pageid);
webservicehelper::require_capabilities(self::CAP_VIEWOTHER, $studyplan->context());
if ($studyplan->exist_for_user($userid)) {
return $studyplan->user_model($userid); return $studyplan->user_model($userid);
} else { } else {
return null; return null;
@ -169,35 +167,35 @@ class studentstudyplanservice extends \external_api {
/**************************** /****************************
* * * *
* get_invited_studyplan * * list_invited_studyplan *
* * * *
****************************/ ****************************/
/** /**
* Parameter description for webservice function get_invited_studyplan * Parameter description for webservice function list_invited_studyplan
*/ */
public static function get_invited_studyplan_parameters() : \external_function_parameters { public static function list_invited_studyplans_parameters() : \external_function_parameters {
return new \external_function_parameters( [ return new \external_function_parameters( [
"invitekey" => new \external_value(PARAM_RAW, 'invite key'), "invitekey" => new \external_value(PARAM_RAW, 'invite key'),
] ); ] );
} }
/** /**
* Return value description for webservice function get_invited_studyplan * Return value description for webservice function list_invited_studyplan
*/ */
public static function get_invited_studyplan_returns() : \external_description { public static function list_invited_studyplans_returns() : \external_description {
return new \external_multiple_structure( return new \external_multiple_structure(
studyplan::user_structure() studyplan::simple_structure()
); );
} }
/** /**
* Get studyplan based on invite key * List available studyplans based on invite key
* @param int $invitekey Invitation key * @param int $invitekey Invitation key
* @return array * @return array
*/ */
public static function get_invited_studyplan($invitekey) { public static function list_invited_studyplans($invitekey) {
global $CFG, $DB; global $DB;
$invite = $DB->get_record_select( $invite = $DB->get_record_select(
"local_treestudyplan_invit", "local_treestudyplan_invit",
@ -214,12 +212,126 @@ class studentstudyplanservice extends \external_api {
$userid = $invite->user_id; $userid = $invite->user_id;
$map = []; $list = [];
$studyplans = studyplan::find_for_user($userid); $studyplans = studyplan::find_for_user($userid);
foreach ($studyplans as $studyplan) { foreach ($studyplans as $studyplan) {
$map[] = $studyplan->user_model($userid); // Only include studyplans in the context the user has permissions for.
if (webservicehelper::has_capabilities(self::CAP_VIEWOTHER, $studyplan->context(), false)) {
$list[] = $studyplan->simple_model($userid);
}
}
return $list;
}
/****************************
* *
* get_invited_studyplan *
* *
****************************/
/**
* Parameter description for webservice function get_invited_studyplan
*/
public static function get_invited_studyplan_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"invitekey" => new \external_value(PARAM_RAW, 'invite key'),
"studyplanid" => new \external_value(PARAM_INT, 'studyplan_id'),
] );
}
/**
* Return value description for webservice function get_invited_studyplan
*/
public static function get_invited_studyplan_returns() : \external_description {
return studyplan::user_structure();
}
/**
* Get studyplan based on invite key and studyplan id
* @param string $invitekey Invitation key
* @param int $studyplanid ID of the studyplan to retrieve
* @return array
*/
public static function get_invited_studyplan($invitekey,$studyplanid) {
global $DB;
$invite = $DB->get_record_select(
"local_treestudyplan_invit",
$DB->sql_compare_text("invitekey"). " = " . $DB->sql_compare_text(":invitekey"),
['invitekey' => $invitekey]
);
if (empty($invite)) {
return [];
}
// Validate context now.
\external_api::validate_context(\context_system::instance());
$studyplan = studyplan::find_by_id($studyplanid);
$userid = $invite->user_id;
if ($studyplan->exist_for_user($userid)) {
return $studyplan->user_model($userid);
} else {
throw new \moodle_exception("Invitation's user is not linked to this studyplan");
}
}
/****************************
* *
* get_invited_page *
* *
****************************/
/**
* Parameter description for webservice function get_invited_studyplan
*/
public static function get_invited_page_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"invitekey" => new \external_value(PARAM_RAW, 'invite key'),
"pageid" => new \external_value(PARAM_INT, 'studyplan page id'),
] );
}
/**
* Return value description for webservice function get_invited_studyplan
*/
public static function get_invited_page_returns() : \external_description {
return studyplanpage::user_structure();
}
/**
* Get studyplan based on invite key and studyplan id
* @param string $invitekey Invitation key
* @param int $pageid ID of the studyplan to retrieve
* @return array
*/
public static function get_invited_page($invitekey,$pageid) {
global $DB;
$invite = $DB->get_record_select(
"local_treestudyplan_invit",
$DB->sql_compare_text("invitekey"). " = " . $DB->sql_compare_text(":invitekey"),
['invitekey' => $invitekey]
);
if (empty($invite)) {
return [];
}
// Validate context now.
\external_api::validate_context(\context_system::instance());
$page = studyplanpage::find_by_id($pageid);
$studyplan = $page->studyplan();
$userid = $invite->user_id;
if ($studyplan->exist_for_user($userid)) {
return $page->user_model($userid);
} else {
throw new \moodle_exception("Invitation's user is not linked to this studyplan");
} }
return $map;
} }
/************************ /************************
@ -255,8 +367,9 @@ class studentstudyplanservice extends \external_api {
$list = []; $list = [];
$studyplans = studyplan::find_for_user($userid); $studyplans = studyplan::find_for_user($userid);
foreach ($studyplans as $studyplan) { foreach ($studyplans as $studyplan) {
$list[] = $studyplan->simple_model(); $list[] = $studyplan->simple_model($userid);
} }
return $list; return $list;
} }
@ -271,7 +384,7 @@ class studentstudyplanservice extends \external_api {
*/ */
public static function get_own_studyplan_parameters() : \external_function_parameters { public static function get_own_studyplan_parameters() : \external_function_parameters {
return new \external_function_parameters( [ return new \external_function_parameters( [
"id" => new \external_value(PARAM_INT, 'id of specific studyplan to provide', VALUE_DEFAULT), "studyplanid" => new \external_value(PARAM_INT, 'id of studyplan to retrieve'),
] ); ] );
} }
@ -279,17 +392,15 @@ class studentstudyplanservice extends \external_api {
* Return value description for webservice function get_own_studyplan * Return value description for webservice function get_own_studyplan
*/ */
public static function get_own_studyplan_returns() : \external_description { public static function get_own_studyplan_returns() : \external_description {
return new \external_multiple_structure( return studyplan::user_structure();
studyplan::user_structure()
);
} }
/** /**
* Get all studyplans for current user or a specific one * Get a studyplans for the current user
* @param int $id Optional id of specific studyplan * @param int $studyplanid Id of specific studyplan
* @return array * @return array
*/ */
public static function get_own_studyplan($id = null) { public static function get_own_studyplan($studyplanid) {
global $USER; global $USER;
// Validate this call in the system context. // Validate this call in the system context.
@ -297,45 +408,79 @@ class studentstudyplanservice extends \external_api {
$userid = $USER->id; $userid = $USER->id;
$studyplans = studyplan::find_for_user($userid); $studyplan = studyplan::find_by_id($studyplanid);
if (isset($id) && $id > 0) { if ($studyplan->exist_for_user($userid)) {
if (isset($studyplans[$id])) { return debug::dump($studyplan->user_model($userid));
$studyplan = $studyplans[$id];
return [$studyplan->user_model($userid)];
} else { } else {
return []; throw new \moodle_exception("You do not have access to this studyplan");
} }
}
/************************
* *
* get_own_page *
* *
************************/
/**
* Parameter description for webservice function get_own_page
*/
public static function get_own_page_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"pageid" => new \external_value(PARAM_INT, 'id of studyplan to retrieve'),
] );
}
/**
* Return value description for webservice function get_own_page
*/
public static function get_own_page_returns() : \external_description {
return studyplan::user_structure();
}
/**
* Get studyplanpage for current user
* @param int $pagid ID of specific studyplan page
* @return array
*/
public static function get_own_page($pageid) {
global $USER;
// Validate this call in the system context.
\external_api::validate_context(\context_system::instance());
$userid = $USER->id;
$page = studyplanpage::find_by_id($pageid);
$studyplan = $page->studyplan();
if ($studyplan->exist_for_user($userid)) {
return $page->user_model($userid);
} else { } else {
$map = []; throw new \moodle_exception("You do not have access to this studyplan page");
foreach ($studyplans as $studyplan) {
$map[] = $studyplan->user_model($userid);
}
return $map;
} }
} }
/*************************** /***************************
* * * *
* get_teaching_studyplans * * list_teaching_studyplans *
* * * *
***************************/ ***************************/
/** /**
* Parameter description for webservice function get_teaching_studyplans * Parameter description for webservice function list_teaching_studyplans
*/ */
public static function get_teaching_studyplans_parameters() : \external_function_parameters { public static function list_teaching_studyplans_parameters() : \external_function_parameters {
return new \external_function_parameters( [ return new \external_function_parameters( [] );
"id" => new \external_value(PARAM_INT, 'id of specific studyplan to provide', VALUE_DEFAULT),
] );
} }
/** /**
* Return value description for webservice function get_teaching_studyplans * Return value description for webservice function list_teaching_studyplans
*/ */
public static function get_teaching_studyplans_returns() : \external_description { public static function list_teaching_studyplans_returns() : \external_description {
return new \external_multiple_structure( return new \external_multiple_structure(
studyplan::editor_structure() studyplan::simple_structure()
); );
} }
@ -344,26 +489,101 @@ class studentstudyplanservice extends \external_api {
* @param int $id Optional specific id of studyplan * @param int $id Optional specific id of studyplan
* @return array * @return array
*/ */
public static function get_teaching_studyplans($id = null) { public static function list_teaching_studyplans() {
global $CFG, $DB, $USER; global $USER;
$userid = $USER->id; $userid = $USER->id;
\external_api::validate_context(\context_system::instance()); \external_api::validate_context(\context_system::instance());
$studyplans = teachingfinder::list_my_plans(); $studyplans = teachingfinder::list_my_plans();
if (isset($id) && $id > 0) { $list = [];
if (isset($studyplans[$id])) {
$studyplan = $studyplans[$id];
return [$studyplan->editor_model($userid)];
} else {
return [];
}
} else {
$map = [];
foreach ($studyplans as $studyplan) { foreach ($studyplans as $studyplan) {
$map[] = $studyplan->editor_model($userid); $list[] = $studyplan->simple_model($userid);
} }
return $map; return $list;
}
/***************************
* *
* get_teaching_studyplan *
* *
***************************/
/**
* Parameter description for webservice function get_teaching_studyplan
*/
public static function get_teaching_studyplan_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"studyplanid" => new \external_value(PARAM_INT, 'id of specific studyplan to provide', VALUE_DEFAULT),
] );
}
/**
* Return value description for webservice function get_teaching_studyplan
*/
public static function get_teaching_studyplan_returns() : \external_description {
return studyplan::editor_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 get_teaching_studyplan($studyplanid) {
global $USER;
$userid = $USER->id;
\external_api::validate_context(\context_system::instance());
$studyplan = studyplan::find_by_id($studyplanid);
if (teachingfinder::am_teaching_studyplan($studyplan)) {
return $studyplan->editor_model();
} else {
throw new \moodle_exception("You are not teaching in this studyplan");
} }
} }
/***************************
* *
* get_teaching_page *
* *
***************************/
/**
* Parameter description for webservice function get_teaching_page
*/
public static function get_teaching_page_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"pageid" => new \external_value(PARAM_INT, 'id of specific studyplan page to provide', VALUE_DEFAULT),
] );
}
/**
* Return value description for webservice function get_teaching_page
*/
public static function get_teaching_page_returns() : \external_description {
return studyplanpage::editor_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 get_teaching_page($pageid) {
global $USER;
$userid = $USER->id;
\external_api::validate_context(\context_system::instance());
$page = studyplanpage::find_by_id($pageid);
$studyplan = $page->studyplan();
if (teachingfinder::am_teaching_studyplan($studyplan)) {
return $page->editor_model();
} else {
throw new \moodle_exception("You are not teaching in this studyplan");
}
}
} }

View File

@ -229,7 +229,7 @@ class studyitem {
if ($mode == "export") { if ($mode == "export") {
$model['course'] = $ci->shortname(); $model['course'] = $ci->shortname();
} else { } else {
$model['course'] = $ci->editor_model($this, $this->aggregator->usecorecompletioninfo()); $model['course'] = $ci->editor_model($this->aggregator->usecorecompletioninfo());
} }
} }

View File

@ -24,6 +24,7 @@ namespace local_treestudyplan;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
require_once($CFG->libdir.'/filelib.php');
/** /**
* Model class for study plan * Model class for study plan
*/ */
@ -159,7 +160,7 @@ class studyplan {
* Return description with all file references resolved * Return description with all file references resolved
*/ */
public function description() { public function description() {
$text = file_rewrite_pluginfile_urls( $text = \file_rewrite_pluginfile_urls(
// The content of the text stored in the database. // The content of the text stored in the database.
$this->r->description, $this->r->description,
// The pluginfile URL which will serve the request. // The pluginfile URL which will serve the request.
@ -222,6 +223,7 @@ class studyplan {
"aggregation_info" => aggregator::basic_structure(), "aggregation_info" => aggregator::basic_structure(),
"pages" => new \external_multiple_structure(studyplanpage::simple_structure(), 'pages'), "pages" => new \external_multiple_structure(studyplanpage::simple_structure(), 'pages'),
"progress" => new \external_value(PARAM_FLOAT,"fraction of completed modules",VALUE_OPTIONAL), "progress" => new \external_value(PARAM_FLOAT,"fraction of completed modules",VALUE_OPTIONAL),
"amteaching" => new \external_value(PARAM_BOOL,"Current user is teaching one or more courses in this studyplan",VALUE_OPTIONAL),
], 'Basic studyplan info', $value); ], 'Basic studyplan info', $value);
} }
@ -229,13 +231,14 @@ class studyplan {
* Webservice model for basic info * Webservice model for basic info
* @return array Webservice data model * @return array Webservice data model
*/ */
public function simple_model() { public function simple_model($userid=null) {
global $USER;
$pages = []; $pages = [];
foreach ($this->pages() as $p) { foreach ($this->pages() as $p) {
$pages[] = $p->simple_model(); $pages[] = $p->simple_model();
} }
return [ $model = [
'id' => $this->r->id, 'id' => $this->r->id,
'name' => $this->r->name, 'name' => $this->r->name,
'shortname' => $this->r->shortname, 'shortname' => $this->r->shortname,
@ -248,9 +251,12 @@ class studyplan {
'aggregation_config' => $this->aggregator->config_string(), 'aggregation_config' => $this->aggregator->config_string(),
'aggregation_info' => $this->aggregator->basic_model(), 'aggregation_info' => $this->aggregator->basic_model(),
'pages' => $pages, 'pages' => $pages,
// Next line is for development debugging only.
//"progress" => (\rand(0,100) / 100),
]; ];
if(isset($userid)) {
$model["progress"] = $this->scanuserprogress($userid);
$model['amteaching'] = teachingfinder::is_teaching_studyplan($this,$userid);
}
return $model;
} }
/** /**
@ -510,13 +516,13 @@ class studyplan {
public static function exist_for_user($userid) : bool { public static function exist_for_user($userid) : bool {
global $DB; global $DB;
$count = 0; $count = 0;
$sql = "SELECT s.* FROM {local_treestudyplan} s $sql = "SELECT COUNT(s.id) FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_cohort} j ON j.studyplan_id = s.id INNER JOIN {local_treestudyplan_cohort} j ON j.studyplan_id = s.id
INNER JOIN {cohort_members} cm ON j.cohort_id = cm.cohortid INNER JOIN {cohort_members} cm ON j.cohort_id = cm.cohortid
WHERE cm.userid = :userid"; WHERE cm.userid = :userid";
$count += $DB->count_records_sql($sql, ['userid' => $userid]); $count += $DB->count_records_sql($sql, ['userid' => $userid]);
$sql = "SELECT s.* FROM {local_treestudyplan} s $sql = "SELECT COUNT(s.id) FROM {local_treestudyplan} s
INNER JOIN {local_treestudyplan_user} j ON j.studyplan_id = s.id INNER JOIN {local_treestudyplan_user} j ON j.studyplan_id = s.id
WHERE j.user_id = :userid"; WHERE j.user_id = :userid";
$count += $DB->count_records_sql($sql, ['userid' => $userid]); $count += $DB->count_records_sql($sql, ['userid' => $userid]);
@ -624,6 +630,7 @@ class studyplan {
} }
// Now average it out over the amount of pages // Now average it out over the amount of pages
$progress = $progress / count($pages); $progress = $progress / count($pages);
return $progress;
} }
/** /**

View File

@ -24,6 +24,7 @@ namespace local_treestudyplan;
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
require_once($CFG->libdir.'/filelib.php');
/** /**
* Studyplan page management class * Studyplan page management class
*/ */
@ -147,7 +148,7 @@ class studyplanpage {
* @return string * @return string
*/ */
public function description() { public function description() {
$text = file_rewrite_pluginfile_urls( $text = \file_rewrite_pluginfile_urls(
// The content of the text stored in the database. // The content of the text stored in the database.
$this->r->description, $this->r->description,
// The pluginfile URL which will serve the request. // The pluginfile URL which will serve the request.

View File

@ -35,7 +35,8 @@ class teachingfinder {
/** /**
* List all studyplans the vurrent user is teaching * List all studyplans the current user is teaching
* (Updates the cache if no results are found the first time)
* @return studyplan[] List of studyplans * @return studyplan[] List of studyplans
*/ */
public static function list_my_plans() { public static function list_my_plans() {
@ -46,7 +47,7 @@ class teachingfinder {
if (count($records) == 0) { if (count($records) == 0) {
// Initiate a search if the cache is empty. // Initiate a search if the cache is empty.
self::update_teaching_cache($userid); self::update_teaching_cache($userid);
$DB->get_records(self::TABLE, ['teacher_id' => $userid]); $records = $DB->get_records(self::TABLE, ['teacher_id' => $userid]);
} }
$list = []; $list = [];
foreach ($records as $r) { foreach ($records as $r) {
@ -55,6 +56,74 @@ class teachingfinder {
return $list; return $list;
} }
/**
* Check if a user is teaching in a specific studyplan
* (Does not update the cache if results are 0)
* @param studyplan $plan Studyplan to check
* @return bool If teaching in this plan
*/
public static function is_teaching_studyplan(studyplan $plan,$userid) {
global $DB;
$count = $DB->count_records(self::TABLE, ['teacher_id' => $userid, "studyplan_id" => $plan->id()]);
return ($count > 0)?true:false;
}
/**
* Check if a user is teaching courses in any studyplan
* (Does not update the cache if results are 0)
* @param studyplan $plan Studyplan to check
* @return bool If teaching in this plan
*/
public static function is_teaching($userid) {
global $DB;
$count = $DB->count_records(self::TABLE, ['teacher_id' => $userid]);
return ($count > 0)?true:false;
}
/**
* Check if current user is teaching in a specific studyplan
* (Does not update the cache if results are 0)
* @param studyplan $plan Studyplan to check
* @return bool If teaching in this plan
*/
public static function am_teaching_studyplan(studyplan $plan) {
global $USER;
return self::is_teaching_studyplan($plan,$USER->id);
}
/**
* Check if current user is teaching courses in any studyplan
* (Does not update the cache if results are 0)
* @param studyplan $plan Studyplan to check
* @return bool If teaching in this plan
*/
public static function am_teaching() {
global $USER;
return self::is_teaching($USER->id);
}
/**
* Check if a user is teaching in a specific course
* @param int $courseid ID of the course
* @param int $userid ID of the user
* @return bool True if teaching in the course
*/
public static function is_teaching_course($courseid, $userid) {
$coursecontext = \context_course::instance($courseid);
return is_enrolled($coursecontext, $userid, 'mod/assign:grade');
}
/**
* Check if current user is teaching in a specific course
* @param int $courseid ID of the course
* @return bool True if teaching in the course
*/
public static function am_teaching_course($courseid) {
global $USER;
return self::is_teaching_course($courseid,$USER->id);
}
/** /**
* Find The active studyplan pages where the specified user is a teacher * Find The active studyplan pages where the specified user is a teacher
* (Has the mod/assign::grade capability in one of the linked courses) * (Has the mod/assign::grade capability in one of the linked courses)
@ -82,8 +151,7 @@ class teachingfinder {
$linked = false; $linked = false;
foreach ($courseids as $cid) { foreach ($courseids as $cid) {
$coursecontext = \context_course::instance($cid); if (self::is_teaching_course($cid, $userid)) {
if (is_enrolled($coursecontext, $userid, 'mod/assign:grade')) {
$linked = true; $linked = true;
break; // No need to search further. break; // No need to search further.
} }

View File

@ -1299,7 +1299,9 @@
border-color: #aaa; border-color: #aaa;
} }
.path-local-treestudyplan .s-studyplan-page-edit, .path-local-treestudyplan .s-studyplan-page-edit,
.features-treestudyplan .s-studyplan-page-edit { .path-local-treestudyplan .t-tab-extra,
.features-treestudyplan .s-studyplan-page-edit,
.features-treestudyplan .t-tab-extra {
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
margin-left: 0.5em; margin-left: 0.5em;
@ -1307,8 +1309,8 @@
.path-local-treestudyplan .card.s-studyplan-card, .path-local-treestudyplan .card.s-studyplan-card,
.features-treestudyplan .card.s-studyplan-card { .features-treestudyplan .card.s-studyplan-card {
min-width: 300px; min-width: 400px;
max-width: 500px; max-width: 400px;
margin-bottom: 1em; margin-bottom: 1em;
} }
.path-local-treestudyplan .card.s-studyplan-card.timing-past .card-header, .path-local-treestudyplan .card.s-studyplan-card.timing-past .card-header,
@ -1376,8 +1378,8 @@
flex-direction: column; flex-direction: column;
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.path-local-treestudyplan .s-studyplan-card-info > :last-child, .path-local-treestudyplan .s-studyplan-card-info > :last-child:not(:only-child),
.features-treestudyplan .s-studyplan-card-info > :last-child { .features-treestudyplan .s-studyplan-card-info > :last-child:not(:only-child) {
margin-top: auto; margin-top: auto;
} }
.path-local-treestudyplan .s-studyplan-card-progressbar, .path-local-treestudyplan .s-studyplan-card-progressbar,
@ -1419,6 +1421,11 @@
font-size: 80%; font-size: 80%;
color: var(--gray); color: var(--gray);
} }
.path-local-treestudyplan .s-studyplan-details img,
.features-treestudyplan .s-studyplan-details img {
width: 128px;
height: 128px;
}
.path-local-treestudyplan .b-modal-justify-footer-between .modal-footer, .path-local-treestudyplan .b-modal-justify-footer-between .modal-footer,
.features-treestudyplan .b-modal-justify-footer-between .modal-footer { .features-treestudyplan .b-modal-justify-footer-between .modal-footer {

View File

@ -23,35 +23,7 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$services = [ $services = [
"Competency listing" => [
'functions' => [
'local_treestudyplan_get_studyplan_map',
'local_treestudyplan_get_studyline_map',
'local_treestudyplan_add_studyplan',
'local_treestudyplan_add_studyline',
'local_treestudyplan_edit_studyplan',
'local_treestudyplan_edit_studyline',
'local_treestudyplan_delete_studyplan',
'local_treestudyplan_delete_studyline',
'local_treestudyplan_reorder_studylines',
'local_treestudyplan_get_studyitem',
'local_treestudyplan_add_studyitem',
'local_treestudyplan_edit_studyitem',
'local_treestudyplan_reorder_studyitems',
'local_treestudyplan_delete_studyitem',
'local_treestudyplan_connect_studyitems',
'local_treestudyplan_disconnect_studyitems',
'local_treestudyplan_get_user_studyplan_map',
'local_treestudyplan_map_courses',
'local_treestudyplan_export_studyplan',
'local_treestudyplan_import_studyplan',
],
'requiredcapability' => 'local/treestudyplan:configure',
'shortname' => 'local_treestudyplan_cohorts',
'restrictedusers' => 0,
'enabled' => 0,
'ajax' => true,
],
]; ];
$functions = [ $functions = [
@ -323,60 +295,6 @@ $functions = [
'capabilities' => 'local/treestudyplan:editstudyplan', 'capabilities' => 'local/treestudyplan:editstudyplan',
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_list_user_studyplans' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'list_user_studyplans', // External function name.
'description' => 'List user studyplans',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports',
'loginrequired' => true,
],
'local_treestudyplan_get_user_studyplans' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_user_studyplans', // External function name.
'description' => 'Retrieve user studyplan',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports',
'loginrequired' => true,
],
'local_treestudyplan_get_user_studyplan' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_user_studyplan', // External function name.
'description' => 'Retrieve user studyplan',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports',
'loginrequired' => true,
],
'local_treestudyplan_get_invited_studyplan' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_invited_studyplan', // External function name.
'description' => 'Retrieve user studyplan based on invite',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => false,
],
'local_treestudyplan_list_own_studyplans' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'list_own_studyplans', // External function name.
'description' => 'List own studyplans',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_get_own_studyplan' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_own_studyplan', // External function name.
'description' => 'Retrieve own studyplan',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_map_categories' => [ // Web service function name. 'local_treestudyplan_map_categories' => [ // Web service function name.
'classname' => '\local_treestudyplan\courseservice', // Class containing the external function. 'classname' => '\local_treestudyplan\courseservice', // Class containing the external function.
'methodname' => 'map_categories', // External function name. 'methodname' => 'map_categories', // External function name.
@ -521,15 +439,6 @@ $functions = [
'capabilities' => 'local/treestudyplan:editstudyplan', 'capabilities' => 'local/treestudyplan:editstudyplan',
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_get_teaching_studyplans' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_teaching_studyplans', // External function name.
'description' => 'Get the studyplans I currently teach in',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports',
'loginrequired' => true,
],
'local_treestudyplan_list_accessible_categories' => [ // Web service function name. 'local_treestudyplan_list_accessible_categories' => [ // Web service function name.
'classname' => '\local_treestudyplan\courseservice', // Class containing the external function. 'classname' => '\local_treestudyplan\courseservice', // Class containing the external function.
'methodname' => 'list_accessible_categories', // External function name. 'methodname' => 'list_accessible_categories', // External function name.
@ -628,4 +537,112 @@ $functions = [
'ajax' => true, 'ajax' => true,
'loginrequired' => true, 'loginrequired' => true,
], ],
'local_treestudyplan_list_user_studyplans' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'list_user_studyplans', // External function name.
'description' => 'List user studyplans',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports',
'loginrequired' => true,
],
'local_treestudyplan_get_user_studyplan' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_user_studyplan', // External function name.
'description' => 'Retrieve user studyplan',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports',
'loginrequired' => true,
],
'local_treestudyplan_get_user_page' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_user_page', // External function name.
'description' => 'Retrieve user studyplan page',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:viewuserreports',
'loginrequired' => true,
],
'local_treestudyplan_list_invited_studyplans' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'list_invited_studyplans', // External function name.
'description' => 'List studyplans for user from invite',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_get_invited_studyplan' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_invited_studyplan', // External function name.
'description' => 'Retrieve studyplan for user from invite',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_get_invited_page' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_invited_page', // External function name.
'description' => 'Retrieve studyplan page for user from invite',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_list_own_studyplans' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'list_own_studyplans', // External function name.
'description' => 'List studyplans for current user',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_get_own_studyplan' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_own_studyplan', // External function name.
'description' => 'Retrieve studyplan for current user',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_get_own_page' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_own_page', // External function name.
'description' => 'Retrieve studyplan page for current user',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_list_teaching_studyplans' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'list_teaching_studyplans', // External function name.
'description' => 'List studyplans for current user as teacher',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_get_teaching_studyplan' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_teaching_studyplan', // External function name.
'description' => 'Retrieve studyplan for current user as teacher',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
'local_treestudyplan_get_teaching_page' => [ // Web service function name.
'classname' => '\local_treestudyplan\studentstudyplanservice', // Class containing the external function.
'methodname' => 'get_teaching_page', // External function name.
'description' => 'Retrieve studyplan page for current user as teacher',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => '',
'loginrequired' => true,
],
]; ];

1
debug.log Symbolic link
View File

@ -0,0 +1 @@
/tmp/debug.log

View File

@ -83,7 +83,7 @@ if (empty($invite)) {
</div> </div>
</div> </div>
<div v-cloak> <div v-cloak>
<r-report v-model="studyplans" :guestmode="true"></r-report> <r-report type="invited" :invitekey="invitekey"></r-report>
</div> </div>
</div> </div>

View File

@ -131,6 +131,7 @@ $string["studyplan_idnumber"] = 'ID Number';
$string["studyplan_idnumber_ph"] = ''; $string["studyplan_idnumber_ph"] = '';
$string["studyplan_description"] = 'Description'; $string["studyplan_description"] = 'Description';
$string["studyplan_description_ph"] = ''; $string["studyplan_description_ph"] = '';
$string["studyplan_details"] = 'About';
$string["studyplan_slots"] = 'Number of periods in plan'; $string["studyplan_slots"] = 'Number of periods in plan';
$string["studyplan_startdate"] = 'Start date of plan'; $string["studyplan_startdate"] = 'Start date of plan';
$string["studyplan_enddate"] = 'End date of plan'; $string["studyplan_enddate"] = 'End date of plan';
@ -208,8 +209,12 @@ $string["grade_points"] = 'Maximum grade points';
$string["view_feedback"] = 'View feedback'; $string["view_feedback"] = 'View feedback';
$string["coursetiming_past"] = "Past course"; $string["coursetiming_past"] = "Past course";
$string["coursetiming_present"] = "Active course"; $string["coursetiming_present"] = "Current course";
$string["coursetiming_future"] = "Upcoming course"; $string["coursetiming_future"] = "Upcoming course";
$string["studyplan_past"] = "Past study plans";
$string["studyplan_present"] = "Current study plans";
$string["studyplan_future"] = "Upcoming study plans";
$string["link_myreport"] = "My study plan"; $string["link_myreport"] = "My study plan";
$string["link_viewplan"] = "Study plans"; $string["link_viewplan"] = "Study plans";

View File

@ -127,6 +127,7 @@ $string["studyplan_shortname_ph"] = '';
$string["studyplan_idnumber"] = 'Opleidings-ID'; $string["studyplan_idnumber"] = 'Opleidings-ID';
$string["studyplan_idnumber_ph"] = ''; $string["studyplan_idnumber_ph"] = '';
$string["studyplan_description"] = 'Beschrijving'; $string["studyplan_description"] = 'Beschrijving';
$string["studyplan_details"] = 'Meer info';
$string["studyplan_description_ph"] = ''; $string["studyplan_description_ph"] = '';
$string["studyplan_slots"] = 'Aantal periodes in studieplan'; $string["studyplan_slots"] = 'Aantal periodes in studieplan';
$string["studyplan_startdate"] = 'Startdatum'; $string["studyplan_startdate"] = 'Startdatum';
@ -208,6 +209,10 @@ $string["coursetiming_past"] = "Eerdere cursus";
$string["coursetiming_present"] = "Actieve cursus"; $string["coursetiming_present"] = "Actieve cursus";
$string["coursetiming_future"] = "Toekomstige cursus"; $string["coursetiming_future"] = "Toekomstige cursus";
$string["studyplan_past"] = "Eerdere studieplannen";
$string["studyplan_present"] = "Actieve studieplannen";
$string["studyplan_future"] = "Toekomstige studieplannen";
$string["link_myreport"] = "Mijn studieplan"; $string["link_myreport"] = "Mijn studieplan";
$string["link_viewplan"] = "Studieplannen"; $string["link_viewplan"] = "Studieplannen";
$string["link_editplan"] = "Studieplannen beheren"; $string["link_editplan"] = "Studieplannen beheren";

View File

@ -25,6 +25,8 @@ require_once("../../config.php");
require_once($CFG->libdir.'/weblib.php'); require_once($CFG->libdir.'/weblib.php');
use local_treestudyplan; use local_treestudyplan;
use local_treestudyplan\studyplan;
use local_treestudyplan\teachingfinder;
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
@ -34,9 +36,11 @@ require_login();
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
$PAGE->set_context($systemcontext); $PAGE->set_context($systemcontext);
$teachermode = has_capability("local/treestudyplan:viewuserreports", $systemcontext); //$teachermode = has_capability("local/treestudyplan:viewuserreports", $systemcontext);
$am_teaching = teachingfinder::is_teaching($USER->id);
$have_plans = studyplan::exist_for_user($USER->id);
if ($teachermode) { if ($am_teaching) {
$PAGE->set_title(get_string('myreport_teachermode', 'local_treestudyplan')); $PAGE->set_title(get_string('myreport_teachermode', 'local_treestudyplan'));
$PAGE->set_heading(get_string('myreport_teachermode', 'local_treestudyplan')); $PAGE->set_heading(get_string('myreport_teachermode', 'local_treestudyplan'));
} else { } else {
@ -49,7 +53,7 @@ $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/boot
if ($CFG->debugdeveloper) { if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
} }
$PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init', [$teachermode ? 'teaching' : 'myreport']); $PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init', [$am_teaching ? 'teaching' : 'own']);
/** /**
* Shortcut function to provide translations * Shortcut function to provide translations
@ -67,9 +71,9 @@ print $OUTPUT->header();
print '<div class="m-buttonbar" style="margin-bottom: 1em; text-align: right;">'; print '<div class="m-buttonbar" style="margin-bottom: 1em; text-align: right;">';
if (!$teachermode) { if (!$am_teaching) {
print '<a class="btn btn-primary" href="invitations.php" id="manage_invites">'; print '<a class="btn btn-primary" href="invitations.php" id="manage_invites">';
print '<i class="fa fa-share"></i>'.t('manage_invites').'</a>'; print '&nbsp;<i class="fa fa-share"></i>&nbsp;'.t('manage_invites').'</a>';
} }
print "</div>"; print "</div>";
@ -80,7 +84,12 @@ print " <span class='sr-only'>Loading...</span>";
print " </div>"; print " </div>";
print " </div>"; print " </div>";
print " <div v-cloak>"; print " <div v-cloak>";
print " <r-report v-model='studyplans' " . ($teachermode ? "teachermode" : "")." ></r-report>"; if ($am_teaching) {
print " <r-report type='teaching' teachermode ></r-report>";
} else {
print " <r-report type='own' ></r-report>";
}
print " </div>"; print " </div>";
print "</div>"; print "</div>";

View File

@ -1112,7 +1112,8 @@
border-color: #aaa; border-color: #aaa;
} }
.s-studyplan-page-edit { .s-studyplan-page-edit,
.t-tab-extra {
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
margin-left: 0.5em; margin-left: 0.5em;

View File

@ -1,8 +1,8 @@
.path-local-treestudyplan, .features-treestudyplan { .path-local-treestudyplan, .features-treestudyplan {
.card.s-studyplan-card { .card.s-studyplan-card {
min-width: 300px; min-width: 400px;
max-width: 500px; max-width: 400px;
margin-bottom: 1em; margin-bottom: 1em;
} }
@ -57,6 +57,8 @@
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: space-between; justify-content: space-between;
gap: 5px; gap: 5px;
} }
.s-studyplan-card-titlebuttons { .s-studyplan-card-titlebuttons {
margin-left: auto; margin-left: auto;
@ -68,7 +70,7 @@
flex-direction: column; flex-direction: column;
flex-wrap: nowrap; flex-wrap: nowrap;
> :last-child { > :last-child:not(:only-child) {
margin-top: auto; margin-top: auto;
} }
} }
@ -109,5 +111,12 @@
font-size: 80%; font-size: 80%;
color: var(--gray); color: var(--gray);
} }
.s-studyplan-details {
img {
width: 128px;
height: 128px;
}
}
} }

View File

@ -1299,7 +1299,9 @@
border-color: #aaa; border-color: #aaa;
} }
.path-local-treestudyplan .s-studyplan-page-edit, .path-local-treestudyplan .s-studyplan-page-edit,
.features-treestudyplan .s-studyplan-page-edit { .path-local-treestudyplan .t-tab-extra,
.features-treestudyplan .s-studyplan-page-edit,
.features-treestudyplan .t-tab-extra {
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
margin-left: 0.5em; margin-left: 0.5em;
@ -1307,8 +1309,8 @@
.path-local-treestudyplan .card.s-studyplan-card, .path-local-treestudyplan .card.s-studyplan-card,
.features-treestudyplan .card.s-studyplan-card { .features-treestudyplan .card.s-studyplan-card {
min-width: 300px; min-width: 400px;
max-width: 500px; max-width: 400px;
margin-bottom: 1em; margin-bottom: 1em;
} }
.path-local-treestudyplan .card.s-studyplan-card.timing-past .card-header, .path-local-treestudyplan .card.s-studyplan-card.timing-past .card-header,
@ -1376,8 +1378,8 @@
flex-direction: column; flex-direction: column;
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.path-local-treestudyplan .s-studyplan-card-info > :last-child, .path-local-treestudyplan .s-studyplan-card-info > :last-child:not(:only-child),
.features-treestudyplan .s-studyplan-card-info > :last-child { .features-treestudyplan .s-studyplan-card-info > :last-child:not(:only-child) {
margin-top: auto; margin-top: auto;
} }
.path-local-treestudyplan .s-studyplan-card-progressbar, .path-local-treestudyplan .s-studyplan-card-progressbar,
@ -1419,6 +1421,11 @@
font-size: 80%; font-size: 80%;
color: var(--gray); color: var(--gray);
} }
.path-local-treestudyplan .s-studyplan-details img,
.features-treestudyplan .s-studyplan-details img {
width: 128px;
height: 128px;
}
.path-local-treestudyplan .b-modal-justify-footer-between .modal-footer, .path-local-treestudyplan .b-modal-justify-footer-between .modal-footer,
.features-treestudyplan .b-modal-justify-footer-between .modal-footer { .features-treestudyplan .b-modal-justify-footer-between .modal-footer {

View File

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

View File

@ -122,8 +122,13 @@ print $OUTPUT->header();
:value='studyplan.id' :value='studyplan.id'
>{{ studyplan.name }}</b-form-select-option> >{{ studyplan.name }}</b-form-select-option>
</b-form-select>&nbsp; </b-form-select>&nbsp;
<b-button v-if="activestudyplan" variant='primary' v-b-toggle.toolbox-sidebar <s-studyplan-details
v-model="displayedstudyplan"
v-if="displayedstudyplan && displayedstudyplan.description"
></s-studyplan-details>
<b-button class="ml-1" v-if="activestudyplan" variant='primary' v-b-toggle.toolbox-sidebar
><?php t('selectstudent_btn') ?></b-button> ><?php t('selectstudent_btn') ?></b-button>
</div> </div>
<div class='t-studyplan-container'> <div class='t-studyplan-container'>
<h2 v-if='displayedstudyplan&& selectedstudent' <h2 v-if='displayedstudyplan&& selectedstudent'