public function test_groups_delete_group_members() { global $DB; $this->resetAfterTest(); $course = $this->getDataGenerator()->create_course(); $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user1->id, $course->id); $this->getDataGenerator()->enrol_user($user2->id, $course->id); $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); // Test deletion of all the users. $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user1->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id)); $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id))); $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id))); $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id))); groups_delete_group_members($course->id); $this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id))); $this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id))); $this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id))); // Test deletion of a specific user. $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user1->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user2->id)); $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id))); $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id))); $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id))); groups_delete_group_members($course->id, $user2->id); $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user1->id))); $this->assertTrue($DB->record_exists('groups_members', array('groupid' => $group2->id, 'userid' => $user1->id))); $this->assertFalse($DB->record_exists('groups_members', array('groupid' => $group1->id, 'userid' => $user2->id))); }
/** * This function will empty a course of user data. * It will retain the activities and the structure of the course. * @param object $data an object containing all the settings including courseid (without magic quotes) * @return array status array of array component, item, error */ function reset_course_userdata($data) { global $CFG, $USER; require_once $CFG->libdir . '/gradelib.php'; require_once $CFG->dirroot . '/group/lib.php'; $data->courseid = $data->id; $context = get_context_instance(CONTEXT_COURSE, $data->courseid); // calculate the time shift of dates if (!empty($data->reset_start_date)) { // time part of course startdate should be zero $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old); } else { $data->timeshift = 0; } // result array: component, item, error $status = array(); // start the resetting $componentstr = get_string('general'); // move the course start time if (!empty($data->reset_start_date) and $data->timeshift) { // change course start data set_field('course', 'startdate', $data->reset_start_date, 'id', $data->courseid); // update all course and group events - do not move activity events $updatesql = "UPDATE {$CFG->prefix}event\n SET timestart = timestart + ({$data->timeshift})\n WHERE courseid={$data->courseid} AND instance=0"; execute_sql($updatesql, false); $status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => false); } if (!empty($data->reset_logs)) { delete_records('log', 'course', $data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('deletelogs'), 'error' => false); } if (!empty($data->reset_events)) { delete_records('event', 'courseid', $data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('deleteevents', 'calendar'), 'error' => false); } if (!empty($data->reset_notes)) { require_once $CFG->dirroot . '/notes/lib.php'; note_delete_all($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('deletenotes', 'notes'), 'error' => false); } $componentstr = get_string('roles'); if (!empty($data->reset_roles_overrides)) { $children = get_child_contexts($context); foreach ($children as $child) { delete_records('role_capabilities', 'contextid', $child->id); } delete_records('role_capabilities', 'contextid', $context->id); //force refresh for logged in users mark_context_dirty($context->path); $status[] = array('component' => $componentstr, 'item' => get_string('deletecourseoverrides', 'role'), 'error' => false); } if (!empty($data->reset_roles_local)) { $children = get_child_contexts($context); foreach ($children as $child) { role_unassign(0, 0, 0, $child->id); } //force refresh for logged in users mark_context_dirty($context->path); $status[] = array('component' => $componentstr, 'item' => get_string('deletelocalroles', 'role'), 'error' => false); } // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc. $data->unenrolled = array(); if (!empty($data->reset_roles)) { foreach ($data->reset_roles as $roleid) { if ($users = get_role_users($roleid, $context, false, 'u.id', 'u.id ASC')) { foreach ($users as $user) { role_unassign($roleid, $user->id, 0, $context->id); if (!has_capability('moodle/course:view', $context, $user->id)) { $data->unenrolled[$user->id] = $user->id; } } } } } if (!empty($data->unenrolled)) { $status[] = array('component' => $componentstr, 'item' => get_string('unenrol') . ' (' . count($data->unenrolled) . ')', 'error' => false); } $componentstr = get_string('groups'); // remove all group members if (!empty($data->reset_groups_members)) { groups_delete_group_members($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('removegroupsmembers', 'group'), 'error' => false); } // remove all groups if (!empty($data->reset_groups_remove)) { groups_delete_groups($data->courseid, false); $status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroups', 'group'), 'error' => false); } // remove all grouping members if (!empty($data->reset_groupings_members)) { groups_delete_groupings_groups($data->courseid, false); $status[] = array('component' => $componentstr, 'item' => get_string('removegroupingsmembers', 'group'), 'error' => false); } // remove all groupings if (!empty($data->reset_groupings_remove)) { groups_delete_groupings($data->courseid, false); $status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroupings', 'group'), 'error' => false); } // Look in every instance of every module for data to delete $unsupported_mods = array(); if ($allmods = get_records('modules')) { foreach ($allmods as $mod) { $modname = $mod->name; if (!count_records($modname, 'course', $data->courseid)) { continue; // skip mods with no instances } $modfile = $CFG->dirroot . '/mod/' . $modname . '/lib.php'; $moddeleteuserdata = $modname . '_reset_userdata'; // Function to delete user data if (file_exists($modfile)) { include_once $modfile; if (function_exists($moddeleteuserdata)) { $modstatus = $moddeleteuserdata($data); if (is_array($modstatus)) { $status = array_merge($status, $modstatus); } else { debugging('Module ' . $modname . ' returned incorrect staus - must be an array!'); } } else { $unsupported_mods[] = $mod; } } else { debugging('Missing lib.php in ' . $modname . ' module!'); } } } // mention unsupported mods if (!empty($unsupported_mods)) { foreach ($unsupported_mods as $mod) { $status[] = array('component' => get_string('modulenameplural', $mod->name), 'item' => '', 'error' => get_string('resetnotimplemented')); } } $componentstr = get_string('gradebook', 'grades'); // reset gradebook if (!empty($data->reset_gradebook_items)) { remove_course_grades($data->courseid, false); grade_grab_course_grades($data->courseid); grade_regrade_final_grades($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('removeallcourseitems', 'grades'), 'error' => false); } else { if (!empty($data->reset_gradebook_grades)) { grade_course_reset($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('removeallcoursegrades', 'grades'), 'error' => false); } } return $status; }
/** * This function will empty a course of user data. * It will retain the activities and the structure of the course. * * @param object $data an object containing all the settings including courseid (without magic quotes) * @return array status array of array component, item, error */ function reset_course_userdata($data) { global $CFG, $DB; require_once $CFG->libdir . '/gradelib.php'; require_once $CFG->libdir . '/completionlib.php'; require_once $CFG->dirroot . '/group/lib.php'; $data->courseid = $data->id; $context = context_course::instance($data->courseid); $eventparams = array('context' => $context, 'courseid' => $data->id, 'other' => array('reset_options' => (array) $data)); $event = \core\event\course_reset_started::create($eventparams); $event->trigger(); // Calculate the time shift of dates. if (!empty($data->reset_start_date)) { // Time part of course startdate should be zero. $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old); } else { $data->timeshift = 0; } // Result array: component, item, error. $status = array(); // Start the resetting. $componentstr = get_string('general'); // Move the course start time. if (!empty($data->reset_start_date) and $data->timeshift) { // Change course start data. $DB->set_field('course', 'startdate', $data->reset_start_date, array('id' => $data->courseid)); // Update all course and group events - do not move activity events. $updatesql = "UPDATE {event}\n SET timestart = timestart + ?\n WHERE courseid=? AND instance=0"; $DB->execute($updatesql, array($data->timeshift, $data->courseid)); // Update any date activity restrictions. if ($CFG->enableavailability) { \availability_date\condition::update_all_dates($data->courseid, $data->timeshift); } $status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => false); } if (!empty($data->reset_end_date)) { // If the user set a end date value respect it. $DB->set_field('course', 'enddate', $data->reset_end_date, array('id' => $data->courseid)); } else { if ($data->timeshift > 0 && $data->reset_end_date_old) { // If there is a time shift apply it to the end date as well. $enddate = $data->reset_end_date_old + $data->timeshift; $DB->set_field('course', 'enddate', $enddate, array('id' => $data->courseid)); } } if (!empty($data->reset_events)) { $DB->delete_records('event', array('courseid' => $data->courseid)); $status[] = array('component' => $componentstr, 'item' => get_string('deleteevents', 'calendar'), 'error' => false); } if (!empty($data->reset_notes)) { require_once $CFG->dirroot . '/notes/lib.php'; note_delete_all($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('deletenotes', 'notes'), 'error' => false); } if (!empty($data->delete_blog_associations)) { require_once $CFG->dirroot . '/blog/lib.php'; blog_remove_associations_for_course($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('deleteblogassociations', 'blog'), 'error' => false); } if (!empty($data->reset_completion)) { // Delete course and activity completion information. $course = $DB->get_record('course', array('id' => $data->courseid)); $cc = new completion_info($course); $cc->delete_all_completion_data(); $status[] = array('component' => $componentstr, 'item' => get_string('deletecompletiondata', 'completion'), 'error' => false); } if (!empty($data->reset_competency_ratings)) { \core_competency\api::hook_course_reset_competency_ratings($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('deletecompetencyratings', 'core_competency'), 'error' => false); } $componentstr = get_string('roles'); if (!empty($data->reset_roles_overrides)) { $children = $context->get_child_contexts(); foreach ($children as $child) { $DB->delete_records('role_capabilities', array('contextid' => $child->id)); } $DB->delete_records('role_capabilities', array('contextid' => $context->id)); // Force refresh for logged in users. $context->mark_dirty(); $status[] = array('component' => $componentstr, 'item' => get_string('deletecourseoverrides', 'role'), 'error' => false); } if (!empty($data->reset_roles_local)) { $children = $context->get_child_contexts(); foreach ($children as $child) { role_unassign_all(array('contextid' => $child->id)); } // Force refresh for logged in users. $context->mark_dirty(); $status[] = array('component' => $componentstr, 'item' => get_string('deletelocalroles', 'role'), 'error' => false); } // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc. $data->unenrolled = array(); if (!empty($data->unenrol_users)) { $plugins = enrol_get_plugins(true); $instances = enrol_get_instances($data->courseid, true); foreach ($instances as $key => $instance) { if (!isset($plugins[$instance->enrol])) { unset($instances[$key]); continue; } } foreach ($data->unenrol_users as $withroleid) { if ($withroleid) { $sql = "SELECT ue.*\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)\n JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)\n JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)"; $params = array('courseid' => $data->courseid, 'roleid' => $withroleid, 'courselevel' => CONTEXT_COURSE); } else { // Without any role assigned at course context. $sql = "SELECT ue.*\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)\n JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)\n LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid)\n WHERE ra.id IS null"; $params = array('courseid' => $data->courseid, 'courselevel' => CONTEXT_COURSE); } $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $ue) { if (!isset($instances[$ue->enrolid])) { continue; } $instance = $instances[$ue->enrolid]; $plugin = $plugins[$instance->enrol]; if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) { continue; } $plugin->unenrol_user($instance, $ue->userid); $data->unenrolled[$ue->userid] = $ue->userid; } $rs->close(); } } if (!empty($data->unenrolled)) { $status[] = array('component' => $componentstr, 'item' => get_string('unenrol', 'enrol') . ' (' . count($data->unenrolled) . ')', 'error' => false); } $componentstr = get_string('groups'); // Remove all group members. if (!empty($data->reset_groups_members)) { groups_delete_group_members($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('removegroupsmembers', 'group'), 'error' => false); } // Remove all groups. if (!empty($data->reset_groups_remove)) { groups_delete_groups($data->courseid, false); $status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroups', 'group'), 'error' => false); } // Remove all grouping members. if (!empty($data->reset_groupings_members)) { groups_delete_groupings_groups($data->courseid, false); $status[] = array('component' => $componentstr, 'item' => get_string('removegroupingsmembers', 'group'), 'error' => false); } // Remove all groupings. if (!empty($data->reset_groupings_remove)) { groups_delete_groupings($data->courseid, false); $status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroupings', 'group'), 'error' => false); } // Look in every instance of every module for data to delete. $unsupportedmods = array(); if ($allmods = $DB->get_records('modules')) { foreach ($allmods as $mod) { $modname = $mod->name; $modfile = $CFG->dirroot . '/mod/' . $modname . '/lib.php'; $moddeleteuserdata = $modname . '_reset_userdata'; // Function to delete user data. if (file_exists($modfile)) { if (!$DB->count_records($modname, array('course' => $data->courseid))) { continue; // Skip mods with no instances. } include_once $modfile; if (function_exists($moddeleteuserdata)) { $modstatus = $moddeleteuserdata($data); if (is_array($modstatus)) { $status = array_merge($status, $modstatus); } else { debugging('Module ' . $modname . ' returned incorrect staus - must be an array!'); } } else { $unsupportedmods[] = $mod; } } else { debugging('Missing lib.php in ' . $modname . ' module!'); } } } // Mention unsupported mods. if (!empty($unsupportedmods)) { foreach ($unsupportedmods as $mod) { $status[] = array('component' => get_string('modulenameplural', $mod->name), 'item' => '', 'error' => get_string('resetnotimplemented')); } } $componentstr = get_string('gradebook', 'grades'); // Reset gradebook,. if (!empty($data->reset_gradebook_items)) { remove_course_grades($data->courseid, false); grade_grab_course_grades($data->courseid); grade_regrade_final_grades($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('removeallcourseitems', 'grades'), 'error' => false); } else { if (!empty($data->reset_gradebook_grades)) { grade_course_reset($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('removeallcoursegrades', 'grades'), 'error' => false); } } // Reset comments. if (!empty($data->reset_comments)) { require_once $CFG->dirroot . '/comment/lib.php'; comment::reset_course_page_comments($context); } $event = \core\event\course_reset_ended::create($eventparams); $event->trigger(); return $status; }
/** * Unenrol user from course, * the last unenrolment removes all remaining roles. * * @param stdClass $instance * @param int $userid * @return void */ public function unenrol_user(stdClass $instance, $userid) { global $CFG, $USER, $DB; require_once "{$CFG->dirroot}/group/lib.php"; $name = $this->get_name(); $courseid = $instance->courseid; if ($instance->enrol !== $name) { throw new coding_exception('invalid enrol instance!'); } $context = context_course::instance($instance->courseid, MUST_EXIST); if (!($ue = $DB->get_record('user_enrolments', array('enrolid' => $instance->id, 'userid' => $userid)))) { // weird, user not enrolled return; } // Remove all users groups linked to this enrolment instance. if ($gms = $DB->get_records('groups_members', array('userid' => $userid, 'component' => 'enrol_' . $name, 'itemid' => $instance->id))) { foreach ($gms as $gm) { groups_remove_member($gm->groupid, $gm->userid); } } role_unassign_all(array('userid' => $userid, 'contextid' => $context->id, 'component' => 'enrol_' . $name, 'itemid' => $instance->id)); $DB->delete_records('user_enrolments', array('id' => $ue->id)); // add extra info and trigger event $ue->courseid = $courseid; $ue->enrol = $name; $sql = "SELECT 'x'\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid)\n WHERE ue.userid = :userid AND e.courseid = :courseid"; if ($DB->record_exists_sql($sql, array('userid' => $userid, 'courseid' => $courseid))) { $ue->lastenrol = false; events_trigger('user_unenrolled', $ue); // user still has some enrolments, no big cleanup yet } else { // the big cleanup IS necessary! require_once "{$CFG->libdir}/gradelib.php"; // remove all remaining roles role_unassign_all(array('userid' => $userid, 'contextid' => $context->id), true, false); //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc. groups_delete_group_members($courseid, $userid); grade_user_unenrol($courseid, $userid); $DB->delete_records('user_lastaccess', array('userid' => $userid, 'courseid' => $courseid)); $ue->lastenrol = true; // means user not enrolled any more events_trigger('user_unenrolled', $ue); } // reset all enrol caches $context->mark_dirty(); // reset current user enrolment caching if ($userid == $USER->id) { if (isset($USER->enrol['enrolled'][$courseid])) { unset($USER->enrol['enrolled'][$courseid]); } if (isset($USER->enrol['tempguest'][$courseid])) { unset($USER->enrol['tempguest'][$courseid]); remove_temp_course_roles($context); } } }
/** * This function will empty a course of user data. * It will retain the activities and the structure of the course. * * @param object $data an object containing all the settings including courseid (without magic quotes) * @return array status array of array component, item, error */ function reset_course_userdata($data) { global $CFG, $USER, $DB; require_once $CFG->libdir . '/gradelib.php'; require_once $CFG->libdir . '/completionlib.php'; require_once $CFG->dirroot . '/group/lib.php'; $data->courseid = $data->id; $context = get_context_instance(CONTEXT_COURSE, $data->courseid); // calculate the time shift of dates if (!empty($data->reset_start_date)) { // time part of course startdate should be zero $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old); } else { $data->timeshift = 0; } // result array: component, item, error $status = array(); // start the resetting $componentstr = get_string('general'); // move the course start time if (!empty($data->reset_start_date) and $data->timeshift) { // change course start data $DB->set_field('course', 'startdate', $data->reset_start_date, array('id' => $data->courseid)); // update all course and group events - do not move activity events $updatesql = "UPDATE {event}\n SET timestart = timestart + ?\n WHERE courseid=? AND instance=0"; $DB->execute($updatesql, array($data->timeshift, $data->courseid)); $status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => false); } if (!empty($data->reset_logs)) { $DB->delete_records('log', array('course' => $data->courseid)); $status[] = array('component' => $componentstr, 'item' => get_string('deletelogs'), 'error' => false); } if (!empty($data->reset_events)) { $DB->delete_records('event', array('courseid' => $data->courseid)); $status[] = array('component' => $componentstr, 'item' => get_string('deleteevents', 'calendar'), 'error' => false); } if (!empty($data->reset_notes)) { require_once $CFG->dirroot . '/notes/lib.php'; note_delete_all($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('deletenotes', 'notes'), 'error' => false); } if (!empty($data->delete_blog_associations)) { require_once $CFG->dirroot . '/blog/lib.php'; blog_remove_associations_for_course($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('deleteblogassociations', 'blog'), 'error' => false); } if (!empty($data->reset_course_completion)) { // Delete course completion information $course = $DB->get_record('course', array('id' => $data->courseid)); $cc = new completion_info($course); $cc->delete_course_completion_data(); $status[] = array('component' => $componentstr, 'item' => get_string('deletecoursecompletiondata', 'completion'), 'error' => false); } $componentstr = get_string('roles'); if (!empty($data->reset_roles_overrides)) { $children = get_child_contexts($context); foreach ($children as $child) { $DB->delete_records('role_capabilities', array('contextid' => $child->id)); } $DB->delete_records('role_capabilities', array('contextid' => $context->id)); //force refresh for logged in users mark_context_dirty($context->path); $status[] = array('component' => $componentstr, 'item' => get_string('deletecourseoverrides', 'role'), 'error' => false); } if (!empty($data->reset_roles_local)) { $children = get_child_contexts($context); foreach ($children as $child) { role_unassign_all(array('contextid' => $child->id)); } //force refresh for logged in users mark_context_dirty($context->path); $status[] = array('component' => $componentstr, 'item' => get_string('deletelocalroles', 'role'), 'error' => false); } // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc. $data->unenrolled = array(); if (!empty($data->unenrol_users)) { $plugins = enrol_get_plugins(true); $instances = enrol_get_instances($data->courseid, true); foreach ($instances as $key => $instance) { if (!isset($plugins[$instance->enrol])) { unset($instances[$key]); continue; } if (!$plugins[$instance->enrol]->allow_unenrol($instance)) { unset($instances[$key]); } } $sqlempty = $DB->sql_empty(); foreach ($data->unenrol_users as $withroleid) { $sql = "SELECT DISTINCT ue.userid, ue.enrolid\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)\n JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)\n JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)"; $params = array('courseid' => $data->courseid, 'roleid' => $withroleid, 'courselevel' => CONTEXT_COURSE); $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $ue) { if (!isset($instances[$ue->enrolid])) { continue; } $plugins[$instances[$ue->enrolid]->enrol]->unenrol_user($instances[$ue->enrolid], $ue->userid); $data->unenrolled[$ue->userid] = $ue->userid; } } } if (!empty($data->unenrolled)) { $status[] = array('component' => $componentstr, 'item' => get_string('unenrol', 'enrol') . ' (' . count($data->unenrolled) . ')', 'error' => false); } $componentstr = get_string('groups'); // remove all group members if (!empty($data->reset_groups_members)) { groups_delete_group_members($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('removegroupsmembers', 'group'), 'error' => false); } // remove all groups if (!empty($data->reset_groups_remove)) { groups_delete_groups($data->courseid, false); $status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroups', 'group'), 'error' => false); } // remove all grouping members if (!empty($data->reset_groupings_members)) { groups_delete_groupings_groups($data->courseid, false); $status[] = array('component' => $componentstr, 'item' => get_string('removegroupingsmembers', 'group'), 'error' => false); } // remove all groupings if (!empty($data->reset_groupings_remove)) { groups_delete_groupings($data->courseid, false); $status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroupings', 'group'), 'error' => false); } // Look in every instance of every module for data to delete $unsupported_mods = array(); if ($allmods = $DB->get_records('modules')) { foreach ($allmods as $mod) { $modname = $mod->name; if (!$DB->count_records($modname, array('course' => $data->courseid))) { continue; // skip mods with no instances } $modfile = $CFG->dirroot . '/mod/' . $modname . '/lib.php'; $moddeleteuserdata = $modname . '_reset_userdata'; // Function to delete user data if (file_exists($modfile)) { include_once $modfile; if (function_exists($moddeleteuserdata)) { $modstatus = $moddeleteuserdata($data); if (is_array($modstatus)) { $status = array_merge($status, $modstatus); } else { debugging('Module ' . $modname . ' returned incorrect staus - must be an array!'); } } else { $unsupported_mods[] = $mod; } } else { debugging('Missing lib.php in ' . $modname . ' module!'); } } } // mention unsupported mods if (!empty($unsupported_mods)) { foreach ($unsupported_mods as $mod) { $status[] = array('component' => get_string('modulenameplural', $mod->name), 'item' => '', 'error' => get_string('resetnotimplemented')); } } $componentstr = get_string('gradebook', 'grades'); // reset gradebook if (!empty($data->reset_gradebook_items)) { remove_course_grades($data->courseid, false); grade_grab_course_grades($data->courseid); grade_regrade_final_grades($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('removeallcourseitems', 'grades'), 'error' => false); } else { if (!empty($data->reset_gradebook_grades)) { grade_course_reset($data->courseid); $status[] = array('component' => $componentstr, 'item' => get_string('removeallcoursegrades', 'grades'), 'error' => false); } } // reset comments if (!empty($data->reset_comments)) { require_once $CFG->dirroot . '/comment/lib.php'; comment::reset_course_page_comments($context); } return $status; }
/** * Delete all groups from course * @param int $courseid * @param bool $showfeedback * @return bool success */ function groups_delete_groups($courseid, $showfeedback = false) { global $CFG; require_once $CFG->libdir . '/gdlib.php'; $groupssql = "SELECT id FROM {$CFG->prefix}groups g WHERE g.courseid = {$courseid}"; // delete any uses of groups groups_delete_groupings_groups($courseid, $showfeedback); groups_delete_group_members($courseid, 0, $showfeedback); // delete group pictures if ($groups = get_records('groups', 'courseid', $courseid)) { foreach ($groups as $group) { delete_profile_image($group->id, 'groups'); } } // delete group calendar events delete_records_select('event', "groupid IN ({$groupssql})"); delete_records('groups', 'courseid', $courseid); //trigger groups events events_trigger('groups_groups_deleted', $courseid); if ($showfeedback) { notify(get_string('deleted') . ' groups'); } return true; }
/** * Deletes one or more role assignments. You must specify at least one parameter. * @param $roleid * @param $userid * @param $groupid * @param $contextid * @param $enrol unassign only if enrolment type matches, NULL means anything * @return boolean - success or failure */ function role_unassign($roleid = 0, $userid = 0, $groupid = 0, $contextid = 0, $enrol = NULL) { global $USER, $CFG, $DB; require_once $CFG->dirroot . '/group/lib.php'; $success = true; $args = array('roleid', 'userid', 'groupid', 'contextid'); $select = array(); $params = array(); foreach ($args as $arg) { if (${$arg}) { $select[] = "{$arg} = ?"; $params[] = ${$arg}; } } if (!empty($enrol)) { $select[] = "enrol=?"; $params[] = $enrol; } if ($select) { if ($ras = $DB->get_records_select('role_assignments', implode(' AND ', $select), $params)) { $mods = get_list_of_plugins('mod'); foreach ($ras as $ra) { $fireevent = false; /// infinite loop protection when deleting recursively if (!($ra = $DB->get_record('role_assignments', array('id' => $ra->id)))) { continue; } if ($DB->delete_records('role_assignments', array('id' => $ra->id))) { $fireevent = true; } else { $success = false; } if (!($context = get_context_instance_by_id($ra->contextid))) { // strange error, not much to do continue; } /* mark contexts as dirty here, because we need the refreshed * caps bellow to delete group membership and user_lastaccess! * and yes, this is very expensive for bulk operations :-( */ mark_context_dirty($context->path); /// If the user is the current user, then do full reload of capabilities too. if (!empty($USER->id) && $USER->id == $ra->userid) { load_all_capabilities(); } /// Ask all the modules if anything needs to be done for this user foreach ($mods as $mod) { include_once $CFG->dirroot . '/mod/' . $mod . '/lib.php'; $functionname = $mod . '_role_unassign'; if (function_exists($functionname)) { $functionname($ra->userid, $context); // watch out, $context might be NULL if something goes wrong } } /// now handle metacourse role unassigment and removing from goups if in course context if ($context->contextlevel == CONTEXT_COURSE) { // cleanup leftover course groups/subscriptions etc when user has // no capability to view course // this may be slow, but this is the proper way of doing it if (!has_capability('moodle/course:view', $context, $ra->userid)) { // remove from groups groups_delete_group_members($context->instanceid, $ra->userid); // delete lastaccess records $DB->delete_records('user_lastaccess', array('userid' => $ra->userid, 'courseid' => $context->instanceid)); } //unassign roles in metacourses if needed if ($parents = $DB->get_records('course_meta', array('child_course' => $context->instanceid))) { foreach ($parents as $parent) { sync_metacourse($parent->parent_course); } } } if ($fireevent) { events_trigger('role_unassigned', $ra); } } } } return $success; }
function groups_delete_groups($courseid, $showfeedback = false) { global $CFG; require_once $CFG->libdir . '/gdlib.php'; // delete any uses of groups $sql = "DELETE FROM {$CFG->prefix}groupings_groups\n WHERE groupid in (SELECT id FROM {$CFG->prefix}groups g WHERE g.courseid = {$courseid})"; execute_sql($sql, false); groups_delete_group_members($courseid, false); // delete group pictures if ($groups = get_records('groups', 'courseid', $courseid)) { foreach ($groups as $group) { delete_profile_image($group->id, 'groups'); } } delete_records('groups', 'courseid', $courseid); if ($showfeedback) { notify(get_string('deleted') . ' groups'); } return true; }
/** * Delete all groups from course * * @param int $courseid * @param bool $showfeedback * @return bool success */ function groups_delete_groups($courseid, $showfeedback = false) { global $CFG, $DB, $OUTPUT; // delete any uses of groups // Any associated files are deleted as part of groups_delete_groupings_groups groups_delete_groupings_groups($courseid, $showfeedback); groups_delete_group_members($courseid, 0, $showfeedback); // delete group pictures and descriptions $context = context_course::instance($courseid); $fs = get_file_storage(); $fs->delete_area_files($context->id, 'group'); // delete group calendar events $groupssql = "SELECT id FROM {groups} g WHERE g.courseid = ?"; $DB->delete_records_select('event', "groupid IN ({$groupssql})", array($courseid)); $context = context_course::instance($courseid); $fs = get_file_storage(); $fs->delete_area_files($context->id, 'group'); $DB->delete_records('groups', array('courseid' => $courseid)); // Invalidate the grouping cache for the course cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($courseid)); // trigger groups events events_trigger('groups_groups_deleted', $courseid); if ($showfeedback) { echo $OUTPUT->notification(get_string('deleted') . ' - ' . get_string('groups', 'group'), 'notifysuccess'); } return true; }
/** * Duplicate a course * * @param int $courseid * @param string $fullname Duplicated course fullname * @param int $newcourse Destination course * @param array $options List of backup options * @return stdClass New course info */ function local_ltiprovider_duplicate_course($courseid, $newcourse, $visible = 1, $options = array(), $useridcreating = null, $context) { global $CFG, $USER, $DB; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/util/includes/restore_includes.php'; if (empty($USER)) { // Emulate session. cron_setup_user(); } // Context validation. if (!($course = $DB->get_record('course', array('id' => $courseid)))) { throw new moodle_exception('invalidcourseid', 'error'); } $removeoptions = array(); $removeoptions['keep_roles_and_enrolments'] = true; $removeoptions['keep_groups_and_groupings'] = true; remove_course_contents($newcourse->id, false, $removeoptions); $backupdefaults = array('activities' => 1, 'blocks' => 1, 'filters' => 1, 'users' => 0, 'role_assignments' => 0, 'comments' => 0, 'userscompletion' => 0, 'logs' => 0, 'grade_histories' => 0); $backupsettings = array(); // Check for backup and restore options. if (!empty($options)) { foreach ($options as $option) { // Strict check for a correct value (allways 1 or 0, true or false). $value = clean_param($option['value'], PARAM_INT); if ($value !== 0 and $value !== 1) { throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); } if (!isset($backupdefaults[$option['name']])) { throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']); } $backupsettings[$option['name']] = $value; } } // Backup the course. $admin = get_admin(); $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $admin->id); foreach ($backupsettings as $name => $value) { $bc->get_plan()->get_setting($name)->set_value($value); } $backupid = $bc->get_backupid(); $backupbasepath = $bc->get_plan()->get_basepath(); $bc->execute_plan(); $results = $bc->get_results(); $file = $results['backup_destination']; $bc->destroy(); // Restore the backup immediately. // Check if we need to unzip the file because the backup temp dir does not contains backup files. if (!file_exists($backupbasepath . "/moodle_backup.xml")) { $file->extract_to_pathname(get_file_packer(), $backupbasepath); } $rc = new restore_controller($backupid, $newcourse->id, backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $admin->id, backup::TARGET_CURRENT_DELETING); foreach ($backupsettings as $name => $value) { $setting = $rc->get_plan()->get_setting($name); if ($setting->get_status() == backup_setting::NOT_LOCKED) { $setting->set_value($value); } } if (!$rc->execute_precheck()) { $precheckresults = $rc->get_precheck_results(); if (is_array($precheckresults) && !empty($precheckresults['errors'])) { if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } $errorinfo = ''; foreach ($precheckresults['errors'] as $error) { $errorinfo .= $error; } if (array_key_exists('warnings', $precheckresults)) { foreach ($precheckresults['warnings'] as $warning) { $errorinfo .= $warning; } } throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo); } } $rc->execute_plan(); $rc->destroy(); $course = $DB->get_record('course', array('id' => $newcourse->id), '*', MUST_EXIST); $course->visible = $visible; $course->fullname = $newcourse->fullname; $course->shortname = $newcourse->shortname; $course->idnumber = $newcourse->idnumber; // Set shortname and fullname back. $DB->update_record('course', $course); if (empty($CFG->keeptempdirectoriesonbackup)) { fulldelete($backupbasepath); } // Delete the course backup file created by this WebService. Originally located in the course backups area. $file->delete(); // We have to unenroll all the user except the one that create the course. if (get_config('local_ltiprovider', 'duplicatecourseswithoutusers') and $useridcreating) { require_once $CFG->dirroot . '/group/lib.php'; // Previous to unenrol users, we assign some type of activities to the user that created the course. if ($user = $DB->get_record('user', array('id' => $useridcreating))) { if ($databases = $DB->get_records('data', array('course' => $course->id))) { foreach ($databases as $data) { $DB->execute("UPDATE {data_records} SET userid = ? WHERE dataid = ?", array($user->id, $data->id)); } } if ($glossaries = $DB->get_records('glossary', array('course' => $course->id))) { foreach ($glossaries as $glossary) { $DB->execute("UPDATE {glossary_entries} SET userid = ? WHERE glossaryid = ?", array($user->id, $glossary->id)); } } // Same for questions. $newcoursecontextid = context_course::instance($course->id); if ($qcategories = $DB->get_records('question_categories', array('contextid' => $newcoursecontextid->id))) { foreach ($qcategories as $qcategory) { $DB->execute("UPDATE {question} SET createdby = ?, modifiedby = ? WHERE category = ?", array($user->id, $user->id, $qcategory->id)); } } // Enrol the user. if ($tool = $DB->get_record('local_ltiprovider', array('contextid' => $newcoursecontextid->id))) { $roles = explode(',', strtolower($context->info['roles'])); local_ltiprovider_enrol_user($tool, $user, $roles, true); } // Now, we unenrol all the users except the one who created the course. $plugins = enrol_get_plugins(true); $instances = enrol_get_instances($course->id, true); foreach ($instances as $key => $instance) { if (!isset($plugins[$instance->enrol])) { unset($instances[$key]); continue; } } $sql = "SELECT ue.*\n FROM {user_enrolments} ue\n JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)\n JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)"; $params = array('courseid' => $course->id, 'courselevel' => CONTEXT_COURSE); $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $ue) { if ($ue->userid == $user->id) { continue; } if (!isset($instances[$ue->enrolid])) { continue; } $instance = $instances[$ue->enrolid]; $plugin = $plugins[$instance->enrol]; if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) { continue; } $plugin->unenrol_user($instance, $ue->userid); } $rs->close(); groups_delete_group_members($course->id); groups_delete_groups($course->id, false); groups_delete_groupings_groups($course->id, false); groups_delete_groupings($course->id, false); } } return $course; }
/** * Unenrol user from course, * the last unenrolment removes all remaining roles. * * @param stdClass $instance * @param int $userid * @return void */ public function unenrol_user(stdClass $instance, $userid) { global $CFG, $USER, $DB; $name = $this->get_name(); $courseid = $instance->courseid; if ($instance->enrol !== $name) { throw new coding_exception('invalid enrol instance!'); } $context = get_context_instance(CONTEXT_COURSE, $instance->courseid, MUST_EXIST); if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) { // weird, user not enrolled return; } role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id)); $DB->delete_records('user_enrolments', array('id'=>$ue->id)); // add extra info and trigger event $ue->courseid = $courseid; $ue->enrol = $name; $sql = "SELECT 'x' FROM {user_enrolments} ue JOIN {enrol} e ON (e.id = ue.enrolid) WHERE ue.userid = :userid AND e.courseid = :courseid"; if ($DB->record_exists_sql($sql, array('userid'=>$userid, 'courseid'=>$courseid))) { $ue->lastenrol = false; events_trigger('user_unenrolled', $ue); // user still has some enrolments, no big cleanup yet } else { // the big cleanup IS necessary! require_once("$CFG->dirroot/group/lib.php"); require_once("$CFG->libdir/gradelib.php"); // remove all remaining roles role_unassign_all(array('userid'=>$userid, 'contextid'=>$context->id), true, false); //clean up ALL invisible user data from course if this is the last enrolment - groups, grades, etc. groups_delete_group_members($courseid, $userid); grade_user_unenrol($courseid, $userid); $DB->delete_records('user_lastaccess', array('userid'=>$userid, 'courseid'=>$courseid)); $ue->lastenrol = true; // means user not enrolled any more events_trigger('user_unenrolled', $ue); } // reset primitive require_login() caching if ($userid == $USER->id) { if (isset($USER->enrol['enrolled'][$courseid])) { unset($USER->enrol['enrolled'][$courseid]); } if (isset($USER->enrol['tempguest'][$courseid])) { unset($USER->enrol['tempguest'][$courseid]); $USER->access = remove_temp_roles($context, $USER->access); } } }
/** * This function will empty a course of USER data as much as /// possible. It will retain the activities and the structure /// of the course. * * @uses $USER * @uses $SESSION * @uses $CFG * @param object $data an object containing all the boolean settings and courseid * @param bool $showfeedback if false then do it all silently * @return bool * @todo Finish documenting this function */ function reset_course_userdata($data, $showfeedback = true) { global $CFG, $USER, $SESSION; require_once $CFG->dirroot . '/group/lib.php'; $result = true; $strdeleted = get_string('deleted'); // Look in every instance of every module for data to delete if ($allmods = get_records('modules')) { foreach ($allmods as $mod) { $modname = $mod->name; $modfile = $CFG->dirroot . '/mod/' . $modname . '/lib.php'; $moddeleteuserdata = $modname . '_delete_userdata'; // Function to delete user data if (file_exists($modfile)) { @(include_once $modfile); if (function_exists($moddeleteuserdata)) { $moddeleteuserdata($data, $showfeedback); } } } } else { error('No modules are installed!'); } // Delete other stuff $context = get_context_instance(CONTEXT_COURSE, $data->courseid); if (!empty($data->reset_students) or !empty($data->reset_teachers)) { $teachers = array_keys(get_users_by_capability($context, 'moodle/course:update')); $participants = array_keys(get_users_by_capability($context, 'moodle/course:view')); $students = array_diff($participants, $teachers); if (!empty($data->reset_students)) { foreach ($students as $studentid) { role_unassign(0, $studentid, 0, $context->id); } if ($showfeedback) { notify($strdeleted . ' ' . get_string('students'), 'notifysuccess'); } /// Delete group members (but keep the groups) $result = groups_delete_group_members($data->courseid, $showfeedback) && $result; } if (!empty($data->reset_teachers)) { foreach ($teachers as $teacherid) { role_unassign(0, $teacherid, 0, $context->id); } if ($showfeedback) { notify($strdeleted . ' ' . get_string('teachers'), 'notifysuccess'); } } } if (!empty($data->reset_groups)) { $result = groups_delete_groupings($data->courseid, $showfeedback) && $result; $result = groups_delete_groups($data->courseid, $showfeedback) && $result; } if (!empty($data->reset_events)) { if (delete_records('event', 'courseid', $data->courseid)) { if ($showfeedback) { notify($strdeleted . ' event', 'notifysuccess'); } } else { $result = false; } } if (!empty($data->reset_logs)) { if (delete_records('log', 'course', $data->courseid)) { if ($showfeedback) { notify($strdeleted . ' log', 'notifysuccess'); } } else { $result = false; } } // deletes all role assignments, and local override, // these have no courseid in table and needs separate process delete_records('role_capabilities', 'contextid', $context->id); // force accessinfo refresh for users visiting this context... mark_context_dirty($context->path); return $result; }
/** * Test the group event handlers */ public function test_group_event_handlers() { global $DB,$CFG; $this->resetAfterTest(); $this->setAdminUser(); // Setup course, user and groups $course = $this->getDataGenerator()->create_course(); $user1 = $this->getDataGenerator()->create_user(); $studentrole = $DB->get_record('role', array('shortname'=>'student')); $this->assertNotEmpty($studentrole); $this->assertTrue(enrol_try_internal_enrol($course->id, $user1->id, $studentrole->id)); $group1 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id)); $group2 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id)); $this->assertTrue(groups_add_member($group1, $user1)); $this->assertTrue(groups_add_member($group2, $user1)); $uniqueid = 0; $quiz_generator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); $quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>0)); // add a group1 override $DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>null)); // add an attempt $attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++)); // update timecheckstate quiz_update_open_attempts(array('quizid'=>$quiz->id)); $this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid))); // remove from group $this->assertTrue(groups_remove_member($group1, $user1)); $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid))); // add back to group $this->assertTrue(groups_add_member($group1, $user1)); $this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid))); // delete group groups_delete_group($group1); $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid))); $this->assertEquals(0, $DB->count_records('quiz_overrides', array('quiz'=>$quiz->id))); // add a group2 override $DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1400, 'timelimit'=>null)); quiz_update_open_attempts(array('quizid'=>$quiz->id)); $this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid))); // delete user1 from all groups groups_delete_group_members($course->id, $user1->id); $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid))); // add back to group2 $this->assertTrue(groups_add_member($group2, $user1)); $this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid))); // delete everyone from all groups groups_delete_group_members($course->id); $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid))); }
/** * Delete all groups from course * @param int $courseid * @param bool $showfeedback * @return bool success */ function groups_delete_groups($courseid, $showfeedback = false) { global $CFG, $DB, $OUTPUT; require_once $CFG->libdir . '/gdlib.php'; // delete any uses of groups groups_delete_groupings_groups($courseid, $showfeedback); groups_delete_group_members($courseid, 0, $showfeedback); // delete group pictures if ($groups = $DB->get_records('groups', array('courseid' => $courseid))) { foreach ($groups as $group) { delete_profile_image($group->id, 'groups'); } } // delete group calendar events $groupssql = "SELECT id FROM {groups} g WHERE g.courseid = ?"; $DB->delete_records_select('event', "groupid IN ({$groupssql})", array($courseid)); $DB->delete_records('groups', array('courseid' => $courseid)); //trigger groups events events_trigger('groups_groups_deleted', $courseid); if ($showfeedback) { echo $OUTPUT->notification(get_string('deleted') . ' groups'); } return true; }