mirror of
https://github.com/moodle/moodle.git
synced 2025-08-07 01:46:45 +02:00
Merge branch 'MDL-72099-master' of https://github.com/aanabit/moodle
This commit is contained in:
commit
e95aee50cc
13 changed files with 511 additions and 86 deletions
|
@ -4101,6 +4101,116 @@ function count_role_users($roleid, context $context, $parent = false) {
|
|||
return $DB->count_records_sql($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function gets the list of course and course category contexts that this user has a particular capability in.
|
||||
*
|
||||
* It is now reasonably efficient, but bear in mind that if there are users who have the capability
|
||||
* everywhere, it may return an array of all contexts.
|
||||
*
|
||||
* @param string $capability Capability in question
|
||||
* @param int $userid User ID or null for current user
|
||||
* @param bool $getcategories Wether to return also course_categories
|
||||
* @param bool $doanything True if 'doanything' is permitted (default)
|
||||
* @param string $coursefieldsexceptid Leave blank if you only need 'id' in the course records;
|
||||
* otherwise use a comma-separated list of the fields you require, not including id.
|
||||
* Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
|
||||
* @param string $categoryfieldsexceptid Leave blank if you only need 'id' in the course records;
|
||||
* otherwise use a comma-separated list of the fields you require, not including id.
|
||||
* Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
|
||||
* @param string $courseorderby If set, use a comma-separated list of fields from course
|
||||
* table with sql modifiers (DESC) if needed
|
||||
* @param string $categoryorderby If set, use a comma-separated list of fields from course_category
|
||||
* table with sql modifiers (DESC) if needed
|
||||
* @param int $limit Limit the number of courses to return on success. Zero equals all entries.
|
||||
* @return array Array of categories and courses.
|
||||
*/
|
||||
function get_user_capability_contexts(string $capability, bool $getcategories, $userid = null, $doanything = true,
|
||||
$coursefieldsexceptid = '', $categoryfieldsexceptid = '', $courseorderby = '',
|
||||
$categoryorderby = '', $limit = 0): array {
|
||||
global $DB, $USER;
|
||||
|
||||
// Default to current user.
|
||||
if (!$userid) {
|
||||
$userid = $USER->id;
|
||||
}
|
||||
|
||||
if ($doanything && is_siteadmin($userid)) {
|
||||
// If the user is a site admin and $doanything is enabled then there is no need to restrict
|
||||
// the list of courses.
|
||||
$contextlimitsql = '';
|
||||
$contextlimitparams = [];
|
||||
} else {
|
||||
// Gets SQL to limit contexts ('x' table) to those where the user has this capability.
|
||||
list ($contextlimitsql, $contextlimitparams) = \core\access\get_user_capability_course_helper::get_sql(
|
||||
$userid, $capability);
|
||||
if (!$contextlimitsql) {
|
||||
// If the does not have this capability in any context, return false without querying.
|
||||
return [false, false];
|
||||
}
|
||||
|
||||
$contextlimitsql = 'WHERE' . $contextlimitsql;
|
||||
}
|
||||
|
||||
$categories = [];
|
||||
if ($getcategories) {
|
||||
$fieldlist = \core\access\get_user_capability_course_helper::map_fieldnames($categoryfieldsexceptid);
|
||||
if ($categoryorderby) {
|
||||
$fields = explode(',', $categoryorderby);
|
||||
$orderby = '';
|
||||
foreach ($fields as $field) {
|
||||
if ($orderby) {
|
||||
$orderby .= ',';
|
||||
}
|
||||
$orderby .= 'c.'.$field;
|
||||
}
|
||||
$orderby = 'ORDER BY '.$orderby;
|
||||
}
|
||||
$rs = $DB->get_recordset_sql("
|
||||
SELECT c.id $fieldlist
|
||||
FROM {course_categories} c
|
||||
JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
|
||||
$contextlimitsql
|
||||
$orderby", array_merge([CONTEXT_COURSECAT], $contextlimitparams));
|
||||
$basedlimit = $limit;
|
||||
foreach ($rs as $category) {
|
||||
$categories[] = $category;
|
||||
$basedlimit--;
|
||||
if ($basedlimit == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$courses = [];
|
||||
$fieldlist = \core\access\get_user_capability_course_helper::map_fieldnames($coursefieldsexceptid);
|
||||
if ($courseorderby) {
|
||||
$fields = explode(',', $courseorderby);
|
||||
$courseorderby = '';
|
||||
foreach ($fields as $field) {
|
||||
if ($courseorderby) {
|
||||
$courseorderby .= ',';
|
||||
}
|
||||
$courseorderby .= 'c.'.$field;
|
||||
}
|
||||
$courseorderby = 'ORDER BY '.$courseorderby;
|
||||
}
|
||||
$rs = $DB->get_recordset_sql("
|
||||
SELECT c.id $fieldlist
|
||||
FROM {course} c
|
||||
JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
|
||||
$contextlimitsql
|
||||
$courseorderby", array_merge([CONTEXT_COURSE], $contextlimitparams));
|
||||
foreach ($rs as $course) {
|
||||
$courses[] = $course;
|
||||
$limit--;
|
||||
if ($limit == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$rs->close();
|
||||
return [$categories, $courses];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function gets the list of courses that this user has a particular capability in.
|
||||
*
|
||||
|
@ -4118,84 +4228,20 @@ function count_role_users($roleid, context $context, $parent = false) {
|
|||
* @param int $limit Limit the number of courses to return on success. Zero equals all entries.
|
||||
* @return array|bool Array of courses, if none found false is returned.
|
||||
*/
|
||||
function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '', $orderby = '',
|
||||
$limit = 0) {
|
||||
global $DB, $USER;
|
||||
|
||||
// Default to current user.
|
||||
if (!$userid) {
|
||||
$userid = $USER->id;
|
||||
}
|
||||
|
||||
if ($doanything && is_siteadmin($userid)) {
|
||||
// If the user is a site admin and $doanything is enabled then there is no need to restrict
|
||||
// the list of courses.
|
||||
$contextlimitsql = '';
|
||||
$contextlimitparams = [];
|
||||
} else {
|
||||
// Gets SQL to limit contexts ('x' table) to those where the user has this capability.
|
||||
list ($contextlimitsql, $contextlimitparams) = \core\access\get_user_capability_course_helper::get_sql(
|
||||
$userid, $capability);
|
||||
if (!$contextlimitsql) {
|
||||
// If the does not have this capability in any context, return false without querying.
|
||||
return false;
|
||||
}
|
||||
|
||||
$contextlimitsql = 'WHERE' . $contextlimitsql;
|
||||
}
|
||||
|
||||
// Convert fields list and ordering
|
||||
$fieldlist = '';
|
||||
if ($fieldsexceptid) {
|
||||
$fields = array_map('trim', explode(',', $fieldsexceptid));
|
||||
foreach ($fields as $field) {
|
||||
// Context fields have a different alias.
|
||||
if (strpos($field, 'ctx') === 0) {
|
||||
switch($field) {
|
||||
case 'ctxlevel' :
|
||||
$realfield = 'contextlevel';
|
||||
break;
|
||||
case 'ctxinstance' :
|
||||
$realfield = 'instanceid';
|
||||
break;
|
||||
default:
|
||||
$realfield = substr($field, 3);
|
||||
break;
|
||||
}
|
||||
$fieldlist .= ',x.' . $realfield . ' AS ' . $field;
|
||||
} else {
|
||||
$fieldlist .= ',c.'.$field;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($orderby) {
|
||||
$fields = explode(',', $orderby);
|
||||
$orderby = '';
|
||||
foreach ($fields as $field) {
|
||||
if ($orderby) {
|
||||
$orderby .= ',';
|
||||
}
|
||||
$orderby .= 'c.'.$field;
|
||||
}
|
||||
$orderby = 'ORDER BY '.$orderby;
|
||||
}
|
||||
|
||||
$courses = array();
|
||||
$rs = $DB->get_recordset_sql("
|
||||
SELECT c.id $fieldlist
|
||||
FROM {course} c
|
||||
JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
|
||||
$contextlimitsql
|
||||
$orderby", array_merge([CONTEXT_COURSE], $contextlimitparams));
|
||||
foreach ($rs as $course) {
|
||||
$courses[] = $course;
|
||||
$limit--;
|
||||
if ($limit == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$rs->close();
|
||||
return empty($courses) ? false : $courses;
|
||||
function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '',
|
||||
$orderby = '', $limit = 0) {
|
||||
list($categories, $courses) = get_user_capability_contexts(
|
||||
$capability,
|
||||
false,
|
||||
$userid,
|
||||
$doanything,
|
||||
$fieldsexceptid,
|
||||
'',
|
||||
$orderby,
|
||||
'',
|
||||
$limit
|
||||
);
|
||||
return $courses;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -429,4 +429,39 @@ class get_user_capability_course_helper {
|
|||
return self::create_sql($root);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Map fieldnames to get ready for the SQL query.
|
||||
*
|
||||
* @param string $fieldsexceptid A comma-separated list of the fields you require, not including id.
|
||||
* Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
|
||||
* @return string Mapped field list for the SQL query.
|
||||
*/
|
||||
public static function map_fieldnames(string $fieldsexceptid = ''): string {
|
||||
// Convert fields list and ordering.
|
||||
$fieldlist = '';
|
||||
if ($fieldsexceptid) {
|
||||
$fields = array_map('trim', explode(',', $fieldsexceptid));
|
||||
foreach ($fields as $field) {
|
||||
// Context fields have a different alias.
|
||||
if (strpos($field, 'ctx') === 0) {
|
||||
switch($field) {
|
||||
case 'ctxlevel' :
|
||||
$realfield = 'contextlevel';
|
||||
break;
|
||||
case 'ctxinstance' :
|
||||
$realfield = 'instanceid';
|
||||
break;
|
||||
default:
|
||||
$realfield = substr($field, 3);
|
||||
break;
|
||||
}
|
||||
$fieldlist .= ',x.' . $realfield . ' AS ' . $field;
|
||||
} else {
|
||||
$fieldlist .= ',c.'.$field;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $fieldlist;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -493,4 +493,27 @@ $definitions = array(
|
|||
'staticacceleration' => true,
|
||||
'datasource' => '\core_course\cache\course_image',
|
||||
],
|
||||
|
||||
// Cache the course categories where the user has access the content bank.
|
||||
'contentbank_allowed_categories' => [
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'simplekeys' => true,
|
||||
'simpledata' => true,
|
||||
'invalidationevents' => [
|
||||
'changesincoursecat',
|
||||
'changesincategoryenrolment',
|
||||
],
|
||||
],
|
||||
|
||||
// Cache the courses where the user has access the content bank.
|
||||
'contentbank_allowed_courses' => [
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'simplekeys' => true,
|
||||
'simpledata' => true,
|
||||
'invalidationevents' => [
|
||||
'changesincoursecat',
|
||||
'changesincategoryenrolment',
|
||||
'changesincourse',
|
||||
],
|
||||
],
|
||||
);
|
||||
|
|
|
@ -2230,6 +2230,89 @@ class core_accesslib_testcase extends advanced_testcase {
|
|||
$this->assert_course_ids([SITEID, $c1->id, $c2->id], $courses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests get_user_capability_contexts() which checks a capability across all courses and categories.
|
||||
* Testing for categories only because courses results are covered by test_get_user_capability_course.
|
||||
*/
|
||||
public function test_get_user_capability_contexts() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$cap = 'moodle/contentbank:access';
|
||||
$defaultcategoryid = 1;
|
||||
|
||||
// The structure being created here is this:
|
||||
//
|
||||
// All tests work with the single capability 'moodle/contentbank:access'.
|
||||
// ROLE DEF/OVERRIDE .
|
||||
// Role: Allow Prohibit Empty .
|
||||
// System ALLOW PROHIBIT .
|
||||
// cat1 PREVENT ALLOW ALLOW .
|
||||
// cat3 ALLOW PROHIBIT .
|
||||
// cat2 PROHIBIT PROHIBIT PROHIBIT .
|
||||
|
||||
// Create a role which allows contentbank:access and one that prohibits it, and one neither.
|
||||
$allowroleid = $generator->create_role();
|
||||
$prohibitroleid = $generator->create_role();
|
||||
$emptyroleid = $generator->create_role();
|
||||
$systemcontext = context_system::instance();
|
||||
assign_capability($cap, CAP_ALLOW, $allowroleid, $systemcontext->id);
|
||||
assign_capability($cap, CAP_PROHIBIT, $prohibitroleid, $systemcontext->id);
|
||||
|
||||
// Create three categories (two of them nested).
|
||||
$cat1 = $generator->create_category();
|
||||
$cat2 = $generator->create_category();
|
||||
$cat3 = $generator->create_category(['parent' => $cat1->id]);
|
||||
|
||||
// Category overrides: in cat 1, empty role is allowed; in cat 2, empty role is prevented.
|
||||
assign_capability($cap, CAP_ALLOW, $emptyroleid,
|
||||
context_coursecat::instance($cat1->id)->id);
|
||||
assign_capability($cap, CAP_PREVENT, $emptyroleid,
|
||||
context_coursecat::instance($cat2->id)->id);
|
||||
|
||||
// Course category overrides: in cat1, allow role is prevented and prohibit role is allowed;
|
||||
// in Cat2, allow role is prohibited.
|
||||
assign_capability($cap, CAP_PREVENT, $allowroleid,
|
||||
context_coursecat::instance($cat1->id)->id);
|
||||
assign_capability($cap, CAP_ALLOW, $prohibitroleid,
|
||||
context_coursecat::instance($cat1->id)->id);
|
||||
assign_capability($cap, CAP_PROHIBIT, $allowroleid,
|
||||
context_coursecat::instance($cat2->id)->id);
|
||||
|
||||
// User 1 has no roles except default user role.
|
||||
$u1 = $generator->create_user();
|
||||
|
||||
// It returns false (annoyingly) if there are no course categories.
|
||||
list($categories, $courses) = get_user_capability_contexts($cap, true, $u1->id, true, '', '', '', 'id');
|
||||
$this->assertFalse($categories);
|
||||
|
||||
// User 2 has allow role (system wide).
|
||||
$u2 = $generator->create_user();
|
||||
role_assign($allowroleid, $u2->id, $systemcontext->id);
|
||||
|
||||
// Should get $defaultcategory only. cat2 is prohibited; cat1 is prevented, so cat3 is not allowed.
|
||||
list($categories, $courses) = get_user_capability_contexts($cap, true, $u2->id, true, '', '', '', 'id');
|
||||
// Using same assert_course_ids helper even when we are checking course category ids.
|
||||
$this->assert_course_ids([$defaultcategoryid], $categories);
|
||||
|
||||
// User 3 has empty role (system wide).
|
||||
$u3 = $generator->create_user();
|
||||
role_assign($emptyroleid, $u3->id, $systemcontext->id);
|
||||
|
||||
// Should get cat1 and cat3. cat2 is prohibited; no access to system level.
|
||||
list($categories, $courses) = get_user_capability_contexts($cap, true, $u3->id, true, '', '', '', 'id');
|
||||
$this->assert_course_ids([$cat1->id, $cat3->id], $categories);
|
||||
|
||||
// User 4 has prohibit role (system wide).
|
||||
$u4 = $generator->create_user();
|
||||
role_assign($prohibitroleid, $u4->id, $systemcontext->id);
|
||||
|
||||
// Should not get any, because all of them are prohibited at system level.
|
||||
// Even if we try to allow an specific category.
|
||||
list($categories, $courses) = get_user_capability_contexts($cap, true, $u4->id, true, '', '', '', 'id');
|
||||
$this->assertFalse($categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an array of course ids to make the above test script shorter.
|
||||
*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue