MDL-44725 Availability: Add SQL feature for user lists (11)

The previous API included a facility to filter a list of users
to include only those who are allowed to access an activity, i.e.
only people who belong to the required groups etc.

This change adds a new API function to return SQL that obtains
this list of users, so that it can be combined with other
queries.
This commit is contained in:
sam marshall 2014-08-05 17:30:09 +01:00
parent c13ac85db8
commit 1a7049af22
13 changed files with 549 additions and 20 deletions

View File

@ -590,6 +590,36 @@ abstract class info {
return $result;
}
/**
* Obtains SQL that returns a list of enrolled users that has been filtered
* by the conditions applied in the availability API, similar to calling
* get_enrolled_users and then filter_user_list. As for filter_user_list,
* this ONLY filteres out users with conditions that are marked as applying
* to user lists. For example, group conditions are included but date
* conditions are not included.
*
* The returned SQL is a query that returns a list of user IDs. It does not
* include brackets, so you neeed to add these to make it into a subquery.
* You would normally use it in an SQL phrase like "WHERE u.id IN ($sql)".
*
* The function returns an array with '' and an empty array, if there are
* no restrictions on users from these conditions.
*
* The SQL will be complex and may be slow. It uses named parameters (sorry,
* I know they are annoying, but it was unavoidable here).
*
* @param bool $onlyactive True if including only active enrolments
* @return array Array of SQL code (may be empty) and params
*/
public function get_user_list_sql($onlyactive) {
global $CFG;
if (is_null($this->availability) || !$CFG->enableavailability) {
return array('', array());
}
$tree = $this->get_availability_tree();
return $tree->get_user_list_sql(false, $this, $onlyactive);
}
/**
* Formats the $cm->availableinfo string for display. This includes
* filling in the names of any course-modules that might be mentioned.

View File

@ -109,6 +109,30 @@ class info_module extends info {
return parent::filter_user_list($filtered);
}
public function get_user_list_sql($onlyactive = true) {
global $CFG, $DB;
if (!$CFG->enableavailability) {
return array('', array());
}
// Get query for section (if any) and module.
$section = $this->cm->get_modinfo()->get_section_info(
$this->cm->sectionnum, MUST_EXIST);
$sectioninfo = new info_section($section);
$sectionresult = $sectioninfo->get_user_list_sql($onlyactive);
$moduleresult = parent::get_user_list_sql($onlyactive);
if (!$sectionresult[0]) {
return $moduleresult;
}
if (!$moduleresult[0]) {
return $sectionresult;
}
return array('(' . $sectionresult[0] . ') INTERSECT (' . $moduleresult[0] . ')',
array_merge($sectionresult[1], $moduleresult[1]));
}
/**
* Checks if an activity is visible to the given user.
*

View File

@ -350,6 +350,52 @@ class tree extends tree_node {
}
}
public function get_user_list_sql($not, info $info, $onlyactive) {
// Get logic flags from operator.
list($innernot, $andoperator) = $this->get_logic_flags($not);
// Loop through all valid children, getting SQL for each.
$childresults = array();
foreach ($this->children as $index => $child) {
if (!$child->is_applied_to_user_lists()) {
continue;
}
$childresult = $child->get_user_list_sql($innernot, $info, $onlyactive);
if ($childresult[0]) {
$childresults[] = $childresult;
} else if (!$andoperator) {
// When using OR operator, if any part doesn't have restrictions,
// then nor does the whole thing.
return array('', array());
}
}
// If there are no conditions, return null.
if (!$childresults) {
return array('', array());
}
// If there is a single condition, return it.
if (count($childresults) === 1) {
return $childresults[0];
}
// Combine results using INTERSECT or UNION.
$outsql = null;
$outparams = null;
foreach ($childresults as $childresult) {
if (!$outsql) {
$outsql = '(' . $childresult[0] . ')';
$outparams = $childresult[1];
} else {
$outsql .= $andoperator ? ' INTERSECT (' : ' UNION (';
$outsql .= $childresult[0];
$outsql .= ')';
$outparams = array_merge($outparams, $childresult[1]);
}
}
return array($outsql, $outparams);
}
public function is_available_for_all($not = false) {
// Get logic flags.
list($innernot, $andoperator) = $this->get_logic_flags($not);

View File

@ -159,4 +159,60 @@ abstract class tree_node {
throw new \coding_exception('Not implemented (do not call unless '.
'is_applied_to_user_lists is true)');
}
/**
* Obtains SQL that returns a list of enrolled users that has been filtered
* by the conditions applied in the availability API, similar to calling
* get_enrolled_users and then filter_user_list. As for filter_user_list,
* this ONLY filteres out users with conditions that are marked as applying
* to user lists. For example, group conditions are included but date
* conditions are not included.
*
* The returned SQL is a query that returns a list of user IDs. It does not
* include brackets, so you neeed to add these to make it into a subquery.
* You would normally use it in an SQL phrase like "WHERE u.id IN ($sql)".
*
* The SQL will be complex and may be slow. It uses named parameters (sorry,
* I know they are annoying, but it was unavoidable here).
*
* If there are no conditions, the returned result is array('', array()).
*
* @param bool $not True if this condition is applying in negative mode
* @param \core_availability\info $info Item we're checking
* @param bool $onlyactive If true, only returns active enrolments
* @return array Array with two elements: SQL subquery and parameters array
* @throws \coding_exception If called on a condition that doesn't apply to user lists
*/
public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
if (!$this->is_applied_to_user_lists()) {
throw new \coding_exception('Not implemented (do not call unless '.
'is_applied_to_user_lists is true)');
}
// Handle situation where plugin does not implement this, by returning a
// default (all enrolled users). This ensures compatibility with 2.7
// plugins and behaviour. Plugins should be updated to support this
// new function (if they return true to is_applied_to_user_lists).
debugging('Availability plugins that return true to is_applied_to_user_lists ' .
'should also now implement get_user_list_sql: ' . get_class($this),
DEBUG_DEVELOPER);
return get_enrolled_sql($info->get_context(), '', 0, $onlyactive);
}
/**
* Utility function for generating SQL parameters (because we can't use ?
* parameters because get_enrolled_sql has infected us with horrible named
* parameters).
*
* @param array $params Params array (value will be added to this array)
* @param string|int $value Value
* @return SQL code for the parameter, e.g. ':pr1234'
*/
protected static function unique_sql_parameter(array &$params, $value) {
static $count = 1;
$unique = 'usp' . $count;
$params[$unique] = $value;
$count++;
return ':' . $unique;
}
}

