Implemented coach view

This commit is contained in:
PMKuipers 2024-03-09 22:51:34 +01:00
parent 6bb13de5cf
commit 7dbcb437c3
22 changed files with 224 additions and 166 deletions

View file

@ -1,3 +1,3 @@
define("local_treestudyplan/page-coach",["exports","core/ajax","core/notification","./vue/vue","./util/debugger","./util/string-helper","./studyplan-processor","./util/date-helper","./report-viewer-components","./treestudyplan-components","./modedit-modal","./portal-vue/portal-vue.esm","./bootstrap-vue/bootstrap-vue"],(function(_exports,_ajax,_notification,_vue,_debugger,_stringHelper,_studyplanProcessor,_dateHelper,_reportViewerComponents,_treestudyplanComponents,_modeditModal,_portalVue,_bootstrapVue){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){let app=new _vue.default({el:"#root",data:{displayedstudyplan:null,activestudyplan:null,associatedstudents:[],selectedstudent:null,studentstudyplan:null,loadingstudyplan:!1,studyplans:[],text:strings.studyplan,toolbox:{right:!0},usedcontexts:[]},async mounted(){(0,_ajax.call)([{methodname:"list_coaching_studyplans",args:{}}])[0].then((function(response){const timingval={present:0,past:1,future:2};response.sort(((a,b)=>{const timinga=(0,_dateHelper.studyplanTiming)(a),timingb=(0,_dateHelper.studyplanTiming)(b),t=timingval[timinga]-timingval[timingb];return 0==t?a.name.localeCompare(b.name):t})),app.studyplans=response;const parts=window.location.hash.replace("#","").split("-");if(parts&&parts.length>0)for(let idx in app.studyplans)if(app.studyplans[idx].id==parts[0]){app.selectStudyplan(app.studyplans[idx],parts[1]);break}})).catch(_notification.default.exception)},computed:{},methods:{closeStudyplan(){app.activestudyplan=null,app.associatedstudents=[],app.studentstudyplan=[],app.displayedstudyplan=null,window.location.hash=""},selectStudyplan(studyplan,studentid){app.loadingstudyplan=!0,app.activestudyplan=null,app.associatedstudents=[],app.selectedstudent=null,app.studentstudyplan=null,(0,_ajax.call)([{methodname:"local_treestudyplan_get_studyplan_map",args:{id:studyplan.id}}])[0].then((function(response){app.activestudyplan=(0,_studyplanProcessor.ProcessStudyplan)(response,!0),app.displayedstudyplan=app.activestudyplan,app.loadingstudyplan=!1,window.location.hash=app.activestudyplan.id,(0,_ajax.call)([{methodname:"local_treestudyplan_all_associated_grouped",args:{studyplan_id:studyplan.id}}])[0].then((function(response){if(app.associatedstudents=response,studentid)for(const group of app.associatedstudents)for(const student of group.users)if(student.id==studentid){app.showStudentView(student);break}})).catch(_notification.default.exception)})).catch((function(error){_notification.default.exception(error),app.loadingstudyplan=!1}))},showStudentView(student){app.selectedstudent=student,app.studentstudyplan=null,student&&(app.loadingstudyplan=!0,(0,_ajax.call)([{methodname:"local_treestudyplan_get_user_studyplan",args:{userid:student.id,studyplanid:app.activestudyplan.id}}])[0].then((function(response){app.studentstudyplan=(0,_studyplanProcessor.ProcessStudyplan)(response,!1),app.displayedstudyplan=app.studentstudyplan,app.loadingstudyplan=!1,window.location.hash=app.activestudyplan.id+"-"+student.id})).catch((function(error){_notification.default.exception(error),app.loadingstudyplan=!1})))},showOverview(){app.selectedstudent=null,app.studentstudyplan=null,app.displayedstudyplan=app.activestudyplan,window.location.hash=app.activestudyplan.id}}})},_notification=_interopRequireDefault(_notification),_vue=_interopRequireDefault(_vue),_debugger=_interopRequireDefault(_debugger),_reportViewerComponents=_interopRequireDefault(_reportViewerComponents),_treestudyplanComponents=_interopRequireDefault(_treestudyplanComponents),_modeditModal=_interopRequireDefault(_modeditModal),_portalVue=_interopRequireDefault(_portalVue),_bootstrapVue=_interopRequireDefault(_bootstrapVue),_vue.default.use(_reportViewerComponents.default),_vue.default.use(_modeditModal.default),_vue.default.use(_portalVue.default),_vue.default.use(_bootstrapVue.default);new _debugger.default("treestudyplanviewer");let strings=(0,_stringHelper.load_strings)({studyplan:{studyplan_select_placeholder:"studyplan_select_placeholder"}})})); define("local_treestudyplan/page-coach",["exports","core/ajax","core/notification","./vue/vue","./util/debugger","./util/string-helper","./studyplan-processor","./util/date-helper","./studyplan-editor-components","./treestudyplan-components","./report-viewer-components","./modedit-modal","./portal-vue/portal-vue.esm","./bootstrap-vue/bootstrap-vue"],(function(_exports,_ajax,_notification,_vue,_debugger,_stringHelper,_studyplanProcessor,_dateHelper,_studyplanEditorComponents,_treestudyplanComponents,_reportViewerComponents,_modeditModal,_portalVue,_bootstrapVue){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){let app=new _vue.default({el:"#root",data:{displayedstudyplan:null,activestudyplan:null,associatedstudents:[],selectedstudent:null,studentstudyplan:null,loadingstudyplan:!1,studyplans:[],text:strings.coach,toolbox:{right:!0},usedcontexts:[],editmode:!1},async mounted(){(0,_ajax.call)([{methodname:"local_treestudyplan_list_coaching_studyplans",args:{}}])[0].then((function(response){const timingval={present:0,past:1,future:2};response.sort(((a,b)=>{const timinga=(0,_dateHelper.studyplanTiming)(a),timingb=(0,_dateHelper.studyplanTiming)(b),t=timingval[timinga]-timingval[timingb];return 0==t?a.name.localeCompare(b.name):t})),app.studyplans=response;const parts=window.location.hash.replace("#","").split("-");if(parts&&parts.length>0)for(let idx in app.studyplans)if(app.studyplans[idx].id==parts[0]){app.selectStudyplan(app.studyplans[idx],parts[1]);break}})).catch(_notification.default.exception)},computed:{},methods:{closeStudyplan(){app.activestudyplan=null,app.associatedstudents=[],app.studentstudyplan=[],app.displayedstudyplan=null,window.location.hash=""},selectStudyplan(studyplan,studentid){const self=this;self.loadingstudyplan=!0,self.activestudyplan=null,self.associatedstudents=[],self.selectedstudent=null,self.studentstudyplan=null,(0,_ajax.call)([{methodname:"local_treestudyplan_get_studyplan_map",args:{id:studyplan.id}}])[0].then((function(response){self.activestudyplan=(0,_studyplanProcessor.ProcessStudyplan)(response,!0),self.displayedstudyplan=self.activestudyplan,self.loadingstudyplan=!1,window.location.hash=self.activestudyplan.id,(0,_ajax.call)([{methodname:"local_treestudyplan_all_associated_grouped",args:{studyplan_id:studyplan.id}}])[0].then((function(response){if(self.associatedstudents=response,studentid){for(const group of self.associatedstudents)for(const student of group.users)if(student.id==studentid){self.showStudentView(student);break}}else for(const group of self.associatedstudents)for(const student of group.users){self.showStudentView(student);break}})).catch(_notification.default.exception)})).catch((function(error){_notification.default.exception(error),app.loadingstudyplan=!1}))},showStudentView(student){app.selectedstudent=student,app.studentstudyplan=null,student&&(app.loadingstudyplan=!0,(0,_ajax.call)([{methodname:"local_treestudyplan_get_user_studyplan",args:{userid:student.id,studyplanid:app.activestudyplan.id}}])[0].then((function(response){app.studentstudyplan=(0,_studyplanProcessor.ProcessStudyplan)(response,!1),app.displayedstudyplan=app.studentstudyplan,app.loadingstudyplan=!1,window.location.hash=app.activestudyplan.id+"-"+student.id})).catch((function(error){_notification.default.exception(error),app.loadingstudyplan=!1})))},showOverview(){app.selectedstudent=null,app.studentstudyplan=null,app.displayedstudyplan=app.activestudyplan,window.location.hash=app.activestudyplan.id}}})},_notification=_interopRequireDefault(_notification),_vue=_interopRequireDefault(_vue),_debugger=_interopRequireDefault(_debugger),_studyplanEditorComponents=_interopRequireDefault(_studyplanEditorComponents),_treestudyplanComponents=_interopRequireDefault(_treestudyplanComponents),_reportViewerComponents=_interopRequireDefault(_reportViewerComponents),_modeditModal=_interopRequireDefault(_modeditModal),_portalVue=_interopRequireDefault(_portalVue),_bootstrapVue=_interopRequireDefault(_bootstrapVue),_vue.default.use(_studyplanEditorComponents.default),_vue.default.use(_treestudyplanComponents.default),_vue.default.use(_reportViewerComponents.default),_vue.default.use(_modeditModal.default),_vue.default.use(_portalVue.default),_vue.default.use(_bootstrapVue.default);new _debugger.default("treestudyplancoach");let strings=(0,_stringHelper.load_strings)({coach:{studyplan_select_placeholder:"studyplan_select_placeholder",switch_coach_editmode:"switch_coach_editmode"}})}));
//# sourceMappingURL=page-coach.min.js.map //# sourceMappingURL=page-coach.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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -16,9 +16,15 @@ import {load_strings} from './util/string-helper';
import {ProcessStudyplan} from './studyplan-processor'; import {ProcessStudyplan} from './studyplan-processor';
import {studyplanTiming} from './util/date-helper'; import {studyplanTiming} from './util/date-helper';
import EditorComponents from './studyplan-editor-components';
Vue.use(EditorComponents);
import TSComponents from './treestudyplan-components';
Vue.use(TSComponents);
import RVComponents from './report-viewer-components'; import RVComponents from './report-viewer-components';
Vue.use(RVComponents); Vue.use(RVComponents);
import TSComponents from './treestudyplan-components';
import ModalComponents from './modedit-modal'; import ModalComponents from './modedit-modal';
Vue.use(ModalComponents); Vue.use(ModalComponents);
@ -28,11 +34,12 @@ import BootstrapVue from './bootstrap-vue/bootstrap-vue';
Vue.use(BootstrapVue); Vue.use(BootstrapVue);
let debug = new Debugger("treestudyplanviewer"); let debug = new Debugger("treestudyplancoach");
let strings = load_strings({ let strings = load_strings({
studyplan: { coach: {
studyplan_select_placeholder: 'studyplan_select_placeholder', studyplan_select_placeholder: 'studyplan_select_placeholder',
switch_coach_editmode: 'switch_coach_editmode',
}, },
}); });
@ -50,15 +57,16 @@ export function init() {
studentstudyplan: null, studentstudyplan: null,
loadingstudyplan: false, loadingstudyplan: false,
studyplans: [], studyplans: [],
text: strings.studyplan, text: strings.coach,
toolbox: { toolbox: {
right: true, right: true,
}, },
usedcontexts: [], usedcontexts: [],
editmode: false,
}, },
async mounted() { async mounted() {
call([{ call([{
methodname: 'list_coaching_studyplans', methodname: 'local_treestudyplan_list_coaching_studyplans',
args: {} args: {}
}])[0].then(function(response){ }])[0].then(function(response){
const timingval = { present: 0, past: 1, future: 2}; const timingval = { present: 0, past: 1, future: 2};
@ -103,33 +111,42 @@ export function init() {
selectStudyplan(studyplan,studentid){ selectStudyplan(studyplan,studentid){
// fetch studyplan // fetch studyplan
app.loadingstudyplan = true; const self = this;
app.activestudyplan = null; self.loadingstudyplan = true;
app.associatedstudents = []; self.activestudyplan = null;
app.selectedstudent = null; self.associatedstudents = [];
app.studentstudyplan = null; self.selectedstudent = null;
self.studentstudyplan = null;
call([{ call([{
methodname: 'local_treestudyplan_get_studyplan_map', methodname: 'local_treestudyplan_get_studyplan_map',
args: { id: studyplan.id} args: { id: studyplan.id}
}])[0].then(function(response){ }])[0].then(function(response){
app.activestudyplan = ProcessStudyplan(response,true); self.activestudyplan = ProcessStudyplan(response,true);
app.displayedstudyplan = app.activestudyplan; self.displayedstudyplan = self.activestudyplan;
app.loadingstudyplan = false; self.loadingstudyplan = false;
window.location.hash = app.activestudyplan.id; window.location.hash = self.activestudyplan.id;
call([{ call([{
methodname: 'local_treestudyplan_all_associated_grouped', methodname: 'local_treestudyplan_all_associated_grouped',
args: { studyplan_id: studyplan.id} args: { studyplan_id: studyplan.id}
}])[0].then(function(response){ }])[0].then(function(response){
app.associatedstudents = response; self.associatedstudents = response;
if(studentid){ if(studentid){
for(const group of app.associatedstudents) { for(const group of self.associatedstudents) {
for(const student of group.users){ for(const student of group.users){
if(student.id == studentid){ if(student.id == studentid){
app.showStudentView(student); self.showStudentView(student);
break; break;
} }
} }
} }
} else {
// Select first student available.
for(const group of self.associatedstudents) {
for(const student of group.users){
self.showStudentView(student);
break;
}
}
} }
}).catch(notification.exception); }).catch(notification.exception);

View file

@ -11,10 +11,8 @@ import {call} from 'core/ajax';
import notification from 'core/notification'; import notification from 'core/notification';
import {resetAllFormDirtyStates} from 'core_form/changechecker'; import {resetAllFormDirtyStates} from 'core_form/changechecker';
import Vue from './vue/vue'; import Vue from './vue/vue';
import EditorComponents from './studyplan-editor-components'; import EditorComponents from './studyplan-editor-components';
Vue.use(EditorComponents); Vue.use(EditorComponents);
@ -35,10 +33,6 @@ Vue.use(PortalVue);
import BootstrapVue from './bootstrap-vue/bootstrap-vue'; import BootstrapVue from './bootstrap-vue/bootstrap-vue';
Vue.use(BootstrapVue); Vue.use(BootstrapVue);
import {Drag, Drop, DropList} from './vue-easy-dnd/vue-easy-dnd.esm';
Vue.component('drag',Drag);
Vue.component('drop',Drop);
Vue.component('drop-list',DropList);
const debug = new Debugger("treestudyplan"); const debug = new Debugger("treestudyplan");

View file

@ -539,6 +539,10 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
coaching: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@ -704,7 +708,7 @@ export default {
<!-- First paint the headings--> <!-- First paint the headings-->
<div class='r-studyplan-headings' <div class='r-studyplan-headings'
><s-studyline-header-heading :identifier="Number(page.id)" ><s-studyline-header-heading :identifier="Number(page.id)"
><a v-if="teachermode && selectedpage" class="ml-2" ><a v-if="teachermode && selectedpage && !coaching" class="ml-2"
:href="wwwroot+'//local/treestudyplan/result-overview.php?page='+selectedpage.id" :href="wwwroot+'//local/treestudyplan/result-overview.php?page='+selectedpage.id"
target='_blank'><i class='fa fa-list-ul'></i>&nbsp;{{text.overview}}</a target='_blank'><i class='fa fa-list-ul'></i>&nbsp;{{text.overview}}</a
@ -731,7 +735,7 @@ export default {
v-if="index > 0" v-if="index > 0"
v-model="page.perioddesc[index-1]" v-model="page.perioddesc[index-1]"
:identifier="Number(page.id)" :identifier="Number(page.id)"
><a v-if="teachermode && selectedpage" ><a v-if="teachermode && selectedpage && !coaching"
v-b-tooltip.hover v-b-tooltip.hover
:href="wwwroot+'//local/treestudyplan/result-overview.php?page='+selectedpage.id :href="wwwroot+'//local/treestudyplan/result-overview.php?page='+selectedpage.id
+'&firstperiod='+index+'&lastperiod='+index" +'&firstperiod='+index+'&lastperiod='+index"

View file

@ -23,6 +23,9 @@ import { premiumenabled, premiumstatus } from "./util/premium";
import TSComponents from './treestudyplan-components'; import TSComponents from './treestudyplan-components';
import mFormComponents from "./util/mform-helper"; import mFormComponents from "./util/mform-helper";
import {Drag, Drop, DropList} from './vue-easy-dnd/vue-easy-dnd.esm';
const STUDYPLAN_EDITOR_FIELDS = const STUDYPLAN_EDITOR_FIELDS =
['name','shortname','description','idnumber','context_id', 'aggregation','aggregation_config']; ['name','shortname','description','idnumber','context_id', 'aggregation','aggregation_config'];
@ -41,10 +44,12 @@ const datechanger_globals = {
export default { export default {
STUDYPLAN_EDITOR_FIELDS: STUDYPLAN_EDITOR_FIELDS, // make copy available in plugin STUDYPLAN_EDITOR_FIELDS: STUDYPLAN_EDITOR_FIELDS, // make copy available in plugin
install(Vue/*,options*/){ install(Vue/*,options*/){
Vue.component('drag',Drag);
Vue.component('drop',Drop);
Vue.component('drop-list',DropList);
Vue.use(TSComponents); Vue.use(TSComponents);
Vue.use(mFormComponents); Vue.use(mFormComponents);
let debug = new Debugger("treestudyplan-editor"); let debug = new Debugger("treestudyplan-editor");
debug.info("config",Config);
/************************************ /************************************
* * * *
* Treestudyplan Editor components * * Treestudyplan Editor components *
@ -1452,16 +1457,17 @@ export default {
this.edit.studyline.editmode = true; this.edit.studyline.editmode = true;
} }
// Retrieve available roles if (!self.coaching) {
call([{ // Retrieve available roles (only needed as manager)
methodname: 'local_treestudyplan_list_roles', call([{
args: { methodname: 'local_treestudyplan_list_roles',
'studyplan_id': this.value.id, args: {
} 'studyplan_id': this.value.id,
}])[0].then(function(response){ }
self.availableroles = response; }])[0].then(function(response){
}).catch(notification.exception); self.availableroles = response;
}).catch(notification.exception);
}
this.$root.$emit('redrawLines'); this.$root.$emit('redrawLines');
this.$emit('pagechanged',this.selectedpage); this.$emit('pagechanged',this.selectedpage);
}, },
@ -1971,6 +1977,7 @@ export default {
<b-form-select-option <b-form-select-option
v-for="(nr,n) in 4" v-for="(nr,n) in 4"
:value="n" :value="n"
:key="n"
>{{text['line_enrollable_'+n]}}</b-form-select-option> >{{text['line_enrollable_'+n]}}</b-form-select-option>
</b-form-select> </b-form-select>
</b-col> </b-col>
@ -4147,7 +4154,7 @@ export default {
type: Object, type: Object,
default() { return null;} default() { return null;}
}, },
'coaching': { coaching: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
@ -4197,6 +4204,7 @@ export default {
self.loadingcourses = false; self.loadingcourses = false;
}).catch(notification.exception); }).catch(notification.exception);
this.filter_systembadges(); this.filter_systembadges();
this.filter_relatedbadges();
}, },
filter_systembadges() { filter_systembadges() {
const self = this; const self = this;

View file

@ -475,7 +475,11 @@ class associationservice extends \external_api {
public static function associated_users($studyplanid) { public static function associated_users($studyplanid) {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid); $studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context()); if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
}
$sql = "SELECT DISTINCT u.* FROM {user} u INNER JOIN {local_treestudyplan_user} j ON j.user_id = u.id $sql = "SELECT DISTINCT u.* FROM {user} u INNER JOIN {local_treestudyplan_user} j ON j.user_id = u.id
WHERE j.studyplan_id = :studyplan_id WHERE j.studyplan_id = :studyplan_id
@ -614,7 +618,11 @@ class associationservice extends \external_api {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid); $studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context()); if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
}
$userlist = [ $userlist = [
[ [

View file

@ -212,7 +212,7 @@ class studyplanpage_editform extends formbase {
$customdata->editoroptions, $customdata->editoroptions,
\context_system::instance(), \context_system::instance(),
'local_treestudyplan', 'local_treestudyplan',
'studyplan', 'studyplanpage',
$page->id()); $page->id());
// Update the description // Update the description
$page->edit([ $page->edit([

View file

@ -400,4 +400,13 @@ Klc5I28bGbvxIV5pnL6ZSjHEDp2WreM8HB0XFJwU+Q==
} }
} }
/**
* Throw an error if premium status is not enabled
*/
public static function require_premium($message="premiumfeature:warning") {
if (! self::enabled()) {
throw new \moodle_exception($message,"local_treestudyplan");
}
}
} }

View file

@ -119,7 +119,11 @@ class studentstudyplanservice extends \external_api {
global $CFG, $DB; global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid); $studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEWOTHER, $studyplan->context()); if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_VIEWOTHER, $studyplan->context());
}
if ($studyplan->exist_for_user($userid)) { if ($studyplan->exist_for_user($userid)) {
@ -547,11 +551,11 @@ class studentstudyplanservice extends \external_api {
\external_api::validate_context(\context_system::instance()); \external_api::validate_context(\context_system::instance());
$studyplanids = $DB->get_records("local_treestudyplan_coach",["user_id" => $userid]); $records = $DB->get_records("local_treestudyplan_coach",["user_id" => $userid]);
$list = []; $list = [];
foreach ($studyplanids as $id) { foreach ($records as $r) {
$studyplan = studyplan::find_by_id($id); $studyplan = studyplan::find_by_id($r->studyplan_id);
if (has_capability(self::CAP_COACH,$studyplan->context(),$USER)) { if (has_capability(self::CAP_COACH,$studyplan->context(),$USER)) {
$list[] = $studyplan->simple_model($userid); $list[] = $studyplan->simple_model($userid);
} }

View file

@ -32,6 +32,9 @@ class studyplan {
/** @var string */ /** @var string */
const TABLE = "local_treestudyplan"; const TABLE = "local_treestudyplan";
/** @var string */
const TABLE_COACH = "local_treestudyplan_coach";
/** /**
* Cache retrieved studyitems in this session * Cache retrieved studyitems in this session
@ -606,6 +609,28 @@ class studyplan {
} }
} }
/** Check if this studyplan is linked to a particular user
* @param bool|stdClass|null $user The userid or user record of the user Leave empty to check current user.
*/
public function is_coach($user=null) {
global $DB, $USER;
if($user == null) {
$user = $USER;
$userid = $USER->id;
} else if (is_numeric($user)) {
$userid = intval($user);
} else {
$userid = $user->id;
}
$r = $DB->get_record(self::TABLE_COACH,["studyplan_id" => $this->id, "user_id"=> $userid]);
if ($r && has_capability(associationservice::CAP_COACH,$this->context(),$userid)) {
return true;
} else {
return false;
}
}
/** /**
* Webservice structure for userinfo * Webservice structure for userinfo
* @param int $value Webservice requirement constant * @param int $value Webservice requirement constant

View file

@ -46,6 +46,11 @@ class studyplanservice extends \external_api {
* @var string * @var string
*/ */
const CAP_VIEW = "local/treestudyplan:viewuserreports"; const CAP_VIEW = "local/treestudyplan:viewuserreports";
/**
* Capability required to view studyplans (for other users)
* @var string
*/
const CAP_COACH = "local/treestudyplan:coach";
/************************ /************************
* * * *
@ -121,7 +126,13 @@ class studyplanservice extends \external_api {
public static function get_studyplan_map($id) { public static function get_studyplan_map($id) {
if (isset($id) && $id > 0) { if (isset($id) && $id > 0) {
$studyplan = studyplan::find_by_id($id); $studyplan = studyplan::find_by_id($id);
webservicehelper::require_capabilities([self::CAP_EDIT, self::CAP_VIEW], $studyplan->context());
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities([self::CAP_EDIT, self::CAP_VIEW], $studyplan->context());
}
$model = $studyplan->editor_model(); $model = $studyplan->editor_model();
debug::dump($model); debug::dump($model);
return $model; return $model;
@ -281,9 +292,10 @@ class studyplanservice extends \external_api {
$enddate, $aggregation = "bistate", $aggregationconfig = '', $contextid = 0) { $enddate, $aggregation = "bistate", $aggregationconfig = '', $contextid = 0) {
// Validate access in the intended context. // Validate access in the intended context.
$context = webservicehelper::find_context($contextid); $context = webservicehelper::find_context($contextid);
// Do not validate the context in this case, just check the permissions. // Do not validate the context in this case, just check the permissions in the specified context.
webservicehelper::require_capabilities(self::CAP_EDIT, $context, false); webservicehelper::require_capabilities(self::CAP_EDIT, $context, false);
// Also check the permissions in the context of the studyplan, in case it is not the same.
$o = studyplan::find_by_id($id); $o = studyplan::find_by_id($id);
webservicehelper::require_capabilities(self::CAP_EDIT, $o->context()); webservicehelper::require_capabilities(self::CAP_EDIT, $o->context());
@ -621,7 +633,13 @@ class studyplanservice extends \external_api {
* @return array * @return array
*/ */
public static function add_studyitem($lineid, $type, $details, $slot = -1, $layer = 0) { public static function add_studyitem($lineid, $type, $details, $slot = -1, $layer = 0) {
webservicehelper::require_capabilities(self::CAP_EDIT, studyline::find_by_id($lineid)->context()); $line = studyline::find_by_id($lineid);
$studyplan = $line->studyplan();
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
}
$o = studyitem::add([ $o = studyitem::add([
'line_id' => $lineid, 'line_id' => $lineid,
@ -670,7 +688,12 @@ class studyplanservice extends \external_api {
public static function edit_studyitem($id, $conditions, $continuationid = false) { public static function edit_studyitem($id, $conditions, $continuationid = false) {
$o = studyitem::find_by_id($id); $o = studyitem::find_by_id($id);
webservicehelper::require_capabilities(self::CAP_EDIT, $o->context()); $studyplan = $o->studyline()->studyplan();
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_EDIT, $o->context());
}
$config = [ $config = [
'conditions' => $conditions, 'conditions' => $conditions,
@ -721,7 +744,14 @@ class studyplanservice extends \external_api {
public static function reorder_studyitems($resequence) { public static function reorder_studyitems($resequence) {
// Check for permissions to modify the studyplan. // Check for permissions to modify the studyplan.
foreach ($resequence as $sq) { foreach ($resequence as $sq) {
webservicehelper::require_capabilities(self::CAP_EDIT, studyitem::find_by_id(($sq['id']))->context()); $item = studyitem::find_by_id(($sq['id']));
$studyplan = $item->studyline()->studyplan();
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
}
} }
return studyitem::reorder($resequence)->model(); return studyitem::reorder($resequence)->model();
@ -756,7 +786,12 @@ class studyplanservice extends \external_api {
*/ */
public static function delete_studyitem($id) { public static function delete_studyitem($id) {
$o = studyitem::find_by_id($id); $o = studyitem::find_by_id($id);
webservicehelper::require_capabilities(self::CAP_EDIT, $o->context()); $studyplan = $o->studyline()->studyplan();
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_EDIT, $o->context());
}
return $o->delete()->model(); return $o->delete()->model();
} }
@ -792,8 +827,16 @@ class studyplanservice extends \external_api {
*/ */
public static function connect_studyitems($fromid, $toid) { public static function connect_studyitems($fromid, $toid) {
// Validate permissions. // Validate permissions.
webservicehelper::require_capabilities(self::CAP_EDIT, studyitem::find_by_id($fromid)->context()); $studyplan = studyitem::find_by_id($fromid)->studyline()->studyplan();
webservicehelper::require_capabilities(self::CAP_EDIT, studyitem::find_by_id($toid)->context()); $toplan = studyitem::find_by_id($toid)->studyline()->studyplan();
if ($toplan->id() != $studyplan->id()) {
throw new \webservice_access_exception("The items to connect need to be in the same studyplan" );
}
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
}
$o = studyitemconnection::connect($fromid, $toid); $o = studyitemconnection::connect($fromid, $toid);
return $o->model(); return $o->model();
@ -830,8 +873,19 @@ class studyplanservice extends \external_api {
*/ */
public static function disconnect_studyitems($fromid, $toid) { public static function disconnect_studyitems($fromid, $toid) {
// Validate permissions. // Validate permissions.
webservicehelper::require_capabilities(self::CAP_EDIT, studyitem::find_by_id($fromid)->context()); $studyplan = studyitem::find_by_id($fromid)->studyline()->studyplan();
webservicehelper::require_capabilities(self::CAP_EDIT, studyitem::find_by_id($toid)->context()); $toplan = studyitem::find_by_id($toid)->studyline()->studyplan();
if ($toplan->id() != $studyplan->id()) {
throw new \webservice_access_exception("The items to connect need to be in the same studyplan" );
}
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
}
return studyitemconnection::disconnect($fromid, $toid)->model(); return studyitemconnection::disconnect($fromid, $toid)->model();
} }
@ -946,7 +1000,12 @@ class studyplanservice extends \external_api {
$results = []; $results = [];
$page = studyplanpage::find_by_id(($pageid)); $page = studyplanpage::find_by_id(($pageid));
webservicehelper::require_capabilities(self::CAP_EDIT,$page->studyplan()->context()); $studyplan = $page->studyplan();
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context());
}
$badges = badgeinfo::find_page_related_badges($page,$search,$active,$includecoursebadges); $badges = badgeinfo::find_page_related_badges($page,$search,$active,$includecoursebadges);
foreach ($badges as $badgeinfo) { foreach ($badges as $badgeinfo) {
@ -996,10 +1055,11 @@ class studyplanservice extends \external_api {
// Find related course and course context. // Find related course and course context.
$coursecontext = gradeinfo::get_coursecontext_by_id($gradeid); $coursecontext = gradeinfo::get_coursecontext_by_id($gradeid);
// Do sanity checks. // Do sanity checks.
\external_api::validate_context($coursecontext);
$studyplan = studyitem::find_by_id($itemid)->studyline()->studyplan();
\external_api::validate_context($studyplan->context());
// Check correct capabilities. // Check correct capabilities.
if (has_capability('local/treestudyplan:editstudyplan', studyitem::find_by_id($itemid)->context()) || if ($studyplan->is_coach() || has_capability(self::CAP_EDIT, $studyplan->context()) ||
is_enrolled($coursecontext, $USER, 'local/treestudyplan:selectowngradables')) { is_enrolled($coursecontext, $USER, 'local/treestudyplan:selectowngradables')) {
return gradeinfo::include_grade($gradeid, $itemid, $include, $required)->model(); return gradeinfo::include_grade($gradeid, $itemid, $include, $required)->model();
} else { } else {
@ -1044,18 +1104,17 @@ class studyplanservice extends \external_api {
public static function require_competency($competencyid, $itemid, $required) { public static function require_competency($competencyid, $itemid, $required) {
global $USER; global $USER;
$item = studyitem::find_by_id($itemid); $item = studyitem::find_by_id($itemid);
\external_api::validate_context($item->context());
// Find related course and course context. // Find related course and course context.
if($item->courseid()) { if($item->courseid()) {
$coursecontext = \context_course::instance($item->courseid()); $coursecontext = \context_course::instance($item->courseid());
// Do sanity checks.
\external_api::validate_context($coursecontext);
} else { } else {
$coursecontext = null; $coursecontext = null;
\external_api::validate_context($item->context());
} }
// Check correct capabilities. // Check correct capabilities.
if (has_capability('local/treestudyplan:editstudyplan', $item->context()) || $studyplan = $item->studyline()->studyplan();
if ($studyplan->is_coach() || has_capability('local/treestudyplan:editstudyplan', $studyplan->context()) ||
($coursecontext && is_enrolled($coursecontext, $USER, 'local/treestudyplan:selectowngradables'))) { ($coursecontext && is_enrolled($coursecontext, $USER, 'local/treestudyplan:selectowngradables'))) {
return coursecompetencyinfo::require_competency($competencyid, $itemid, $required)->model(); return coursecompetencyinfo::require_competency($competencyid, $itemid, $required)->model();
} else { } else {
@ -1781,7 +1840,12 @@ class studyplanservice extends \external_api {
*/ */
public static function set_studyitem_span($id, $span = null) { public static function set_studyitem_span($id, $span = null) {
$o = studyitem::find_by_id($id); $o = studyitem::find_by_id($id);
webservicehelper::require_capabilities(self::CAP_EDIT, $o->context()); $studyplan = $o->studyline()->studyplan();
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_EDIT, $o->context());
}
$config = [ 'span' => $span]; $config = [ 'span' => $span];
$o->edit($config); $o->edit($config);

View file

@ -22,23 +22,24 @@
require_once("../../config.php"); require_once("../../config.php");
use local_treestudyplan\contextinfo; use \local_treestudyplan\premium;
use \local_treestudyplan\courseservice;
require_once($CFG->libdir.'/weblib.php'); require_once($CFG->libdir.'/weblib.php');
$systemcontext = context_system::instance(); $systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/view-plan.php", array()); $PAGE->set_url("/local/treestudyplan/coach.php", array());
require_login(); require_login();
$PAGE->set_pagelayout('base'); $PAGE->set_pagelayout('base');
//$PAGE->set_context($studyplancontext); $PAGE->set_context($systemcontext);
$PAGE->set_title(get_string('coaching_plans', 'local_treestudyplan')." - ".$contextname); $PAGE->set_title(get_string('coaching_plans', 'local_treestudyplan'));
$PAGE->set_heading(get_string('coaching_plans', 'local_treestudyplan')." - ".$contextname); $PAGE->set_heading(get_string('coaching_plans', 'local_treestudyplan'));
premium::require_premium();
// Load javascripts and specific css. // Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css')); $PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
@ -93,7 +94,7 @@ print $OUTPUT->header();
variant="primary" variant="primary"
> >
<template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template> <template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template>
<template #defaultlabel><span class='text-primary'><?php t("showoverview"); ?></span></template> <template #defaultlabel><span class='text-primary'><?php t("coacheditmode"); ?></span></template>
</s-prevnext-selector> </s-prevnext-selector>
</div> </div>
</template> </template>
@ -103,15 +104,14 @@ print $OUTPUT->header();
>{{selectedstudent.firstname}} {{selectedstudent.lastname}} - {{displayedstudyplan.name}}</h2> >{{selectedstudent.firstname}} {{selectedstudent.lastname}} - {{displayedstudyplan.name}}</h2>
<h2 v-else-if='displayedstudyplan'><?php t("showoverview"); ?> - {{displayedstudyplan.name}}</h2> <h2 v-else-if='displayedstudyplan'><?php t("showoverview"); ?> - {{displayedstudyplan.name}}</h2>
<template v-if="!loadingstudyplan && displayedstudyplan"> <template v-if="!loadingstudyplan && displayedstudyplan">
<r-studyplan v-if="!editmode" <r-studyplan v-if="selectedstudent"
v-model='displayedstudyplan' :teachermode='!selectedstudent' v-model='displayedstudyplan'
coaching
></r-studyplan> ></r-studyplan>
<t-studyplan <t-studyplan
v-else v-else
v-model='displayedstudyplan' v-model='displayedstudyplan'
@toggletoolbox="toggletoolbox" coaching
@pagechanged="onPageChange"
coachmode
></t-studyplan> ></t-studyplan>
</template> </template>
<div v-else-if='loadingstudyplan' class="spinner-border text-primary" role="status"> <div v-else-if='loadingstudyplan' class="spinner-border text-primary" role="status">

View file

@ -284,6 +284,7 @@ $string["selectstudent_btn"] = "View student plans";
$string["selectstudent"] = "Choose student"; $string["selectstudent"] = "Choose student";
$string["selectstudent_details"] = "Pick a student from the list below to see their progress in this study plan"; $string["selectstudent_details"] = "Pick a student from the list below to see their progress in this study plan";
$string["showoverview"] = "Teacher view"; $string["showoverview"] = "Teacher view";
$string["coacheditmode"] = "Edit content";
$string["open"] = "Open"; $string["open"] = "Open";
$string["noenddate"] = "Ongoing"; $string["noenddate"] = "Ongoing";
$string["back"] = "Back"; $string["back"] = "Back";
@ -468,6 +469,7 @@ $string["settingdesc_premium_key"] = 'Paste the premium key you received in the
$string["premiumfeature:morestudyplans"] = 'Creating more than 5 studyplans in a single category is a premium feature.'; $string["premiumfeature:morestudyplans"] = 'Creating more than 5 studyplans in a single category is a premium feature.';
$string["premiumfeature:morecategories"] = 'Creating studyplans in more than 20 categories is a premium feature.'; $string["premiumfeature:morecategories"] = 'Creating studyplans in more than 20 categories is a premium feature.';
$string["premiumfeature:warning"] = 'The features on this page are only accessible if your site has premium features enabled.';
$string["overall"] = 'Course'; $string["overall"] = 'Course';
$string["studyplan_report"] = 'Studyplan result overview'; $string["studyplan_report"] = 'Studyplan result overview';
$string["overviewreport:all"] = 'Result overview'; $string["overviewreport:all"] = 'Result overview';
@ -490,4 +492,4 @@ $string["line_cannot_enrol"] = 'You cannot register yourself for this line';
$string["line_can_enrol"] = 'You can register for this line'; $string["line_can_enrol"] = 'You can register for this line';
$string["line_is_enrolled"] = 'You are registered for this line'; $string["line_is_enrolled"] = 'You are registered for this line';
$string["line_enrolled_in"] = 'Registered in {$a}'; $string["line_enrolled_in"] = 'Registered in {$a}';
$string["switch_coach_editmode"] = "Edit studyplan";

View file

@ -285,6 +285,7 @@ $string["selectstudent_btn"] = "Bekijk studentenvoortgang";
$string["selectstudent"] = "Kies een student"; $string["selectstudent"] = "Kies een student";
$string["selectstudent_details"] = "Kies een student uit de lijst om zijn/haar voortgang in dit studieplan te bekijken"; $string["selectstudent_details"] = "Kies een student uit de lijst om zijn/haar voortgang in dit studieplan te bekijken";
$string["showoverview"] = "Docentenweergave"; $string["showoverview"] = "Docentenweergave";
$string["coacheditmode"] = "Bewerk inhoud";
$string["open"] = "Openen"; $string["open"] = "Openen";
$string["noenddate"] = "&infin;"; $string["noenddate"] = "&infin;";
$string["back"] = "Terug"; $string["back"] = "Terug";
@ -469,6 +470,7 @@ $string["settingdesc_premium_key"] = 'Premium activation key';
$string["premiumfeature:morestudyplans"] = 'Meer dan 5 studieplannen in één categorie aanmaken kan alleen met premium toegang.'; $string["premiumfeature:morestudyplans"] = 'Meer dan 5 studieplannen in één categorie aanmaken kan alleen met premium toegang.';
$string["premiumfeature:morecategories"] = 'In meer dan 20 categoriën een studieplan aanmaken kan alleen met premium toegang.'; $string["premiumfeature:morecategories"] = 'In meer dan 20 categoriën een studieplan aanmaken kan alleen met premium toegang.';
$string["premiumfeature:warning"] = 'De fucnties in deze pagina kun je alleen gebruiken met premium toegang.';
$string["overall"] = 'Cursus voltooid'; $string["overall"] = 'Cursus voltooid';
$string["studyplan_report"] = 'Studieplan resultatenoverzicht'; $string["studyplan_report"] = 'Studieplan resultatenoverzicht';
$string["overviewreport:all"] = 'Resultatenoverzicht'; $string["overviewreport:all"] = 'Resultatenoverzicht';
@ -491,3 +493,4 @@ $string["line_cannot_enrol"] = 'Je kunt je niet zelf inschrijven voor deze leerl
$string["line_can_enrol"] = 'Je kunt jezelf inschrijven voor deze leerlijn'; $string["line_can_enrol"] = 'Je kunt jezelf inschrijven voor deze leerlijn';
$string["line_is_enrolled"] = 'Je bent ingeschreven voor deze leerlijn'; $string["line_is_enrolled"] = 'Je bent ingeschreven voor deze leerlijn';
$string["line_enrolled_in"] = 'Ingeschreven in {$a}'; $string["line_enrolled_in"] = 'Ingeschreven in {$a}';
$string["switch_coach_editmode"] = "Studieplan bewerken";

84
lib.php
View file

@ -416,15 +416,12 @@ function local_treestudyplan_pluginfile(
// Make sure the filearea is one of those used by the plugin. // Make sure the filearea is one of those used by the plugin.
if (in_array($filearea,["studyplan","icon"])) { if (in_array($filearea,["studyplan","icon","studyplanpage"])) {
// The args is an array containing [itemid, path]. // The args is an array containing [itemid, path].
// Fetch the itemid from the path. // Fetch the itemid from the path.
$itemid = array_shift($args); $itemid = array_shift($args);
$plan = studyplan::find_by_id($itemid); // Studyplan icons and description images are not secret, so don't overdo it on access control...
$planctx = $plan->context();
// Studyplan icons are not secret, so don't check for access..
if ( true ) { if ( true ) {
// Extract the filename / filepath from the $args array // Extract the filename / filepath from the $args array
$filename = array_pop($args); // The last item in the $args array. $filename = array_pop($args); // The last item in the $args array.
@ -449,40 +446,6 @@ function local_treestudyplan_pluginfile(
return false; return false;
} }
} else if (in_array($filearea,["studyplanpage"])) {
// The args is an array containing [itemid, path].
// Fetch the itemid from the path.
$itemid = array_shift($args);
$page = studyplanpage::find_by_id($itemid);
$plan = $page->studyplan();
$planctx = $plan->context();
// Check if the current user has access to this studyplan
if ( webservicehelper::has_capabilities($studyplan_filecaps,$planctx) || $plan->has_linked_user($USER)) {
// Extract the filename / filepath from the $args array
$filename = array_pop($args); // The last item in the $args array.
if (empty($args)) {
// $args is empty => the path is '/'.
$filepath = '/';
} else {
// $args contains the remaining elements of the filepath.
$filepath = '/' . implode('/', $args) . '/';
}
// Retrieve the file from the Files API.
$fs = get_file_storage();
$file = $fs->get_file(\context_system::instance()->id, 'local_treestudyplan', $filearea, $itemid, $filepath, $filename);
if (!$file) {
// The file does not exist.
return false;
}
// We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering.
send_stored_file($file, 24*60*60, 0, $forcedownload, $options);
} else {
return false;
}
} else if (in_array($filearea,['defaulticon'])) { } else if (in_array($filearea,['defaulticon'])) {
// The args is an array containing [itemid, path]. // The args is an array containing [itemid, path].
// Fetch the itemid from the path. // Fetch the itemid from the path.
@ -514,46 +477,3 @@ function local_treestudyplan_pluginfile(
} }
/**
* Things to do on the start of every instance.
*
* Beware: do not access theme in this function, find another hook!!
*/
/*
function local_treestudyplan_after_config() {
global $PAGE;
/* WARNINGS from the documentation about this callback:
This callback is very "raw" and so there are a number of edge cases that you should think about and possibly protect against. If this callback throws an exception that means you can very easily brick your moodle site
- running while in unit tests
- during install
- during upgrade
- generic exceptions of any type
Also depending on the defines set before config.php is required, you may not have a $USER object, or other apis, eg if ABORT_AFTER_CONFIG or NO_MOODLE_COOKIES is defined. */
/*
// So, catch all exceptions to avoid page breaking. We should never break the page in this function.
try {
// Check if $PAGE is available and valid for use.
if (!empty($PAGE) && is_object($PAGE) && method_exists($PAGE,"add_body_class")) {
// Check to see if the theme is based on classic or boost to see if we need to add a polyfill...
$bootstrapbased = false;
foreach ($PAGE->theme->parents as $p) {
if ($p->name == 'boost' || $p->name == 'classic') {
$bootstrapbased = true;
break;
}
}
if (!$bootstrapbased) {
//$PAGE->add_body_class("bootstrap-polyfill"); // Enable the polyfill for boost styling.
}
}
} catch (\Exception $x) {
}
}
*/