Cascade cohort sync fixes

This commit is contained in:
PMKuipers 2023-06-26 21:44:31 +02:00
parent 686d38ca7a
commit e849d612cc
12 changed files with 157 additions and 56 deletions

View File

@ -58,8 +58,8 @@ export default {
studyline_editmode: 'studyline_editmode',
editmode_modules_hidden:'editmode_modules_hidden',
studyline_add: 'studyline_add',
add$core: 'add',
edit$core: 'edit',
add$core: 'add$core',
edit$core: 'edit$core',
studyline_name: 'studyline_name',
studyline_name_ph: 'studyline_name_ph',
studyline_shortname: 'studyline_shortname',
@ -83,8 +83,8 @@ export default {
advanced_tools: 'advanced_tools',
confirm_cancel: 'confirm_cancel',
confirm_ok: 'confirm_ok',
success$core: 'success',
error$core: 'failed',
success$core: 'success$core',
error$core: 'failed$core',
advanced_converted: 'advanced_converted',
advanced_skipped: 'advanced_skipped',
advanced_failed: 'advanced_failed',
@ -110,6 +110,9 @@ export default {
advanced_import_from_file: 'advanced_import_from_file',
advanced_purge: "advanced_purge",
advanced_purge_expl: "advanced_purge_expl",
advanced_cascade_cohortsync_title: "advanced_cascade_cohortsync_title",
advanced_cascade_cohortsync_desc: "advanced_cascade_cohortsync_desc",
advanced_cascade_cohortsync: "advanced_cascade_cohortsync",
},
studyplan_edit: {
studyplan_edit: 'studyplan_edit',
@ -711,6 +714,19 @@ export default {
}).fail(notification.exception);
},
cascade_cohortsync(){
const self = this;
call([{
methodname: 'local_treestudyplan_cascade_cohortsync',
args: {
studyplan_id: this.value.id,
},
}])[0].done(function(response){
self.$bvModal.msgBoxConfirm(self.text.cascade_cohortsync + "\n"
+ (response.success?self.text.success$core:self.text.error$core)
+ "\n" + response.msg);
}).fail(notification.exception);
},
modal_close(){
this.force_scales.result = [];
}
@ -739,6 +755,16 @@ export default {
<b-tab :title="text.advanced_course_manipulation_title" >
<b-container>
<b-row><b-col cols="*">
<h3>{{ text.advanced_cascade_cohortsync_title}}</h3>
{{ text.advanced_cascade_cohortsync_desc}}
</b-col></b-row>
<b-row><b-col cols="*">
<b-button
variant="info"
@click="cascade_cohortsync"
>{{ text.advanced_cascade_cohortsync}}</b-button>
</b-col></b-row>
<b-row class="mt-3"><b-col cols="*">
<h3>{{ text.advanced_force_scale_title}}</h3>
{{ text.advanced_force_scale_desc}}
</b-col></b-row>
@ -757,7 +783,7 @@ export default {
</b-col>
</b-row>
<b-row><b-col cols="*">
<b-row class="mt-3"><b-col cols="*">
<ul class='t-advanced-scrollable' v-if="force_scales.result.length > 0">
<li v-for="c in force_scales.result">
<span class='t-advanced-coursename'>{{c.course.fullname}}</span>
@ -2187,7 +2213,7 @@ export default {
},
methods: {
hasGrades() {
if(this.value.course.grades && this.value.course.grades > 0){
if(this.value.course.grades && this.value.course.grades.length > 0){
for(const g of this.value.course.grades){
if(g.selected){
return true;
@ -2364,14 +2390,6 @@ export default {
},
template: `
<div>
<b-form-group v-if="useItemConditions"
:label="text.select_conditions"
><b-form-select size="sm"
@input="updateConditions"
v-model="value.conditions"
:options="condition_options"
></b-form-select>
</b-form-group>
<b-form-group
:label="text.select_grades"
><ul class="t-item-module-children">

View File

@ -461,16 +461,15 @@ class associationservice extends \external_api
}
// Actual functions
public static function casdade_cohortsync($studyplan_id)
public static function cascade_cohortsync($studyplan_id)
{
$studyplan = studyplan::findById($studyplan_id);
webservicehelper::require_capabilities(self::CAP_EDIT,$studyplan->context());
$enroller = new cascadecohortsync($studyplan);
$enroller->sync();
$enroller = new enrolcohortsync($studyplan);
$result = $enroller->sync();
return $result?success::success():success::fail();
return success::success()->model();
}

View File

@ -3,42 +3,80 @@ namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php');
use \local_treestudyplan\studyplan;
use \local_treestudyplan\local\helpers\debugger;
class enrolcohortsync {
class cascadecohortsync {
private const METHOD = "cohort";
private $studyplan;
private $studyplanid;
private $debug;
function __construct($studyplan){
function __construct(studyplan $studyplan){
$this->studyplan = $studyplan;
$this->studyplanid = $studyplan->id();
$this->debug = new debugger("/tmp/treestudyplan.log","ccsync/{$studyplan->shortname()}/{$this->studyplanid}");
}
static private function array_remove_value(&$array,$value){
while (($key = array_search($value, $array)) !== false) {
unset($array[$key]);
static private function array_remove_value($array,$value){
$a = [];
foreach($array as $v){
if($v != $value){
$a[] = $v;
}
}
return $a;
}
static function uploadenrolmentmethods_get_group($courseid, $groupname) {
// Function shamelessly copied from tool/uploadenrolmentmethods/locallib.php
global $DB, $CFG;
require_once($CFG->dirroot.'/group/lib.php');
// Check to see if the group name already exists in this course.
if ($DB->record_exists('groups', array('name' => $groupname, 'courseid' => $courseid))) {
$group = $DB->get_record('groups', array('name' => $groupname, 'courseid' => $courseid));
return $group->id;
}
// The named group doesn't exist, so create a new one in the course.
$groupdata = new \stdClass();
$groupdata->courseid = $courseid;
$groupdata->name = $groupname;
$groupid = groups_create_group($groupdata);
return $groupid;
}
function sync(){
global $DB;
// TODO: Determine if it would be better to add a database table of our own to store references between studyplan and cohort sync enrolment
// instead of using customtext4 for this
/* Explainer:
This script uses {enrol}.customtext4 to store a json array of all studyplans that need this cohort sync to exist.
Since the cohort-sync enrolment method uses only customint1 and customint2, this is a safe place to store the data.
(Should the cohortsync script at any future time be modified to use customtext fields, it is still extremely unlikely that
customtext4 will be used.)
Because of the overhead involved in keeping an extra table up to date and clean it up if cohort syncs are removed outside of this script,
it was determined to be the simplest and cleanest solution.
*/
$enrol = enrol_get_plugin(self::METHOD);
$this->debug->dump($enrol,"Enrol");
// Find the courses that need to be synced to the associated cohorts
$courseids = $this->studyplan->get_linked_course_ids();
$this->debug->dump($courseids,"courseids");
// And find the cohorts that are linked to this studyplan.
$cohortids = $this->studyplan->get_linked_cohort_ids();
$this->debug->dump($cohortids,"cohortids");
// Next, for each course that is linked:
foreach($courseids as $courseid){
$course = get_course($courseid);
$this->debug->write("Processing Course {$courseid} {$course->shortname}");
// first create any nonexistent links
foreach($cohortids as $cohortid){
$cohort = $DB->get_record('cohort',['id'=>$cohortid]);
$this->debug->write("Processing cohort {$cohortid} {$cohort->shortname}");
$instanceparams = [
'courseid' => $courseid,
@ -53,56 +91,75 @@ class enrolcohortsync {
'roleid' => get_config("local_treestudyplan","csync_roleid"),
];
$this->debug->dump($instanceparams,"instanceparams");
$this->debug->dump($instancenewparams,"instancenewparams");
// Create group:
// 1: check if a link exists
// If not, make it (maybe use some of the custom text to list the studyplans involved)
if ($instance = $DB->get_record('enrol', $instanceparams)) {
$this->debug->write("Instance exists");
// it already exists
// check if this studyplan is already referenced in customtext4 in json format
if(empty($instance->customtext4)){
// Cohort sync was already done, but without studyplan info
// make sure we add a "manual" link to the plan list
$plans = ["manual"];
}
else{
$plans = json_decode($instance->customtext4);
//TODO: Check this code - Maybe add option to not remember manually added stuff
$plans = json_decode($instance->customtext4);
if($plans == false || !is_array(($plans))){
// If the data was not an array (null or garbled), count it as manually added
// This will prevent it's deletion upon
if(get_config("local_treestudyplan","csync_remember_manual_csync")){
$plans = ["manual"];
} else {
$plans = [];
}
}
$this->debug->dump($plans,"plans");
if(!in_array($this->studyplanid ,$plans)){
// if not, add it to the reference
$this->debug->write("Adding this plan to the list");
$plans[] = $this->studyplanid;
$enrol->update_instance($instance,["customtext4"=>json_encode($plans)]);
$enrol->update_instance($instance,(object)["customtext4"=>json_encode($plans)]);
}
} else {
$this->debug->write("New instance should be made");
// If method members should be added to a group, create it or get its ID.
if (get_config("local_treestudyplan","csync_creategroup")) {
// Make or get new new cohort group - but only on creating of instances
$groupname = $cohort->name." ".strtolower(get_string('defaultgroupname', 'core'));
$groupname = $cohort->name." ".strtolower(\get_string('defaultgroupname', 'core_group'));
$this->debug->write("Adding group {$groupname} for this method");
// and make sure the
$instancenewparams['customint2'] = uploadenrolmentmethods_get_group($courseid, $groupname);
$instancenewparams['customint2'] = self::uploadenrolmentmethods_get_group($courseid, $groupname);
}
if ($instanceid = $enrol->add_instance($course, $instancenewparams)) {
// also record the (as of yet only) studyplans id requiring this association
// in the customtext4 field in json format
$enrol->update_instance($instance,["customtext4"=>json_encode([$this->studyplanid])]);
$this->debug->write("Instance ({$instanceid} created. Updateing instance with studyplan id");
$instance = $DB->get_record('enrol', array('id' => $instanceid));
$enrol->update_instance($instance,(object)["customtext4"=>json_encode([$this->studyplanid])]);
$this->debug->write("Synchronize the enrolment");
// Successfully added a valid new instance, so now instantiate it.
// First synchronise the enrolment.
$cohorttrace = new \null_progress_trace();
enrol_cohort_sync($cohorttrace, $cohortid);
$result = enrol_cohort_sync($cohorttrace, $cohortid);
if($result > 0){
$this->debug->write("Error during 'enrol_cohort_sync': code {$result}");
}
$cohorttrace->finished();
} else {
// Instance not added for some reason, so report an error somewhere
// (or not)
$this->debug->write("Error - instance not added for some reason");
}
}
}
// 2: Check if there are cohort links for this studyplan in this course that should be removed
// A: Check if there are cohort links that are no longer related to this studyplan
// B: Check if these links are valid through another studyplan...
@ -112,7 +169,7 @@ class enrolcohortsync {
if(get_config("local_treestudyplan","csync_autoremove")){
// Only try the autoremove if the option is enabled
$this->debug->write("Autoremove scan for course {$courseid} {$course->shortname}");
// find all cohort syncs for this course
$searchparams = [
'courseid' => $courseid,
@ -125,21 +182,27 @@ class enrolcohortsync {
if(!empty($instance->customtext4)){ // only check the records that have studyplan information in the customtext4 field
// first check if the cohort is not one of the cohort id's we have associated
if(!in_array($instance->customint1,$cohortids)){
$this->debug->write("Found cohort sync instance that is not currently liked to the studyplan: {$instance->id}");
// So it may or may not need to be removed
$plans = json_decode($instance->customtext4);
$this->debug->dump($plans,"Plans attachted to instance {$instance->id}");
if($plans !== null && is_array($plans)){
//if a valid array is not returned, better leave it be, we don't want to mess with it
// otherwise, check if we should remove it
if(in_array($this->studyplanid,$plans)){
$this->debug->write("Found this studyplan in the id list - removing from list");
//if this plan was referenced before
// first remove the link
self::array_remove_value($plans,$this->studyplanid);
if(count($plans) == 0){
$fplans = self::array_remove_value($plans,$this->studyplanid);
$this->debug->dump($fplans,"Plans proposed to attach to instance {$instance->id}");
if(count($fplans) == 0){
// delete the sync if there are no studyplan references left
$this->debug->write("No references are left, removing instance");
$enrol->delete_instance($instance);
} else {
// otherwise just update the references so this studyplan is no longer linked
$enrol->update_instance($instance,["customtext4"=>json_encode($plans)]);
$this->debug->write("Still references left in the list, updating list...");
$enrol->update_instance($instance,(object)["customtext4"=>json_encode($fplans)]);
}
}
}
@ -148,5 +211,6 @@ class enrolcohortsync {
}
}
}
$this->debug->write("Cascading complete");
}
}

View File

@ -58,7 +58,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
}
}
} else {
debug::msg("ARRAY is NOT CONFIG","");
}
}

View File

@ -35,7 +35,7 @@ class core_aggregator extends \local_treestudyplan\aggregator {
}
}
} else {
debug::msg("ARRAY is NOT CONFIG","");
}
}

View File

@ -22,10 +22,12 @@ class debugger {
}
public function dump($object)
public function dump($object,string $tag="")
{
global $CFG;
$this->writeblock(print_r($object,true));
if(strlen($tag) > 0){
$tag .= ":\n";
}
$this->writeblock($tag.print_r($object,true));
}

View File

@ -771,7 +771,7 @@ class studyplan {
$sql = "SELECT DISTINCT j.cohort_id FROM {local_treestudyplan_cohort} j
WHERE j.studyplan_id = :studyplan_id";
$fields = $DB->get_field_sql($sql, ['studyplan_id' => $this->id]);
$fields = $DB->get_fieldset_sql($sql, ['studyplan_id' => $this->id]);
return $fields;
}

View File

@ -2,7 +2,7 @@
namespace local_treestudyplan\task;
require_once($CFG->dirroot.'/course/externallib.php');
use local_treestudyplan\studyplan;
use local_treestudyplan\enrolcohortsync;
use local_treestudyplan\cascadecohortsync;
class autocohortsync extends \core\task\scheduled_task {
@ -23,7 +23,7 @@ class autocohortsync extends \core\task\scheduled_task {
$studyplans = studyplan::find_all();
foreach($studyplans as $studyplan) {
$enroller = new enrolcohortsync($studyplan);
$enroller = new cascadecohortsync($studyplan);
$enroller->sync();
}
}

View File

@ -67,6 +67,8 @@ $string['setting_csync_creategroup_field'] = 'Create groups';
$string['settingdesc_csync_creategroup_field'] = 'Create a group in the course for each cohort sync';
$string['setting_csync_role_field'] = 'Role';
$string['settingdesc_csync_role_field'] = 'The role to use for automatic cohort sync enrolment created in courses';
$string['setting_csync_remember_manual_csync_field'] = 'Remember existing cohort-syncs';
$string['settingdesc_csync_remember_manual_csync_field'] = 'Mark cohort syncs that were manually created earlier, so they won\'t be removed during autosync if cohorts are removed from the studyplan';
$string['studyplan_add'] = 'Add study plan';
$string['studyplan_edit'] = 'Edit study plan';
@ -244,11 +246,15 @@ $string["advanced_import_export"] = 'Backup and restore';
$string["advanced_import"] = 'Restore studylines from backup';
$string["advanced_export"] = 'Backup studyplan';
$string["advanced_export_csv"] = 'Export as CSV';
$string["advanced_import_from_file"] = "Open from file";
$string["advanced_purge"] = "Delete";
$string["advanced_purge_expl"] = "Delete the entire studyplan. This is not recoverable";
$string["advanced_cascade_cohortsync_title"] = "Cascade cohort sync";
$string["advanced_cascade_cohortsync_desc"] = "Add cohort sync enrolment to each course in this studyplan for all cohorts linked to this studyplan";
$string["advanced_cascade_cohortsync"] = "Cascade cohort sync";
$string["advanced_course_manipulation_title"] = 'Course manipulation';
$string["advanced_disable_autoenddate_title"] = 'Disable automatic end date';
$string["advanced_disable_autoenddate_desc"] = 'Disable the default on automatic end date function weekly topics have set in all courses in the studyplan';

View File

@ -69,7 +69,8 @@ $string['setting_csync_creategroup_field'] = 'Groepen aanmaken';
$string['settingdesc_csync_creategroup_field'] = 'Maak voor elke site-groep synchronisatie een groep aan';
$string['setting_csync_role_field'] = 'Synchronisatierol';
$string['settingdesc_csync_role_field'] = 'Welke rol te gebruiken voor automatische site-groep sync koppelingen';
$string['setting_csync_remember_manual_csync_field'] = 'Bewaar bestaande site-group syncs';
$string['settingdesc_csync_remember_manual_csync_field'] = 'Markeer site-group synchronisaties die eerder handmatig zijn aangemaakt, zodat deze niet worden verwijderd als ze uit het studieplan worden gehaald.';
$string['studyplan_add'] = 'Nieuw studieplan';
$string['studyplan_edit'] = 'Studieplan bewerken';
@ -249,9 +250,13 @@ $string["advanced_import"] = 'Studieplan invullan vanuit backup';
$string["advanced_export"] = 'Backup downloaden';
$string["advanced_export_csv"] = 'Export als CSV';
$string["advanced_import_from_file"] = "Vanuit bestand";
$string["advanced_purge"] = "Verwijderen";
$string["advanced_purge_expl"] = "Dit hele studieplan permanent verwijderen.";
$string["advanced_cascade_cohortsync_title"] = "Site-groep synchronisatie doorzetten";
$string["advanced_cascade_cohortsync_desc"] = "Voeg een site-group synchronisatie aanmelding toe aan alle cursussen in dit studieplan, voor alle gekoppelde site-groepen";
$string["advanced_cascade_cohortsync"] = "Site-groep sync doorzetten";
$string["advanced_course_manipulation_title"] = 'Cursussen aanpassen';
$string["advanced_disable_autoenddate_title"] = 'Automatische einddatum uitschakelen';

View File

@ -178,6 +178,13 @@ if ($hassiteconfig) {
true
));
// Enable/disable remembering previously added cohort syncs so they're not automatically deleted
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_remember_manual_csync',
get_string('setting_csync_remember_manual_csync_field', 'local_treestudyplan'),
get_string('settingdesc_csync_remember_manual_csync_field', 'local_treestudyplan'),
true
));
// Enable/disable group creation
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_creategroup',
get_string('setting_csync_creategroup_field', 'local_treestudyplan'),

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 = 2023061601; // YYYYMMDDHH (year, month, day, iteration)
$plugin->version = 2023062602; // YYYYMMDDHH (year, month, day, iteration)
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11)
$plugin->dependencies = [