added coach association to studyplan

This commit is contained in:
PMKuipers 2024-03-08 17:05:07 +01:00
parent a57ee3d884
commit c480c20098
16 changed files with 657 additions and 23 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -19,6 +19,8 @@ import {ProcessStudyplan, ProcessStudyplanPage, objCopy} from './studyplan-proce
import TSComponents from './treestudyplan-components';
import {eventTypes as editSwEventTypes} from 'core/edit_switch';
import { premiumenabled, premiumstatus } from "./util/premium";
import FitTextVue from './util/fittext-vue';
// Make π available as a constant
const π = Math.PI;
@ -53,7 +55,9 @@ const ENROLLABLE_SELF_ROLE = 3;
export default {
install(Vue/*,options*/){
Vue.use(TSComponents);
Vue.use(FitTextVue);
let debug = new Debugger("treestudyplan-viewer");
let lastCaller = null;
/**
* Scroll current period into view
@ -972,6 +976,8 @@ export default {
},
load_students() {
const self=this;
self.students=null;
self.can_unenrol=false;
call([{
methodname: 'local_treestudyplan_list_line_enrolled_students',
args: { id: self.value.id },
@ -995,9 +1001,10 @@ export default {
:data-studyline="value.id" ref="mainEl"
><div class="r-studyline-handle" :style="'background-color: ' + value.color"></div>
<div class="r-studyline-title"><div>
<abbr v-b-tooltip.hover :title="value.name">{{ value.shortname }}</abbr><br>
<abbr v-b-tooltip.hover :title="value.name">{{ value.shortname }}</abbr>
<template v-if="premiumenabled() && enrollable">
<template v-if="teachermode">
<br>
<a v-if="!can_enrol"
href='#' @click.prevent=""
v-b-modal="'r-enrollments-'+value.id"
@ -1060,9 +1067,6 @@ export default {
>{{format_datetime(student.enrol.enrolled_time)}}</span></td>
<td><span v-if="student.enrol.enrolled"
>{{student.enrol.enrolled_by}}</span></td>
<td v-if="can_enrol"><b-button variant="success"
@click="enrol_student(student.user)"
>{{text.enrol}}</b-button></td>
<td ><b-button v-if="!student.enrol.enrolled && (can_enrol || can_unenrol)"
variant="success"
size="sm"
@ -1080,22 +1084,26 @@ export default {
</table>
</b-modal>
</template>
<template v-else>
<template v-else-if="value.enrol.selfview">
<br>
<a v-if="!enrolled && !can_enrol"
href='#' @click.prevent=""
@click.prevent=""
href='#'
v-b-tooltip.focus
:title="text.cannot_enrol"
><i class='fa fa-lock text-danger'></i>&nbsp;{{text.cannot_enrol}}</a>
><fittext maxsize="12pt"><i class='fa fa-lock text-danger'></i>&nbsp;{{text.cannot_enrol}}</fittext></a>
<a v-else-if="!enrolled && can_enrol"
href='#' @click.prevent=""
@click.prevent=""
href='#'
v-b-modal="'r-enrol-'+value.id"
:title="text.can_enrol"
><i class='fa fa-unlock-alt text-info'></i>&nbsp;{{text.enrol}}</a>
><fittext maxsize="12pt"><i class='fa fa-unlock-alt text-info'></i>&nbsp;{{text.enrol}}</fittext></a>
<a v-else-if="enrolled"
href='#' @click.prevent=""
@click.prevent=""
href='#'
v-b-modal="'r-enrollment-'+value.id"
:title="text.enrolled"
><i class='fa fa-unlock text-success'></i>&nbsp;{{text.enrolled}}</a>
><fittext maxsize="12pt"><i class='fa fa-unlock text-success'></i>&nbsp;{{text.enrolled}}</fittext></a>
<b-modal
:id="'r-enrol-'+value.id"
:title="text.confirm"
@ -1117,6 +1125,11 @@ export default {
<b>{{by}}</b> {{this.value.enrol.enrolled_by}}</p>
</b-modal>
</template>
<template v-else>
&nbsp;
<i v-if="!enrolled" class='fa fa-lock text-danger'></i>
<i v-else class='fa fa-unlock text-success'></i>
</template>
</template>
</div></div>
</div>

View File

@ -200,14 +200,17 @@ export default {
associations: 'associations',
associated_cohorts: 'associated_cohorts',
associated_users: 'associated_users',
associated_coaches: 'associated_coaches',
associate_cohorts: 'associate_cohorts',
associate_users: 'associate_users',
associate_coached: 'associate_coaches',
add_association: 'add_association',
delete_association: 'delete_association',
associations_empty: 'associations_empty',
associations_search: 'associations_search',
cohorts: 'cohorts',
users: 'users',
coaches: 'coaches',
selected: 'selected',
name: 'name',
context: 'context',
@ -762,15 +765,17 @@ export default {
association: {
cohorts: [],
users: [],
coaches: []
},
loading: {
cohorts: false,
users: false,
coaches: false,
},
search: {users: [], cohorts:[]},
search: {users: [], cohorts:[], coaches:[]},
selected: {
search: {users: [] , cohorts:[]},
associated: {users: [] , cohorts:[]}
search: {users: [] , cohorts:[], coaches: []},
associated: {users: [] , cohorts:[], coaches: []}
},
text: strings.studyplan_associate,
};
@ -784,6 +789,7 @@ export default {
},
methods: {
premiumenabled,
showModal(){
this.show = true;
this.loadAssociations();
@ -819,6 +825,17 @@ export default {
self.association.cohorts = response.map(self.cohortOptionModel);
self.loading.cohorts = false;
}).catch(notification.exception);
if(premiumenabled()) {
self.loading.coaches = true;
call([{
methodname: 'local_treestudyplan_associated_coaches',
args: { studyplan_id: self.value.id,}
}])[0].then(function(response){
self.association.coaches = response.map(self.userOptionModel);
self.loading.coaches = false;
}).catch(notification.exception);
}
},
searchCohorts(searchtext){
const self = this;
@ -937,6 +954,71 @@ export default {
}
call(requests);
},
searchCoaches(searchtext){
if(premiumenabled()){
const self = this;
if(searchtext.length > 0)
{
call([{
methodname: 'local_treestudyplan_find_coach',
args: { like: searchtext, exclude_id: self.value.id}
}])[0].then(function(response){
self.search.coaches = response.map(self.userOptionModel);
}).catch(notification.exception);
}
else {
self.search.coaches = [];
}
}
},
coachAssociate(){
if(premiumenabled()){
const self = this;
let requests = [];
const associated = self.association.coaches;
const search = self.search.coaches;
const searchselected = self.selected.search.coaches;
for(const i in searchselected){
const r = searchselected[i];
requests.push({
methodname: 'local_treestudyplan_connect_coach',
args: {studyplan_id: self.value.id, user_id: r},
fail: notification.exception,
done: function(response){
if(response.success){
transportItem(associated,search,r);
}
}
});
}
call(requests);
}
},
coachDisassociate(){
if(premiumenabled()){
const self = this;
let requests = [];
const associated = self.association.coaches;
const associatedselected = self.selected.associated.coaches;
const search = self.search.coaches;
for(const i in associatedselected){
const r = associatedselected[i];
requests.push({
methodname: 'local_treestudyplan_disconnect_coach',
args: {studyplan_id: self.value.id, user_id: r},
fail: notification.exception,
done: function(response){
if(response.success){
transportItem(search,associated,r);
}
}
});
}
call(requests);
}
},
}
,
template:
@ -1041,6 +1123,52 @@ export default {
</b-row>
</b-container>
</b-tab>
<b-tab :title="text.coaches">
<b-container>
<b-row class='mb-2 mt-2'>
<b-col>{{text.associated_coaches}}</b-col>
<b-col>{{text.associate_coaches}}</b-col>
</b-row>
<b-row class='mb-2'>
<b-col>
</b-col>
<b-col>
<b-form-input
type="text"
@input="searchCoaches($event)"
placeholder="Search coaches"></b-form-input>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-select
multiple
v-model="selected.associated.coaches"
:options="association.coaches"
:select-size="10"
></b-form-select>
</b-col>
<b-col>
<b-form-select
multiple
v-model="selected.search.coaches"
:options="search.coaches"
:select-size="10"
></b-form-select>
</b-col>
</b-row>
<b-row class='mt-2'>
<b-col>
<b-button variant='danger' @click.prevent="coachDisassociate()"
><i class='fa fa-chain-broken'></i>&nbsp;{{text.delete_association}}</b-button>
</b-col>
<b-col>
<b-button variant='success' @click.prevent="coachAssociate()"
><i class='fa fa-link'></i>&nbsp;{{text.add_association}}</b-button>
</b-col>
</b-row>
</b-container>
</b-tab>
</b-tabs>
</b-modal>
</span>

View File

@ -42,7 +42,11 @@ class associationservice extends \external_api {
* @var string
*/
const CAP_VIEW = "local/treestudyplan:viewuserreports";
/**
* Capability required to be linked as coach to a studyplan
* @var string
*/
const CAP_COACH = "local/treestudyplan:coach";
/**
* Webservice structure to use in describing a user
*/
@ -705,4 +709,198 @@ class associationservice extends \external_api {
}
/**
* Parameter description for webservice function connect_user
*/
public static function connect_coach_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
"user_id" => new \external_value(PARAM_INT, 'id of user to link', VALUE_OPTIONAL),
] );
}
/**
* Return value description for webservice function connect_user
*/
public static function connect_coach_returns() : \external_description {
return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
"msg" => new \external_value(PARAM_TEXT, 'message'),
]);
}
/**
* Connect a user to a studyplan
* @param mixed $studyplanid Id of studyplan
* @param mixed $userid Id of user
* @return array Success/fail model
*/
public static function connect_coach($studyplanid, $userid) {
global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
$user = $DB->get_record("user",["id" => $userid]);
if( has_capability(self::CAP_COACH,$studyplan->context(),$user)) {
if (!$DB->record_exists('local_treestudyplan_coach', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) {
$id = $DB->insert_record('local_treestudyplan_coach', [
'studyplan_id' => $studyplanid,
'user_id' => $userid,
]);
return ['success' => true, 'msg' => 'User connected as coach'];
} else {
return ['success' => true, 'msg' => 'User already connected as coach'];
}
} else {
return ['success' => false, 'msg' => 'User does not have coach capability in this studyplan\'s context'];
}
}
/**
* Parameter description for webservice function disconnect_user
*/
public static function disconnect_coach_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
"user_id" => new \external_value(PARAM_INT, 'id of user to link', VALUE_OPTIONAL),
] );
}
/**
* Return value description for webservice function disconnect_user
*/
public static function disconnect_coach_returns() : \external_description {
return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
"msg" => new \external_value(PARAM_TEXT, 'message'),
]);
}
/**
* Disconnect a user from a studyplan
* @param mixed $studyplanid Id of studyplan
* @param mixed $userid Id of user
* @return array Success/fail model
*/
public static function disconnect_coach($studyplanid, $userid) {
global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
if ($DB->record_exists('local_treestudyplan_coach', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) {
$DB->delete_records('local_treestudyplan_coach', [
'studyplan_id' => $studyplanid,
'user_id' => $userid,
]);
return ['success' => true, 'msg' => 'User Disconnected as coach'];
} else {
return ['success' => true, 'msg' => 'Connection does not exist'];
}
}
/**
* Parameter description for webservice function find_user
*/
public static function find_coach_parameters() : \external_function_parameters {
return new \external_function_parameters( [
'like' => new \external_value(PARAM_TEXT, 'search text'),
'exclude_id' => new \external_value(PARAM_INT, 'exclude members of this studyplan', VALUE_OPTIONAL),
'context_id' => new \external_value(PARAM_INT, 'context for this request', VALUE_OPTIONAL),
] );
}
/**
* Return value description for webservice function find_user
*/
public static function find_coach_returns() : \external_description {
return new \external_multiple_structure(self::user_structure());
}
/**
* Search users for match
* @param string $like String to match user firstname/lastname with
* @param null $excludeid Do not include coaches connected to this studyplan id
* @param int $contextid Context to search (default system)
* @return array
*/
public static function find_coach($like, $excludeid = null, $contextid = 1) {
global $CFG, $DB;
// Only allow this if the user has the right to edit in this context.
$context = webservicehelper::find_context($contextid);
webservicehelper::require_capabilities(self::CAP_EDIT, $context);
$pattern = "%{$like}%";
$params = ["pattern_fn" => $pattern,
"pattern_ln" => $pattern,
"pattern_un" => $pattern,
];
$sql = "SELECT DISTINCT u.* FROM {user} u LEFT JOIN {local_treestudyplan_coach} j ON u.id = j.user_id
WHERE u.deleted != 1 AND (firstname LIKE :pattern_fn OR lastname LIKE :pattern_ln OR username LIKE :pattern_un)";
if (isset($excludeid) && is_numeric($excludeid)) {
$sql .= " AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
$params['exclude_id'] = $excludeid;
}
$users = [];
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $r) {
if (has_capability(self::CAP_COACH,$context,$r)) {
$users[] = static::make_user_model($r);
}
}
$rs->close();
self::sortusermodels($users);
return $users;
}
/**
* Parameter description for webservice function associated_users
*/
public static function associated_coaches_parameters() : \external_function_parameters {
return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
] );
}
/**
* Return value description for webservice function associated_users
*/
public static function associated_coaches_returns() : \external_description {
return new \external_multiple_structure(self::user_structure());
}
/**
* List all users associated to a studyplan
* @param mixed $studyplanid Id of studyplan
* @return array
*/
public static function associated_coaches($studyplanid) {
global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
$sql = "SELECT DISTINCT u.* FROM {user} u INNER JOIN {local_treestudyplan_coach} j ON j.user_id = u.id
WHERE j.studyplan_id = :studyplan_id
ORDER BY u.lastname, u.firstname";
$rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplanid]);
$users = [];
foreach ($rs as $u) {
$user = self::make_user_model($u);
$users[] = $user;
}
$rs->close();
self::sortusermodels($users);
return $users;
}
}

