Merge branch 'MDL-68612-master-integration' of git://github.com/mickhawkins/moodle

This commit is contained in:
Jun Pataleta 2020-06-03 15:39:36 +08:00
commit 7fcdd9d983
44 changed files with 1629 additions and 797 deletions

View file

@ -230,7 +230,12 @@ class behat_core_generator extends behat_generator_base {
'datagenerator' => 'setup_backpack_connected',
'required' => ['user', 'externalbackpack'],
'switchids' => ['user' => 'userid', 'externalbackpack' => 'externalbackpackid']
]
],
'last access times' => [
'datagenerator' => 'last_access_times',
'required' => ['user', 'course', 'lastaccess'],
'switchids' => ['user' => 'userid', 'course' => 'courseid'],
],
];
}
@ -951,4 +956,100 @@ class behat_core_generator extends behat_generator_base {
$backpack->externalbackpackid = $data['externalbackpackid'];
$DB->insert_record('badge_backpack', $backpack);
}
/**
* Creates user last access data within given courses.
*
* @param array $data
* @return void
*/
protected function process_last_access_times(array $data) {
global $DB;
if (!isset($data['userid'])) {
throw new Exception('\'last acces times\' requires the field \'user\' to be specified');
}
if (!isset($data['courseid'])) {
throw new Exception('\'last acces times\' requires the field \'course\' to be specified');
}
if (!isset($data['lastaccess'])) {
throw new Exception('\'last acces times\' requires the field \'lastaccess\' to be specified');
}
$userdata = [];
$userdata['old'] = $DB->get_record('user', ['id' => $data['userid']], 'firstaccess, lastaccess, lastlogin, currentlogin');
$userdata['new'] = [
'firstaccess' => $userdata['old']->firstaccess,
'lastaccess' => $userdata['old']->lastaccess,
'lastlogin' => $userdata['old']->lastlogin,
'currentlogin' => $userdata['old']->currentlogin,
];
// Check for lastaccess data for this course.
$lastaccessdata = [
'userid' => $data['userid'],
'courseid' => $data['courseid'],
];
$lastaccessid = $DB->get_field('user_lastaccess', 'id', $lastaccessdata);
$dbdata = (object) $lastaccessdata;
$dbdata->timeaccess = $data['lastaccess'];
// Set the course last access time.
if ($lastaccessid) {
$dbdata->id = $lastaccessid;
$DB->update_record('user_lastaccess', $dbdata);
} else {
$DB->insert_record('user_lastaccess', $dbdata);
}
// Store changes to other user access times as needed.
// Update first access if this is the user's first login, or this access is earlier than their current first access.
if (empty($userdata['new']['firstaccess']) ||
$userdata['new']['firstaccess'] > $data['lastaccess']) {
$userdata['new']['firstaccess'] = $data['lastaccess'];
}
// Update last access if it is the user's most recent access.
if (empty($userdata['new']['lastaccess']) ||
$userdata['new']['lastaccess'] < $data['lastaccess']) {
$userdata['new']['lastaccess'] = $data['lastaccess'];
}
// Update last and current login if it is the user's most recent access.
if (empty($userdata['new']['lastlogin']) ||
$userdata['new']['lastlogin'] < $data['lastaccess']) {
$userdata['new']['lastlogin'] = $data['lastaccess'];
$userdata['new']['currentlogin'] = $data['lastaccess'];
}
$updatedata = [];
if ($userdata['new']['firstaccess'] != $userdata['old']->firstaccess) {
$updatedata['firstaccess'] = $userdata['new']['firstaccess'];
}
if ($userdata['new']['lastaccess'] != $userdata['old']->lastaccess) {
$updatedata['lastaccess'] = $userdata['new']['lastaccess'];
}
if ($userdata['new']['lastlogin'] != $userdata['old']->lastlogin) {
$updatedata['lastlogin'] = $userdata['new']['lastlogin'];
}
if ($userdata['new']['currentlogin'] != $userdata['old']->currentlogin) {
$updatedata['currentlogin'] = $userdata['new']['currentlogin'];
}
// Only update user access data if there have been any changes.
if (!empty($updatedata)) {
$updatedata['id'] = $data['userid'];
$updatedata = (object) $updatedata;
$DB->update_record('user', $updatedata);
}
}
}

View file

