]> git.mxchange.org Git - friendica.git/blobdiff - src/Model/User.php
Apply suggestions from code review
[friendica.git] / src / Model / User.php
index 574830603e816395364903e16db9c68b9fef18fc..854961154b5dfe56772dc11c70c7d4314a1eaae0 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
  *
@@ -35,17 +35,18 @@ use Friendica\Core\System;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
 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\Network\HTTPClient\Client\HttpClientAccept;
 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;
 
@@ -87,7 +88,7 @@ class User
         * ACCOUNT_TYPE_NEWS - the account is a news reflector
         *      Associated page type: PAGE_FLAGS_SOAPBOX
         *
         * ACCOUNT_TYPE_NEWS - the account is a news reflector
         *      Associated page type: PAGE_FLAGS_SOAPBOX
         *
-        * ACCOUNT_TYPE_COMMUNITY - the account is community forum
+        * ACCOUNT_TYPE_COMMUNITY - the account is community group
         *      Associated page types: PAGE_COMMUNITY, PAGE_FLAGS_PRVGROUP
         *
         * ACCOUNT_TYPE_RELAY - the account is a relay
         *      Associated page types: PAGE_COMMUNITY, PAGE_FLAGS_PRVGROUP
         *
         * ACCOUNT_TYPE_RELAY - the account is a relay
@@ -158,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'] = '';
@@ -165,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'] = (string)DI::baseUrl();
                $system['dob'] = '0000-00-00';
 
                // Ensure that the user contains data
                $system['dob'] = '0000-00-00';
 
                // Ensure that the user contains data
@@ -218,7 +220,7 @@ class User
                        'self'         => true,
                        'network'      => Protocol::ACTIVITYPUB,
                        'name'         => 'System Account',
                        'self'         => true,
                        'network'      => Protocol::ACTIVITYPUB,
                        'name'         => 'System Account',
-                       'addr'         => $system_actor_name . '@' . DI::baseUrl()->getHostname(),
+                       'addr'         => $system_actor_name . '@' . DI::baseUrl()->getHost(),
                        'nick'         => $system_actor_name,
                        'url'          => DI::baseUrl() . '/friendica',
                        'pubkey'       => $keys['pubkey'],
                        'nick'         => $system_actor_name,
                        'url'          => DI::baseUrl() . '/friendica',
                        'pubkey'       => $keys['pubkey'],
@@ -265,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;
@@ -381,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 = []) : array
        {
                if (!empty(DI::config()->get('config', 'admin_nickname'))) {
                        return self::getByNickname(DI::config()->get('config', 'admin_nickname'), $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);
-               } 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] ?? [];
        }
 
        /**
        }
 
        /**
@@ -483,23 +483,41 @@ class User
        }
 
        /**
        }
 
        /**
-        * Returns the default group for a given user and network
+        * Returns the default circle for a given user
         *
         * @param int $uid User id
         *
         *
         * @param int $uid User id
         *
-        * @return int group id
+        * @return int circle id
         * @throws Exception
         */
         * @throws Exception
         */
-       public static function getDefaultGroup(int $uid): int
+       public static function getDefaultCircle(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)) {
-                       $default_group = $user["def_gid"];
+                       $default_circle = $user['def_gid'];
                } else {
                } else {
-                       $default_group = 0;
+                       $default_circle = 0;
                }
 
                }
 