View File

@ -325,6 +325,7 @@ class studyline {
"enrolled" => new \external_value(PARAM_BOOL, 'student is enrolled',VALUE_OPTIONAL),
"enrolled_time" => new \external_value(PARAM_INT, 'moment of enrollment',VALUE_OPTIONAL),
"enrolled_by" => new \external_value(PARAM_TEXT, 'Name of enrolling user',VALUE_OPTIONAL),
"selfview" => new \external_value(PARAM_BOOL, 'viewing user is the student',VALUE_OPTIONAL),
],"Enrollment info",$value);
}
@ -597,6 +598,7 @@ class studyline {
"enrolled" => $enrolled,
"enrolled_time" => $enrolled_time,
"enrolled_by" => $enrolled_by,
"selfview" => boolval($userid == $USER->id),
];
$model = array_merge($model,$usermodel);
} else {

201
coach.php Normal file
View File

@ -0,0 +1,201 @@
<?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/>.
/**
* View study plans - teacher view and student view
* @package local_treestudyplan
* @copyright 2023 P.M. Kuipers
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("../../config.php");
use local_treestudyplan\contextinfo;
use \local_treestudyplan\courseservice;
require_once($CFG->libdir.'/weblib.php');
$systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/view-plan.php", array());
require_login();
// Figure out the context (category or system, based on either category or context parameter).
$categoryid = optional_param('categoryid', 0, PARAM_INT); // Category id.
$contextid = optional_param('contextid', 0, PARAM_INT); // Context id.
if ($categoryid > 0) {
$studyplancontext = context_coursecat::instance($categoryid);
} else if ($contextid > 0) {
$studyplancontext = context::instance_by_id($contextid);
if (in_array($studyplancontext->contextlevel, [CONTEXT_SYSTEM, CONTEXT_COURSECAT])) {
$categoryid = $studyplancontext->instanceid;
} else {
$studyplancontext = $systemcontext;
}
} else {
// If no context is selected, find the first available one.
$availablecontexts = courseservice::list_available_categories("view");
$contextid = 1; // Fallback to system context.
foreach ($availablecontexts as $ctx) {
if ($ctx["studyplancount"] > 0) {
$contextid = $ctx["context_id"];
break;
}
}
// Reload page with selected category.
$url = new \moodle_url('/local/treestudyplan/view-plan.php', ["contextid" => $contextid]);
header('Location: '.$url->out(false), true, 302);
exit;
}
$ci = new contextinfo($studyplancontext);
$contextname = $ci->pathstr();
$PAGE->set_pagelayout('base');
//$PAGE->set_context($studyplancontext);
$PAGE->set_title(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
$PAGE->set_heading(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
if ($studyplancontext->id > 1) {
navigation_node::override_active_url(new moodle_url('/course/index.php', ['categoryid' => $categoryid ]));
$PAGE->navbar->add(get_string('view_plan', 'local_treestudyplan'));
// Coursecat context
$cat = \core_course_category::get($studyplancontext->instanceid,IGNORE_MISSING,true); // We checck visibility later
} else {
// System context
$cat = \core_course_category::top();
}
if (!$cat->is_uservisible()) {
throw new \moodle_exception("error:cannotviewcategory","local_treestudyplan","/local/treestudyplan/view_plan.php",$contextname);
}
if(!has_capability('local/treestudyplan:viewuserreports', $studyplancontext)) {
throw new \moodle_exception("error:nostudyplanviewaccess","local_treestudyplan","/local/treestudyplan/view_plan.php",$contextname);
}
// Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
}
$PAGE->requires->js_call_amd('local_treestudyplan/page-view-plan', 'init', [$studyplancontext->id, $categoryid]);
/**
* Shortcut function to provide translations
*
* @param mixed $str Translation key
* @param null|string[] $param Parameters to pass to translation
* @param string $plugin Location to search for translation strings
* @return string Translation of key
*/
function t($str, $param = null, $plugin = 'local_treestudyplan') {
print get_string($str, $plugin, $param);
}
print $OUTPUT->header();
?>
<div id='root'>
<div class='vue-loader' v-show='false'>
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<div v-cloak>
<div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3 s-context-selector'>
<b-form-select text='<?php print($contextname);?>' :value="contextid" @change='switchContext'
:class="(!(usedcontexts.length))?'text-primary':''">
<b-form-select-option v-if='!(usedcontexts.length)' :value="contextid"
:class="'text-primary'">
<span><?php t("loading",null,"core"); ?>...</span></b-form-select-option>
<b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id"
:class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''"
><span v-for="(p, i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span>
<span>({{ ctx.studyplancount }})</span></b-form-select-option>
</b-form-select>
<div v-if="!(usedcontexts.length)" style="position: relative; top: 0.3rem; width: 1.2rem; height: 1.2rem; font-size: 0.7rem;"
class="spinner-border text-primary" role="status"></div>
</div>
<h3 v-else><?php print $contextname; ?></h3>
<div class="m-buttonbar" style="margin-bottom: 1em;">
<a href='#' v-if='displayedstudyplan' @click.prevent='closeStudyplan'
><i style='font-size: 150%;' class='fa fa-chevron-left'></i> <?php t('back');?></a>
<span v-if='displayedstudyplan'><?php t("studyplan_select"); ?></span>&nbsp;
<b-form-select v-if='displayedstudyplan' lazy :text='dropdown_title' :value='displayedstudyplan.id'>
<b-form-select-option
v-for='(studyplan, planindex) in studyplans'
:key='studyplan.id'
@click='selectStudyplan(studyplan)'
:value='studyplan.id'
>{{ studyplan.name }}</b-form-select-option>
</b-form-select>&nbsp;
<s-studyplan-details
v-model="displayedstudyplan"
v-if="displayedstudyplan && displayedstudyplan.description"
></s-studyplan-details>
<div class="flex-grow-1"><!-- Spacer to align student selector right --></div>
<div v-if="displayedstudyplan && displayedstudyplan.description">
<span><?php t('selectstudent_btn') ?></span>
<s-prevnext-selector
:options="associatedstudents"
title="firstname"
v-model="selectedstudent"
defaultselectable
grouped
optionsfield='users'
arrows
@change="showStudentView"
class="ml-2"
variant="primary"
>
<template v-slot="{value}">{{value.firstname}} {{value.lastname}}</template>
<template #defaultlabel><span class='text-primary'><?php t("showoverview"); ?></span></template>
</s-prevnext-selector>
</div>
</div>
<div class='t-studyplan-container'>
<h2 v-if='displayedstudyplan&& selectedstudent'
>{{selectedstudent.firstname}} {{selectedstudent.lastname}} - {{displayedstudyplan.name}}</h2>
<h2 v-else-if='displayedstudyplan'><?php t("showoverview"); ?> - {{displayedstudyplan.name}}</h2>
<r-studyplan v-if='!loadingstudyplan && displayedstudyplan'
v-model='displayedstudyplan' :teachermode='!selectedstudent'
></r-studyplan>
<div v-else-if='loadingstudyplan' class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
<div v-else class='t-studyplan-notselected'>
<p><?php t("studyplan_noneselected"); ?></p>
<b-card-group deck>
<s-studyplan-card
v-for='(studyplan, planindex) in studyplans'
:key='studyplan.id'
v-model='studyplans[planindex]'
open
@open='selectStudyplan(studyplan)'
></s-studyplan-card>
</b-card-group>
</div>
</div>
</div>
</div>
<?php
print $OUTPUT->footer();

View File

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

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="local/treestudyplan/db" VERSION="20240225" COMMENT="XMLDB file for Moodle local/treestudyplan"
<XMLDB PATH="local/treestudyplan/db" VERSION="20240308" COMMENT="XMLDB file for Moodle local/treestudyplan"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
@ -196,5 +196,17 @@
<KEY NAME="enrolledby-id" TYPE="foreign" FIELDS="enrolledby" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="local_treestudyplan_coach" COMMENT="Default comment for the table, please edit me">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="user_id" TYPE="int" LENGTH="18" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="studyplan_id" TYPE="int" LENGTH="18" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="user_id-id" TYPE="foreign" FIELDS="user_id" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="studyplan_id-id" TYPE="foreign" FIELDS="studyplan_id" REFTABLE="local_treestudyplan" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>

View File

@ -277,7 +277,6 @@ $functions = [
'capabilities' => 'local/treestudyplan:editstudyplan',
'loginrequired' => true,
],
'local_treestudyplan_connect_user' => [ // Web service function name.
'classname' => '\local_treestudyplan\associationservice', // Class containing the external function.
'methodname' => 'connect_user', // External function name.
@ -314,6 +313,42 @@ $functions = [
'capabilities' => 'local/treestudyplan:editstudyplan',
'loginrequired' => true,
],
'local_treestudyplan_associated_coaches' => [ // Web service function name.
'classname' => '\local_treestudyplan\associationservice', // Class containing the external function.
'methodname' => 'associated_coaches', // External function name.
'description' => 'List coaches associated with a studyplan',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan',
'loginrequired' => true,
],
'local_treestudyplan_find_coach' => [ // Web service function name.
'classname' => '\local_treestudyplan\associationservice', // Class containing the external function.
'methodname' => 'find_coach', // External function name.
'description' => 'Find available coach',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan',
'loginrequired' => true,
],
'local_treestudyplan_connect_coach' => [ // Web service function name.
'classname' => '\local_treestudyplan\associationservice', // Class containing the external function.
'methodname' => 'connect_coach', // External function name.
'description' => 'Connect coach to study plan',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan',
'loginrequired' => true,
],
'local_treestudyplan_disconnect_coach' => [ // Web service function name.
'classname' => '\local_treestudyplan\associationservice', // Class containing the external function.
'methodname' => 'disconnect_coach', // External function name.
'description' => 'Disconnect coach from studyplan',
'type' => 'read', // Database rights of the web service function (read, write).
'ajax' => true,
'capabilities' => 'local/treestudyplan:editstudyplan',
'loginrequired' => true,
],
/***************************
* Category mapping
***************************/

View File

@ -580,5 +580,30 @@ function xmldb_local_treestudyplan_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2024022504, 'local', 'treestudyplan');
}
if ($oldversion < 2024030801) {
// Define table local_treestudyplan_coach to be created.
$table = new xmldb_table('local_treestudyplan_coach');
// Adding fields to table local_treestudyplan_coach.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('user_id', XMLDB_TYPE_INTEGER, '18', null, XMLDB_NOTNULL, null, null);
$table->add_field('studyplan_id', XMLDB_TYPE_INTEGER, '18', null, XMLDB_NOTNULL, null, null);
// Adding keys to table local_treestudyplan_coach.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$table->add_key('user_id-id', XMLDB_KEY_FOREIGN, ['user_id'], 'user', ['id']);
$table->add_key('studyplan_id-id', XMLDB_KEY_FOREIGN, ['studyplan_id'], 'local_treestudyplan', ['id']);
// Conditionally launch create table for local_treestudyplan_coach.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Treestudyplan savepoint reached.
upgrade_plugin_savepoint(true, 2024030801, 'local', 'treestudyplan');
}
return true;
}

