]> git.mxchange.org Git - friendica.git/blobdiff - src/Model/User.php
Function renamed
[friendica.git] / src / Model / User.php
index 93538bce641d684ba3601767fdffcc1a67f7288d..11d55e7f7e26529fa507f8ddacd2b5a60124b385 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
 <?php
 /**
- * @copyright Copyright (C) 2010-2022, the Friendica project
+ * @copyright Copyright (C) 2010-2023, the Friendica project
  *
  * @license GNU AGPL version 3 or any later version
  *
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -30,20 +30,23 @@ use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Logger;
 use Friendica\Core\Protocol;
 use Friendica\Core\L10n;
 use Friendica\Core\Logger;
 use Friendica\Core\Protocol;
+use Friendica\Core\Search;
 use Friendica\Core\System;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Core\System;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Module;
+use Friendica\Network\HTTPClient\Client\HttpClientAccept;
 use Friendica\Security\TwoFactor\Model\AppSpecificPassword;
 use Friendica\Network\HTTPException;
 use Friendica\Object\Image;
 use Friendica\Security\TwoFactor\Model\AppSpecificPassword;
 use Friendica\Network\HTTPException;
 use Friendica\Object\Image;
+use Friendica\Protocol\Delivery;
 use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Images;
 use Friendica\Util\Network;
 use Friendica\Util\Proxy;
 use Friendica\Util\Strings;
 use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Images;
 use Friendica\Util\Network;
 use Friendica\Util\Proxy;
 use Friendica\Util\Strings;
-use Friendica\Worker\Delivery;
 use ImagickException;
 use LightOpenID;
 
 use ImagickException;
 use LightOpenID;
 
@@ -115,16 +118,18 @@ class User
                switch ($accounttype) {
                        case 'person':
                                return User::ACCOUNT_TYPE_PERSON;
                switch ($accounttype) {
                        case 'person':
                                return User::ACCOUNT_TYPE_PERSON;
+
                        case 'organisation':
                                return User::ACCOUNT_TYPE_ORGANISATION;
                        case 'organisation':
                                return User::ACCOUNT_TYPE_ORGANISATION;
+
                        case 'news':
                                return User::ACCOUNT_TYPE_NEWS;
                        case 'news':
                                return User::ACCOUNT_TYPE_NEWS;
+
                        case 'community':
                                return User::ACCOUNT_TYPE_COMMUNITY;
                        case 'community':
                                return User::ACCOUNT_TYPE_COMMUNITY;
-                       default:
-                               return null;
-                       break;
+
                }
                }
+               return null;
        }
 
        /**
        }
 
        /**
@@ -132,7 +137,7 @@ class User
         *
         * @return array system account
         */
         *
         * @return array system account
         */
-       public static function getSystemAccount()
+       public static function getSystemAccount(): array
        {
                $system = Contact::selectFirst([], ['self' => true, 'uid' => 0]);
                if (!DBA::isResult($system)) {
        {
                $system = Contact::selectFirst([], ['self' => true, 'uid' => 0]);
                if (!DBA::isResult($system)) {
@@ -154,6 +159,7 @@ class User
                $system['publish'] = false;
                $system['net-publish'] = false;
                $system['hide-friends'] = true;
                $system['publish'] = false;
                $system['net-publish'] = false;
                $system['hide-friends'] = true;
+               $system['hidewall'] = true;
                $system['prv_keywords'] = '';
                $system['pub_keywords'] = '';
                $system['address'] = '';
                $system['prv_keywords'] = '';
                $system['pub_keywords'] = '';
                $system['address'] = '';
@@ -161,7 +167,7 @@ class User
                $system['region'] = '';
                $system['postal-code'] = '';
                $system['country-name'] = '';
                $system['region'] = '';
                $system['postal-code'] = '';
                $system['country-name'] = '';
-               $system['homepage'] = DI::baseUrl()->get();
+               $system['homepage'] = DI::baseUrl();
                $system['dob'] = '0000-00-00';
 
                // Ensure that the user contains data
                $system['dob'] = '0000-00-00';
 
                // Ensure that the user contains data
@@ -208,32 +214,33 @@ class User
                        throw new Exception(DI::l10n()->t('SERIOUS ERROR: Generation of security keys failed.'));
                }
 
                        throw new Exception(DI::l10n()->t('SERIOUS ERROR: Generation of security keys failed.'));
                }
 
-               $system = [];
-               $system['uid'] = 0;
-               $system['created'] = DateTimeFormat::utcNow();
-               $system['self'] = true;
-               $system['network'] = Protocol::ACTIVITYPUB;
-               $system['name'] = 'System Account';
-               $system['addr'] = $system_actor_name . '@' . DI::baseUrl()->getHostname();
-               $system['nick'] = $system_actor_name;
-               $system['url'] = DI::baseUrl() . '/friendica';
+               $system = [
+                       'uid'          => 0,
+                       'created'      => DateTimeFormat::utcNow(),
+                       'self'         => true,
+                       'network'      => Protocol::ACTIVITYPUB,
+                       'name'         => 'System Account',
+                       'addr'         => $system_actor_name . '@' . DI::baseUrl()->getHost(),
+                       'nick'         => $system_actor_name,
+                       'url'          => DI::baseUrl() . '/friendica',
+                       'pubkey'       => $keys['pubkey'],
+                       'prvkey'       => $keys['prvkey'],
+                       'blocked'      => 0,
+                       'pending'      => 0,
+                       'contact-type' => Contact::TYPE_RELAY, // In AP this is translated to 'Application'
+                       'name-date'    => DateTimeFormat::utcNow(),
+                       'uri-date'     => DateTimeFormat::utcNow(),
+                       'avatar-date'  => DateTimeFormat::utcNow(),
+                       'closeness'    => 0,
+                       'baseurl'      => DI::baseUrl(),
+               ];
 
                $system['avatar'] = $system['photo'] = Contact::getDefaultAvatar($system, Proxy::SIZE_SMALL);
 
                $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'];
-               $system['blocked'] = 0;
-               $system['pending'] = 0;
-               $system['contact-type'] = Contact::TYPE_RELAY; // In AP this is translated to 'Application'
-               $system['name-date'] = DateTimeFormat::utcNow();
-               $system['uri-date'] = DateTimeFormat::utcNow();
-               $system['avatar-date'] = DateTimeFormat::utcNow();
-               $system['closeness'] = 0;
-               $system['baseurl'] = DI::baseUrl();
-               $system['gsid'] = GServer::getID($system['baseurl']);
+               $system['thumb']  = Contact::getDefaultAvatar($system, Proxy::SIZE_THUMB);
+               $system['micro']  = Contact::getDefaultAvatar($system, Proxy::SIZE_MICRO);
+               $system['nurl']   = Strings::normaliseLink($system['url']);
+               $system['gsid']   = GServer::getID($system['baseurl']);
+
                Contact::insert($system);
        }
 
                Contact::insert($system);
        }
 
@@ -242,7 +249,7 @@ class User
         *
         * @return string actor account name
         */
         *
         * @return string actor account name
         */
-       public static function getActorName()
+       public static function getActorName(): string
        {
                $system_actor_name = DI::config()->get('system', 'actor_name');
                if (!empty($system_actor_name)) {
        {
                $system_actor_name = DI::config()->get('system', 'actor_name');
                if (!empty($system_actor_name)) {
@@ -260,7 +267,7 @@ class User
                // List of possible actor names
                $possible_accounts = ['friendica', 'actor', 'system', 'internal'];
                foreach ($possible_accounts as $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' => false]) &&
+                       if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'account_expired' => false]) &&
                                !DBA::exists('userd', ['username' => $name])) {
                                DI::config()->set('system', 'actor_name', $name);
                                return $name;
                                !DBA::exists('userd', ['username' => $name])) {
                                DI::config()->set('system', 'actor_name', $name);
                                return $name;
@@ -272,11 +279,12 @@ class User
        /**
         * Returns true if a user record exists with the provided id
         *
        /**
         * Returns true if a user record exists with the provided id
         *
-        * @param  integer $uid
+        * @param  int $uid
+        *
         * @return boolean
         * @throws Exception
         */
         * @return boolean
         * @throws Exception
         */
-       public static function exists($uid)
+       public static function exists(int $uid): bool
        {
                return DBA::exists('user', ['uid' => $uid]);
        }
        {
                return DBA::exists('user', ['uid' => $uid]);
        }
@@ -287,7 +295,7 @@ class User
         * @return array|boolean User record if it exists, false otherwise
         * @throws Exception
         */
         * @return array|boolean User record if it exists, false otherwise
         * @throws Exception
         */
-       public static function getById($uid, array $fields = [])
+       public static function getById(int $uid, array $fields = [])
        {
                return !empty($uid) ? DBA::selectFirst('user', $fields, ['uid' => $uid]) : [];
        }
        {
                return !empty($uid) ? DBA::selectFirst('user', $fields, ['uid' => $uid]) : [];
        }
@@ -319,7 +327,7 @@ class User
         * @return array|boolean User record if it exists, false otherwise
         * @throws Exception
         */
         * @return array|boolean User record if it exists, false otherwise
         * @throws Exception
         */
-       public static function getByNickname($nickname, array $fields = [])
+       public static function getByNickname(string $nickname, array $fields = [])
        {
                return DBA::selectFirst('user', $fields, ['nickname' => $nickname]);
        }
        {
                return DBA::selectFirst('user', $fields, ['nickname' => $nickname]);
        }
@@ -332,7 +340,7 @@ class User
         * @return integer user id
         * @throws Exception
         */
         * @return integer user id
         * @throws Exception
         */
-       public static function getIdForURL(string $url)
+       public static function getIdForURL(string $url): int
        {
                // Avoid database queries when the local node hostname isn't even part of the url.
                if (!Contact::isLocal($url)) {
        {
                // Avoid database queries when the local node hostname isn't even part of the url.
                if (!Contact::isLocal($url)) {
@@ -360,14 +368,12 @@ class User
        /**
         * Get a user based on its email
         *
        /**
         * Get a user based on its email
         *
-        * @param string        $email
-        * @param array          $fields
-        *
+        * @param string $email
+        * @param array  $fields
         * @return array|boolean User record if it exists, false otherwise
         * @return array|boolean User record if it exists, false otherwise
-        *
         * @throws Exception
         */
         * @throws Exception
         */
-       public static function getByEmail($email, array $fields = [])
+       public static function getByEmail(string $email, array $fields = [])
        {
                return DBA::selectFirst('user', $fields, ['email' => $email]);
        }
        {
                return DBA::selectFirst('user', $fields, ['email' => $email]);
        }
@@ -377,17 +383,15 @@ class User
         *
         * @param array $fields
         * @return array user
         *
         * @param array $fields
         * @return array user
+        * @throws Exception
         */
         */
-       public static function getFirstAdmin(array $fields = [])
+       public static function getFirstAdmin(array $fields = []) : array
        {
                if (!empty(DI::config()->get('config', 'admin_nickname'))) {
                        return self::getByNickname(DI::config()->get('config', 'admin_nickname'), $fields);
        {
                if (!empty(DI::config()->get('config', 'admin_nickname'))) {
                        return self::getByNickname(DI::config()->get('config', 'admin_nickname'), $fields);
-               } elseif (!empty(DI::config()->get('config', 'admin_email'))) {
-                       $adminList = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email')));
-                       return self::getByEmail($adminList[0], $fields);
-               } else {
-                       return [];
                }
                }
+
+               return self::getAdminList()[0] ?? [];
        }
 
        /**
        }
 
        /**
@@ -410,7 +414,7 @@ class User
 
                $owner = DBA::selectFirst('owner-view', [], ['uid' => $uid]);
                if (!DBA::isResult($owner)) {
 
                $owner = DBA::selectFirst('owner-view', [], ['uid' => $uid]);
                if (!DBA::isResult($owner)) {
-                       if (!DBA::exists('user', ['uid' => $uid]) || !$repairMissing) {
+                       if (!self::exists($uid) || !$repairMissing) {
                                return false;
                        }
                        if (!DBA::exists('profile', ['uid' => $uid])) {
                                return false;
                        }
                        if (!DBA::exists('profile', ['uid' => $uid])) {
@@ -467,7 +471,7 @@ class User
         * @return boolean|array
         * @throws Exception
         */
         * @return boolean|array
         * @throws Exception
         */
-       public static function getOwnerDataByNick($nick)
+       public static function getOwnerDataByNick(string $nick)
        {
                $user = DBA::selectFirst('user', ['uid'], ['nickname' => $nick]);
 
        {
                $user = DBA::selectFirst('user', ['uid'], ['nickname' => $nick]);
 
@@ -486,7 +490,7 @@ class User
         * @return int group id
         * @throws Exception
         */
         * @return int group id
         * @throws Exception
         */
-       public static function getDefaultGroup($uid)
+       public static function getDefaultGroup(int $uid): int
        {
                $user = DBA::selectFirst('user', ['def_gid'], ['uid' => $uid]);
                if (DBA::isResult($user)) {
        {
                $user = DBA::selectFirst('user', ['def_gid'], ['uid' => $uid]);
                if (DBA::isResult($user)) {
@@ -510,7 +514,7 @@ class User
         * @throws HTTPException\ForbiddenException
         * @throws HTTPException\NotFoundException
         */
         * @throws HTTPException\ForbiddenException
         * @throws HTTPException\NotFoundException
         */
-       public static function getIdFromPasswordAuthentication($user_info, $password, $third_party = false)
+       public static function getIdFromPasswordAuthentication($user_info, string $password, bool $third_party = false): int
        {
                // Addons registered with the "authenticate" hook may create the user on the
                // fly. `getAuthenticationInfo` will fail if the user doesn't exist yet. If
        {
                // Addons registered with the "authenticate" hook may create the user on the
                // fly. `getAuthenticationInfo` will fail if the user doesn't exist yet. If
@@ -525,7 +529,7 @@ class User
                        // 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
                        // 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.
+                       // ambiguity 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;
                        // So let's be very careful about that.
                        if (empty($username) || is_numeric($username)) {
                                throw $e;
@@ -578,7 +582,7 @@ class User
         * @return int User Id if authentication is successful
         * @throws HTTPException\ForbiddenException
         */
         * @return int User Id if authentication is successful
         * @throws HTTPException\ForbiddenException
         */
-       public static function getIdFromAuthenticateHooks($username, $password)
+       public static function getIdFromAuthenticateHooks(string $username, string $password): int
        {
                $addon_auth = [
                        'username'      => $username,
        {
                $addon_auth = [
                        'username'      => $username,
@@ -611,7 +615,7 @@ class User
         * - User array with at least the uid and the hashed password
         *
         * @param mixed $user_info
         * - User array with at least the uid and the hashed password
         *
         * @param mixed $user_info
-        * @return array
+        * @return array|null Null if not found/determined
         * @throws HTTPException\NotFoundException
         */
        public static function getAuthenticationInfo($user_info)
         * @throws HTTPException\NotFoundException
         */
        public static function getAuthenticationInfo($user_info)
@@ -663,13 +667,35 @@ class User
                return $user;
        }
 
                return $user;
        }
 
+       /**
+        * Update the day of the last activity of the given user
+        *
+        * @param integer $uid
+        * @return void
+        */
+       public static function updateLastActivity(int $uid)
+       {
+               $user = User::getById($uid, ['last-activity']);
+               if (empty($user)) {
+                       return;
+               }
+
+               $current_day = DateTimeFormat::utcNow('Y-m-d');
+
+               if ($user['last-activity'] != $current_day) {
+                       User::update(['last-activity' => $current_day], $uid);
+                       // Set the last activity for all identities of the user
+                       DBA::update('user', ['last-activity' => $current_day], ['parent-uid' => $uid, 'account_removed' => false]);
+               }
+       }
+
        /**
         * Generates a human-readable random password
         *
         * @return string
         * @throws Exception
         */
        /**
         * Generates a human-readable random password
         *
         * @return string
         * @throws Exception
         */
-       public static function generateNewPassword()
+       public static function generateNewPassword(): string
        {
                return ucfirst(Strings::getRandomName(8)) . random_int(1000, 9999);
        }
        {
                return ucfirst(Strings::getRandomName(8)) . random_int(1000, 9999);
        }
@@ -681,7 +707,7 @@ class User
         * @return bool
         * @throws Exception
         */
         * @return bool
         * @throws Exception
         */
-       public static function isPasswordExposed($password)
+       public static function isPasswordExposed(string $password): bool
        {
                $cache = new CacheItemPool();
                $cache->changeConfig([
        {
                $cache = new CacheItemPool();
                $cache->changeConfig([
@@ -710,7 +736,7 @@ class User
         * @param string $password
         * @return string
         */
         * @param string $password
         * @return string
         */
-       private static function hashPasswordLegacy($password)
+       private static function hashPasswordLegacy(string $password): string
        {
                return hash('whirlpool', $password);
        }
        {
                return hash('whirlpool', $password);
        }
@@ -722,7 +748,7 @@ class User
         * @return string
         * @throws Exception
         */
         * @return string
         * @throws Exception
         */
-       public static function hashPassword($password)
+       public static function hashPassword(string $password): string
        {
                if (!trim($password)) {
                        throw new Exception(DI::l10n()->t('Password can\'t be empty'));
        {
                if (!trim($password)) {
                        throw new Exception(DI::l10n()->t('Password can\'t be empty'));
@@ -731,6 +757,29 @@ class User
                return password_hash($password, PASSWORD_DEFAULT);
        }
 
                return password_hash($password, PASSWORD_DEFAULT);
        }
 
+       /**
+        * Allowed characters are a-z, A-Z, 0-9 and special characters except white spaces and accentuated letters.
+        *
+        * Password length is limited to 72 characters if the current default password hashing algorithm is Blowfish.
+        * From the manual: "Using the PASSWORD_BCRYPT as the algorithm, will result in the password parameter being
+        * truncated to a maximum length of 72 bytes."
+        *
+        * @see https://www.php.net/manual/en/function.password-hash.php#refsect1-function.password-hash-parameters
+        *
+        * @param string|null $delimiter Whether the regular expression is meant to be wrapper in delimiter characters
+        * @return string
+        */
+       public static function getPasswordRegExp(string $delimiter = null): string
+       {
+               $allowed_characters = ':!"#$%&\'()*+,-./;<=>?@[\]^_`{|}~';
+
+               if ($delimiter) {
+                       $allowed_characters = preg_quote($allowed_characters, $delimiter);
+               }
+
+               return '^[a-zA-Z0-9' . $allowed_characters . ']' . (PASSWORD_DEFAULT === PASSWORD_BCRYPT ? '{1,72}' : '+') . '$';
+       }
+
        /**
         * Updates a user row with a new plaintext password
         *
        /**
         * Updates a user row with a new plaintext password
         *
@@ -739,7 +788,7 @@ class User
         * @return bool
         * @throws Exception
         */
         * @return bool
         * @throws Exception
         */
-       public static function updatePassword($uid, $password)
+       public static function updatePassword(int $uid, string $password): bool
        {
                $password = trim($password);
 
        {
                $password = trim($password);
 
@@ -751,10 +800,12 @@ class User
                        throw new Exception(DI::l10n()->t('The new password has been exposed in a public data dump, please choose another.'));
                }
 
                        throw new Exception(DI::l10n()->t('The new password has been exposed in a public data dump, please choose another.'));
                }
 
-               $allowed_characters = '!"#$%&\'()*+,-./;<=>?@[\]^_`{|}~';
+               if (PASSWORD_DEFAULT === PASSWORD_BCRYPT && strlen($password) > 72) {
+                       throw new Exception(DI::l10n()->t('The password length is limited to 72 characters.'));
+               }
 
 
-               if (!preg_match('/^[a-z0-9' . preg_quote($allowed_characters, '/') . ']+$/i', $password)) {
-                       throw new Exception(DI::l10n()->t('The password can\'t contain accentuated letters, white spaces or colons (:)'));
+               if (!preg_match('/' . self::getPasswordRegExp('/') . '/', $password)) {
+                       throw new Exception(DI::l10n()->t("The password can't contain white spaces nor accentuated letters"));
                }
 
                return self::updatePasswordHashed($uid, self::hashPassword($password));
                }
 
                return self::updatePasswordHashed($uid, self::hashPassword($password));
@@ -765,14 +816,14 @@ class User
         * Empties the password reset token field just in case.
         *
         * @param int    $uid
         * Empties the password reset token field just in case.
         *
         * @param int    $uid
-        * @param string $pasword_hashed
+        * @param string $password_hashed
         * @return bool
         * @throws Exception
         */
         * @return bool
         * @throws Exception
         */
-       private static function updatePasswordHashed($uid, $pasword_hashed)
+       private static function updatePasswordHashed(int $uid, string $password_hashed): bool
        {
                $fields = [
        {
                $fields = [
-                       'password' => $pasword_hashed,
+                       'password' => $password_hashed,
                        'pwdreset' => null,
                        'pwdreset_time' => null,
                        'legacy_password' => false
                        'pwdreset' => null,
                        'pwdreset_time' => null,
                        'legacy_password' => false
@@ -780,17 +831,33 @@ class User
                return DBA::update('user', $fields, ['uid' => $uid]);
        }
 
                return DBA::update('user', $fields, ['uid' => $uid]);
        }
 
+       /**
+        * Returns if the given uid is valid and in the admin list
+        *
+        * @param int $uid
+        *
+        * @return bool
+        * @throws Exception
+        */
+       public static function isSiteAdmin(int $uid): bool
+       {
+               return DBA::exists('user', [
+                       'uid'   => $uid,
+                       'email' => self::getAdminEmailList()
+               ]);
+       }
+
        /**
         * Checks if a nickname is in the list of the forbidden nicknames
         *
         * Check if a nickname is forbidden from registration on the node by the
        /**
         * Checks if a nickname is in the list of the forbidden nicknames
         *
         * Check if a nickname is forbidden from registration on the node by the
-        * admin. Forbidden nicknames (e.g. role namess) can be configured in the
+        * admin. Forbidden nicknames (e.g. role names) can be configured in the
         * admin panel.
         *
         * @param string $nickname The nickname that should be checked
         * @return boolean True is the nickname is blocked on the node
         */
         * admin panel.
         *
         * @param string $nickname The nickname that should be checked
         * @return boolean True is the nickname is blocked on the node
         */
-       public static function isNicknameBlocked($nickname)
+       public static function isNicknameBlocked(string $nickname): bool
        {
                $forbidden_nicknames = DI::config()->get('system', 'forbidden_nicknames', '');
                if (!empty($forbidden_nicknames)) {
        {
                $forbidden_nicknames = DI::config()->get('system', 'forbidden_nicknames', '');
                if (!empty($forbidden_nicknames)) {
@@ -827,7 +894,7 @@ class User
         * @return string avatar link
         * @throws Exception
         */
         * @return string avatar link
         * @throws Exception
         */
-       public static function getAvatarUrl(array $user, string $size = ''):string
+       public static function getAvatarUrl(array $user, string $size = ''): string
        {
                if (empty($user['nickname'])) {
                        DI::logger()->warning('Missing user nickname key', ['trace' => System::callstack(20)]);
        {
                if (empty($user['nickname'])) {
                        DI::logger()->warning('Missing user nickname key', ['trace' => System::callstack(20)]);
@@ -869,7 +936,7 @@ class User
         * @return string banner link
         * @throws Exception
         */
         * @return string banner link
         * @throws Exception
         */
-       public static function getBannerUrl(array $user):string
+       public static function getBannerUrl(array $user): string
        {
                if (empty($user['nickname'])) {
                        DI::logger()->warning('Missing user nickname key', ['trace' => System::callstack(20)]);
        {
                if (empty($user['nickname'])) {
                        DI::logger()->warning('Missing user nickname key', ['trace' => System::callstack(20)]);
@@ -911,7 +978,7 @@ class User
         * @throws ImagickException
         * @throws Exception
         */
         * @throws ImagickException
         * @throws Exception
         */
-       public static function create(array $data)
+       public static function create(array $data): array
        {
                $return = ['user' => null, 'password' => ''];
 
        {
                $return = ['user' => null, 'password' => ''];
 
@@ -957,7 +1024,7 @@ class User
                                $_SESSION['register'] = 1;
                                $_SESSION['openid'] = $openid_url;
 
                                $_SESSION['register'] = 1;
                                $_SESSION['openid'] = $openid_url;
 
-                               $openid = new LightOpenID(DI::baseUrl()->getHostname());
+                               $openid = new LightOpenID(DI::baseUrl()->getHost());
                                $openid->identity = $openid_url;
                                $openid->returnUrl = DI::baseUrl() . '/openid';
                                $openid->required = ['namePerson/friendly', 'contact/email', 'namePerson'];
                                $openid->identity = $openid_url;
                                $openid->returnUrl = DI::baseUrl() . '/openid';
                                $openid->required = ['namePerson/friendly', 'contact/email', 'namePerson'];
@@ -965,7 +1032,7 @@ class User
                                try {
                                        $authurl = $openid->authUrl();
                                } catch (Exception $e) {
                                try {
                                        $authurl = $openid->authUrl();
                                } catch (Exception $e) {
-                                       throw new Exception(DI::l10n()->t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . EOL . EOL . DI::l10n()->t('The error message was:') . $e->getMessage(), 0, $e);
+                                       throw new Exception(DI::l10n()->t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . '<br />' . DI::l10n()->t('The error message was:') . $e->getMessage(), 0, $e);
                                }
                                System::externalRedirect($authurl);
                                // NOTREACHED
                                }
                                System::externalRedirect($authurl);
                                // NOTREACHED
@@ -1025,11 +1092,8 @@ class User
 
                // Disallow somebody creating an account using openid that uses the admin email address,
                // since openid bypasses email verification. We'll allow it if there is not yet an admin account.
 
                // Disallow somebody creating an account using openid that uses the admin email address,
                // since openid bypasses email verification. We'll allow it if there is not yet an admin account.
-               if (DI::config()->get('config', 'admin_email') && strlen($openid_url)) {
-                       $adminlist = explode(',', str_replace(' ', '', strtolower(DI::config()->get('config', 'admin_email'))));
-                       if (in_array(strtolower($email), $adminlist)) {
-                               throw new Exception(DI::l10n()->t('Cannot use that email.'));
-                       }
+               if (strlen($openid_url) && in_array(strtolower($email), self::getAdminEmailList())) {
+                       throw new Exception(DI::l10n()->t('Cannot use that email.'));
                }
 
                $nickname = $data['nickname'] = strtolower($nickname);
                }
 
                $nickname = $data['nickname'] = strtolower($nickname);
@@ -1150,8 +1214,9 @@ class User
                        $photo_failure = false;
 
                        $filename = basename($photo);
                        $photo_failure = false;
 
                        $filename = basename($photo);
-                       $curlResult = DI::httpClient()->get($photo);
+                       $curlResult = DI::httpClient()->get($photo, HttpClientAccept::IMAGE);
                        if ($curlResult->isSuccess()) {
                        if ($curlResult->isSuccess()) {
+                               Logger::debug('Got picture', ['Content-Type' => $curlResult->getHeader('Content-Type'), 'url' => $photo]);
                                $img_str = $curlResult->getBody();
                                $type = $curlResult->getContentType();
                        } else {
                                $img_str = $curlResult->getBody();
                                $type = $curlResult->getContentType();
                        } else {
@@ -1161,32 +1226,32 @@ class User
 
                        $type = Images::getMimeTypeByData($img_str, $photo, $type);
 
 
                        $type = Images::getMimeTypeByData($img_str, $photo, $type);
 
-                       $Image = new Image($img_str, $type);
-                       if ($Image->isValid()) {
-                               $Image->scaleToSquare(300);
+                       $image = new Image($img_str, $type);
+                       if ($image->isValid()) {
+                               $image->scaleToSquare(300);
 
                                $resource_id = Photo::newResource();
 
 
                                $resource_id = Photo::newResource();
 
-                               // Not using Photo::PROFILE_PHOTOS here, so that it is discovered as translateble string
+                               // Not using Photo::PROFILE_PHOTOS here, so that it is discovered as translatable string
                                $profile_album = DI::l10n()->t('Profile Photos');
 
                                $profile_album = DI::l10n()->t('Profile Photos');
 
-                               $r = Photo::store($Image, $uid, 0, $resource_id, $filename, $profile_album, 4);
+                               $r = Photo::store($image, $uid, 0, $resource_id, $filename, $profile_album, 4);
 
                                if ($r === false) {
                                        $photo_failure = true;
                                }
 
 
                                if ($r === false) {
                                        $photo_failure = true;
                                }
 
-                               $Image->scaleDown(80);
+                               $image->scaleDown(80);
 
 
-                               $r = Photo::store($Image, $uid, 0, $resource_id, $filename, $profile_album, 5);
+                               $r = Photo::store($image, $uid, 0, $resource_id, $filename, $profile_album, 5);
 
                                if ($r === false) {
                                        $photo_failure = true;
                                }
 
 
                                if ($r === false) {
                                        $photo_failure = true;
                                }
 
-                               $Image->scaleDown(48);
+                               $image->scaleDown(48);
 
 
-                               $r = Photo::store($Image, $uid, 0, $resource_id, $filename, $profile_album, 6);
+                               $r = Photo::store($image, $uid, 0, $resource_id, $filename, $profile_album, 6);
 
                                if ($r === false) {
                                        $photo_failure = true;
 
                                if ($r === false) {
                                        $photo_failure = true;
@@ -1202,6 +1267,8 @@ class User
 
                Hook::callAll('register_account', $uid);
 
 
                Hook::callAll('register_account', $uid);
 
+               self::setRegisterMethodByUserCount();
+
                $return['user'] = $user;
                return $return;
        }
                $return['user'] = $user;
                return $return;
        }
@@ -1252,7 +1319,7 @@ class User
 
         * @throws Exception
         */
 
         * @throws Exception
         */
-       public static function block(int $uid, bool $block = true)
+       public static function block(int $uid, bool $block = true): bool
        {
                return DBA::update('user', ['blocked' => $block], ['uid' => $uid]);
        }
        {
                return DBA::update('user', ['blocked' => $block], ['uid' => $uid]);
        }
@@ -1267,7 +1334,7 @@ class User
         * @throws HTTPException\InternalServerErrorException
         * @throws Exception
         */
         * @throws HTTPException\InternalServerErrorException
         * @throws Exception
         */
-       public static function allow(string $hash)
+       public static function allow(string $hash): bool
        {
                $register = Register::getByHash($hash);
                if (!DBA::isResult($register)) {
        {
                $register = Register::getByHash($hash);
                if (!DBA::isResult($register)) {
@@ -1285,9 +1352,9 @@ class User
 
                $profile = DBA::selectFirst('profile', ['net-publish'], ['uid' => $register['uid']]);
 
 
                $profile = DBA::selectFirst('profile', ['net-publish'], ['uid' => $register['uid']]);
 
-               if (DBA::isResult($profile) && $profile['net-publish'] && DI::config()->get('system', 'directory')) {
+               if (DBA::isResult($profile) && $profile['net-publish'] && Search::getGlobalDirectory()) {
                        $url = DI::baseUrl() . '/profile/' . $user['nickname'];
                        $url = DI::baseUrl() . '/profile/' . $user['nickname'];
-                       Worker::add(PRIORITY_LOW, "Directory", $url);
+                       Worker::add(Worker::PRIORITY_LOW, "Directory", $url);
                }
 
                $l10n = DI::l10n()->withLang($register['language']);
                }
 
                $l10n = DI::l10n()->withLang($register['language']);
@@ -1296,7 +1363,7 @@ class User
                        $l10n,
                        $user,
                        DI::config()->get('config', 'sitename'),
                        $l10n,
                        $user,
                        DI::config()->get('config', 'sitename'),
-                       DI::baseUrl()->get(),
+                       DI::baseUrl(),
                        ($register['password'] ?? '') ?: 'Sent in a previous email'
                );
        }
                        ($register['password'] ?? '') ?: 'Sent in a previous email'
                );
        }
@@ -1310,10 +1377,10 @@ class User
         * permanently against re-registration, as the person was not yet
         * allowed to have friends on this system
         *
         * permanently against re-registration, as the person was not yet
         * allowed to have friends on this system
         *
-        * @return bool True, if the deny was successfull
+        * @return bool True, if the deny was successful
         * @throws Exception
         */
         * @throws Exception
         */
-       public static function deny(string $hash)
+       public static function deny(string $hash): bool
        {
                $register = Register::getByHash($hash);
                if (!DBA::isResult($register)) {
        {
                $register = Register::getByHash($hash);
                if (!DBA::isResult($register)) {
@@ -1339,13 +1406,12 @@ class User
         * @param string $email The user's email address
         * @param string $nick  The user's nick name
         * @param string $lang  The user's language (default is english)
         * @param string $email The user's email address
         * @param string $nick  The user's nick name
         * @param string $lang  The user's language (default is english)
-        *
         * @return bool True, if the user was created successfully
         * @throws HTTPException\InternalServerErrorException
         * @throws ErrorException
         * @throws ImagickException
         */
         * @return bool True, if the user was created successfully
         * @throws HTTPException\InternalServerErrorException
         * @throws ErrorException
         * @throws ImagickException
         */
-       public static function createMinimal(string $name, string $email, string $nick, string $lang = L10n::DEFAULT)
+       public static function createMinimal(string $name, string $email, string $nick, string $lang = L10n::DEFAULT): bool
        {
                if (empty($name) ||
                    empty($email) ||
        {
                if (empty($name) ||
                    empty($email) ||
@@ -1389,12 +1455,12 @@ class User
                If you are new and do not know anybody here, they may help
                you to make some new and interesting friends.
 
                If you are new and do not know anybody here, they may help
                you to make some new and interesting friends.
 
-               If you ever want to delete your account, you can do so at %1$s/removeme
+               If you ever want to delete your account, you can do so at %1$s/settings/removeme
 
                Thank you and welcome to %4$s.'));
 
                $preamble = sprintf($preamble, $user['username'], DI::config()->get('config', 'sitename'));
 
                Thank you and welcome to %4$s.'));
 
                $preamble = sprintf($preamble, $user['username'], DI::config()->get('config', 'sitename'));
-               $body = sprintf($body, DI::baseUrl()->get(), $user['nickname'], $result['password'], DI::config()->get('config', 'sitename'));
+               $body = sprintf($body, DI::baseUrl(), $user['nickname'], $result['password'], DI::config()->get('config', 'sitename'));
 
                $email = DI::emailer()
                        ->newSystemMail()
 
                $email = DI::emailer()
                        ->newSystemMail()
@@ -1415,7 +1481,7 @@ class User
         * @return NULL|boolean from notification() and email() inherited
         * @throws HTTPException\InternalServerErrorException
         */
         * @return NULL|boolean from notification() and email() inherited
         * @throws HTTPException\InternalServerErrorException
         */
-       public static function sendRegisterPendingEmail($user, $sitename, $siteurl, $password)
+       public static function sendRegisterPendingEmail(array $user, string $sitename, string $siteurl, string $password)
        {
                $body = Strings::deindent(DI::l10n()->t(
                        '
        {
                $body = Strings::deindent(DI::l10n()->t(
                        '
@@ -1458,7 +1524,7 @@ class User
         * @return NULL|boolean from notification() and email() inherited
         * @throws HTTPException\InternalServerErrorException
         */
         * @return NULL|boolean from notification() and email() inherited
         * @throws HTTPException\InternalServerErrorException
         */
-       public static function sendRegisterOpenEmail(L10n $l10n, $user, $sitename, $siteurl, $password)
+       public static function sendRegisterOpenEmail(L10n $l10n, array $user, string $sitename, string $siteurl, string $password)
        {
                $preamble = Strings::deindent($l10n->t(
                        '
        {
                $preamble = Strings::deindent($l10n->t(
                        '
@@ -1493,7 +1559,7 @@ class User
                        If you are new and do not know anybody here, they may help
                        you to make some new and interesting friends.
 
                        If you are new and do not know anybody here, they may help
                        you to make some new and interesting friends.
 
-                       If you ever want to delete your account, you can do so at %3$s/removeme
+                       If you ever want to delete your account, you can do so at %3$s/settings/removeme
 
                        Thank you and welcome to %2$s.',
                        $user['nickname'],
 
                        Thank you and welcome to %2$s.',
                        $user['nickname'],
@@ -1517,7 +1583,7 @@ class User
         * @return bool
         * @throws HTTPException\InternalServerErrorException
         */
         * @return bool
         * @throws HTTPException\InternalServerErrorException
         */
-       public static function remove(int $uid)
+       public static function remove(int $uid): bool
        {
                if (empty($uid)) {
                        return false;
        {
                if (empty($uid)) {
                        return false;
@@ -1538,15 +1604,16 @@ class User
 
                // 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]);
 
                // 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);
+               Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::REMOVAL, $uid);
 
                // Send an update to the directory
                $self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
 
                // Send an update to the directory
                $self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
-               Worker::add(PRIORITY_LOW, 'Directory', $self['url']);
+               Worker::add(Worker::PRIORITY_LOW, 'Directory', $self['url']);
 
                // Remove the user relevant data
 
                // Remove the user relevant data
-               Worker::add(PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid);
+               Worker::add(Worker::PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid);
 
 
+               self::setRegisterMethodByUserCount();
                return true;
        }
 
                return true;
        }
 
@@ -1571,7 +1638,7 @@ class User
         *    ]
         * @throws Exception
         */
         *    ]
         * @throws Exception
         */
-       public static function identities($uid)
+       public static function identities(int $uid): array
        {
                if (empty($uid)) {
                        return [];
        {
                if (empty($uid)) {
                        return [];
@@ -1643,7 +1710,7 @@ class User
         * @param int $uid
         * @return bool
         */
         * @param int $uid
         * @return bool
         */
-       public static function hasIdentities(int $uid):bool
+       public static function hasIdentities(int $uid): bool
        {
                if (empty($uid)) {
                        return false;
        {
                if (empty($uid)) {
                        return false;
@@ -1676,7 +1743,7 @@ class User
         *
         * @throws Exception
         */
         *
         * @throws Exception
         */
-       public static function getStatistics()
+       public static function getStatistics(): array
        {
                $statistics = [
                        'total_users'           => 0,
        {
                $statistics = [
                        'total_users'           => 0,
@@ -1685,8 +1752,8 @@ class User
                        'active_users_weekly'   => 0,
                ];
 
                        'active_users_weekly'   => 0,
                ];
 
-               $userStmt = DBA::select('owner-view', ['uid', 'login_date', 'last-item'],
-                       ["`verified` AND `login_date` > ? AND NOT `blocked`
+               $userStmt = DBA::select('owner-view', ['uid', 'last-activity', 'last-item'],
+                       ["`verified` AND `last-activity` > ? AND NOT `blocked`
                        AND NOT `account_removed` AND NOT `account_expired`",
                        DBA::NULL_DATETIME]);
                if (!DBA::isResult($userStmt)) {
                        AND NOT `account_removed` AND NOT `account_expired`",
                        DBA::NULL_DATETIME]);
                if (!DBA::isResult($userStmt)) {
@@ -1700,17 +1767,17 @@ class User
                while ($user = DBA::fetch($userStmt)) {
                        $statistics['total_users']++;
 
                while ($user = DBA::fetch($userStmt)) {
                        $statistics['total_users']++;
 
-                       if ((strtotime($user['login_date']) > $halfyear) || (strtotime($user['last-item']) > $halfyear)
+                       if ((strtotime($user['last-activity']) > $halfyear) || (strtotime($user['last-item']) > $halfyear)
                        ) {
                                $statistics['active_users_halfyear']++;
                        }
 
                        ) {
                                $statistics['active_users_halfyear']++;
                        }
 
-                       if ((strtotime($user['login_date']) > $month) || (strtotime($user['last-item']) > $month)
+                       if ((strtotime($user['last-activity']) > $month) || (strtotime($user['last-item']) > $month)
                        ) {
                                $statistics['active_users_monthly']++;
                        }
 
                        ) {
                                $statistics['active_users_monthly']++;
                        }
 
-                       if ((strtotime($user['login_date']) > $week) || (strtotime($user['last-item']) > $week)
+                       if ((strtotime($user['last-activity']) > $week) || (strtotime($user['last-item']) > $week)
                        ) {
                                $statistics['active_users_weekly']++;
                        }
                        ) {
                                $statistics['active_users_weekly']++;
                        }
@@ -1725,14 +1792,13 @@ class User
         *
         * @param int    $start Start count (Default is 0)
         * @param int    $count Count of the items per page (Default is @see Pager::ITEMS_PER_PAGE)
         *
         * @param int    $start Start count (Default is 0)
         * @param int    $count Count of the items per page (Default is @see Pager::ITEMS_PER_PAGE)
-        * @param string $type  The type of users, which should get (all, bocked, removed)
+        * @param string $type  The type of users, which should get (all, blocked, removed)
         * @param string $order Order of the user list (Default is 'contact.name')
         * @param bool   $descending Order direction (Default is ascending)
         * @param string $order Order of the user list (Default is 'contact.name')
         * @param bool   $descending Order direction (Default is ascending)
-        *
-        * @return array The list of the users
+        * @return array|bool The list of the users
         * @throws Exception
         */
         * @throws Exception
         */
-       public static function getList($start = 0, $count = Pager::ITEMS_PER_PAGE, $type = 'all', $order = 'name', bool $descending = false)
+       public static function getList(int $start = 0, int $count = Pager::ITEMS_PER_PAGE, string $type = 'all', string $order = 'name', bool $descending = false)
        {
                $param = ['limit' => [$start, $count], 'order' => [$order => $descending]];
                $condition = [];
        {
                $param = ['limit' => [$start, $count], 'order' => [$order => $descending]];
                $condition = [];
@@ -1741,11 +1807,13 @@ class User
                                $condition['account_removed'] = false;
                                $condition['blocked'] = false;
                                break;
                                $condition['account_removed'] = false;
                                $condition['blocked'] = false;
                                break;
+
                        case 'blocked':
                                $condition['account_removed'] = false;
                                $condition['blocked'] = true;
                                $condition['verified'] = true;
                                break;
                        case 'blocked':
                                $condition['account_removed'] = false;
                                $condition['blocked'] = true;
                                $condition['verified'] = true;
                                break;
+
                        case 'removed':
                                $condition['account_removed'] = true;
                                break;
                        case 'removed':
                                $condition['account_removed'] = true;
                                break;
@@ -1753,4 +1821,89 @@ class User
 
                return DBA::selectToArray('owner-view', [], $condition, $param);
        }
 
                return DBA::selectToArray('owner-view', [], $condition, $param);
        }
+
+       /**
+        * Returns a list of lowercase admin email addresses from the comma-separated list in the config
+        *
+        * @return array
+        */
+       public static function getAdminEmailList(): array
+       {
+               $adminEmails = strtolower(str_replace(' ', '', DI::config()->get('config', 'admin_email')));
+               if (!$adminEmails) {
+                       return [];
+               }
+
+               return explode(',', $adminEmails);
+       }
+
+       /**
+        * Returns the complete list of admin user accounts
+        *
+        * @param array $fields
+        * @return array
+        * @throws Exception
+        */
+       public static function getAdminList(array $fields = []): array
+       {
+               $condition = [
+                       'email'           => self::getAdminEmailList(),
+                       'parent-uid'      => 0,
+                       'blocked'         => 0,
+                       'verified'        => true,
+                       'account_removed' => false,
+                       'account_expired' => false,
+               ];
+
+               return DBA::selectToArray('user', $fields, $condition, ['order' => ['uid']]);
+       }
+
+       /**
+        * Return a list of admin user accounts where each unique email address appears only once.
+        *
+        * This method is meant for admin notifications that do not need to be sent multiple times to the same email address.
+        *
+        * @param array $fields
+        * @return array
+        * @throws Exception
+        */
+       public static function getAdminListForEmailing(array $fields = []): array
+       {
+               return array_filter(self::getAdminList($fields), function ($user) {
+                       static $emails = [];
+
+                       if (in_array($user['email'], $emails)) {
+                               return false;
+                       }
+
+                       $emails[] = $user['email'];
+
+                       return true;
+               });
+       }
+
+       public static function setRegisterMethodByUserCount()
+       {
+               $max_registered_users = DI::config()->get('config', 'max_registered_users');
+               if ($max_registered_users <= 0) {
+                       return;
+               }
+
+               $register_policy = DI::config()->get('config', 'register_policy');
+               if (!in_array($register_policy, [Module\Register::OPEN, Module\Register::CLOSED])) {
+                       Logger::debug('Unsupported register policy.', ['policy' => $register_policy]);
+                       return;
+               }
+
+               $users = DBA::count('user', ['blocked' => false, 'account_removed' => false, 'account_expired' => false]);
+               if (($users >= $max_registered_users) && ($register_policy == Module\Register::OPEN)) {
+                       DI::config()->set('config', 'register_policy', Module\Register::CLOSED);
+                       Logger::notice('Max users reached, registration is closed.', ['users' => $users, 'max' => $max_registered_users]);
+               } elseif (($users < $max_registered_users) && ($register_policy == Module\Register::CLOSED)) {
+                       DI::config()->set('config', 'register_policy', Module\Register::OPEN);
+                       Logger::notice('Below maximum users, registration is opened.', ['users' => $users, 'max' => $max_registered_users]);
+               } else {
+                       Logger::debug('Unchanged register policy', ['policy' => $register_policy, 'users' => $users, 'max' => $max_registered_users]);
+               }
+       }
 }
 }