View File

@ -225,4 +225,40 @@ class condition extends \core_availability\condition {
public static function get_json($groupid = 0) {
return (object)array('type' => 'group', 'id' => (int)$groupid);
}
public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
global $DB;
// Get enrolled users with access all groups. These always are allowed.
list($aagsql, $aagparams) = get_enrolled_sql(
$info->get_context(), 'moodle/site:accessallgroups', 0, $onlyactive);
// Get all enrolled users.
list ($enrolsql, $enrolparams) =
get_enrolled_sql($info->get_context(), '', 0, $onlyactive);
// Condition for specified or any group.
$matchparams = array();
if ($this->groupid) {
$matchsql = "SELECT 1
FROM {groups_members} gm
WHERE gm.userid = userids.id
AND gm.groupid = " .
self::unique_sql_parameter($matchparams, $this->groupid);
} else {
$matchsql = "SELECT 1
FROM {groups_members} gm
JOIN {groups} g ON g.id = gm.groupid
WHERE gm.userid = userids.id
AND g.courseid = " .
self::unique_sql_parameter($matchparams, $info->get_course()->id);
}
// Overall query combines all this.
$condition = $not ? 'NOT' : '';
$sql = "SELECT userids.id
FROM ($enrolsql) userids
WHERE (userids.id IN ($aagsql)) OR $condition EXISTS ($matchsql)";
return array($sql, array_merge($enrolparams, $aagparams, $matchparams));
}
}

