diff --git a/core/classes/Core/Alert.php b/core/classes/Alerts/Alert.php similarity index 98% rename from core/classes/Core/Alert.php rename to core/classes/Alerts/Alert.php index 6c068d5cf5..d36d972742 100644 --- a/core/classes/Core/Alert.php +++ b/core/classes/Alerts/Alert.php @@ -70,6 +70,8 @@ public static function send(int $userId, string $title, ?string $content, ?strin 'created' => date('U'), 'bypass_purify' => $skipPurify, ]); + + // TODO: AlertCreatedEvent for discord to listen to to DM the user on discord? } /** diff --git a/core/classes/Alerts/AlertTemplate.php b/core/classes/Alerts/AlertTemplate.php new file mode 100644 index 0000000000..2f8762f8a5 --- /dev/null +++ b/core/classes/Alerts/AlertTemplate.php @@ -0,0 +1,14 @@ +link === null && $this->content === null) { + throw new InvalidArgumentException('Either link or content must be provided'); + } + } +} diff --git a/core/classes/Database/DB.php b/core/classes/Database/DB.php index ec12e236f1..dfe2202000 100644 --- a/core/classes/Database/DB.php +++ b/core/classes/Database/DB.php @@ -356,12 +356,12 @@ public function insert(string $table, array $fields = []): bool /** * Perform an UPDATE query on a table. * - * @param string $table The table to update. - * @param mixed $where The where clause. If not an array, it will be used for "id" column lookup. - * @param array $fields Array of data in "column => value" format to update. - * @return bool Whether an error occurred or not. + * @param string $table The table to update. + * @param array|int $where The where clause. If not an array, it will be used for "id" column lookup. + * @param array $fields Array of data in "column => value" format to update. + * @return bool Whether an error occurred or not. */ - public function update(string $table, $where, array $fields): bool + public function update(string $table, array|int $where, array $fields): bool { $set = ''; $x = 1; diff --git a/core/classes/Core/Email.php b/core/classes/Emails/Email.php similarity index 60% rename from core/classes/Core/Email.php rename to core/classes/Emails/Email.php index 1652ca9dc9..184e18899c 100644 --- a/core/classes/Core/Email.php +++ b/core/classes/Emails/Email.php @@ -16,41 +16,61 @@ class Email { public const EMAIL_MAX_LENGTH = 75000; - public const REGISTRATION = 1; - public const FORGOT_PASSWORD = 3; - public const API_REGISTRATION = 4; - public const FORUM_TOPIC_REPLY = 5; - public const MASS_MESSAGE = 6; + public const TEST_EMAIL = 'TestEmail'; + public const MASS_MESSAGE = 'MassMessage'; - /** - * @var array Placeholders for email templates - */ - private static array $_message_placeholders = []; + public static function send(User $recipient, EmailTemplate $emailTemplate) + { + $languageCode = DB::getInstance()->get('languages', ['id', '=', $recipient->data()->language_id])->first()->short_code; + + return self::sendInternal( + str_replace('EmailTemplate', '', $emailTemplate::class), + $recipient, + $emailTemplate->subject()->translate($languageCode), + $emailTemplate->renderContent($languageCode) + ); + } + + public static function sendRaw(string $mailer, User $recipient, string $subject, string $content) + { + return self::sendInternal($mailer, $recipient, $subject, $content); + } /** - * Send an email. + * Internal helper method to handle common email sending logic. * - * @param array $recipient Array containing `'email'` and `'name'` strings for the recipient of the email. - * @param string $subject Subject of the email. - * @param string $message Message of the email. - * @param array|null $reply_to Array containing `'email'` and `'name'` strings for the reply-to address, - * if not provided the default setting will be used. - * @return bool|array Returns true if email sent, otherwise returns an array containing the error. + * @param string $mailer Email mailer identifier + * @param User $recipient Recipient user object + * @param string $subject Email subject + * @param string $content Email content + * @return bool|array Returns true if email sent, otherwise returns an array containing the error */ - public static function send(array $recipient, string $subject, string $message, ?array $reply_to = null) + private static function sendInternal(string $mailer, User $recipient, string $subject, string $content) { $email = [ - 'to' => $recipient, - 'subject' => $subject, - 'message' => $message, - 'replyto' => $reply_to ?? self::getReplyTo(), + 'to' => [ + 'email' => $recipient->data()->email, + 'name' => $recipient->getDisplayname(), + ], + 'subject' => SITE_NAME . ' - ' . $subject, + 'message' => $content, + 'replyto' => self::getReplyTo(), ]; - if (Settings::get('phpmailer') == '1') { - return self::sendMailer($email); + $result = Settings::get('phpmailer') == '1' + ? self::sendMailer($email) + : self::sendPHP($email); + + if (isset($result['error'])) { + DB::getInstance()->insert('email_errors', [ + 'mailer' => $mailer, + 'content' => $result['error'], + 'at' => date('U'), + 'user_id' => $recipient->data()->id, + ]); } - return self::sendPHP($email); + return $result; } /** @@ -108,12 +128,10 @@ private static function sendPHP(array $email) private static function sendMailer(array $email) { try { - // Initialise PHPMailer $mail = new PHPMailer(true); $mail->IsSMTP(); $mail->SMTPDebug = SMTP::DEBUG_OFF; - $mail->Debugoutput = 'html'; $mail->CharSet = PHPMailer::CHARSET_UTF8; $mail->Encoding = PHPMailer::ENCODING_BASE64; $mail->Timeout = 15; @@ -156,42 +174,4 @@ private static function sendMailer(array $email) ]; } } - - /** - * Add a custom placeholder/variable for email messages. - * - * @param string $key The key to use for the placeholder, should be enclosed in square brackets. - * @param string|Closure(Language, string): string $value The value to replace the placeholder with. - */ - public static function addPlaceholder(string $key, $value): void - { - self::$_message_placeholders[$key] = $value; - } - - /** - * Format an email template and replace placeholders. - * - * @param string $email Name of email to format. - * @param Language $viewing_language Instance of Language class to use for translations. - * @return string Formatted email. - */ - public static function formatEmail(string $email, Language $viewing_language): string - { - $placeholders = array_keys(self::$_message_placeholders); - - $placeholder_values = []; - foreach (self::$_message_placeholders as $value) { - if (is_callable($value)) { - $placeholder_values[] = $value($viewing_language, $email); - } else { - $placeholder_values[] = $value; - } - } - - return str_replace( - $placeholders, - $placeholder_values, - file_get_contents(implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'custom', 'templates', TEMPLATE, 'email', $email . '.html'])) - ); - } } diff --git a/core/classes/Emails/EmailTemplate.php b/core/classes/Emails/EmailTemplate.php new file mode 100644 index 0000000000..7592fe14d4 --- /dev/null +++ b/core/classes/Emails/EmailTemplate.php @@ -0,0 +1,78 @@ + Placeholders for this email template + */ + private array $_placeholders = []; + + public function __construct() + { + $this->addPlaceholder('[Sitename]', Output::getClean(SITE_NAME)); + $this->addPlaceholder('[Greeting]', new LanguageKey('emails', 'greeting')); + $this->addPlaceholder('[Thanks]', new LanguageKey('emails', 'thanks')); + } + + /** + * Returns the snake_case representation of the email template name, + * derived from the class name with "EmailTemplate" removed. + * For example: RegisterEmailTemplate -> "register", ForgotPasswordEmailTemplate -> "forgot_password". + */ + private function name(): string + { + $baseName = str_replace('EmailTemplate', '', static::class); + + return strtolower(preg_replace('/(?_placeholders[$key] = $value; + } + + final public function renderContent(string $languageCode): string + { + $placeholderKeys = array_keys($this->_placeholders); + $placeholderValues = []; + + foreach ($this->_placeholders as $placeholder) { + if ($placeholder instanceof LanguageKey) { + $placeholderValues[] = $placeholder->translate($languageCode); + } else { + $placeholderValues[] = $placeholder; + } + } + + return str_replace( + $placeholderKeys, + $placeholderValues, + file_get_contents($this->getPath()), + ); + } + + private function getPath(): string + { + $name = $this->name(); + + $customPath = implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'custom', 'templates', TEMPLATE, 'email', $name . '.html']); + if (file_exists($customPath)) { + return $customPath; + } + + $defaultPath = implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'custom', 'templates', 'DefaultRevamp', 'email', $name . '.html']); + if (file_exists($defaultPath)) { + return $defaultPath; + } + + throw new Exception('Email template not found: ' . $name); + } +} diff --git a/core/classes/Misc/MentionsParser.php b/core/classes/Misc/MentionsParser.php index 6b76a5b48d..49ed2b57b7 100644 --- a/core/classes/Misc/MentionsParser.php +++ b/core/classes/Misc/MentionsParser.php @@ -37,7 +37,7 @@ public static function parse(int $author_id, string $content): string * * @return string Parsed post content. */ - public static function parseAndNotify(int $author_id, string $content, string $url, string $notificationType, LanguageKey $notificationTitle): string + public static function parseAndNotify(int $author_id, string $content, string $notificationType, AlertTemplate $notificationAlertTemplate, EmailTemplate $notificationEmailTemplate): string { $receipients = self::getRecipients($content, $author_id); @@ -46,14 +46,10 @@ public static function parseAndNotify(int $author_id, string $content, string $u $notification = new Notification( $notificationType, - $notificationTitle, - // TODO: emails content - right now it will be plaintext and not use a template - $content, + $notificationAlertTemplate, + $notificationEmailTemplate, $notificationRecipients, $author_id, - null, - false, - $url, ); $notification->send(); diff --git a/core/classes/Misc/Report.php b/core/classes/Misc/Report.php index 6e5e03a3d0..cd1006100f 100644 --- a/core/classes/Misc/Report.php +++ b/core/classes/Misc/Report.php @@ -36,18 +36,21 @@ public static function create(Language $language, User $user_reporting, User $re // Alert moderators $moderators = DB::getInstance()->query('SELECT DISTINCT(nl2_users.id) AS id FROM nl2_users LEFT JOIN nl2_users_groups ON nl2_users.id = nl2_users_groups.user_id WHERE group_id IN (SELECT id FROM nl2_groups WHERE permissions LIKE \'%"modcp.reports":1%\')')->results(); + $link = rtrim(URL::getSelfURL(), '/') . URL::build('/panel/users/reports/', 'id=' . $id); $notification = new Notification( 'report', - new LanguageKey('moderator', 'report_alert'), - new LanguageKey('moderator', 'report_email', [ - 'linkStart' => '', - 'linkEnd' => '', - ]), + new AlertTemplate( + new LanguageKey('moderator', 'report_alert'), + null, + $link, + ), + new ReportCreatedEmailTemplate( + $link, + $reported_user, + $user_reporting, + ), array_map(fn ($moderator) => $moderator->id, $moderators), $user_reporting->data()->id, - null, - false, - rtrim(URL::getSelfURL(), '/') . URL::build('/panel/users/reports/', 'id=' . $id), ); $notification->send(); diff --git a/core/migrations/20250503090934_convert_email_error_type_to_string.php b/core/migrations/20250503090934_convert_email_error_type_to_string.php new file mode 100644 index 0000000000..37745dc1fd --- /dev/null +++ b/core/migrations/20250503090934_convert_email_error_type_to_string.php @@ -0,0 +1,38 @@ + RegisterEmailTemplate::class, + 3 => ForgotPasswordEmailTemplate::class, + 5 => ForumTopicReplyEmailTemplate::class, + 6 => MassMessageEmailTemplate::class, + ]; + + public function change(): void + { + $this->table('nl2_email_errors') + ->renameColumn('type', 'mailer') + ->changeColumn('mailer', 'string', ['limit' => 255]) + ->update(); + + $email_errors = DB::getInstance()->query('SELECT * FROM nl2_email_errors')->results(); + + foreach ($email_errors as $error) { + $type = $error->mailer; + if (isset(self::CONVERSION_MAP[$type])) { + DB::getInstance()->update('email_errors', $error->id, [ + 'mailer' => self::CONVERSION_MAP[$type], + ]); + } else { + DB::getInstance()->update('email_errors', $error->id, [ + 'mailer' => 'unknown', + ]); + } + } + } +} diff --git a/custom/panel_templates/Default/core/emails.tpl b/custom/panel_templates/Default/core/emails.tpl index 46142f0b78..910930d95b 100644 --- a/custom/panel_templates/Default/core/emails.tpl +++ b/custom/panel_templates/Default/core/emails.tpl @@ -38,7 +38,6 @@ {if isset($MASS_MESSAGE_LINK)} {$MASS_MESSAGE} {/if} - {$EDIT_EMAIL_MESSAGES} {$EMAIL_ERRORS} {$SEND_TEST_EMAIL} diff --git a/custom/panel_templates/Default/core/emails_edit_messages.tpl b/custom/panel_templates/Default/core/emails_edit_messages.tpl deleted file mode 100644 index 2af9163b86..0000000000 --- a/custom/panel_templates/Default/core/emails_edit_messages.tpl +++ /dev/null @@ -1,136 +0,0 @@ -{include file='header.tpl'} - - - - -
- - - {include file='sidebar.tpl'} - - -
- - -
- - - {include file='navbar.tpl'} - - -
- - -
-

