diff --git a/src/App.php b/src/App.php index 27eaf100f..abad4ee35 100644 --- a/src/App.php +++ b/src/App.php @@ -27,6 +27,7 @@ use Friendica\App\BaseURL; use Friendica\Capabilities\ICanCreateResponses; use Friendica\Core\Config\Factory\Config; use Friendica\Core\Session\Capability\IHandleUserSessions; +use Friendica\Model\User; use Friendica\Module\Maintenance; use Friendica\Security\Authentication; use Friendica\Core\Config\ValueObject\Cache; @@ -164,14 +165,16 @@ class App * Check if current user has admin role. * * @return bool true if user is an admin + * @throws Exception */ public function isSiteAdmin(): bool { - $admin_email = $this->config->get('config', 'admin_email'); - - $adminlist = explode(',', str_replace(' ', '', $admin_email)); - - return $this->session->getLocalUserId() && $admin_email && $this->database->exists('user', ['uid' => $this->getLoggedInUserId(), 'email' => $adminlist]); + return + $this->session->getLocalUserId() + && $this->database->exists('user', [ + 'uid' => $this->getLoggedInUserId(), + 'email' => User::getAdminEmailList() + ]); } /** @@ -259,8 +262,8 @@ class App /** * Set workerqueue information * - * @param array $queue - * @return void + * @param array $queue + * @return void */ public function setQueue(array $queue) { diff --git a/src/Core/Update.php b/src/Core/Update.php index 5f4251720..8ec96dc98 100644 --- a/src/Core/Update.php +++ b/src/Core/Update.php @@ -26,6 +26,7 @@ use Friendica\App\Mode; use Friendica\Database\DBA; use Friendica\Database\DBStructure; use Friendica\DI; +use Friendica\Model\User; use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Util\DateTimeFormat; use Friendica\Util\Strings; @@ -148,7 +149,7 @@ class Update } DI::config()->set('system', 'maintenance', 1); - + // run the pre_update_nnnn functions in update.php for ($version = $stored + 1; $version <= $current; $version++) { Logger::notice('Execute pre update.', ['version' => $version]); @@ -289,30 +290,16 @@ class Update * @return void * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - private static function updateFailed(int $update_id, string $error_message) { - //send the administrators an e-mail - $condition = ['email' => explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))), 'parent-uid' => 0]; - $adminlist = DBA::select('user', ['uid', 'language', 'email'], $condition, ['order' => ['uid']]); - - // No valid result? - if (!DBA::isResult($adminlist)) { + private static function updateFailed(int $update_id, string $error_message) + { + $adminEmails = User::getAdminListForEmailing(['uid', 'language', 'email']); + if (!$adminEmails) { Logger::warning('Cannot notify administrators .', ['update' => $update_id, 'message' => $error_message]); - - // Don't continue return; } - $sent = []; - - // every admin could had different language - while ($admin = DBA::fetch($adminlist)) { - if (in_array($admin['email'], $sent)) { - continue; - } - $sent[] = $admin['email']; - - $lang = $admin['language'] ?? 'en'; - $l10n = DI::l10n()->withLang($lang); + foreach($adminEmails as $admin) { + $l10n = DI::l10n()->withLang($admin['language'] ?: 'en'); $preamble = Strings::deindent($l10n->t(" The friendica developers released update %s recently, @@ -343,35 +330,20 @@ class Update */ private static function updateSuccessful(int $from_build, int $to_build) { - //send the administrators an e-mail - $condition = ['email' => explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))), 'parent-uid' => 0]; - $adminlist = DBA::select('user', ['uid', 'language', 'email'], $condition, ['order' => ['uid']]); + foreach(User::getAdminListForEmailing(['uid', 'language', 'email']) as $admin) { + $l10n = DI::l10n()->withLang($admin['language'] ?: 'en'); - if (DBA::isResult($adminlist)) { - $sent = []; + $preamble = Strings::deindent($l10n->t(' + The friendica database was successfully updated from %s to %s.', + $from_build, $to_build)); - // every admin could had different language - while ($admin = DBA::fetch($adminlist)) { - if (in_array($admin['email'], $sent)) { - continue; - } - $sent[] = $admin['email']; - - $lang = (($admin['language']) ? $admin['language'] : 'en'); - $l10n = DI::l10n()->withLang($lang); - - $preamble = Strings::deindent($l10n->t(' - The friendica database was successfully updated from %s to %s.', - $from_build, $to_build)); - - $email = DI::emailer() - ->newSystemMail() - ->withMessage($l10n->t('[Friendica Notify] Database update'), $preamble) - ->forUser($admin) - ->withRecipient($admin['email']) - ->build(); - DI::emailer()->send($email); - } + $email = DI::emailer() + ->newSystemMail() + ->withMessage($l10n->t('[Friendica Notify] Database update'), $preamble) + ->forUser($admin) + ->withRecipient($admin['email']) + ->build(); + DI::emailer()->send($email); } Logger::debug('Database structure update successful.'); diff --git a/src/Model/Nodeinfo.php b/src/Model/Nodeinfo.php index 9a4d2bd60..b10572612 100644 --- a/src/Model/Nodeinfo.php +++ b/src/Model/Nodeinfo.php @@ -164,25 +164,16 @@ class Nodeinfo * * @param IManageConfigValues $config Configuration instance * @return array Organization information + * @throws \Exception */ public static function getOrganization(IManageConfigValues $config): array { - $organization = [ - 'name' => null, - 'contact' => null, - 'account' => null + $administrator = User::getFirstAdmin(['username', 'email', 'nickname']); + + return [ + 'name' => $administrator['username'] ?? null, + 'contact' => $administrator['email'] ?? null, + 'account' => $administrator['nickname'] ?? '' ? DI::baseUrl()->get() . '/profile/' . $administrator['nickname'] : null, ]; - - if (!empty($config->get('config', 'admin_email'))) { - $adminList = explode(',', str_replace(' ', '', $config->get('config', 'admin_email'))); - $organization['contact'] = $adminList[0]; - $administrator = User::getByEmail($adminList[0], ['username', 'nickname']); - if (!empty($administrator)) { - $organization['name'] = $administrator['username']; - $organization['account'] = DI::baseUrl()->get() . '/profile/' . $administrator['nickname']; - } - } - - return $organization; } } diff --git a/src/Model/User.php b/src/Model/User.php index cc6e11a42..73c380561 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -381,17 +381,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] ?? []; } /** @@ -1054,11 +1052,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); @@ -1783,4 +1778,64 @@ 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; + }); + } } diff --git a/src/Module/Api/GNUSocial/GNUSocial/Config.php b/src/Module/Api/GNUSocial/GNUSocial/Config.php index 8b38ca896..ed27b8ef3 100644 --- a/src/Module/Api/GNUSocial/GNUSocial/Config.php +++ b/src/Module/Api/GNUSocial/GNUSocial/Config.php @@ -23,6 +23,7 @@ namespace Friendica\Module\Api\GNUSocial\GNUSocial; use Friendica\App; use Friendica\DI; +use Friendica\Model\User; use Friendica\Module\BaseApi; use Friendica\Module\Register; @@ -42,7 +43,7 @@ class Config extends BaseApi 'logo' => DI::baseUrl() . '/images/friendica-64.png', 'fancy' => true, 'language' => DI::config()->get('system', 'language'), - 'email' => DI::config()->get('config', 'admin_email'), + 'email' => implode(',', User::getAdminEmailList()), 'broughtby' => '', 'broughtbyurl' => '', 'timezone' => DI::config()->get('system', 'default_timezone'), diff --git a/src/Module/Moderation/BaseUsers.php b/src/Module/Moderation/BaseUsers.php index eddfc0909..b78027764 100644 --- a/src/Module/Moderation/BaseUsers.php +++ b/src/Module/Moderation/BaseUsers.php @@ -113,7 +113,7 @@ abstract class BaseUsers extends BaseModeration protected function setupUserCallback(): \Closure { - $adminlist = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))); + $adminlist = User::getAdminEmailList(); return function ($user) use ($adminlist) { $page_types = [ User::PAGE_FLAGS_NORMAL => $this->t('Normal Account Page'), diff --git a/src/Module/Register.php b/src/Module/Register.php index ad3e1feea..b71fb777c 100644 --- a/src/Module/Register.php +++ b/src/Module/Register.php @@ -352,7 +352,7 @@ class Register extends BaseModule DI::baseUrl()->redirect(); } } elseif (intval(DI::config()->get('config', 'register_policy')) === self::APPROVE) { - if (!strlen(DI::config()->get('config', 'admin_email'))) { + if (!User::getAdminEmailList()) { DI::sysmsg()->addNotice(DI::l10n()->t('Your registration can not be processed.')); DI::baseUrl()->redirect(); } @@ -387,34 +387,23 @@ class Register extends BaseModule DI::sysmsg()->addInfo(DI::l10n()->t('Your registration is pending approval by the site owner.')); DI::baseUrl()->redirect(); } - - return; } private function sendNotification(array $user, string $event) { - // send email to admins - $admins_stmt = DBA::select( - 'user', - ['uid', 'language', 'email'], - ['email' => explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email')))] - ); - - // send notification to admins - while ($admin = DBA::fetch($admins_stmt)) { + foreach (User::getAdminListForEmailing(['uid', 'language', 'email']) as $admin) { DI::notify()->createFromArray([ - 'type' => Model\Notification\Type::SYSTEM, - 'event' => $event, - 'uid' => $admin['uid'], - 'link' => DI::baseUrl()->get(true) . '/moderation/users/', - 'source_name' => $user['username'], - 'source_mail' => $user['email'], - 'source_nick' => $user['nickname'], - 'source_link' => DI::baseUrl()->get(true) . '/moderation/users/', - 'source_photo' => User::getAvatarUrl($user, Proxy::SIZE_THUMB), + 'type' => Model\Notification\Type::SYSTEM, + 'event' => $event, + 'uid' => $admin['uid'], + 'link' => DI::baseUrl()->get(true) . '/moderation/users/', + 'source_name' => $user['username'], + 'source_mail' => $user['email'], + 'source_nick' => $user['nickname'], + 'source_link' => DI::baseUrl()->get(true) . '/moderation/users/', + 'source_photo' => User::getAvatarUrl($user, Proxy::SIZE_THUMB), 'show_in_notification_page' => false ]); } - DBA::close($admins_stmt); } } diff --git a/src/Module/Settings/Account.php b/src/Module/Settings/Account.php index c9b140acb..4217b1e88 100644 --- a/src/Module/Settings/Account.php +++ b/src/Module/Settings/Account.php @@ -113,12 +113,9 @@ class Account extends BaseSettings $err .= DI::l10n()->t('Invalid email.'); } // ensure new email is not the admin mail - if (DI::config()->get('config', 'admin_email')) { - $adminlist = explode(",", str_replace(" ", "", strtolower(DI::config()->get('config', 'admin_email')))); - if (in_array(strtolower($email), $adminlist)) { - $err .= DI::l10n()->t('Cannot change to that email.'); - $email = $user['email']; - } + if (in_array(strtolower($email), User::getAdminEmailList())) { + $err .= DI::l10n()->t('Cannot change to that email.'); + $email = $user['email']; } } diff --git a/src/Module/Settings/RemoveMe.php b/src/Module/Settings/RemoveMe.php index 87bc6152a..32dffd156 100644 --- a/src/Module/Settings/RemoveMe.php +++ b/src/Module/Settings/RemoveMe.php @@ -85,14 +85,8 @@ class RemoveMe extends BaseSettings } // send notification to admins so that they can clean up the backups - $admin_mails = explode(',', $this->config->get('config', 'admin_email')); - foreach ($admin_mails as $mail) { - $admin = $this->database->selectFirst('user', ['uid', 'language', 'email', 'username'], ['email' => trim($mail)]); - if (!$admin) { - continue; - } - - $l10n = $this->l10n->withLang($admin['language']); + foreach (User::getAdminListForEmailing(['uid', 'language', 'email']) as $admin) { + $l10n = $this->l10n->withLang($admin['language'] ?: 'en'); $email = $this->emailer ->newSystemMail() diff --git a/src/Object/Api/Mastodon/Instance.php b/src/Object/Api/Mastodon/Instance.php index 45fc4bf89..b659dc306 100644 --- a/src/Object/Api/Mastodon/Instance.php +++ b/src/Object/Api/Mastodon/Instance.php @@ -86,7 +86,7 @@ class Instance extends BaseDataTransferObject $this->uri = $baseUrl->get(); $this->title = $config->get('config', 'sitename'); $this->short_description = $this->description = $config->get('config', 'info'); - $this->email = $config->get('config', 'admin_email'); + $this->email = implode(',', User::getAdminEmailList()); $this->version = '2.8.0 (compatible; Friendica ' . App::VERSION . ')'; $this->urls = null; // Not supported $this->stats = new Stats($config, $database); @@ -98,13 +98,10 @@ class Instance extends BaseDataTransferObject $this->invites_enabled = false; $this->contact_account = []; - if (!empty($config->get('config', 'admin_email'))) { - $adminList = explode(',', str_replace(' ', '', $config->get('config', 'admin_email'))); - $administrator = User::getByEmail($adminList[0], ['nickname']); - if (!empty($administrator)) { - $adminContact = $database->selectFirst('contact', ['id'], ['nick' => $administrator['nickname'], 'self' => true]); - $this->contact_account = DI::mstdnAccount()->createFromContactId($adminContact['id']); - } + $administrator = User::getFirstAdmin(['nickname']); + if ($administrator) { + $adminContact = $database->selectFirst('contact', ['uri-id'], ['nick' => $administrator['nickname'], 'self' => true]); + $this->contact_account = DI::mstdnAccount()->createFromUriId($adminContact['uri-id']); } } } diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 95bb0f312..ca9a605fc 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -1710,7 +1710,7 @@ class Transmitter } $data['attachment'] = self::createAttachmentList($item); - $data['tag'] = self::createTagList($item, $data['quoteUrl'] ?? ''); + $data['tag'] = self::createTagList($item, $data['quoteUrl'] ?? ''); if (empty($data['location']) && (!empty($item['coord']) || !empty($item['location']))) { $data['location'] = self::createLocation($item); @@ -2073,13 +2073,14 @@ class Transmitter } if (empty($uid)) { - // Fetch the list of administrators - $admin_mail = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))); - // We need to use some user as a sender. It doesn't care who it will send. We will use an administrator account. - $condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false, 'email' => $admin_mail]; - $first_user = DBA::selectFirst('user', ['uid'], $condition); - $uid = $first_user['uid']; + $admin = User::getFirstAdmin(['uid']); + if (!$admin) { + Logger::warning('No available admin user for transmission', ['target' => $target]); + return false; + } + + $uid = $admin['uid']; } $condition = ['verb' => Activity::FOLLOW, 'uid' => 0, 'parent-uri' => $object,