From 9f5170e9557d7f7fc75331a68ee0df04b4ccd0e8 Mon Sep 17 00:00:00 2001 From: John Beedell Date: Tue, 30 Aug 2016 10:58:29 +0100 Subject: [PATCH 1/3] MDL-31243 access/enrol libs: move enrol functions --- lib/accesslib.php | 345 ---------------------------------------------- lib/enrollib.php | 344 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 344 insertions(+), 345 deletions(-) diff --git a/lib/accesslib.php b/lib/accesslib.php index 19033df02f0..1b89ad9339f 100644 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -2035,98 +2035,6 @@ function is_viewing(context $context, $user = null, $withcapability = '') { return true; } -/** - * 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. - * - * @package core_enrol - * @category access - * - * @param context $context - * @param int|stdClass $user if null $USER is used, otherwise user object or id expected - * @param string $withcapability extra capability name - * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions - * @return bool - */ -function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) { - global $USER, $DB; - - // first find the course context - $coursecontext = $context->get_course_context(); - - // make sure there is a real user specified - if ($user === null) { - $userid = isset($USER->id) ? $USER->id : 0; - } else { - $userid = is_object($user) ? $user->id : $user; - } - - if (empty($userid)) { - // not-logged-in! - return false; - } else if (isguestuser($userid)) { - // guest account can not be enrolled anywhere - return false; - } - - if ($coursecontext->instanceid == SITEID) { - // everybody participates on frontpage - } else { - // 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()) { - if ($withcapability and !has_capability($withcapability, $context, $userid)) { - return false; - } - return true; - } - } - } - - 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' - 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 u.deleted = 0"; - $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid); - if (!$DB->record_exists_sql($sql, $params)) { - return false; - } - } - } - - if ($withcapability and !has_capability($withcapability, $context, $userid)) { - return false; - } - - return true; -} - /** * Returns true if the user is able to access the course. * @@ -2244,259 +2152,6 @@ function can_access_course(stdClass $course, $user = null, $withcapability = '', return false; } -/** - * Returns array with sql code and parameters returning all ids - * of users enrolled into course. - * - * This function is using 'eu[0-9]+_' prefix for table names and parameters. - * - * @package core_enrol - * @category access - * - * @param context $context - * @param string $withcapability - * @param int $groupid 0 means ignore groups, any other value limits the result by group id - * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions - * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments - * @return array list($sql, $params) - */ -function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false, $onlysuspended = false) { - global $DB, $CFG; - - // use unique prefix just in case somebody makes some SQL magic with the result - static $i = 0; - $i++; - $prefix = 'eu'.$i.'_'; - - // first find the course context - $coursecontext = $context->get_course_context(); - - $isfrontpage = ($coursecontext->instanceid == SITEID); - - if ($onlyactive && $onlysuspended) { - throw new coding_exception("Both onlyactive and onlysuspended are set, this is probably not what you want!"); - } - if ($isfrontpage && $onlysuspended) { - throw new coding_exception("onlysuspended is not supported on frontpage; please add your own early-exit!"); - } - - $joins = array(); - $wheres = array(); - $params = array(); - - list($contextids, $contextpaths) = get_context_info_list($context); - - // get all relevant capability info for all roles - if ($withcapability) { - list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx'); - $cparams['cap'] = $withcapability; - - $defs = array(); - $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path - FROM {role_capabilities} rc - JOIN {context} ctx on rc.contextid = ctx.id - WHERE rc.contextid $incontexts AND rc.capability = :cap"; - $rcs = $DB->get_records_sql($sql, $cparams); - foreach ($rcs as $rc) { - $defs[$rc->path][$rc->roleid] = $rc->permission; - } - - $access = array(); - if (!empty($defs)) { - foreach ($contextpaths as $path) { - if (empty($defs[$path])) { - continue; - } - foreach($defs[$path] as $roleid => $perm) { - if ($perm == CAP_PROHIBIT) { - $access[$roleid] = CAP_PROHIBIT; - continue; - } - if (!isset($access[$roleid])) { - $access[$roleid] = (int)$perm; - } - } - } - } - - unset($defs); - - // make lists of roles that are needed and prohibited - $needed = array(); // one of these is enough - $prohibited = array(); // must not have any of these - foreach ($access as $roleid => $perm) { - if ($perm == CAP_PROHIBIT) { - unset($needed[$roleid]); - $prohibited[$roleid] = true; - } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) { - $needed[$roleid] = true; - } - } - - $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0; - $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0; - - $nobody = false; - - if ($isfrontpage) { - if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) { - $nobody = true; - } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) { - // everybody not having prohibit has the capability - $needed = array(); - } else if (empty($needed)) { - $nobody = true; - } - } else { - if (!empty($prohibited[$defaultuserroleid])) { - $nobody = true; - } else if (!empty($needed[$defaultuserroleid])) { - // everybody not having prohibit has the capability - $needed = array(); - } else if (empty($needed)) { - $nobody = true; - } - } - - if ($nobody) { - // nobody can match so return some SQL that does not return any results - $wheres[] = "1 = 2"; - - } else { - - if ($needed) { - $ctxids = implode(',', $contextids); - $roleids = implode(',', array_keys($needed)); - $joins[] = "JOIN {role_assignments} {$prefix}ra3 ON ({$prefix}ra3.userid = {$prefix}u.id AND {$prefix}ra3.roleid IN ($roleids) AND {$prefix}ra3.contextid IN ($ctxids))"; - } - - if ($prohibited) { - $ctxids = implode(',', $contextids); - $roleids = implode(',', array_keys($prohibited)); - $joins[] = "LEFT JOIN {role_assignments} {$prefix}ra4 ON ({$prefix}ra4.userid = {$prefix}u.id AND {$prefix}ra4.roleid IN ($roleids) AND {$prefix}ra4.contextid IN ($ctxids))"; - $wheres[] = "{$prefix}ra4.id IS NULL"; - } - - if ($groupid) { - $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; - $params["{$prefix}gmid"] = $groupid; - } - } - - } else { - if ($groupid) { - $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; - $params["{$prefix}gmid"] = $groupid; - } - } - - $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid"; - $params["{$prefix}guestid"] = $CFG->siteguest; - - if ($isfrontpage) { - // all users are "enrolled" on the frontpage - } else { - $where1 = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled"; - $where2 = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)"; - $ejoin = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)"; - $params[$prefix.'courseid'] = $coursecontext->instanceid; - - if (!$onlysuspended) { - $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id"; - $joins[] = $ejoin; - if ($onlyactive) { - $wheres[] = "$where1 AND $where2"; - } - } else { - // Suspended only where there is enrolment but ALL are suspended. - // Consider multiple enrols where one is not suspended or plain role_assign. - $enrolselect = "SELECT DISTINCT {$prefix}ue.userid FROM {user_enrolments} {$prefix}ue $ejoin WHERE $where1 AND $where2"; - $joins[] = "JOIN {user_enrolments} {$prefix}ue1 ON {$prefix}ue1.userid = {$prefix}u.id"; - $joins[] = "JOIN {enrol} {$prefix}e1 ON ({$prefix}e1.id = {$prefix}ue1.enrolid AND {$prefix}e1.courseid = :{$prefix}_e1_courseid)"; - $params["{$prefix}_e1_courseid"] = $coursecontext->instanceid; - $wheres[] = "{$prefix}u.id NOT IN ($enrolselect)"; - } - - if ($onlyactive || $onlysuspended) { - $now = round(time(), -2); // rounding helps caching in DB - $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED, - $prefix.'active'=>ENROL_USER_ACTIVE, - $prefix.'now1'=>$now, $prefix.'now2'=>$now)); - } - } - - $joins = implode("\n", $joins); - $wheres = "WHERE ".implode(" AND ", $wheres); - - $sql = "SELECT DISTINCT {$prefix}u.id - FROM {user} {$prefix}u - $joins - $wheres"; - - return array($sql, $params); -} - -/** - * Returns list of users enrolled into course. - * - * @package core_enrol - * @category access - * - * @param context $context - * @param string $withcapability - * @param int $groupid 0 means ignore groups, any other value limits the result by group id - * @param string $userfields requested user record fields - * @param string $orderby - * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). - * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). - * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions - * @return array of user records - */ -function get_enrolled_users(context $context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = null, - $limitfrom = 0, $limitnum = 0, $onlyactive = false) { - global $DB; - - list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive); - $sql = "SELECT $userfields - FROM {user} u - JOIN ($esql) je ON je.id = u.id - WHERE u.deleted = 0"; - - if ($orderby) { - $sql = "$sql ORDER BY $orderby"; - } else { - list($sort, $sortparams) = users_order_by_sql('u'); - $sql = "$sql ORDER BY $sort"; - $params = array_merge($params, $sortparams); - } - - return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); -} - -/** - * Counts list of users enrolled into course (as per above function) - * - * @package core_enrol - * @category access - * - * @param context $context - * @param string $withcapability - * @param int $groupid 0 means ignore groups, any other value limits the result by group id - * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions - * @return array of user records - */ -function count_enrolled_users(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) { - global $DB; - - list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive); - $sql = "SELECT count(u.id) - FROM {user} u - JOIN ($esql) je ON je.id = u.id - WHERE u.deleted = 0"; - - return $DB->count_records_sql($sql, $params); -} - /** * Loads the capability definitions for the component (from file). * diff --git a/lib/enrollib.php b/lib/enrollib.php index f42e639b23f..0df34f563b1 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -1065,6 +1065,350 @@ function enrol_accessing_via_instance(stdClass $instance) { return $DB->record_exists('user_enrolments', array('userid'=>$USER->id, 'enrolid'=>$instance->id)); } +/** + * 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. + * + * @package core_enrol + * @category access + * + * @param context $context + * @param int|stdClass $user if null $USER is used, otherwise user object or id expected + * @param string $withcapability extra capability name + * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions + * @return bool + */ +function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) { + global $USER, $DB; + + // first find the course context + $coursecontext = $context->get_course_context(); + + // make sure there is a real user specified + if ($user === null) { + $userid = isset($USER->id) ? $USER->id : 0; + } else { + $userid = is_object($user) ? $user->id : $user; + } + + if (empty($userid)) { + // not-logged-in! + return false; + } else if (isguestuser($userid)) { + // guest account can not be enrolled anywhere + return false; + } + + if ($coursecontext->instanceid == SITEID) { + // everybody participates on frontpage + } else { + // 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()) { + if ($withcapability and !has_capability($withcapability, $context, $userid)) { + return false; + } + return true; + } + } + } + + 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' + 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 u.deleted = 0"; + $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid); + if (!$DB->record_exists_sql($sql, $params)) { + return false; + } + } + } + + if ($withcapability and !has_capability($withcapability, $context, $userid)) { + return false; + } + + return true; +} + +/** + * Returns array with sql code and parameters returning all ids + * of users enrolled into course. + * + * This function is using 'eu[0-9]+_' prefix for table names and parameters. + * + * @package core_enrol + * @category access + * + * @param context $context + * @param string $withcapability + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions + * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments + * @return array list($sql, $params) + */ +function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false, $onlysuspended = false) { + global $DB, $CFG; + + // use unique prefix just in case somebody makes some SQL magic with the result + static $i = 0; + $i++; + $prefix = 'eu'.$i.'_'; + + // first find the course context + $coursecontext = $context->get_course_context(); + + $isfrontpage = ($coursecontext->instanceid == SITEID); + + if ($onlyactive && $onlysuspended) { + throw new coding_exception("Both onlyactive and onlysuspended are set, this is probably not what you want!"); + } + if ($isfrontpage && $onlysuspended) { + throw new coding_exception("onlysuspended is not supported on frontpage; please add your own early-exit!"); + } + + $joins = array(); + $wheres = array(); + $params = array(); + + list($contextids, $contextpaths) = get_context_info_list($context); + + // get all relevant capability info for all roles + if ($withcapability) { + list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx'); + $cparams['cap'] = $withcapability; + + $defs = array(); + $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path + FROM {role_capabilities} rc + JOIN {context} ctx on rc.contextid = ctx.id + WHERE rc.contextid $incontexts AND rc.capability = :cap"; + $rcs = $DB->get_records_sql($sql, $cparams); + foreach ($rcs as $rc) { + $defs[$rc->path][$rc->roleid] = $rc->permission; + } + + $access = array(); + if (!empty($defs)) { + foreach ($contextpaths as $path) { + if (empty($defs[$path])) { + continue; + } + foreach($defs[$path] as $roleid => $perm) { + if ($perm == CAP_PROHIBIT) { + $access[$roleid] = CAP_PROHIBIT; + continue; + } + if (!isset($access[$roleid])) { + $access[$roleid] = (int)$perm; + } + } + } + } + + unset($defs); + + // make lists of roles that are needed and prohibited + $needed = array(); // one of these is enough + $prohibited = array(); // must not have any of these + foreach ($access as $roleid => $perm) { + if ($perm == CAP_PROHIBIT) { + unset($needed[$roleid]); + $prohibited[$roleid] = true; + } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) { + $needed[$roleid] = true; + } + } + + $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0; + $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0; + + $nobody = false; + + if ($isfrontpage) { + if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) { + $nobody = true; + } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) { + // everybody not having prohibit has the capability + $needed = array(); + } else if (empty($needed)) { + $nobody = true; + } + } else { + if (!empty($prohibited[$defaultuserroleid])) { + $nobody = true; + } else if (!empty($needed[$defaultuserroleid])) { + // everybody not having prohibit has the capability + $needed = array(); + } else if (empty($needed)) { + $nobody = true; + } + } + + if ($nobody) { + // nobody can match so return some SQL that does not return any results + $wheres[] = "1 = 2"; + + } else { + + if ($needed) { + $ctxids = implode(',', $contextids); + $roleids = implode(',', array_keys($needed)); + $joins[] = "JOIN {role_assignments} {$prefix}ra3 ON ({$prefix}ra3.userid = {$prefix}u.id AND {$prefix}ra3.roleid IN ($roleids) AND {$prefix}ra3.contextid IN ($ctxids))"; + } + + if ($prohibited) { + $ctxids = implode(',', $contextids); + $roleids = implode(',', array_keys($prohibited)); + $joins[] = "LEFT JOIN {role_assignments} {$prefix}ra4 ON ({$prefix}ra4.userid = {$prefix}u.id AND {$prefix}ra4.roleid IN ($roleids) AND {$prefix}ra4.contextid IN ($ctxids))"; + $wheres[] = "{$prefix}ra4.id IS NULL"; + } + + if ($groupid) { + $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; + $params["{$prefix}gmid"] = $groupid; + } + } + + } else { + if ($groupid) { + $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; + $params["{$prefix}gmid"] = $groupid; + } + } + + $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid"; + $params["{$prefix}guestid"] = $CFG->siteguest; + + if ($isfrontpage) { + // all users are "enrolled" on the frontpage + } else { + $where1 = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled"; + $where2 = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)"; + $ejoin = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)"; + $params[$prefix.'courseid'] = $coursecontext->instanceid; + + if (!$onlysuspended) { + $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id"; + $joins[] = $ejoin; + if ($onlyactive) { + $wheres[] = "$where1 AND $where2"; + } + } else { + // Suspended only where there is enrolment but ALL are suspended. + // Consider multiple enrols where one is not suspended or plain role_assign. + $enrolselect = "SELECT DISTINCT {$prefix}ue.userid FROM {user_enrolments} {$prefix}ue $ejoin WHERE $where1 AND $where2"; + $joins[] = "JOIN {user_enrolments} {$prefix}ue1 ON {$prefix}ue1.userid = {$prefix}u.id"; + $joins[] = "JOIN {enrol} {$prefix}e1 ON ({$prefix}e1.id = {$prefix}ue1.enrolid AND {$prefix}e1.courseid = :{$prefix}_e1_courseid)"; + $params["{$prefix}_e1_courseid"] = $coursecontext->instanceid; + $wheres[] = "{$prefix}u.id NOT IN ($enrolselect)"; + } + + if ($onlyactive || $onlysuspended) { + $now = round(time(), -2); // rounding helps caching in DB + $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED, + $prefix.'active'=>ENROL_USER_ACTIVE, + $prefix.'now1'=>$now, $prefix.'now2'=>$now)); + } + } + + $joins = implode("\n", $joins); + $wheres = "WHERE ".implode(" AND ", $wheres); + + $sql = "SELECT DISTINCT {$prefix}u.id + FROM {user} {$prefix}u + $joins + $wheres"; + + return array($sql, $params); +} + +/** + * Returns list of users enrolled into course. + * + * @package core_enrol + * @category access + * + * @param context $context + * @param string $withcapability + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @param string $userfields requested user record fields + * @param string $orderby + * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). + * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). + * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions + * @return array of user records + */ +function get_enrolled_users(context $context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = null, + $limitfrom = 0, $limitnum = 0, $onlyactive = false) { + global $DB; + + list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive); + $sql = "SELECT $userfields + FROM {user} u + JOIN ($esql) je ON je.id = u.id + WHERE u.deleted = 0"; + + if ($orderby) { + $sql = "$sql ORDER BY $orderby"; + } else { + list($sort, $sortparams) = users_order_by_sql('u'); + $sql = "$sql ORDER BY $sort"; + $params = array_merge($params, $sortparams); + } + + return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); +} + +/** + * Counts list of users enrolled into course (as per above function) + * + * @package core_enrol + * @category access + * + * @param context $context + * @param string $withcapability + * @param int $groupid 0 means ignore groups, any other value limits the result by group id + * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions + * @return array of user records + */ +function count_enrolled_users(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) { + global $DB; + + list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive); + $sql = "SELECT count(u.id) + FROM {user} u + JOIN ($esql) je ON je.id = u.id + WHERE u.deleted = 0"; + + return $DB->count_records_sql($sql, $params); +} /** * All enrol plugins should be based on this class, From 9121bb2d2395608dafa0788b9029e58a769cf50e Mon Sep 17 00:00:00 2001 From: John Beedell Date: Fri, 12 Aug 2016 15:13:09 +0100 Subject: [PATCH 2/3] MDL-31243 access/enrol libs: new fns returning useful SQL fragments Refactor similar SQL generation code from get_users_by_capability and get_enrolled_uses to make get_with_capability_sql. Modified files: accesslib.php, enrollib.php, grouplib.php, new tests. --- lib/accesslib.php | 165 ++++++++++++++++++++++ lib/classes/dml/sql_join.php | 69 +++++++++ lib/enrollib.php | 264 ++++++++++++++--------------------- lib/grouplib.php | 36 +++++ lib/tests/accesslib_test.php | 55 ++++++++ lib/tests/grouplib_test.php | 37 +++++ 6 files changed, 469 insertions(+), 157 deletions(-) create mode 100644 lib/classes/dml/sql_join.php diff --git a/lib/accesslib.php b/lib/accesslib.php index 1b89ad9339f..ea059c5d99e 100644 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -7277,3 +7277,168 @@ function get_suspended_userids(context $context, $usecache = false) { return $susers; } + +/** + * Gets sql for finding users with a capability in the given context + * + * @param context $context + * @param string $capability + * @return array($sql, $params) + */ +function get_with_capability_sql(context $context, $capability) { + static $i = 0; + $i++; + $prefix = 'cu' . $i . '_'; + + $capjoin = get_with_capability_join($context, $capability, $prefix . 'u.id'); + + $sql = "SELECT DISTINCT {$prefix}u.id + FROM {user} {$prefix}u + $capjoin->joins + WHERE {$prefix}u.deleted = 0 AND $capjoin->wheres"; + + return array($sql, $capjoin->params); +} + +/** + * Gets sql joins for finding users with a capability in the given context + * + * @param context $context + * @param string $capability + * @param string $useridcolumn e.g. u.id + * @return \core\dml\sql_join Contains joins, wheres, params + */ +function get_with_capability_join(context $context, $capability, $useridcolumn) { + global $DB, $CFG; + + // Use unique prefix just in case somebody makes some SQL magic with the result. + static $i = 0; + $i++; + $prefix = 'eu' . $i . '_'; + + // First find the course context. + $coursecontext = $context->get_course_context(); + + $isfrontpage = ($coursecontext->instanceid == SITEID); + + $joins = array(); + $wheres = array(); + $params = array(); + + list($contextids, $contextpaths) = get_context_info_list($context); + + list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx'); + $cparams['cap'] = $capability; + + $defs = array(); + $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path + FROM {role_capabilities} rc + JOIN {context} ctx on rc.contextid = ctx.id + WHERE rc.contextid $incontexts AND rc.capability = :cap"; + $rcs = $DB->get_records_sql($sql, $cparams); + foreach ($rcs as $rc) { + $defs[$rc->path][$rc->roleid] = $rc->permission; + } + + $access = array(); + if (!empty($defs)) { + foreach ($contextpaths as $path) { + if (empty($defs[$path])) { + continue; + } + foreach ($defs[$path] as $roleid => $perm) { + if ($perm == CAP_PROHIBIT) { + $access[$roleid] = CAP_PROHIBIT; + continue; + } + if (!isset($access[$roleid])) { + $access[$roleid] = (int) $perm; + } + } + } + } + + unset($defs); + + // Make lists of roles that are needed and prohibited. + $needed = array(); // One of these is enough. + $prohibited = array(); // Must not have any of these. + foreach ($access as $roleid => $perm) { + if ($perm == CAP_PROHIBIT) { + unset($needed[$roleid]); + $prohibited[$roleid] = true; + } else { + if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) { + $needed[$roleid] = true; + } + } + } + + $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0; + $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0; + + $nobody = false; + + if ($isfrontpage) { + if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) { + $nobody = true; + } else { + if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) { + // Everybody not having prohibit has the capability. + $needed = array(); + } else { + if (empty($needed)) { + $nobody = true; + } + } + } + } else { + if (!empty($prohibited[$defaultuserroleid])) { + $nobody = true; + } else { + if (!empty($needed[$defaultuserroleid])) { + // Everybody not having prohibit has the capability. + $needed = array(); + } else { + if (empty($needed)) { + $nobody = true; + } + } + } + } + + if ($nobody) { + // Nobody can match so return some SQL that does not return any results. + $wheres[] = "1 = 2"; + + } else { + + if ($needed) { + $ctxids = implode(',', $contextids); + $roleids = implode(',', array_keys($needed)); + $joins[] = "JOIN {role_assignments} {$prefix}ra3 + ON ({$prefix}ra3.userid = $useridcolumn + AND {$prefix}ra3.roleid IN ($roleids) + AND {$prefix}ra3.contextid IN ($ctxids))"; + } + + if ($prohibited) { + $ctxids = implode(',', $contextids); + $roleids = implode(',', array_keys($prohibited)); + $joins[] = "LEFT JOIN {role_assignments} {$prefix}ra4 + ON ({$prefix}ra4.userid = $useridcolumn + AND {$prefix}ra4.roleid IN ($roleids) + AND {$prefix}ra4.contextid IN ($ctxids))"; + $wheres[] = "{$prefix}ra4.id IS NULL"; + } + + } + + $wheres[] = "$useridcolumn <> :{$prefix}guestid"; + $params["{$prefix}guestid"] = $CFG->siteguest; + + $joins = implode("\n", $joins); + $wheres = "(" . implode(" AND ", $wheres) . ")"; + + return new \core\dml\sql_join($joins, $wheres, $params); +} diff --git a/lib/classes/dml/sql_join.php b/lib/classes/dml/sql_join.php new file mode 100644 index 00000000000..1920a3fc7cc --- /dev/null +++ b/lib/classes/dml/sql_join.php @@ -0,0 +1,69 @@ +. + +/** + * An object that contains sql join fragments. + * + * @since Moodle 3.1 + * @package core + * @category dml + * @copyright 2016 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\dml; + +defined('MOODLE_INTERNAL') || die(); + +/** + * An object that contains sql join fragments. + * + * @since Moodle 3.1 + * @package core + * @category dml + * @copyright 2016 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class sql_join { + + /** + * @var string joins. + */ + public $joins; + + /** + * @var string wheres. + */ + public $wheres; + + /** + * @var array params. + */ + public $params; + + /** + * Create an object that contains sql join fragments. + * + * @param string $joins The join sql fragment. + * @param string $wheres The where sql fragment. + * @param array $params Any parameter values. + */ + public function __construct($joins = '', $wheres = '', $params = array()) { + $this->joins = $joins; + $this->wheres = $wheres; + $this->params = $params; + } +} diff --git a/lib/enrollib.php b/lib/enrollib.php index 0df34f563b1..f35623d4d53 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -1071,9 +1071,6 @@ function enrol_accessing_via_instance(stdClass $instance) { * * Since 2.2 the result for active enrolments and current user are cached. * - * @package core_enrol - * @category access - * * @param context $context * @param int|stdClass $user if null $USER is used, otherwise user object or id expected * @param string $withcapability extra capability name @@ -1083,10 +1080,10 @@ function enrol_accessing_via_instance(stdClass $instance) { function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) { global $USER, $DB; - // first find the course context + // First find the course context. $coursecontext = $context->get_course_context(); - // make sure there is a real user specified + // Make sure there is a real user specified. if ($user === null) { $userid = isset($USER->id) ? $USER->id : 0; } else { @@ -1094,17 +1091,16 @@ function is_enrolled(context $context, $user = null, $withcapability = '', $only } if (empty($userid)) { - // not-logged-in! + // Not-logged-in! return false; } else if (isguestuser($userid)) { - // guest account can not be enrolled anywhere + // Guest account can not be enrolled anywhere. return false; } - if ($coursecontext->instanceid == SITEID) { - // everybody participates on frontpage - } else { - // try cached info first - the enrolled flag is set only when active enrolment present + // Note everybody participates on frontpage, so for other contexts... + if ($coursecontext->instanceid != SITEID) { + // 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])) { @@ -1118,7 +1114,7 @@ function is_enrolled(context $context, $user = null, $withcapability = '', $only } if ($onlyactive) { - // look for active enrolments only + // Look for active enrolments only. $until = enrol_get_enrolment_end($coursecontext->instanceid, $userid); if ($until === false) { @@ -1137,13 +1133,13 @@ function is_enrolled(context $context, $user = null, $withcapability = '', $only } } else { - // any enrolment is good for us here, even outdated, disabled or inactive + // Any enrolment is good for us here, even outdated, disabled or inactive. $sql = "SELECT 'x' 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 u.deleted = 0"; - $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid); + $params = array('userid' => $userid, 'courseid' => $coursecontext->instanceid); if (!$DB->record_exists_sql($sql, $params)) { return false; } @@ -1157,15 +1153,55 @@ function is_enrolled(context $context, $user = null, $withcapability = '', $only return true; } +/** + * Returns an array of joins, wheres and params that will limit the group of + * users to only those enrolled and with given capability (if specified). + * + * @param context $context + * @param string $prefix optional, a prefix to the user id column + * @param string $capability optional, may include a capability name + * @param int $group optional, 0 indicates no current group, otherwise the group id + * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions + * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments + * @return \core\dml\sql_join Contains joins, wheres, params + */ +function get_enrolled_with_capabilities_join(context $context, $prefix = '', $capability = '', $group = 0, + $onlyactive = false, $onlysuspended = false) { + $uid = $prefix . 'u.id'; + $joins = array(); + $wheres = array(); + + $enrolledjoin = get_enrolled_join($context, $uid, $onlyactive, $onlysuspended); + $joins[] = $enrolledjoin->joins; + $wheres[] = $enrolledjoin->wheres; + $params = $enrolledjoin->params; + + if (!empty($capability)) { + $capjoin = get_with_capability_join($context, $capability, $uid); + $joins[] = $capjoin->joins; + $wheres[] = $capjoin->wheres; + $params = array_merge($params, $capjoin->params); + } + + if ($group) { + $groupjoin = get_in_group_join($group, $uid); + $joins[] = $groupjoin->joins; + $params = array_merge($params, $groupjoin->params); + } + + $joins = implode("\n", $joins); + $wheres[] = "{$prefix}u.deleted = 0"; + $wheres = implode(" AND ", $wheres); + + return new \core\dml\sql_join($joins, $wheres, $params); +} + /** * Returns array with sql code and parameters returning all ids * of users enrolled into course. * * This function is using 'eu[0-9]+_' prefix for table names and parameters. * - * @package core_enrol - * @category access - * * @param context $context * @param string $withcapability * @param int $groupid 0 means ignore groups, any other value limits the result by group id @@ -1174,14 +1210,44 @@ function is_enrolled(context $context, $user = null, $withcapability = '', $only * @return array list($sql, $params) */ function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false, $onlysuspended = false) { - global $DB, $CFG; - // use unique prefix just in case somebody makes some SQL magic with the result + // Use unique prefix just in case somebody makes some SQL magic with the result. static $i = 0; $i++; - $prefix = 'eu'.$i.'_'; + $prefix = 'eu' . $i . '_'; - // first find the course context + $capjoin = get_enrolled_with_capabilities_join( + $context, $prefix, $withcapability, $groupid, $onlyactive, $onlysuspended); + + $sql = "SELECT DISTINCT {$prefix}u.id + FROM {user} {$prefix}u + $capjoin->joins + WHERE $capjoin->wheres"; + + return array($sql, $capjoin->params); +} + +/** + * Returns array with sql joins and parameters returning all ids + * of users enrolled into course. + * + * This function is using 'ej[0-9]+_' prefix for table names and parameters. + * + * @throws coding_exception + * + * @param context $context + * @param string $useridcolumn User id column used the calling query, e.g. u.id + * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions + * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments + * @return \core\dml\sql_join Contains joins, wheres, params + */ +function get_enrolled_join(context $context, $useridcolumn, $onlyactive = false, $onlysuspended = false) { + // Use unique prefix just in case somebody makes some SQL magic with the result. + static $i = 0; + $i++; + $prefix = 'ej' . $i . '_'; + + // First find the course context. $coursecontext = $context->get_course_context(); $isfrontpage = ($coursecontext->instanceid == SITEID); @@ -1197,125 +1263,17 @@ function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $wheres = array(); $params = array(); - list($contextids, $contextpaths) = get_context_info_list($context); + $wheres[] = "1 = 1"; // Prevent broken where clauses later on. - // get all relevant capability info for all roles - if ($withcapability) { - list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx'); - $cparams['cap'] = $withcapability; - - $defs = array(); - $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path - FROM {role_capabilities} rc - JOIN {context} ctx on rc.contextid = ctx.id - WHERE rc.contextid $incontexts AND rc.capability = :cap"; - $rcs = $DB->get_records_sql($sql, $cparams); - foreach ($rcs as $rc) { - $defs[$rc->path][$rc->roleid] = $rc->permission; - } - - $access = array(); - if (!empty($defs)) { - foreach ($contextpaths as $path) { - if (empty($defs[$path])) { - continue; - } - foreach($defs[$path] as $roleid => $perm) { - if ($perm == CAP_PROHIBIT) { - $access[$roleid] = CAP_PROHIBIT; - continue; - } - if (!isset($access[$roleid])) { - $access[$roleid] = (int)$perm; - } - } - } - } - - unset($defs); - - // make lists of roles that are needed and prohibited - $needed = array(); // one of these is enough - $prohibited = array(); // must not have any of these - foreach ($access as $roleid => $perm) { - if ($perm == CAP_PROHIBIT) { - unset($needed[$roleid]); - $prohibited[$roleid] = true; - } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) { - $needed[$roleid] = true; - } - } - - $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0; - $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0; - - $nobody = false; - - if ($isfrontpage) { - if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) { - $nobody = true; - } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) { - // everybody not having prohibit has the capability - $needed = array(); - } else if (empty($needed)) { - $nobody = true; - } - } else { - if (!empty($prohibited[$defaultuserroleid])) { - $nobody = true; - } else if (!empty($needed[$defaultuserroleid])) { - // everybody not having prohibit has the capability - $needed = array(); - } else if (empty($needed)) { - $nobody = true; - } - } - - if ($nobody) { - // nobody can match so return some SQL that does not return any results - $wheres[] = "1 = 2"; - - } else { - - if ($needed) { - $ctxids = implode(',', $contextids); - $roleids = implode(',', array_keys($needed)); - $joins[] = "JOIN {role_assignments} {$prefix}ra3 ON ({$prefix}ra3.userid = {$prefix}u.id AND {$prefix}ra3.roleid IN ($roleids) AND {$prefix}ra3.contextid IN ($ctxids))"; - } - - if ($prohibited) { - $ctxids = implode(',', $contextids); - $roleids = implode(',', array_keys($prohibited)); - $joins[] = "LEFT JOIN {role_assignments} {$prefix}ra4 ON ({$prefix}ra4.userid = {$prefix}u.id AND {$prefix}ra4.roleid IN ($roleids) AND {$prefix}ra4.contextid IN ($ctxids))"; - $wheres[] = "{$prefix}ra4.id IS NULL"; - } - - if ($groupid) { - $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; - $params["{$prefix}gmid"] = $groupid; - } - } - - } else { - if ($groupid) { - $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; - $params["{$prefix}gmid"] = $groupid; - } - } - - $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid"; - $params["{$prefix}guestid"] = $CFG->siteguest; - - if ($isfrontpage) { - // all users are "enrolled" on the frontpage - } else { + // Note all users are "enrolled" on the frontpage, but for others... + if (!$isfrontpage) { $where1 = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled"; $where2 = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)"; $ejoin = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)"; $params[$prefix.'courseid'] = $coursecontext->instanceid; if (!$onlysuspended) { - $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id"; + $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = $useridcolumn"; $joins[] = $ejoin; if ($onlyactive) { $wheres[] = "$where1 AND $where2"; @@ -1324,37 +1282,30 @@ function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, // Suspended only where there is enrolment but ALL are suspended. // Consider multiple enrols where one is not suspended or plain role_assign. $enrolselect = "SELECT DISTINCT {$prefix}ue.userid FROM {user_enrolments} {$prefix}ue $ejoin WHERE $where1 AND $where2"; - $joins[] = "JOIN {user_enrolments} {$prefix}ue1 ON {$prefix}ue1.userid = {$prefix}u.id"; - $joins[] = "JOIN {enrol} {$prefix}e1 ON ({$prefix}e1.id = {$prefix}ue1.enrolid AND {$prefix}e1.courseid = :{$prefix}_e1_courseid)"; + $joins[] = "JOIN {user_enrolments} {$prefix}ue1 ON {$prefix}ue1.userid = $useridcolumn"; + $joins[] = "JOIN {enrol} {$prefix}e1 ON ({$prefix}e1.id = {$prefix}ue1.enrolid + AND {$prefix}e1.courseid = :{$prefix}_e1_courseid)"; $params["{$prefix}_e1_courseid"] = $coursecontext->instanceid; - $wheres[] = "{$prefix}u.id NOT IN ($enrolselect)"; + $wheres[] = "$useridcolumn NOT IN ($enrolselect)"; } if ($onlyactive || $onlysuspended) { - $now = round(time(), -2); // rounding helps caching in DB - $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED, - $prefix.'active'=>ENROL_USER_ACTIVE, - $prefix.'now1'=>$now, $prefix.'now2'=>$now)); + $now = round(time(), -2); // Rounding helps caching in DB. + $params = array_merge($params, array($prefix . 'enabled' => ENROL_INSTANCE_ENABLED, + $prefix . 'active' => ENROL_USER_ACTIVE, + $prefix . 'now1' => $now, $prefix . 'now2' => $now)); } } $joins = implode("\n", $joins); - $wheres = "WHERE ".implode(" AND ", $wheres); + $wheres = implode(" AND ", $wheres); - $sql = "SELECT DISTINCT {$prefix}u.id - FROM {user} {$prefix}u - $joins - $wheres"; - - return array($sql, $params); + return new \core\dml\sql_join($joins, $wheres, $params); } /** * Returns list of users enrolled into course. * - * @package core_enrol - * @category access - * * @param context $context * @param string $withcapability * @param int $groupid 0 means ignore groups, any other value limits the result by group id @@ -1389,9 +1340,6 @@ function get_enrolled_users(context $context, $withcapability = '', $groupid = 0 /** * Counts list of users enrolled into course (as per above function) * - * @package core_enrol - * @category access - * * @param context $context * @param string $withcapability * @param int $groupid 0 means ignore groups, any other value limits the result by group id @@ -1401,13 +1349,15 @@ function get_enrolled_users(context $context, $withcapability = '', $groupid = 0 function count_enrolled_users(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) { global $DB; - list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive); + $capjoin = get_enrolled_with_capabilities_join( + $context, '', $withcapability, $groupid, $onlyactive); + $sql = "SELECT count(u.id) FROM {user} u - JOIN ($esql) je ON je.id = u.id - WHERE u.deleted = 0"; + $capjoin->joins + WHERE $capjoin->wheres AND u.deleted = 0"; - return $DB->count_records_sql($sql, $params); + return $DB->count_records_sql($sql, $capjoin->params); } /** diff --git a/lib/grouplib.php b/lib/grouplib.php index a3cd0f7672d..c23cfab0764 100644 --- a/lib/grouplib.php +++ b/lib/grouplib.php @@ -931,6 +931,42 @@ function groups_group_visible($groupid, $course, $cm = null, $userid = null) { return false; } +/** + * Get sql and parameters that will return user ids for a group + * + * @param int $groupid + * @return array($sql, $params) + */ +function get_in_group_sql($groupid) { + $groupjoin = get_in_group_join($groupid, 'u.id'); + + $sql = "SELECT DISTINCT u.id + FROM {user} u + $groupjoin->joins + WHERE u.deleted = 0"; + + return array($sql, $groupjoin->params); +} + +/** + * Get sql join to return users in a group + * + * @param int $groupid + * @param string $useridcolumn The column of the user id from the calling SQL, e.g. u.id + * @return \core\dml\sql_join Contains joins, wheres, params + */ +function get_in_group_join($groupid, $useridcolumn) { + // Use unique prefix just in case somebody makes some SQL magic with the result. + static $i = 0; + $i++; + $prefix = 'gm' . $i . '_'; + + $join = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = $useridcolumn AND {$prefix}gm.groupid = :{$prefix}gmid)"; + $param = array("{$prefix}gmid" => $groupid); + + return new \core\dml\sql_join($join, '', $param); +} + /** * Internal method, sets up $SESSION->activegroup and verifies previous value * diff --git a/lib/tests/accesslib_test.php b/lib/tests/accesslib_test.php index 5dbf8b7a383..45ac0d38580 100644 --- a/lib/tests/accesslib_test.php +++ b/lib/tests/accesslib_test.php @@ -3066,6 +3066,61 @@ class core_accesslib_testcase extends advanced_testcase { $this->assertEquals(2, count_role_users($roleid1, context_course::instance($course->id), false)); $this->assertEquals(3, count_role_users($roleid1, context_course::instance($course->id), true)); } + + /** + * Test updating of role capabilities during upgrade + * @return void + */ + public function test_get_with_capability_sql() { + global $DB; + + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $coursecontext = context_course::instance($course->id); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST); + $teacher = $this->getDataGenerator()->create_user(); + $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); + $student = $this->getDataGenerator()->create_user(); + $guest = $DB->get_record('user', array('username' => 'guest')); + + role_assign($teacherrole->id, $teacher->id, $coursecontext); + role_assign($studentrole->id, $student->id, $coursecontext); + $admin = $DB->get_record('user', array('username' => 'admin')); + + // Note: Here are used default capabilities, the full test is in permission evaluation below, + // use two capabilities that teacher has and one does not, none of them should be allowed for not-logged-in user. + $this->assertTrue($DB->record_exists('capabilities', array('name' => 'moodle/backup:backupcourse'))); + $this->assertTrue($DB->record_exists('capabilities', array('name' => 'moodle/site:approvecourse'))); + + list($sql, $params) = get_with_capability_sql($coursecontext, 'moodle/backup:backupcourse'); + $users = $DB->get_records_sql($sql, $params); + + $this->assertTrue(array_key_exists($teacher->id, $users)); + $this->assertFalse(array_key_exists($admin->id, $users)); + $this->assertFalse(array_key_exists($student->id, $users)); + $this->assertFalse(array_key_exists($guest->id, $users)); + + list($sql, $params) = get_with_capability_sql($coursecontext, 'moodle/site:approvecourse'); + $users = $DB->get_records_sql($sql, $params); + + $this->assertFalse(array_key_exists($teacher->id, $users)); + $this->assertFalse(array_key_exists($admin->id, $users)); + $this->assertFalse(array_key_exists($student->id, $users)); + $this->assertFalse(array_key_exists($guest->id, $users)); + + // Test role override. + assign_capability('moodle/site:backupcourse', CAP_PROHIBIT, $teacherrole->id, $coursecontext, true); + assign_capability('moodle/site:backupcourse', CAP_ALLOW, $studentrole->id, $coursecontext, true); + + list($sql, $params) = get_with_capability_sql($coursecontext, 'moodle/site:backupcourse'); + $users = $DB->get_records_sql($sql, $params); + + $this->assertFalse(array_key_exists($teacher->id, $users)); + $this->assertFalse(array_key_exists($admin->id, $users)); + $this->assertTrue(array_key_exists($student->id, $users)); + $this->assertFalse(array_key_exists($guest->id, $users)); + } } /** diff --git a/lib/tests/grouplib_test.php b/lib/tests/grouplib_test.php index 2ff199ca849..9f400829512 100644 --- a/lib/tests/grouplib_test.php +++ b/lib/tests/grouplib_test.php @@ -176,6 +176,43 @@ class core_grouplib_testcase extends advanced_testcase { $this->assertEquals($grouping, groups_get_grouping_by_idnumber($course->id, $idnumber2)); } + + public function test_groups_get_in_group_sql() { + global $DB; + + $this->resetAfterTest(true); + + $generator = $this->getDataGenerator(); + + // Create a course category and course. + $course = $generator->create_course(); + $student = $generator->create_user(); + $plugin = enrol_get_plugin('manual'); + $role = $DB->get_record('role', array('shortname' => 'student')); + $group = $generator->create_group(array('courseid' => $course->id)); + $instance = $DB->get_record('enrol', array( + 'courseid' => $course->id, + 'enrol' => 'manual', + )); + + $this->assertNotEquals($instance, false); + + // Enrol the user in the course. + $plugin->enrol_user($instance, $student->id, $role->id); + + list($sql, $params) = get_in_group_sql($group->id, true); + + // Test an empty group. + $users = $DB->get_records_sql($sql, $params); + + $this->assertFalse(array_key_exists($student->id, $users)); + groups_add_member($group->id, $student->id); + + // Test with a group member. + $users = $DB->get_records_sql($sql, $params); + $this->assertTrue(array_key_exists($student->id, $users)); + } + public function test_groups_get_group_by_name() { $this->resetAfterTest(true); From 905953909f1b7c5352d8004d7d03ced82dcadbbb Mon Sep 17 00:00:00 2001 From: John Beedell Date: Tue, 27 Sep 2016 16:18:24 +0100 Subject: [PATCH 3/3] MDL-31243 access/enrol libs: rename new groups functions --- lib/enrollib.php | 2 +- lib/grouplib.php | 10 +++++----- lib/tests/grouplib_test.php | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/enrollib.php b/lib/enrollib.php index f35623d4d53..5c326da91e9 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -1184,7 +1184,7 @@ function get_enrolled_with_capabilities_join(context $context, $prefix = '', $ca } if ($group) { - $groupjoin = get_in_group_join($group, $uid); + $groupjoin = groups_get_members_join($group, $uid); $joins[] = $groupjoin->joins; $params = array_merge($params, $groupjoin->params); } diff --git a/lib/grouplib.php b/lib/grouplib.php index c23cfab0764..9b57e252ce4 100644 --- a/lib/grouplib.php +++ b/lib/grouplib.php @@ -937,13 +937,13 @@ function groups_group_visible($groupid, $course, $cm = null, $userid = null) { * @param int $groupid * @return array($sql, $params) */ -function get_in_group_sql($groupid) { - $groupjoin = get_in_group_join($groupid, 'u.id'); +function groups_get_members_ids_sql($groupid) { + $groupjoin = groups_get_members_join($groupid, 'u.id'); $sql = "SELECT DISTINCT u.id - FROM {user} u + FROM {user} u $groupjoin->joins - WHERE u.deleted = 0"; + WHERE u.deleted = 0"; return array($sql, $groupjoin->params); } @@ -955,7 +955,7 @@ function get_in_group_sql($groupid) { * @param string $useridcolumn The column of the user id from the calling SQL, e.g. u.id * @return \core\dml\sql_join Contains joins, wheres, params */ -function get_in_group_join($groupid, $useridcolumn) { +function groups_get_members_join($groupid, $useridcolumn) { // Use unique prefix just in case somebody makes some SQL magic with the result. static $i = 0; $i++; diff --git a/lib/tests/grouplib_test.php b/lib/tests/grouplib_test.php index 9f400829512..e1b4618f3a0 100644 --- a/lib/tests/grouplib_test.php +++ b/lib/tests/grouplib_test.php @@ -177,14 +177,13 @@ class core_grouplib_testcase extends advanced_testcase { } - public function test_groups_get_in_group_sql() { + public function test_groups_get_members_ids_sql() { global $DB; $this->resetAfterTest(true); $generator = $this->getDataGenerator(); - // Create a course category and course. $course = $generator->create_course(); $student = $generator->create_user(); $plugin = enrol_get_plugin('manual'); @@ -200,7 +199,7 @@ class core_grouplib_testcase extends advanced_testcase { // Enrol the user in the course. $plugin->enrol_user($instance, $student->id, $role->id); - list($sql, $params) = get_in_group_sql($group->id, true); + list($sql, $params) = groups_get_members_ids_sql($group->id, true); // Test an empty group. $users = $DB->get_records_sql($sql, $params);