Added user sync cascading

This commit is contained in:
PMKuipers 2023-06-30 12:14:11 +02:00
parent 5bbff0799c
commit 1aafb55c78
11 changed files with 124 additions and 36 deletions

View file

@ -722,9 +722,8 @@ export default {
studyplan_id: this.value.id, studyplan_id: this.value.id,
}, },
}])[0].done(function(response){ }])[0].done(function(response){
self.$bvModal.msgBoxConfirm(self.text.cascade_cohortsync + "\n" self.$bvModal.msgBoxOk(response.success?self.text.success$core:self.text.error$core,
+ (response.success?self.text.success$core:self.text.error$core) { title: self.text.advanced_cascade_cohortsync});
+ "\n" + response.msg);
}).fail(notification.exception); }).fail(notification.exception);
}, },
modal_close(){ modal_close(){

View file

@ -145,7 +145,7 @@ class associationservice extends \external_api
// Only allow this if the user has the right to edit in this context (using system rights would make things more confusing) // Only allow this if the user has the right to edit in this context (using system rights would make things more confusing)
$context = webservicehelper::find_context($context_id); $context = webservicehelper::find_context($context_id);
webservicehelper::require_capabilities(self::CAP_EDIT,); webservicehelper::require_capabilities(self::CAP_EDIT,$context);
$pattern = "%{$like}%"; $pattern = "%{$like}%";
$params = ["pattern_fn" => $pattern, $params = ["pattern_fn" => $pattern,
@ -280,6 +280,7 @@ class associationservice extends \external_api
'studyplan_id' => $studyplan_id, 'studyplan_id' => $studyplan_id,
'user_id' => $user_id, 'user_id' => $user_id,
]); ]);
$studyplan->mark_csync_changed();
return ['success' => true, 'msg'=>'Cohort connected']; return ['success' => true, 'msg'=>'Cohort connected'];
@ -317,6 +318,9 @@ class associationservice extends \external_api
'studyplan_id' => $studyplan_id, 'studyplan_id' => $studyplan_id,
'user_id' => $user_id, 'user_id' => $user_id,
]); ]);
$studyplan->mark_csync_changed();
return ['success' => true, 'msg'=>'User Disconnected']; return ['success' => true, 'msg'=>'User Disconnected'];
} else { } else {
return ['success' => true, 'msg'=>'Connection does not exist']; return ['success' => true, 'msg'=>'Connection does not exist'];
@ -471,6 +475,12 @@ class associationservice extends \external_api
$enroller = new cascadecohortsync($studyplan); $enroller = new cascadecohortsync($studyplan);
$enroller->sync(); $enroller->sync();
if(get_config("local_treestudyplan","csync_users")){
$userenroller = new cascadeusersync($studyplan);
$userenroller->sync();
}
$studyplan->clear_csync_changed(); // Clear the csync required flag
return success::success()->model(); return success::success()->model();
} }

View file

