foldershare-8.x-1.2/src/Utilities/UserUtilities.php
src/Utilities/UserUtilities.php
<?php
namespace Drupal\foldershare\Utilities;
use Drupal\Core\Database\Database;
use Drupal\user\Entity\User;
/**
* Defines static utility functions for working with users.
*
* The functions in this class support user queries based upon the user's
* account name, account email address, and display name.
*
* <B>Warning:</B> This class is strictly internal to the FolderShare
* module. The class's existance, name, and content may change from
* release to release without any promise of backwards compatability.
*
* @ingroup foldershare
*/
final class UserUtilities {
/*--------------------------------------------------------------------
*
* Configuration.
*
*-------------------------------------------------------------------*/
/**
* Returns TRUE if the "Real name" contributed module is installed.
*
* @return bool
* Returns TRUE if installed.
*/
public static function isRealnameInstalled() {
return \Drupal::moduleHandler()->moduleExists('realname');
}
/**
* Returns the configured name for the anonymous account.
*
* The anonymous (uid = 0) account is a virtual account for visitors that
* are not logged in. The display name for the account can be set via
* a User module configuration setting.
*
* The configured name is typically the display name for the account,
* but display name hook implementations can override it.
*
* @return string
* The configured name for the anonymous account.
*/
public static function getAnonymousName() {
return \Drupal::config('user.settings')->get('anonymous');
}
/**
* Returns the names of modules implementing the display name hook.
*
* The User::getDisplayName() method invokes the 'user_format_name_alter'
* hook used by modules to override the account name and provide a more
* descriptive name, such as a user's full name. The common "Real name"
* module is one such module, which uses tokens to assemble a name from
* one or more other fields on a User entity.
*
* This method returns a list of modules implementing the hook.
*
* @return string[]
* Returns the machine names of modules implementing the display name
* hook.
*/
public static function getDisplayNameHookModules() {
return \Drupal::moduleHandler()
->getImplementations('user_format_name_alter');
}
/*--------------------------------------------------------------------
*
* Functions.
*
*-------------------------------------------------------------------*/
/**
* Returns the user ID for a given user name, display name, or email address.
*
* Database tables are queried for an exact case insensitive match.
*
* Display name queries are only available if the third-party "Real name"
* module is installed. The module uses tokens to assemble a name from
* site-defined fields, then caches that name in a database table that is
* queried here.
*
* @param string $name
* The account name, display name, or email address of a user.
*
* @return int
* Returns the user ID for the matched user, or (-1) if not found.
*
* @see ::findUserByAccountName()
* @see ::findUserByAccountEmail()
* @see ::findUserByDisplayName()
* @see ::findSimilarUsers()
*/
public static function findUser(string $name) {
$uid = self::findUserByAccountName($name);
if ($uid !== (-1)) {
return $uid;
}
$uid = self::findUserByAccountEmail($name);
if ($uid !== (-1)) {
return $uid;
}
$uid = self::findUserByDisplayName($name, FALSE);
if ($uid !== (-1)) {
return $uid;
}
return (-1);
}
/**
* Returns the user ID for a given user account name.
*
* Database tables are queried for an exact case insensitive match.
*
* Account names are expected to be unique within a site. A database
* table query can therefore return at most one entry.
*
* @param string $name
* The account name of a user.
*
* @return int
* Returns the user ID for the matched user, or (-1) if not found.
*
* @see ::findUser()
* @see ::findUserByAccountEmail()
* @see ::findUserByDisplayName()
* @see ::findSimilarUsers()
*/
public static function findUserByAccountName(string $name) {
// Note: This query could be done by user_load_by_name() in the user
// module. However, that function does a case *sensitive* search and
// it loads the entity. We want a case *insensitive* search and we
// only want the user ID.
$connection = Database::getConnection();
$select = $connection->select('users_field_data', 'u');
$select->addField('u', 'uid', 'uid');
$select->addExpression('LOWER(u.name)', 'unamelower');
$select->condition('unamelower', mb_strtolower($name), '=');
$select->range(0, 1);
$uids = $select->execute()->fetchCol(0);
if (empty($uids) === TRUE) {
return (-1);
}
// Return the first match. There should be only one since account names
// are required to be unique.
return (int) $uids[0];
}
/**
* Returns the user ID for a given user account email address.
*
* Database tables are queried for an exact case insensitive match.
*
* Account email addresses need not be unique within a site. Multiple
* accounts may have the same address. When there are multiple matches,
* this function returns the match with the lowest user ID, which will
* be the earliest account created.
*
* @param string $email
* The account email address of a user.
*
* @return int
* Returns the user ID for the matched user, or (-1) if not found.
*
* @see ::findUser()
* @see ::findUserByAccountName()
* @see ::findUserByDisplayName()
* @see ::findSimilarUsers()
*/
public static function findUserByAccountEmail(string $email) {
// Note: This query could be done by user_load_by_mail() in the user
// module. However, that function does a case *sensitive* search and
// it loads the entity. We want a case *insensitive* search and we
// only want the user ID.
$connection = Database::getConnection();
$select = $connection->select('users_field_data', 'u');
$select->addField('u', 'uid', 'uid');
$select->addExpression('LOWER(u.mail)', 'umaillower');
$select->condition('umaillower', mb_strtolower($email), '=');
$select->orderBy('uid');
$select->range(0, 1);
$uids = $select->execute()->fetchCol(0);
if (empty($uids) === TRUE) {
return (-1);
}
// Return the first match. It is possible for more than one account to
// have the same email address, though this is unlikely. The order-by
// clause above has insured that the first value has the lowest user ID.
return (int) $uids[0];
}
/**
* Returns the user ID for a given user display name.
*
* This method searches for a user entity with a matching display name,
* ignoring case. If no match is found, (-1) is returned.
*
* There are three cases handled:
*
* 1. There are no implementations of the 'user_format_name_alter' hook, and
* therefore the display name is the account name. A special case is
* handled for anonymous, which uses the configured display name.
*
* 2. There is one hook implementation and it is for the common "Real name"
* contributed module. A database search on its tables is then possible.
*
* 3. There are one or more hook implementations and they are not for
* the "Real name" module. A slow search through every User entity is
* required to find the display name.
*
* Cases 1 and 2 are always handled. Case 3 is performed only if
* $searchUsers is TRUE.
*
* @param string $name
* The display name of a user.
* @param bool $searchUsers
* (optional, default = TRUE) When TRUE, the method fails and returns
* (-1) if there are no known shortcuts to getting the display name,
* and the fallback would require loading every User entity, which can
* be slow.
*
* @return int
* Returns the user ID for the matched user, or (-1) if not found.
*
* @see ::findUser()
* @see ::findUserByAccountName()
* @see ::findUserByAccountEmail()
* @see ::findSimilarUsers()
* @see ::isRealnameInstalled()
* @see ::getDisplayNameHookModules()
*/
public static function findUserByDisplayName(
string $name,
bool $searchUsers = FALSE) {
$lowerName = mb_strtolower($name);
$hookModules = self::getDisplayNameHookModules();
if (empty($hookModules) === TRUE) {
// There are no display name hook implementations.
//
// The display name falls back to the account name. Anonymous is an
// exception because it always falls back to the configured name for
// the account.
$anonymousName = mb_strtolower(
\Drupal::config('user.settings')->get('anonymous'));
if ($anonymousName === $lowerName) {
return 0;
}
return self::findUserByAccountName($name);
}
if (count($hookModules) === 1 && $hookModules[0] === 'realname') {
// There is one display name hook implementation and it is the "realname"
// contributed module. This is a common case.
//
// We can search realname's database table of cached computed display
// names. However, the table does not include a name for every user.
// If the fields used by a realname configuration are empty for an
// account, the display name will be empty. Additionally, a value is
// only computed for a user when a display name is needed. Until then,
// there will be no entry in realname's table for the user.
$connection = Database::getConnection();
$select = $connection->select('realname', 'r');
$select->addField('r', 'uid', 'uid');
$select->addField('r', 'realname', 'realname');
$select->addExpression('LOWER(r.realname)', 'rrealnamelower');
$select->condition('rrealnamelower', $lowerName, '=');
$select->orderBy('uid');
$select->range(0, 1);
$qresults = $select->execute()->fetchAll();
if (empty($qresults) === TRUE) {
// There is no entry for the user in realname's table. Check the
// account name.
return self::findUserByAccountName($name);
}
if (empty($qresults[0]->realname) === TRUE) {
// There is an entry for the user, but it is empty. Check the
// account name.
return self::findUserByAccountName($name);
}
return (int) $qresults[0]->uid;
}
// There are either multiple hook implementations, or there is just one
// implementation but it is not the "realname" module. The only way to
// get the display name is by repeated calls to User::getDisplayName().
// This requires loading all User entities, which will be slooooow.
if ($searchUsers === FALSE) {
// No easy way to get the display name, and search disabled.
return (-1);
}
$uids = self::getAllUserIds();
foreach ($uids as $uid) {
$user = User::load($uid);
if ($user !== NULL &&
mb_strtolower($user->getDisplayName()) === $lowerName) {
return $uid;
}
unset($user);
}
return (-1);
}
/**
* Returns a list of user IDs that are similar to a given name.
*
* Database tables are queried for a case insensitive match that includes
* the given text somewhere within the account name, account email, or
* display name.
*
* The user entity's account names are always searched for matches with
* the given name.
*
* If the third-party "Real name" module is installed, its table of
* display names is searched for matches with the given name.
*
* If $matchEmail is TRUE, the user entity's email addresses are searched
* for matches with the given name.
*
* The returned list of user IDs is ordered alphabetically on the
* account name.
*
* @param string $name
* The name or name fragment to search for.
* @param bool $matchEmail
* (optional, default = FALSE) Whether to look for a match against user
* email addresses.
* @param bool $excludeBlocked
* (optional, default = TRUE) Whether to include blocked accounts in the
* returned list.
* @param int[] $excludeUids
* (optional, default = []) A list of user IDs to exclude from the returned
* list.
* @param int $maxReturn
* (optional, default = 10) The maximum number of matches to return. If
* this is <= 0, all matches are returned.
*
* @return int[]
* Returns a list of integer User entity IDs, or an empty list if no
* matches are found. IDs are sorted on the account name.
*
* @see ::findUser()
* @see ::findUserByAccountName()
* @see ::findUserByAccountEmail()
* @see ::findUserByDisplayName()
* @see ::isRealnameInstalled()
*/
public static function findSimilarUsers(
string $name,
bool $matchEmail = FALSE,
bool $excludeBlocked = TRUE,
array $excludeUids = [],
int $maxReturn = 10) {
// Query the user entity's fields to get the user ID and account name
// matches.
$connection = Database::getConnection();
$select = $connection->select('users_field_data', 'u');
$select->addField('u', 'uid', 'uid');
$likeGroup = $select->orConditionGroup();
$likeGroup->condition('u.name', '%' . $name . '%', 'LIKE');
$select->orderBy('u.name', 'ASC');
// Optionally search the email address too.
if ($matchEmail === TRUE) {
$likeGroup->condition('u.mail', '%' . $name . '%', 'LIKE');
}
// If the "Real name" module is enabled, check its fields for display
// name matches.
if (self::isRealnameInstalled() === TRUE) {
$select->leftJoin('realname', 'r', '(r.uid = u.uid)');
$likeGroup->condition('r.realname', '%' . $name . '%', 'LIKE');
}
// Require a match AND optionally exclude blocked users and
// those on an exclude list.
$allowGroup = $select->andConditionGroup();
$allowGroup->condition($likeGroup);
if ($excludeBlocked === TRUE) {
$allowGroup->condition('u.status', 1);
}
if (empty($excludeUids) === FALSE) {
$allowGroup->condition('u.uid', $excludeUids, 'NOT IN');
}
$select = $select->condition($allowGroup);
// Optioinally limit the number of returned results.
if ($maxReturn > 0) {
$select->range(0, $maxReturn);
}
// Get the results and insure they are all integers.
$nums = $select->execute()->fetchCol(0);
$uids = [];
foreach ($nums as $num) {
$uids[] = (int) $num;
}
return $uids;
}
/**
* Returns a list of all user IDs.
*
* @return int[]
* Returns a list of all user IDs.
*
* @see ::getAllAccountNames()
* @see ::getAllDisplayNames()
*/
public static function getAllUserIds() {
$uids = \Drupal::entityTypeManager()
->getStorage('user')
->getQuery()
->execute();
$n = count($uids);
for ($i = 0; $i < $n; ++$i) {
$uids[$i] = (int) $uids[$i];
}
return $uids;
}
/**
* Returns an array of user account names with user ID keys.
*
* The returned list is not sorted.
*
* @return array
* Returns an associative array with user IDs as keys and account
* names as values.
*
* @see ::getAllUserIds()
* @see ::getAllDisplayNames()
*/
public static function getAllAccountNames() {
$connection = Database::getConnection();
$select = $connection->select('users_field_data', 'u');
$select->addField('u', 'uid', 'uid');
$select->addField('u', 'name', 'name');
$select->orderby('name');
$qresults = $select->execute()->fetchAll();
$results = [];
foreach ($qresults as $r) {
$results[$r->uid] = $r->name;
}
return $results;
}
/**
* Returns an array of user display names with user ID keys.
*
* The returned list is not sorted.
*
* @return array
* Returns an associative array with user IDs as keys and display
* names as values.
*
* @see ::getAllUserIds()
* @see ::getAllAccountNames()
* @see ::getDisplayNameHookModules()
*/
public static function getAllDisplayNames() {
$hookModules = self::getDisplayNameHookModules();
if (empty($hookModules) === TRUE) {
// There are no display name hook implementations. All display names
// are just the account name, except for anonymous.
$results = self::getAllAccountNames();
$results[0] = self::getAnonymousName();
return $results;
}
if (count($hookModules) === 1 && $hookModules[0] === 'realname') {
// There is one display name hook implementation and it is the "realname"
// contributed module. This is a common case.
//
// Use realname's table of cached computed display names. However,
// the table does not include a name for every user. If the fields
// used by a realname configuration are empty for an account, the
// display name will be empty. Additionally, a value is only computed
// for a user when a display name is needed. Until then, there will be
// no entry in realname's table for the user.
//
// If there is no display name in the table yet, fall back to the
// account name.
$connection = Database::getConnection();
$select = $connection->select('users_field_data', 'u');
$select->addField('u', 'uid', 'uid');
$select->addField('u', 'name', 'name');
$select->leftJoin('realname', 'r', '(r.uid = u.uid)');
$select->addField('r', 'realname', 'realname');
$select->orderBy('uid');
$qresults = $select->execute()->fetchAll();
$results = [];
foreach ($qresults as $r) {
if (empty($r->realname) === TRUE) {
$results[$r->uid] = $r->name;
}
else {
$results[$r->uid] = $r->realname;
}
}
if (isset($results[0]) === FALSE) {
$results[0] = self::getAnonymousName();
}
return $results;
}
// There are either multiple hook implementations, or there is just one
// implementation but it is not the "realname" module. The only way to
// get the display name is by repeated calls to User::getDisplayName().
// This requires loading all User entities, which will be slooooow.
$uids = self::getAllUserIds();
$results = [];
foreach ($uids as $uid) {
$user = User::load($uid);
if ($user !== NULL) {
$results[$user->id()] = $user->getDisplayName();
}
unset($user);
}
return $results;
}
}
