diff --git a/boot.php b/boot.php index 2bcbb4ebd..3852c3279 100644 --- a/boot.php +++ b/boot.php @@ -152,8 +152,6 @@ define('NOTIFY_TAGSHARE', 256); define('NOTIFY_POKE', 512); define('NOTIFY_SHARE', 1024); -define('SYSTEM_EMAIL', 16384); - define('NOTIFY_SYSTEM', 32768); /* @}*/ diff --git a/include/enotify.php b/include/enotify.php index e21adc91a..42aee5569 100644 --- a/include/enotify.php +++ b/include/enotify.php @@ -72,20 +72,15 @@ function notification($params) $hostname = substr($hostname, 0, strpos($hostname, ':')); } - $sender_email = $a->getSenderEmailAddress(); + $sender_email = DI::emailer()->getSiteEmailAddress(); - if ($params['type'] != SYSTEM_EMAIL) { - $user = DBA::selectFirst('user', ['nickname', 'page-flags'], - ['uid' => $params['uid']]); + $user = User::getById($params['uid'], ['nickname', 'page-flags']); - // There is no need to create notifications for forum accounts - if (!DBA::isResult($user) || in_array($user["page-flags"], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_PRVGROUP])) { - return false; - } - $nickname = $user["nickname"]; - } else { - $nickname = ''; + // There is no need to create notifications for forum accounts + if (!DBA::isResult($user) || in_array($user["page-flags"], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_PRVGROUP])) { + return false; } + $nickname = $user["nickname"]; // with $params['show_in_notification_page'] == false, the notification isn't inserted into // the database, and an email is sent if applicable. @@ -428,30 +423,6 @@ function notification($params) } } - if ($params['type'] == SYSTEM_EMAIL) { - // not part of the notifications. - // it just send a mail to the user. - // It will be used by the system to send emails to users (like - // password reset, invitations and so) using one look (but without - // add a notification to the user, with could be inexistent) - if (!isset($params['subject'])) { - Logger::warning('subject isn\'t set.', ['type' => $params['type']]); - } - $subject = $params['subject'] ?? ''; - - if (!isset($params['preamble'])) { - Logger::warning('preamble isn\'t set.', ['type' => $params['type'], 'subject' => $subject]); - } - $preamble = $params['preamble'] ?? ''; - - if (!isset($params['body'])) { - Logger::warning('body isn\'t set.', ['type' => $params['type'], 'subject' => $subject, 'preamble' => $preamble]); - } - $body = $params['body'] ?? ''; - - $show_in_notification_page = false; - } - $subject .= " (".$nickname."@".$hostname.")"; $h = [ @@ -506,8 +477,7 @@ function notification($params) // send email notification if notification preferences permit if ((intval($params['notify_flags']) & intval($params['type'])) - || $params['type'] == NOTIFY_SYSTEM - || $params['type'] == SYSTEM_EMAIL) { + || $params['type'] == NOTIFY_SYSTEM) { Logger::log('sending notification email'); @@ -548,7 +518,6 @@ function notification($params) $datarray['source_link'] = $params['source_link'] ?? ''; $datarray['source_photo'] = $params['source_photo'] ?? ''; $datarray['uid'] = $params['uid']; - $datarray['username'] = $params['to_name'] ?? ''; $datarray['hsitelink'] = $hsitelink; $datarray['tsitelink'] = $tsitelink; $datarray['hitemlink'] = '' . $itemlink . ''; @@ -564,11 +533,10 @@ function notification($params) Hook::callAll('enotify_mail', $datarray); // check whether sending post content in email notifications is allowed - // always true for SYSTEM_EMAIL - $content_allowed = ((!DI::config()->get('system', 'enotify_no_content')) || ($params['type'] == SYSTEM_EMAIL)); + $content_allowed = (!DI::config()->get('system', 'enotify_no_content')); // load the template for private message notifications - $tpl = Renderer::getMarkupTemplate('email_notify_html.tpl'); + $tpl = Renderer::getMarkupTemplate('email/notify/html.tpl'); $email_html_body = Renderer::replaceMacros($tpl, [ '$banner' => $datarray['banner'], '$product' => $datarray['product'], @@ -578,7 +546,6 @@ function notification($params) '$source_name' => $datarray['source_name'], '$source_link' => $datarray['source_link'], '$source_photo' => $datarray['source_photo'], - '$username' => $datarray['username'], '$hsitelink' => $datarray['hsitelink'], '$hitemlink' => $datarray['hitemlink'], '$thanks' => $datarray['thanks'], @@ -589,17 +556,9 @@ function notification($params) ]); // load the template for private message notifications - $tpl = Renderer::getMarkupTemplate('email_notify_text.tpl'); + $tpl = Renderer::getMarkupTemplate('email/notify/text.tpl'); $email_text_body = Renderer::replaceMacros($tpl, [ - '$banner' => $datarray['banner'], - '$product' => $datarray['product'], '$preamble' => $datarray['preamble'], - '$sitename' => $datarray['sitename'], - '$siteurl' => $datarray['siteurl'], - '$source_name' => $datarray['source_name'], - '$source_link' => $datarray['source_link'], - '$source_photo' => $datarray['source_photo'], - '$username' => $datarray['username'], '$tsitelink' => $datarray['tsitelink'], '$titemlink' => $datarray['titemlink'], '$thanks' => $datarray['thanks'], diff --git a/mod/lostpass.php b/mod/lostpass.php index 59426a1af..51aee56df 100644 --- a/mod/lostpass.php +++ b/mod/lostpass.php @@ -64,17 +64,14 @@ function lostpass_post(App $a) Site Location: %2$s Login Name: %3$s', $resetlink, DI::baseUrl(), $user['nickname'])); - notification([ - 'type' => SYSTEM_EMAIL, - 'language' => $user['language'], - 'to_name' => $user['username'], - 'to_email' => $user['email'], - 'uid' => $user['uid'], - 'subject' => DI::l10n()->t('Password reset requested at %s', $sitename), - 'preamble' => $preamble, - 'body' => $body - ]); + $email = DI::emailer() + ->newSystemMail() + ->withMessage(DI::l10n()->t('Password reset requested at %s', $sitename), $preamble, $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + DI::emailer()->send($email); DI::baseUrl()->redirect(); } @@ -159,16 +156,13 @@ function lostpass_generate_password($user) You may change that password from your account settings page after logging in. ', DI::baseUrl(), $user['nickname'], $new_password)); - notification([ - 'type' => SYSTEM_EMAIL, - 'language' => $user['language'], - 'to_name' => $user['username'], - 'to_email' => $user['email'], - 'uid' => $user['uid'], - 'subject' => DI::l10n()->t('Your password has been changed at %s', $sitename), - 'preamble' => $preamble, - 'body' => $body - ]); + $email = DI::emailer() + ->newSystemMail() + ->withMessage(DI::l10n()->t('Your password has been changed at %s', $sitename), $preamble, $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + DI::emailer()->send($email); } return $o; diff --git a/mod/removeme.php b/mod/removeme.php index 48d4e0e0f..e8615feb7 100644 --- a/mod/removeme.php +++ b/mod/removeme.php @@ -40,17 +40,17 @@ function removeme_post(App $a) if (!DBA::isResult($admin)) { continue; } - notification([ - 'type' => SYSTEM_EMAIL, - 'subject' => DI::l10n()->t('[Friendica System Notify]') . ' ' . DI::l10n()->t('User deleted their account'), - 'preamble' => DI::l10n()->t('On your Friendica node an user deleted their account. Please ensure that their data is removed from the backups.'), - 'body' => DI::l10n()->t('The user id is %d', local_user()), - 'to_email' => $admin['email'], - 'to_name' => $admin['username'], - 'uid' => $admin['uid'], - 'language' => $admin['language'] ? $admin['language'] : 'en', - 'show_in_notification_page' => false - ]); + + $email = DI::emailer() + ->newSystemMail() + ->withMessage( + DI::l10n()->t('[Friendica System Notify]') . ' ' . DI::l10n()->t('User deleted their account'), + DI::l10n()->t('On your Friendica node an user deleted their account. Please ensure that their data is removed from the backups.'), + DI::l10n()->t('The user id is %d', local_user())) + ->forUser($admin) + ->withRecipient($admin['email']) + ->build(); + DI::emailer()->send($email); } if (User::getIdFromPasswordAuthentication($a->user, trim($_POST['qxz_password']))) { diff --git a/src/App.php b/src/App.php index ceb11dd79..892a4c779 100644 --- a/src/App.php +++ b/src/App.php @@ -242,27 +242,6 @@ class App $this->baseURL->get(); } - /** - * Generates the site's default sender email address - * - * @return string - * @throws HTTPException\InternalServerErrorException - */ - public function getSenderEmailAddress() - { - $sender_email = $this->config->get('config', 'sender_email'); - if (empty($sender_email)) { - $hostname = $this->baseURL->getHostname(); - if (strpos($hostname, ':')) { - $hostname = substr($hostname, 0, strpos($hostname, ':')); - } - - $sender_email = 'noreply@' . $hostname; - } - - return $sender_email; - } - /** * Returns the current theme name. May be overriden by the mobile theme name. * diff --git a/src/Core/Update.php b/src/Core/Update.php index 5a151b838..4db32ecfb 100644 --- a/src/Core/Update.php +++ b/src/Core/Update.php @@ -252,7 +252,7 @@ class Update } $sent[] = $admin['email']; - $lang = (($admin['language'])?$admin['language']:'en'); + $lang = $admin['language'] ?? 'en'; $l10n = DI::l10n()->withLang($lang); $preamble = Strings::deindent($l10n->t(" @@ -261,17 +261,15 @@ class Update This needs to be fixed soon and I can't do it alone. Please contact a friendica developer if you can not help me on your own. My database might be invalid.", $update_id)); - $body = $l10n->t("The error message is\n[pre]%s[/pre]", $error_message); + $body = $l10n->t("The error message is\n[pre]%s[/pre]", $error_message); - notification([ - 'uid' => $admin['uid'], - 'type' => SYSTEM_EMAIL, - 'to_email' => $admin['email'], - 'subject' => $l10n->t('[Friendica Notify] Database update'), - 'preamble' => $preamble, - 'body' => $body, - 'language' => $lang] - ); + $email = DI::emailer() + ->newSystemMail() + ->withMessage($l10n->t('[Friendica Notify] Database update'), $preamble, $body) + ->forUser($admin) + ->withRecipient($admin['email']) + ->build(); + DI::emailer()->send($email); } //try the logger @@ -301,15 +299,13 @@ class Update The friendica database was successfully updated from %s to %s.", $from_build, $to_build)); - notification([ - 'uid' => $admin['uid'], - 'type' => SYSTEM_EMAIL, - 'to_email' => $admin['email'], - 'subject' => DI::l10n()->t('[Friendica Notify] Database update'), - 'preamble' => $preamble, - 'body' => $preamble, - 'language' => $lang] - ); + $email = DI::emailer() + ->newSystemMail() + ->withMessage($l10n->t('[Friendica Notify] Database update'), $preamble) + ->forUser($admin) + ->withRecipient($admin['email']) + ->build(); + DI::emailer()->send($email); } } diff --git a/src/Model/User.php b/src/Model/User.php index fd7238819..7e2d37c40 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -897,13 +897,13 @@ class User $password )); - return notification([ - 'type' => SYSTEM_EMAIL, - 'uid' => $user['uid'], - 'to_email' => $user['email'], - 'subject' => DI::l10n()->t('Registration at %s', $sitename), - 'body' => $body - ]); + $email = DI::emailer() + ->newSystemMail() + ->withMessage(DI::l10n()->t('Registration at %s', $sitename), $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + return DI::emailer()->send($email); } /** @@ -965,15 +965,13 @@ class User $password )); - return notification([ - 'uid' => $user['uid'], - 'language' => $user['language'], - 'type' => SYSTEM_EMAIL, - 'to_email' => $user['email'], - 'subject' => DI::l10n()->t('Registration details for %s', $sitename), - 'preamble' => $preamble, - 'body' => $body - ]); + $email = DI::emailer() + ->newSystemMail() + ->withMessage(DI::l10n()->t('Registration details for %s', $sitename), $preamble, $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + return DI::emailer()->send($email); } /** diff --git a/src/Module/Admin/Site.php b/src/Module/Admin/Site.php index 7cb3a9b8f..50a9c9d22 100644 --- a/src/Module/Admin/Site.php +++ b/src/Module/Admin/Site.php @@ -13,6 +13,7 @@ use Friendica\Module\BaseAdmin; use Friendica\Module\Register; use Friendica\Protocol\PortableContact; use Friendica\Util\BasePath; +use Friendica\Util\EMailer\MailBuilder; use Friendica\Util\Strings; use Friendica\Worker\Delivery; @@ -110,6 +111,7 @@ class Site extends BaseAdmin $sitename = (!empty($_POST['sitename']) ? Strings::escapeTags(trim($_POST['sitename'])) : ''); $sender_email = (!empty($_POST['sender_email']) ? Strings::escapeTags(trim($_POST['sender_email'])) : ''); $banner = (!empty($_POST['banner']) ? trim($_POST['banner']) : false); + $email_banner = (!empty($_POST['email_banner']) ? trim($_POST['email_banner']) : false); $shortcut_icon = (!empty($_POST['shortcut_icon']) ? Strings::escapeTags(trim($_POST['shortcut_icon'])) : ''); $touch_icon = (!empty($_POST['touch_icon']) ? Strings::escapeTags(trim($_POST['touch_icon'])) : ''); $additional_info = (!empty($_POST['additional_info']) ? trim($_POST['additional_info']) : ''); @@ -301,6 +303,12 @@ class Site extends BaseAdmin DI::config()->set('system', 'banner', $banner); } + if (empty($email_banner)) { + DI::config()->delete('system', 'email_banner'); + } else { + DI::config()->set('system', 'email_banner', $email_banner); + } + if (empty($additional_info)) { DI::config()->delete('config', 'info'); } else { @@ -489,6 +497,12 @@ class Site extends BaseAdmin $banner = 'logoFriendica'; } + $email_banner = DI::config()->get('system', 'email_banner'); + + if ($email_banner == false) { + $email_banner = MailBuilder::DEFAULT_EMAIL_BANNER; + } + $additional_info = DI::config()->get('config', 'info'); // Automatically create temporary paths @@ -571,6 +585,7 @@ class Site extends BaseAdmin '$sitename' => ['sitename', DI::l10n()->t('Site name'), DI::config()->get('config', 'sitename'), ''], '$sender_email' => ['sender_email', DI::l10n()->t('Sender Email'), DI::config()->get('config', 'sender_email'), DI::l10n()->t('The email address your server shall use to send notification emails from.'), '', '', 'email'], '$banner' => ['banner', DI::l10n()->t('Banner/Logo'), $banner, ''], + '$email_banner' => ['email_banner', DI::l10n()->t('Email Banner/Logo'), $email_banner, ''], '$shortcut_icon' => ['shortcut_icon', DI::l10n()->t('Shortcut icon'), DI::config()->get('system', 'shortcut_icon'), DI::l10n()->t('Link to an icon that will be used for browsers.')], '$touch_icon' => ['touch_icon', DI::l10n()->t('Touch icon'), DI::config()->get('system', 'touch_icon'), DI::l10n()->t('Link to an icon that will be used for tablets and mobiles.')], '$additional_info' => ['additional_info', DI::l10n()->t('Additional Info'), $additional_info, DI::l10n()->t('For public servers: you can add additional information here that will be listed at %s/servers.', Search::getGlobalDirectory())], diff --git a/src/Module/Admin/Users.php b/src/Module/Admin/Users.php index 0498a80da..f52d7b36f 100644 --- a/src/Module/Admin/Users.php +++ b/src/Module/Admin/Users.php @@ -76,15 +76,13 @@ class Users extends BaseAdmin $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')); - notification([ - 'type' => SYSTEM_EMAIL, - 'language' => $user['language'], - 'to_name' => $user['username'], - 'to_email' => $user['email'], - 'uid' => $user['uid'], - 'subject' => DI::l10n()->t('Registration details for %s', DI::config()->get('config', 'sitename')), - 'preamble' => $preamble, - 'body' => $body]); + $email = DI::emailer() + ->newSystemMail() + ->withMessage(DI::l10n()->t('Registration details for %s', DI::config()->get('config', 'sitename')), $preamble, $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + return DI::emailer()->send($email); } if (!empty($_POST['page_users_block'])) { diff --git a/src/Module/Invite.php b/src/Module/Invite.php index df569377b..fc8b4abf9 100644 --- a/src/Module/Invite.php +++ b/src/Module/Invite.php @@ -77,7 +77,7 @@ class Invite extends BaseModule } $additional_headers = 'From: ' . $app->user['email'] . "\n" - . 'Sender: ' . $app->getSenderEmailAddress() . "\n" + . 'Sender: ' . DI::emailer()->getSiteEmailAddress() . "\n" . 'Content-type: text/plain; charset=UTF-8' . "\n" . 'Content-transfer-encoding: 8bit'; diff --git a/src/Util/EMailer/MailBuilder.php b/src/Util/EMailer/MailBuilder.php new file mode 100644 index 000000000..c47d22d83 --- /dev/null +++ b/src/Util/EMailer/MailBuilder.php @@ -0,0 +1,199 @@ +l10n = $l10n; + $this->baseUrl = $baseUrl; + $this->config = $config; + $this->logger = $logger; + + $hostname = $baseUrl->getHostname(); + if (strpos($hostname, ':')) { + $hostname = substr($hostname, 0, strpos($hostname, ':')); + } + + $this->headers = ""; + $this->headers .= "Precedence: list\n"; + $this->headers .= "X-Friendica-Host: " . $hostname . "\n"; + $this->headers .= "X-Friendica-Platform: " . FRIENDICA_PLATFORM . "\n"; + $this->headers .= "X-Friendica-Version: " . FRIENDICA_VERSION . "\n"; + $this->headers .= "List-ID: \n"; + $this->headers .= "List-Archive: <" . $baseUrl->get() . "/notifications/system>\n"; + } + + /** + * Gets the subject of the concrete builder, which inherits this base class + * + * @return string + */ + abstract protected function getSubject(); + + /** + * Gets the HTML version of the body of the concrete builder, which inherits this base class + * + * @return string + */ + abstract protected function getHtmlMessage(); + + /** + * Gets the Plaintext version of the body of the concrete builder, which inherits this base class + * + * @return string + */ + abstract protected function getPlaintextMessage(); + + /** + * Adds the User ID to the email in case the mail sending needs additional properties of this user + * + * @param array $user The user entity/array, for which the email should be sent + * + * @return static + * @todo Once the user array is replaced with a user entity, replace this array parameter as well + */ + public function forUser(array $user) + { + $this->recipientUid = $user['uid'] ?? 0; + try { + $this->l10n = $user['language'] ? $this->l10n->withLang($user['language']) : $this->l10n; + } catch (Exception $e) { + $this->logger->warning('cannot use language.', ['user' => $user, 'exception' => $e]); + } + + return $this; + } + + /** + * Adds the sender to the email (if not called/set, the sender will get loaded with the help of the user id) + * + * @param string $name The name of the sender + * @param string $address The (email) address of the sender + * @param string|null $noReply Optional "no-reply" (email) address (if not set, it's the same as the address) + * + * @return static + */ + public function withSender(string $name, string $address, string $noReply = null) + { + $this->senderName = $name; + $this->senderAddress = $address; + $this->senderNoReply = $noReply ?? $this->senderNoReply; + + return $this; + } + + /** + * Adds a recipient to the email + * + * @param string $address The (email) address of the recipient + * + * @return static + */ + public function withRecipient(string $address) + { + $this->recipientAddress = $address; + + return $this; + } + + /** + * Build a email based on the given attributes + * + * @param bool $raw True, if the email shouldn't get extended by the default email-template + * + * @return IEmail A new generated email + * + * @throws InternalServerErrorException + * @throws Exception + */ + public function build(bool $raw = false) + { + if ((empty($this->recipientAddress)) && + !empty($this->recipientUid)) { + $user = User::getById($this->recipientUid, ['email']); + + if (!empty($user['email'])) { + $this->recipientAddress = $user['email']; + } + } + + if (empty($this->recipientAddress)) { + throw new InternalServerErrorException('Recipient address is missing.'); + } + + if (empty($this->senderAddress) || empty($this->senderName)) { + throw new InternalServerErrorException('Sender address or name is missing.'); + } + + $this->senderNoReply = $this->senderNoReply ?? $this->senderAddress; + + $msgHtml = $this->getHtmlMessage() ?? ''; + + if (!$raw) { + // load the template for private message notifications + $tpl = Renderer::getMarkupTemplate('email/html.tpl'); + $msgHtml = Renderer::replaceMacros($tpl, [ + '$title' => $this->l10n->t('Friendica Notification'), + '$product' => FRIENDICA_PLATFORM, + '$htmlversion' => $msgHtml, + '$sitename' => $this->config->get('config', 'sitename'), + '$banner' => $this->config->get('system', 'email_banner', + $this->baseUrl->get(true) . DIRECTORY_SEPARATOR . self::DEFAULT_EMAIL_BANNER), + ]); + } + + return new Email( + $this->senderName, + $this->senderAddress, + $this->senderNoReply, + $this->recipientAddress, + $this->getSubject() ?? '', + $msgHtml, + $this->getPlaintextMessage() ?? '', + $this->headers, + $this->recipientUid ?? null); + } +} diff --git a/src/Util/EMailer/SystemMailBuilder.php b/src/Util/EMailer/SystemMailBuilder.php new file mode 100644 index 000000000..24c1593c9 --- /dev/null +++ b/src/Util/EMailer/SystemMailBuilder.php @@ -0,0 +1,114 @@ +config->get('config', 'admin_name')) { + $this->siteAdmin = $l10n->t('%1$s, %2$s Administrator', $this->config->get('config', 'admin_name'), $siteName); + } else { + $this->siteAdmin = $l10n->t('%s Administrator', $siteName); + } + + // Set the system wide site address/name as sender (default for system mails) + $this->senderName = $siteName; + $this->senderAddress = $siteEmailAddress; + $this->senderNoReply = $siteEmailAddress; + } + + /** + * Adds a message + * + * @param string $subject The subject of the email + * @param string $preamble The preamble of the email + * @param string|null $body The body of the email (if not set, the preamble will get used as body) + * + * @return static + */ + public function withMessage(string $subject, string $preamble, string $body = null) + { + if (!isset($body)) { + $body = $preamble; + } + + $this->subject = $subject; + $this->preamble = $preamble; + $this->body = $body; + + return $this; + } + + /** + * {@inheritDoc} + */ + protected function getSubject() + { + return $this->subject; + } + + /** + * {@inheritDoc} + * + * @throws InternalServerErrorException + * @throws Exception + */ + protected function getHtmlMessage() + { + $htmlVersion = BBCode::convert($this->body); + + // load the template for private message notifications + $tpl = Renderer::getMarkupTemplate('email/system/html.tpl'); + return Renderer::replaceMacros($tpl, [ + '$preamble' => str_replace("\n", "
\n", $this->preamble), + '$thanks' => $this->l10n->t('thanks'), + '$site_admin' => $this->siteAdmin, + '$htmlversion' => $htmlVersion, + ]); + } + + /** + * {@inheritDoc} + * + * @throws Exception + */ + protected function getPlaintextMessage() + { + $textVersion = BBCode::toPlaintext($this->body); + + // load the template for private message notifications + $tpl = Renderer::getMarkupTemplate('email/system/text.tpl'); + return Renderer::replaceMacros($tpl, [ + '$preamble' => $this->preamble, + '$thanks' => $this->l10n->t('thanks'), + '$site_admin' => $this->siteAdmin, + '$textversion' => $textVersion, + ]); + } +} diff --git a/src/Util/Emailer.php b/src/Util/Emailer.php index 19755bebd..1e1c6856c 100644 --- a/src/Util/Emailer.php +++ b/src/Util/Emailer.php @@ -7,10 +7,12 @@ namespace Friendica\Util; use Friendica\App; use Friendica\Core\Config\IConfig; use Friendica\Core\Hook; +use Friendica\Core\L10n; use Friendica\Core\PConfig\IPConfig; use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Object\EMail\IEmail; use Friendica\Protocol\Email; +use Friendica\Util\EMailer\SystemMailBuilder; use Psr\Log\LoggerInterface; /** @@ -26,13 +28,65 @@ class Emailer private $logger; /** @var App\BaseURL */ private $baseUrl; + /** @var L10n */ + private $l10n; - public function __construct(IConfig $config, IPConfig $pConfig, App\BaseURL $baseURL, LoggerInterface $logger) + /** @var string */ + private $siteEmailAddress; + /** @var string */ + private $siteEmailName; + + public function __construct(IConfig $config, IPConfig $pConfig, App\BaseURL $baseURL, LoggerInterface $logger, + L10n $defaultLang) { $this->config = $config; $this->pConfig = $pConfig; $this->logger = $logger; $this->baseUrl = $baseURL; + $this->l10n = $defaultLang; + + $this->siteEmailAddress = $this->config->get('config', 'sender_email'); + if (empty($sysEmailAddress)) { + $hostname = $this->baseUrl->getHostname(); + if (strpos($hostname, ':')) { + $hostname = substr($hostname, 0, strpos($hostname, ':')); + } + + $this->siteEmailAddress = 'noreply@' . $hostname; + } + + $this->siteEmailName = $this->config->get('config', 'sitename', 'Friendica Social Network'); + } + + /** + * Gets the site's default sender email address + * + * @return string + */ + public function getSiteEmailAddress() + { + return $this->siteEmailAddress; + } + + /** + * Gets the site's default sender name + * + * @return string + */ + public function getSiteEmailName() + { + return $this->siteEmailName; + } + + /** + * Creates a new system email + * + * @return SystemMailBuilder + */ + public function newSystemMail() + { + return new SystemMailBuilder($this->l10n, $this->baseUrl, $this->config, $this->logger, + $this->getSiteEmailAddress(), $this->getSiteEmailName()); } /** diff --git a/tests/Util/SampleMailBuilder.php b/tests/Util/SampleMailBuilder.php new file mode 100644 index 000000000..59638eea9 --- /dev/null +++ b/tests/Util/SampleMailBuilder.php @@ -0,0 +1,57 @@ +subject = $subject; + $this->html = $html; + $this->text = $text; + + return $this; + } + + /** + * @inheritDoc + */ + protected function getSubject() + { + return $this->subject; + } + + /** + * @inheritDoc + */ + protected function getHtmlMessage() + { + return $this->html; + } + + /** + * @inheritDoc + */ + protected function getPlaintextMessage() + { + return $this->text; + } +} diff --git a/tests/src/Util/Emailer/MailBuilderTest.php b/tests/src/Util/Emailer/MailBuilderTest.php new file mode 100644 index 000000000..1e475c9f7 --- /dev/null +++ b/tests/src/Util/Emailer/MailBuilderTest.php @@ -0,0 +1,177 @@ +setUpVfsDir(); + + $this->config = \Mockery::mock(IConfig::class); + $this->l10n = \Mockery::mock(L10n::class); + $this->baseUrl = \Mockery::mock(BaseURL::class); + $this->baseUrl->shouldReceive('getHostname')->andReturn('friendica.local'); + $this->baseUrl->shouldReceive('get')->andReturn('http://friendica.local'); + + $this->defaultHeaders = ""; + } + + public function assertEmail(IEmail $email, array $asserts) + { + $this->assertEquals($asserts['subject'] ?? $email->getSubject(), $email->getSubject()); + $this->assertEquals($asserts['html'] ?? $email->getMessage(), $email->getMessage()); + $this->assertEquals($asserts['text'] ?? $email->getMessage(true), $email->getMessage(true)); + $this->assertEquals($asserts['toAddress'] ?? $email->getToAddress(), $email->getToAddress()); + $this->assertEquals($asserts['fromAddress'] ?? $email->getFromAddress(), $email->getFromAddress()); + $this->assertEquals($asserts['fromName'] ?? $email->getFromName(), $email->getFromName()); + $this->assertEquals($asserts['replyTo'] ?? $email->getReplyTo(), $email->getReplyTo()); + $this->assertEquals($asserts['uid'] ?? $email->getRecipientUid(), $email->getRecipientUid()); + $this->assertEquals($asserts['header'] ?? $email->getAdditionalMailHeader(), $email->getAdditionalMailHeader()); + } + + /** + * Test if the builder instance can get created + */ + public function testBuilderInstance() + { + $builder = new SampleMailBuilder($this->l10n, $this->baseUrl, $this->config, new NullLogger()); + + $this->assertInstanceOf(MailBuilder::class, $builder); + } + + /** + * Test if the builder can create full rendered emails + * + * @todo Create test once "Renderer" and "BBCode" are dynamic + */ + public function testBuilderWithNonRawEmail() + { + $this->markTestIncomplete('Cannot easily mock Renderer and BBCode, so skipping tests wit them'); + } + + /** + * Test if the builder can create a "simple" raw mail + */ + public function testBuilderWithRawEmail() + { + $builder = new SampleMailBuilder($this->l10n, $this->baseUrl, $this->config, new NullLogger()); + + $testEmail = $builder + ->withMessage('Subject', 'Html', 'text') + ->withRecipient('recipient@friendica.local') + ->withSender('Sender', 'sender@friendica.local', 'no-reply@friendica.local') + ->forUser(['uid' => 100]) + ->build(true); + + $this->assertEmail($testEmail, [ + 'subject' => 'Subject', + 'html' => 'Html', + 'text' => 'text', + 'toAddress' => 'recipient@friendica.local', + 'fromName' => 'Sender', + 'fromAddress' => 'sender@friendica.local', + 'noReply' => 'no-reply@friendica.local', + 'uid' => 100, + 'headers' => $this->defaultHeaders, + ]); + } + + /** + * Test if the builder throws an exception in case no recipient + * + * @expectedException \Friendica\Network\HTTPException\InternalServerErrorException + * @expectedExceptionMessage Recipient address is missing. + */ + public function testBuilderWithEmptyMail() + { + $builder = new SampleMailBuilder($this->l10n, $this->baseUrl, $this->config, new NullLogger()); + + $builder->build(true); + } + + /** + * Test if the builder throws an exception in case no sender + * + * @expectedException \Friendica\Network\HTTPException\InternalServerErrorException + * @expectedExceptionMessage Sender address or name is missing. + */ + public function testBuilderWithEmptySender() + { + $builder = new SampleMailBuilder($this->l10n, $this->baseUrl, $this->config, new NullLogger()); + + $builder + ->withRecipient('test@friendica.local') + ->build(true); + } + + /** + * Test if the builder is capable of creating "empty" mails if needed (not the decision of the builder if so ..) + */ + public function testBuilderWithoutMessage() + { + $builder = new SampleMailBuilder($this->l10n, $this->baseUrl, $this->config, new NullLogger()); + + $testEmail = $builder + ->withRecipient('recipient@friendica.local') + ->withSender('Sender', 'sender@friendica.local') + ->build(true); + + $this->assertEmail($testEmail, [ + 'toAddress' => 'recipient@friendica.local', + 'fromName' => 'Sender', + 'fromAddress' => 'sender@friendica.local', + 'noReply' => 'sender@friendica.local', // no-reply is set same as address in case it's not set + 'headers' => $this->defaultHeaders, + ]); + } + + /** + * Test if the builder sets for the text the same as for + */ + public function testBuilderWithJustPreamble() + { + $builder = new SampleMailBuilder($this->l10n, $this->baseUrl, $this->config, new NullLogger()); + + $testEmail = $builder + ->withRecipient('recipient@friendica.local') + ->withSender('Sender', 'sender@friendica.local') + ->build(true); + + $this->assertEmail($testEmail, [ + 'toAddress' => 'recipient@friendica.local', + 'fromName' => 'Sender', + 'fromAddress' => 'sender@friendica.local', + 'noReply' => 'sender@friendica.local', // no-reply is set same as address in case it's not set, + 'headers' => $this->defaultHeaders, + ]); + } +} diff --git a/tests/src/Util/Emailer/SystemMailBuilderTest.php b/tests/src/Util/Emailer/SystemMailBuilderTest.php new file mode 100644 index 000000000..e85560276 --- /dev/null +++ b/tests/src/Util/Emailer/SystemMailBuilderTest.php @@ -0,0 +1,57 @@ +setUpVfsDir(); + + $this->config = \Mockery::mock(IConfig::class); + $this->config->shouldReceive('get')->with('config', 'admin_name')->andReturn('Admin'); + $this->l10n = \Mockery::mock(L10n::class); + $this->l10n->shouldReceive('t')->andReturnUsing(function ($msg) { + return $msg; + }); + $this->baseUrl = \Mockery::mock(BaseURL::class); + $this->baseUrl->shouldReceive('getHostname')->andReturn('friendica.local'); + $this->baseUrl->shouldReceive('get')->andReturn('http://friendica.local'); + + $this->defaultHeaders = ""; + } + + /** + * Test if the builder instance can get created + */ + public function testBuilderInstance() + { + $builder = new SystemMailBuilder($this->l10n, $this->baseUrl, $this->config, new NullLogger(), 'moreply@friendica.local', 'FriendicaSite'); + + $this->assertInstanceOf(MailBuilder::class, $builder); + $this->assertInstanceOf(SystemMailBuilder::class, $builder); + } +} diff --git a/view/templates/admin/site.tpl b/view/templates/admin/site.tpl index 19774b425..9ccda33ee 100644 --- a/view/templates/admin/site.tpl +++ b/view/templates/admin/site.tpl @@ -15,6 +15,7 @@ {{include file="field_input.tpl" field=$sitename}} {{include file="field_input.tpl" field=$sender_email}} {{include file="field_textarea.tpl" field=$banner}} + {{include file="field_input.tpl" field=$email_banner}} {{include file="field_input.tpl" field=$shortcut_icon}} {{include file="field_input.tpl" field=$touch_icon}} {{include file="field_textarea.tpl" field=$additional_info}} diff --git a/view/templates/email/html.tpl b/view/templates/email/html.tpl new file mode 100644 index 000000000..103572eae --- /dev/null +++ b/view/templates/email/html.tpl @@ -0,0 +1,23 @@ + + + + {{$title}} + + + + + + + + + +
+ Friendica Banner +
{{$product}}
+
+
+

+ {{$htmlversion nofilter}} +

+ + diff --git a/view/templates/email_notify_html.tpl b/view/templates/email/notify/html.tpl similarity index 100% rename from view/templates/email_notify_html.tpl rename to view/templates/email/notify/html.tpl diff --git a/view/templates/email_notify_text.tpl b/view/templates/email/notify/text.tpl similarity index 100% rename from view/templates/email_notify_text.tpl rename to view/templates/email/notify/text.tpl diff --git a/view/templates/email/system/html.tpl b/view/templates/email/system/html.tpl new file mode 100644 index 000000000..c34ddaba8 --- /dev/null +++ b/view/templates/email/system/html.tpl @@ -0,0 +1,5 @@ + + + + +
{{$htmlversion nofilter}}
{{$thanks}}
{{$site_admin}}
\ No newline at end of file diff --git a/view/templates/email/system/text.tpl b/view/templates/email/system/text.tpl new file mode 100644 index 000000000..4dd7c8ccb --- /dev/null +++ b/view/templates/email/system/text.tpl @@ -0,0 +1,7 @@ + +{{$preamble nofilter}} + +{{$textversion nofilter}} + +{{$thanks nofilter}} +{{$site_admin nofilter}}