@ -3,18 +3,15 @@ namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php'); require_once($CFG->libdir.'/externallib.php');
use \local_treestudyplan\studyplan; use \local_treestudyplan\studyplan;
use \local_treestudyplan\local\helpers\debugger;
class cascadecohortsync { class cascadecohortsync {
private const METHOD = "cohort"; private const METHOD = "cohort";
private $studyplan; private $studyplan;
private $studyplanid; private $studyplanid;
private $debug;
function __construct(studyplan $studyplan){ function __construct(studyplan $studyplan){
$this->studyplan = $studyplan; $this->studyplan = $studyplan;
$this->studyplanid = $studyplan->id(); $this->studyplanid = $studyplan->id();
$this->debug = new debugger("/tmp/treestudyplan.log","ccsync/{$studyplan->shortname()}/{$this->studyplanid}");
} }
static private function array_remove_value($array,$value){ static private function array_remove_value($array,$value){
@ -27,7 +24,6 @@ class cascadecohortsync {
return $a; return $a;
} }
static function uploadenrolmentmethods_get_group($courseid, $groupname) { static function uploadenrolmentmethods_get_group($courseid, $groupname) {
// Function shamelessly copied from tool/uploadenrolmentmethods/locallib.php // Function shamelessly copied from tool/uploadenrolmentmethods/locallib.php
global $DB, $CFG; global $DB, $CFG;
@ -60,23 +56,20 @@ class cascadecohortsync {
it was determined to be the simplest and cleanest solution. it was determined to be the simplest and cleanest solution.
*/ */
$enrol = enrol_get_plugin(self::METHOD); $enrol = \enrol_get_plugin(self::METHOD);
$this->debug->dump($enrol,"Enrol");
// Find the courses that need to be synced to the associated cohorts // Find the courses that need to be synced to the associated cohorts
$courseids = $this->studyplan->get_linked_course_ids(); $courseids = $this->studyplan->get_linked_course_ids();
$this->debug->dump($courseids,"courseids");
// And find the cohorts that are linked to this studyplan. // And find the cohorts that are linked to this studyplan.
$cohortids = $this->studyplan->get_linked_cohort_ids(); $cohortids = $this->studyplan->get_linked_cohort_ids();
$this->debug->dump($cohortids,"cohortids");
// Next, for each course that is linked: // Next, for each course that is linked:
foreach($courseids as $courseid){ foreach($courseids as $courseid){
$course = get_course($courseid); $course = \get_course($courseid);
$this->debug->write("Processing Course {$courseid} {$course->shortname}"); //\mtrace("Processing Course {$courseid} {$course->shortname}");
// first create any nonexistent links // first create any nonexistent links
foreach($cohortids as $cohortid){ foreach($cohortids as $cohortid){
$cohort = $DB->get_record('cohort',['id'=>$cohortid]); $cohort = $DB->get_record('cohort',['id'=>$cohortid]);
$this->debug->write("Processing cohort {$cohortid} {$cohort->shortname}"); //\mtrace("Processing cohort {$cohortid} {$cohort->shortname}");
$instanceparams = [ $instanceparams = [
'courseid' => $courseid, 'courseid' => $courseid,
@ -91,14 +84,12 @@ class cascadecohortsync {
'roleid' => get_config("local_treestudyplan","csync_roleid"), 'roleid' => get_config("local_treestudyplan","csync_roleid"),
]; ];
$this->debug->dump($instanceparams,"instanceparams");
$this->debug->dump($instancenewparams,"instancenewparams");
// Create group: // Create group:
// 1: check if a link exists // 1: check if a link exists
// If not, make it (maybe use some of the custom text to list the studyplans involved) // If not, make it (maybe use some of the custom text to list the studyplans involved)
if ($instance = $DB->get_record('enrol', $instanceparams)) { if ($instance = $DB->get_record('enrol', $instanceparams)) {
$this->debug->write("Instance exists"); //\mtrace("Instance exists");
// it already exists // it already exists
// check if this studyplan is already referenced in customtext4 in json format // check if this studyplan is already referenced in customtext4 in json format
@ -113,23 +104,21 @@ class cascadecohortsync {
$plans = []; $plans = [];
} }
} }
$this->debug->dump($plans,"plans");
if(!in_array($this->studyplanid ,$plans)){ if(!in_array($this->studyplanid ,$plans)){
// if not, add it to the reference // if not, add it to the reference
$this->debug->write("Adding this plan to the list"); //\mtrace("Adding this plan to the list");
$plans[] = (int)($this->studyplanid); $plans[] = (int)($this->studyplanid);
$enrol->update_instance($instance,(object)["customtext4"=>json_encode($plans)]); $enrol->update_instance($instance,(object)["customtext4"=>json_encode($plans)]);
} }
} else { } else {
$this->debug->write("New instance should be made"); //\mtrace("New instance should be made");
// If method members should be added to a group, create it or get its ID. // If method members should be added to a group, create it or get its ID.
if (get_config("local_treestudyplan","csync_creategroup")) { if (get_config("local_treestudyplan","csync_creategroup")) {
// Make or get new new cohort group - but only on creating of instances // Make or get new new cohort group - but only on creating of instances
$groupname = $cohort->name." ".strtolower(\get_string('defaultgroupname', 'core_group')); $groupname = $cohort->name." ".strtolower(\get_string('defaultgroupname', 'core_group'));
$this->debug->write("Adding group {$groupname} for this method"); //\mtrace("Adding group {$groupname} for this method");
// and make sure the // and make sure the
$instancenewparams['customint2'] = self::uploadenrolmentmethods_get_group($courseid, $groupname); $instancenewparams['customint2'] = self::uploadenrolmentmethods_get_group($courseid, $groupname);
} }
@ -137,24 +126,24 @@ class cascadecohortsync {
if ($instanceid = $enrol->add_instance($course, $instancenewparams)) { if ($instanceid = $enrol->add_instance($course, $instancenewparams)) {
// also record the (as of yet only) studyplans id requiring this association // also record the (as of yet only) studyplans id requiring this association
// in the customtext4 field in json format // in the customtext4 field in json format
$this->debug->write("Instance ({$instanceid} created. Updateing instance with studyplan id"); //\mtrace("Instance ({$instanceid} created. Updateing instance with studyplan id");
$instance = $DB->get_record('enrol', array('id' => $instanceid)); $instance = $DB->get_record('enrol', array('id' => $instanceid));
$enrol->update_instance($instance,(object)["customtext4"=>json_encode([(int)($this->studyplanid)])]); $enrol->update_instance($instance,(object)["customtext4"=>json_encode([(int)($this->studyplanid)])]);
$this->debug->write("Synchronize the enrolment"); //\mtrace("Synchronize the enrolment");
// Successfully added a valid new instance, so now instantiate it. // Successfully added a valid new instance, so now instantiate it.
// First synchronise the enrolment. // First synchronise the enrolment.
$cohorttrace = new \null_progress_trace(); $cohorttrace = new \null_progress_trace();
$result = enrol_cohort_sync($cohorttrace, $cohortid); $result = enrol_cohort_sync($cohorttrace, $cohortid);
if($result > 0){ if($result > 0){
$this->debug->write("Error during 'enrol_cohort_sync': code {$result}"); //\mtrace("Error during 'enrol_cohort_sync': code {$result}");
} }
$cohorttrace->finished(); $cohorttrace->finished();
} else { } else {
// Instance not added for some reason, so report an error somewhere // Instance not added for some reason, so report an error somewhere
// (or not) // (or not)
$this->debug->write("Error - instance not added for some reason"); //\mtrace("Error - instance not added for some reason");
} }
} }
} }
@ -169,7 +158,7 @@ class cascadecohortsync {
if(get_config("local_treestudyplan","csync_autoremove")){ if(get_config("local_treestudyplan","csync_autoremove")){
// Only try the autoremove if the option is enabled // Only try the autoremove if the option is enabled
$this->debug->write("Autoremove scan for course {$courseid} {$course->shortname}"); //\mtrace("Autoremove scan for course {$courseid} {$course->shortname}");
// find all cohort syncs for this course // find all cohort syncs for this course
$searchparams = [ $searchparams = [
'courseid' => $courseid, 'courseid' => $courseid,
@ -182,26 +171,24 @@ class cascadecohortsync {
if(!empty($instance->customtext4)){ // only check the records that have studyplan information in the customtext4 field 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 // first check if the cohort is not one of the cohort id's we have associated
if(!in_array($instance->customint1,$cohortids)){ if(!in_array($instance->customint1,$cohortids)){
$this->debug->write("Found cohort sync instance that is not currently liked to the studyplan: {$instance->id}"); //\mtrace("Found cohort sync instance that is not currently liked to the studyplan: {$instance->id}");
// So it may or may not need to be removed // So it may or may not need to be removed
$plans = json_decode($instance->customtext4); $plans = json_decode($instance->customtext4);
$this->debug->dump($plans,"Plans attachted to instance {$instance->id}");
if($plans !== null && is_array($plans)){ 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 //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 // otherwise, check if we should remove it
if(in_array($this->studyplanid,$plans)){ if(in_array($this->studyplanid,$plans)){
$this->debug->write("Found this studyplan in the id list - removing from list"); //\mtrace("Found this studyplan in the id list - removing from list");
//if this plan was referenced before //if this plan was referenced before
// first remove the link // first remove the link
$fplans = self::array_remove_value($plans,$this->studyplanid); $fplans = self::array_remove_value($plans,$this->studyplanid);
$this->debug->dump($fplans,"Plans proposed to attach to instance {$instance->id}");
if(count($fplans) == 0){ if(count($fplans) == 0){
// delete the sync if there are no studyplan references left // delete the sync if there are no studyplan references left
$this->debug->write("No references are left, removing instance"); //\mtrace("No references are left, removing instance");
$enrol->delete_instance($instance); $enrol->delete_instance($instance);
} else { } else {
// otherwise just update the references so this studyplan is no longer linked // otherwise just update the references so this studyplan is no longer linked
$this->debug->write("Still references left in the list, updating list..."); //\mtrace("Still references left in the list, updating list...");
$enrol->update_instance($instance,(object)["customtext4"=>json_encode($fplans)]); $enrol->update_instance($instance,(object)["customtext4"=>json_encode($fplans)]);
} }
} }
@ -211,6 +198,6 @@ class cascadecohortsync {
} }
} }
} }
$this->debug->write("Cascading complete"); //\mtrace("Cascading complete");
} }
} }

View file

@ -0,0 +1,64 @@
<?php
namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php');
use \local_treestudyplan\studyplan;
class cascadeusersync {
private const METHOD = "manual";
private $studyplan;
function __construct(studyplan $studyplan){
$this->studyplan = $studyplan;
}
function sync(){
global $DB;
/* 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);
// Find the courses that need to be synced to the associated cohorts
$courseids = $this->studyplan->get_linked_course_ids();
// And find the users that are linked to this studyplan.
$userids = $this->studyplan->get_linked_user_ids();
// Get the roleid to use for synchronizations
$roleid = get_config("local_treestudyplan","csync_roleid");
// Next, for each course that is linked:
foreach($courseids as $courseid){
$course = \get_course($courseid);
if(count($userids) > 0){
// Get the manual enrol instance for this course
$instanceparams = ['courseid' => $courseid, 'enrol' => 'manual'];
if(!($instance = $DB->get_record('enrol', $instanceparams))){
if ($instanceid = $enrol->add_default_instance($course)) {
$instance = $DB->get_record('enrol', array('id' => $instanceid));
}
else {
// Instance not added for some reason, so report an error somewhere
// (or not)
$instance = null;
}
}
if($instance !== null){
foreach($userids as $uid){
// Try a manual registration - it will just be updated if it is already there....
$enrol->enrol_user($instance,$uid,$roleid);
}
}
}
// We do not do any autoremoval for user syncs, to avoid students losing access to the course data
}
}
}

View file

@ -16,6 +16,7 @@ class courseinfo {
private $course; private $course;
private $context; private $context;
private $coursecontext;
private $studyitem; private $studyitem;
private static $contentitems = null; private static $contentitems = null;

View file

@ -797,7 +797,17 @@ class studyplan {
return $fields; return $fields;
} }
/**
* List the user id's explicitly associated with this studyplan
*/
public function get_linked_user_ids(){
global $CFG, $DB;
$sql = "SELECT DISTINCT j.user_id FROM {local_treestudyplan_user} j
WHERE j.studyplan_id = :studyplan_id";
$fields = $DB->get_fieldset_sql($sql, ['studyplan_id' => $this->id]);
return $fields;
}
/** /**
* See if the specified course id is linked in this studyplan * See if the specified course id is linked in this studyplan
*/ */

View file

@ -3,6 +3,7 @@ namespace local_treestudyplan\task;
require_once($CFG->dirroot.'/course/externallib.php'); require_once($CFG->dirroot.'/course/externallib.php');
use local_treestudyplan\studyplan; use local_treestudyplan\studyplan;
use local_treestudyplan\cascadecohortsync; use local_treestudyplan\cascadecohortsync;
use local_treestudyplan\cascadeusersync;
class autocohortsync extends \core\task\scheduled_task { class autocohortsync extends \core\task\scheduled_task {
@ -30,7 +31,12 @@ class autocohortsync extends \core\task\scheduled_task {
\mtrace("Studyplan {$studyplan->shortname()} needs processing"); \mtrace("Studyplan {$studyplan->shortname()} needs processing");
$enroller = new cascadecohortsync($studyplan); $enroller = new cascadecohortsync($studyplan);
$enroller->sync(); $enroller->sync();
if(get_config("local_treestudyplan","csync_users")){
$userenroller = new cascadeusersync($studyplan);
$userenroller->sync();
}
$studyplan->clear_csync_changed(); $studyplan->clear_csync_changed();
} }
} }
\mtrace("Task done"); \mtrace("Task done");

View file

@ -69,6 +69,8 @@ $string['setting_csync_role_field'] = 'Role';
$string['settingdesc_csync_role_field'] = 'The role to use for automatic cohort sync enrolment created in courses'; $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['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['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['setting_csync_users_field'] = 'Enrol linked users';
$string['settingdesc_csync_users_field'] = 'Also enrol all users that are explicitly linked to a studyplan in that studyplan\'s courses.';
$string['autocohortsync_name'] = 'Studyplan automatic cohort sync cascading'; $string['autocohortsync_name'] = 'Studyplan automatic cohort sync cascading';

View file

@ -71,6 +71,8 @@ $string['setting_csync_role_field'] = 'Synchronisatierol';
$string['settingdesc_csync_role_field'] = 'Welke rol te gebruiken voor automatische site-groep sync koppelingen'; $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['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['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['setting_csync_users_field'] = 'Gekoppelde gebruikers automatisch inchrijven';
$string['settingdesc_csync_users_field'] = 'Ook alle gebruikers die expliciet aan een studieplan gekoppeld zijn inschrijven in de cursussen van dat studieplan.';
$string['autocohortsync_name'] = 'Studyplan automatisch site-group synchronisatie doorzetten'; $string['autocohortsync_name'] = 'Studyplan automatisch site-group synchronisatie doorzetten';
$string['studyplan_add'] = 'Nieuw studieplan'; $string['studyplan_add'] = 'Nieuw studieplan';

View file

@ -192,6 +192,13 @@ if ($hassiteconfig) {
true true
)); ));
// Sync users too yes/no?
$page_csync->add(new admin_setting_configcheckbox('local_treestudyplan/csync_users',
get_string('setting_csync_users_field', 'local_treestudyplan'),
get_string('settingdesc_csync_users_field', 'local_treestudyplan'),
true
));
// Select csync enrol role // Select csync enrol role
if (!during_initial_install()) { if (!during_initial_install()) {
$options = get_default_enrol_roles(context_system::instance()); $options = get_default_enrol_roles(context_system::instance());

View file

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