mc-cms-namelessmc/core/classes/Core/User.php
tadhgboyle ab2fed32a1
Improve online staff/users widget caching (#3691)
* Improve online staff/users widget caching

* lint

* format sql, order by group id

* same for staff widget
2025-07-05 15:36:58 -07:00

1206 lines
36 KiB
PHP

<?php
/**
* Represents a user, logged in or not.
*
* @package NamelessMC\Core
* @author Samerton
* @author Partydragen
* @author Aberdeener
* @version 2.2.0
* @license MIT
*/
class User
{
private static array $_user_cache = [];
private static array $_group_cache = [];
private static array $_integration_cache = [];
private DB $_db;
/**
* @var UserData|null The user's data. Basically just the row from `nl2_users` where the user ID is the key.
*/
private ?UserData $_data;
/**
* @var array<int, Group> The user's groups.
*/
private array $_groups;
/**
* @var IntegrationUser[] The user's integrations.
*/
private array $_integrations;
/**
* @var Group The user's main group.
*/
private Group $_main_group;
/**
* @var array The user's placeholders.
*/
private array $_placeholders;
/**
* @var string The session name configuration value for remembering the user.
*/
private string $_sessionName;
/**
* @var string The cookie name configuration value.
*/
private string $_cookieName;
/**
* @var bool Whether this user is logged in or not.
*/
private bool $_isLoggedIn = false;
/**
* @var string The session name configuration value for remembering the admin user.
*/
private string $_admSessionName;
/**
* @var bool Whether this user is logged in as an admin or not.
*/
private bool $_isAdmLoggedIn = false;
public function __construct(?string $user = null, string $field = 'id')
{
$this->_db = DB::getInstance();
$this->_sessionName = Config::get('session.session_name');
$this->_cookieName = Config::get('remember.cookie_name');
$this->_admSessionName = Config::get('session.admin_name');
if ($user === null) {
if (Session::exists($this->_sessionName)) {
$hash = Session::get($this->_sessionName);
if ($this->find($hash, 'hash')) {
$this->_isLoggedIn = true;
$this->_db->update('users_session', ['hash', $hash], ['last_seen' => date('U')]);
}
}
if (Session::exists($this->_admSessionName)) {
$hash = Session::get($this->_admSessionName);
if ($this->find($hash, 'hash')) {
$this->_isAdmLoggedIn = true;
$this->_db->update('users_session', ['hash', $hash], ['last_seen' => date('U')]);
}
}
} else {
$this->find($user, $field);
}
}
/**
* Find a user by unique identifier (username, ID, email, etc).
* Loads instance variables for this class.
*
* @param string $value Unique identifier.
* @param string $field What column to check for their unique identifier in.
*
* @return bool True/false on success or failure respectfully.
*/
private function find(string $value, string $field = 'id'): bool
{
if (isset(self::$_user_cache["$value.$field"])) {
$this->_data = self::$_user_cache["$value.$field"];
return true;
}
if ($field !== 'hash') {
$data = $this->_db->get('users', [$field, $value]);
} else {
$data = $this->_db->query(
<<<'SQL'
SELECT
nl2_users.*
FROM nl2_users
LEFT JOIN nl2_users_session
ON nl2_users.id = nl2_users_session.user_id
WHERE
nl2_users_session.hash = ?
AND nl2_users_session.active = 1
AND (
nl2_users_session.expires_at IS NULL
OR nl2_users_session.expires_at > ?
)
SQL,
[
$value,
time(),
]
);
}
if ($data->count()) {
$this->_data = new UserData($data->first());
self::$_user_cache["$value.$field"] = $this->_data;
return true;
}
return false;
}
/**
* Add a group to this user.
*
* @param int $group_id ID of group to give.
* @param int $expire Expiry in epoch time. If not supplied, group will never expire.
*
* @return bool True on success, false if they already have it.
*/
public function addGroup(int $group_id, int $expire = 0): bool
{
if (array_key_exists($group_id, $this->getGroups())) {
return false;
}
$group = Group::find($group_id);
if (!$group) {
ErrorHandler::logWarning('Could not add invalid group ' . $group_id . ' to user ' . $this->data()->id);
return false;
}
$this->_db->query('INSERT INTO `nl2_users_groups` (`user_id`, `group_id`, `received`, `expire`) VALUES (?, ?, ?, ?)', [
$this->data()->id,
$group_id,
date('U'),
$expire,
]);
$this->_groups[$group_id] = $group;
self::$_group_cache[$this->data()->id][$group_id] = $group;
EventHandler::executeEvent(new UserGroupAddedEvent(
$this,
$this->_groups[$group_id],
));
return true;
}
/**
* Get the user's data.
*
* @return UserData This user's data.
*/
public function data(): ?UserData
{
return $this->_data ?? null;
}
/**
* Get this user's main group CSS styling.
*
* @return string The CSS styling.
*/
public function getGroupStyle(): string
{
$group = $this->getMainGroup();
$group_username_color = Output::getClean($group->group_username_color);
$group_username_css = Output::getClean($group->group_username_css);
$css = '';
if ($group_username_color) {
$css .= "color: $group_username_color;";
}
if ($group_username_css) {
$css .= $group_username_css;
}
return $css;
}
/**
* Update a user's data in the database.
*
* @param array $fields Column names and values to update.
* @throws Exception
*/
public function update(array $fields = []): void
{
if (!$this->_db->update('users', $this->data()->id, $fields)) {
throw new RuntimeException('There was a problem updating your details.');
}
}
/**
* Create a new user.
*
* @param array $fields Column names and values to insert to database.
*/
public function create(array $fields = []): void
{
if (!$this->_db->insert('users', $fields)) {
throw new RuntimeException('There was a problem creating an account.');
}
}
/**
* Get a user's username from their ID.
*
* @param int $id Their ID.
*
* @return ?string Their username, null on failure.
*/
public function idToName(int $id): ?string
{
$data = $this->_db->get('users', ['id', $id]);
if ($data->count()) {
return $data->first()->username;
}
return null;
}
/**
* Get a user's nickname from their ID.
*
* @param int $id Their ID.
*
* @return ?string Their nickname, null on failure.
*/
public function idToNickname(int $id): ?string
{
$data = $this->_db->get('users', ['id', $id]);
if ($data->count()) {
return $data->first()->nickname;
}
return null;
}
/**
* Log the user in.
*
* @param string|null $username Their username (or email, depending on $method).
* @param string|null $password Their password.
* @param bool $remember Whether to keep them logged in or not.
* @param string $method What column to check for their details in. Can be either `username` or `email` or `oauth`.
*
* @return bool True/false on success or failure respectfully.
*/
public function login(?string $username = null, ?string $password = null, bool $remember = false, string $method = 'email'): bool
{
return $this->_commonLogin($username, $password, $remember, $method, false);
}
private function _commonLogin(?string $username, ?string $password, bool $remember, string $method, bool $is_admin): bool
{
$sessionName = $is_admin ? $this->_admSessionName : $this->_sessionName;
if (!$username && $method == 'hash' && $this->exists()) {
// Logged in using hash from cookie
Session::put($sessionName, $password);
if (!$is_admin) {
$this->_isLoggedIn = true;
}
} elseif ($this->checkCredentials($username, $password, $method) === true) {
// Valid credentials
$hash = SecureRandom::alphanumeric();
$expiresAt = $remember ? time() + Config::get('remember.cookie_expiry') : null;
$this->_db->insert('users_session', [
'user_id' => $this->data()->id,
'hash' => $hash,
'remember_me' => $remember,
'active' => true,
'login_method' => $is_admin ? 'admin' : $method,
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'ip' => HttpUtils::getRemoteAddress(),
'expires_at' => $expiresAt,
]);
Session::put($sessionName, $hash);
if ($remember) {
$cookieName = $is_admin ? ($this->_cookieName . '_adm') : $this->_cookieName;
Cookie::put($cookieName, $hash, $expiresAt, HttpUtils::getProtocol() === 'https', true, false);
}
return true;
}
return false;
}
/**
* Does this user exist?
*
* @return bool Whether the user exists (has data) or not.
*/
public function exists(): bool
{
return !empty($this->_data);
}
/**
* Check whether given credentials are valid.
*
* @param string $username Username (or email) to check.
* @param string $password Password entered by user.
* @param string $method Column to search for user with. Can be `email` or `username` or `oauth`. If it is `oauth`, then the request will be granted.
*
* @return bool True if correct, false otherwise.
*/
public function checkCredentials(string $username, string $password, string $method = 'email'): bool
{
$user = $this->find($username, $method === 'oauth' ? 'id' : $method);
if ($method === 'oauth') {
return true;
}
if ($user) {
switch ($this->data()->pass_method) {
case 'sha256':
[$salt, $pass] = explode('$', $this->data()->password);
return $salt . hash('sha256', hash('sha256', $password) . $salt) == $salt . $pass;
case 'pbkdf2':
[$iterations, $salt, $pass] = explode('$', $this->data()->password);
$hashed = hash_pbkdf2('sha256', $password, $salt, $iterations, 64, true);
return $hashed == hex2bin($pass);
case 'modernbb':
case 'sha1':
return sha1($password) == $this->data()->password;
default:
// Default to bcrypt
return password_verify($password, $this->data()->password);
}
}
return false;
}
/**
* Handle StaffCP logins.
*
* @param string|null $username Their username (or email, depending on $method).
* @param string|null $password Their password.
* @param string $method What column to check for their details in. Can be either `username` or `email`.
*
* @return bool True/false on success or failure respectfully.
*/
public function adminLogin(?string $username = null, ?string $password = null, string $method = 'email'): bool
{
return $this->_commonLogin($username, $password, true, $method, true);
}
/**
* Get user's display name.
*
* @param bool $username If true, will use their username. If false, will use their nickname.
* @return string Their display name.
*/
public function getDisplayname(bool $username = false): string
{
if ($username) {
return Output::getClean($this->data()->username);
}
return Output::getClean($this->data()->nickname);
}
/**
* Build this user's profile link.
*
* @return string Compiled profile URL.
*/
public function getProfileURL(): string
{
return URL::build('/profile/' . urlencode($this->data()->username));
}
/**
* Get all of a user's groups id. Logged out/non-existent users will return just `0`.
*
* @return array Array of all their group IDs.
*/
public function getAllGroupIds(): array
{
if (!$this->exists()) {
return [0 => 0];
}
$groups = [];
foreach ($this->getGroups() as $group) {
$groups[$group->id] = $group->id;
}
return $groups;
}
/**
* Get if this user is currently logged in or not.
*
* @return bool Whether they're logged in.
*/
public function isLoggedIn(): bool
{
return $this->_isLoggedIn;
}
/**
* Get all of a user's group HTML display code.
*
* @return array Array of all their groups HTML.
*/
public function getAllGroupHtml(): array
{
$groups = [];
foreach ($this->getGroups() as $group) {
$groups[] = $group->group_html;
}
return $groups;
}
/**
* Get this user's signature.
*
* @return string Their signature.
*/
public function getSignature(): string
{
return $this->data()->signature ?? '';
}
/**
* Get this user's avatar.
*
* @param int $size Size of image to render in pixels.
* @param bool $full Whether to use full site URL or not, for external loading - ie discord webhooks.
*
* @return string URL to their avatar image.
*/
public function getAvatar(int $size = 128, bool $full = false): string
{
$data_obj = new stdClass();
// Convert UserData object to stdClass so we can dynamically add the 'uuid' property
foreach (get_object_vars($this->data()) as $key => $value) {
$data_obj->{$key} = $value;
}
$integrationUser = $this->getIntegration('Minecraft');
if ($integrationUser != null) {
$data_obj->uuid = $integrationUser->data()->identifier;
} else {
$data_obj->uuid = '';
}
return AvatarSource::getAvatarFromUserData($data_obj, $this->hasPermission('usercp.gif_avatar'), $size, $full);
}
/**
* Does the user have a specific permission in any of their groups?
*
* @param string $permission Permission node to check recursively for.
*
* @return bool Whether they inherit this permission or not.
*/
public function hasPermission(string $permission): bool
{
if (!$this->exists()) {
return false;
}
foreach ($this->getGroups() as $group) {
$permissions = json_decode($group->permissions, true) ?? [];
if (isset($permissions['administrator']) && $permissions['administrator'] == 1) {
return true;
}
if (isset($permissions[$permission]) && $permissions[$permission] == 1) {
return true;
}
}
return false;
}
public function getActiveSessions(): array
{
return DB::getInstance()->query(
'SELECT * FROM nl2_users_session WHERE user_id = ? AND active = 1 ORDER BY last_seen DESC',
[
$this->data()->id,
]
)->results();
}
/**
* Log the user out from a selected session.
*
* @param string $sessionId Selected session ID.
*
* @return void
*/
public function logoutSessionById(string $sessionId): void
{
DB::getInstance()->query('UPDATE nl2_users_session SET `active` = 0 WHERE user_id = ? AND id = ?', [
$this->data()->id,
$sessionId,
]);
}
/**
* Log the user out from all other sessions.
*/
public function logoutAllOtherSessions(): void
{
DB::getInstance()->query('UPDATE nl2_users_session SET `active` = 0 WHERE user_id = ? AND hash != ?', [
$this->data()->id,
Session::get(Config::get('session.session_name')),
]);
}
/**
* Log the user out.
* Deletes their cookies, sessions and database session entry.
*/
public function logout(): void
{
$this->_db->update('users_session', [['user_id', $this->data()->id], ['hash', Session::get($this->_sessionName)]], [
'active' => 0,
]);
Session::delete($this->_sessionName);
Cookie::delete($this->_cookieName);
}
/**
* Process logout if user is admin.
*/
public function admLogout(): void
{
$this->_db->update('users_session', [['user_id', $this->data()->id], ['hash', Session::get($this->_admSessionName)]], [
'active' => 0,
]);
Session::delete($this->_admSessionName);
Cookie::delete($this->_cookieName . '_adm');
}
/**
* Get the user's groups.
*
* @return array<int, Group> Their groups.
*/
public function getGroups(): array
{
if (isset($this->_groups)) {
return $this->_groups;
}
if (isset(self::$_group_cache[$this->data()->id])) {
$this->_groups = self::$_group_cache[$this->data()->id];
} else {
$groups_query = $this->_db->query('SELECT nl2_groups.* FROM nl2_users_groups INNER JOIN nl2_groups ON group_id = nl2_groups.id WHERE user_id = ? AND deleted = 0 ORDER BY `order`', [$this->data()->id]);
if ($groups_query->count()) {
foreach ($groups_query->results() as $item) {
$this->_groups[$item->id] = new Group($item);
}
} else {
$this->_groups = [];
}
self::$_group_cache[$this->data()->id] = $this->_groups;
}
if (!count($this->_groups)) {
// Get default group
$default_group = Group::find(1, 'default_group');
$default_group_id = $default_group->id ?? 1;
$this->addGroup($default_group_id);
}
return $this->_groups;
}
/**
* Get the user's integrations.
*
* @return IntegrationUser[] Their integrations.
*/
public function getIntegrations(): array
{
if (isset($this->_integrations)) {
return $this->_integrations;
}
$integrations = Integrations::getInstance();
if (isset(self::$_integration_cache[$this->data()->id])) {
$integrations_query = self::$_integration_cache[$this->data()->id];
} else {
$integrations_query = $this->_db->query('SELECT nl2_users_integrations.*, nl2_integrations.name as integration_name FROM nl2_users_integrations LEFT JOIN nl2_integrations ON integration_id = nl2_integrations.id WHERE user_id = ?', [$this->data()->id]);
if ($integrations_query->count()) {
$integrations_query = $integrations_query->results();
} else {
$integrations_query = [];
}
self::$_integration_cache[$this->data()->id] = $integrations_query;
}
$integrations_list = [];
foreach ($integrations_query as $item) {
$integration = $integrations->getIntegration($item->integration_name);
if ($integration != null) {
$integrationUser = new IntegrationUser($integration, $this->data()->id, 'user_id', $item);
$integrations_list[$item->integration_name] = $integrationUser;
}
}
return $this->_integrations = $integrations_list;
}
/**
* Get the user's integration.
*
* @param string $integrationName Integration name
*
* @return IntegrationUser|null Their integration user if connected otherwise null.
*/
public function getIntegration(string $integrationName): ?IntegrationUser
{
return $this->getIntegrations()[$integrationName] ?? null;
}
/**
* Get the users placeholders.
*
* @return array Their placeholders.
*/
public function getPlaceholders(): array
{
if (isset($this->_placeholders)) {
return $this->_placeholders;
}
$integrationUser = $this->getIntegration('Minecraft');
if ($integrationUser !== null) {
return $this->_placeholders = Placeholders::getInstance()->loadUserPlaceholders($integrationUser->data()->identifier);
}
return [];
}
/**
* Get this user's placeholders to display on their forum posts.
*
* @return array Forum placeholders.
*/
public function getForumPlaceholders(): array
{
return array_filter($this->getPlaceholders(), static function ($placeholder) {
return $placeholder->show_on_forum;
});
}
/**
* Get this user's placeholders to display on their profile.
*
* @return array Profile placeholders.
*/
public function getProfilePlaceholders(): array
{
return array_filter($this->getPlaceholders(), static function ($placeholder) {
return $placeholder->show_on_profile;
});
}
/**
* Get this user's main group (with highest order).
*
* @return Group The group
*/
public function getMainGroup(): Group
{
return $this->_main_group ??= array_reduce($this->getGroups(), static function (?Group $carry, Group $group) {
return $carry === null || $carry->order > $group->order ? $group : $carry;
});
}
/**
* Set a group to user and remove all other groups.
*
* @param int $group_id ID of group to set as main group.
* @param int $expire Expiry in epoch time. If not supplied, group will never expire.
* @return false|void
*/
public function setGroup(int $group_id, int $expire = 0)
{
$group = Group::find($group_id);
if (!$group) {
ErrorHandler::logWarning('Could not set invalid group ' . $group_id . ' to user ' . $this->data()->id);
return false;
}
if ($this->data()->id == 1) {
return false;
}
$this->_db->query('DELETE FROM `nl2_users_groups` WHERE `user_id` = ?', [$this->data()->id]);
$this->_db->query('INSERT INTO `nl2_users_groups` (`user_id`, `group_id`, `received`, `expire`) VALUES (?, ?, ?, ?)', [
$this->data()->id,
$group_id,
date('U'),
$expire,
]);
$this->_groups = [];
$this->_groups[$group_id] = $group;
self::$_group_cache[$this->data()->id] = $this->_groups;
}
/**
* Remove a group from the user.
*
* @param int|null $group_id ID of group to remove.
*
* @return bool Returns false if they did not have this group or the admin group is being removed from root user
*/
public function removeGroup(?int $group_id): bool
{
if (!array_key_exists($group_id, $this->getGroups())) {
return false;
}
if ($group_id == 2 && $this->data()->id == 1) {
return false;
}
$this->_db->query('DELETE FROM `nl2_users_groups` WHERE `user_id` = ? AND `group_id` = ?', [
$this->data()->id,
$group_id,
]);
EventHandler::executeEvent(new UserGroupRemovedEvent(
$this,
$this->_groups[$group_id],
));
unset($this->_groups[$group_id]);
unset(self::$_group_cache[$this->data()->id][$group_id]);
return true;
}
/**
* Get if this user is active/validated or not.
*
* @return bool Whether this user has been validated/activated.
*/
public function isValidated(): bool
{
return $this->data()->active;
}
/**
* Get a comma separated string of all other users who have not blocked this user.
* For the new private message dropdown.
*
* @return array Array of usernames.
*/
public function listAllOtherUsers(): array
{
$data = $this->_db->query(
'SELECT u.username FROM nl2_users u WHERE u.id <> ? AND u.id NOT IN (SELECT user_id FROM nl2_blocked_users bu WHERE bu.user_blocked_id = ?)',
[$this->data()->id, $this->data()->id]
)->results();
$return = [];
foreach ($data as $item) {
$return[] = $item->username;
}
return $return;
}
/**
* Return an ID from a username.
*
* @param string $username Username to get ID for.
*
* @return ?int ID on success, null on failure.
*/
public function nameToId(string $username): ?int
{
$data = $this->_db->get('users', ['username', $username]);
if ($data->count()) {
return $data->first()->id;
}
return null;
}
/**
* Return an ID from an email.
*
* @param string $email Email to get ID for.
* @return ?int ID on success, false on failure.
*/
public function emailToId(string $email): ?int
{
$data = $this->_db->get('users', ['email', $email]);
if ($data->count()) {
return $data->first()->id;
}
return null;
}
/**
* Get a list of PMs a user has access to.
*
* @param int $user_id ID of user to get PMs for.
* @return array Array of PMs.
*/
public function listPMs(int $user_id): array
{
$return = []; // Array to return containing info of PMs
// Get a list of PMs which the user is in
$data = $this->_db->get('private_messages_users', ['user_id', $user_id]);
if ($data->count()) {
$data = $data->results();
foreach ($data as $result) {
// Get a list of users who are in this conversation and return them as an array
$pms = $this->_db->get('private_messages_users', ['pm_id', $result->pm_id])->results();
$users = []; // Array containing users with permission
foreach ($pms as $pm) {
$users[] = $pm->user_id;
}
// Get the PM data
$pm = $this->_db->get('private_messages', ['id', $result->pm_id])->first();
$return[$pm->id]['id'] = $pm->id;
$return[$pm->id]['title'] = $pm->title;
$return[$pm->id]['created'] = $pm->created;
$return[$pm->id]['updated'] = $pm->last_reply_date;
$return[$pm->id]['user_updated'] = $pm->last_reply_user;
$return[$pm->id]['users'] = $users;
}
}
// Order the PMs by date updated - most recent first
usort($return, static function ($a, $b) {
return $b['updated'] - $a['updated'];
});
return $return;
}
/**
* Get a specific private message, and see if the user actually has permission to view it.
*
* @param int $pm_id ID of PM to find.
* @param int $user_id ID of user to check permission for.
* @return array|null Array of info about PM, null on failure.
*/
public function getPM(int $pm_id, int $user_id): ?array
{
// Get the PM - is the user the author?
$data = $this->_db->get('private_messages', ['id', $pm_id]);
if ($data->count()) {
$data = $data->first();
// Does the user have permission to view the PM?
$pms = $this->_db->get('private_messages_users', ['pm_id', $pm_id])->results();
foreach ($pms as $pm) {
if ($pm->user_id == $user_id) {
$has_permission = true;
$pm_user_id = $pm->id;
break;
}
}
if (!isset($has_permission)) {
return null; // User doesn't have permission
}
// Set message to "read"
if ($pm->read == 0) {
$this->_db->update('private_messages_users', $pm_user_id, [
'read' => true,
]);
}
// User has permission, return the PM information
$users = []; // Array to store users
foreach ($pms as $pm) {
$users[] = $pm->user_id;
}
return [$data, $users];
}
return null;
}
/**
* Check the user's permission to see if they can view this staffCP page or not.
* If they cannot, this will handle appropriate redirection.
*
* @param string|null $permission Permission required for this page.
* @return bool
*/
public function handlePanelPageLoad(?string $permission = null): bool
{
// Set page user is trying to access in session, to allow for redirection post-auth
if (FRIENDLY_URLS === true) {
$split = explode('?', $_SERVER['REQUEST_URI']);
if ($split != null && count($split) > 1) {
$_SESSION['last_page'] = URL::build($split[0], $split[1]);
} else {
$_SESSION['last_page'] = URL::build($split[0]);
}
if (defined('CONFIG_PATH')) {
$_SESSION['last_page'] = substr($_SESSION['last_page'], strlen(CONFIG_PATH));
}
} else {
$_SESSION['last_page'] = URL::build($_GET['route']);
}
if (!$this->isLoggedIn()) {
Redirect::to(URL::build('/login'));
}
if (!$this->canViewStaffCP()) {
Redirect::to(URL::build('/'));
}
if (!$this->isAdmLoggedIn()) {
Redirect::to(URL::build('/panel/auth'));
}
return !($permission != null && !$this->hasPermission($permission));
}
/**
* Can the specified user view StaffCP?
*
* @return bool Whether they can view it or not.
*/
public function canViewStaffCP(): bool
{
foreach ($this->getGroups() as $group) {
if ($group->admin_cp == 1) {
return true;
}
}
return false;
}
/**
* Get if the current user is authenticated as an administrator.
*
* @return bool Whether they're logged in as admin.
*/
public function isAdmLoggedIn(): bool
{
return $this->_isAdmLoggedIn;
}
/**
* Get profile fields for this user.
*
* @param bool $show_private Whether to only return public fields or not (default `true`).
* @param bool $only_forum Whether to only return fields which display on forum posts, only if $public is true (default `false`).
*
* @return array<int, UserProfileField> Array of profile fields.
*/
public function getProfileFields(bool $show_private = false, bool $only_forum = false): array
{
$rows = DB::getInstance()->query('SELECT pf.*, upf.id as upf_id, upf.value, upf.updated FROM nl2_profile_fields pf LEFT JOIN nl2_users_profile_fields upf ON (pf.id = upf.field_id AND upf.user_id = ?)', [
$this->data()->id,
])->results();
$fields = [];
foreach ($rows as $row) {
$field = new UserProfileField($row);
// Check that the field is public, or they are viewing private fields
// also if they're checking forum fields, check that the field is a forum field
// TODO: ideally within the query
if (($field->public || $show_private) && (!$only_forum || $field->forum_posts)) {
$fields[$row->id] = $field;
}
}
return $fields;
}
/**
* Is a user blocked?
*
* @param int $user ID of first user
* @param int $blocked ID of user who may or may not be blocked
*
* @return bool Whether they are blocked or not.
*/
public function isBlocked(int $user, int $blocked): bool
{
return DB::getInstance()->query(
'SELECT 1 FROM nl2_blocked_users WHERE user_id = ? AND user_blocked_id = ?',
[$user, $blocked]
)->exists();
}
/**
* Get this user's profile views.
*
* @return int Numer of profile views they have
*/
public function getProfileViews(): int
{
if ($this->exists()) {
return $this->data()->profile_views;
}
return 0;
}
/**
* Is private profile enabled and does he have the permission to use it?
*
* @return bool Whether profile privatizing is allowed and if they have permission to use it.
*/
public function canPrivateProfile(): bool
{
return Settings::get('private_profile') === '1' && $this->hasPermission('usercp.private_profile');
}
/**
* Can the user bypass private profiles?
*
* @return bool Whether the user can bypass private profiles
*/
public function canBypassPrivateProfile(): bool
{
return Settings::get('private_profile') === '1' && $this->hasPermission('profile.private.bypass');
}
/**
* Is the profile page set to private?
*
* @return bool Whether their profile is set to private or not.
*/
public function isPrivateProfile(): bool
{
return $this->data()->private_profile ?? false;
}
/**
* Get templates this user's group has access to.
*
* @return array Templates which the user has access to.
*/
public function getUserTemplates(): array
{
$groups = '(';
foreach ($this->getGroups() as $group) {
$groups .= ((int) $group->id) . ',';
}
$groups = rtrim($groups, ',') . ')';
return $this->_db->query("SELECT template.id, template.name FROM nl2_templates AS template WHERE template.enabled = 1 AND template.id IN (SELECT template_id FROM nl2_groups_templates WHERE can_use_template = 1 AND group_id IN $groups)")->results();
}
/**
* Save/update this user's placeholders.
*
* @param int $server_id Server ID from staffcp -> integrations to assoc these placeholders with.
* @param array $placeholders Key/value array of placeholders name/value from API endpoint.
*/
public function savePlaceholders(int $server_id, array $placeholders): void
{
$integrationUser = $this->getIntegration('Minecraft');
if ($integrationUser === null || !$integrationUser->getIntegration()->isEnabled()) {
return;
}
$uuid = hex2bin(str_replace('-', '', $integrationUser->data()->identifier));
foreach ($placeholders as $name => $value) {
Placeholders::getInstance()->registerPlaceholder($server_id, $name);
$last_updated = time();
$this->_db->query('INSERT INTO nl2_users_placeholders (server_id, uuid, name, value, last_updated) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = ?, last_updated = ?', [
$server_id,
$uuid,
$name,
$value,
$last_updated,
$value,
$last_updated,
]);
}
}
/**
* Get user's notification preferences.
*
* TODO: return type (PHP 8)
*
* @param string $type Optional type of notification to filter by
*
* @return UserNotificationData|UserNotificationData[]|null
*/
public function getNotificationPreferences(string $type = '')
{
if ($this->exists()) {
$where = 'user_id = ?';
$whereVars = [$this->data()->id];
if (!empty($type)) {
$where .= ' AND `type` = ?';
$whereVars[] = $type;
}
$this->_db->get(
<<<SQL
SELECT `type`, `alert`, `email`
FROM nl2_users_notification_preferences
WHERE $where
SQL,
$whereVars
);
if (!empty($type)) {
return $this->_db->first();
}
return $this->_db->results();
}
return null;
}
}