View File

@ -38,6 +38,8 @@ $string["treestudyplan:configure"] = "Configure study plans";
$string["treestudyplan:viewuserreports"] = "View study plan of others";
$string["treestudyplan:forcescales"] = 'Advanced: Allow study plan manager to force assignment scales to setting (manual modes only)';
$string["treestudyplan:selectowngradables"] = 'Teachers can select gradables in their own courses in study plan view mode (manual modes only)';
$string["treestudyplan:lineunenrol"] = "Manage student registration in lines";
$string["treestudyplan:coach"] = "Available as coach";
$string["report"] = 'Progress report';
$string["report_invited"] = 'Progress report for {$a}';
@ -253,14 +255,17 @@ $string["nav_invited"] = "View study plan by invitation";
$string["associations"] = 'Associations';
$string["associated_cohorts"] = 'Linked cohorts';
$string["associated_users"] = 'Linked users';
$string["associated_coaches"] = 'Linked coaches';
$string["associate_cohorts"] = 'Search cohorts to add';
$string["associate_users"] = 'Search users to add';
$string["associate_coaches"] = 'Search coaches to add';
$string["add_association"] = 'Add';
$string["delete_association"] = 'Delete';
$string["associations_empty"] = 'No active associations';
$string["associations_search"] = 'Search';
$string["users"] = 'Users';
$string["cohorts"] = 'Cohorts';
$string["coaches"] = 'Coaches';
$string["selected"] = 'Select';
$string["name"] = 'Name';
$string["context"] = 'Category';

