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 12, 2024
1 parent d1512c1 commit 104a628
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 95 deletions.
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,
];
}

}
5 changes: 4 additions & 1 deletion mod/assign/classes/output/grading_actionmenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,11 @@ public function export_for_template(\renderer_base $output): array {
)->out(false);
}

$actionbarrenderer = $PAGE->get_renderer('core_course', 'actionbar');
$resetlink = new moodle_url('/mod/assign/view.php', ['id' => $this->cmid, 'action' => 'grading']);
$data['userselector'] = $actionbarrenderer->render(new \core_course\output\actionbar\user_selector($course, $resetlink));

if ($course->groupmode) {
$actionbarrenderer = $PAGE->get_renderer('core_course', 'actionbar');
$data['groupselector'] = $actionbarrenderer->render(new \core_course\output\actionbar\group_selector($course));
}

Expand Down
34 changes: 34 additions & 0 deletions mod/assign/locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
require_once($CFG->libdir . '/portfolio/caller.php');

use core_user\fields;
use mod_assign\event\submission_removed;
use mod_assign\event\submission_status_updated;
use \mod_assign\output\grading_app;
Expand Down Expand Up @@ -207,6 +208,12 @@ class assign {
/** @var float grade value. */
public $grade;

/** @var string $usersearch The content that the current user is looking for. */
protected string $usersearch = '';

/** @var int $userid The ID of the user that the current user is looking for. */
protected int $userid = -1;

/**
* Constructor for the base assign class.
*
Expand Down Expand Up @@ -236,6 +243,14 @@ public function __construct($coursemodulecontext, $coursemodule, $course) {

// Extra entropy is required for uniqid() to work on cygwin.
$this->useridlistid = clean_param(uniqid('', true), PARAM_ALPHANUM);

$this->userid = optional_param('userid', -1, PARAM_INT);
$this->usersearch = optional_param('search', '', PARAM_NOTAGS);

if ($this->userid !== -1) {
$user = \core_user::get_user($this->userid);
$this->usersearch = fullname($user);
}
}

/**
Expand Down Expand Up @@ -2319,6 +2334,23 @@ public function list_participants($currentgroup, $idsonly, $tablesort = false) {
$params['markerid'] = $USER->id;
}

// When a user wants to view a particular user rather than a set of users.
// By omission when selecting one user, also allow passing the search value around.
if ($this->userid !== -1) {
$additionalfilters .= " AND u.id = :uid";
$params['uid'] = $this->userid;
}

// A user wants to return a subset of learners that match their search criteria.
if ($this->usersearch !== '' && $this->userid === -1) {
[
'where' => $keywordswhere,
'params' => $keywordsparams,
] = \core_user::get_users_search_sql($this->usersearch, $this->context);
$additionalfilters .= " AND $keywordswhere";
$params = array_merge($params, $keywordsparams);
}

$sql = "SELECT $fields
FROM {user} u
JOIN ($esql UNION $ssql) je ON je.id = u.id
Expand Down Expand Up @@ -4569,6 +4601,8 @@ protected function view_grading_table() {
$currenturl = new moodle_url('/mod/assign/view.php', ['id' => $this->get_course_module()->id, 'action' => 'grading']);
$PAGE->activityheader->set_attrs(['hidecompletion' => true]);

$PAGE->requires->js_call_amd('core_course/actionbar/user', 'init', [$currenturl->out(false)]);

// Conditionally add the group JS if we have groups enabled.
if ($this->get_course()->groupmode) {
$PAGE->requires->js_call_amd('core_course/actionbar/group', 'init', [$currenturl->out(false)]);
Expand Down
6 changes: 6 additions & 0 deletions mod/assign/templates/grading_actionmenu.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
<h2>{{#str}}gradeitem:submissions, mod_assign{{/str}}</h2>
</div>
<div class="navitem-divider d-none d-sm-flex"></div>
{{#userselector}}
<div class="navitem">
{{{.}}}
</div>
<div class="navitem-divider d-none d-sm-flex"></div>
{{/userselector}}
{{#groupselector}}
<div class="navitem">
{{{.}}}
Expand Down

0 comments on commit 104a628

Please sign in to comment.