{$EMAILS_MESSAGES}

- -
- - - {include file='includes/update.tpl'} - -
-
- {$BACK} -
- - - {include file='includes/alerts.tpl'} - -
-

{$OPTIONS}

-
-
- -
- -
-
-
- -
- -
-
-
- - -
- -
-
-
-
- {foreach from=$EMAILS_LIST item=item} -

{$item[1]}

-
-
- -
- -
-
-
- -
- -
-
-
- -
-
-
- {/foreach} -
- - -
-
- -
-
- - -
- - -
- - -
- - {include file='footer.tpl'} - - -
- - -
- - {include file='scripts.tpl'} - - - - \ No newline at end of file diff --git a/custom/panel_templates/Default/core/emails_edit_messages_preview.tpl b/custom/panel_templates/Default/core/emails_edit_messages_preview.tpl deleted file mode 100644 index 887ef479eb..0000000000 --- a/custom/panel_templates/Default/core/emails_edit_messages_preview.tpl +++ /dev/null @@ -1 +0,0 @@ -{$MESSAGE} diff --git a/custom/panel_templates/Default/core/emails_errors.tpl b/custom/panel_templates/Default/core/emails_errors.tpl index 5203dd1b48..596b387cce 100644 --- a/custom/panel_templates/Default/core/emails_errors.tpl +++ b/custom/panel_templates/Default/core/emails_errors.tpl @@ -49,39 +49,35 @@ {include file='includes/alerts.tpl'} {if isset($NO_ERRORS)} - {$NO_ERRORS} + {$NO_ERRORS} {else} -
- - - - - - - - - - - {foreach from=$EMAIL_ERRORS_ARRAY item=item} - - - - - - - {/foreach} - -
{$TYPE}{$DATE}{$USERNAME}{$ACTIONS}
{$item.type}{$item.date}{$item.user} - - -
-
- {$PAGINATION} +
+ + + + + + + + + + + {foreach from=$EMAIL_ERRORS_ARRAY item=item} + + + + + + + {/foreach} + +
{$MAILER}{$DATE}{$USERNAME}{$ACTIONS}
{$item.mailer}{$item.date}{$item.user} + + +
+
+ {$PAGINATION} {/if} - - @@ -165,4 +161,4 @@ - \ No newline at end of file + diff --git a/custom/panel_templates/Default/core/emails_errors_view.tpl b/custom/panel_templates/Default/core/emails_errors_view.tpl index b148539de0..7e44228d98 100644 --- a/custom/panel_templates/Default/core/emails_errors_view.tpl +++ b/custom/panel_templates/Default/core/emails_errors_view.tpl @@ -47,8 +47,8 @@
- - + + @@ -67,15 +67,10 @@
{$ACTIONS}
- {if $TYPE_ID eq 1} - {if isset($VALIDATE_USER_TEXT)} - {$VALIDATE_USER_TEXT} - {/if} - {elseif $TYPE_ID eq 4} - {if isset($SHOW_REGISTRATION_LINK)} - - {/if} + {if $MAILER_VALUE eq "Register"} + {if isset($VALIDATE_USER_TEXT)} + {$VALIDATE_USER_TEXT} + {/if} {/if} {$DELETE_ERROR} @@ -158,4 +153,4 @@ - \ No newline at end of file + diff --git a/custom/templates/DefaultRevamp/email/api_register.html b/custom/templates/DefaultRevamp/email/api_register.html deleted file mode 100755 index 7f8437adfd..0000000000 --- a/custom/templates/DefaultRevamp/email/api_register.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - [Sitename] - - - -
-

[Greeting]

-

[Message]

- [Link] -
- [Thanks]
[Sitename] -
- - - \ No newline at end of file diff --git a/custom/templates/DefaultRevamp/email/forgot_password.html b/custom/templates/DefaultRevamp/email/forgot_password.html new file mode 100755 index 0000000000..9f633e50f0 --- /dev/null +++ b/custom/templates/DefaultRevamp/email/forgot_password.html @@ -0,0 +1,17 @@ + + + + + [Sitename] + + + +
+

[Greeting]

+

[Message]

+ [Link] +
+ [Thanks]
[Sitename] +
+ + diff --git a/custom/templates/DefaultRevamp/email/change_password.html b/custom/templates/DefaultRevamp/email/forum_topic_mention.html old mode 100755 new mode 100644 similarity index 88% rename from custom/templates/DefaultRevamp/email/change_password.html rename to custom/templates/DefaultRevamp/email/forum_topic_mention.html index 7f8437adfd..7a864f2264 --- a/custom/templates/DefaultRevamp/email/change_password.html +++ b/custom/templates/DefaultRevamp/email/forum_topic_mention.html @@ -3,7 +3,7 @@ - [Sitename] + [Sitename] • [TopicReply] @@ -16,4 +16,4 @@

[Greeting]

- \ No newline at end of file + diff --git a/custom/templates/DefaultRevamp/email/forum_topic_reply.html b/custom/templates/DefaultRevamp/email/forum_topic_reply.html index d93ecadf78..7a864f2264 100644 --- a/custom/templates/DefaultRevamp/email/forum_topic_reply.html +++ b/custom/templates/DefaultRevamp/email/forum_topic_reply.html @@ -10,9 +10,10 @@

[Greeting]

[Message]

+ [Link]
[Thanks]
[Sitename]
- \ No newline at end of file + diff --git a/custom/templates/DefaultRevamp/email/mass_message.html b/custom/templates/DefaultRevamp/email/mass_message.html new file mode 100755 index 0000000000..90ad90241d --- /dev/null +++ b/custom/templates/DefaultRevamp/email/mass_message.html @@ -0,0 +1,16 @@ + + + + + [Sitename] + + + +
+

[Greeting]

+

[Message]

+
+ [Thanks]
[Sitename] +
+ + diff --git a/custom/templates/DefaultRevamp/email/report_created.html b/custom/templates/DefaultRevamp/email/report_created.html new file mode 100755 index 0000000000..9f633e50f0 --- /dev/null +++ b/custom/templates/DefaultRevamp/email/report_created.html @@ -0,0 +1,17 @@ + + + + + [Sitename] + + + +
+

[Greeting]

+

[Message]

