]> git.mxchange.org Git - friendica.git/blobdiff - src/Model/User.php
Fix uddate issues and improve speed when displaying contact posts
[friendica.git] / src / Model / User.php
index 5b749ec1dc7b8613a393f0c27be94245c0198336..a1d3bc65f851faf8d0a8e2a1da878a46095dd05c 100644 (file)
@@ -35,12 +35,12 @@ use Friendica\Core\System;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
-use Friendica\Module\Register;
+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\Protocol\Delivery;
+use Friendica\Security\TwoFactor\Model\AppSpecificPassword;
 use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Images;
@@ -88,7 +88,7 @@ class User
         * 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
@@ -127,11 +127,21 @@ class User
 
                        case 'community':
                                return User::ACCOUNT_TYPE_COMMUNITY;
-
                }
                return null;
        }
 
+       /**
+        * Get the Uri-Id of the system account
+        *
+        * @return integer
+        */
+       public static function getSystemUriId(): int
+       {
+               $system = self::getSystemAccount();
+               return $system['uri-id'] ?? 0;
+       }
+
        /**
         * Fetch the system account
         *
@@ -167,11 +177,11 @@ class User
                $system['region'] = '';
                $system['postal-code'] = '';
                $system['country-name'] = '';
-               $system['homepage'] = DI::baseUrl();
+               $system['homepage'] = (string)DI::baseUrl();
                $system['dob'] = '0000-00-00';
 
                // Ensure that the user contains data
-               $user = DBA::selectFirst('user', ['prvkey', 'guid'], ['uid' => 0]);
+               $user = DBA::selectFirst('user', ['prvkey', 'guid', 'language'], ['uid' => 0]);
                if (empty($user['prvkey']) || empty($user['guid'])) {
                        $fields = [
                                'username' => $system['name'],
@@ -191,7 +201,8 @@ class User
 
                        $system['guid'] = $fields['guid'];
                } else {
-                       $system['guid'] = $user['guid'];
+                       $system['guid']     = $user['guid'];
+                       $system['language'] = $user['language'];
                }
 
                return $system;
@@ -267,8 +278,7 @@ class User
                // 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, 'account_expired' => false]) &&
-                               !DBA::exists('userd', ['username' => $name])) {
+                       if (!DBA::exists('user', ['nickname' => $name]) && !DBA::exists('userd', ['username' => $name])) {
                                DI::config()->set('system', 'actor_name', $name);
                                return $name;
                        }
@@ -313,7 +323,7 @@ class User
        public static function getByGuid(string $guid, array $fields = [], bool $active = true)
        {
                if ($active) {
-                       $cond = ['guid' => $guid, 'account_expired' => false, 'account_removed' => false];
+                       $cond = ['guid' => $guid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
                } else {
                        $cond = ['guid' => $guid];
                }
@@ -332,6 +342,35 @@ class User
                return DBA::selectFirst('user', $fields, ['nickname' => $nickname]);
        }
 
+       /**
+        * Set static settings for community user accounts
+        *
+        * @param integer $uid
+        * @return void
+        */
+       public static function setCommunityUserSettings(int $uid)
+       {
+               $user = self::getById($uid, ['account-type', 'page-flags']);
+               if ($user['account-type'] != User::ACCOUNT_TYPE_COMMUNITY) {
+                       return;
+               }
+
+               DI::pConfig()->set($uid, 'system', 'unlisted', true);
+
+               $fields = [
+                       'allow_cid'  => '',
+                       'allow_gid'  => $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP ? '<' . Circle::FOLLOWERS . '>' : '',
+                       'deny_cid'   => '',
+                       'deny_gid'   => '',
+                       'blockwall'  => true,
+                       'blocktags'  => true,
+               ];
+
+               User::update($fields, $uid);
+
+               Profile::update(['hide-friends' => true], $uid);
+       }
+
        /**
         * Returns the user id of a given profile URL
         *
@@ -385,7 +424,7 @@ class User
         * @return array user
         * @throws Exception
         */
-       public static function getFirstAdmin(array $fields = []) : array
+       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);
@@ -483,23 +522,125 @@ 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
         *
