diff --git a/classes/observers.php b/classes/observers.php index c7a9509..769dc23 100644 --- a/classes/observers.php +++ b/classes/observers.php @@ -40,13 +40,15 @@ public static function enrol_instance_created(\core\event\enrol_instance_created if (strcasecmp($instance->enrol, 'meta') == 0) { $course = get_course($instance->courseid); + $syncall = get_config('local_metagroups', 'syncall'); + // Return early if course doesn't use groups. - if (groups_get_course_groupmode($course) == NOGROUPS) { + if (!$syncall || groups_get_course_groupmode($course) == NOGROUPS) { return; } // Immediate synchronization could be expensive, defer to adhoc task. - $task = new \local_metagroups\task\synchronize(); + $task = new \local_metagroups\task\adhoc(); $task->set_custom_data(['courseid' => $course->id]); \core\task\manager::queue_adhoc_task($task); @@ -86,14 +88,16 @@ public static function enrol_instance_deleted(\core\event\enrol_instance_deleted public static function group_created(\core\event\group_created $event) { global $DB; + $syncall = get_config('local_metagroups', 'syncall'); + $group = $event->get_record_snapshot('groups', $event->objectid); $courseids = local_metagroups_parent_courses($group->courseid); foreach ($courseids as $courseid) { $course = get_course($courseid); - // If parent course doesn't use groups, we can skip synchronization. - if (groups_get_course_groupmode($course) == NOGROUPS) { + // If parent course doesn't use groups and syncall disabled, we can skip synchronization. + if (!$syncall && groups_get_course_groupmode($course) == NOGROUPS) { continue; } @@ -102,6 +106,12 @@ public static function group_created(\core\event\group_created $event) { $metagroup->courseid = $course->id; $metagroup->idnumber = $group->id; $metagroup->name = $group->name; + $metagroup->description = $group->description; + $metagroup->descriptionformat = $group->descriptionformat; + $metagroup->picture = $group->picture; + $metagroup->hidepicture = $group->hidepicture; + // No need to sync enrolmentkey, user should be able to enrol only on source course. + $metagroup->enrolmentkey = null; groups_create_group($metagroup, false, false); } @@ -125,6 +135,12 @@ public static function group_updated(\core\event\group_updated $event) { if ($metagroup = $DB->get_record('groups', array('courseid' => $course->id, 'idnumber' => $group->id))) { $metagroup->name = $group->name; + $metagroup->description = $group->description; + $metagroup->descriptionformat = $group->descriptionformat; + $metagroup->picture = $group->picture; + $metagroup->hidepicture = $group->hidepicture; + // No need to sync enrolmentkey, user should be able to enrol only on source course. + $metagroup->enrolmentkey = null; groups_update_group($metagroup, false, false); } @@ -195,4 +211,171 @@ public static function group_member_removed(\core\event\group_member_removed $ev } } } + + /** + * Grouping created + * + * @param \core\event\grouping_created $event + * @return void + */ + public static function grouping_created(\core\event\grouping_created $event) { + global $DB; + + $syncgroupings = get_config('local_metagroups', 'syncgroupings'); + if (!$syncgroupings) { + return; + } + + $grouping = $event->get_record_snapshot('groupings', $event->objectid); + + $courseids = local_metagroups_parent_courses($grouping->courseid); + foreach ($courseids as $courseid) { + $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); + + // If parent course doesn't use groups and syncall disabled, we can skip synchronization. + if (!$syncall && groups_get_course_groupmode($course) == NOGROUPS) { + continue; + } + + if (!$DB->record_exists('groupings', array('courseid' => $course->id, 'idnumber' => $grouping->id))) { + $metagrouping = new \stdClass(); + $metagrouping->courseid = $course->id; + $metagrouping->idnumber = $grouping->id; + $metagrouping->name = $grouping->name; + groups_create_grouping($metagrouping); + } + } + } + + /** + * Grouping deleted + * + * @param \core\event\grouping_deleted $event + * @return void + */ + public static function grouping_deleted(\core\event\grouping_deleted $event) { + global $DB; + + $syncgroupings = get_config('local_metagroups', 'syncgroupings'); + if (!$syncgroupings) { + return; + } + + $grouping = $event->get_record_snapshot('groupings', $event->objectid); + + $courseids = local_metagroups_parent_courses($grouping->courseid); + + foreach ($courseids as $courseid) { + $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); + + if ($metagrouping = $DB->get_record('groupings', array('courseid' => $course->id, 'idnumber' => $grouping->id))) { + groups_delete_grouping($metagrouping); + } + } + } + + /** + * Grouping updated + * + * @param \core\event\grouping_updated $event + * @return void + */ + public static function grouping_updated(\core\event\grouping_updated $event) { + global $DB; + + $syncgroupings = get_config('local_metagroups', 'syncgroupings'); + if (!$syncgroupings) { + return; + } + + $grouping = $event->get_record_snapshot('groupings', $event->objectid); + + $courseids = local_metagroups_parent_courses($grouping->courseid); + foreach ($courseids as $courseid) { + $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); + + if ($metagrouping = $DB->get_record('groupings', array('courseid' => $course->id, 'idnumber' => $grouping->id))) { + $metagrouping->name = $grouping->name; + groups_update_grouping($metagrouping); + } + } + } + + /** + * Course updated + * We need to listen for course_updated event to check situation, when group mode changed from NOGROUPS + * + * @param \core\event\course_updated $event + * @return void + */ + public static function course_updated(\core\event\course_updated $event) { + global $DB; + + $syncall = get_config('local_metagroups', 'syncall'); + if ($syncall) {// If syncall is on, then course is alredy synced. + return; + } + + // Immediate synchronization could be expensive, defer to adhoc task. + $task = new \local_metagroups\task\adhoc(); + $task->set_custom_data(['courseid' => $event->objectid]); + + \core\task\manager::queue_adhoc_task($task); + } + + /** + * Group assigned to grouping + * + * @param \core\event\grouping_group_assigned $event + * @return void + */ + public static function grouping_group_assigned(\core\event\grouping_group_assigned $event) { + global $DB; + + $syncgroupings = get_config('local_metagroups', 'syncgroupings'); + if (!$syncgroupings) { + return; + } + + $grouping = $event->get_record_snapshot('groupings', $event->objectid); + $groupid = $event->other['groupid']; + + $parents = local_metagroups_parent_courses($grouping->courseid); + foreach($parents as $parentid) { + if ($metagrouping = $DB->get_record('groupings', array('courseid' => $parentid, 'idnumber' => $grouping->id))) { + $targetgroups = local_metagroups_group_match($groupid, $parentid); + foreach ($targetgroups as $targetgroup) { + groups_assign_grouping($metagrouping->id, $targetgroup->id); + } + } + } + } + + /** + * Group unassigned from grouping + * + * @param \core\event\grouping_group_assigned $event + * @return void + */ + public static function grouping_group_unassigned(\core\event\grouping_group_unassigned $event) { + global $DB; + + $syncgroupings = get_config('local_metagroups', 'syncgroupings'); + if (!$syncgroupings) { + return; + } + + $grouping = $event->get_record_snapshot('groupings', $event->objectid); + $groupid = $event->other['groupid']; + + $parents = local_metagroups_parent_courses($grouping->courseid); + foreach($parents as $parentid) { + if ($metagrouping = $DB->get_record('groupings', array('courseid' => $parentid, 'idnumber' => $grouping->id))) { + $targetgroups = local_metagroups_group_match($groupid, $parentid); + foreach ($targetgroups as $targetgroup) { + groups_unassign_grouping($metagrouping->id, $targetgroup->id); + } + } + } + } } diff --git a/classes/task/synchronize.php b/classes/task/adhoc.php similarity index 96% rename from classes/task/synchronize.php rename to classes/task/adhoc.php index 769f12b..354d00d 100644 --- a/classes/task/synchronize.php +++ b/classes/task/adhoc.php @@ -26,7 +26,7 @@ require_once($CFG->dirroot . '/local/metagroups/locallib.php'); -class synchronize extends \core\task\adhoc_task { +class adhoc extends \core\task\adhoc_task { /** * Execute the synchronize task diff --git a/classes/task/scheduled.php b/classes/task/scheduled.php new file mode 100644 index 0000000..8ddac51 --- /dev/null +++ b/classes/task/scheduled.php @@ -0,0 +1,58 @@ +. + +/** + * Sync scheduled task + * + * @package local_metagroups + * @copyright 2016 Vadim Dvorovenko (vadimon@mail.ru) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace local_metagroups\task; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/local/metagroups/locallib.php'); + +/** + * Class for sync scheduled task + * @package local_metagroups + * @copyright 2016 Vadim Dvorovenko (vadimon@mail.ru) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class scheduled extends \core\task\scheduled_task { + + /** + * Get a descriptive name for this task (shown to admins). + * + * @return string + */ + public function get_name() { + return get_string('scheduledtask', 'local_metagroups'); + } + + /** + * Do the job. + * Throw exceptions on errors (the job will be retried). + */ + public function execute() { + $trace = new \text_progress_trace(); + local_metagroups_sync($trace, null); + $trace->finished(); + } + +} diff --git a/db/events.php b/db/events.php index 494ce88..dea8d8c 100644 --- a/db/events.php +++ b/db/events.php @@ -57,4 +57,35 @@ 'eventname' => '\core\event\group_member_removed', 'callback' => '\local_metagroups\observers::group_member_removed', ), + + array( + 'eventname' => '\core\event\grouping_created', + 'callback' => '\local_metagroups\observers::grouping_created', + ), + + array( + 'eventname' => '\core\event\grouping_updated', + 'callback' => '\local_metagroups\observers::grouping_updated', + ), + + array( + 'eventname' => '\core\event\grouping_deleted', + 'callback' => '\local_metagroups\observers::grouping_deleted', + ), + + array( + 'eventname' => '\core\event\course_updated', + 'callback' => '\local_metagroups\observers::course_updated', + ), + + array( + 'eventname' => '\core\event\grouping_group_assigned', + 'callback' => '\local_metagroups\observers::grouping_group_assigned', + ), + + array( + 'eventname' => '\core\event\grouping_group_unassigned', + 'callback' => '\local_metagroups\observers::grouping_group_unassigned', + ), + ); diff --git a/db/tasks.php b/db/tasks.php new file mode 100644 index 0000000..cd1cf8c --- /dev/null +++ b/db/tasks.php @@ -0,0 +1,38 @@ +. + +/** + * Scheduled tasks definition + * + * @package local_metagroups + * @copyright 2016 Vadim Dvorovenko (vadimon@mail.ru) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$tasks = array( + array( + 'classname' => 'local_metagroups\task\scheduled', + 'blocking' => 0, + 'minute' => '0', + 'hour' => '0', + 'day' => '*', + 'month' => '*', + 'dayofweek' => '*', + 'disabled' => 1 + ) +); diff --git a/lang/en/local_metagroups.php b/lang/en/local_metagroups.php index 1c79674..c4271f0 100644 --- a/lang/en/local_metagroups.php +++ b/lang/en/local_metagroups.php @@ -22,5 +22,10 @@ defined('MOODLE_INTERNAL') || die(); -$string['pluginname'] = 'Meta-course group synchronization'; +$string['pluginname'] = 'Meta-course group and grouping synchronization'; $string['privacy:metadata:core_group'] = 'The Meta-course group synchronization plugin can create groups or use existing groups to add participants of linked courses'; +$string['scheduledtask'] = 'Meta-course group and grouping synchronization'; +$string['syncall'] = 'Syncronize groups in all courses'; +$string['syncall_desc'] = 'If enabled, this plugin will syncronize groups and groupings in courses, where group mode is set to "No groups".'; +$string['syncgroupings'] = 'Syncronize groupings'; +$string['syncgroupings_desc'] = 'If enabled, this plugin will syncronize groupings in addition to groups.'; diff --git a/locallib.php b/locallib.php index 6478fe6..3ab68ae 100644 --- a/locallib.php +++ b/locallib.php @@ -56,6 +56,40 @@ function local_metagroups_child_courses($courseid) { return $DB->get_records_menu('enrol', $conditions, 'sortorder', 'id, customint1'); } +/** + * Returns the groups in the specified grouping. + * + * @param int $groupingid The groupingid to get the groups for + * @param string $fields The fields to return + * @param string $sort optional sorting of returned users + * @return array|bool Returns an array of the groups for the specified + * group or false if no groups or an error returned. + */ +function local_metagroups_get_grouping_groups($groupingid, $fields = 'g.*', $sort = 'name ASC') { + global $DB; + + return $DB->get_records_sql("SELECT $fields + FROM {groups} g, {groupings_groups} gg + WHERE g.id = gg.groupid AND gg.groupingid = ? + ORDER BY $sort", array($groupingid)); +} + +/** + * Returns the group id of a group in a parent course matching the child course. + * + * @param int $groupid The groupid to get the group for + * @param int $courseid The course "parent" id + * @return array|bool Returns an array of the id for the specified + * course or false if no group or an error returned. + */ +function local_metagroups_group_match($groupid, $courseid) { + global $DB; + + return $DB->get_records_sql("SELECT g.id + FROM {groups} g + WHERE g.idnumber = $groupid AND g.courseid=$courseid"); +} + /** * Run synchronization process * @@ -72,11 +106,14 @@ function local_metagroups_sync(progress_trace $trace, $courseid = null) { $courseids = local_metagroups_parent_courses(); } + $syncall = get_config('local_metagroups', 'syncall'); + $syncgroupings = get_config('local_metagroups', 'syncgroupings'); + foreach (array_unique($courseids) as $courseid) { $parent = get_course($courseid); - // If parent course doesn't use groups, we can skip synchronization. - if (groups_get_course_groupmode($parent) == NOGROUPS) { + // If parent course doesn't use groups and syncall disabled, we can skip synchronization. + if (!$syncall && groups_get_course_groupmode($parent) == NOGROUPS) { continue; } @@ -94,6 +131,12 @@ function local_metagroups_sync(progress_trace $trace, $courseid = null) { $metagroup->courseid = $parent->id; $metagroup->idnumber = $group->id; $metagroup->name = $group->name; + $metagroup->description = $group->description; + $metagroup->descriptionformat = $group->descriptionformat; + $metagroup->picture = $group->picture; + $metagroup->hidepicture = $group->hidepicture; + // No need to sync enrolmentkey, user should be able to enrol only on source course. + $metagroup->enrolmentkey = null; $metagroup->id = groups_create_group($metagroup, false, false); } @@ -105,6 +148,40 @@ function local_metagroups_sync(progress_trace $trace, $courseid = null) { groups_add_member($metagroup->id, $user->id, 'local_metagroups', $group->id); } } + + if (!$syncgroupings) { + continue; + } + + $childgroupings = groups_get_all_groupings($child->id); // Get groupings from child course. + foreach ($childgroupings as $grouping) { // Browse child course groupings and create in parent course if necessary. + if (!$metagrouping = $DB->get_record('groupings', array('courseid' => $parent->id, 'idnumber' => $grouping->id))) { + $metagrouping = new stdClass(); + $metagrouping->courseid = $parent->id; + $metagrouping->idnumber = $grouping->id; + $metagrouping->name = $grouping->name; + $metagrouping->id = groups_create_grouping($metagrouping); + } + + $trace->output($metagrouping->name, 3); + + // Get groups of current grouping (child course). + $groupinggroupschild = local_metagroups_get_grouping_groups($grouping->id); + // Get groups of current grouping (parent course). + $groupinggroupsparent = local_metagroups_get_grouping_groups($metagrouping->id); + + foreach ($groupinggroupschild as $groupinggroup) { + $targetgroupsid = local_metagroups_group_match($groupinggroup->id, $parent->id); // Should be one group. + foreach ($targetgroupsid as $targetgroupid) { + groups_assign_grouping($metagrouping->id, $targetgroupid->id); + unset($groupinggroupsparent[$targetgroupid->id]); // Unset from group for unassign afterward. + } + } + + foreach ($groupinggroupsparent as $groupinggroup) { // Unassign in parent course. + groups_unassign_grouping($metagrouping->id, $groupinggroup->id); + } + } } } } diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..4c521a2 --- /dev/null +++ b/settings.php @@ -0,0 +1,40 @@ +. + +/** + * local_metagroups plugin settings page + * + * @package local_metagroups + * @copyright 2016 Vadim Dvorovenko (vadimon@mail.ru) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +if ($hassiteconfig) { + $settings = new admin_settingpage('local_metagroups', get_string('pluginname', 'local_metagroups')); + $ADMIN->add('localplugins', $settings); + $settings->add(new admin_setting_configcheckbox( + 'local_metagroups/syncall', + get_string('syncall', 'local_metagroups'), + get_string('syncall_desc', 'local_metagroups'), + 0)); + $settings->add(new admin_setting_configcheckbox( + 'local_metagroups/syncgroupings', + get_string('syncgroupings', 'local_metagroups'), + get_string('syncgroupings_desc', 'local_metagroups'), + 1)); +} diff --git a/tests/observers_test.php b/tests/observers_test.php index 8194704..aebf1e2 100644 --- a/tests/observers_test.php +++ b/tests/observers_test.php @@ -76,7 +76,7 @@ public function test_enrol_instance_created() { // Execute queued plugin adhoc tasks. ob_start(); - $this->runAdhocTasks(\local_metagroups\task\synchronize::class); + $this->runAdhocTasks(\local_metagroups\task\adhoc::class); ob_end_clean(); // The group from the parent should have been created in linked course. diff --git a/version.php b/version.php index c8ed024..347662d 100644 --- a/version.php +++ b/version.php @@ -23,8 +23,8 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'local_metagroups'; -$plugin->release = '2.0'; -$plugin->version = 2018120700; +$plugin->release = '2.1'; +$plugin->version = 2019011400; $plugin->requires = 2017051500; // Moodle 3.3 onwards. $plugin->maturity = MATURITY_STABLE;