MDL-68246 user: Adding the participants_search class
authorMichael Hawkins <michaelh@moodle.com>
Thu, 26 Mar 2020 08:13:40 +0000 (16:13 +0800)
committerMichael Hawkins <michaelh@moodle.com>
Thu, 14 May 2020 07:47:34 +0000 (15:47 +0800)
This class introduces support for multiple values per filter to the
participants page.

user/classes/table/participants.php
user/classes/table/participants_search.php [new file with mode: 0644]

index 6dee76b..c1058cd 100644 (file)
@@ -30,6 +30,7 @@ use context;
 use core_table\dynamic as dynamic_table;
 use core_table\local\filter\filterset;
 use core_user\output\status_field;
+use core_user\table\participants_search;
 use moodle_url;
 
 defined('MOODLE_INTERNAL') || die;
@@ -53,36 +54,6 @@ class participants extends \table_sql implements dynamic_table {
      */
     protected $courseid;
 
-    /**
-     * @var int|false False if groups not used, int if groups used, 0 for all groups.
-     */
-    protected $currentgroup;
-
-    /**
-     * @var int $accesssince The time the user last accessed the site
-     */
-    protected $accesssince;
-
-    /**
-     * @var int $roleid The role we are including, 0 means all enrolled users
-     */
-    protected $roleid;
-
-    /**
-     * @var int $enrolid The applied filter for the user enrolment ID.
-     */
-    protected $enrolid;
-
-    /**
-     * @var int $status The applied filter for the user's enrolment status.
-     */
-    protected $status;
-
-    /**
-     * @var string $search The string being searched.
-     */
-    protected $search;
-
     /**
      * @var bool $selectall Has the user selected all users on the page?
      */
@@ -133,6 +104,11 @@ class participants extends \table_sql implements dynamic_table {
      */
     protected $profileroles;
 
+    /**
+     * @var filterset Filterset describing which participants to include.
+     */
+    protected $filterset;
+
     /** @var \stdClass[] $viewableroles */
     private $viewableroles;
 
@@ -431,9 +407,9 @@ class participants extends \table_sql implements dynamic_table {
      */
     public function query_db($pagesize, $useinitialsbar = true) {
         list($twhere, $tparams) = $this->get_sql_where();
+        $psearch = new participants_search($this->course, $this->context, $this->filterset);
 
-        $total = user_get_total_participants($this->course->id, $this->currentgroup, $this->accesssince,
-            $this->roleid, $this->enrolid, $this->status, $this->search, $twhere, $tparams);
+        $total = $psearch->get_total_participants_count($twhere, $tparams);
 
         $this->pagesize($pagesize, $total);
 
@@ -442,9 +418,8 @@ class participants extends \table_sql implements dynamic_table {
             $sort = 'ORDER BY ' . $sort;
         }
 
-        $rawdata = user_get_participants($this->course->id, $this->currentgroup, $this->accesssince,
-            $this->roleid, $this->enrolid, $this->status, $this->search, $twhere, $tparams, $sort, $this->get_page_start(),
-            $this->get_page_size());
+        $rawdata = $psearch->get_participants($twhere, $tparams, $sort, $this->get_page_start(), $this->get_page_size());
+
         $this->rawdata = [];
         foreach ($rawdata as $user) {
             $this->rawdata[$user->id] = $user;
@@ -501,36 +476,6 @@ class participants extends \table_sql implements dynamic_table {
         $this->context = \context_course::instance($this->courseid, MUST_EXIST);
 
         // Process the filterset.
-        $this->currentgroup = null;
-        if ($filterset->has_filter('groups')) {
-            $this->currentgroup = $filterset->get_filter('groups')->current();
-        }
-
-        $this->roleid = null;
-        if ($filterset->has_filter('roles')) {
-            $this->roleid = $filterset->get_filter('roles')->current();
-        }
-
-        $this->enrolid = null;
-        if ($filterset->has_filter('enrolments')) {
-            $this->enrolid = $filterset->get_filter('enrolments')->current();
-        }
-
-        $this->status = -1;
-        if ($filterset->has_filter('status')) {
-            $this->status = $filterset->get_filter('status')->current();
-        }
-
-        $this->accesssince = null;
-        if ($filterset->has_filter('accesssince')) {
-            $this->accesssince = $filterset->get_filter('accesssince')->current();
-        }
-
-        $this->search = null;
-        if ($filterset->has_filter('keywords')) {
-            $this->search = $filterset->get_filter('keywords')->get_filter_values();
-        }
-
         parent::set_filterset($filterset);
     }
 
diff --git a/user/classes/table/participants_search.php b/user/classes/table/participants_search.php
new file mode 100644 (file)
index 0000000..1ede7f6
--- /dev/null
@@ -0,0 +1,595 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Class used to fetch participants based on a filterset.
+ *
+ * @package    core_user
+ * @copyright  2020 Michael Hawkins <michaelh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_user\table;
+
+use context;
+use context_helper;
+use core_table\local\filter\filterset;
+use core_user;
+use moodle_recordset;
+use stdClass;
+use user_picture;
+
+/**
+ * Class used to fetch participants based on a filterset.
+ *
+ * @package    core_user
+ * @copyright  2020 Michael Hawkins <michaelh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class participants_search {
+
+    /**
+     * @var filterset $filterset The filterset describing which participants to include in the search.
+     */
+    protected $filterset;
+
+    /**
+     * @var stdClass $course The course being searched.
+     */
+    protected $course;
+
+    /**
+     * @var context_course $context The course context being searched.
+     */
+    protected $context;
+
+    /**
+     * @var string[] $userfields Names of any extra user fields to be shown when listing users.
+     */
+    protected $userfields;
+
+    /**
+     * Class constructor.
+     *
+     * @param stdClass $course The course being searched.
+     * @param context $context The context of the search.
+     * @param filterset $filterset The filterset used to filter the participants in a course.
+     */
+    public function __construct(stdClass $course, context $context, filterset $filterset) {
+        $this->course = $course;
+        $this->context = $context;
+        $this->filterset = $filterset;
+
+        $this->userfields = get_extra_user_fields($this->context);
+    }
+
+    /**
+     * Fetch participants matching the filterset.
+     *
+     * @param string $additionalwhere Any additional SQL to add to where.
+     * @param array $additionalparams The additional params used by $additionalwhere.
+     * @param string $sort Optional 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
+     */
+    public function get_participants(string $additionalwhere = '', array $additionalparams = [], string $sort = '',
+            int $limitfrom = 0, int $limitnum = 0): moodle_recordset {
+        global $DB;
+
+        [
+            'select' => $select,
+            'from' => $from,
+            'where' => $where,
+            'params' => $params,
+        ] = $this->get_participants_sql($additionalwhere, $additionalparams);
+
+        return $DB->get_recordset_sql("{$select} {$from} {$where} {$sort}", $params, $limitfrom, $limitnum);
+    }
+
+    /**
+     * Returns the total number of participants for a given course.
+     *
+     * @param string $additionalwhere Any additional SQL to add to where.
+     * @param array $additionalparams The additional params used by $additionalwhere.
+     * @return int
+     */
+    public function get_total_participants_count(string $additionalwhere = '', array $additionalparams = []): int {
+        global $DB;
+
+        [
+            'from' => $from,
+            'where' => $where,
+            'params' => $params,
+        ] = $this->get_participants_sql($additionalwhere, $additionalparams);
+
+        return $DB->count_records_sql("SELECT COUNT(u.id) {$from} {$where}", $params);
+    }
+
+    /**
+     * Generate the SQL used to fetch filtered data for the participants table.
+     *
+     * @param string $additionalwhere Any additional SQL to add to where
+     * @param array $additionalparams The additional params
+     * @return array
+     */
+    protected function get_participants_sql(string $additionalwhere, array $additionalparams): array {
+        $isfrontpage = ($this->course->id == SITEID);
+        $accesssince = $this->filterset->has_filter('accesssince') ? $this->filterset->get_filter('accesssince')->current() : 0;
+
+        [
+            'sql' => $esql,
+            'params' => $params,
+        ] = $this->get_enrolled_sql();
+
+        $joins = ['FROM {user} u'];
+        $wheres = [];
+
+        $userfieldssql = user_picture::fields('u', $this->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'] = $this->course->id;
+            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;
+
+        // Apply any role filtering.
+        if ($this->filterset->has_filter('roles')) {
+            [
+                'where' => $roleswhere,
+                'params' => $rolesparams,
+            ] = $this->get_roles_sql();
+
+            if (!empty($roleswhere)) {
+                $wheres[] = "({$roleswhere})";
+            }
+
+            if (!empty($rolesparams)) {
+                $params = array_merge($params, $rolesparams);
+            }
+        }
+
+        // Apply any keyword text searches.
+        if ($this->filterset->has_filter('keywords')) {
+            [
+                'wheres' => $keywordswheres,
+                'params' => $keywordsparams,
+            ] = $this->get_keywords_search_sql();
+
+            if (!empty($keywordswheres)) {
+                $wheres = array_merge($wheres, $keywordswheres);
+            }
+
+            if (!empty($keywordsparams)) {
+                $params = array_merge($params, $keywordsparams);
+            }
+        }
+
+        // Add any supplied additional WHERE clauses.
+        if (!empty($additionalwhere)) {
+            $wheres[] = $additionalwhere;
+            $params = array_merge($params, $additionalparams);
+        }
+
+        // Prepare final values.
+        $from = implode("\n", $joins);
+        if ($wheres) {
+            $where = 'WHERE ' . implode(' AND ', $wheres);
+        } else {
+            $where = '';
+        }
+
+        return [
+            'select' => $select,
+            'from' => $from,
+            'where' => $where,
+            'params' => $params,
+        ];
+    }
+
+    /**
+     * Prepare SQL and associated parameters for users enrolled in the course.
+     *
+     * @return array SQL query data in the format ['sql' => '', 'params' => []].
+     */
+    protected function get_enrolled_sql(): array {
+        // Default status filter settings.
+        // We only show active by default, especially if the user has no capability to review enrolments.
+        $onlyactive = true;
+        $onlysuspended = false;
+
+        $enrolids = [];
+        $groupids = [];
+
+        if ($this->filterset->has_filter('enrolments')) {
+            $enrolids = $this->filterset->get_filter('enrolments')->get_filter_values();
+        }
+
+        if ($this->filterset->has_filter('groups')) {
+            $groupids = $this->filterset->get_filter('groups')->get_filter_values();
+        }
+
+        $prefix = 'eu_';
+        $uid = "{$prefix}u.id";
+        $joins = [];
+        $wheres = [];
+
+        // Set enrolment types.
+        if (has_capability('moodle/course:enrolreview', $this->context) &&
+                (has_capability('moodle/course:viewsuspendedusers', $this->context))) {
+            $statusids = [-1];
+
+            if ($this->filterset->has_filter('status')) {
+                $statusids = $this->filterset->get_filter('status')->get_filter_values();
+            }
+
+            // If both status IDs are selected, treat it as not filtering by status.
+            // Note: This is a temporary measure that supports the existing logic.
+            // It will be updated when support is added for all logical operators (all/none).
+            if (count($statusids) !== 1) {
+                $statusid = -1;
+            } else {
+                $statusid = $statusids[0];
+            }
+
+            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;
+            }
+        }
+
+        // Prepare enrolment type filtering.
+        // This will need to use a custom method or new function when 'All'/'Not' cases are introduced,
+        // to avoid the separate passing in of status values ($onlyactive and $onlysuspended).
+        $enrolledjoin = $this->get_enrolled_join($this->context, $uid, $onlyactive, $onlysuspended, $enrolids);
+        $joins[] = $enrolledjoin->joins;
+        $wheres[] = $enrolledjoin->wheres;
+        $params = $enrolledjoin->params;
+
+        // Prepare any groups filtering.
+        if ($groupids) {
+            $groupjoin = groups_get_members_join($groupids, $uid, $this->context);
+            $joins[] = $groupjoin->joins;
+            $params = array_merge($params, $groupjoin->params);
+            if (!empty($groupjoin->wheres)) {
+                $wheres[] = $groupjoin->wheres;
+            }
+        }
+
+        $joinsql = implode("\n", $joins);
+        $wheres[] = "{$prefix}u.deleted = 0";
+        $wheresql = implode(" AND ", $wheres);
+
+        $sql = "SELECT DISTINCT {$prefix}u.id
+                  FROM {user} {$prefix}u
+                       {$joinsql}
+                 WHERE {$wheresql}";
+        return [
+            'sql' => $sql,
+            'params' => $params,
+        ];
+    }
+
+    /**
+     * Returns array with SQL joins and parameters returning all IDs of users enrolled into course.
+     *
+     * Note: This is a temporary method (based on get_enrolled_join from enrollib), supporting multiple enrolment IDs
+     * matched using logical OR. A more complete implementation of other logical operators and supporting more
+     * flexible enrolment statuses will be implemented in MDL-68348.
+     *
+     * This method is using 'ej[0-9]+_' prefix for table names and parameters.
+     *
+     * @throws coding_exception
+     *
+     * @param \context $context
+     * @param string $useridcolumn User id column used the calling query, e.g. u.id
+     * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
+     * @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
+     * @param array $enrolids The enrolment IDs. If not [], only users enrolled using these enrolment methods will be returned.
+     * @return \core\dml\sql_join Contains joins, wheres, params
+     */
+    protected function get_enrolled_join(\context $context, $useridcolumn, $onlyactive = false, $onlysuspended = false,
+            $enrolids = []) {
+
+        global $DB;
+
+        // Use unique prefix just in case somebody makes some SQL magic with the result.
+        static $i = 0;
+        $i++;
+        $prefix = 'ej' . $i . '_';
+
+        if (!is_array($enrolids)) {
+            $enrolids = $enrolids ? [$enrolids] : [];
+        }
+
+        // First find the course context.
+        $coursecontext = $context->get_course_context();
+
+        $isfrontpage = ($coursecontext->instanceid == SITEID);
+
+        if ($onlyactive && $onlysuspended) {
+            throw new \coding_exception("Both onlyactive and onlysuspended are set, this is probably not what you want!");
+        }
+        if ($isfrontpage && $onlysuspended) {
+            throw new \coding_exception("onlysuspended is not supported on frontpage; please add your own early-exit!");
+        }
+
+        $joins  = [];
+        $wheres = [];
+        $params = [];
+
+        $wheres[] = "1 = 1"; // Prevent broken where clauses later on.
+
+        // Note all users are "enrolled" on the frontpage, but for others...
+        if (!$isfrontpage) {
+            $where1 = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
+            $where2 = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0
+                       OR {$prefix}ue.timeend > :{$prefix}now2)";
+
+            $enrolconditions = [
+                "{$prefix}e.id = {$prefix}ue.enrolid",
+                "{$prefix}e.courseid = :{$prefix}courseid",
+            ];
+
+            // TODO: This only handles 'Any' (logical OR) of the provided enrol IDs. MDL-68348 will add 'All' and 'None' support.
+            if (!empty($enrolids)) {
+                list($enrolidssql, $enrolidsparams) = $DB->get_in_or_equal($enrolids, SQL_PARAMS_NAMED, $prefix);
+                $enrolconditions[] = "{$prefix}e.id {$enrolidssql}";
+                $params = array_merge($params, $enrolidsparams);
+            }
+
+            $enrolconditionssql = implode(" AND ", $enrolconditions);
+            $ejoin = "JOIN {enrol} {$prefix}e ON ($enrolconditionssql)";
+
+            $params[$prefix.'courseid'] = $coursecontext->instanceid;
+
+            if (!$onlysuspended) {
+                $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = $useridcolumn";
+                $joins[] = $ejoin;
+                if ($onlyactive) {
+                    $wheres[] = "$where1 AND $where2";
+                }
+            } else {
+                // Suspended only where there is enrolment but ALL are suspended.
+                // Consider multiple enrols where one is not suspended or plain role_assign.
+                $enrolselect = "SELECT DISTINCT {$prefix}ue.userid
+                                           FROM {user_enrolments} {$prefix}ue $ejoin
+                                          WHERE $where1 AND $where2";
+                $joins[] = "JOIN {user_enrolments} {$prefix}ue1 ON {$prefix}ue1.userid = $useridcolumn";
+                $enrolconditions = [
+                    "{$prefix}e1.id = {$prefix}ue1.enrolid",
+                    "{$prefix}e1.courseid = :{$prefix}_e1_courseid",
+                ];
+
+                if (!empty($enrolids)) {
+                    list($enrolidssql, $enrolidsparams) = $DB->get_in_or_equal($enrolids, SQL_PARAMS_NAMED, $prefix);
+                    $enrolconditions[] = "{$prefix}e1.id {$enrolidssql}";
+                    $params = array_merge($params, $enrolidsparams);
+                }
+
+                $enrolconditionssql = implode(" AND ", $enrolconditions);
+                $joins[] = "JOIN {enrol} {$prefix}e1 ON ($enrolconditionssql)";
+                $params["{$prefix}_e1_courseid"] = $coursecontext->instanceid;
+                $wheres[] = "$useridcolumn NOT IN ($enrolselect)";
+            }
+
+            if ($onlyactive || $onlysuspended) {
+                $now = round(time(), -2); // Rounding helps caching in DB.
+                $params = array_merge($params, [
+                        $prefix . 'enabled' => ENROL_INSTANCE_ENABLED,
+                        $prefix . 'active' => ENROL_USER_ACTIVE,
+                        $prefix . 'now1' => $now,
+                        $prefix . 'now2' => $now]);
+            }
+        }
+
+        $joins = implode("\n", $joins);
+        $wheres = implode(" AND ", $wheres);
+
+        return new \core\dml\sql_join($joins, $wheres, $params);
+    }
+
+    /**
+     * Prepare SQL where clause and associated parameters for any roles filtering being performed.
+     *
+     * @return array SQL query data in the format ['where' => '', 'params' => []].
+     */
+    protected function get_roles_sql(): array {
+        global $DB;
+
+        $where = '';
+        $params = [];
+
+        // Limit list to users with some role only.
+        if ($this->filterset->has_filter('roles')) {
+            $roleids = $this->filterset->get_filter('roles')->get_filter_values();
+
+            // We want to query both the current context and parent contexts.
+            $rolecontextids = $this->context->get_parent_context_ids(true);
+
+            // Get users without any role, if needed.
+            if (($withoutkey = array_search(-1, $roleids)) !== false) {
+                list($relatedctxsql1, $relatedctxparams1) = $DB->get_in_or_equal($rolecontextids, SQL_PARAMS_NAMED, 'relatedctx1');
+
+                $where .= "(u.id NOT IN (SELECT userid FROM {role_assignments} WHERE contextid {$relatedctxsql1}))";
+                $params = array_merge($params, $relatedctxparams1);
+                unset($roleids[$withoutkey]);
+
+                if (!empty($roleids)) {
+                    // Currently only handle 'Any' (logical OR) case within filters.
+                    // This will need to be extended to support 'All'/'None'.
+                    $where .= ' OR ';
+                }
+            }
+
+            // Get users with specified roles, if needed.
+            if (!empty($roleids)) {
+                list($relatedctxsql2, $relatedctxparams2) = $DB->get_in_or_equal($rolecontextids, SQL_PARAMS_NAMED, 'relatedctx2');
+                list($roleidssql, $roleidsparams) = $DB->get_in_or_equal($roleids, SQL_PARAMS_NAMED);
+
+                $where .= "(u.id IN (
+                                  SELECT userid
+                                    FROM {role_assignments}
+                                   WHERE roleid {$roleidssql}
+                                     AND contextid {$relatedctxsql2})
+                                )";
+                $params = array_merge($params, $roleidsparams, $relatedctxparams2);
+            }
+        }
+
+        return [
+            'where' => $where,
+            'params' => $params,
+        ];
+    }
+
+    /**
+     * Prepare SQL where clauses and associated parameters for any keyword searches being performed.
+     *
+     * @return array SQL query data in the format ['wheres' => [], 'params' => []].
+     */
+    protected function get_keywords_search_sql(): array {
+        global $CFG, $DB, $USER;
+
+        $keywords = [];
+        $wheres = [];
+        $params = [];
+
+        if ($this->filterset->has_filter('keywords')) {
+            $keywords = $this->filterset->get_filter('keywords')->get_filter_values();
+        }
+
+        foreach ($keywords 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 = [];
+            // 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', $this->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', $this->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, $this->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%";
+        }
+
+        return [
+            'wheres' => $wheres,
+            'params' => $params,
+        ];
+    }
+}