-        * @return int group id
+        * @return int circle id
         * @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)) {
-                       $default_group = $user["def_gid"];
+                       $default_circle = $user['def_gid'];
                } else {
-                       $default_group = 0;
+                       $default_circle = 0;
+               }
+
+               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;
+       }
+
+       /**
+        * Fetch the language code from the given user. If the code is invalid, return the system language
+        *
+        * @param integer $uid User-Id
+        * @return string
+        */
+       public static function getLanguageCode(int $uid): string
+       {
+               $owner    = self::getOwnerDataById($uid);
+               $language = DI::l10n()->toISO6391($owner['language']);
+               if (in_array($language, array_keys(DI::l10n()->getLanguageCodes()))) {
+                       return $language;
+               }
+               return DI::l10n()->toISO6391(DI::config()->get('system', 'language'));
+       }
+
+       /**
+        * Fetch the wanted languages for a given user
+        *
+        * @param integer $uid
+        * @return array
+        */
+       public static function getWantedLanguages(int $uid): array
+       {
+               return DI::pConfig()->get($uid, 'channel', 'languages', [User::getLanguageCode($uid)]) ?? [];
+       }
+
+       /**
+        * Get a list of all languages that are used by the users
+        *
+        * @return array
+        */
+       public static function getLanguages(): array
+       {
+               $cachekey  = 'user:getLanguages';
+               $languages = DI::cache()->get($cachekey);
+               if (!is_null($languages)) {
+                       return $languages;
+               }
+
+               $supported = array_keys(DI::l10n()->getLanguageCodes());
+               $languages = [];
+               $uids      = [];
+
+               $condition = ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `uid` > ?", 0];
+
+               $abandon_days = intval(DI::config()->get('system', 'account_abandon_days'));
+               if (!empty($abandon_days)) {
+                       $condition = DBA::mergeConditions($condition, ["`last-activity` > ?", DateTimeFormat::utc('now - ' . $abandon_days . ' days')]);
+               }
+
+               $users = DBA::select('user', ['uid', 'language'], $condition);
+               while ($user = DBA::fetch($users)) {
+                       $uids[] = $user['uid'];
+                       $code = DI::l10n()->toISO6391($user['language']);
+                       if (!in_array($code, $supported)) {
+                               continue;
+                       }
+                       $languages[$code] = $code;
+               }
+               DBA::close($users);
+
+               $channels = DBA::select('pconfig', ['uid', 'v'], ["`cat` = ? AND `k` = ? AND `v` != ?", 'channel', 'languages', '']);
+               while ($channel = DBA::fetch($channels)) {
+                       if (!in_array($channel['uid'], $uids)) {
+                               continue;
+                       }
+                       $values = unserialize($channel['v']);
+                       if (!empty($values) && is_array($values)) {
+                               foreach ($values as $language) {
+                                       $language = DI::l10n()->toISO6391($language);
+                                       $languages[$language] = $language;
+                               }
+                       }
                }
+               DBA::close($channels);
 
-               return $default_group;
+               ksort($languages);
+               $languages = array_keys($languages);
+               DI::cache()->set($cachekey, $languages);
+
+               return $languages;
        }
 
        /**
@@ -653,7 +794,7 @@ class User
                                $fields = ['uid', 'nickname', 'password', 'legacy_password'];
                                $condition = [
                                        "(`email` = ? OR `username` = ? OR `nickname` = ?)
-                                       AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified`",
+                                       AND `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`",
                                        $user_info, $user_info, $user_info
                                ];
                                $user = DBA::selectFirst('user', $fields, $condition);
@@ -675,6 +816,10 @@ class User
         */
        public static function updateLastActivity(int $uid)
        {
+               if (!$uid) {
+                       return;
+               }
+
                $user = User::getById($uid, ['last-activity']);
                if (empty($user)) {
                        return;
@@ -685,7 +830,7 @@ class User
                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]);
+                       DBA::update('user', ['last-activity' => $current_day], ['parent-uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
                }
        }
 
@@ -816,14 +961,14 @@ class User
         * Empties the password reset token field just in case.
         *
         * @param int    $uid
-        * @param string $pasword_hashed
+        * @param string $password_hashed
         * @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 = [
-                       'password' => $pasword_hashed,
+                       'password' => $password_hashed,
                        'pwdreset' => null,
                        'pwdreset_time' => null,
                        'legacy_password' => false
@@ -847,6 +992,20 @@ class User
                ]);
        }
 
