From 1aafb55c78c61721c06dae16fa88ec17b3813d46 Mon Sep 17 00:00:00 2001 From: PMKuipers Date: Fri, 30 Jun 2023 12:14:11 +0200 Subject: [PATCH] Added user sync cascading --- amd/src/studyplan-editor-components.js | 5 +- classes/associationservice.php | 12 ++++- classes/cascadecohortsync.php | 49 ++++++++------------ classes/cascadeusersync.php | 64 ++++++++++++++++++++++++++ classes/courseinfo.php | 1 + classes/studyplan.php | 10 ++++ classes/task/autocohortsync.php | 6 +++ lang/en/local_treestudyplan.php | 2 + lang/nl/local_treestudyplan.php | 2 + settings.php | 7 +++ version.php | 2 +- 11 files changed, 124 insertions(+), 36 deletions(-) create mode 100644 classes/cascadeusersync.php diff --git a/amd/src/studyplan-editor-components.js b/amd/src/studyplan-editor-components.js index bef28c2..85c29a9 100644 --- a/amd/src/studyplan-editor-components.js +++ b/amd/src/studyplan-editor-components.js @@ -722,9 +722,8 @@ export default { 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); + self.$bvModal.msgBoxOk(response.success?self.text.success$core:self.text.error$core, + { title: self.text.advanced_cascade_cohortsync}); }).fail(notification.exception); }, modal_close(){ diff --git a/classes/associationservice.php b/classes/associationservice.php index 809601f..45ed0de 100644 --- a/classes/associationservice.php +++ b/classes/associationservice.php @@ -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) $context = webservicehelper::find_context($context_id); - webservicehelper::require_capabilities(self::CAP_EDIT,); + webservicehelper::require_capabilities(self::CAP_EDIT,$context); $pattern = "%{$like}%"; $params = ["pattern_fn" => $pattern, @@ -280,6 +280,7 @@ class associationservice extends \external_api 'studyplan_id' => $studyplan_id, 'user_id' => $user_id, ]); + $studyplan->mark_csync_changed(); return ['success' => true, 'msg'=>'Cohort connected']; @@ -317,6 +318,9 @@ class associationservice extends \external_api 'studyplan_id' => $studyplan_id, 'user_id' => $user_id, ]); + + $studyplan->mark_csync_changed(); + return ['success' => true, 'msg'=>'User Disconnected']; } else { return ['success' => true, 'msg'=>'Connection does not exist']; @@ -471,6 +475,12 @@ class associationservice extends \external_api $enroller = new cascadecohortsync($studyplan); $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(); } diff --git a/classes/cascadecohortsync.php b/classes/cascadecohortsync.php index 63792ee..ba54ce3 100644 --- a/classes/cascadecohortsync.php +++ b/classes/cascadecohortsync.php @@ -3,18 +3,15 @@ namespace local_treestudyplan; require_once($CFG->libdir.'/externallib.php'); use \local_treestudyplan\studyplan; -use \local_treestudyplan\local\helpers\debugger; class cascadecohortsync { private const METHOD = "cohort"; private $studyplan; private $studyplanid; - private $debug; 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){ @@ -26,7 +23,6 @@ class cascadecohortsync { } return $a; } - static function uploadenrolmentmethods_get_group($courseid, $groupname) { // Function shamelessly copied from tool/uploadenrolmentmethods/locallib.php @@ -60,23 +56,20 @@ class cascadecohortsync { it was determined to be the simplest and cleanest solution. */ - $enrol = enrol_get_plugin(self::METHOD); - $this->debug->dump($enrol,"Enrol"); + $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(); - $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}"); + $course = \get_course($courseid); + //\mtrace("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}"); + //\mtrace("Processing cohort {$cohortid} {$cohort->shortname}"); $instanceparams = [ 'courseid' => $courseid, @@ -91,14 +84,12 @@ class cascadecohortsync { '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"); + //\mtrace("Instance exists"); // it already exists // check if this studyplan is already referenced in customtext4 in json format @@ -113,23 +104,21 @@ class cascadecohortsync { $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"); + //\mtrace("Adding this plan to the list"); $plans[] = (int)($this->studyplanid); $enrol->update_instance($instance,(object)["customtext4"=>json_encode($plans)]); } } 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 (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_group')); - $this->debug->write("Adding group {$groupname} for this method"); + //\mtrace("Adding group {$groupname} for this method"); // and make sure the $instancenewparams['customint2'] = self::uploadenrolmentmethods_get_group($courseid, $groupname); } @@ -137,24 +126,24 @@ class cascadecohortsync { 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 - $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)); $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. // First synchronise the enrolment. $cohorttrace = new \null_progress_trace(); $result = enrol_cohort_sync($cohorttrace, $cohortid); if($result > 0){ - $this->debug->write("Error during 'enrol_cohort_sync': code {$result}"); + //\mtrace("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"); + //\mtrace("Error - instance not added for some reason"); } } } @@ -169,7 +158,7 @@ class cascadecohortsync { 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}"); + //\mtrace("Autoremove scan for course {$courseid} {$course->shortname}"); // find all cohort syncs for this course $searchparams = [ 'courseid' => $courseid, @@ -182,26 +171,24 @@ class cascadecohortsync { 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}"); + //\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 $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"); + //\mtrace("Found this studyplan in the id list - removing from list"); //if this plan was referenced before // first remove the link $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"); + //\mtrace("No references are left, removing instance"); $enrol->delete_instance($instance); } else { // 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)]); } } @@ -211,6 +198,6 @@ class cascadecohortsync { } } } - $this->debug->write("Cascading complete"); + //\mtrace("Cascading complete"); } } \ No newline at end of file diff --git a/classes/cascadeusersync.php b/classes/cascadeusersync.php new file mode 100644 index 0000000..9d7e92b --- /dev/null +++ b/classes/cascadeusersync.php @@ -0,0 +1,64 @@ +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 + + } + } +} \ No newline at end of file diff --git a/classes/courseinfo.php b/classes/courseinfo.php index 1279912..5855ac3 100644 --- a/classes/courseinfo.php +++ b/classes/courseinfo.php @@ -16,6 +16,7 @@ class courseinfo { private $course; private $context; + private $coursecontext; private $studyitem; private static $contentitems = null; diff --git a/classes/studyplan.php b/classes/studyplan.php index 92c9616..1daedf2 100644 --- a/classes/studyplan.php +++ b/classes/studyplan.php @@ -797,7 +797,17 @@ class studyplan { 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 */ diff --git a/classes/task/autocohortsync.php b/classes/task/autocohortsync.php index 0b63d9e..4bc46d4 100644 --- a/classes/task/autocohortsync.php +++ b/classes/task/autocohortsync.php @@ -3,6 +3,7 @@ namespace local_treestudyplan\task; require_once($CFG->dirroot.'/course/externallib.php'); use local_treestudyplan\studyplan; use local_treestudyplan\cascadecohortsync; +use local_treestudyplan\cascadeusersync; class autocohortsync extends \core\task\scheduled_task { @@ -30,7 +31,12 @@ class autocohortsync extends \core\task\scheduled_task { \mtrace("Studyplan {$studyplan->shortname()} needs processing"); $enroller = new cascadecohortsync($studyplan); $enroller->sync(); + if(get_config("local_treestudyplan","csync_users")){ + $userenroller = new cascadeusersync($studyplan); + $userenroller->sync(); + } $studyplan->clear_csync_changed(); + } } \mtrace("Task done"); diff --git a/lang/en/local_treestudyplan.php b/lang/en/local_treestudyplan.php index 6c102be..58003a4 100644 --- a/lang/en/local_treestudyplan.php +++ b/lang/en/local_treestudyplan.php @@ -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['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['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'; diff --git a/lang/nl/local_treestudyplan.php b/lang/nl/local_treestudyplan.php index a0b4b8b..5c5f9bc 100644 --- a/lang/nl/local_treestudyplan.php +++ b/lang/nl/local_treestudyplan.php @@ -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['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['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['studyplan_add'] = 'Nieuw studieplan'; diff --git a/settings.php b/settings.php index 792cefa..687a4a6 100644 --- a/settings.php +++ b/settings.php @@ -192,6 +192,13 @@ if ($hassiteconfig) { 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 if (!during_initial_install()) { $options = get_default_enrol_roles(context_system::instance()); diff --git a/version.php b/version.php index 9218d8b..d674ad2 100644 --- a/version.php +++ b/version.php @@ -1,6 +1,6 @@ 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->dependencies = [