mirror of
https://github.com/moodle/moodle.git
synced 2025-04-22 17:02:03 +02:00
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:
parent
c13ac85db8
commit
1a7049af22
@ -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.
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
14
availability/tests/fixtures/mock_condition.php
vendored
14
availability/tests/fixtures/mock_condition.php
vendored
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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
17
availability/upgrade.txt
Normal 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.
|
Loading…
x
Reference in New Issue
Block a user