View File

@ -164,7 +164,8 @@ class availability_group_condition_testcase extends advanced_testcase {
}
/**
* Tests the filter_users (bulk checking) function.
* Tests the filter_users (bulk checking) function. Also tests the SQL
* variant get_user_list_sql.
*/
public function test_filter_users() {
global $DB;
@ -205,21 +206,48 @@ class availability_group_condition_testcase extends advanced_testcase {
$cond = new condition((object)array());
$result = array_keys($cond->filter_user_list($allusers, false, $info, $checker));
ksort($result);
$this->assertEquals(array($teacher->id, $students[1]->id, $students[2]->id), $result);
$expected = array($teacher->id, $students[1]->id, $students[2]->id);
$this->assertEquals($expected, $result);
// Test it with get_user_list_sql.
list ($sql, $params) = $cond->get_user_list_sql(false, $info, true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
// Test NOT version (note that teacher can still access because AAG works
// both ways).
$result = array_keys($cond->filter_user_list($allusers, true, $info, $checker));
ksort($result);
$this->assertEquals(array($teacher->id, $students[0]->id), $result);
$expected = array($teacher->id, $students[0]->id);
$this->assertEquals($expected, $result);
// Test with get_user_list_sql.
list ($sql, $params) = $cond->get_user_list_sql(true, $info, true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
// Test specific group.
$cond = new condition((object)array('id' => (int)$group1->id));
$result = array_keys($cond->filter_user_list($allusers, false, $info, $checker));
ksort($result);
$this->assertEquals(array($teacher->id, $students[1]->id), $result);
$expected = array($teacher->id, $students[1]->id);
$this->assertEquals($expected, $result);
list ($sql, $params) = $cond->get_user_list_sql(false, $info, true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
$result = array_keys($cond->filter_user_list($allusers, true, $info, $checker));
ksort($result);
$this->assertEquals(array($teacher->id, $students[0]->id, $students[2]->id), $result);
$expected = array($teacher->id, $students[0]->id, $students[2]->id);
$this->assertEquals($expected, $result);
list ($sql, $params) = $cond->get_user_list_sql(true, $info, true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
}
}

View File

@ -258,4 +258,32 @@ class condition extends \core_availability\condition {
}
return $result;
}
public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
global $DB;
// Get enrolled users with access all groups. These always are allowed.
list($aagsql, $aagparams) = get_enrolled_sql(
$info->get_context(), 'moodle/site:accessallgroups', 0, $onlyactive);
// Get all enrolled users.
list ($enrolsql, $enrolparams) =
get_enrolled_sql($info->get_context(), '', 0, $onlyactive);
// Condition for specified or any group.
$matchparams = array();
$matchsql = "SELECT 1
FROM {groups_members} gm
JOIN {groupings_groups} gg ON gg.groupid = gm.groupid
WHERE gm.userid = userids.id
AND gg.groupingid = " .
self::unique_sql_parameter($matchparams, $this->get_grouping_id($info));
// Overall query combines all this.
$condition = $not ? 'NOT' : '';
$sql = "SELECT userids.id
FROM ($enrolsql) userids
WHERE (userids.id IN ($aagsql)) OR $condition EXISTS ($matchsql)";
return array($sql, array_merge($enrolparams, $aagparams, $matchparams));
}
}

View File

@ -206,7 +206,8 @@ class availability_grouping_condition_testcase extends advanced_testcase {
}
/**
* Tests the filter_users (bulk checking) function.
* Tests the filter_users (bulk checking) function. Also tests the SQL
* variant get_user_list_sql.
*/
public function test_filter_users() {
global $DB, $CFG;
@ -258,16 +259,39 @@ class availability_grouping_condition_testcase extends advanced_testcase {
$cond = new condition((object)array('id' => (int)$grouping1->id));
$result = array_keys($cond->filter_user_list($allusers, false, $info, $checker));
ksort($result);
$this->assertEquals(array($teacher->id, $students[1]->id), $result);
$expected = array($teacher->id, $students[1]->id);
$this->assertEquals($expected, $result);
// Test it with get_user_list_sql.
list ($sql, $params) = $cond->get_user_list_sql(false, $info, true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
// NOT test.
$result = array_keys($cond->filter_user_list($allusers, true, $info, $checker));
ksort($result);
$this->assertEquals(array($teacher->id, $students[0]->id, $students[2]->id), $result);
$expected = array($teacher->id, $students[0]->id, $students[2]->id);
$this->assertEquals($expected, $result);
// NOT with get_user_list_sql.
list ($sql, $params) = $cond->get_user_list_sql(true, $info, true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
// Test course-module grouping.
$modinfo = get_fast_modinfo($course);
$cm = $modinfo->get_cm($page->cmid);
$info = new \core_availability\info_module($cm);
$result = array_keys($info->filter_user_list($allusers, $course));
$this->assertEquals(array($teacher->id, $students[2]->id), $result);
$expected = array($teacher->id, $students[2]->id);
$this->assertEquals($expected, $result);
// With get_user_list_sql.
list ($sql, $params) = $info->get_user_list_sql(true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
}
}

View File

@ -461,4 +461,104 @@ class condition extends \core_availability\condition {
}
return $result;
}
/**
* Gets SQL to match a field against this condition. The second copy of the
* field is in case you're using variables for the field so that it needs
* to be two different ones.
*
* @param string $field Field name
* @param string $field2 Second copy of field name (default same).
* @return array Array of SQL and parameters
*/
private function get_condition_sql($field, $field2 = null) {
global $DB;
if (is_null($field2)) {
$field2 = $field;
}
$params = array();
switch($this->operator) {
case self::OP_CONTAINS:
$sql = $DB->sql_like($field, self::unique_sql_parameter(
$params, '%' . $this->value . '%'));
break;
case self::OP_DOES_NOT_CONTAIN:
if (empty($this->value)) {
// The 'does not contain nothing' expression matches everyone.
return null;
}
$sql = $DB->sql_like($field, self::unique_sql_parameter(
$params, '%' . $this->value . '%'), true, true, true);
break;
case self::OP_IS_EQUAL_TO:
$sql = $field . ' = ' . self::unique_sql_parameter(
$params, $this->value);
break;
case self::OP_STARTS_WITH:
$sql = $DB->sql_like($field, self::unique_sql_parameter(
$params, $this->value . '%'));
break;
case self::OP_ENDS_WITH:
$sql = $DB->sql_like($field, self::unique_sql_parameter(
$params, '%' . $this->value));
break;
case self::OP_IS_EMPTY:
// Mimic PHP empty() behaviour for strings, '0' or ''.
$sql = '(' . $field . " IN ('0', '') OR $field2 IS NULL)";
break;
case self::OP_IS_NOT_EMPTY:
$sql = '(' . $field . " NOT IN ('0', '') AND $field2 IS NOT NULL)";
break;
}
return array($sql, $params);
}
public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
global $DB;
// Build suitable SQL depending on custom or standard field.
if ($this->customfield) {
$customfields = self::get_custom_profile_fields();
if (!array_key_exists($this->customfield, $customfields)) {
// If the field isn't found, nobody matches.
return array('SELECT id FROM {user} WHERE 0 = 1', array());
}
$customfield = $customfields[$this->customfield];
$mainparams = array();
$tablesql = "LEFT JOIN {user_info_data} uid ON uid.fieldid = " .
self::unique_sql_parameter($mainparams, $customfield->id) .
" AND uid.userid = userids.id";
list ($condition, $conditionparams) = $this->get_condition_sql('uid.data');
$mainparams = array_merge($mainparams, $conditionparams);
// If default is true, then allow that too.
if ($this->is_field_condition_met(
$this->operator, $customfield->defaultdata, $this->value)) {
$where = "((uid.data IS NOT NULL AND $condition) OR (uid.data IS NULL))";
} else {
$where = "(uid.data IS NOT NULL AND $condition)";
}
} else {
$tablesql = "JOIN {user} u ON u.id = userids.id";
list ($where, $mainparams) = $this->get_condition_sql(
'u.' . $this->standardfield);
}
// Handle NOT.
if ($not) {
$where = 'NOT (' . $where . ')';
}
// Get enrolled user SQL and combine with this query.
list ($enrolsql, $enrolparams) =
get_enrolled_sql($info->get_context(), '', 0, $onlyactive);
$sql = "SELECT userids.id
FROM ($enrolsql) userids
$tablesql
WHERE $where";
$params = array_merge($enrolparams, $mainparams);
return array($sql, $params);
}
}

View File

@ -40,6 +40,11 @@ class availability_profile_condition_testcase extends advanced_testcase {
/** @var array Array of user IDs for whome we already set the profile field */
protected $setusers = array();
/** @var condition Current condition */
private $cond;
/** @var \core_availability\info Current info */
private $info;
public function setUp() {
global $DB, $CFG;
@ -345,21 +350,25 @@ class availability_profile_condition_testcase extends advanced_testcase {
*
* @param int $userid User id
* @param string|null $value Field value or null to clear
* @param int $fieldid Field id or 0 to use default one
*/
protected function set_field($userid, $value) {
protected function set_field($userid, $value, $fieldid = 0) {
global $DB, $USER;
if (!$fieldid) {
$fieldid = $this->profilefield->id;
}
$alreadyset = array_key_exists($userid, $this->setusers);
if (is_null($value)) {
$DB->delete_records('user_info_data',
array('userid' => $userid, 'fieldid' => $this->profilefield->id));
array('userid' => $userid, 'fieldid' => $fieldid));
unset($this->setusers[$userid]);
} else if ($alreadyset) {
$DB->set_field('user_info_data', 'data', $value,
array('userid' => $userid, 'fieldid' => $this->profilefield->id));
array('userid' => $userid, 'fieldid' => $fieldid));
} else {
$DB->insert_record('user_info_data', array('userid' => $userid,
'fieldid' => $this->profilefield->id, 'data' => $value));
'fieldid' => $fieldid, 'data' => $value));
$this->setusers[$userid] = true;
}
}
@ -441,4 +450,101 @@ class availability_profile_condition_testcase extends advanced_testcase {
ksort($result);
$this->assertEquals(array($student3->id), $result);
}
/**
* Tests getting user list SQL. This is a different test from the above because
* there is some additional code in this function so more variants need testing.
*/
public function test_get_user_list_sql() {
global $DB, $CFG;
$this->resetAfterTest();
$CFG->enableavailability = true;
// Erase static cache before test.
condition::wipe_static_cache();
// For testing, make another info field with default value.
$DB->insert_record('user_info_field', array(
'shortname' => 'tonguestyle', 'name' => 'Tongue style', 'categoryid' => 1,
'datatype' => 'text', 'defaultdata' => 'Slimy'));
$otherprofilefield = $DB->get_record('user_info_field',
array('shortname' => 'tonguestyle'));
// Make a test course and some users.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$student1 = $generator->create_user(array('institution' => 'Unseen University'));
$student2 = $generator->create_user(array('institution' => 'Hogwarts'));
$student3 = $generator->create_user(array('institution' => 'Unseen University'));
$student4 = $generator->create_user(array('institution' => '0'));
$allusers = array();
foreach (array($student1, $student2, $student3, $student4) as $student) {
$generator->enrol_user($student->id, $course->id);
$allusers[$student->id] = $student;
}
$this->set_field($student1->id, 'poison dart');
$this->set_field($student2->id, 'poison dart');
$this->set_field($student3->id, 'Rough', $otherprofilefield->id);
$this->info = new \core_availability\mock_info($course);
// Test standard field condition (positive).
$this->cond = new condition((object)array('sf' => 'institution',
'op' => condition::OP_CONTAINS, 'v' => 'Univ'));
$this->assert_user_list_sql_results(array($student1->id, $student3->id));
// Now try it negative.
$this->assert_user_list_sql_results(array($student2->id, $student4->id), true);
// Try all the other condition types.
$this->cond = new condition((object)array('sf' => 'institution',
'op' => condition::OP_DOES_NOT_CONTAIN, 'v' => 's'));
$this->assert_user_list_sql_results(array($student4->id));
$this->cond = new condition((object)array('sf' => 'institution',
'op' => condition::OP_IS_EQUAL_TO, 'v' => 'Hogwarts'));
$this->assert_user_list_sql_results(array($student2->id));
$this->cond = new condition((object)array('sf' => 'institution',
'op' => condition::OP_STARTS_WITH, 'v' => 'U'));
$this->assert_user_list_sql_results(array($student1->id, $student3->id));
$this->cond = new condition((object)array('sf' => 'institution',
'op' => condition::OP_ENDS_WITH, 'v' => 'rts'));
$this->assert_user_list_sql_results(array($student2->id));
$this->cond = new condition((object)array('sf' => 'institution',
'op' => condition::OP_IS_EMPTY));
$this->assert_user_list_sql_results(array($student4->id));
$this->cond = new condition((object)array('sf' => 'institution',
'op' => condition::OP_IS_NOT_EMPTY));
$this->assert_user_list_sql_results(array($student1->id, $student2->id, $student3->id));
// Try with a custom field condition that doesn't have a default.
$this->cond = new condition((object)array('cf' => 'frogtype',
'op' => condition::OP_CONTAINS, 'v' => 'poison'));
$this->assert_user_list_sql_results(array($student1->id, $student2->id));
$this->cond = new condition((object)array('cf' => 'frogtype',
'op' => condition::OP_IS_EMPTY));
$this->assert_user_list_sql_results(array($student3->id, $student4->id));
// Try with one that does have a default.
$this->cond = new condition((object)array('cf' => 'tonguestyle',
'op' => condition::OP_STARTS_WITH, 'v' => 'Sli'));
$this->assert_user_list_sql_results(array($student1->id, $student2->id,
$student4->id));
$this->cond = new condition((object)array('cf' => 'tonguestyle',
'op' => condition::OP_IS_EMPTY));
$this->assert_user_list_sql_results(array());
}
/**
* Convenience function. Gets the user list SQL and runs it, then checks
* results.
*
* @param array $expected Array of expected user ids
* @param bool $not True if using NOT condition
*/
private function assert_user_list_sql_results(array $expected, $not = false) {
global $DB;
list ($sql, $params) = $this->cond->get_user_list_sql($not, $this->info, true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
}
}

View File

@ -122,4 +122,18 @@ class condition extends \core_availability\condition {
}
return $result;
}
public function get_user_list_sql($not, \core_availability\info $info, $onlyactive) {
global $DB;
// The data for this condition is not really stored in the database,
// so we return SQL that contains the hard-coded user list.
list ($enrolsql, $enrolparams) =
get_enrolled_sql($info->get_context(), '', 0, $onlyactive);
$condition = $not ? 'NOT' : '';
list ($matchsql, $matchparams) = $DB->get_in_or_equal($this->filter, SQL_PARAMS_NAMED);
$sql = "SELECT userids.id
FROM ($enrolsql) userids
WHERE $condition (userids.id $matchsql)";
return array($sql, array_merge($enrolparams, $matchparams));
}
}