+ [Link] +
+ [Thanks]
[Sitename] +
+ + diff --git a/custom/templates/DefaultRevamp/login.tpl b/custom/templates/DefaultRevamp/login.tpl index 2deff3f462..0d88bdc02d 100755 --- a/custom/templates/DefaultRevamp/login.tpl +++ b/custom/templates/DefaultRevamp/login.tpl @@ -19,6 +19,18 @@ {/if} +{if isset($SUCCESS)} +
+ +
+
+
{$SUCCESS_TITLE}
+ {$SUCCESS} +
+
+
+{/if} +
diff --git a/dev/scripts/cli_install.php b/dev/scripts/cli_install.php index 0a34a4bc10..9f816ee2e6 100644 --- a/dev/scripts/cli_install.php +++ b/dev/scripts/cli_install.php @@ -57,11 +57,6 @@ function getEnvVar(string $name, ?string $fallback = null, ?array $valid_values exit(1); } -// check all the required environment variables are set -foreach (['NAMELESS_SITE_NAME', 'NAMELESS_SITE_CONTACT_EMAIL', 'NAMELESS_SITE_OUTGOING_EMAIL', 'NAMELESS_ADMIN_EMAIL'] as $var) { - getEnvVar($var); -} - $start = microtime(true); echo '🗑 Deleting cache directories...' . PHP_EOL; @@ -171,16 +166,16 @@ function getEnvVar(string $name, ?string $fallback = null, ?array $valid_values DatabaseInitialiser::runPreUser(); -Settings::set('sitename', getEnvVar('NAMELESS_SITE_NAME')); -Settings::set('incoming_email', getEnvVar('NAMELESS_SITE_CONTACT_EMAIL')); -Settings::set('outgoing_email', getEnvVar('NAMELESS_SITE_OUTGOING_EMAIL')); +Settings::set('sitename', getEnvVar('NAMELESS_SITE_NAME', 'NamelessMC')); +Settings::set('incoming_email', getEnvVar('NAMELESS_SITE_CONTACT_EMAIL', 'contact@example.com')); +Settings::set('outgoing_email', getEnvVar('NAMELESS_SITE_OUTGOING_EMAIL', 'no-reply@example.com')); Settings::set('email_verification', getEnvVar('NAMELESS_EMAIL_VERIFICATION', '1', ['0', '1'])); echo '👮 Creating admin account...' . PHP_EOL; $username = getEnvVar('NAMELESS_ADMIN_USERNAME', 'admin'); $password = getEnvVar('NAMELESS_ADMIN_PASSWORD', 'password'); -$email = getEnvVar('NAMELESS_ADMIN_EMAIL'); +$email = getEnvVar('NAMELESS_ADMIN_EMAIL', 'admin@example.com'); $user = new User(); $user->create([ @@ -221,6 +216,20 @@ function getEnvVar(string $name, ?string $fallback = null, ?array $valid_values } } +$defaultNotifications = array_filter( + Notification::getTypes(), + static fn ($type) => $type['defaultPreferences']['alert'] || $type['defaultPreferences']['email'] +); + +foreach ($defaultNotifications as $notificationType) { + DB::getInstance()->insert('users_notification_preferences', [ + 'user_id' => 1, + 'type' => $notificationType['key'], + 'alert' => $notificationType['defaultPreferences']['alert'] === true, + 'email' => $notificationType['defaultPreferences']['email'] === true, + ]); +} + DatabaseInitialiser::runPostUser(); Config::set('core.installed', true); diff --git a/dev/scripts/find_unused_language_terms.sh b/dev/scripts/find_unused_language_terms.sh index a5ae0d095b..aa0fbd7bc0 100644 --- a/dev/scripts/find_unused_language_terms.sh +++ b/dev/scripts/find_unused_language_terms.sh @@ -20,7 +20,6 @@ WHITELISTED_TERMS=( "installer/module_forum_description" "installer/module_core_description" "installer/module_members_description" - "user/user_tag_info" ) for FILE in "${FILES[@]}" diff --git a/modules/Core/classes/Core/Notification.php b/modules/Core/classes/Core/Notification.php index 607dc6e0de..293f706bbe 100644 --- a/modules/Core/classes/Core/Notification.php +++ b/modules/Core/classes/Core/Notification.php @@ -11,11 +11,12 @@ class Notification { + private AlertTemplate $_alertTemplate; + private EmailTemplate $_emailTemplate; private int $_authorId; private array $_recipients = []; - private bool $_skipPurify; private string $_type; - private ?string $_alertUrl = null; + private bool $_bypassNotificationSettings; private static array $_types = []; @@ -23,34 +24,31 @@ class Notification { * Instantiate a new notification * * @param string $type Type of notification - * @param string|LanguageKey $title Title of notification - * @param string|LanguageKey $content Notification content. For alerts, if $alertUrl is set, this will ignored. If $alertUrl is not set, this will be the content of the alert. This will always be the content of the email. + * @param AlertTemplate $alertTemplate Alert template + * @param EmailTemplate $emailTemplate Email template * @param int|int[] $recipients Notification recipient or recipients - array of user IDs * @param int $authorId User ID that sent the notification - * @param ?callable $contentCallback Optional callback to perform for each recipient's content - * @param bool $skipPurify Whether to skip content purifying, default false - * @param ?string $alertUrl Optional URL to link to when clicking the alert + * @param bool $bypassNotificationSettings Whether to bypass the user's notification settings * * @throws NotificationTypeNotFoundException */ public function __construct( string $type, - string|LanguageKey $title, - string|LanguageKey $content, + AlertTemplate $alertTemplate, + EmailTemplate $emailTemplate, int|array $recipients, int $authorId, - ?callable $contentCallback = null, - bool $skipPurify = false, - ?string $alertUrl = null, + bool $bypassNotificationSettings = false, ) { if (!in_array($type, array_column(self::getTypes(), 'key'))) { throw new NotificationTypeNotFoundException("Type $type not registered"); } - $this->_authorId = $authorId; - $this->_skipPurify = $skipPurify; $this->_type = $type; - $this->_alertUrl = $alertUrl; + $this->_alertTemplate = $alertTemplate; + $this->_emailTemplate = $emailTemplate; + $this->_authorId = $authorId; + $this->_bypassNotificationSettings = $bypassNotificationSettings; if (!is_array($recipients)) { $recipients = [$recipients]; @@ -60,29 +58,16 @@ public function __construct( return; } - if ($title instanceof LanguageKey || $content instanceof LanguageKey) { - $languageCodes = DB::getInstance()->query( - 'SELECT nl2_users.id, COALESCE(nl2_languages.short_code, NULL) AS `short_code` FROM nl2_users LEFT JOIN nl2_languages ON nl2_languages.id = nl2_users.language_id WHERE nl2_users.id IN (' . implode(',', array_map(static fn ($_) => '?', $recipients)) . ')', - $recipients - )->results(); - $languageCodes = array_column($languageCodes, 'short_code', 'id'); - } - - $this->_recipients = array_map(static function ($recipientId) use ($content, $contentCallback, $skipPurify, $title, $languageCodes) { - $recipientLanguageCode = $languageCodes[$recipientId] ?? DEFAULT_LANGUAGE; - if ($title instanceof LanguageKey) { - $title = $title->translate($recipientLanguageCode); - } - if ($content instanceof LanguageKey) { - $content = $content->translate($recipientLanguageCode); - } - - $newContent = $contentCallback ? $contentCallback($recipientId, $title, $content, $skipPurify) : $content; + $languageCodes = DB::getInstance()->query( + 'SELECT nl2_users.id, COALESCE(nl2_languages.short_code, NULL) AS `short_code` FROM nl2_users LEFT JOIN nl2_languages ON nl2_languages.id = nl2_users.language_id WHERE nl2_users.id IN (' . implode(',', array_map(static fn ($_) => '?', $recipients)) . ')', + $recipients + )->results(); + $languageCodes = array_column($languageCodes, 'short_code', 'id'); + $this->_recipients = array_map(static function ($recipientId) use ($languageCodes) { return [ 'id' => $recipientId, - 'title' => $title, - 'content' => $newContent + 'language_code' => $languageCodes[$recipientId] ?? DEFAULT_LANGUAGE, ]; }, $recipients); } @@ -90,9 +75,14 @@ public function __construct( public function send(): void { /** @var array $recipient */ foreach ($this->_recipients as $recipient) { - $id = $recipient['id']; - $title = $recipient['title']; - $content = $recipient['content']; + $userId = $recipient['id']; + $languageCode = $recipient['language_code']; + + if ($this->_bypassNotificationSettings) { + $this->sendAlert($userId, $languageCode); + $this->sendEmail($userId, $languageCode); + continue; + } $preferences = DB::getInstance()->query( <<_type, $id] + [$this->_type, $userId] )->first(); if ($preferences->alert) { - $this->sendAlert($id, $title, $content); + $this->sendAlert($userId, $languageCode); } if ($preferences->email) { - $this->sendEmail($id, $title, $content); + $this->sendEmail($userId, $languageCode); } } } - private function sendAlert(int $userId, string $title, string $content): void { + private function sendAlert(int $userId, string $languageCode): void { + if ($this->_alertTemplate->title instanceof LanguageKey) { + $title = $this->_alertTemplate->title->translate($languageCode); + } else { + $title = $this->_alertTemplate->title; + } + + if ($this->_alertTemplate->link) { + $content = null; + } else if ($this->_alertTemplate->content instanceof LanguageKey) { + $content = $this->_alertTemplate->content->translate($languageCode); + } else { + $content = $this->_alertTemplate->content; + } + Alert::send( $userId, $title, - // if $this->_alertUrl is set, we don't want to send the content as the alert content - $this->_alertUrl ? null : $content, - $this->_alertUrl, - $this->_skipPurify + // if the alert has a link set, we don't want to send the content as the alert content + $content, + $this->_alertTemplate->link, ); } - private function sendEmail(int $userId, string $title, string $content): void { + private function sendEmail(int $userId, string $languageCode): void { + if ($this->_emailTemplate->subject() instanceof LanguageKey) { + $content = $this->_emailTemplate->subject()->translate($languageCode); + } else { + $content = $this->_emailTemplate->subject(); + } + $task = (new SendEmail())->fromNew( Module::getIdFromName('Core'), 'Send Email Notification', [ - 'content' => $content, - 'title' => SITE_NAME . ' - ' . $title, + 'subject' => $content, + 'content' => $this->_emailTemplate->renderContent($languageCode), + 'mailer' => str_replace('EmailTemplate', '', $this->_emailTemplate::class), ], date('U'), // TODO: schedule a date/time? 'User', diff --git a/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php b/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php new file mode 100644 index 0000000000..ee65e2229e --- /dev/null +++ b/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php @@ -0,0 +1,19 @@ +addPlaceholder('[Link]', $link); + $this->addPlaceholder('[Message]', new LanguageKey('emails', 'forgot_password_message')); + + parent::__construct(); + } + + public function subject(): LanguageKey + { + return new LanguageKey('emails', 'forgot_password_subject'); + } +} diff --git a/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php b/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php new file mode 100644 index 0000000000..b721936b64 --- /dev/null +++ b/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php @@ -0,0 +1,20 @@ +subject = $subject; + + $this->addPlaceholder('[Message]', $content); + + parent::__construct(); + } + + public function subject(): string + { + return $this->subject; + } +} diff --git a/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php b/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php new file mode 100644 index 0000000000..f9c627362f --- /dev/null +++ b/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php @@ -0,0 +1,20 @@ +addPlaceholder('[Link]', $link); + $this->addPlaceholder('[Message]', new LanguageKey('emails', 'register_message')); + + parent::__construct(); + } + + public function subject(): LanguageKey + { + return new LanguageKey('emails', 'register_subject'); + } +} diff --git a/modules/Core/classes/Email_Templates/ReportCreatedEmailTemplate.php b/modules/Core/classes/Email_Templates/ReportCreatedEmailTemplate.php new file mode 100644 index 0000000000..1ea2ecf11e --- /dev/null +++ b/modules/Core/classes/Email_Templates/ReportCreatedEmailTemplate.php @@ -0,0 +1,20 @@ +addPlaceholder('[Link]', $link); + $this->addPlaceholder('[Message]', new LanguageKey('emails', 'report_message', [ + 'reported' => $reported->getDisplayname(), + 'author' => $author->getDisplayname(), + ])); + + parent::__construct(); + } + + public function subject(): LanguageKey + { + return new LanguageKey('emails', 'report_subject'); + } +} diff --git a/modules/Core/classes/Events/GenerateMassMessageContentEvent.php b/modules/Core/classes/Events/GenerateMassMessageContentEvent.php new file mode 100644 index 0000000000..ca7f27e402 --- /dev/null +++ b/modules/Core/classes/Events/GenerateMassMessageContentEvent.php @@ -0,0 +1,18 @@ +content = $content; + $this->skip_purify = $skip_purify; + $this->title = $title; + } + + public static function internal(): bool { + return true; + } +} diff --git a/modules/Core/classes/Events/GenerateNotificationContentEvent.php b/modules/Core/classes/Events/GenerateNotificationContentEvent.php deleted file mode 100644 index 1ac9a3c184..0000000000 --- a/modules/Core/classes/Events/GenerateNotificationContentEvent.php +++ /dev/null @@ -1,28 +0,0 @@ -content = $content; - $this->skip_purify = $skip_purify; - $this->title = $title; - $this->user = $user; - } - - public static function name(): string { - return 'generateNotificationContent'; - } - - public static function description(): string { - return (new Language())->get('admin', 'generate_notification_content_hook_info'); - } - - public static function internal(): bool { - return true; - } -} diff --git a/modules/Core/classes/Misc/Core_Emails.php b/modules/Core/classes/Misc/Core_Emails.php deleted file mode 100644 index ebf0a35d2c..0000000000 --- a/modules/Core/classes/Misc/Core_Emails.php +++ /dev/null @@ -1,34 +0,0 @@ - $email_address, 'name' => $username], - SITE_NAME . ' - ' . $language->get('emails', 'register_subject'), - str_replace('[Link]', $link, Email::formatEmail('register', $language)), - ); - - if (isset($sent['error'])) { - DB::getInstance()->insert('email_errors', [ - 'type' => Email::REGISTRATION, - 'content' => $sent['error'], - 'at' => date('U'), - 'user_id' => $user_id - ]); - - return false; - } - - return true; - } -} diff --git a/modules/Core/classes/Tasks/MassMessage.php b/modules/Core/classes/Tasks/MassMessage.php index 1347a3d65a..94b54d00fb 100644 --- a/modules/Core/classes/Tasks/MassMessage.php +++ b/modules/Core/classes/Tasks/MassMessage.php @@ -32,14 +32,27 @@ public function run(): string { $whereVars ); + $content = $this->getData()['content']; + $title = $this->getData()['title']; + $skipPurify = $this->getData()['skip_purify']; + + $event = new GenerateMassMessageContentEvent($content, $title, $skipPurify); + EventHandler::executeEvent($event); + $content = $event->content; + $notification = new Notification( - $this->getData()['type'], - $this->getData()['title'], - $this->getData()['content'], + 'mass_message', + new AlertTemplate( + $title, + $content, + ), + new MassMessageEmailTemplate( + $title, + $content, + ), array_map(static fn ($r) => $r->id, $recipients->results()), $this->getUserId(), - $this->getData()['callback'], - $this->getData()['skip_purify'] ?? false + (bool) $this->getData()['bypass_notification_settings'], ); $notification->send(); @@ -48,12 +61,4 @@ public function run(): string { return $nextStatus; } - - public static function parseContent(int $userId, string $title, string $content, bool $skipPurify = false): string { - $user = new User($userId); - $event = new GenerateNotificationContentEvent($content, $title, $user, $skipPurify); - EventHandler::executeEvent($event); - - return $event->content; - } } diff --git a/modules/Core/classes/Tasks/SendEmail.php b/modules/Core/classes/Tasks/SendEmail.php index 8bc2b0567a..7d89149460 100644 --- a/modules/Core/classes/Tasks/SendEmail.php +++ b/modules/Core/classes/Tasks/SendEmail.php @@ -26,7 +26,7 @@ public function run(): string { $validate = Validate::check( $this->getData(), [ - 'title' => [ + 'subject' => [ Validate::REQUIRED => true, Validate::MIN => 1, ], @@ -46,25 +46,14 @@ public function run(): string { return Task::STATUS_ERROR; } - $username = $user->getDisplayname(); - $title = Output::getPurified($this->getData()['title']); - - $content = $this->getData()['content']; - - $sent = Email::send( - ['email' => $user->data()->email, 'name' => $username], - $title, - $content, + $sent = Email::sendRaw( + $this->getData()['mailer'], + $user, + $this->getData()['subject'], + $this->getData()['content'], ); if (isset($sent['error'])) { - DB::getInstance()->insert('email_errors', [ - 'type' => Email::MASS_MESSAGE, - 'content' => $sent['error'], - 'at' => date('U'), - 'user_id' => $this->getEntityId(), - ]); - $this->setOutput([ 'errors' => [$language->get('admin', 'email_task_error')], 'data' => $sent['error'], diff --git a/modules/Core/hooks/MentionsHook.php b/modules/Core/hooks/MentionsHook.php index 7e91a6e5b3..a4cc018b8e 100644 --- a/modules/Core/hooks/MentionsHook.php +++ b/modules/Core/hooks/MentionsHook.php @@ -20,13 +20,14 @@ class MentionsHook extends HookBase { */ public static function preCreate(AbstractEvent $event): void { if (!empty($event->content) && isset($event->user)) { - if (isset($event->alert_url, $event->mention_notification_type, $event->mention_notification_title)) { + // TODO: better subclassing? ie: $event instanceof MentionableEvent? + if (isset($event->mention_notification_type, $event->mention_notification_alert_template, $event->mention_notification_email_template)) { $event->content = MentionsParser::parseAndNotify( $event->user->data()->id, $event->content, - $event->alert_url, $event->mention_notification_type, - $event->mention_notification_title + $event->mention_notification_alert_template, + $event->mention_notification_email_template, ); } else { $event->content = MentionsParser::parse( diff --git a/modules/Core/includes/endpoints/RegisterEndpoint.php b/modules/Core/includes/endpoints/RegisterEndpoint.php index 063b22ac9b..391b5287ba 100644 --- a/modules/Core/includes/endpoints/RegisterEndpoint.php +++ b/modules/Core/includes/endpoints/RegisterEndpoint.php @@ -184,7 +184,6 @@ private function createUser(Nameless2API $api, string $username, string $email, * @param string $username The username of the new user to create * @param string $email The email of the new user * @see Nameless2API::register() - * */ private function sendRegistrationEmail(Nameless2API $api, string $username, string $email): void { // Generate random code @@ -194,26 +193,15 @@ private function sendRegistrationEmail(Nameless2API $api, string $username, stri $user_id = $this->createUser($api, $username, $email, false, $code); $user_id = $user_id['user_id']; - // Get link + template - $link = URL::getSelfURL() . ltrim(URL::build('/complete_signup/', 'c=' . urlencode($code)), '/'); - - $sent = Email::send( - ['email' => $email, 'name' => $username], - SITE_NAME . ' - ' . $api->getLanguage()->get('emails', 'register_subject'), - str_replace('[Link]', $link, Email::formatEmail('register', $api->getLanguage())), + $email = Email::send( + new User($user_id), + new RegisterEmailTemplate($code), ); - if (isset($sent['error'])) { - $api->getDb()->insert('email_errors', [ - 'type' => Email::API_REGISTRATION, - 'content' => $sent['error'], - 'at' => date('U'), - 'user_id' => $user_id - ]); - - $api->throwError(CoreApiErrors::ERROR_UNABLE_TO_SEND_REGISTRATION_EMAIL, null, Response::HTTP_INTERNAL_SERVER_ERROR); + if ($email === true) { + $api->returnArray(['message' => $api->getLanguage()->get('api', 'finish_registration_email')]); } - $api->returnArray(['message' => $api->getLanguage()->get('api', 'finish_registration_email')]); + $api->throwError(CoreApiErrors::ERROR_UNABLE_TO_SEND_REGISTRATION_EMAIL, null, Response::HTTP_INTERNAL_SERVER_ERROR); } } diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index 28afd1c1a5..c04093ed5b 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -26,7 +26,6 @@ "admin/api_info": "The API allows for plugins and other services to interact with your website, such as the {{pluginLinkStart}}official Nameless plugin{{pluginLinkEnd}} and the {{botLinkStart}}official NamelessMC Discord Bot{{botLinkEnd}}.", "admin/api_key": "API Key", "admin/api_key_regenerated": "The API key has been regenerated successfully.", - "admin/api_registration_email": "API Registration Email", "admin/api_settings_updated_successfully": "API settings updated successfully.", "admin/api_url": "API URL", "admin/at_least_one_external": "Please enter at least 1 external group (Minecraft or Discord)", @@ -160,7 +159,6 @@ "admin/drag_files_here": "Drag files here to upload.", "admin/dropdown_items": "Dropdown Items", "admin/dropdown_name": "Dropdown Name", - "admin/edit_email_messages": "Email Messages", "admin/editable": "Editable", "admin/editing_announcement": "Editing Announcement", "admin/editing_announcement_failure": "Announcement update failed.", @@ -168,8 +166,6 @@ "admin/editing_hook": "Editing Webhook", "admin/editing_integration_for_x": "Editing {{integration}} integration for {{user}}", "admin/editing_integration_x": "Editing integration {{integration}}", - "admin/editing_language": "Editing Language", - "admin/editing_messages": "Editing Messages", "admin/editing_page_x": "Editing Page {{page}}", "admin/editing_profile_field": "Editing Profile Field", "admin/editing_reaction": "Editing Reaction", @@ -181,18 +177,11 @@ "admin/email_errors": "Email Errors", "admin/email_errors_logged": "Email errors have been logged", "admin/email_errors_purged_successfully": "Email errors have been purged successfully.", - "admin/email_language_info": "Not seeing your language? Make sure its language file is writable by your webserver in \/custom\/languages\/.", "admin/email_logs": "Mass Emails", - "admin/email_message_greeting": "Greeting", - "admin/email_message_message": "Message", - "admin/email_message_options": "Options", "admin/email_message_subject": "Subject", - "admin/email_message_thanks": "Thanks", "admin/email_password_hidden": "The password is not shown for security reasons.", "admin/email_port": "Port", "admin/email_port_invalid": "Please insert a valid email port.", - "admin/email_preview_popup": "Preview", - "admin/email_preview_popup_message": "Click here to see a preview of the email.", "admin/email_resend_failed": "Email resend failed, please check your email settings.", "admin/email_resent_successfully": "Email resent successfully.", "admin/email_settings_updated_successfully": "Email settings have been updated successfully.", @@ -254,11 +243,8 @@ "admin/edit_user_tfa_disabled": "Two factor authentication has successfully been disabled for this user.", "admin/disable_tfa": "Disable 2FA", "admin/force_www": "Force www?", - "admin/forgot_password_email": "Forgot Password Email", "admin/forum_posts": "Display on Forum", - "admin/forum_topic_reply_email": "Forum Topic Reply", "admin/general_settings": "General Settings", - "admin/generate_notification_content_hook_info": "Generates notification content before sending", "admin/generate_sitemap": "Generate Sitemap", "admin/google_analytics": "Google Analytics", "admin/google_analytics_help": "Add Google Analytics to your website to track visitors and statistics. You will need to create a Google Analytics account to use this functionality. Enter your Google Analytics Web Property ID. The ID looks like UA-XXXXA-X and you can find it in your account information or in the tracking code provided by Google.", @@ -590,8 +576,6 @@ "admin/registered": "Registered", "admin/registration": "Registration", "admin/registration_disabled_message": "Registration disabled message", - "admin/registration_email": "Registration Email", - "admin/registration_link": "Registration Link", "admin/registration_settings_updated": "Registration settings updated successfully.", "admin/registrations": "Registrations", "admin/report_hook_info": "Report creation", @@ -648,7 +632,6 @@ "admin/show_ip_on_status_page": "Show IP on status page?", "admin/show_ip_on_status_page_info": "If this is enabled, users will be able to view and copy the IP address when viewing the Status page.", "admin/show_nickname_instead_of_username": "Show user's nickname instead of username?", - "admin/show_registration_link": "Show registration link", "admin/sitemap": "Sitemap", "admin/sitemap_generated": "Sitemap generated successfully", "admin/sitemap_last_generated_x": "The sitemap was last generated {{generatedAt}}.", @@ -782,6 +765,7 @@ "admin/integration_settings_does_not_exist": "Integration settings file for integration {{integration}} does not exist", "admin/top": "Top", "admin/footer": "Footer", + "admin/mailer": "Mailer", "api/account_validated": "Account validated successfully", "api/finish_registration_email": "Please check your emails to complete registration.", "api/finish_registration_link": "Please click on the following link to complete registration:", @@ -793,14 +777,16 @@ "api/unknown_error": "Unknown error", "api/username_updated": "Username updated successfully", "api/webhook_added": "The webhook has been created", - "emails/change_password_message": "To reset your password, please click the following link. If you did not request this yourself, you can safely delete this email.", - "emails/change_password_subject": "Forgot password", + "emails/forgot_password_message": "To reset your password, please click the following link. If you did not request this yourself, you can safely delete this email.", + "emails/forgot_password_subject": "Forgot password", "emails/forum_topic_reply_message": "{{author}} has replied to a topic you follow. Content: {{content}}", - "emails/forum_topic_reply_subject": "{{author}} has replied to {{topic}}", + "emails/forum_topic_mention_message": "{{author}} has mentioned you in a post. Content: {{content}}", "emails/greeting": "Hi,", "emails/register_message": "Thanks for registering! In order to complete your registration, please click the following link:", "emails/register_subject": "Validate Account", "emails/thanks": "Thanks,", + "emails/report_subject": "New report created", + "emails/report_message": "{{reported}} has been reported by {{author}}. Please click the following link to view it.", "errors/403_back": "Go back", "errors/403_content": "You do not have permission to view this page.", "errors/403_home": "Home", @@ -1088,7 +1074,6 @@ "moderator/recent_reports": "Recent Reports", "moderator/reopen_report": "Reopen report", "moderator/report_alert": "New report created", - "moderator/report_email": "A new report has been created. Click {{linkStart}}here{{linkEnd}} to view it.", "moderator/report_closed": "Report closed successfully.", "moderator/report_comment_invalid": "Invalid comment content. Please ensure you have entered a comment between 1 and 10,000 characters.", "moderator/report_reopened": "Report reopened successfully.", diff --git a/modules/Core/module.php b/modules/Core/module.php index f132747525..06a2c92da4 100644 --- a/modules/Core/module.php +++ b/modules/Core/module.php @@ -304,7 +304,7 @@ public function __construct(Language $language, Pages $pages, User $user, Naviga // -- Events EventHandler::registerEvent(AnnouncementCreatedEvent::class); - EventHandler::registerEvent(GenerateNotificationContentEvent::class); + EventHandler::registerEvent(GenerateMassMessageContentEvent::class); EventHandler::registerEvent(GroupClonedEvent::class); EventHandler::registerEvent(ReportCreatedEvent::class); EventHandler::registerEvent(UserBannedEvent::class); @@ -453,9 +453,9 @@ public function __construct(Language $language, Pages $pages, User $user, Naviga EventHandler::registerListener(GroupClonedEvent::class, CloneGroupHook::class); - EventHandler::registerListener(GenerateNotificationContentEvent::class, 'ContentHook::purify'); - EventHandler::registerListener(GenerateNotificationContentEvent::class, 'ContentHook::renderEmojis', 10); - EventHandler::registerListener(GenerateNotificationContentEvent::class, 'MentionsHook::parsePost', 5); + EventHandler::registerListener(GenerateMassMessageContentEvent::class, 'ContentHook::purify'); + EventHandler::registerListener(GenerateMassMessageContentEvent::class, 'ContentHook::renderEmojis', 10); + EventHandler::registerListener(GenerateMassMessageContentEvent::class, 'MentionsHook::parsePost', 5); EventHandler::registerListener(RenderContentEvent::class, [ContentHook::class, 'purify']); EventHandler::registerListener(RenderContentEvent::class, [ContentHook::class, 'renderEmojis'], 10); @@ -474,11 +474,6 @@ public function __construct(Language $language, Pages $pages, User $user, Naviga EventHandler::registerListener(UserRegisteredEvent::class, DefaultUserNotificationPreferencesHook::class); - Email::addPlaceholder('[Sitename]', Output::getClean(SITE_NAME)); - Email::addPlaceholder('[Greeting]', static fn(Language $viewing_language) => $viewing_language->get('emails', 'greeting')); - Email::addPlaceholder('[Message]', static fn(Language $viewing_language, string $email) => $viewing_language->get('emails', $email . '_message')); - Email::addPlaceholder('[Thanks]', static fn(Language $viewing_language) => $viewing_language->get('emails', 'thanks')); - if (Util::isModuleEnabled('Members')) { MemberListManager::getInstance()->registerListProvider(new RegisteredMembersListProvider($language)); MemberListManager::getInstance()->registerListProvider(new StaffMembersListProvider($language)); diff --git a/modules/Core/pages/authme_connector.php b/modules/Core/pages/authme_connector.php index 9de68c834a..f641819af2 100644 --- a/modules/Core/pages/authme_connector.php +++ b/modules/Core/pages/authme_connector.php @@ -199,7 +199,10 @@ if (Settings::get('email_verification') === '1') { // Send registration email - Core_Emails::sendRegisterEmail($language, $email, $mcname, $user_id, $code); + Email::send( + $user, + new RegisterEmailTemplate($code), + ); Session::flash('home', $language->get('user', 'registration_check_email')); Redirect::to(URL::build('/')); diff --git a/modules/Core/pages/forgot_password.php b/modules/Core/pages/forgot_password.php index 386a238ce6..d1b109986c 100644 --- a/modules/Core/pages/forgot_password.php +++ b/modules/Core/pages/forgot_password.php @@ -54,26 +54,14 @@ $code = SecureRandom::alphanumeric(); // Send an email - $link = rtrim(URL::getSelfURL(), '/') . URL::build('/forgot_password/', 'c=' . urlencode($code)); - $sent = Email::send( - ['email' => $target_user->data()->email, 'name' => $target_user->getDisplayname()], - SITE_NAME . ' - ' . $language->get('emails', 'change_password_subject'), - str_replace('[Link]', $link, Email::formatEmail('change_password', $language)), + $target_user, + new ForgotPasswordEmailTemplate($code), ); if (isset($sent['error'])) { - DB::getInstance()->insert('email_errors', [ - 'type' => Email::FORGOT_PASSWORD, - 'content' => $sent['error'], - 'at' => date('U'), - 'user_id' => $target_user->data()->id - ]); - $error = $language->get('user', 'unable_to_send_forgot_password_email'); - } - - if (!isset($error)) { + } else { $target_user->update([ 'reset_code' => $code ]); @@ -124,7 +112,7 @@ // Check code exists $target_user = new User($_GET['c'], 'reset_code'); if (!$target_user->exists()) { - Redirect::to('/forgot_password'); + Redirect::to(URL::build('/forgot_password')); } if (Input::exists()) { @@ -152,17 +140,13 @@ if ($validation->passed()) { if (strcasecmp($target_user->data()->email, $_POST['email']) == 0) { $new_password = password_hash(Input::get('password'), PASSWORD_BCRYPT, ['cost' => 13]); - try { - $target_user->update([ - 'password' => $new_password, - 'reset_code' => null - ]); + $target_user->update([ + 'password' => $new_password, + 'reset_code' => null + ]); - Session::flash('login_success', $language->get('user', 'forgot_password_change_successful')); - Redirect::to(URL::build('/login')); - } catch (Exception $e) { - $errors = [$e->getMessage()]; - } + Session::flash('login_success', $language->get('user', 'forgot_password_change_successful')); + Redirect::to(URL::build('/login')); } else { $errors = [$language->get('user', 'incorrect_email')]; } diff --git a/modules/Core/pages/login.php b/modules/Core/pages/login.php index 9535645d10..851d570fa5 100644 --- a/modules/Core/pages/login.php +++ b/modules/Core/pages/login.php @@ -308,7 +308,10 @@ } if (Session::exists('login_success')) { - $template->getEngine()->addVariable('SUCCESS', Session::flash('login_success')); + $template->getEngine()->addVariables([ + 'SUCCESS_TITLE' => $language->get('general', 'success'), + 'SUCCESS' => Session::flash('login_success') + ]); } if ($captcha) { diff --git a/modules/Core/pages/panel/emails.php b/modules/Core/pages/panel/emails.php index 853a18de93..a33384d535 100644 --- a/modules/Core/pages/panel/emails.php +++ b/modules/Core/pages/panel/emails.php @@ -29,20 +29,6 @@ $page_title = $language->get('admin', 'emails'); require_once ROOT_PATH . '/core/templates/backend_init.php'; -// Since emails are sent in the user's language, they need to be able to pick which language's messages to edit -if (Session::exists('editing_language')) { - $lang_short_code = Session::get('editing_language'); -} else { - $default_lang = DB::getInstance()->get('languages', ['is_default', true])->results(); - $lang_short_code = $default_lang[0]->short_code; -} -$editing_language = new Language('core', $lang_short_code); -$emails = [ - ['register', $language->get('admin', 'registration'), ['subject' => $editing_language->get('emails', 'register_subject'), 'message' => $editing_language->get('emails', 'register_message')]], - ['change_password', $language->get('user', 'change_password'), ['subject' => str_replace('?', '', $editing_language->get('emails', 'change_password_subject')), 'message' => $editing_language->get('emails', 'change_password_message')]], - ['forum_topic_reply', $language->get('admin', 'forum_topic_reply_email'), ['subject' => $editing_language->get('emails', 'forum_topic_reply_subject'), 'message' => $editing_language->get('emails', 'forum_topic_reply_message')]] -]; - if (isset($_GET['action'])) { if ($_GET['action'] == 'test') { @@ -55,10 +41,11 @@ if (isset($_GET['do']) && $_GET['do'] == 'send') { $errors = []; - $sent = Email::send( - ['email' => $user->data()->email, 'name' => $user->data()->nickname], - Output::getClean(SITE_NAME) . ' - Test Email', - Output::getClean(SITE_NAME) . ' - Test email successful!', + $sent = Email::sendRaw( + Email::TEST_EMAIL, + $user, + 'Test Email', + Output::getClean(SITE_NAME) . ' - Test email successful!' ); if (isset($sent['error'])) { @@ -90,54 +77,6 @@ } $template_file = 'core/emails_test'; - } else { - if ($_GET['action'] == 'edit_messages') { - - $available_languages = []; - - $languages = DB::getInstance()->get('languages', ['id', '<>', 0])->results(); - foreach ($languages as $language_db) { - $lang = new Language('core', $language_db->short_code); - $lang_file = $lang->getActiveLanguageFile(); - if (file_exists($lang_file) && is_writable($lang_file)) { - $available_languages[] = $language_db; - } - } - - $template->getEngine()->addVariables([ - 'BACK' => $language->get('general', 'back'), - 'BACK_LINK' => URL::build('/panel/core/emails'), - 'EMAILS_MESSAGES' => $language->get('admin', 'edit_email_messages'), - 'EDITING_MESSAGES' => $language->get('admin', 'editing_messages'), - 'OPTIONS' => $language->get('admin', 'email_message_options'), - 'SELECT_LANGUAGE' => $language->get('admin', 'editing_language'), - 'EDITING_LANGUAGE' => $editing_language->getActiveLanguage(), - 'LANGUAGES' => $available_languages, - 'INFO' => $language->get('general', 'info'), - 'LANGUAGE_INFO' => $language->get('admin', 'email_language_info'), - 'GREETING' => $language->get('admin', 'email_message_greeting'), - 'GREETING_VALUE' => $editing_language->get('emails', 'greeting'), - 'THANKS' => $language->get('admin', 'email_message_thanks'), - 'THANKS_VALUE' => $editing_language->get('emails', 'thanks'), - 'EMAILS_LIST' => $emails, - 'SUBJECT' => $language->get('admin', 'email_message_subject'), - 'MESSAGE' => $language->get('admin', 'email_message_message'), - 'PREVIEW' => $language->get('admin', 'email_preview_popup'), - 'PREVIEW_INFO' => $language->get('admin', 'email_preview_popup_message'), - 'SUBMIT' => $language->get('general', 'submit'), - 'TOKEN' => Token::get() - ]); - - $template_file = 'core/emails_edit_messages'; - } else { - if ($_GET['action'] == 'preview') { - $viewing_language = new Language('core', Session::get('editing_language')); - - $template->getEngine()->addVariable('MESSAGE', Email::formatEmail($_GET['email'], $viewing_language)); - - $template_file = 'core/emails_edit_messages_preview'; - } - } } } else { // Handle input @@ -145,47 +84,29 @@ $errors = []; if (Token::check()) { + Settings::set('phpmailer', (isset($_POST['enable_mailer']) && $_POST['enable_mailer']) ? '1' : '0'); - // Handle email message updating - if (isset($_POST['greeting'])) { - $editing_lang = new Language('core', $lang_short_code); - - Session::put('editing_language', Input::get('editing_language')); - - $editing_lang->set('emails', 'greeting', Output::getClean(Input::get('greeting'))); - $editing_lang->set('emails', 'thanks', Output::getClean(Input::get('thanks'))); - - foreach ($emails as $email) { - $editing_lang->set('emails', $email[0] . '_subject', Output::getClean(Input::get($email[0] . '_subject'))); - $editing_lang->set('emails', $email[0] . '_message', Output::getClean(Input::get($email[0] . '_message'))); - } - Session::flash('emails_success', $language->get('admin', 'email_settings_updated_successfully')); - Redirect::to(URL::build('/panel/core/emails', 'action=edit_messages')); - } else { - Settings::set('phpmailer', (isset($_POST['enable_mailer']) && $_POST['enable_mailer']) ? '1' : '0'); - - if (!empty($_POST['email'])) { - Settings::set('outgoing_email', $_POST['email']); - } + if (!empty($_POST['email'])) { + Settings::set('outgoing_email', $_POST['email']); + } - if ($_POST['port'] && !is_numeric($_POST['port'])) { - $errors[] = $language->get('admin', 'email_port_invalid'); - } + if ($_POST['port'] && !is_numeric($_POST['port'])) { + $errors[] = $language->get('admin', 'email_port_invalid'); + } - if (!count($errors)) { - // Update config + if (!count($errors)) { + // Update config - Config::set('email.email', !empty($_POST['email']) ? $_POST['email'] : Config::get('email.email', '')); - Config::set('email.username', !empty($_POST['username']) ? $_POST['username'] : Config::get('email.username', '')); - Config::set('email.password', !empty($_POST['password']) ? $_POST['password'] : Config::get('email.password', '')); - Config::set('email.name', !empty($_POST['name']) ? $_POST['name'] : Config::get('email.name', '')); - Config::set('email.host', !empty($_POST['host']) ? $_POST['host'] : Config::get('email.host', '')); - Config::set('email.port', !empty($_POST['port']) ? (int) $_POST['port'] : Config::get('email.port', '')); + Config::set('email.email', !empty($_POST['email']) ? $_POST['email'] : Config::get('email.email', '')); + Config::set('email.username', !empty($_POST['username']) ? $_POST['username'] : Config::get('email.username', '')); + Config::set('email.password', !empty($_POST['password']) ? $_POST['password'] : Config::get('email.password', '')); + Config::set('email.name', !empty($_POST['name']) ? $_POST['name'] : Config::get('email.name', '')); + Config::set('email.host', !empty($_POST['host']) ? $_POST['host'] : Config::get('email.host', '')); + Config::set('email.port', !empty($_POST['port']) ? (int) $_POST['port'] : Config::get('email.port', '')); - // Redirect to refresh config values - Session::flash('emails_success', $language->get('admin', 'email_settings_updated_successfully')); - Redirect::to(URL::build('/panel/core/emails')); - } + // Redirect to refresh config values + Session::flash('emails_success', $language->get('admin', 'email_settings_updated_successfully')); + Redirect::to(URL::build('/panel/core/emails')); } } else { $errors[] = $language->get('general', 'invalid_token'); @@ -200,8 +121,6 @@ } $template->getEngine()->addVariables([ - 'EDIT_EMAIL_MESSAGES' => $language->get('admin', 'edit_email_messages'), - 'EDIT_EMAIL_MESSAGES_LINK' => URL::build('/panel/core/emails/', 'action=edit_messages'), 'SEND_TEST_EMAIL' => $language->get('admin', 'send_test_email'), 'SEND_TEST_EMAIL_LINK' => URL::build('/panel/core/emails/', 'action=test'), 'EMAIL_ERRORS' => $language->get('admin', 'email_errors'), diff --git a/modules/Core/pages/panel/emails_errors.php b/modules/Core/pages/panel/emails_errors.php index 6ba709f15a..cf71b97de0 100644 --- a/modules/Core/pages/panel/emails_errors.php +++ b/modules/Core/pages/panel/emails_errors.php @@ -37,6 +37,9 @@ DB::getInstance()->delete('email_errors', ['id', '<>', 0]); + $cache->setCache('notices_cache'); + $cache->store('email_errors', 0); + Session::flash('emails_errors_success', $language->get('admin', 'email_errors_purged_successfully')); Redirect::to(URL::build('/panel/core/emails/errors')); } @@ -45,6 +48,9 @@ DB::getInstance()->delete('email_errors', ['id', $_GET['id']]); + $cache->setCache('notices_cache'); + $cache->erase('email_errors'); + Session::flash('emails_errors_success', $language->get('admin', 'error_deleted_successfully')); Redirect::to(URL::build('/panel/core/emails/errors')); } @@ -62,27 +68,6 @@ } $error = $error[0]; - switch ($error->type) { - case Email::REGISTRATION: - $type = $language->get('admin', 'registration_email'); - break; - case Email::FORGOT_PASSWORD: - $type = $language->get('admin', 'forgot_password_email'); - break; - case Email::API_REGISTRATION: - $type = $language->get('admin', 'api_registration_email'); - break; - case Email::FORUM_TOPIC_REPLY: - $type = $language->get('admin', 'forum_topic_reply_email'); - break; - case Email::MASS_MESSAGE: - $type = $language->get('admin', 'mass_message'); - break; - default: - $type = $language->get('admin', 'unknown'); - break; - } - $template->getEngine()->addVariables([ 'BACK_LINK' => URL::build('/panel/core/emails/errors'), 'VIEWING_ERROR' => $language->get('admin', 'viewing_email_error'), @@ -90,9 +75,8 @@ 'USERNAME_VALUE' => $error->user_id ? Output::getClean($user->idToName($error->user_id)) : $language->get('general', 'deleted_user'), 'DATE' => $language->get('general', 'date'), 'DATE_VALUE' => date(DATE_FORMAT, $error->at), - 'TYPE' => $language->get('admin', 'type'), - 'TYPE_ID' => $error->type, - 'TYPE_VALUE' => $type, + 'MAILER' => $language->get('admin', 'mailer'), + 'MAILER_VALUE' => $error->mailer, 'CONTENT' => $language->get('admin', 'content'), 'CONTENT_VALUE' => Output::getPurified($error->content), 'ACTIONS' => $language->get('general', 'actions'), @@ -105,28 +89,13 @@ 'CLOSE' => $language->get('general', 'close') ]); - if ($error->type == Email::REGISTRATION) { - $user_validated = DB::getInstance()->get('users', ['id', $error->user_id])->results(); - if (count($user_validated)) { - $user_validated = $user_validated[0]; - if ($user_validated->active == 0) { - $template->getEngine()->addVariables([ - 'VALIDATE_USER_LINK' => URL::build('/panel/users/edit/', 'id=' . urlencode($error->user_id) . '&action=validate'), - 'VALIDATE_USER_TEXT' => $language->get('admin', 'validate_user') - ]); - } - } - } else if ($error->type == Email::API_REGISTRATION) { - $user_error = DB::getInstance()->get('users', ['id', $error->user_id])->results(); - if (count($user_error)) { - $user_error = $user_error[0]; - if ($user_error->active == 0 && !is_null($user_error->reset_code)) { - $template->getEngine()->addVariables([ - 'REGISTRATION_LINK' => $language->get('admin', 'registration_link'), - 'SHOW_REGISTRATION_LINK' => $language->get('admin', 'show_registration_link'), - 'REGISTRATION_LINK_VALUE' => rtrim(URL::getSelfURL(), '/') . URL::build('/complete_signup/', 'c=' . urlencode($user_error->reset_code)) - ]); - } + if ($error->mailer == 'Register') { + $user_validated = DB::getInstance()->get('users', $error->user_id)->first(); + if ($user_validated && $user_validated->active == 0) { + $template->getEngine()->addVariables([ + 'VALIDATE_USER_LINK' => URL::build('/panel/users/edit/', 'id=' . urlencode($error->user_id) . '&action=validate'), + 'VALIDATE_USER_TEXT' => $language->get('admin', 'validate_user') + ]); } } @@ -161,7 +130,7 @@ $template->getEngine()->addVariables([ 'BACK_LINK' => URL::build('/panel/core/emails'), - 'TYPE' => $language->get('admin', 'type'), + 'MAILER' => $language->get('admin', 'mailer'), 'DATE' => $language->get('general', 'date'), 'USERNAME' => $language->get('user', 'username'), 'ACTIONS' => $language->get('general', 'actions') @@ -171,29 +140,8 @@ $template_errors = []; foreach ($results->data as $error) { - switch ($error->type) { - case Email::REGISTRATION: - $type = $language->get('admin', 'registration_email'); - break; - case Email::FORGOT_PASSWORD: - $type = $language->get('admin', 'forgot_password_email'); - break; - case Email::API_REGISTRATION: - $type = $language->get('admin', 'api_registration_email'); - break; - case Email::FORUM_TOPIC_REPLY: - $type = $language->get('admin', 'forum_topic_reply_email'); - break; - case Email::MASS_MESSAGE: - $type = $language->get('admin', 'mass_message'); - break; - default: - $type = $language->get('admin', 'unknown'); - break; - } - $template_errors[] = [ - 'type' => $type, + 'mailer' => $error->mailer, 'date' => date(DATE_FORMAT, $error->at), 'user' => $error->user_id ? Output::getClean($user->idToName($error->user_id)) : $language->get('general', 'deleted_user'), 'view_link' => URL::build('/panel/core/emails/errors/', 'do=view&id=' . $error->id), diff --git a/modules/Core/pages/panel/mass_message.php b/modules/Core/pages/panel/mass_message.php index 3f3cc08cc5..96c42e70d1 100644 --- a/modules/Core/pages/panel/mass_message.php +++ b/modules/Core/pages/panel/mass_message.php @@ -150,12 +150,11 @@ Module::getIdFromName('Core'), $language->get('admin', 'mass_message'), [ - 'callback' => 'MassMessage::parseContent', - 'content' => Input::get('content'), 'title' => Input::get('subject'), - 'type' => 'mass_message', + 'content' => Input::get('content'), 'users' => $users, 'skip_purify' => (bool) Input::get('unsafe_html'), + 'bypass_notification_settings' => (bool) Input::get('ignore_opt_in'), ], date('U'), null, diff --git a/modules/Core/pages/panel/users_edit.php b/modules/Core/pages/panel/users_edit.php index 451c21b0cf..c8b41991b4 100644 --- a/modules/Core/pages/panel/users_edit.php +++ b/modules/Core/pages/panel/users_edit.php @@ -61,7 +61,7 @@ } } } else if ($_GET['action'] == 'resend_email' && $user_query->active == 0) { - if (Core_Emails::sendRegisterEmail($language, $user_query->email, $user_query->username, $user_query->id, $user_query->reset_code)) { + if (Email::send($view_user, new RegisterEmailTemplate($user_query->reset_code))) { Session::flash('edit_user_success', $language->get('admin', 'email_resent_successfully')); } else { Session::flash('edit_user_error', $language->get('admin', 'email_resend_failed')); diff --git a/modules/Core/pages/register.php b/modules/Core/pages/register.php index 07c2616005..e330a29b92 100644 --- a/modules/Core/pages/register.php +++ b/modules/Core/pages/register.php @@ -207,6 +207,7 @@ // Check if there was any integrations errors if (!isset($integration_errors)) { + DB::getInstance()->beginTransaction(); $user = new User(); $ip = HttpUtils::getRemoteAddress(); @@ -307,14 +308,17 @@ if (!$auto_verify_oauth_email && Settings::get('email_verification') === '1') { // Send registration email - Core_Emails::sendRegisterEmail($language, Output::getClean(Input::get('email')), $username, $user_id, $code); + Email::send( + $user, + new RegisterEmailTemplate($code), + ); Session::flash('home', $language->get('user', 'registration_check_email')); } else { // Redirect straight to verification link Redirect::to(URL::build('/validate/', 'c=' . urlencode($code))); } - + DB::getInstance()->commitTransaction(); Redirect::to(URL::build('/')); } else { // Integrations errors diff --git a/modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php b/modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php new file mode 100644 index 0000000000..6cffb2413e --- /dev/null +++ b/modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php @@ -0,0 +1,28 @@ +addPlaceholder('[Message]', new LanguageKey('emails', 'forum_topic_mention_message', [ + 'author' => $author->data()->username, + 'content' => $content, + ])); + + $this->addPlaceholder('[Link]', $link); + + $this->author = $author; + + parent::__construct(); + } + + public function subject(): LanguageKey + { + return new LanguageKey('user', 'user_tag_info', [ + 'author' => $this->author->data()->username + ]); + } +} diff --git a/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php b/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php new file mode 100644 index 0000000000..487f7efc6b --- /dev/null +++ b/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php @@ -0,0 +1,30 @@ +addPlaceholder('[Message]', new LanguageKey('emails', 'forum_topic_reply_message', [ + 'author' => $author->data()->username, + 'content' => $replyContent, + ])); + + $this->addPlaceholder('[Link]', $link); + + $this->author = $author; + $this->topicTitle = $topicTitle; + + parent::__construct(); + } + + public function subject(): LanguageKey + { + return new LanguageKey('forum', 'new_reply_in_topic', [ + 'author' => $this->author->data()->username, 'topic' => $this->topicTitle, + ], ROOT_PATH . '/modules/Forum/language/'); + } +} diff --git a/modules/Forum/classes/Events/PrePostCreateEvent.php b/modules/Forum/classes/Events/PrePostCreateEvent.php index 67a898afe0..0ceebe57ed 100644 --- a/modules/Forum/classes/Events/PrePostCreateEvent.php +++ b/modules/Forum/classes/Events/PrePostCreateEvent.php @@ -4,19 +4,25 @@ class PrePostCreateEvent extends AbstractEvent { public string $content; public User $user; - public string $alert_url; public string $mention_notification_type; - public LanguageKey $mention_notification_title; + public AlertTemplate $mention_notification_alert_template; + public EmailTemplate $mention_notification_email_template; - public function __construct(string $content, User $user, string $alert_url, string $mention_notification_type, LanguageKey $mention_notification_title) { + public function __construct( + string $content, + User $user, + string $mention_notificiation_type, + AlertTemplate $mention_notification_alert_template, + EmailTemplate $mention_notification_email_template, + ) { $this->content = $content; $this->user = $user; - $this->alert_url = $alert_url; - $this->mention_notification_type = $mention_notification_type; - $this->mention_notification_title = $mention_notification_title; + $this->mention_notification_type = $mention_notificiation_type; + $this->mention_notification_alert_template = $mention_notification_alert_template; + $this->mention_notification_email_template = $mention_notification_email_template; } public static function internal(): bool { return true; } -} \ No newline at end of file +} diff --git a/modules/Forum/classes/Events/PreTopicCreateEvent.php b/modules/Forum/classes/Events/PreTopicCreateEvent.php index a0db1fb53f..70649af385 100644 --- a/modules/Forum/classes/Events/PreTopicCreateEvent.php +++ b/modules/Forum/classes/Events/PreTopicCreateEvent.php @@ -4,19 +4,25 @@ class PreTopicCreateEvent extends AbstractEvent { public string $content; public User $user; - public string $alert_url; public string $mention_notification_type; - public LanguageKey $mention_notification_title; + public AlertTemplate $mention_notification_alert_template; + public EmailTemplate $mention_notification_email_template; - public function __construct(string $content, User $user, string $alert_url, string $mention_notification_type, LanguageKey $mention_notification_title) { + public function __construct( + string $content, + User $user, + string $mention_notificiation_type, + AlertTemplate $mention_notification_alert_template, + EmailTemplate $mention_notification_email_template, + ) { $this->content = $content; $this->user = $user; - $this->alert_url = $alert_url; - $this->mention_notification_type = $mention_notification_type; - $this->mention_notification_title = $mention_notification_title; + $this->mention_notification_type = $mention_notificiation_type; + $this->mention_notification_alert_template = $mention_notification_alert_template; + $this->mention_notification_email_template = $mention_notification_email_template; } public static function internal(): bool { return true; } -} \ No newline at end of file +} diff --git a/modules/Forum/language/en_UK.json b/modules/Forum/language/en_UK.json index 2bf8f80652..9b437a82fa 100644 --- a/modules/Forum/language/en_UK.json +++ b/modules/Forum/language/en_UK.json @@ -178,7 +178,6 @@ "forum/unstick_topic": "Unstick Topic", "forum/use_reactions": "Use Reactions?", "forum/user_no_posts": "This user has not made any forum posts yet.", - "forum/user_tag_info": "You have been tagged in a post by {{author}}.", "forum/views": "views", "forum/x_posts": "{{count}} posts", "forum/x_topics": "{{count}} topics", diff --git a/modules/Forum/pages/forum/new_topic.php b/modules/Forum/pages/forum/new_topic.php index 0c77b26d59..d214ac33ff 100644 --- a/modules/Forum/pages/forum/new_topic.php +++ b/modules/Forum/pages/forum/new_topic.php @@ -196,14 +196,25 @@ // Get last post ID $last_post_id = DB::getInstance()->lastId(); + + $topic_title = Input::get('title'); + $topic_link = URL::build('/forum/topic/' . urlencode($topic_id) . '-' . $forum->titleToURL(Input::get('title')), 'pid=' . $last_post_id); $topic_event = new PreTopicCreateEvent( $content, $user, - URL::build('/forum/topic/' . urlencode($topic_id), 'pid=' . urlencode($last_post_id)), 'forum_topic_mention', - new LanguageKey('forum', 'user_tag_info', [ - 'author' => $user->getDisplayname(), - ], ROOT_PATH . '/modules/Forum/language'), + new AlertTemplate( + new LanguageKey('user', 'user_tag_info', [ + 'author' => $user->data()->username, + ]), + null, + $topic_link, + ), + new ForumTopicMentionEmailTemplate( + $user, + $content, + $topic_link + ), ); EventHandler::executeEvent($topic_event); diff --git a/modules/Forum/pages/forum/view_topic.php b/modules/Forum/pages/forum/view_topic.php index cae6c5c3d5..0bfe740fcd 100644 --- a/modules/Forum/pages/forum/view_topic.php +++ b/modules/Forum/pages/forum/view_topic.php @@ -301,14 +301,24 @@ // Get last post ID $last_post_id = DB::getInstance()->lastId(); + + $post_link = rtrim(URL::getSelfURL(), '/') . URL::build('/forum/topic/' . urlencode($tid) . '-' . $forum->titleToURL($topic->topic_title), 'pid=' . $last_post_id); $post_event = new PrePostCreateEvent( $content, $user, - URL::build('/forum/topic/' . urlencode($tid), 'pid=' . urlencode($last_post_id)), 'forum_topic_mention', - new LanguageKey('forum', 'user_tag_info', [ - 'author' => $user->getDisplayname(), - ], ROOT_PATH . '/modules/Forum/language') + new AlertTemplate( + new LanguageKey('user', 'user_tag_info', [ + 'author' => $user->data()->username + ]), + null, + $post_link, + ), + new ForumTopicMentionEmailTemplate( + $user, + $content, + $post_link + ), ); EventHandler::executeEvent($post_event); @@ -339,39 +349,31 @@ $available_hooks, )); - // Notifications + // Notifications - TODO: can this become a listener of TopicReplyCreatedEvent? $users_following = DB::getInstance()->query('SELECT DISTINCT(user_id) FROM nl2_topics_following WHERE topic_id = ? AND user_id != ? AND existing_alerts = 0', [ $tid, $user->data()->id ])->results(); $users_following = array_map(fn ($row) => $row->user_id, $users_following); - $path = implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'custom', 'templates', TEMPLATE, 'email', 'forum_topic_reply.html']); - $html = file_get_contents($path); - - // TODO: Use Email::formatEmail() instead of this? - $message = str_replace( - ['[Sitename]', '[TopicReply]', '[Greeting]', '[Message]', '[Link]', '[Thanks]'], - [ - Output::getClean(SITE_NAME), - $language->get('emails', 'forum_topic_reply_subject', ['author' => $user->data()->username, 'topic' => $topic->topic_title]), - $language->get('emails', 'greeting'), - $language->get('emails', 'forum_topic_reply_message', ['author' => $user->data()->username, 'content' => html_entity_decode($original_content)]), - rtrim(URL::getSelfURL(), '/') . URL::build('/forum/topic/' . urlencode($tid) . '-' . $forum->titleToURL($topic->topic_title), 'pid=' . $last_post_id), - $language->get('emails', 'thanks') - ], - $html - ); - $notification = new Notification( 'forum_topic_reply', - new LanguageKey('forum', 'new_reply_in_topic', ['author' => $user->data()->username, 'topic' => $topic->topic_title], ROOT_PATH . '/modules/Forum/language'), - $message, + new AlertTemplate( + new LanguageKey('forum', 'new_reply_in_topic', [ + 'author' => $user->data()->username, 'topic' => $topic->topic_title + ], ROOT_PATH . '/modules/Forum/language'), + null, + $post_link, + ), + new ForumTopicReplyEmailTemplate( + $user, + $topic->topic_title, + $original_content, + $post_link, + ), $users_following, $user->data()->id, - null, - false, - URL::build('/forum/topic/' . urlencode($tid) . '-' . $forum->titleToURL($topic->topic_title), 'pid=' . $last_post_id), + ); $notification->send();
{$TYPE}{$TYPE_VALUE}{$MAILER}{$MAILER_VALUE}
{$DATE}