@ -3559,3 +3559,267 @@ function cron_bc_hack_plugin_functions($plugintype, $plugins) {
return $plugins;
}
/**
* Returns the SQL used by the participants table.
*
* @deprecated since Moodle 3.9 MDL-68612 - See \core_user\table\participants_search for an improved way to fetch participants.
* @param int $courseid The course id
* @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
* @param int $accesssince The time since last access, 0 means any time
* @param int $roleid The role id, 0 means all roles and -1 no roles
* @param int $enrolid The enrolment id, 0 means all enrolment methods will be returned.
* @param int $statusid The user enrolment status, -1 means all enrolments regardless of the status will be returned, if allowed.
* @param string|array $search The search that was performed, empty means perform no search
* @param string $additionalwhere Any additional SQL to add to where
* @param array $additionalparams The additional params
* @return array
*/
function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $enrolid = 0, $statusid = -1,
$search = '', $additionalwhere = '', $additionalparams = array()) {
global $DB, $USER, $CFG;
$deprecatedtext = __FUNCTION__ . '() is deprecated. ' .
'Please use \core\table\participants_search::class with table filtersets instead.';
debugging($deprecatedtext, DEBUG_DEVELOPER);
// Get the context.
$context = \context_course::instance($courseid, MUST_EXIST);
$isfrontpage = ($courseid == SITEID);
// Default filter settings. We only show active by default, especially if the user has no capability to review enrolments.
$onlyactive = true;
$onlysuspended = false;
if (has_capability('moodle/course:enrolreview', $context) && (has_capability('moodle/course:viewsuspendedusers', $context))) {
switch ($statusid) {
case ENROL_USER_ACTIVE:
// Nothing to do here.
break;
case ENROL_USER_SUSPENDED:
$onlyactive = false;
$onlysuspended = true;
break;
default:
// If the user has capability to review user enrolments, but statusid is set to -1, set $onlyactive to false.
$onlyactive = false;
break;
}
}
list($esql, $params) = get_enrolled_sql($context, null, $groupid, $onlyactive, $onlysuspended, $enrolid);
$joins = array('FROM {user} u');
$wheres = array();
$userfields = get_extra_user_fields($context);
$userfieldssql = user_picture::fields('u', $userfields);
if ($isfrontpage) {
$select = "SELECT $userfieldssql, u.lastaccess";
$joins[] = "JOIN ($esql) e ON e.id = u.id"; // Everybody on the frontpage usually.
if ($accesssince) {
$wheres[] = user_get_user_lastaccess_sql($accesssince);
}
} else {
$select = "SELECT $userfieldssql, COALESCE(ul.timeaccess, 0) AS lastaccess";
$joins[] = "JOIN ($esql) e ON e.id = u.id"; // Course enrolled users only.
// Not everybody has accessed the course yet.
$joins[] = 'LEFT JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = :courseid)';
$params['courseid'] = $courseid;
if ($accesssince) {
$wheres[] = user_get_course_lastaccess_sql($accesssince);
}
}
// Performance hacks - we preload user contexts together with accounts.
$ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$ccjoin = 'LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)';
$params['contextlevel'] = CONTEXT_USER;
$select .= $ccselect;
$joins[] = $ccjoin;
// Limit list to users with some role only.
if ($roleid) {
// We want to query both the current context and parent contexts.
list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true),
SQL_PARAMS_NAMED, 'relatedctx');
// Get users without any role.
if ($roleid == -1) {
$wheres[] = "u.id NOT IN (SELECT userid FROM {role_assignments} WHERE contextid $relatedctxsql)";
$params = array_merge($params, $relatedctxparams);
} else {
$wheres[] = "u.id IN (SELECT userid FROM {role_assignments} WHERE roleid = :roleid AND contextid $relatedctxsql)";
$params = array_merge($params, array('roleid' => $roleid), $relatedctxparams);
}
}
if (!empty($search)) {
if (!is_array($search)) {
$search = [$search];
}
foreach ($search as $index => $keyword) {
$searchkey1 = 'search' . $index . '1';
$searchkey2 = 'search' . $index . '2';
$searchkey3 = 'search' . $index . '3';
$searchkey4 = 'search' . $index . '4';
$searchkey5 = 'search' . $index . '5';
$searchkey6 = 'search' . $index . '6';
$searchkey7 = 'search' . $index . '7';
$conditions = array();
// Search by fullname.
$fullname = $DB->sql_fullname('u.firstname', 'u.lastname');
$conditions[] = $DB->sql_like($fullname, ':' . $searchkey1, false, false);
// Search by email.
$email = $DB->sql_like('email', ':' . $searchkey2, false, false);
if (!in_array('email', $userfields)) {
$maildisplay = 'maildisplay' . $index;
$userid1 = 'userid' . $index . '1';
// Prevent users who hide their email address from being found by others
// who aren't allowed to see hidden email addresses.
$email = "(". $email ." AND (" .
"u.maildisplay <> :$maildisplay " .
"OR u.id = :$userid1". // User can always find himself.
"))";
$params[$maildisplay] = core_user::MAILDISPLAY_HIDE;
$params[$userid1] = $USER->id;
}
$conditions[] = $email;
// Search by idnumber.
$idnumber = $DB->sql_like('idnumber', ':' . $searchkey3, false, false);
if (!in_array('idnumber', $userfields)) {
$userid2 = 'userid' . $index . '2';
// Users who aren't allowed to see idnumbers should at most find themselves
// when searching for an idnumber.
$idnumber = "(". $idnumber . " AND u.id = :$userid2)";
$params[$userid2] = $USER->id;
}
$conditions[] = $idnumber;
if (!empty($CFG->showuseridentity)) {
// Search all user identify fields.
$extrasearchfields = explode(',', $CFG->showuseridentity);
foreach ($extrasearchfields as $extrasearchfield) {
if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) {
// Already covered above. Search by country not supported.
continue;
}
$param = $searchkey3 . $extrasearchfield;
$condition = $DB->sql_like($extrasearchfield, ':' . $param, false, false);
$params[$param] = "%$keyword%";
if (!in_array($extrasearchfield, $userfields)) {
// User cannot see this field, but allow match if their own account.
$userid3 = 'userid' . $index . '3' . $extrasearchfield;
$condition = "(". $condition . " AND u.id = :$userid3)";
$params[$userid3] = $USER->id;
}
$conditions[] = $condition;
}
}
// Search by middlename.
$middlename = $DB->sql_like('middlename', ':' . $searchkey4, false, false);
$conditions[] = $middlename;
// Search by alternatename.
$alternatename = $DB->sql_like('alternatename', ':' . $searchkey5, false, false);
$conditions[] = $alternatename;
// Search by firstnamephonetic.
$firstnamephonetic = $DB->sql_like('firstnamephonetic', ':' . $searchkey6, false, false);
$conditions[] = $firstnamephonetic;
// Search by lastnamephonetic.
$lastnamephonetic = $DB->sql_like('lastnamephonetic', ':' . $searchkey7, false, false);
$conditions[] = $lastnamephonetic;
$wheres[] = "(". implode(" OR ", $conditions) .") ";
$params[$searchkey1] = "%$keyword%";
$params[$searchkey2] = "%$keyword%";
$params[$searchkey3] = "%$keyword%";
$params[$searchkey4] = "%$keyword%";
$params[$searchkey5] = "%$keyword%";
$params[$searchkey6] = "%$keyword%";
$params[$searchkey7] = "%$keyword%";
}
}
if (!empty($additionalwhere)) {
$wheres[] = $additionalwhere;
$params = array_merge($params, $additionalparams);
}
$from = implode("\n", $joins);
if ($wheres) {
$where = 'WHERE ' . implode(' AND ', $wheres);
} else {
$where = '';
}
return array($select, $from, $where, $params);
}
/**
* Returns the total number of participants for a given course.
*
* @deprecated since Moodle 3.9 MDL-68612 - See \core_user\table\participants_search for an improved way to fetch participants.
* @param int $courseid The course id
* @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
* @param int $accesssince The time since last access, 0 means any time
* @param int $roleid The role id, 0 means all roles
* @param int $enrolid The applied filter for the user enrolment ID.
* @param int $status The applied filter for the user's enrolment status.
* @param string|array $search The search that was performed, empty means perform no search
* @param string $additionalwhere Any additional SQL to add to where
* @param array $additionalparams The additional params
* @return int
*/
function user_get_total_participants($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $enrolid = 0, $statusid = -1,
$search = '', $additionalwhere = '', $additionalparams = array()) {
global $DB;
$deprecatedtext = __FUNCTION__ . '() is deprecated. ' .
'Please use \core\table\participants_search::class with table filtersets instead.';
debugging($deprecatedtext, DEBUG_DEVELOPER);
list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, $enrolid,
$statusid, $search, $additionalwhere, $additionalparams);
return $DB->count_records_sql("SELECT COUNT(u.id) $from $where", $params);
}
/**
* Returns the participants for a given course.
*
* @deprecated since Moodle 3.9 MDL-68612 - See \core_user\table\participants_search for an improved way to fetch participants.
* @param int $courseid The course id
* @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
* @param int $accesssince The time since last access
* @param int $roleid The role id
* @param int $enrolid The applied filter for the user enrolment ID.
* @param int $status The applied filter for the user's enrolment status.
* @param string $search The search that was performed
* @param string $additionalwhere Any additional SQL to add to where
* @param array $additionalparams The additional params
* @param string $sort The SQL sort
* @param int $limitfrom return a subset of records, starting at this point (optional).
* @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
* @return moodle_recordset
*/
function user_get_participants($courseid, $groupid = 0, $accesssince, $roleid, $enrolid = 0, $statusid, $search,
$additionalwhere = '', $additionalparams = array(), $sort = '', $limitfrom = 0, $limitnum = 0) {
global $DB;
$deprecatedtext = __FUNCTION__ . '() is deprecated. ' .
'Please use \core\table\participants_search::class with table filtersets instead.';
debugging($deprecatedtext, DEBUG_DEVELOPER);
list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, $enrolid,
$statusid, $search, $additionalwhere, $additionalparams);
return $DB->get_recordset_sql("$select $from $where $sort", $params, $limitfrom, $limitnum);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -158,6 +158,18 @@ export const updateTable = (tableRoot, {
}
};
/**
* Get the table dataset for the specified tableRoot, ensuring that the provided table is a dynamic table.
*
* @param {HTMLElement} tableRoot
* @returns {DOMStringMap}
*/
const getTableData = tableRoot => {
checkTableIsDynamic(tableRoot);
return tableRoot.dataset;
};
/**
* Update the specified table using the new filters.
*
@ -169,6 +181,18 @@ export const updateTable = (tableRoot, {
export const setFilters = (tableRoot, filters, refreshContent = true) =>
updateTable(tableRoot, {filters}, refreshContent);
/**
* Get the filter data for the specified table.
*
* @param {HTMLElement} tableRoot
* @returns {Object}
*/
export const getFilters = tableRoot => {
checkTableIsDynamic(tableRoot);
return getFiltersetFromTable(tableRoot);
};
/**
* Update the sort order.
*
@ -192,6 +216,14 @@ export const setSortOrder = (tableRoot, sortBy, sortOrder, refreshContent = true
export const setPageNumber = (tableRoot, pageNumber, refreshContent = true) =>
updateTable(tableRoot, {pageNumber}, refreshContent);
/**
* Get the current page number.
*
* @param {HTMLElement} tableRoot
* @returns {Number}
*/
export const getPageNumber = tableRoot => getTableData(tableRoot).tablePageNumber;
/**
* Set the page size.
*
@ -203,6 +235,14 @@ export const setPageNumber = (tableRoot, pageNumber, refreshContent = true) =>
export const setPageSize = (tableRoot, pageSize, refreshContent = true) =>
updateTable(tableRoot, {pageSize, pageNumber: 0}, refreshContent);
/**
* Get the current page size.
*
* @param {HTMLElement} tableRoot
* @returns {Number}
*/
export const getPageSize = tableRoot => getTableData(tableRoot).tablePageSize;
/**
* Update the first initial to show.
*
@ -214,6 +254,14 @@ export const setPageSize = (tableRoot, pageSize, refreshContent = true) =>
export const setFirstInitial = (tableRoot, firstInitial, refreshContent = true) =>
updateTable(tableRoot, {firstInitial}, refreshContent);
/**
* Get the current first initial filter.
*
* @param {HTMLElement} tableRoot
* @returns {String}
*/
export const getFirstInitial = tableRoot => getTableData(tableRoot).tableFirstInitial;
/**
* Update the last initial to show.
*
@ -225,6 +273,14 @@ export const setFirstInitial = (tableRoot, firstInitial, refreshContent = true)
export const setLastInitial = (tableRoot, lastInitial, refreshContent = true) =>
updateTable(tableRoot, {lastInitial}, refreshContent);
/**
* Get the current last initial filter.
*
* @param {HTMLElement} tableRoot
* @returns {String}
*/
export const getLastInitial = tableRoot => getTableData(tableRoot).tableLastInitial;
/**
* Hide a column in the participants table.
*

View file

@ -41,11 +41,8 @@ use Iterator;
*/
class filter implements Countable, Iterator, JsonSerializable {
/**
* @var in The default filter type (ALL)
* Note: This is for backwards compatibility with the old UI behaviour and will be set to JOINTYPE_ANY as part of MDL-68612.
*/
const JOINTYPE_DEFAULT = 2;
/** @var in The default filter type (ANY) */
const JOINTYPE_DEFAULT = 1;
/** @var int None of the following match */
const JOINTYPE_NONE = 0;

View file

@ -40,11 +40,8 @@ use moodle_exception;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class filterset implements JsonSerializable {
/**
* @var in The default filter type (ALL)
* Note: This is for backwards compatibility with the old UI behaviour and will be set to JOINTYPE_ANY as part of MDL-68612.
*/
const JOINTYPE_DEFAULT = 2;
/** @var in The default filter type (ANY) */
const JOINTYPE_DEFAULT = 1;
/** @var int None of the following match */
const JOINTYPE_NONE = 0;