View File

@ -391,9 +391,9 @@ class info_testcase extends \advanced_testcase {
}
/**
* Tests the filter_users() function.
* Tests the filter_user_list() and get_user_list_sql() functions.
*/
public function test_filter_users() {
public function test_filter_user_list() {
global $CFG, $DB;
require_once($CFG->dirroot . '/course/lib.php');
$this->resetAfterTest();
@ -408,6 +408,10 @@ class info_testcase extends \advanced_testcase {
$u2 = $generator->create_user();
$u3 = $generator->create_user();
$allusers = array($u1->id => $u1, $u2->id => $u2, $u3->id => $u3);
$generator->enrol_user($u1->id, $course->id);
$generator->enrol_user($u2->id, $course->id);
$generator->enrol_user($u3->id, $course->id);
$pagegen = $generator->get_plugin_generator('mod_page');
$page = $pagegen->create_instance(array('course' => $course));
$page2 = $pagegen->create_instance(array('course' => $course,
@ -425,6 +429,7 @@ class info_testcase extends \advanced_testcase {
$info = new info_module($modinfo->get_cm($page->cmid));
$this->assertEquals(array($u1->id, $u2->id, $u3->id),
array_keys($info->filter_user_list($allusers)));
$this->assertEquals(array('', array()), $info->get_user_list_sql(true));
// Set an availability restriction in database for section 1.
// For the section we set it so it doesn't support filters; for the
@ -440,28 +445,43 @@ class info_testcase extends \advanced_testcase {
// Now it should work (for the module).
$info = new info_module($modinfo->get_cm($page->cmid));
$this->assertEquals(array($u3->id),
$expected = array($u3->id);
$this->assertEquals($expected,
array_keys($info->filter_user_list($allusers)));
list ($sql, $params) = $info->get_user_list_sql();
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
$info = new info_section($modinfo->get_section_info(1));
$this->assertEquals(array($u1->id, $u2->id, $u3->id),
array_keys($info->filter_user_list($allusers)));
$this->assertEquals(array('', array()), $info->get_user_list_sql(true));
// With availability disabled, module returns full list too.
$CFG->enableavailability = false;
$info = new info_module($modinfo->get_cm($page->cmid));
$this->assertEquals(array($u1->id, $u2->id, $u3->id),
array_keys($info->filter_user_list($allusers)));
$this->assertEquals(array('', array()), $info->get_user_list_sql(true));
// Check the other section...
$CFG->enableavailability = true;
$info = new info_section($modinfo->get_section_info(2));
$this->assertEquals(array($u1->id, $u2->id),
array_keys($info->filter_user_list($allusers)));
$expected = array($u1->id, $u2->id);
$this->assertEquals($expected, array_keys($info->filter_user_list($allusers)));
list ($sql, $params) = $info->get_user_list_sql(true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
// And the module in that section - which has combined the section and
// module restrictions.
$info = new info_module($modinfo->get_cm($page2->cmid));
$this->assertEquals(array($u2->id),
array_keys($info->filter_user_list($allusers)));
$expected = array($u2->id);
$this->assertEquals($expected, array_keys($info->filter_user_list($allusers)));
list ($sql, $params) = $info->get_user_list_sql(true);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals($expected, $result);
}
}

17
availability/upgrade.txt Normal file
View File

@ -0,0 +1,17 @@
This files describes API changes in /availability/*.
The information here is intended only for developers.
=== 2.8 ===
* There is a new API function in the info_module/info_section objects (and
related functions in internal API): get_user_list_sql. This returns SQL code
that does roughly the same as filter_user_list to return a list of users who
should be shown as having access to the module or section.
* Any third-party availability plugins which return true to
is_applied_to_user_lists (and therefore previously implemented
filter_user_list) should now also implement get_user_list_sql. If not
implemented, a debugging warning will occur when anybody calls
get_user_list_sql if the affected plugin is in use, and that user list will
not be filtered by the plugin.