X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FUser.php;h=11d55e7f7e26529fa507f8ddacd2b5a60124b385;hb=c5ca5bfdf84f6fb5e4ba4d8509df25c58aeb516a;hp=574830603e816395364903e16db9c68b9fef18fc;hpb=49394aedeb96b35303eac061563c754ab23a9b96;p=friendica.git diff --git a/src/Model/User.php b/src/Model/User.php index 574830603e..11d55e7f7e 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -1,6 +1,6 @@ get(); + $system['homepage'] = DI::baseUrl(); $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', - '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'], @@ -265,7 +267,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, '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; @@ -381,17 +383,15 @@ class 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); - } 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] ?? []; } /** @@ -529,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 - // 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; @@ -667,6 +667,28 @@ class 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 * @@ -736,7 +758,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 @@ -749,13 +771,13 @@ class User */ public static function getPasswordRegExp(string $delimiter = null): string { - $allowed_characters = '!"#$%&\'()*+,-./;<=>?@[\]^_`{|}~'; + $allowed_characters = ':!"#$%&\'()*+,-./;<=>?@[\]^_`{|}~'; 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 +805,7 @@ class User } 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)); @@ -794,14 +816,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 @@ -809,11 +831,27 @@ class User 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 - * 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 @@ -986,7 +1024,7 @@ class User $_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']; @@ -994,7 +1032,7 @@ class User 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.') . '
' . DI::l10n()->t('The error message was:') . $e->getMessage(), 0, $e); } System::externalRedirect($authurl); // NOTREACHED @@ -1054,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. - 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); @@ -1197,7 +1232,7 @@ class User $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); @@ -1232,6 +1267,8 @@ class User Hook::callAll('register_account', $uid); + self::setRegisterMethodByUserCount(); + $return['user'] = $user; return $return; } @@ -1317,7 +1354,7 @@ class User 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']); @@ -1326,7 +1363,7 @@ class User $l10n, $user, DI::config()->get('config', 'sitename'), - DI::baseUrl()->get(), + DI::baseUrl(), ($register['password'] ?? '') ?: 'Sent in a previous email' ); } @@ -1340,7 +1377,7 @@ class User * 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 @@ -1418,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 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')); - $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() @@ -1522,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 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'], @@ -1567,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]); - 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]); - Worker::add(PRIORITY_LOW, 'Directory', $self['url']); + Worker::add(Worker::PRIORITY_LOW, 'Directory', $self['url']); // Remove the user relevant data - Worker::add(PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid); + Worker::add(Worker::PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid); + self::setRegisterMethodByUserCount(); return true; } @@ -1714,8 +1752,8 @@ class User '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)) { @@ -1729,17 +1767,17 @@ class User 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']++; } - 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']++; } - 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']++; } @@ -1754,7 +1792,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 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 @@ -1783,4 +1821,89 @@ class User 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]); + } + } }