Skip to content

Commit

Permalink
MDL-80744 mod_assign: Add user search.
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyatregubov committed Jun 14, 2024
1 parent 32e5d8e commit a0ade37
Show file tree
Hide file tree
Showing 14 changed files with 384 additions and 99 deletions.
19 changes: 16 additions & 3 deletions course/classes/output/actionbar/user_selector.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ class user_selector implements renderable, templatable {
*/
protected moodle_url $resetlink;

/**
* @var int $instanceid Module instance id.
*/
protected ?int $instanceid = null;

/**
* @var stdClass The course object.
*/
Expand All @@ -62,15 +67,23 @@ class user_selector implements renderable, templatable {
*
* @param stdClass $course The course object.
*/
public function __construct(stdClass $course, moodle_url $resetlink = null, ?int $userid = null, ?int $groupid = null, $usersearch = '') {
public function __construct(
stdClass $course,
moodle_url $resetlink = null,
?int $userid = null,
?int $groupid = null,
$usersearch = '',
?int $instanceid = null
) {
$this->course = $course;
$this->userid = $userid;
$this->usersearch = $usersearch;
$this->instanceid = $instanceid;

$this->groupid = $groupid;
$this->resetlink = $resetlink;

if ($this->userid !== 0) {
if (isset($this->userid) && $this->userid) {
$user = \core_user::get_user($this->userid);
$this->usersearch = fullname($user);
}
Expand All @@ -88,7 +101,7 @@ public function export_for_template(renderer_base $output) {
$searchinput = $OUTPUT->render_from_template('core_user/comboboxsearch/user_selector', [
'currentvalue' => $this->usersearch,
'courseid' => $this->course->id,
'instance' => rand(),
'instance' => $this->instanceid ?? rand(),
'resetlink' => $this->resetlink->out(false),
'group' => $this->groupid ?? 0,
'name' => 'usersearch',
Expand Down
95 changes: 1 addition & 94 deletions grade/report/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -485,108 +485,15 @@ public function setup_users() {

// A user wants to return a subset of learners that match their search criteria.
if ($this->usersearch !== '' && $this->userid === -1) {
// Get the fields for all contexts because there is a special case later where it allows
// matches of fields you can't access if they are on your own account.
$userfields = fields::for_identity(null, false)->with_userpic();
['mappings' => $mappings] = (array)$userfields->get_sql('u', true);
[
'where' => $keywordswhere,
'params' => $keywordsparams,
] = $this->get_users_search_sql($mappings, $userfields->get_required_fields());
] = \core_user::get_users_search_sql($this->usersearch, $this->context);
$this->userwheresql .= " AND $keywordswhere";
$this->userwheresql_params = array_merge($this->userwheresql_params, $keywordsparams);
}
}

/**
* Prepare SQL where clause and associated parameters for any user searching being performed.
* This mostly came from core_user\table\participants_search with some slight modifications four our use case.
*
* @param array $mappings Array of field mappings (fieldname => SQL code for the value)
* @param array $userfields An array that we cast from user profile fields to search within.
* @return array SQL query data in the format ['where' => '', 'params' => []].
*/
protected function get_users_search_sql(array $mappings, array $userfields): array {
global $DB, $USER;

$canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);

$params = [];
$searchkey1 = 'search01';
$searchkey2 = 'search02';
$searchkey3 = 'search03';

$conditions = [];

// Search by fullname.
[$fullname, $fullnameparams] = fields::get_sql_fullname('u', $canviewfullnames);
$conditions[] = $DB->sql_like($fullname, ':' . $searchkey1, false, false);
$params = array_merge($params, $fullnameparams);

// Search by email.
$email = $DB->sql_like('email', ':' . $searchkey2, false, false);

if (!in_array('email', $userfields)) {
$maildisplay = 'maildisplay0';
$userid1 = 'userid01';
// 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". // Users can always find themselves.
"))";
$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 = 'userid02';
// 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;

// Search all user identify fields.
$extrasearchfields = fields::get_identity_fields(null, false);
foreach ($extrasearchfields as $fieldindex => $extrasearchfield) {
if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) {
// Already covered above.
continue;
}
// The param must be short (max 32 characters) so don't include field name.
$param = $searchkey3 . '_ident' . $fieldindex;
$fieldsql = $mappings[$extrasearchfield];
$condition = $DB->sql_like($fieldsql, ':' . $param, false, false);
$params[$param] = "%$this->usersearch%";

if (!in_array($extrasearchfield, $userfields)) {
// User cannot see this field, but allow match if their own account.
$userid3 = 'userid03_ident' . $fieldindex;
$condition = "(". $condition . " AND u.id = :$userid3)";
$params[$userid3] = $USER->id;
}
$conditions[] = $condition;
}

$where = "(". implode(" OR ", $conditions) .") ";
$params[$searchkey1] = "%$this->usersearch%";
$params[$searchkey2] = "%$this->usersearch%";
$params[$searchkey3] = "%$this->usersearch%";

return [
'where' => $where,
'params' => $params,
];
}

/**
* Returns an arrow icon inside an <a> tag, for the purpose of sorting a column.
* @param string $direction
Expand Down
95 changes: 95 additions & 0 deletions lib/classes/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

use core_user\fields;

defined('MOODLE_INTERNAL') || die();

/**
Expand Down Expand Up @@ -1497,4 +1499,97 @@ public static function get_initials(stdClass $user): string {
return $initials;
}

/**
* Prepare SQL where clause and associated parameters for any user searching being performed.
* This mostly came from core_user\table\participants_search with some slight modifications four our use case.
*
* @param string $usersearch Array of field mappings (fieldname => SQL code for the value)
* @param array $userfields An array that we cast from user profile fields to search within.
* @return array SQL query data in the format ['where' => '', 'params' => []].
*/
public static function get_users_search_sql(string $usersearch = '', context $context = null): array {
global $DB, $USER;

$userfields = fields::for_identity($context, false)->with_userpic();
['mappings' => $mappings] = (array)$userfields->get_sql('u', true);
$userfields = $userfields->get_required_fields();

$canviewfullnames = has_capability('moodle/site:viewfullnames', $context);

$params = [];
$searchkey1 = 'search01';
$searchkey2 = 'search02';
$searchkey3 = 'search03';

$conditions = [];

// Search by fullname.
[$fullname, $fullnameparams] = fields::get_sql_fullname('u', $canviewfullnames);
$conditions[] = $DB->sql_like($fullname, ':' . $searchkey1, false, false);
$params = array_merge($params, $fullnameparams);

// Search by email.
$email = $DB->sql_like('email', ':' . $searchkey2, false, false);

if (!in_array('email', $userfields)) {
$maildisplay = 'maildisplay0';
$userid1 = 'userid01';
// 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". // Users can always find themselves.
"))";
$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 = 'userid02';
// 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;

// Search all user identify fields.
$extrasearchfields = fields::get_identity_fields(null, false);
foreach ($extrasearchfields as $fieldindex => $extrasearchfield) {
if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) {
// Already covered above.
continue;
}
// The param must be short (max 32 characters) so don't include field name.
$param = $searchkey3 . '_ident' . $fieldindex;
$fieldsql = $mappings[$extrasearchfield];
$condition = $DB->sql_like($fieldsql, ':' . $param, false, false);
$params[$param] = "%$usersearch%";

if (!in_array($extrasearchfield, $userfields)) {
// User cannot see this field, but allow match if their own account.
$userid3 = 'userid03_ident' . $fieldindex;
$condition = "(". $condition . " AND u.id = :$userid3)";
$params[$userid3] = $USER->id;
}
$conditions[] = $condition;
}

$where = "(". implode(" OR ", $conditions) .") ";
$params[$searchkey1] = "%$usersearch%";
$params[$searchkey2] = "%$usersearch%";
$params[$searchkey3] = "%$usersearch%";

return [
'where' => $where,
'params' => $params,
];
}

}
10 changes: 10 additions & 0 deletions mod/assign/amd/build/repository.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions mod/assign/amd/build/repository.min.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions mod/assign/amd/build/user.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a0ade37

Please sign in to comment.