MDL-30131 improve current user enrol caching

This commit is contained in:
Petr Skoda 2011-11-05 12:12:48 +01:00
parent 735de1c276
commit bbfdff344c
9 changed files with 222 additions and 133 deletions

View File

@ -41,10 +41,13 @@ $all = enrol_get_plugins(false);
$return = new moodle_url('/admin/settings.php', array('section'=>'manageenrols'));
$syscontext = context_system::instance();
switch ($action) {
case 'disable':
unset($enabled[$enrol]);
set_config('enrol_plugins_enabled', implode(',', array_keys($enabled)));
$syscontext->mark_dirty(); // resets all enrol caches
break;
case 'enable':
@ -54,6 +57,7 @@ switch ($action) {
$enabled = array_keys($enabled);
$enabled[] = $enrol;
set_config('enrol_plugins_enabled', implode(',', $enabled));
$syscontext->mark_dirty(); // resets all enrol caches
break;
case 'up':
@ -106,6 +110,7 @@ switch ($action) {
} else { // Delete everything!!
uninstall_plugin('enrol', $enrol);
$syscontext->mark_dirty(); // resets all enrol caches
$a->plugin = $strplugin;
$a->directory = "$CFG->dirroot/enrol/$enrol";

View File

@ -68,7 +68,6 @@ class enrol_guest_plugin extends enrol_plugin {
* calling code has to make sure the plugin and instance are active.
*
* @param stdClass $instance course enrol instance
* @param stdClass $user record
* @return bool|int false means no guest access, integer means end of cached time
*/
public function try_guestaccess(stdClass $instance) {
@ -78,7 +77,7 @@ class enrol_guest_plugin extends enrol_plugin {
// Temporarily assign them some guest role for this context
$context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
load_temp_course_role($context, $CFG->guestroleid);
return ENROL_REQUIRE_LOGIN_CACHE_PERIOD + time();
return ENROL_MAX_TIMESTAMP;
}
return false;
@ -278,12 +277,18 @@ class enrol_guest_plugin extends enrol_plugin {
$i = $instance->id;
if (isset($data->{'enrol_guest_status_'.$i})) {
$reset = ($instance->status != $data->{'enrol_guest_status_'.$i});
$instance->status = $data->{'enrol_guest_status_'.$i};
$instance->timemodified = time();
if ($instance->status == ENROL_INSTANCE_ENABLED) {
$instance->password = $data->{'enrol_guest_password_'.$i};
}
$DB->update_record('enrol', $instance);
if ($reset) {
$context->mark_dirty();
}
}
}
}

View File

@ -98,6 +98,7 @@ if ($canconfig and $action and confirm_sesskey()) {
if ($confirm) {
$plugin->delete_instance($instance);
$context->mark_dirty(); // invalidate all enrol caches
redirect($PAGE->url);
}
@ -115,6 +116,7 @@ if ($canconfig and $action and confirm_sesskey()) {
if ($instance->status == ENROL_INSTANCE_ENABLED) {
$instance->status = ENROL_INSTANCE_DISABLED;
$DB->update_record('enrol', $instance);
$context->mark_dirty(); // invalidate all enrol caches
redirect($PAGE->url);
}
@ -123,6 +125,7 @@ if ($canconfig and $action and confirm_sesskey()) {
if ($instance->status == ENROL_INSTANCE_DISABLED) {
$instance->status = ENROL_INSTANCE_ENABLED;
$DB->update_record('enrol', $instance);
$context->mark_dirty(); // invalidate all enrol caches
redirect($PAGE->url);
}
}

View File

@ -70,12 +70,18 @@ if ($mform->is_cancelled()) {
} else if ($data = $mform->get_data()) {
if ($instance->id) {
$reset = ($instance->status != $data->status);
$instance->status = $data->status;
$instance->enrolperiod = $data->enrolperiod;
$instance->roleid = $data->roleid;
$instance->timemodified = time();
$DB->update_record('enrol', $instance);
if ($reset) {
$context->mark_dirty();
}
} else {
$fields = array('status'=>$data->status, 'enrolperiod'=>$data->enrolperiod, 'roleid'=>$data->roleid);
$plugin->add_instance($course, $fields);

View File

@ -65,6 +65,8 @@ if ($mform->is_cancelled()) {
} else if ($data = $mform->get_data()) {
if ($instance->id) {
$reset = ($instance->status != $data->status);
$instance->status = $data->status;
$instance->name = $data->name;
$instance->cost = $data->cost;
@ -76,6 +78,10 @@ if ($mform->is_cancelled()) {
$instance->timemodified = time();
$DB->update_record('enrol', $instance);
if ($reset) {
$context->mark_dirty();
}
} else {
$fields = array('status'=>$data->status, 'name'=>$data->name, 'cost'=>$data->cost, 'currency'=>$data->currency, 'roleid'=>$data->roleid,
'enrolperiod'=>$data->enrolperiod, 'enrolstartdate'=>$data->enrolstartdate, 'enrolenddate'=>$data->enrolenddate);

View File

@ -65,6 +65,8 @@ if ($mform->is_cancelled()) {
} else if ($data = $mform->get_data()) {
if ($instance->id) {
$reset = ($instance->status != $data->status);
$instance->status = $data->status;
$instance->name = $data->name;
$instance->password = $data->password;
@ -80,6 +82,10 @@ if ($mform->is_cancelled()) {
$instance->timemodified = time();
$DB->update_record('enrol', $instance);
if ($reset) {
$context->mark_dirty();
}
} else {
$fields = array('status'=>$data->status, 'name'=>$data->name, 'password'=>$data->password, 'customint1'=>$data->customint1, 'customint2'=>$data->customint2,
'customint3'=>$data->customint3, 'customint4'=>$data->customint4, 'customtext1'=>$data->customtext1,

View File

@ -1122,7 +1122,9 @@ function load_all_capabilities() {
// Clear to force a refresh
unset($USER->mycourses);
unset($USER->enrol);
// init/reset internal enrol caches - active course enrolments and temp access
$USER->enrol = array('enrolled'=>array(), 'tempguest'=>array());
}
/**
@ -1172,15 +1174,18 @@ function reload_all_capabilities() {
* @return void
*/
function load_temp_course_role(context_course $coursecontext, $roleid) {
global $USER;
//TODO: this gets removed if there are any dirty contexts, we should probably store list of these temp roles somewhere (skodak)
global $USER, $SITE;
if (empty($roleid)) {
debugging('invalid role specified in load_temp_course_role()');
return;
}
if ($coursecontext->instanceid == $SITE->id) {
debugging('Can not use temp roles on the frontpage');
return;
}
if (!isset($USER->access)) {
load_all_capabilities();
}
@ -1207,7 +1212,12 @@ function load_temp_course_role(context_course $coursecontext, $roleid) {
* @return void
*/
function remove_temp_course_roles(context_course $coursecontext) {
global $DB, $USER;
global $DB, $USER, $SITE;
if ($coursecontext->instanceid == $SITE->id) {
debugging('Can not use temp roles on the frontpage');
return;
}
if (empty($USER->access['ra'][$coursecontext->path])) {
//no roles here, weird
@ -1877,6 +1887,8 @@ function is_viewing(context $context, $user = null, $withcapability = '') {
* Returns true if user is enrolled (is participating) in course
* this is intended for students and teachers.
*
* Since 2.2 the result for active enrolments and current user are cached.
*
* @param context $context
* @param int|stdClass $user, if null $USER is used, otherwise user object or id expected
* @param string $withcapability extra capability name
@ -1907,32 +1919,35 @@ function is_enrolled(context $context, $user = null, $withcapability = '', $only
if ($coursecontext->instanceid == SITEID) {
// everybody participates on frontpage
} else {
if ($onlyactive) {
$sql = "SELECT ue.*
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
JOIN {user} u ON u.id = ue.userid
WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
$params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
// this result should be very small, better not do the complex time checks in sql for now ;-)
$enrolments = $DB->get_records_sql($sql, $params);
$now = time();
// make sure the enrol period is ok
$result = false;
foreach ($enrolments as $e) {
if ($e->timestart > $now) {
continue;
// try cached info first - the enrolled flag is set only when active enrolment present
if ($USER->id == $userid) {
$coursecontext->reload_if_dirty();
if (isset($USER->enrol['enrolled'][$coursecontext->instanceid])) {
if ($USER->enrol['enrolled'][$coursecontext->instanceid] > time()) {
return true;
}
if ($e->timeend and $e->timeend < $now) {
continue;
}
$result = true;
break;
}
if (!$result) {
}
if ($onlyactive) {
// look for active enrolments only
$until = enrol_get_enrolment_end($coursecontext->instanceid, $userid);
if ($until === false) {
return false;
}
if ($USER->id == $userid) {
if ($until == 0) {
$until = ENROL_MAX_TIMESTAMP;
}
$USER->enrol['enrolled'][$coursecontext->instanceid] = $until;
if (isset($USER->enrol['tempguest'][$coursecontext->instanceid])) {
unset($USER->enrol['tempguest'][$coursecontext->instanceid]);
remove_temp_course_roles($coursecontext);
}
}
} else {
// any enrolment is good for us here, even outdated, disabled or inactive
$sql = "SELECT 'x'
@ -2031,57 +2046,22 @@ function can_access_course(stdClass $course, $user = null, $withcapability = '',
// === from here we deal only with $USER ===
// verify our caches
if (!isset($USER->enrol)) {
/**
* These flags within the $USER object should NEVER be used outside of this
* function can_access_course and the function require_login.
* Doing so WILL break future versions!!!!
*/
$USER->enrol = array();
$USER->enrol['enrolled'] = array();
$USER->enrol['tempguest'] = array();
}
$coursecontext->reload_if_dirty();
if (isset($USER->enrol['enrolled'][$course->id])) {
if ($USER->enrol['enrolled'][$course->id] == 0) {
return true;
} else if ($USER->enrol['enrolled'][$course->id] > time()) {
if ($USER->enrol['enrolled'][$course->id] > time()) {
return true;
}
}
if (isset($USER->enrol['tempguest'][$course->id])) {
if ($USER->enrol['tempguest'][$course->id] == 0) {
return true;
} else if ($USER->enrol['tempguest'][$course->id] > time()) {
if ($USER->enrol['tempguest'][$course->id] > time()) {
return true;
}
}
if (is_enrolled($coursecontext, $USER, '', true)) {
// active participants may always access
// TODO: refactor this into some new function
$now = time();
$sql = "SELECT MAX(ue.timeend)
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
JOIN {user} u ON u.id = ue.userid
WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0
AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
$params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE,
'userid'=>$USER->id, 'courseid'=>$coursecontext->instanceid, 'now1'=>$now, 'now2'=>$now);
$until = $DB->get_field_sql($sql, $params);
if (!$until or $until > time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD) {
$until = time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD;
}
$USER->enrol['enrolled'][$course->id] = $until;
// remove traces of previous temp guest access
remove_temp_course_roles($coursecontext);
if (is_enrolled($coursecontext, $USER, '', $onlyactive)) {
return true;
}
unset($USER->enrol['enrolled'][$course->id]);
// if not enrolled try to gain temporary guest access
$instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
@ -2090,14 +2070,17 @@ function can_access_course(stdClass $course, $user = null, $withcapability = '',
if (!isset($enrols[$instance->enrol])) {
continue;
}
// Get a duration for the guestaccess, a timestamp in the future or false.
// Get a duration for the guest access, a timestamp in the future, 0 (always) or false.
$until = $enrols[$instance->enrol]->try_guestaccess($instance);
if ($until !== false) {
if ($until !== false and $until > time()) {
$USER->enrol['tempguest'][$course->id] = $until;
return true;
}
}
unset($USER->enrol['tempguest'][$course->id]);
if (isset($USER->enrol['tempguest'][$course->id])) {
unset($USER->enrol['tempguest'][$course->id]);
remove_temp_course_roles($coursecontext);
}
return false;
}

View File

@ -39,9 +39,12 @@ define('ENROL_USER_ACTIVE', 0);
/** User participation in course is suspended (used in user_enrolments->status) */
define('ENROL_USER_SUSPENDED', 1);
/** Enrol info is cached for this number of seconds in require_login() */
/** @deprecated - enrol caching was reworked, use ENROL_MAX_TIMESTAMP instead */
define('ENROL_REQUIRE_LOGIN_CACHE_PERIOD', 1800);
/** The timestamp indicating forever */
define('ENROL_MAX_TIMESTAMP', 2147483647);
/** When user disappears from external source, the enrolment is completely removed */
define('ENROL_EXT_REMOVED_UNENROL', 0);
@ -72,7 +75,7 @@ define('ENROL_EXT_REMOVED_SUSPENDNOROLES', 3);
/**
* Returns instances of enrol plugins
* @param bool $enable return enabled only
* @param bool $enabled return enabled only
* @return array of enrol plugins name=>instance
*/
function enrol_get_plugins($enabled) {
@ -804,7 +807,7 @@ function enrol_user_delete($user) {
/**
* Called when course is about to be deleted.
* @param stdClass $object
* @param stdClass $course
* @return void
*/
function enrol_course_delete($course) {
@ -885,6 +888,88 @@ function enrol_selfenrol_available($courseid) {
return $result;
}
/**
* This function returns the end of current active user enrolment.
*
* It deals correctly with multiple overlapping user enrolments.
*
* @param int $courseid
* @param int $userid
* @return int|bool timestamp when active enrolment ends, false means no active enrolment now, 0 means never
*/
function enrol_get_enrolment_end($courseid, $userid) {
global $DB;
$sql = "SELECT ue.*
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
JOIN {user} u ON u.id = ue.userid
WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0";
$params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$courseid);
if (!$enrolments = $DB->get_records_sql($sql, $params)) {
return false;
}
$changes = array();
foreach ($enrolments as $ue) {
$start = (int)$ue->timestart;
$end = (int)$ue->timeend;
if ($end != 0 and $end < $start) {
debugging('Invalid enrolment start or end in user_enrolment id:'.$ue->id);
continue;
}
if (isset($changes[$start])) {
$changes[$start] = $changes[$start] + 1;
} else {
$changes[$start] = 1;
}
if ($end === 0) {
// no end
} else if (isset($changes[$end])) {
$changes[$end] = $changes[$end] - 1;
} else {
$changes[$end] = -1;
}
}
// let's sort then enrolment starts&ends and go through them chronologically,
// looking for current status and the next future end of enrolment
ksort($changes);
$now = time();
$current = 0;
$present = null;
foreach ($changes as $time => $change) {
if ($time > $now) {
if ($present === null) {
// we have just went past current time
$present = $current;
if ($present < 1) {
// no enrolment active
return false;
}
}
if ($present !== null) {
// we are already in the future - look for possible end
if ($current + $change < 1) {
return $time;
}
}
}
$current += $change;
}
if ($current > 0) {
return 0;
} else {
return false;
}
}
/**
* All enrol plugins should be based on this class,
* this is also the main source of documentation.
@ -1051,7 +1136,6 @@ abstract class enrol_plugin {
* This should return either a timestamp in the future or false.
*
* @param stdClass $instance course enrol instance
* @param stdClass $user record
* @return bool|int false means not enrolled, integer means timeend
*/
public function try_autoenrol(stdClass $instance) {
@ -1067,7 +1151,6 @@ abstract class enrol_plugin {
* This should return either a timestamp in the future or false.
*
* @param stdClass $instance course enrol instance
* @param stdClass $user record
* @return bool|int false means no guest access, integer means timeend
*/
public function try_guestaccess(stdClass $instance) {
@ -1142,10 +1225,12 @@ abstract class enrol_plugin {
$ue->courseid = $courseid;
$ue->enrol = $name;
events_trigger('user_enrol_modified', $ue);
// resets current enrolment caches
$context->mark_dirty();
}
if ($roleid) {
// this must be done after the enrolment event so that the role_assigned event is trigerred aftwerwards
// this must be done after the enrolment event so that the role_assigned event is triggered afterwards
if ($this->roles_protected()) {
role_assign($roleid, $userid, $context->id, 'enrol_'.$name, $instance->id);
} else {
@ -1153,7 +1238,7 @@ abstract class enrol_plugin {
}
}
// reset primitive require_login() caching
// reset current user enrolment caching
if ($userid == $USER->id) {
if (isset($USER->enrol['enrolled'][$courseid])) {
unset($USER->enrol['enrolled'][$courseid]);
@ -1210,6 +1295,7 @@ abstract class enrol_plugin {
$ue->modifierid = $USER->id;
$DB->update_record('user_enrolments', $ue);
context_course::instance($instance->courseid)->mark_dirty(); // reset enrol caches
// trigger event
$ue->courseid = $instance->courseid;
@ -1275,7 +1361,11 @@ abstract class enrol_plugin {
$ue->lastenrol = true; // means user not enrolled any more
events_trigger('user_unenrolled', $ue);
}
// reset primitive require_login() caching
// 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]);
@ -1508,7 +1598,7 @@ abstract class enrol_plugin {
* By defaults looks for manage links only.
*
* @param navigation_node $instancesnode
* @param object $instance
* @param stdClass $instance
* @return void
*/
public function add_course_navigation($instancesnode, stdClass $instance) {

View File

@ -2758,13 +2758,6 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
}
}
// very simple enrolment caching - changes in course setting are not reflected immediately
if (!isset($USER->enrol)) {
$USER->enrol = array();
$USER->enrol['enrolled'] = array();
$USER->enrol['tempguest'] = array();
}
$access = false;
if (is_viewing($coursecontext, $USER)) {
@ -2773,10 +2766,12 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
} else {
if (isset($USER->enrol['enrolled'][$course->id])) {
if ($USER->enrol['enrolled'][$course->id] == 0) {
$access = true;
} else if ($USER->enrol['enrolled'][$course->id] > time()) {
if ($USER->enrol['enrolled'][$course->id] > time()) {
$access = true;
if (isset($USER->enrol['tempguest'][$course->id])) {
unset($USER->enrol['tempguest'][$course->id]);
remove_temp_course_roles($coursecontext);
}
} else {
//expired
unset($USER->enrol['enrolled'][$course->id]);
@ -2796,60 +2791,50 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
if ($access) {
// cache ok
} else if (is_enrolled($coursecontext, $USER, '', true)) {
// active participants may always access
// TODO: refactor this into some new function
$now = time();
$sql = "SELECT MAX(ue.timeend)
FROM {user_enrolments} ue
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
JOIN {user} u ON u.id = ue.userid
WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0
AND ue.timestart < :now1 AND (ue.timeend = 0 OR ue.timeend > :now2)";
$params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE,
'userid'=>$USER->id, 'courseid'=>$coursecontext->instanceid, 'now1'=>$now, 'now2'=>$now);
$until = $DB->get_field_sql($sql, $params);
if (!$until or $until > time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD) {
$until = time() + ENROL_REQUIRE_LOGIN_CACHE_PERIOD;
}
$USER->enrol['enrolled'][$course->id] = $until;
$access = true;
// remove traces of previous temp guest access
remove_temp_course_roles($coursecontext);
} else {
$instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
$enrols = enrol_get_plugins(true);
// first ask all enabled enrol instances in course if they want to auto enrol user
foreach($instances as $instance) {
if (!isset($enrols[$instance->enrol])) {
continue;
$until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
if ($until !== false) {
// active participants may always access, a timestamp in the future, 0 (always) or false.
if ($until == 0) {
$until = ENROL_MAX_TIMESTAMP;
}
// Get a duration for the guestaccess, a timestamp in the future or false.
$until = $enrols[$instance->enrol]->try_autoenrol($instance);
if ($until !== false) {
$USER->enrol['enrolled'][$course->id] = $until;
remove_temp_course_roles($coursecontext);
$access = true;
break;
}
}
// if not enrolled yet try to gain temporary guest access
if (!$access) {
$USER->enrol['enrolled'][$course->id] = $until;
$access = true;
} else {
$instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
$enrols = enrol_get_plugins(true);
// first ask all enabled enrol instances in course if they want to auto enrol user
foreach($instances as $instance) {
if (!isset($enrols[$instance->enrol])) {
continue;
}
// Get a duration for the guestaccess, a timestamp in the future or false.
$until = $enrols[$instance->enrol]->try_guestaccess($instance);
// Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
$until = $enrols[$instance->enrol]->try_autoenrol($instance);
if ($until !== false) {
$USER->enrol['tempguest'][$course->id] = $until;
if ($until == 0) {
$until = ENROL_MAX_TIMESTAMP;
}
$USER->enrol['enrolled'][$course->id] = $until;
$access = true;
break;
}
}
// if not enrolled yet try to gain temporary guest access
if (!$access) {
foreach($instances as $instance) {
if (!isset($enrols[$instance->enrol])) {
continue;
}
// Get a duration for the guest access, a timestamp in the future or false.
$until = $enrols[$instance->enrol]->try_guestaccess($instance);
if ($until !== false and $until > time()) {
$USER->enrol['tempguest'][$course->id] = $until;
$access = true;
break;
}
}
}
}
}
}