Finalized functionality for period spanning and period matching

This commit is contained in:
PMKuipers 2023-08-06 23:17:36 +02:00
parent 7b36ee3284
commit 5146ebdf0a
15 changed files with 351 additions and 155 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -194,7 +194,6 @@ export function init(contextid,categoryid) {
window.location.search = params.toString();
},
onStudyPlanCreated(newstudyplan){
debug.info("New studyplan:",newstudyplan);
app.studyplans.push(newstudyplan);
app.selectStudyplan(newstudyplan);
@ -213,7 +212,6 @@ export function init(contextid,categoryid) {
methodname: 'local_treestudyplan_get_studyplan_map',
args: { id: studyplan.id}
}])[0].done(function(response){
debug.info(response);
app.activestudyplan = ProcessStudyplan(response,true);
debug.info('studyplan processed');
app.loadingstudyplan = false;

View File

@ -24,6 +24,12 @@ const PERIOD_EDITOR_FIELDS =
const LINE_GRAVITY = 1.3;
const datechanger_globals = {
default: false,
defaultchoice: false,
hidewarn: false,
};
export default {
STUDYPLAN_EDITOR_FIELDS: STUDYPLAN_EDITOR_FIELDS, // make copy available in plugin
install(Vue/*,options*/){
@ -158,6 +164,8 @@ export default {
desc: 'course_timing_desc',
question: 'course_timing_question',
warning: 'course_timing_warning',
timing_ok: 'course_timing_ok',
timing_off: 'course_timing_off',
course: 'course$core',
period: 'period',
yes: 'yes$core',
@ -171,6 +179,7 @@ export default {
day: 'day$core',
rememberchoice: 'course_timing_rememberchoice',
hidewarning: 'course_timing_hidewarning',
periodspan: 'course_period_span',
},
studyplan_associate: {
associations: 'associations',
@ -199,7 +208,8 @@ export default {
coursetiming_present: "coursetiming_present",
coursetiming_future: "coursetiming_future",
grade_include: "grade_include",
grade_require: "grade_require",
grade_require: "grade_require",
ok: "ok$core",
},
invalid: {
error: 'error',
@ -1262,7 +1272,6 @@ export default {
this.$root.$emit('redrawLines');
},
updated() {
console.info("UPDATED Studyplan");
this.$root.$emit('redrawLines');
ItemEventBus.$emit('redrawLines');
},
@ -1334,7 +1343,6 @@ export default {
'sequence': page.studylines.length,
}
}])[0].done(function(response){
debug.info("New studyline:",response);
page.studylines.push(response);
newlineinfo.name = '';
newlineinfo.shortname = '';
@ -1349,7 +1357,6 @@ export default {
editLineFinish() {
let editedline = this.edit.studyline.data;
let originalline = this.edit.studyline.original;
debug.info('Edit Line',this.edit.studyline);
call([{
methodname: 'local_treestudyplan_edit_studyline',
args: { 'id': editedline.id,
@ -1357,14 +1364,12 @@ export default {
'shortname': editedline.shortname,
'color': editedline.color,}
}])[0].done(function(response){
debug.info('Edit response:', response);
originalline['name'] = response['name'];
originalline['shortname'] = response['shortname'];
originalline['color'] = response['color'];
}).fail(notification.exception);
},
deleteLine(page,line) {
debug.info('Delete Line',line);
const self=this;
get_strings([
{key: 'studyline_confirm_remove', param: line.name, component: 'local_treestudyplan' },
@ -1379,7 +1384,6 @@ export default {
methodname: 'local_treestudyplan_delete_studyline',
args: { 'id': line.id, }
}])[0].done(function(response){
debug.info('Delete response:', response);
if(response.success == true){
let index = page.studylines.indexOf(line);
page.studylines.splice(index, 1);
@ -1390,7 +1394,6 @@ export default {
});
},
reorderLines(event,lines){
debug.info("Reorder lines",event,lines);
// apply reordering
event.apply(lines);
@ -1404,12 +1407,10 @@ export default {
methodname: 'local_treestudyplan_reorder_studylines',
args: { 'sequence': sequence }
}])[0].done(function(response){
debug.info('Reorder response:', response);
}).fail(notification.exception);
},
deletePlan(studyplan){
const self=this;
debug.info('Delete studyplan:', studyplan);
get_strings([
{key: 'studyplan_confirm_remove', param: studyplan.name, component: 'local_treestudyplan' },
{key: 'delete', component: 'core' },
@ -1423,7 +1424,6 @@ export default {
methodname: 'local_treestudyplan_delete_studyplan',
args: { 'id': studyplan.id, }
}])[0].done(function(response){
debug.info('Delete response:', response);
if(response.success == true){
self.$root.$emit("studyplanRemoved",studyplan);
}
@ -1433,7 +1433,6 @@ export default {
});
},
deleteStudyItem(event){
debug.info('Delete studyitem:', event);
//const self = this;
let item = event.data;
@ -1441,7 +1440,6 @@ export default {
methodname: 'local_treestudyplan_delete_studyitem',
args: { 'id': item.id, }
}])[0].done(function(response){
debug.info('Delete response:', response);
if(response.success == true){
event.source.$emit('cut',event);
}
@ -1952,10 +1950,10 @@ export default {
// then on the next tick, we inform the back end
// Since moving things around has never been unsuccessful, unless you have other problems,
// it's better to have nice visuals.
this.$nextTick(() => {
self.relocateStudyItem(item).done(()=>{
self.validate_course_period();
});
self.relocateStudyItem(item).done(()=>{
if(this.$refs.timingChecker){
this.$refs.timingChecker.validate_course_period();
}
});
}
else if(event.type.component){
@ -1977,14 +1975,18 @@ export default {
}
}
}])[0].done((response) => {
console.info('Add item response:', response);
let item = response;
self.relocateStudyItem(item).done(()=>{
self.value.push(item);
self.$emit("input",self.value);
// call the validate period function on next tick,
// since it paints the item in the slot first
this.$nextTick(self.validate_course_period);
this.$nextTick(() => {
if(this.$refs.timingChecker){
this.$refs.timingChecker.validate_course_period();
}
});
});
}).fail(notification.exception);
}
@ -2000,7 +2002,6 @@ export default {
}
}
}])[0].done((response) => {
console.info('Add item response:', response);
let item = response;
self.relocateStudyItem(item).done(()=>{
self.value.push(item);
@ -2037,58 +2038,6 @@ export default {
this.hover.component = null;
this.hover.type = null;
},
validate_course_period() {
const self = this;
debug.info("Validating course and period",self.item,self.period);
if(self.item && self.item.type == 'course'){
self.datechanger.coursespan = datespaninfo(self.item.course.startdate,self.item.course.enddate);
self.datechanger.periodspan = datespaninfo(self.period.startdate,self.period.enddate);
if( self.datechanger.coursespan.first != self.datechanger.periodspan.first
|| self.datechanger.coursespan.last != self.datechanger.periodspan.last ){
debug.info("Course timing does not match period timing");
if(self.item.course.canupdatecourse){
if(!self.datechanger.default){
// Periods do not match, pop up the date change request
this.$bvModal.show("t-course-date-matching-"+this.slotkey);
} else if (self.datechanger.defaultvalue){
// go for it without asking
self.change_course_period();
}
}
else {
// user is not able to change course timing - show a warning
if(!self.datechanger.hidewarn){
this.$bvModal.show("t-course-date-warning-"+this.slotkey);
}
}
}
else {
debug.info("Course timing matches period",self.datechanger);
}
}
},
change_course_period() {
const self=this;
return call([{
methodname: 'local_treestudyplan_course_period_timing',
args: { page_id: self.page_id,
period: self.period.period,
course_id: this.item.course.id,
}
}])[0].fail(notification.exception);
},
format_duration(dsi){
let s = "";
if(dsi.years == 1){ s += `1 ${this.text.year}, `;}
else if(dsi.years > 1){ s += `${dsi.years} ${this.text.years}, `;}
if(dsi.weeks == 1){ s += `1 ${this.text.week}, `;}
else if(dsi.weeks > 1){ s += `${dsi.weeks} ${this.text.weeks}, `;}
if(dsi.days == 1){ s += `1 ${this.text.day}, `;}
else if(dsi.days > 1){ s += `${dsi.days} ${this.text.days}, `;}
return s.toLocaleLowerCase();
},
maxSpan(){
// Determine the maximum span for components in this slot
// Used for setting the max in the timing adjustment screen (s)
@ -2100,7 +2049,8 @@ export default {
for(let i = this.slotindex + 1; i <= this.page.periods; i++){
if(this.line.slots && this.line.slots[i] && this.line.slots[i].competencies){
const l = this.line.slots[i].competencies;
if(l[this.layer]) {
const f = this.line.slots[i-1].filters; // next filter is in the same slot
if(l[this.layer] || f[this.layer]) {
// slot is busy in this layer.
break;
} else {
@ -2148,7 +2098,7 @@ export default {
:data="item"
:type="makeType(item)"
@cut="onCut"
><t-item v-model="item" :plan="plan"></t-item
><t-item v-model="item" :plan="plan" :page='page' :period='period' :maxspan='maxSpan()'></t-item
></drag
><drop v-else
:class="'t-slot-drop '+type + (layer > 0?' secondary':' primary')"
@ -2180,8 +2130,171 @@ export default {
:key="hover.type">--{{ hover.type }}--</div
></template
></drop>
<t-item-timing-checker hidden
v-if="item && item.course"
ref="timingChecker"
:maxspan="maxSpan()"
:page="page"
:period="period"
v-model="item"
></t-item-timing-checker>
</div>
`,
});
Vue.component('t-item-timing-checker', {
props: {
value: {
type: Object, // t-item model
},
page: {
type: Object, // Studyplan data
},
period: {
type: Object, // Studyplan data
},
maxspan: {
type: Number,
},
hidden: {
type: Boolean,
default: false,
}
},
computed: {
endperiod() {
const endperiodnr = Math.min(this.page.periods,this.period.period + (this.value.span - 1));
return this.page.perioddesc[endperiodnr-1];
},
course_period_matches() {
const self=this;
if(self.value && self.value.type == 'course'){
self.datechanger.coursespan = datespaninfo(self.value.course.startdate,self.value.course.enddate);
self.datechanger.periodspan = datespaninfo(self.period.startdate,self.endperiod.enddate);
if( self.datechanger.coursespan.first.getTime() == self.datechanger.periodspan.first.getTime()
&& self.datechanger.coursespan.last.getTime() == self.datechanger.periodspan.last.getTime() ){
return true;
}
else {
return false;
}
} else {
debug.warn("Timing thing not proper configured",self.value,self.period,self.maxspan);
return false;
}
},
},
data() {
return {
// Create random id to avoid opening the wrong modals
id: Math.floor(Math.random() * Date.now()).toString(16),
text: strings.course_timing,
datechanger: {
coursespan: null,
periodspan: null,
globals: datechanger_globals,
}
};
},
methods: {
validate_course_period(force) {
const self = this;
debug.info("Validating course and period");
if(!(self.course_period_matches)){
debug.info("Course timing does not match period timing");
if(self.value.course.canupdatecourse){
if(!self.hidden || !self.datechanger.globals.default){
// Periods do not match, pop up the date change request
this.$bvModal.show('t-course-timing-matching-'+this.id);
} else if (self.datechanger.globals.defaultvalue){
// go for it without asking
self.change_course_period();
}
}
else {
// user is not able to change course timing - show a warning
if(!self.hidden || !self.datechanger.globals.hidewarn){
this.$bvModal.show('t-course-timing-warning-'+this.id);
}
}
}
else {
debug.info("Course timing matches period",self.datechanger);
}
},
change_course_period() {
const self=this;
// Save the state
if(self.datechanger.globals.default){
self.datechanger.globals.defaultvalue = true;
}
return call([{
methodname: 'local_treestudyplan_course_period_timing',
args: { period_id: self.period.id,
course_id: this.value.course.id,
span: this.value.span,
}
}])[0].fail(notification.exception).done((response) => {
self.value.course.startdate = response.startdate;
self.value.course.enddate = response.enddate;
self.value.course.timing = response.timing;
self.$emit("input",self.value);
});
},
change_span(span) {
const self=this;
return call([{
methodname: 'local_treestudyplan_set_studyitem_span',
args: { id: self.value.id,
span: span
}
}])[0].fail(notification.exception).done((response) => {
self.value.span = response.span;
self.$emit('input',self.value);
self.$nextTick(() => {
self.validate_course_period();
});
} );
},
format_duration(dsi){
let s = "";
if(dsi.years == 1){ s += `1 ${this.text.year}, `;}
else if(dsi.years > 1){ s += `${dsi.years} ${this.text.years}, `;}
if(dsi.weeks == 1){ s += `1 ${this.text.week}, `;}
else if(dsi.weeks > 1){ s += `${dsi.weeks} ${this.text.weeks}, `;}
if(dsi.days == 1){ s += `1 ${this.text.day}, `;}
else if(dsi.days > 1){ s += `${dsi.days} ${this.text.days}, `;}
return s.toLocaleLowerCase();
},
},
// To avoid the span creeping in the dom where it shouldn't, set display to none if it is hidden
// This does not affect the modals, which are rendered outside of this element when needed
template: `
<span :class="'t-course-timing-matcher'" :style="hidden?'display: none ':''">
<span v-if="!hidden">
<i v-if="course_period_matches" class="text-success fa fa-calendar-check-o"
v-b-tooltip.hover.topright :title="text.timing_ok"
></i
><a v-else
href='#' @click="validate_course_period()" class="text-warning"
><i class="fa fa-calendar-times-o" v-b-tooltip.hover.topright :title="text.timing_off"
></i
></a>
<span v-if='value.span > 1 || value.span < maxspan' >
{{ text.periodspan}}
<b-form-select @change="change_span" v-model="value.span">
<b-form-select-option v-for="(n,i) in maxspan" :value='n'
>{{ n }}</b-form-select-option>
</b-form-select>
</span>
</span>
<b-modal
:id="'t-course-date-matching-'+this.slotkey"
:id="'t-course-timing-matching-'+this.id"
size="lg"
:title="text.title"
@ok="change_course_period"
@ -2190,85 +2303,97 @@ export default {
:cancel-title="text.no"
cancel-variant="primary"
>
<b-container v-if="datechanger.coursespan && datechanger.periodspan && item && item.course">
<b-container v-if="datechanger.coursespan && datechanger.periodspan && value && value.course">
<b-row><b-col cols="12">{{ text.desc }}</b-col></b-row>
<b-row><b-col cols="12"><div class="generalbox alert alert-warning">{{ text.question }}</div></b-col></b-row>
<b-row>
<b-col cols="6">
<h3> {{ text.course }} </h3>
<p><b>{{ item.course.fullname }}</b></p>
<p><b>{{ item.course.shortname }}</b></p>
<p><b>{{ value.course.fullname }}</b></p>
<p><b>{{ value.course.shortname }}</b></p>
<p>{{ datechanger.coursespan.formatted.first}} - {{ datechanger.coursespan.formatted.last}}</p>
<p><b>{{ text.duration }}</b><br>
{{ format_duration(datechanger.coursespan)}}</p>
</b-col>
<b-col cols="6">
<h3> {{ text.period }} </h3>
<p><b>{{ period.fullname }}</b></p>
<p><b>{{ period.shortname }}</b></p>
<p><b>{{ period.fullname }}</b><b v-if="value.span > 1"> - {{ endperiod.fullname }}</b></p>
<p><b>{{ period.shortname }}</b><b v-if="value.span > 1"> - {{ endperiod.shortname }}</b></p>
<p>{{ datechanger.periodspan.formatted.first}} - {{ datechanger.periodspan.formatted.last}}</p>
<p><b>{{ text.duration }}</b><br>
{{ format_duration(datechanger.periodspan)}}</p>
</b-col>
</b-row>
<b-row><b-col cols="12">
<b-form-checkbox type="checkbox" v-model="datechanger.default">{{ text.rememberchoice }}</b-form-checkbox>
<b-row v-if='hidden'><b-col cols="12">
<b-form-checkbox type="checkbox" v-model="datechanger.globals.default">{{ text.rememberchoice }}</b-form-checkbox>
</b-col></b-row>
</b-container>
</b-modal>
<b-modal
:id="'t-course-date-warning-'+this.slotkey"
:id="'t-course-timing-warning-'+this.id"
size="lg"
ok-variant="primary"
:title="text.title"
:ok-title="text.yes"
ok-only
>
<b-container v-if="datechanger.coursespan && datechanger.periodspan && item && item.course">
<b-container v-if="datechanger.coursespan && datechanger.periodspan && value && value.course">
<b-row><b-col cols="12">{{ text.desc }}</b-col></b-row>
<b-row><b-col cols="12"><div class="generalbox alert alert-warning">{{ text.warning }}</div></b-col></b-row>
<b-row>
<b-col cols="6">
<h3> {{ text.course }} </h3>
<p><b>{{ item.course.fullname }}</b></p>
<p><b>{{ item.course.shortname }}</b></p>
<p><b>{{ value.course.fullname }}</b></p>
<p><b>{{ value.course.shortname }}</b></p>
<p>{{ datechanger.coursespan.formatted.first}} - {{ datechanger.coursespan.formatted.last}}</p>
<p><b>{{ text.duration }}</b><br>
{{ format_duration(datechanger.coursespan)}}</p>
</b-col>
<b-col cols=>"6">
<h3> {{ text.period }} </h3>
<p><b>{{ period.fullname }}</b></p>
<p><b>{{ period.shortname }}</b></p>
<p><b>{{ period.fullname }}</b><b v-if="value.span > 1"> - {{ endperiod.fullname }}</b></p>
<p><b>{{ period.shortname }}</b><b v-if="value.span > 1"> - {{ endperiod.shortname }}</b></p>
<p>{{ datechanger.periodspan.formatted.first}} - {{ datechanger.periodspan.formatted.last}}</p>
<p><b>{{ text.duration }}</b><br>
{{ format_duration(datechanger.periodspan)}}</p>
</b-col>
</b-row>
<b-row><b-col cols="12">
<b-form-checkbox type="checkbox" v-model="datechanger.hidewarn">{{ text.hidewarning }}</b-form-checkbox>
<b-row v-if='hidden'><b-col cols="12">
<b-form-checkbox type="checkbox" v-model="datechanger.globals.hidewarn">{{ text.hidewarning }}</b-form-checkbox>
</b-col></b-row>
</b-container>
</b-modal>
</div>
</span>
`,
});
Vue.component('t-item', {
props: {
'value' :{
value :{
type: Object,
default(){ return null;},
},
'dummy' :{
dummy :{
type: Boolean,
default() { return false;},
},
'plan': {
plan: {
type: Object, // Studyplan page
default() { return null;},
},
page: {
type: Object, // Studyplan page
default() { return null;},
},
period: {
type: Object, // Studyplan page
default() { return null;},
},
maxspan: {
type: Number,
default() { return 0;},
},
},
data() {
return {
@ -2324,9 +2449,8 @@ export default {
call([{
methodname: 'local_treestudyplan_connect_studyitems',
args: { 'from_id': from_id, 'to_id': to_id }
}])[0].done((result)=>{
console.info("Drop result",result);
let conn = {'id': result.id, 'from_id': result.from_id, 'to_id': result.to_id};
}])[0].done((response)=>{
let conn = {'id': response.id, 'from_id': response.from_id, 'to_id': response.to_id};
ItemEventBus.$emit("createdConnection",conn);
this.value.connections.in.push(conn);
}).fail(notification.exception);
@ -2355,12 +2479,11 @@ export default {
},
deleteLine(conn){
const self = this;
// console.info("Delete Line",conn);
call([{
methodname: 'local_treestudyplan_disconnect_studyitems',
args: { 'from_id': conn.from_id, 'to_id': conn.to_id }
}])[0].done((result)=>{
if(result.success){
}])[0].done((response)=>{
if(response.success){
this.removeLine(conn);
// send disconnect event on message bus, so the connection on the other end can delete it too
ItemEventBus.$emit("connectionDisconnected",conn);
@ -2397,7 +2520,6 @@ export default {
redrawLines(){
for(let i in this.value.connections.out){
let conn = this.value.connections.out[i];
// console.info('Connection out', conn);
this.redrawLine(conn);
}
},
@ -2405,7 +2527,6 @@ export default {
// EVENT LISTENERS
onCreatedConnection(conn){
if(conn.from_id == this.value.id){
// console.info("incomingConnection",conn);
this.value.connections.out.push(conn);
this.redrawLine(conn);
}
@ -2415,7 +2536,6 @@ export default {
for(let i in this.value.connections.in){
let c_in = this.value.connections.in[i];
if(conn.id == c_in.id){
// console.info("Deleting incoming connection",conn);
self.value.connections.out.splice(i, 1);
}
}
@ -2513,7 +2633,6 @@ export default {
if(!this.dummy)
{
// console.info('Mounted', this);
this.redrawLines();
setTimeout(()=>{
ItemEventBus.$emit("rePositioned",this.value.id);
@ -2547,7 +2666,7 @@ export default {
template: `
<div class="t-item-base" :id="'studyitem-'+value.id">
<t-item-course v-model="value" v-if="value.type == 'course'"
:plan='plan' ></t-item-course>
:plan='plan' :page='page' :period='period' :maxspan='maxspan'></t-item-course>
<t-item-junction v-model="value" v-if="value.type == 'junction'" ></t-item-junction>
<t-item-start v-model="value" v-if="value.type == 'start'" ></t-item-start>
<t-item-finish v-model="value" v-if="value.type == 'finish'" ></t-item-finish>
@ -2636,14 +2755,26 @@ export default {
Vue.component('t-item-course', {
props: {
'value' :{
value:{
type: Object,
default(){ return null;},
},
'plan' :{
plan:{
type: Object,
default(){ return null;},
},
page: {
type: Object, // PAge data
default() { return null;}
},
period: {
type: Object, // Period data
default() { return null;}
},
maxspan: {
type: Number,
default() { return 0;}
},
},
data() {
return {
@ -2788,6 +2919,20 @@ export default {
</div>
</div>
</template>
<template #modal-footer="{ ok }" class='d-flex'>
<div class="flex-fill">
<!-- Configure spans and timing if needed -->
<t-item-timing-checker
:maxspan="maxspan"
:page="page"
:period="period"
v-model="value"
></t-item-timing-checker>
</div>
<b-button class='' variant="primary" @click="ok()">
{{ text.ok }}
</b-button>
</template>
<t-item-course-grades
v-if='!!value.course.grades && value.course.grades.length > 0'
@ -3181,7 +3326,6 @@ export default {
methodname: 'local_treestudyplan_get_category',
args: { "id": this.value.id}
}])[0].done(function(response){
debug.info("Course info:",response);
self.$emit('input', response);
}).fail(notification.exception);
}

View File

@ -124,7 +124,6 @@ export default {
},
methods: {
onHeaderHeightChange(newheight){
//console.info("Height change event to",newheight);
this.$refs.main.style.height = `${newheight}px`;
}
},

View File

@ -214,7 +214,7 @@ class studyitem {
$info = ['id' => $this->id,];
foreach($editable as $f){
if(array_key_exists($f,$fields)){
if(array_key_exists($f,$fields) && isset($fields[$f])){
$info[$f] = $fields[$f];
}
}

View File

@ -1180,16 +1180,15 @@ class studyplanservice extends \external_api
}
/************************
* *
* edit_period *
* *
************************/
* *
* Change course timing *
* *
************************/
public static function course_period_timing_parameters()
{
return new \external_function_parameters( [
"page_id" => new \external_value(PARAM_INT, 'Studyplan page id'),
"period" => new \external_value(PARAM_INT, 'Period number within page'),
"period_id" => new \external_value(PARAM_INT, 'Period number within page'),
"course_id"=> new \external_value(PARAM_INT, 'Id of course to adjust dates for'),
"span"=> new \external_value(PARAM_INT, 'Period span (default 1)',VALUE_DEFAULT),
]);
@ -1197,43 +1196,75 @@ class studyplanservice extends \external_api
public static function course_period_timing_returns()
{
return success::structure();
return courseinfo::editor_structure();
}
public static function course_period_timing($page_id, $period, $course_id, $span=1){
$course = \get_course($course_id);
$coursecontext = \context_course::instance($course_id);
$page = studyplanpage::findById($page_id);
public static function course_period_timing($period_id, $course_id, $span=1){
$period = period::findById($period_id);
$periodnr = $period->period();
$page = $period->page();
// Check for studyplan edit permissions
webservicehelper::require_capabilities(self::CAP_EDIT,$page->studyplan()->context());
$course = \get_course($course_id);
$coursecontext = \context_course::instance($course_id);
// Determine end period number
$endperiod = $period + ($span -1);
// Get the proper list of periods for this page
$periods = period::findForPage($page);
if(array_key_exists($period,$periods)){
$pstart = $periods[$period];
$pend = $periods[$endperiod];
if(webservicehelper::has_capabilities("moodle/course:update",$coursecontext)){
if(webservicehelper::has_capabilities("moodle/course:update",$coursecontext)){
// Actually perform the timing changes, while also updating the module times
// Like what happens on a course "reset"
$status = reset_course_userdata((object)[
'id' => $course->id,
'reset_start_date' => $pstart->startdate()->getTimestamp(),
'reset_end_date' => $pend->enddate()->getTimestamp(),
'reset_start_date_old' => $course->startdate,
'reset_end_date_old' => $course->enddate,
// Get the proper list of all the periods for this page
$periods = period::findForPage($page);
$pstart = $periods[$periodnr];
]);
return success::success()->model();
} else {
// probably should return a nice message
return success::fail("You do not have date change permissions on this course")->model();
// Determine end period number - Clip span between 1 and last period
if($span <= 1){
$pend = $pstart;
}
else if ($periodnr + ($span - 1) > $page->periods()){
$pend = $periods[$page->periods()];
}
else {
$pend = $periods[$periodnr + ($span - 1)];
}
// Actually perform the timing changes, while also updating the module times
// Like what happens on a course "reset"
reset_course_userdata((object)[
'id' => $course->id,
'reset_start_date' => $pstart->startdate()->getTimestamp(),
'reset_end_date' => $pend->enddate()->getTimestamp(),
'reset_start_date_old' => $course->startdate,
'reset_end_date_old' => $course->enddate,
]);
return (new courseinfo($course->id))->editor_model();
} else {
// probably should return a nice message
throw new \webservice_access_exception("You do not have date change permissions on this course");
}
}
public static function set_studyitem_span_parameters()
{
return new \external_function_parameters( [
"id" => new \external_value(PARAM_INT, 'id of study item'),
"span" => new \external_value(PARAM_INT, 'span of item'),
]);
}
public static function set_studyitem_span_returns()
{
return studyitem::editor_structure();
}
public static function set_studyitem_span($id,$span=null)
{
$o = studyitem::findById($id);
webservicehelper::require_capabilities(self::CAP_EDIT,$o->context());
$config = [ 'span' => $span];
$o->edit($config);
return $o->editor_model();
}
}

View File

@ -70,7 +70,7 @@ class teachingfinder {
foreach($list as $page_id){
// Retrieve the studyplan id from the page
//TODO: Change this when page management is implemented to return the page instead of the plan
$planid = $DB->get_field("local_treestudyplan_page","studyplan_id",["page_id" => $page_id]);
$planid = $DB->get_field("local_treestudyplan_page","studyplan_id",["id" => $page_id]);
$DB->insert_record(self::TABLE,["teacher_id"=>$userid,"studyplan_id"=>$planid,"update_time"=>$now]);
}

View File

@ -542,4 +542,22 @@ $functions = [
'ajax' => true,
'loginrequired' => true,
],
'local_treestudyplan_course_period_timing' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'course_period_timing', //external function name
'description' => 'Chenge course start and end times to match period', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'ajax' => true,
'loginrequired' => true,
],
'local_treestudyplan_set_studyitem_span' => [ //web service function name
'classname' => '\local_treestudyplan\studyplanservice', //class containing the external function
'methodname' => 'set_studyitem_span', //external function name
'description' => 'Change the span of a course item', //human readable description of the web service function
'type' => 'read', //database rights of the web service function (read, write)
'capabilities' => 'local/treestudyplan:editstudyplan', // Advises the admin which capabilities are required
'ajax' => true,
'loginrequired' => true,
],
];

View File

@ -292,10 +292,13 @@ $string["badgeissuedstats"] = "Issuing progress";
$string["period_default_fullname"] = 'Period {$a}';
$string["period_default_shortname"] = 'P{$a}';
$string["course_timing_title"] = 'Course timing does not match period timing';
$string["course_timing_desc"] = 'The start and end date of the course you are dropping into this period does not match the start and end date of the period.';
$string["course_timing_desc"] = 'The start and end date of the course do not match the start and end date of it\'s period(s) in the studyplan.';
$string["course_timing_question"] = 'Do you want to update the course\'s start and end time to match that of the period?';
$string["course_timing_warning"] = 'You do not have permission to automatically update this course start and end date. Automatic timing update not available';
$string["period"] = 'Period';
$string["duration"] = 'Duration';
$string["course_timing_rememberchoice"] = 'Remember my choice for future date mismatches';
$string["course_timing_hidewarning"] = 'Hide this warning next time';
$string["course_timing_hidewarning"] = 'Hide this warning next time';
$string["course_timing_ok"] = 'Course timing does matches period timing';
$string["course_timing_off"] = 'Course timing does not match period timing';
$string["course_period_span"] = 'Number of periods';

View File

@ -301,4 +301,7 @@ $string["course_timing_warning"] = 'Je hebt geen rechten om de start- en eindtij
$string["period"] = 'Periode';
$string["duration"] = 'Duur';
$string["course_timing_rememberchoice'"] = 'Onthoud mijn keuze voor toekomstige mismatches tussen cursus en periode';
$string["course_timing_hidewarning"] = 'Hide this warning next time';
$string["course_timing_hidewarning"] = 'Hide this warning next time';
$string["course_timing_ok"] = 'Cursustiming en periodetiming komen overeen';
$string["course_timing_off"] = 'Cursustiming en periodetiming komen niet overeen';
$string["course_period_span"] = 'Aantal perioden';

View File

@ -1,6 +1,6 @@
<?php
$plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494)
$plugin->version = 2023080300; // YYYYMMDDHH (year, month, day, iteration)
$plugin->version = 2023080500; // YYYYMMDDHH (year, month, day, iteration)
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11)
$plugin->dependencies = [