View File

@ -38,6 +38,9 @@ $string["treestudyplan:configure"] = "Studieplannen configureren";
$string["treestudyplan:viewuserreports"] = "Studieplannen van anderen bekijken";
$string["treestudyplan:forcescales"] = 'Gevorderd: Studieplanbeheerder kan alle opdrachten in studieplan instellen op specifieke resultaatschaal (alleen handmatige modes)';
$string["treestudyplan:selectowngradables"] = 'Docenten kunnen in hun eigen cursussen zelf activiteiten selecteren in een studieplan (docentenweergave, alleen handmatige modes)';
$string["treestudyplan:lineunenrol"] = "Beheer inschrijvingen van studenten in leerlijnen";
$string["treestudyplan:coach"] = "Beschikbaar als coach";
$string["report"] = 'Voortgangsrapport';
$string["report_invited"] = 'Voortgang van {$a}';
@ -253,14 +256,17 @@ $string["nav_invited"] = "Studieplan op uitnodiging bekijken";
$string["associations"] = 'Koppelingen';
$string["associated_cohorts"] = 'Gekoppelde site-groepen';
$string["associated_users"] = 'Gekoppelde gebruikers';
$string["associated_coaches"] = 'Gekoppelde coaches';
$string["associate_cohorts"] = 'Zoek om te koppelen';
$string["associate_users"] = 'Zoek om te koppelen';
$string["associate_coaches"] = 'Zoek coaches';
$string["add_association"] = 'Toevoegen';
$string["delete_association"] = 'Verwijderen';
$string["associations_empty"] = 'Geen koppelingen';
$string["associations_search"] = 'Zoeken';
$string["users"] = 'Gebruikers';
$string["cohorts"] = 'Site-groepen';
$string["coaches"] = 'Coaches';
$string["selected"] = 'Kies';
$string["name"] = 'Naam';
$string["context"] = 'Categorie';

View File

@ -22,10 +22,10 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494).
$plugin->version = 2024030200; // YYYYMMDDHH (year, month, day, iteration).
$plugin->version = 2024030804; // YYYYMMDDHH (year, month, day, iteration).
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11).
$plugin->release = "1.1.5";
$plugin->release = "1.1.6";
$plugin->maturity = MATURITY_BETA; /*MATURITY_STABLE;*/
// Supported from Moodle 3.11 to 4.3.