<?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
*
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
+use Friendica\Module;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
+use Friendica\Network\HTTPException\InternalServerErrorException;
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\Worker\Delivery;
use ImagickException;
use LightOpenID;
* 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
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
*
$system['publish'] = false;
$system['net-publish'] = false;
$system['hide-friends'] = true;
+ $system['hidewall'] = true;
$system['prv_keywords'] = '';
$system['pub_keywords'] = '';
$system['address'] = '';
$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
- $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'],
$system['guid'] = $fields['guid'];
} else {
- $system['guid'] = $user['guid'];
+ $system['guid'] = $user['guid'];
+ $system['language'] = $user['language'];
}
return $system;
'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'],
}
/**
- * 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_group;
+ 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
+ * @param boolean $short If true, return the short form g.g. "en", otherwise the long form e.g. "en-gb"
+ * @return string
+ */
+ public static function getLanguageCode(int $uid, bool $short): string
+ {
+ $owner = self::getOwnerDataById($uid);
+ $languages = DI::l10n()->getAvailableLanguages();
+ if (in_array($owner['language'], array_keys($languages))) {
+ $language = $owner['language'];
+ } else {
+ $language = DI::config()->get('system', 'language');
+ }
+ if ($short) {
+ return substr($language, 0, 2);
+ }
+ return $language;
}
/**
// 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;
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
*
}
/**
- * 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
*/
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}' : '+') . '$';
}
/**
}
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));
* 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
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()
+ ]);
+ }
+
+ /**
+ * 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
*
* 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
$_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'];
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];
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);
$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);
Hook::callAll('register_account', $uid);
+ self::setRegisterMethodByUserCount();
+
$return['user'] = $user;
return $return;
}
/**
* 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);
}
$l10n,
$user,
DI::config()->get('config', 'sitename'),
- DI::baseUrl()->get(),
+ DI::baseUrl(),
($register['password'] ?? '') ?: 'Sent in a previous email'
);
}
* 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
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()
// Remove the user relevant data
Worker::add(Worker::PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid);
+ self::setRegisterMethodByUserCount();
return true;
}
*/
public static function identities(int $uid): array
{
- if (empty($uid)) {
+ if (!$uid) {
return [];
}
return $identities;
}
- if ($user['parent-uid'] == 0) {
+ if (!$user['parent-uid']) {
// First add our own entry
$identities = [[
'uid' => $user['uid'],
*/
public static function hasIdentities(int $uid): bool
{
- if (empty($uid)) {
+ if (!$uid) {
return false;
}
return false;
}
- if ($user['parent-uid'] != 0) {
+ if ($user['parent-uid']) {
return true;
}
'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)) {
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']++;
}
*
* @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
{
$condition = [
'email' => self::getAdminEmailList(),
- 'parent-uid' => 0,
- 'blocked' => 0,
+ 'parent-uid' => null,
+ 'blocked' => false,
'verified' => true,
'account_removed' => false,
'account_expired' => false,
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]);
+ }
+ }
}