X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FUser.php;h=2131406d4a5f8cf2be7432c66b57f2839936224c;hb=777919e679b37069615581b4d7f0cb31226c9752;hp=2a954e6e609c6853ca63efe455aaaccb10fdaecb;hpb=b12a2c486e689e2d780f67f7d017e07bb8c67feb;p=friendica.git diff --git a/src/Model/User.php b/src/Model/User.php index 2a954e6e60..93538bce64 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -1,6 +1,6 @@ get(); + $system['dob'] = '0000-00-00'; + + // Ensure that the user contains data + $user = DBA::selectFirst('user', ['prvkey', 'guid'], ['uid' => 0]); + if (empty($user['prvkey']) || empty($user['guid'])) { + $fields = [ + 'username' => $system['name'], + 'nickname' => $system['nick'], + 'register_date' => $system['created'], + 'pubkey' => $system['pubkey'], + 'prvkey' => $system['prvkey'], + 'spubkey' => $system['spubkey'], + 'sprvkey' => $system['sprvkey'], + 'guid' => System::createUUID(), + 'verified' => true, + 'page-flags' => User::PAGE_FLAGS_SOAPBOX, + 'account-type' => User::ACCOUNT_TYPE_RELAY, + ]; + + DBA::update('user', $fields, ['uid' => 0]); + + $system['guid'] = $fields['guid']; + } else { + $system['guid'] = $user['guid']; + } + return $system; } @@ -146,11 +216,12 @@ class User $system['name'] = 'System Account'; $system['addr'] = $system_actor_name . '@' . DI::baseUrl()->getHostname(); $system['nick'] = $system_actor_name; - $system['avatar'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO; - $system['photo'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO; - $system['thumb'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_THUMB; - $system['micro'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_MICRO; $system['url'] = DI::baseUrl() . '/friendica'; + + $system['avatar'] = $system['photo'] = Contact::getDefaultAvatar($system, Proxy::SIZE_SMALL); + $system['thumb'] = Contact::getDefaultAvatar($system, Proxy::SIZE_THUMB); + $system['micro'] = Contact::getDefaultAvatar($system, Proxy::SIZE_MICRO); + $system['nurl'] = Strings::normaliseLink($system['url']); $system['pubkey'] = $keys['pubkey']; $system['prvkey'] = $keys['prvkey']; @@ -163,7 +234,7 @@ class User $system['closeness'] = 0; $system['baseurl'] = DI::baseUrl(); $system['gsid'] = GServer::getID($system['baseurl']); - DBA::insert('contact', $system); + Contact::insert($system); } /** @@ -175,13 +246,21 @@ class User { $system_actor_name = DI::config()->get('system', 'actor_name'); if (!empty($system_actor_name)) { + $self = Contact::selectFirst(['nick'], ['uid' => 0, 'self' => true]); + if (!empty($self['nick'])) { + if ($self['nick'] != $system_actor_name) { + // Reset the actor name to the already used name + DI::config()->set('system', 'actor_name', $self['nick']); + $system_actor_name = $self['nick']; + } + } return $system_actor_name; } // List of possible actor names $possible_accounts = ['friendica', 'actor', 'system', 'internal']; foreach ($possible_accounts as $name) { - if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'expire']) && + if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'expire' => false]) && !DBA::exists('userd', ['username' => $name])) { DI::config()->set('system', 'actor_name', $name); return $name; @@ -210,7 +289,7 @@ class User */ public static function getById($uid, array $fields = []) { - return DBA::selectFirst('user', $fields, ['uid' => $uid]); + return !empty($uid) ? DBA::selectFirst('user', $fields, ['uid' => $uid]) : []; } /** @@ -255,8 +334,8 @@ class User */ public static function getIdForURL(string $url) { - // Avoid any database requests when the hostname isn't even part of the url. - if (!strpos($url, DI::baseUrl()->getHostname())) { + // Avoid database queries when the local node hostname isn't even part of the url. + if (!Contact::isLocal($url)) { return 0; } @@ -314,23 +393,32 @@ class User /** * Get owner data by user id * - * @param int $uid - * @param boolean $check_valid Test if data is invalid and correct it + * @param int $uid + * @param boolean $repairMissing Repair the owner data if it's missing * @return boolean|array * @throws Exception */ - public static function getOwnerDataById(int $uid, bool $check_valid = true) + public static function getOwnerDataById(int $uid, bool $repairMissing = true) { + if ($uid == 0) { + return self::getSystemAccount(); + } + if (!empty(self::$owner[$uid])) { return self::$owner[$uid]; } $owner = DBA::selectFirst('owner-view', [], ['uid' => $uid]); if (!DBA::isResult($owner)) { - if (!DBA::exists('user', ['uid' => $uid]) || !$check_valid) { + if (!DBA::exists('user', ['uid' => $uid]) || !$repairMissing) { return false; } - Contact::createSelfFromUserId($uid); + if (!DBA::exists('profile', ['uid' => $uid])) { + DBA::insert('profile', ['uid' => $uid]); + } + if (!DBA::exists('contact', ['uid' => $uid, 'self' => true])) { + Contact::createSelfFromUserId($uid); + } $owner = self::getOwnerDataById($uid, false); } @@ -338,7 +426,7 @@ class User return false; } - if (!$check_valid) { + if (!$repairMissing || $owner['account_expired']) { return $owner; } @@ -346,12 +434,12 @@ class User // Check for correct url and normalised nurl $url = DI::baseUrl() . '/profile/' . $owner['nickname']; - $repair = ($owner['url'] != $url) || ($owner['nurl'] != Strings::normaliseLink($owner['url'])); + $repair = empty($owner['network']) || ($owner['url'] != $url) || ($owner['nurl'] != Strings::normaliseLink($owner['url'])); if (!$repair) { // Check if "addr" is present and correct $addr = $owner['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3); - $repair = ($addr != $owner['addr']); + $repair = ($addr != $owner['addr']) || empty($owner['prvkey']) || empty($owner['pubkey']); } if (!$repair) { @@ -394,52 +482,22 @@ class User * Returns the default group for a given user and network * * @param int $uid User id - * @param string $network network name * * @return int group id - * @throws InternalServerErrorException + * @throws Exception */ - public static function getDefaultGroup($uid, $network = '') + public static function getDefaultGroup($uid) { - $default_group = 0; - - if ($network == Protocol::OSTATUS) { - $default_group = DI::pConfig()->get($uid, "ostatus", "default_group"); - } - - if ($default_group != 0) { - return $default_group; - } - $user = DBA::selectFirst('user', ['def_gid'], ['uid' => $uid]); - if (DBA::isResult($user)) { $default_group = $user["def_gid"]; + } else { + $default_group = 0; } return $default_group; } - - /** - * Authenticate a user with a clear text password - * - * @param mixed $user_info - * @param string $password - * @param bool $third_party - * @return int|boolean - * @deprecated since version 3.6 - * @see User::getIdFromPasswordAuthentication() - */ - public static function authenticate($user_info, $password, $third_party = false) - { - try { - return self::getIdFromPasswordAuthentication($user_info, $password, $third_party); - } catch (Exception $ex) { - return false; - } - } - /** * Authenticate a user with a clear text password * @@ -449,11 +507,32 @@ class User * @param string $password * @param bool $third_party * @return int User Id if authentication is successful - * @throws Exception + * @throws HTTPException\ForbiddenException + * @throws HTTPException\NotFoundException */ public static function getIdFromPasswordAuthentication($user_info, $password, $third_party = false) { - $user = self::getAuthenticationInfo($user_info); + // Addons registered with the "authenticate" hook may create the user on the + // fly. `getAuthenticationInfo` will fail if the user doesn't exist yet. If + // the user doesn't exist, we should give the addons a chance to create the + // user in our database, if applicable, before re-throwing the exception if + // they fail. + try { + $user = self::getAuthenticationInfo($user_info); + } catch (Exception $e) { + $username = (is_string($user_info) ? $user_info : $user_info['nickname'] ?? ''); + + // Addons can create users, and since this 'catch' branch should only + // execute if getAuthenticationInfo can't find an existing user, that's + // exactly what will happen here. Creating a numeric username would create + // abiguity with user IDs, possibly opening up an attack vector. + // So let's be very careful about that. + if (empty($username) || is_numeric($username)) { + throw $e; + } + + return self::getIdFromAuthenticateHooks($username, $password); + } if ($third_party && DI::pConfig()->get($user['uid'], '2fa', 'verified')) { // Third-party apps can't verify two-factor authentication, we use app-specific passwords instead @@ -482,9 +561,44 @@ class User } return $user['uid']; + } else { + return self::getIdFromAuthenticateHooks($user['nickname'], $password); // throws } - throw new Exception(DI::l10n()->t('Login failed')); + throw new HTTPException\ForbiddenException(DI::l10n()->t('Login failed')); + } + + /** + * Try to obtain a user ID via "authenticate" hook addons + * + * Returns the user id associated with a successful password authentication + * + * @param string $username + * @param string $password + * @return int User Id if authentication is successful + * @throws HTTPException\ForbiddenException + */ + public static function getIdFromAuthenticateHooks($username, $password) + { + $addon_auth = [ + 'username' => $username, + 'password' => $password, + 'authenticated' => 0, + 'user_record' => null + ]; + + /* + * An addon indicates successful login by setting 'authenticated' to non-zero value and returning a user record + * Addons should never set 'authenticated' except to indicate success - as hooks may be chained + * and later addons should not interfere with an earlier one that succeeded. + */ + Hook::callAll('authenticate', $addon_auth); + + if ($addon_auth['authenticated'] && $addon_auth['user_record']) { + return $addon_auth['user_record']['uid']; + } + + throw new HTTPException\ForbiddenException(DI::l10n()->t('Login failed')); } /** @@ -498,9 +612,9 @@ class User * * @param mixed $user_info * @return array - * @throws Exception + * @throws HTTPException\NotFoundException */ - private static function getAuthenticationInfo($user_info) + public static function getAuthenticationInfo($user_info) { $user = null; @@ -522,7 +636,7 @@ class User if (is_int($user_info)) { $user = DBA::selectFirst( 'user', - ['uid', 'password', 'legacy_password'], + ['uid', 'nickname', 'password', 'legacy_password'], [ 'uid' => $user_info, 'blocked' => 0, @@ -532,7 +646,7 @@ class User ] ); } else { - $fields = ['uid', 'password', 'legacy_password']; + $fields = ['uid', 'nickname', 'password', 'legacy_password']; $condition = [ "(`email` = ? OR `username` = ? OR `nickname` = ?) AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified`", @@ -542,7 +656,7 @@ class User } if (!DBA::isResult($user)) { - throw new Exception(DI::l10n()->t('User not found')); + throw new HTTPException\NotFoundException(DI::l10n()->t('User not found')); } } @@ -553,6 +667,7 @@ class User * Generates a human-readable random password * * @return string + * @throws Exception */ public static function generateNewPassword() { @@ -568,16 +683,16 @@ class User */ public static function isPasswordExposed($password) { - $cache = new \DivineOmega\DOFileCachePSR6\CacheItemPool(); + $cache = new CacheItemPool(); $cache->changeConfig([ - 'cacheDirectory' => get_temppath() . '/password-exposed-cache/', + 'cacheDirectory' => System::getTempPath() . '/password-exposed-cache/', ]); try { $passwordExposedChecker = new PasswordExposed\PasswordExposedChecker(null, $cache); return $passwordExposedChecker->passwordExposed($password) === PasswordExposed\PasswordStatus::EXPOSED; - } catch (\Exception $e) { + } catch (Exception $e) { Logger::error('Password Exposed Exception: ' . $e->getMessage(), [ 'code' => $e->getCode(), 'file' => $e->getFile(), @@ -674,13 +789,11 @@ class User * * @param string $nickname The nickname that should be checked * @return boolean True is the nickname is blocked on the node - * @throws InternalServerErrorException */ public static function isNicknameBlocked($nickname) { $forbidden_nicknames = DI::config()->get('system', 'forbidden_nicknames', ''); if (!empty($forbidden_nicknames)) { - // check if the nickname is in the list of blocked nicknames $forbidden = explode(',', $forbidden_nicknames); $forbidden = array_map('trim', $forbidden); } else { @@ -697,6 +810,7 @@ class User return false; } + // check if the nickname is in the list of blocked nicknames if (in_array(strtolower($nickname), $forbidden)) { return true; } @@ -705,6 +819,79 @@ class User return false; } + /** + * Get avatar link for given user + * + * @param array $user + * @param string $size One of the Proxy::SIZE_* constants + * @return string avatar link + * @throws Exception + */ + public static function getAvatarUrl(array $user, string $size = ''):string + { + if (empty($user['nickname'])) { + DI::logger()->warning('Missing user nickname key', ['trace' => System::callstack(20)]); + } + + $url = DI::baseUrl() . '/photo/'; + + switch ($size) { + case Proxy::SIZE_MICRO: + $url .= 'micro/'; + $scale = 6; + break; + case Proxy::SIZE_THUMB: + $url .= 'avatar/'; + $scale = 5; + break; + default: + $url .= 'profile/'; + $scale = 4; + break; + } + + $updated = ''; + $mimetype = ''; + + $photo = Photo::selectFirst(['type', 'created', 'edited', 'updated'], ["scale" => $scale, 'uid' => $user['uid'], 'profile' => true]); + if (!empty($photo)) { + $updated = max($photo['created'], $photo['edited'], $photo['updated']); + $mimetype = $photo['type']; + } + + return $url . $user['nickname'] . Images::getExtensionByMimeType($mimetype) . ($updated ? '?ts=' . strtotime($updated) : ''); + } + + /** + * Get banner link for given user + * + * @param array $user + * @return string banner link + * @throws Exception + */ + public static function getBannerUrl(array $user):string + { + if (empty($user['nickname'])) { + DI::logger()->warning('Missing user nickname key', ['trace' => System::callstack(20)]); + } + + $url = DI::baseUrl() . '/photo/banner/'; + + $updated = ''; + $mimetype = ''; + + $photo = Photo::selectFirst(['type', 'created', 'edited', 'updated'], ["scale" => 3, 'uid' => $user['uid'], 'photo-type' => Photo::USER_BANNER]); + if (!empty($photo)) { + $updated = max($photo['created'], $photo['edited'], $photo['updated']); + $mimetype = $photo['type']; + } else { + // Only for the RC phase: Don't return an image link for the default picture + return ''; + } + + return $url . $user['nickname'] . Images::getExtensionByMimeType($mimetype) . ($updated ? '?ts=' . strtotime($updated) : ''); + } + /** * Catch-all user creation function * @@ -719,9 +906,9 @@ class User * * @param array $data * @return array - * @throws \ErrorException - * @throws InternalServerErrorException - * @throws \ImagickException + * @throws ErrorException + * @throws HTTPException\InternalServerErrorException + * @throws ImagickException * @throws Exception */ public static function create(array $data) @@ -730,18 +917,18 @@ class User $using_invites = DI::config()->get('system', 'invitation_only'); - $invite_id = !empty($data['invite_id']) ? Strings::escapeTags(trim($data['invite_id'])) : ''; - $username = !empty($data['username']) ? Strings::escapeTags(trim($data['username'])) : ''; - $nickname = !empty($data['nickname']) ? Strings::escapeTags(trim($data['nickname'])) : ''; - $email = !empty($data['email']) ? Strings::escapeTags(trim($data['email'])) : ''; - $openid_url = !empty($data['openid_url']) ? Strings::escapeTags(trim($data['openid_url'])) : ''; - $photo = !empty($data['photo']) ? Strings::escapeTags(trim($data['photo'])) : ''; - $password = !empty($data['password']) ? trim($data['password']) : ''; - $password1 = !empty($data['password1']) ? trim($data['password1']) : ''; - $confirm = !empty($data['confirm']) ? trim($data['confirm']) : ''; + $invite_id = !empty($data['invite_id']) ? trim($data['invite_id']) : ''; + $username = !empty($data['username']) ? trim($data['username']) : ''; + $nickname = !empty($data['nickname']) ? trim($data['nickname']) : ''; + $email = !empty($data['email']) ? trim($data['email']) : ''; + $openid_url = !empty($data['openid_url']) ? trim($data['openid_url']) : ''; + $photo = !empty($data['photo']) ? trim($data['photo']) : ''; + $password = !empty($data['password']) ? trim($data['password']) : ''; + $password1 = !empty($data['password1']) ? trim($data['password1']) : ''; + $confirm = !empty($data['confirm']) ? trim($data['confirm']) : ''; $blocked = !empty($data['blocked']); $verified = !empty($data['verified']); - $language = !empty($data['language']) ? Strings::escapeTags(trim($data['language'])) : 'en'; + $language = !empty($data['language']) ? trim($data['language']) : 'en'; $netpublish = $publish = !empty($data['profile_publish_reg']); @@ -798,7 +985,7 @@ class User $username_max_length = max(1, min(64, intval(DI::config()->get('system', 'username_max_length', 48)))); if ($username_min_length > $username_max_length) { - Logger::log(DI::l10n()->t('system.username_min_length (%s) and system.username_max_length (%s) are excluding each other, swapping values.', $username_min_length, $username_max_length), Logger::WARNING); + Logger::error(DI::l10n()->t('system.username_min_length (%s) and system.username_max_length (%s) are excluding each other, swapping values.', $username_min_length, $username_max_length)); $tmp = $username_min_length; $username_min_length = $username_max_length; $username_max_length = $tmp; @@ -847,7 +1034,7 @@ class User $nickname = $data['nickname'] = strtolower($nickname); - if (!preg_match('/^[a-z0-9][a-z0-9\_]*$/', $nickname)) { + if (!preg_match('/^[a-z0-9][a-z0-9_]*$/', $nickname)) { throw new Exception(DI::l10n()->t('Your nickname can only contain a-z, 0-9 and _.')); } @@ -919,8 +1106,8 @@ class User $insert_result = DBA::insert('profile', [ 'uid' => $uid, 'name' => $username, - 'photo' => DI::baseUrl() . "/photo/profile/{$uid}.jpg", - 'thumb' => DI::baseUrl() . "/photo/avatar/{$uid}.jpg", + 'photo' => self::getAvatarUrl($user), + 'thumb' => self::getAvatarUrl($user, Proxy::SIZE_THUMB), 'publish' => $publish, 'net-publish' => $netpublish, ]); @@ -963,7 +1150,7 @@ class User $photo_failure = false; $filename = basename($photo); - $curlResult = DI::httpRequest()->get($photo, true); + $curlResult = DI::httpClient()->get($photo); if ($curlResult->isSuccess()) { $img_str = $curlResult->getBody(); $type = $curlResult->getContentType(); @@ -980,7 +1167,10 @@ class User $resource_id = Photo::newResource(); - $r = Photo::store($Image, $uid, 0, $resource_id, $filename, DI::l10n()->t('Profile Photos'), 4); + // Not using Photo::PROFILE_PHOTOS here, so that it is discovered as translateble string + $profile_album = DI::l10n()->t('Profile Photos'); + + $r = Photo::store($Image, $uid, 0, $resource_id, $filename, $profile_album, 4); if ($r === false) { $photo_failure = true; @@ -988,7 +1178,7 @@ class User $Image->scaleDown(80); - $r = Photo::store($Image, $uid, 0, $resource_id, $filename, DI::l10n()->t('Profile Photos'), 5); + $r = Photo::store($Image, $uid, 0, $resource_id, $filename, $profile_album, 5); if ($r === false) { $photo_failure = true; @@ -996,16 +1186,18 @@ class User $Image->scaleDown(48); - $r = Photo::store($Image, $uid, 0, $resource_id, $filename, DI::l10n()->t('Profile Photos'), 6); + $r = Photo::store($Image, $uid, 0, $resource_id, $filename, $profile_album, 6); if ($r === false) { $photo_failure = true; } if (!$photo_failure) { - Photo::update(['profile' => 1], ['resource-id' => $resource_id]); + Photo::update(['profile' => true, 'photo-type' => Photo::USER_AVATAR], ['resource-id' => $resource_id]); } } + + Contact::updateSelfFromUserID($uid, true); } Hook::callAll('register_account', $uid); @@ -1014,6 +1206,42 @@ class User return $return; } + /** + * Update a user entry and distribute the changes if needed + * + * @param array $fields + * @param integer $uid + * @return boolean + */ + public static function update(array $fields, int $uid): bool + { + $old_owner = self::getOwnerDataById($uid); + if (empty($old_owner)) { + return false; + } + + if (!DBA::update('user', $fields, ['uid' => $uid])) { + return false; + } + + $update = Contact::updateSelfFromUserID($uid); + + $owner = self::getOwnerDataById($uid); + if (empty($owner)) { + return false; + } + + if ($old_owner['name'] != $owner['name']) { + Profile::update(['name' => $owner['name']], $uid); + } + + if ($update) { + Profile::publishUpdate($uid); + } + + return true; + } + /** * Sets block state for a given user * @@ -1036,7 +1264,7 @@ class User * * @return bool True, if the allow was successful * - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException * @throws Exception */ public static function allow(string $hash) @@ -1097,6 +1325,9 @@ class User return false; } + // Delete the avatar + Photo::delete(['uid' => $register['uid']]); + return DBA::delete('user', ['uid' => $register['uid']]) && Register::deleteByHash($register['hash']); } @@ -1110,16 +1341,16 @@ class User * @param string $lang The user's language (default is english) * * @return bool True, if the user was created successfully - * @throws InternalServerErrorException - * @throws \ErrorException - * @throws \ImagickException + * @throws HTTPException\InternalServerErrorException + * @throws ErrorException + * @throws ImagickException */ public static function createMinimal(string $name, string $email, string $nick, string $lang = L10n::DEFAULT) { if (empty($name) || empty($email) || empty($nick)) { - throw new InternalServerErrorException('Invalid arguments.'); + throw new HTTPException\InternalServerErrorException('Invalid arguments.'); } $result = self::create([ @@ -1182,7 +1413,7 @@ class User * @param string $siteurl * @param string $password Plaintext password * @return NULL|boolean from notification() and email() inherited - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public static function sendRegisterPendingEmail($user, $sitename, $siteurl, $password) { @@ -1218,16 +1449,16 @@ class User * * It's here as a function because the mail is sent from different parts * - * @param \Friendica\Core\L10n $l10n The used language - * @param array $user User record array - * @param string $sitename - * @param string $siteurl - * @param string $password Plaintext password + * @param L10n $l10n The used language + * @param array $user User record array + * @param string $sitename + * @param string $siteurl + * @param string $password Plaintext password * * @return NULL|boolean from notification() and email() inherited - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ - public static function sendRegisterOpenEmail(\Friendica\Core\L10n $l10n, $user, $sitename, $siteurl, $password) + public static function sendRegisterOpenEmail(L10n $l10n, $user, $sitename, $siteurl, $password) { $preamble = Strings::deindent($l10n->t( ' @@ -1284,15 +1515,15 @@ class User /** * @param int $uid user to remove * @return bool - * @throws InternalServerErrorException + * @throws HTTPException\InternalServerErrorException */ public static function remove(int $uid) { - if (!$uid) { + if (empty($uid)) { return false; } - Logger::log('Removing user: ' . $uid); + Logger::notice('Removing user', ['user' => $uid]); $user = DBA::selectFirst('user', [], ['uid' => $uid]); @@ -1302,6 +1533,9 @@ class User // unique), so it cannot be re-registered in the future. DBA::insert('userd', ['username' => $user['nickname']]); + // Remove all personal settings, especially connector settings + DBA::delete('pconfig', ['uid' => $uid]); + // The user and related data will be deleted in Friendica\Worker\ExpireAndRemoveUsers DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc('now + 7 day')], ['uid' => $uid]); Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::REMOVAL, $uid); @@ -1339,6 +1573,10 @@ class User */ public static function identities($uid) { + if (empty($uid)) { + return []; + } + $identities = []; $user = DBA::selectFirst('user', ['uid', 'nickname', 'username', 'parent-uid'], ['uid' => $uid]); @@ -1399,6 +1637,38 @@ class User return $identities; } + /** + * Check if the given user id has delegations or is delegated + * + * @param int $uid + * @return bool + */ + public static function hasIdentities(int $uid):bool + { + if (empty($uid)) { + return false; + } + + $user = DBA::selectFirst('user', ['parent-uid'], ['uid' => $uid, 'account_removed' => false]); + if (!DBA::isResult($user)) { + return false; + } + + if ($user['parent-uid'] != 0) { + return true; + } + + if (DBA::exists('user', ['parent-uid' => $uid, 'account_removed' => false])) { + return true; + } + + if (DBA::exists('manage', ['uid' => $uid])) { + return true; + } + + return false; + } + /** * Returns statistical information about the current users of this node * @@ -1472,7 +1742,9 @@ class User $condition['blocked'] = false; break; case 'blocked': + $condition['account_removed'] = false; $condition['blocked'] = true; + $condition['verified'] = true; break; case 'removed': $condition['account_removed'] = true;