+       /**
+        * Returns if the given uid is valid and a moderator
+        *
+        * @param int $uid
+        *
+        * @return bool
+        * @throws Exception
+        */
+       public static function isModerator(int $uid): bool
+       {
+               // @todo Replace with a moderator check in the future
+               return self::isSiteAdmin($uid);
+       }
+
        /**
         * Checks if a nickname is in the list of the forbidden nicknames
         *
@@ -897,7 +1056,7 @@ class User
        public static function getAvatarUrl(array $user, string $size = ''): string
        {
                if (empty($user['nickname'])) {
-                       DI::logger()->warning('Missing user nickname key', ['trace' => System::callstack(20)]);
+                       DI::logger()->warning('Missing user nickname key');
                }
 
                $url = DI::baseUrl() . '/photo/';
@@ -939,7 +1098,7 @@ class User
        public static function getBannerUrl(array $user): string
        {
                if (empty($user['nickname'])) {
-                       DI::logger()->warning('Missing user nickname key', ['trace' => System::callstack(20)]);
+                       DI::logger()->warning('Missing user nickname key');
                }
 
                $url = DI::baseUrl() . '/photo/banner/';
@@ -1066,7 +1225,7 @@ class User
                        throw new Exception(DI::l10n()->tt('Username should be at most %s character.', 'Username should be at most %s characters.', $username_max_length));
                }
 
-               // So now we are just looking for a space in the full name.
+               // So now we are just looking for a space in the display name.
                $loose_reg = DI::config()->get('system', 'no_regfullname');
                if (!$loose_reg) {
                        $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8');
@@ -1188,13 +1347,13 @@ class User
                        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]);
 
-                       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];
@@ -1204,6 +1363,11 @@ class User
 
                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);
@@ -1276,33 +1440,18 @@ class User
        /**
         * Update a user entry and distribute the changes if needed
         *
-        * @param array $fields
+        * @param array   $fields
         * @param integer $uid
         * @return boolean
+        * @throws Exception
         */
        public static function update(array $fields, int $uid): bool
        {
-               $old_owner = self::getOwnerDataById($uid);
-               if (empty($old_owner)) {
-                       return false;
-               }
-
                if (!DBA::update('user', $fields, ['uid' => $uid])) {
                        return false;
                }
 
-               $update = Contact::updateSelfFromUserID($uid);
-
-               $owner = self::getOwnerDataById($uid);
-               if (empty($owner)) {
-                       return false;
-               }
-
-               if ($old_owner['name'] != $owner['name']) {
-                       Profile::update(['name' => $owner['name']], $uid);
-               }
-
-               if ($update) {
+               if (Contact::updateSelfFromUserID($uid)) {
                        Profile::publishUpdate($uid);
                }
 
@@ -1396,7 +1545,7 @@ class User
                Photo::delete(['uid' => $register['uid']]);
 
                return DBA::delete('user', ['uid' => $register['uid']]) &&
-                      Register::deleteByHash($register['hash']);
+                       Register::deleteByHash($register['hash']);
        }
 
        /**
@@ -1446,10 +1595,9 @@ class User
                You may also wish to add some basic information to your default profile
                (on the "Profiles" page) so that other people can easily find you.
 
-               We recommend setting your full name, adding a profile photo,
-               adding some profile "keywords" (very useful in making new friends) - and
-               perhaps what country you live in; if you do not wish to be more specific
-               than that.
+               We recommend adding a profile photo, adding some profile "keywords" 
+               (very useful in making new friends) - and perhaps what country you live in; 
+               if you do not wish to be more specific than that.
 
                We fully respect your right to privacy, and none of these items are necessary.
                If you are new and do not know anybody here, they may help
@@ -1550,10 +1698,9 @@ class User
                        You may also wish to add some basic information to your default profile
                        ' . "\x28" . 'on the "Profiles" page' . "\x29" . ' so that other people can easily find you.
 
-                       We recommend setting your full name, adding a profile photo,
-                       adding some profile "keywords" ' . "\x28" . 'very useful in making new friends' . "\x29" . ' - and
-                       perhaps what country you live in; if you do not wish to be more specific
-                       than that.
+                       We recommend adding a profile photo, adding some profile "keywords" ' . "\x28" . 'very useful
+                       in making new friends' . "\x29" . ' - and perhaps what country you live in; if you do not wish
+                       to be more specific than that.
 
                        We fully respect your right to privacy, and none of these items are necessary.
                        If you are new and do not know anybody here, they may help
@@ -1582,16 +1729,24 @@ class User
         * @param int $uid user to remove
         * @return bool
         * @throws HTTPException\InternalServerErrorException
+        * @throws HTTPException\NotFoundException
         */
        public static function remove(int $uid): bool
        {
                if (empty($uid)) {
-                       return false;
+                       throw new \InvalidArgumentException('uid needs to be greater than 0');
                }
 
                Logger::notice('Removing user', ['user' => $uid]);
 
-               $user = DBA::selectFirst('user', [], ['uid' => $uid]);
+               $user = self::getById($uid);
+               if (!$user) {
+                       throw new HTTPException\NotFoundException('User not found with uid: ' . $uid);
+               }
+
+               if (DBA::exists('user', ['parent-uid' => $uid])) {
+                       throw new \RuntimeException(DI::l10n()->t("User with delegates can't be removed, please remove delegate users first"));
+               }
 
                Hook::callAll('remove_user', $user);
 
@@ -1640,18 +1795,18 @@ class User
         */
        public static function identities(int $uid): array
        {
-               if (empty($uid)) {
+               if (!$uid) {
                        return [];
                }
 
                $identities = [];
 
-               $user = DBA::selectFirst('user', ['uid', 'nickname', 'username', 'parent-uid'], ['uid' => $uid]);
+               $user = DBA::selectFirst('user', ['uid', 'nickname', 'username', 'parent-uid'], ['uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
                if (!DBA::isResult($user)) {
                        return $identities;
                }
 
-               if ($user['parent-uid'] == 0) {
+               if (!$user['parent-uid']) {
                        // First add our own entry
                        $identities = [[
                                'uid' => $user['uid'],
@@ -1663,7 +1818,7 @@ class User
                        $r = DBA::select(
                                'user',
                                ['uid', 'username', 'nickname'],
-                               ['parent-uid' => $user['uid'], 'account_removed' => false]
+                               ['parent-uid' => $user['uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]
                        );
                        if (DBA::isResult($r)) {
                                $identities = array_merge($identities, DBA::toArray($r));
@@ -1673,7 +1828,7 @@ class User
                        $r = DBA::select(
                                'user',
                                ['uid', 'username', 'nickname'],
-                               ['uid' => $user['parent-uid'], 'account_removed' => false]
+                               ['uid' => $user['parent-uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]
                        );
                        if (DBA::isResult($r)) {
                                $identities = DBA::toArray($r);
@@ -1683,7 +1838,7 @@ class User
                        $r = DBA::select(
                                'user',
                                ['uid', 'username', 'nickname'],
-                               ['parent-uid' => $user['parent-uid'], 'account_removed' => false]
+                               ['parent-uid' => $user['parent-uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]
                        );
                        if (DBA::isResult($r)) {
                                $identities = array_merge($identities, DBA::toArray($r));
@@ -1694,7 +1849,7 @@ class User
                        "SELECT `user`.`uid`, `user`.`username`, `user`.`nickname`
                        FROM `manage`
                        INNER JOIN `user` ON `manage`.`mid` = `user`.`uid`
-                       WHERE `user`.`account_removed` = 0 AND `manage`.`uid` = ?",
+                       WHERE NOT `user`.`account_removed` AND `manage`.`uid` = ?",
                        $user['uid']
                );
                if (DBA::isResult($r)) {
@@ -1712,20 +1867,20 @@ class User
         */
        public static function hasIdentities(int $uid): bool
        {
-               if (empty($uid)) {
+               if (!$uid) {
                        return false;
                }
 
-               $user = DBA::selectFirst('user', ['parent-uid'], ['uid' => $uid, 'account_removed' => false]);
+               $user = DBA::selectFirst('user', ['parent-uid'], ['uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
                if (!DBA::isResult($user)) {
                        return false;
                }
 
-               if ($user['parent-uid'] != 0) {
+               if ($user['parent-uid']) {
                        return true;
                }
 
-               if (DBA::exists('user', ['parent-uid' => $uid, 'account_removed' => false])) {
+               if (DBA::exists('user', ['parent-uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false])) {
                        return true;
                }
 
@@ -1848,8 +2003,8 @@ class User
        {
                $condition = [
                        'email'           => self::getAdminEmailList(),
-                       'parent-uid'      => 0,
-                       'blocked'         => 0,
+                       'parent-uid'      => null,
+                       'blocked'         => false,
                        'verified'        => true,
                        'account_removed' => false,
                        'account_expired' => false,
@@ -1890,17 +2045,17 @@ class User
                }
 
                $register_policy = DI::config()->get('config', 'register_policy');
-               if (!in_array($register_policy, [Register::OPEN, Register::CLOSED])) {
+               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 == Register::OPEN)) {
-                       DI::config()->set('config', 'register_policy', Register::CLOSED);
+               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 == Register::CLOSED)) {
-                       DI::config()->set('config', 'register_policy', Register::OPEN);
+               } 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]);