-               return $default_group;
+               return $default_circle;
+       }
+
+       /**
+        * Returns the default circle for groups for a given user
+        *
+        * @param int $uid User id
+        *
+        * @return int circle id
+        * @throws Exception
+        */
+       public static function getDefaultGroupCircle(int $uid): int
+       {
+               $default_circle = DI::pConfig()->get($uid, 'system', 'default-group-gid');
+               if (empty($default_circle)) {
+                       $default_circle = self::getDefaultCircle($uid);
+               }
+
+               return $default_circle;
        }
 
        /**
        }
 
        /**
@@ -529,7 +547,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;
@@ -667,6 +685,32 @@ 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)
+       {
+               if (!$uid) {
+                       return;
+               }
+
+               $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
         *
        /**
         * Generates a human-readable random password
         *
@@ -736,7 +780,7 @@ class User
        }
 
        /**
        }
 
        /**
-        * Allowed characters are a-z, A-Z, 0-9 and special characters except white spaces, accentuated letters and colon (:).
+        * 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
         *
         * 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
@@ -749,13 +793,13 @@ class User
         */
        public static function getPasswordRegExp(string $delimiter = null): string
        {
         */
        public static function getPasswordRegExp(string $delimiter = null): string
        {
-               $allowed_characters = '!"#$%&\'()*+,-./;<=>?@[\]^_`{|}~';
+               $allowed_characters = ':!"#$%&\'()*+,-./;<=>?@[\]^_`{|}~';
 
                if ($delimiter) {
                        $allowed_characters = preg_quote($allowed_characters, $delimiter);
                }
 
 
                if ($delimiter) {
                        $allowed_characters = preg_quote($allowed_characters, $delimiter);
                }
 
-               return '^[a-zA-Z0-9' . $allowed_characters . ']' . (PASSWORD_DEFAULT !== PASSWORD_BCRYPT ? '{1,72}' : '+') . '$';
+               return '^[a-zA-Z0-9' . $allowed_characters . ']' . (PASSWORD_DEFAULT === PASSWORD_BCRYPT ? '{1,72}' : '+') . '$';
        }
 
        /**
        }
 
        /**
@@ -783,7 +827,7 @@ class User
                }
 
                if (!preg_match('/' . self::getPasswordRegExp('/') . '/', $password)) {
                }
 
                if (!preg_match('/' . self::getPasswordRegExp('/') . '/', $password)) {
-                       throw new Exception(DI::l10n()->t('The password can\'t contain accentuated letters, white spaces or colons (:)'));
+                       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));
@@ -794,14 +838,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(int $uid, string $pasword_hashed): bool
+       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
@@ -809,11 +853,27 @@ 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
         * admin panel.
         *
         * @param string $nickname The nickname that should be checked
@@ -986,7 +1046,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'];
@@ -994,7 +1054,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
@@ -1054,11 +1114,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);
@@ -1153,13 +1210,13 @@ class User
                        throw new Exception(DI::l10n()->t('An error occurred creating your self contact. Please try again.'));
                }
 
                        throw new Exception(DI::l10n()->t('An error occurred creating your self contact. Please try again.'));
                }
 
-               // Create a group with no members. This allows somebody to use it
-               // right away as a default group for new contacts.
-               $def_gid = Group::create($uid, DI::l10n()->t('Friends'));
+               // Create a circle with no members. This allows somebody to use it
+               // right away as a default circle for new contacts.
+               $def_gid = Circle::create($uid, DI::l10n()->t('Friends'));
                if (!$def_gid) {
                        DBA::delete('user', ['uid' => $uid]);
 
                if (!$def_gid) {
                        DBA::delete('user', ['uid' => $uid]);
 
-                       throw new Exception(DI::l10n()->t('An error occurred creating your default contact group. Please try again.'));
+                       throw new Exception(DI::l10n()->t('An error occurred creating your default contact circle. Please try again.'));
                }
 
                $fields = ['def_gid' => $def_gid];
                }
 
                $fields = ['def_gid' => $def_gid];
@@ -1169,6 +1226,11 @@ class User
 
                DBA::update('user', $fields, ['uid' => $uid]);
 
 
                DBA::update('user', $fields, ['uid' => $uid]);
 
+               $def_gid_groups = Circle::create($uid, DI::l10n()->t('Groups'));
+               if ($def_gid_groups) {
+                       DI::pConfig()->set($uid, 'system', 'default-group-gid', $def_gid_groups);
+               }
+
                // if we have no OpenID photo try to look up an avatar
                if (!strlen($photo)) {
                        $photo = Network::lookupAvatarByEmail($email);
                // if we have no OpenID photo try to look up an avatar
                if (!strlen($photo)) {
                        $photo = Network::lookupAvatarByEmail($email);
@@ -1197,7 +1259,7 @@ class User
 
                                $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');
 
                                $r = Photo::store($image, $uid, 0, $resource_id, $filename, $profile_album, 4);
                                $profile_album = DI::l10n()->t('Profile Photos');
 
                                $r = Photo::store($image, $uid, 0, $resource_id, $filename, $profile_album, 4);
@@ -1232,6 +1294,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;
        }
@@ -1317,7 +1381,7 @@ class User
 
                if (DBA::isResult($profile) && $profile['net-publish'] && Search::getGlobalDirectory()) {
                        $url = DI::baseUrl() . '/profile/' . $user['nickname'];
 
                if (DBA::isResult($profile) && $profile['net-publish'] && Search::getGlobalDirectory()) {
                        $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']);
@@ -1326,7 +1390,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'
                );
        }
@@ -1340,7 +1404,7 @@ 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
         */
        public static function deny(string $hash): bool
         * @throws Exception
         */
        public static function deny(string $hash): bool
@@ -1418,12 +1482,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()
@@ -1522,7 +1586,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'],
@@ -1567,15 +1631,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;
        }
 
@@ -1602,7 +1667,7 @@ class User
         */
        public static function identities(int $uid): array
        {
         */
        public static function identities(int $uid): array
        {
-               if (empty($uid)) {
+               if (!$uid) {
                        return [];
                }
 
                        return [];
                }
 
@@ -1613,7 +1678,7 @@ class User
                        return $identities;
                }
 
                        return $identities;
                }
 
-               if ($user['parent-uid'] == 0) {
+               if (!$user['parent-uid']) {
                        // First add our own entry
                        $identities = [[
                                'uid' => $user['uid'],
                        // First add our own entry
                        $identities = [[
                                'uid' => $user['uid'],
@@ -1674,7 +1739,7 @@ class User
         */
        public static function hasIdentities(int $uid): bool
        {
         */
        public static function hasIdentities(int $uid): bool
        {
-               if (empty($uid)) {
+               if (!$uid) {
                        return false;
                }
 
                        return false;
                }
 
@@ -1683,7 +1748,7 @@ class User
                        return false;
                }
 
                        return false;
                }
 
-               if ($user['parent-uid'] != 0) {
+               if ($user['parent-uid']) {
                        return true;
                }
 
                        return true;
                }
 
@@ -1714,8 +1779,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)) {
@@ -1729,17 +1794,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']++;
                        }
@@ -1754,7 +1819,7 @@ 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)
         * @return array|bool The list of the users
         * @param string $order Order of the user list (Default is 'contact.name')
         * @param bool   $descending Order direction (Default is ascending)
         * @return array|bool The list of the users
@@ -1783,4 +1848,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'      => null,
+                       'blocked'         => false,
+                       '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]);
+               }
+       }
 }
 }