MDL-26171 user: implement method for retrieving fullname via SQL.

This commit is contained in:
Paul Holden 2021-06-28 20:40:28 +01:00
parent a747fd3055
commit 4098af1c52
5 changed files with 148 additions and 1 deletions

View File

@ -16,6 +16,8 @@
namespace core_user;
use core_text;
/**
* Class for retrieving information about user fields that are needed for displaying user identity.
*
@ -571,6 +573,65 @@ class fields {
'mappings' => $mappings];
}
/**
* Similar to {@see \moodle_database::sql_fullname} except it returns all user name fields as defined by site config, in a
* single select statement suitable for inclusion in a query/filter for a users fullname, e.g.
*
* [$select, $params] = fields::get_sql_fullname('u');
* $users = $DB->get_records_sql_menu("SELECT u.id, {$select} FROM {user} u", $params);
*
* @param string|null $tablealias User table alias, if set elsewhere in the query, null if not required
* @param bool $override If true then the alternativefullnameformat format rather than fullnamedisplay format will be used
* @return array SQL select snippet and parameters
*/
public static function get_sql_fullname(?string $tablealias = 'u', bool $override = false): array {
global $DB;
$unique = self::$uniqueidentifier++;
$namefields = self::get_name_fields();
// Create a dummy user object containing all name fields.
$dummyuser = (object) array_combine($namefields, $namefields);
$dummyfullname = fullname($dummyuser, $override);
// Extract any name fields from the fullname format in the order that they appear.
$matchednames = array_values(order_in_string($namefields, $dummyfullname));
$namelookup = $namepattern = $elements = $params = [];
foreach ($namefields as $index => $namefield) {
$namefieldwithalias = $tablealias ? "{$tablealias}.{$namefield}" : $namefield;
// Coalesce the name fields to ensure we don't return null.
$emptyparam = "uf{$unique}ep_{$index}";
$namelookup[$namefield] = "COALESCE({$namefieldwithalias}, :{$emptyparam})";
$params[$emptyparam] = '';
$namepattern[] = '\b' . preg_quote($namefield) . '\b';
}
// Grab any content between the name fields, inserting them after each name field.
$chunks = preg_split('/(' . implode('|', $namepattern) . ')/', $dummyfullname);
foreach ($chunks as $index => $chunk) {
if ($index > 0) {
$elements[] = $namelookup[$matchednames[$index - 1]];
}
if (core_text::strlen($chunk) > 0) {
// If content is just whitespace, add to elements directly (also Oracle doesn't support passing ' ' as param).
if (preg_match('/^\s+$/', $chunk)) {
$elements[] = "'$chunk'";
} else {
$elementparam = "uf{$unique}fp_{$index}";
$elements[] = ":{$elementparam}";
$params[$elementparam] = $chunk;
}
}
}
return [$DB->sql_concat(...$elements), $params];
}
/**
* Gets the display name of a given user field.
*

View File

@ -960,6 +960,8 @@ class participants_search {
$keywords = $keywordsfilter->get_filter_values();
}
$canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
foreach ($keywords as $index => $keyword) {
$searchkey1 = 'search' . $index . '1';
$searchkey2 = 'search' . $index . '2';
@ -970,9 +972,11 @@ class participants_search {
$searchkey7 = 'search' . $index . '7';
$conditions = [];
// Search by fullname.
$fullname = $DB->sql_fullname('u.firstname', 'u.lastname');
[$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);

View File

@ -521,4 +521,77 @@ class fields_test extends \advanced_testcase {
$selects = $fields->get_sql()->selects;
$this->assertEquals(', id, city', $selects);
}
/**
* Data provider for {@see test_get_sql_fullname}
*
* @return array
*/
public function get_sql_fullname_provider(): array {
return [
['firstname lastname', 'FN LN'],
['lastname, firstname', 'LN, FN'],
['alternatename \'middlename\' lastname!', 'AN \'MN\' LN!'],
['[firstname lastname alternatename]', '[FN LN AN]'],
['firstnamephonetic lastnamephonetic', 'FNP LNP'],
['firstname alternatename lastname', 'FN AN LN'],
];
}
/**
* Test sql_fullname_display method with various fullname formats
*
* @param string $fullnamedisplay
* @param string $expectedfullname
*
* @dataProvider get_sql_fullname_provider
*/
public function test_get_sql_fullname(string $fullnamedisplay, string $expectedfullname): void {
global $DB;
$this->resetAfterTest();
set_config('fullnamedisplay', $fullnamedisplay);
$user = $this->getDataGenerator()->create_user([
'firstname' => 'FN',
'lastname' => 'LN',
'firstnamephonetic' => 'FNP',
'lastnamephonetic' => 'LNP',
'middlename' => 'MN',
'alternatename' => 'AN',
]);
[$sqlfullname, $params] = fields::get_sql_fullname('u');
$fullname = $DB->get_field_sql("SELECT {$sqlfullname} FROM {user} u WHERE u.id = :id", $params + [
'id' => $user->id,
]);
$this->assertEquals($expectedfullname, $fullname);
}
/**
* Test sql_fullname_display when one of the configured name fields is null
*/
public function test_get_sql_fullname_null_field(): void {
global $DB;
$this->resetAfterTest();
set_config('fullnamedisplay', 'firstname lastname alternatename');
$user = $this->getDataGenerator()->create_user([
'firstname' => 'FN',
'lastname' => 'LN',
]);
// Set alternatename field to null, ensure we still get result in later assertion.
$user->alternatename = null;
user_update_user($user, false);
[$sqlfullname, $params] = fields::get_sql_fullname('u');
$fullname = $DB->get_field_sql("SELECT {$sqlfullname} FROM {user} u WHERE u.id = :id", $params + [
'id' => $user->id,
]);
$this->assertEquals('FN LN ', $fullname);
}
}

View File

@ -1073,6 +1073,14 @@ class participants_search_test extends advanced_testcase {
'tony.rogers',
],
],
'ANY: Filter on fullname only' => (object) [
'keywords' => ['Barbara Bennett'],
'jointype' => filter::JOINTYPE_ANY,
'count' => 1,
'expectedusers' => [
'barbara.bennett',
],
],
'ANY: Filter on middlename only' => (object) [
'keywords' => ['Jeff'],
'jointype' => filter::JOINTYPE_ANY,

View File

@ -6,6 +6,7 @@ This files describes API changes for code that uses the user API.
update failed all users in the operation would fail.
* External function core_user_external::update_users() now returns an error code and message to why a user update
action failed.
* New method `core_user\fields::get_sql_fullname` for retrieving user fullname format in SQL statement
=== 3.11 ===