From 1515b29a647c3bba82e9285757bbbb478900f3b9 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Mon, 28 Apr 2025 17:48:42 -0700 Subject: [PATCH 01/23] Introduce `EmailTemplate` classes --- core/classes/Core/Email.php | 44 --------------- core/classes/Core/EmailTemplate.php | 66 ++++++++++++++++++++++ modules/Core/classes/Core/Notification.php | 5 +- 3 files changed, 70 insertions(+), 45 deletions(-) create mode 100644 core/classes/Core/EmailTemplate.php diff --git a/core/classes/Core/Email.php b/core/classes/Core/Email.php index dec3a7d471..c97ab6576a 100644 --- a/core/classes/Core/Email.php +++ b/core/classes/Core/Email.php @@ -20,11 +20,6 @@ class Email public const FORUM_TOPIC_REPLY = 5; public const MASS_MESSAGE = 6; - /** - * @var array Placeholders for email templates - */ - private static array $_message_placeholders = []; - /** * Send an email. * @@ -106,7 +101,6 @@ private static function sendPHP(array $email) private static function sendMailer(array $email) { try { - // Initialise PHPMailer $mail = new PHPMailer(true); $mail->IsSMTP(); @@ -154,42 +148,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/Core/EmailTemplate.php b/core/classes/Core/EmailTemplate.php new file mode 100644 index 0000000000..d39d791e34 --- /dev/null +++ b/core/classes/Core/EmailTemplate.php @@ -0,0 +1,66 @@ + Placeholders for email templates + */ + private static array $_message_placeholders = []; + + public function __construct( + private string $name, + ) { + $file = implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'custom', 'templates', TEMPLATE, 'email', $name . '.html']); + + if (!file_exists($file)) { + throw new InvalidArgumentException("Email template file $file does not exist"); + } + } + + public function render(Language $language, array $variables = []): string + { + return self::formatEmail( + $this->name, + $language, + $variables + ); + } + + /** + * 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/modules/Core/classes/Core/Notification.php b/modules/Core/classes/Core/Notification.php index 5b284d3114..565774bbb9 100644 --- a/modules/Core/classes/Core/Notification.php +++ b/modules/Core/classes/Core/Notification.php @@ -16,6 +16,7 @@ class Notification { private bool $_skipPurify; private string $_type; private ?string $_alertUrl = null; + private EmailTemplate $_emailTemplate; private static array $_types = []; @@ -42,6 +43,7 @@ public function __construct( ?callable $contentCallback = null, bool $skipPurify = false, ?string $alertUrl = null, + EmailTemplate $emailTemplate, ) { if (!in_array($type, array_column(self::getTypes(), 'key'))) { throw new NotificationTypeNotFoundException("Type $type not registered"); @@ -51,6 +53,7 @@ public function __construct( $this->_skipPurify = $skipPurify; $this->_type = $type; $this->_alertUrl = $alertUrl; + $this->_emailTemplate = $emailTemplate; if (!is_array($recipients)) { $recipients = [$recipients]; @@ -124,7 +127,7 @@ private function sendEmail(int $userId, string $title, string $content): void { Module::getIdFromName('Core'), 'Send Email Notification', [ - 'content' => $content, + 'content' => $this->_emailTemplate->render($content), 'title' => $title, ], date('U'), // TODO: schedule a date/time? From 8b9973318a92d6cf91fcc41e0ad7847d6203c911 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Tue, 29 Apr 2025 22:48:34 -0700 Subject: [PATCH 02/23] wip --- core/classes/Core/EmailTemplate.php | 66 ----------- core/classes/{Core => Emails}/Email.php | 31 ++++- core/classes/Emails/EmailTemplate.php | 111 ++++++++++++++++++ modules/Core/classes/Core/Notification.php | 3 +- .../Email_Templates/RegisterEmailTemplate.php | 26 ++++ modules/Core/classes/Misc/Core_Emails.php | 34 ------ .../includes/endpoints/RegisterEndpoint.php | 24 +--- modules/Core/module.php | 5 - modules/Core/pages/authme_connector.php | 5 +- modules/Core/pages/panel/users_edit.php | 2 +- modules/Core/pages/register.php | 8 +- 11 files changed, 185 insertions(+), 130 deletions(-) delete mode 100644 core/classes/Core/EmailTemplate.php rename core/classes/{Core => Emails}/Email.php (82%) create mode 100644 core/classes/Emails/EmailTemplate.php create mode 100644 modules/Core/classes/Email_Templates/RegisterEmailTemplate.php delete mode 100644 modules/Core/classes/Misc/Core_Emails.php diff --git a/core/classes/Core/EmailTemplate.php b/core/classes/Core/EmailTemplate.php deleted file mode 100644 index d39d791e34..0000000000 --- a/core/classes/Core/EmailTemplate.php +++ /dev/null @@ -1,66 +0,0 @@ - Placeholders for email templates - */ - private static array $_message_placeholders = []; - - public function __construct( - private string $name, - ) { - $file = implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'custom', 'templates', TEMPLATE, 'email', $name . '.html']); - - if (!file_exists($file)) { - throw new InvalidArgumentException("Email template file $file does not exist"); - } - } - - public function render(Language $language, array $variables = []): string - { - return self::formatEmail( - $this->name, - $language, - $variables - ); - } - - /** - * 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/Core/Email.php b/core/classes/Emails/Email.php similarity index 82% rename from core/classes/Core/Email.php rename to core/classes/Emails/Email.php index e076635d31..f8e97e117c 100644 --- a/core/classes/Core/Email.php +++ b/core/classes/Emails/Email.php @@ -22,6 +22,36 @@ class Email public const FORUM_TOPIC_REPLY = 5; public const MASS_MESSAGE = 6; + public static function sendNext(User $recipient, EmailTemplate $emailTemplate) + { + $languageCode = DB::getInstance()->get('languages', ['id', '=', $recipient->data()->language_id])->first()->short_code; + + $email = [ + 'to' => [ + 'email' => $recipient->data()->email, + 'name' => $recipient->getDisplayname(), + ], + 'subject' => SITE_NAME . ' - ' . $emailTemplate->subject()->translate($languageCode), + 'message' => $emailTemplate->renderContent($languageCode), + 'replyto' => self::getReplyTo(), + ]; + + $result = Settings::get('phpmailer') == '1' + ? self::sendMailer($email) + : self::sendPHP($email); + + if (isset($result['error'])) { + DB::getInstance()->insert('email_errors', [ + 'type' => $emailTemplate->id(), + 'content' => $result['error'], + 'at' => date('U'), + 'user_id' => $recipient->data()->id, + ]); + } + + return $result; + } + /** * Send an email. * @@ -107,7 +137,6 @@ private static function sendMailer(array $email) $mail->IsSMTP(); $mail->SMTPDebug = SMTP::DEBUG_OFF; - $mail->Debugoutput = 'html'; $mail->CharSet = PHPMailer::CHARSET_UTF8; $mail->Encoding = PHPMailer::ENCODING_BASE64; $mail->Timeout = 15; diff --git a/core/classes/Emails/EmailTemplate.php b/core/classes/Emails/EmailTemplate.php new file mode 100644 index 0000000000..c72eed44d3 --- /dev/null +++ b/core/classes/Emails/EmailTemplate.php @@ -0,0 +1,111 @@ + Placeholders for all email templates + */ + private static array $_global_placeholders = []; + + /** + * @var array Placeholders for this email template + */ + private array $_placeholders = []; + + public function __construct() + { + self::addGlobalPlaceholder('[Sitename]', Output::getClean(SITE_NAME)); + self::addGlobalPlaceholder('[Greeting]', new LanguageKey('emails', 'greeting')); + self::addGlobalPlaceholder('[Thanks]', new LanguageKey('emails', 'thanks')); + } + + public abstract function id(): int; + + /** + * 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 + { + $language = new Language($this->moduleLanguagePath(), $languageCode); + + $allPlaceholders = array_merge(self::$_global_placeholders, $this->_placeholders); + $placeholderKeys = array_keys($allPlaceholders); + $placeholderValues = []; + + foreach ($allPlaceholders as $placeholder) { + if ($placeholder instanceof LanguageKey) { + $placeholderValues[] = $placeholder->translate($languageCode); + } else if (is_callable($placeholder)) { + $placeholderValues[] = $placeholder($language, $this->name()); + } 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); + } + + private function moduleLanguagePath(): string + { + $reflection = new ReflectionClass($this); + $filePath = $reflection->getFileName(); + + preg_match('#modules/([^/]+)/#', $filePath, $matches); + + return 'modules/' . $matches[1] . '/language'; + } +} diff --git a/modules/Core/classes/Core/Notification.php b/modules/Core/classes/Core/Notification.php index 8d415912b8..297790560c 100644 --- a/modules/Core/classes/Core/Notification.php +++ b/modules/Core/classes/Core/Notification.php @@ -131,8 +131,7 @@ private function sendEmail(int $userId, string $title, string $content): void { Module::getIdFromName('Core'), 'Send Email Notification', [ - 'content' => $this->_emailTemplate->render($content), - 'title' => SITE_NAME . ' - ' . $title, + 'email_template' => $this->_emailTemplate, ], date('U'), // TODO: schedule a date/time? 'User', diff --git a/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php b/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php new file mode 100644 index 0000000000..7d93a120bd --- /dev/null +++ b/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php @@ -0,0 +1,26 @@ +addPlaceholder('[Link]', $link); + $this->addPlaceholder('[Message]', new LanguageKey('emails', 'register_message')); + + parent::__construct(); + } + + public function id(): int + { + return self::ID; + } + + public function subject(): LanguageKey + { + return new LanguageKey('emails', 'register_subject'); + } +} 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/includes/endpoints/RegisterEndpoint.php b/modules/Core/includes/endpoints/RegisterEndpoint.php index 063b22ac9b..63eb4019c8 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::sendNext( + 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/module.php b/modules/Core/module.php index c118c886a9..458c893cae 100644 --- a/modules/Core/module.php +++ b/modules/Core/module.php @@ -546,11 +546,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..ae0d5340f6 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::sendNext( + $user, + new RegisterEmailTemplate($code), + ); Session::flash('home', $language->get('user', 'registration_check_email')); Redirect::to(URL::build('/')); diff --git a/modules/Core/pages/panel/users_edit.php b/modules/Core/pages/panel/users_edit.php index 451c21b0cf..89d718ef9b 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::sendNext($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..c6f58d2ff0 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::sendNext( + $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 From 4c2d0a5a8bbbbc1506c2e9c742b5b6ef1c2335f1 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Tue, 29 Apr 2025 23:01:46 -0700 Subject: [PATCH 03/23] convert test email --- core/classes/Emails/Email.php | 1 + .../templates/DefaultRevamp/email/test.html | 12 ++++++++++ .../Email_Templates/RegisterEmailTemplate.php | 3 ++- .../Email_Templates/TestEmailTemplate.php | 23 +++++++++++++++++++ modules/Core/language/en_UK.json | 2 ++ modules/Core/pages/panel/emails.php | 7 +++--- 6 files changed, 43 insertions(+), 5 deletions(-) create mode 100755 custom/templates/DefaultRevamp/email/test.html create mode 100644 modules/Core/classes/Email_Templates/TestEmailTemplate.php diff --git a/core/classes/Emails/Email.php b/core/classes/Emails/Email.php index f8e97e117c..741f325b93 100644 --- a/core/classes/Emails/Email.php +++ b/core/classes/Emails/Email.php @@ -21,6 +21,7 @@ class Email public const API_REGISTRATION = 4; public const FORUM_TOPIC_REPLY = 5; public const MASS_MESSAGE = 6; + public const TEST_EMAIL = 7; public static function sendNext(User $recipient, EmailTemplate $emailTemplate) { diff --git a/custom/templates/DefaultRevamp/email/test.html b/custom/templates/DefaultRevamp/email/test.html new file mode 100755 index 0000000000..0d1529f01d --- /dev/null +++ b/custom/templates/DefaultRevamp/email/test.html @@ -0,0 +1,12 @@ + + + + + + [Sitename] + + + + [Message] + + diff --git a/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php b/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php index 7d93a120bd..71634fc55d 100644 --- a/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php +++ b/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php @@ -6,7 +6,8 @@ class RegisterEmailTemplate extends EmailTemplate public function __construct(string $code) { - $link = URL::getSelfURL() . ltrim(URL::build('/complete_signup/', 'c=' . urlencode($code)), '/'); + // TODO sometimes this needs to be complete_signup? + $link = rtrim(URL::getSelfURL(), '/') . URL::build('/validate/', 'c=' . urlencode($code)); $this->addPlaceholder('[Link]', $link); $this->addPlaceholder('[Message]', new LanguageKey('emails', 'register_message')); diff --git a/modules/Core/classes/Email_Templates/TestEmailTemplate.php b/modules/Core/classes/Email_Templates/TestEmailTemplate.php new file mode 100644 index 0000000000..c61c33284e --- /dev/null +++ b/modules/Core/classes/Email_Templates/TestEmailTemplate.php @@ -0,0 +1,23 @@ +addPlaceholder('[Message]', new LanguageKey('emails', 'test_message')); + + parent::__construct(); + } + + public function id(): int + { + return self::ID; + } + + public function subject(): LanguageKey + { + return new LanguageKey('emails', 'test_subject'); + } +} diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index 879c2d3117..ff94481cc1 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -812,6 +812,8 @@ "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/test_subject": "Test email", + "emails/test_message": "Test email successful!", "emails/thanks": "Thanks,", "errors/403_back": "Go back", "errors/403_content": "You do not have permission to view this page.", diff --git a/modules/Core/pages/panel/emails.php b/modules/Core/pages/panel/emails.php index 853a18de93..8305be7e11 100644 --- a/modules/Core/pages/panel/emails.php +++ b/modules/Core/pages/panel/emails.php @@ -55,10 +55,9 @@ 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::sendNext( + $user, + new TestEmailTemplate, ); if (isset($sent['error'])) { From ef61116c1d499efaaa2999b6332fa0a062806f12 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Tue, 29 Apr 2025 23:08:25 -0700 Subject: [PATCH 04/23] convert forgot password --- .../DefaultRevamp/email/change_password.html | 19 -------------- .../DefaultRevamp/email/forgot_password.html | 17 ++++++++++++ .../templates/DefaultRevamp/email/test.html | 3 +-- .../ForgotPasswordEmailTemplate.php | 26 +++++++++++++++++++ modules/Core/language/en_UK.json | 4 +-- modules/Core/pages/forgot_password.php | 16 +++--------- 6 files changed, 49 insertions(+), 36 deletions(-) delete mode 100755 custom/templates/DefaultRevamp/email/change_password.html create mode 100755 custom/templates/DefaultRevamp/email/forgot_password.html create mode 100644 modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php diff --git a/custom/templates/DefaultRevamp/email/change_password.html b/custom/templates/DefaultRevamp/email/change_password.html deleted file mode 100755 index 7f8437adfd..0000000000 --- a/custom/templates/DefaultRevamp/email/change_password.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/test.html b/custom/templates/DefaultRevamp/email/test.html index 0d1529f01d..0b2da0c684 100755 --- a/custom/templates/DefaultRevamp/email/test.html +++ b/custom/templates/DefaultRevamp/email/test.html @@ -1,6 +1,5 @@ - - + [Sitename] diff --git a/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php b/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php new file mode 100644 index 0000000000..165987b822 --- /dev/null +++ b/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php @@ -0,0 +1,26 @@ +addPlaceholder('[Link]', $link); + $this->addPlaceholder('[Message]', new LanguageKey('emails', 'forgot_password_message')); + + parent::__construct(); + } + + public function id(): int + { + return self::ID; + } + + public function subject(): LanguageKey + { + return new LanguageKey('emails', 'forgot_password_subject'); + } +} diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index ff94481cc1..371875a56f 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -805,8 +805,8 @@ "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/greeting": "Hi,", diff --git a/modules/Core/pages/forgot_password.php b/modules/Core/pages/forgot_password.php index 386a238ce6..a197fecdc6 100644 --- a/modules/Core/pages/forgot_password.php +++ b/modules/Core/pages/forgot_password.php @@ -54,22 +54,12 @@ $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)), + $sent = Email::sendNext( + $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'); } From 14c2fa9ce68d4c3da4d282b2ee67f0b9eb0d9348 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Wed, 30 Apr 2025 00:02:47 -0700 Subject: [PATCH 05/23] wip --- core/classes/{Core => Alerts}/Alert.php | 0 core/classes/Alerts/AlertTemplate.php | 14 ++++ core/classes/Emails/Email.php | 33 +++++---- core/classes/Emails/EmailTemplate.php | 14 ---- core/classes/Misc/MentionsParser.php | 14 ++-- .../DefaultRevamp/email/api_register.html | 19 ----- modules/Core/classes/Core/Notification.php | 72 +++++++------------ modules/Core/classes/Tasks/SendEmail.php | 23 ++---- modules/Core/hooks/MentionsHook.php | 4 +- .../ForumTopicReplyEmailTemplate.php | 37 ++++++++++ modules/Forum/pages/forum/new_topic.php | 1 - modules/Forum/pages/forum/view_topic.php | 53 +++++++------- 12 files changed, 141 insertions(+), 143 deletions(-) rename core/classes/{Core => Alerts}/Alert.php (100%) create mode 100644 core/classes/Alerts/AlertTemplate.php delete mode 100755 custom/templates/DefaultRevamp/email/api_register.html create mode 100644 modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php diff --git a/core/classes/Core/Alert.php b/core/classes/Alerts/Alert.php similarity index 100% rename from core/classes/Core/Alert.php rename to core/classes/Alerts/Alert.php diff --git a/core/classes/Alerts/AlertTemplate.php b/core/classes/Alerts/AlertTemplate.php new file mode 100644 index 0000000000..4e8759e303 --- /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/Emails/Email.php b/core/classes/Emails/Email.php index 741f325b93..fd803006f7 100644 --- a/core/classes/Emails/Email.php +++ b/core/classes/Emails/Email.php @@ -18,7 +18,6 @@ class Email 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 = 7; @@ -56,27 +55,37 @@ public static function sendNext(User $recipient, EmailTemplate $emailTemplate) /** * Send an email. * - * @param array $recipient Array containing `'email'` and `'name'` strings for the recipient of the email. + * @param User $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. */ - public static function send(array $recipient, string $subject, string $message, ?array $reply_to = null) + public static function sendRaw(int $type, 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', [ + 'type' => $type, + 'content' => $result['error'], + 'at' => date('U'), + 'user_id' => $recipient->data()->id, + ]); } - return self::sendPHP($email); + return $result; } /** diff --git a/core/classes/Emails/EmailTemplate.php b/core/classes/Emails/EmailTemplate.php index c72eed44d3..41356a2853 100644 --- a/core/classes/Emails/EmailTemplate.php +++ b/core/classes/Emails/EmailTemplate.php @@ -59,8 +59,6 @@ final public function addPlaceholder(string $key, $value): void final public function renderContent(string $languageCode): string { - $language = new Language($this->moduleLanguagePath(), $languageCode); - $allPlaceholders = array_merge(self::$_global_placeholders, $this->_placeholders); $placeholderKeys = array_keys($allPlaceholders); $placeholderValues = []; @@ -68,8 +66,6 @@ final public function renderContent(string $languageCode): string foreach ($allPlaceholders as $placeholder) { if ($placeholder instanceof LanguageKey) { $placeholderValues[] = $placeholder->translate($languageCode); - } else if (is_callable($placeholder)) { - $placeholderValues[] = $placeholder($language, $this->name()); } else { $placeholderValues[] = $placeholder; } @@ -98,14 +94,4 @@ private function getPath(): string throw new Exception('Email template not found: ' . $name); } - - private function moduleLanguagePath(): string - { - $reflection = new ReflectionClass($this); - $filePath = $reflection->getFileName(); - - preg_match('#modules/([^/]+)/#', $filePath, $matches); - - return 'modules/' . $matches[1] . '/language'; - } } diff --git a/core/classes/Misc/MentionsParser.php b/core/classes/Misc/MentionsParser.php index 96eb7087ee..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(); @@ -69,6 +65,10 @@ private static function getRecipients(string $content, int $author_id): array preg_match_all(self::USER_MENTIONS_REGEX, $content, $matches); $nicknames = $matches[1]; + if (empty($nicknames)) { + return []; + } + return DB::getInstance()->query( 'SELECT u.id, u.nickname, EXISTS (SELECT 1 FROM nl2_blocked_users bu WHERE bu.user_id = u.id AND bu.user_blocked_id = ?) as blocked_author FROM nl2_users u WHERE u.nickname IN (' . implode(',', array_map(static fn ($_) => '?', $nicknames)) . ')', [ 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/modules/Core/classes/Core/Notification.php b/modules/Core/classes/Core/Notification.php index 297790560c..2d0a19ac42 100644 --- a/modules/Core/classes/Core/Notification.php +++ b/modules/Core/classes/Core/Notification.php @@ -11,12 +11,11 @@ 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 EmailTemplate $_emailTemplate; private static array $_types = []; @@ -36,24 +35,19 @@ class Notification { */ 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, - EmailTemplate $emailTemplate, ) { 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; if (!is_array($recipients)) { $recipients = [$recipients]; @@ -63,29 +57,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); } @@ -93,9 +74,8 @@ 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']; $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 { 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 + $this->_alertTemplate->title->translate($languageCode), + // if the alert has a link set, we don't want to send the content as the alert content + $this->_alertTemplate->link ? null : $this->_alertTemplate->content->translate($languageCode), + $this->_alertTemplate->link, ); } - private function sendEmail(int $userId, string $title, string $content): void { + private function sendEmail(int $userId, string $languageCode): void { $task = (new SendEmail())->fromNew( Module::getIdFromName('Core'), 'Send Email Notification', [ - 'email_template' => $this->_emailTemplate, + 'subject' => $this->_emailTemplate->subject()->translate($languageCode), + 'content' => $this->_emailTemplate->renderContent($languageCode), ], date('U'), // TODO: schedule a date/time? 'User', diff --git a/modules/Core/classes/Tasks/SendEmail.php b/modules/Core/classes/Tasks/SendEmail.php index 8bc2b0567a..3151d1ca67 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( + Email::MASS_MESSAGE, + $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 0360485051..d4566bcdcd 100644 --- a/modules/Core/hooks/MentionsHook.php +++ b/modules/Core/hooks/MentionsHook.php @@ -23,9 +23,9 @@ public static function preCreate(array $params = []): array { $params['content'] = MentionsParser::parseAndNotify( $params['user']->data()->id, $params['content'], - $params['alert_url'], $params['mention_notification_type'], - $params['mention_notification_title'], + $params['mention_notification_alert_template'], + $params['mention_notification_email_template'], ); } diff --git a/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php b/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php new file mode 100644 index 0000000000..1581ab0685 --- /dev/null +++ b/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php @@ -0,0 +1,37 @@ +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 id(): int + { + return self::ID; + } + + 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/pages/forum/new_topic.php b/modules/Forum/pages/forum/new_topic.php index b1282d56b2..10b464d0ba 100644 --- a/modules/Forum/pages/forum/new_topic.php +++ b/modules/Forum/pages/forum/new_topic.php @@ -197,7 +197,6 @@ // Get last post ID $last_post_id = DB::getInstance()->lastId(); $content = EventHandler::executeEvent('preTopicCreate', [ - 'alert_url' => URL::build('/forum/topic/' . urlencode($topic_id), 'pid=' . urlencode($last_post_id)), 'content' => $content, 'user' => $user, 'mention_notification_type' => 'forum_topic_mention', diff --git a/modules/Forum/pages/forum/view_topic.php b/modules/Forum/pages/forum/view_topic.php index b8a8785d9c..cc91035d51 100644 --- a/modules/Forum/pages/forum/view_topic.php +++ b/modules/Forum/pages/forum/view_topic.php @@ -302,13 +302,22 @@ // Get last post ID $last_post_id = DB::getInstance()->lastId(); $content = EventHandler::executeEvent('prePostCreate', [ - 'alert_url' => URL::build('/forum/topic/' . urlencode($tid), 'pid=' . urlencode($last_post_id)), 'content' => $content, 'user' => $user, 'mention_notification_type' => 'forum_topic_mention', - 'mention_notification_title' => new LanguageKey('forum', 'user_tag_info', [ - 'author' => $user->getDisplayname(), - ], ROOT_PATH . '/modules/Forum/language'), + 'mention_notification_alert_template' => new AlertTemplate( + new LanguageKey('forum', 'new_reply_in_topic', [ + 'author' => $user->data()->username, 'topic' => $topic->topic_title + ], ROOT_PATH . '/modules/Forum/language'), + null, + URL::build('/forum/topic/' . urlencode($tid), 'pid=' . urlencode($last_post_id)) + ), + 'mention_notification_email_template' => new ForumTopicReplyEmailTemplate( + $user, + $topic->topic_title, + $content, + URL::build('/forum/topic/' . urlencode($tid), 'pid=' . urlencode($last_post_id)) + ), ])['content']; DB::getInstance()->update('posts', $last_post_id, [ @@ -344,32 +353,26 @@ ])->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 - ); + $replyLink = URL::build('/forum/topic/' . urlencode($tid) . '-' . $forum->titleToURL($topic->topic_title), 'pid=' . $last_post_id); $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, + $replyLink + ), + new ForumTopicReplyEmailTemplate( + $user, + $topic->topic_title, + $original_content, + $replyLink, + ), $users_following, $user->data()->id, - null, - false, - URL::build('/forum/topic/' . urlencode($tid) . '-' . $forum->titleToURL($topic->topic_title), 'pid=' . $last_post_id), + ); $notification->send(); From dfddc1acff1bce31c768c35545a325209a95d2e6 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Wed, 30 Apr 2025 00:06:10 -0700 Subject: [PATCH 06/23] convert test email to sendRaw --- .../templates/DefaultRevamp/email/test.html | 11 --------- .../Email_Templates/TestEmailTemplate.php | 23 ------------------- modules/Core/language/en_UK.json | 2 -- modules/Core/pages/panel/emails.php | 6 +++-- 4 files changed, 4 insertions(+), 38 deletions(-) delete mode 100755 custom/templates/DefaultRevamp/email/test.html delete mode 100644 modules/Core/classes/Email_Templates/TestEmailTemplate.php diff --git a/custom/templates/DefaultRevamp/email/test.html b/custom/templates/DefaultRevamp/email/test.html deleted file mode 100755 index 0b2da0c684..0000000000 --- a/custom/templates/DefaultRevamp/email/test.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - [Sitename] - - - - [Message] - - diff --git a/modules/Core/classes/Email_Templates/TestEmailTemplate.php b/modules/Core/classes/Email_Templates/TestEmailTemplate.php deleted file mode 100644 index c61c33284e..0000000000 --- a/modules/Core/classes/Email_Templates/TestEmailTemplate.php +++ /dev/null @@ -1,23 +0,0 @@ -addPlaceholder('[Message]', new LanguageKey('emails', 'test_message')); - - parent::__construct(); - } - - public function id(): int - { - return self::ID; - } - - public function subject(): LanguageKey - { - return new LanguageKey('emails', 'test_subject'); - } -} diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index 371875a56f..978c62d17f 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -812,8 +812,6 @@ "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/test_subject": "Test email", - "emails/test_message": "Test email successful!", "emails/thanks": "Thanks,", "errors/403_back": "Go back", "errors/403_content": "You do not have permission to view this page.", diff --git a/modules/Core/pages/panel/emails.php b/modules/Core/pages/panel/emails.php index 8305be7e11..e948c4e4b1 100644 --- a/modules/Core/pages/panel/emails.php +++ b/modules/Core/pages/panel/emails.php @@ -55,9 +55,11 @@ if (isset($_GET['do']) && $_GET['do'] == 'send') { $errors = []; - $sent = Email::sendNext( + $sent = Email::sendRaw( + Email::TEST_EMAIL, $user, - new TestEmailTemplate, + 'Test Email', + Output::getClean(SITE_NAME) . ' - Test email successful!' ); if (isset($sent['error'])) { From a9bb2369a8c68ceda3b886b169b4ac74c5cbd99d Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Wed, 30 Apr 2025 17:13:48 -0700 Subject: [PATCH 07/23] refactor email class --- core/classes/Emails/Email.php | 48 +++++++------------ .../includes/endpoints/RegisterEndpoint.php | 2 +- modules/Core/pages/authme_connector.php | 2 +- modules/Core/pages/forgot_password.php | 2 +- modules/Core/pages/panel/users_edit.php | 2 +- modules/Core/pages/register.php | 2 +- 6 files changed, 23 insertions(+), 35 deletions(-) diff --git a/core/classes/Emails/Email.php b/core/classes/Emails/Email.php index fd803006f7..33a2c5f6b2 100644 --- a/core/classes/Emails/Email.php +++ b/core/classes/Emails/Email.php @@ -22,45 +22,33 @@ class Email public const MASS_MESSAGE = 6; public const TEST_EMAIL = 7; - public static function sendNext(User $recipient, EmailTemplate $emailTemplate) + public static function send(User $recipient, EmailTemplate $emailTemplate) { $languageCode = DB::getInstance()->get('languages', ['id', '=', $recipient->data()->language_id])->first()->short_code; - $email = [ - 'to' => [ - 'email' => $recipient->data()->email, - 'name' => $recipient->getDisplayname(), - ], - 'subject' => SITE_NAME . ' - ' . $emailTemplate->subject()->translate($languageCode), - 'message' => $emailTemplate->renderContent($languageCode), - 'replyto' => self::getReplyTo(), - ]; - - $result = Settings::get('phpmailer') == '1' - ? self::sendMailer($email) - : self::sendPHP($email); - - if (isset($result['error'])) { - DB::getInstance()->insert('email_errors', [ - 'type' => $emailTemplate->id(), - 'content' => $result['error'], - 'at' => date('U'), - 'user_id' => $recipient->data()->id, - ]); - } + return self::sendInternal( + $emailTemplate->id(), + $recipient, + $emailTemplate->subject()->translate($languageCode), + $emailTemplate->renderContent($languageCode) + ); + } - return $result; + public static function sendRaw(int $type, User $recipient, string $subject, string $content) + { + return self::sendInternal($type, $recipient, $subject, $content); } /** - * Send an email. + * Internal helper method to handle common email sending logic * - * @param User $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. - * @return bool|array Returns true if email sent, otherwise returns an array containing the error. + * @param int $type Email type 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 sendRaw(int $type, User $recipient, string $subject, string $content) + private static function sendInternal(int $type, User $recipient, string $subject, string $content) { $email = [ 'to' => [ diff --git a/modules/Core/includes/endpoints/RegisterEndpoint.php b/modules/Core/includes/endpoints/RegisterEndpoint.php index 63eb4019c8..391b5287ba 100644 --- a/modules/Core/includes/endpoints/RegisterEndpoint.php +++ b/modules/Core/includes/endpoints/RegisterEndpoint.php @@ -193,7 +193,7 @@ private function sendRegistrationEmail(Nameless2API $api, string $username, stri $user_id = $this->createUser($api, $username, $email, false, $code); $user_id = $user_id['user_id']; - $email = Email::sendNext( + $email = Email::send( new User($user_id), new RegisterEmailTemplate($code), ); diff --git a/modules/Core/pages/authme_connector.php b/modules/Core/pages/authme_connector.php index ae0d5340f6..f641819af2 100644 --- a/modules/Core/pages/authme_connector.php +++ b/modules/Core/pages/authme_connector.php @@ -199,7 +199,7 @@ if (Settings::get('email_verification') === '1') { // Send registration email - Email::sendNext( + Email::send( $user, new RegisterEmailTemplate($code), ); diff --git a/modules/Core/pages/forgot_password.php b/modules/Core/pages/forgot_password.php index a197fecdc6..b7aa028aa2 100644 --- a/modules/Core/pages/forgot_password.php +++ b/modules/Core/pages/forgot_password.php @@ -54,7 +54,7 @@ $code = SecureRandom::alphanumeric(); // Send an email - $sent = Email::sendNext( + $sent = Email::send( $target_user, new ForgotPasswordEmailTemplate($code), ); diff --git a/modules/Core/pages/panel/users_edit.php b/modules/Core/pages/panel/users_edit.php index 89d718ef9b..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 (Email::sendNext($view_user, new RegisterEmailTemplate($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 c6f58d2ff0..e330a29b92 100644 --- a/modules/Core/pages/register.php +++ b/modules/Core/pages/register.php @@ -308,7 +308,7 @@ if (!$auto_verify_oauth_email && Settings::get('email_verification') === '1') { // Send registration email - Email::sendNext( + Email::send( $user, new RegisterEmailTemplate($code), ); From 2b7e13cc4ab0e6ddec387730d83610d6fd9437ef Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Thu, 1 May 2025 14:46:34 -0700 Subject: [PATCH 08/23] remove global placeholders --- core/classes/Emails/EmailTemplate.php | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/core/classes/Emails/EmailTemplate.php b/core/classes/Emails/EmailTemplate.php index 41356a2853..4c26dea6e5 100644 --- a/core/classes/Emails/EmailTemplate.php +++ b/core/classes/Emails/EmailTemplate.php @@ -2,11 +2,6 @@ abstract class EmailTemplate { - /** - * @var array Placeholders for all email templates - */ - private static array $_global_placeholders = []; - /** * @var array Placeholders for this email template */ @@ -14,9 +9,9 @@ abstract class EmailTemplate public function __construct() { - self::addGlobalPlaceholder('[Sitename]', Output::getClean(SITE_NAME)); - self::addGlobalPlaceholder('[Greeting]', new LanguageKey('emails', 'greeting')); - self::addGlobalPlaceholder('[Thanks]', new LanguageKey('emails', 'thanks')); + $this->addPlaceholder('[Sitename]', Output::getClean(SITE_NAME)); + $this->addPlaceholder('[Greeting]', new LanguageKey('emails', 'greeting')); + $this->addPlaceholder('[Thanks]', new LanguageKey('emails', 'thanks')); } public abstract function id(): int; @@ -35,17 +30,6 @@ private function name(): string public abstract function subject(): LanguageKey; - /** - * 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 addGlobalPlaceholder(string $key, $value): void - { - self::$_global_placeholders[$key] = $value; - } - /** * Add a custom placeholder/variable for email messages. * @@ -59,11 +43,10 @@ final public function addPlaceholder(string $key, $value): void final public function renderContent(string $languageCode): string { - $allPlaceholders = array_merge(self::$_global_placeholders, $this->_placeholders); - $placeholderKeys = array_keys($allPlaceholders); + $placeholderKeys = array_keys($this->_placeholders); $placeholderValues = []; - foreach ($allPlaceholders as $placeholder) { + foreach ($this->_placeholders as $placeholder) { if ($placeholder instanceof LanguageKey) { $placeholderValues[] = $placeholder->translate($languageCode); } else { From 02ee70830053cf6ddfe608b2def65448d6aa5f2f Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 01:21:18 -0700 Subject: [PATCH 09/23] wip --- core/classes/Alerts/AlertTemplate.php | 2 +- core/classes/Emails/Email.php | 2 + core/classes/Misc/Report.php | 19 +++++---- .../email/forum_topic_mention.html | 19 +++++++++ .../email/forum_topic_reply.html | 3 +- .../DefaultRevamp/email/mass_message.html | 16 ++++++++ .../DefaultRevamp/email/report_created.html | 17 ++++++++ dev/scripts/find_unused_language_terms.sh | 1 - modules/Core/classes/Core/Notification.php | 10 ++++- .../MassMessageEmailTemplate.php | 23 +++++++++++ .../ReportCreatedEmailTemplate.php | 27 +++++++++++++ .../GenerateNotificationContentEvent.php | 4 +- modules/Core/classes/Tasks/MassMessage.php | 29 +++++++------- modules/Core/hooks/MentionsHook.php | 7 ++-- modules/Core/language/en_UK.json | 3 ++ modules/Core/pages/panel/mass_message.php | 4 +- .../ForumTopicMentionEmailTemplate.php | 35 +++++++++++++++++ .../classes/Events/PrePostCreateEvent.php | 20 ++++++---- .../classes/Events/PreTopicCreateEvent.php | 20 ++++++---- modules/Forum/language/en_UK.json | 1 - modules/Forum/pages/forum/new_topic.php | 18 +++++++-- modules/Forum/pages/forum/view_topic.php | 39 ++++++++----------- 22 files changed, 243 insertions(+), 76 deletions(-) create mode 100644 custom/templates/DefaultRevamp/email/forum_topic_mention.html create mode 100755 custom/templates/DefaultRevamp/email/mass_message.html create mode 100755 custom/templates/DefaultRevamp/email/report_created.html create mode 100644 modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php create mode 100644 modules/Core/classes/Email_Templates/ReportCreatedEmailTemplate.php create mode 100644 modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php diff --git a/core/classes/Alerts/AlertTemplate.php b/core/classes/Alerts/AlertTemplate.php index 4e8759e303..d08ba8bc3e 100644 --- a/core/classes/Alerts/AlertTemplate.php +++ b/core/classes/Alerts/AlertTemplate.php @@ -4,7 +4,7 @@ class AlertTemplate { public function __construct( public LanguageKey $title, - public ?LanguageKey $content, + public LanguageKey|string|null $content = null, public ?string $link = null, ) { if ($this->link === null && $this->content === null) { diff --git a/core/classes/Emails/Email.php b/core/classes/Emails/Email.php index 33a2c5f6b2..48e7348145 100644 --- a/core/classes/Emails/Email.php +++ b/core/classes/Emails/Email.php @@ -21,6 +21,8 @@ class Email public const FORUM_TOPIC_REPLY = 5; public const MASS_MESSAGE = 6; public const TEST_EMAIL = 7; + public const REPORT = 8; + public const FORUM_TOPIC_MENTION = 9; public static function send(User $recipient, EmailTemplate $emailTemplate) { 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/custom/templates/DefaultRevamp/email/forum_topic_mention.html b/custom/templates/DefaultRevamp/email/forum_topic_mention.html new file mode 100644 index 0000000000..7a864f2264 --- /dev/null +++ b/custom/templates/DefaultRevamp/email/forum_topic_mention.html @@ -0,0 +1,19 @@ + + + + + + [Sitename] • [TopicReply] + + + +
+

[Greeting]

+

[Message]

+ [Link] +
+ [Thanks]
[Sitename] +
+ + + 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/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 2d0a19ac42..f4b6e907a7 100644 --- a/modules/Core/classes/Core/Notification.php +++ b/modules/Core/classes/Core/Notification.php @@ -96,11 +96,19 @@ public function send(): void { } private function sendAlert(int $userId, string $languageCode): void { + 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, $this->_alertTemplate->title->translate($languageCode), // if the alert has a link set, we don't want to send the content as the alert content - $this->_alertTemplate->link ? null : $this->_alertTemplate->content->translate($languageCode), + $content, $this->_alertTemplate->link, ); } diff --git a/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php b/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php new file mode 100644 index 0000000000..54099d6d77 --- /dev/null +++ b/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php @@ -0,0 +1,23 @@ +addPlaceholder('[Message]', $content); + + parent::__construct(); + } + + public function id(): int + { + return self::ID; + } + + public function subject(): LanguageKey + { + return new LanguageKey('admin', 'mass_message'); + } +} diff --git a/modules/Core/classes/Email_Templates/ReportCreatedEmailTemplate.php b/modules/Core/classes/Email_Templates/ReportCreatedEmailTemplate.php new file mode 100644 index 0000000000..3d050e6a2a --- /dev/null +++ b/modules/Core/classes/Email_Templates/ReportCreatedEmailTemplate.php @@ -0,0 +1,27 @@ +addPlaceholder('[Link]', $link); + $this->addPlaceholder('[Message]', new LanguageKey('emails', 'report_message', [ + 'reported' => $reported->getDisplayname(), + 'author' => $author->getDisplayname(), + ])); + + parent::__construct(); + } + + public function id(): int + { + return self::ID; + } + + public function subject(): LanguageKey + { + return new LanguageKey('emails', 'report_subject'); + } +} diff --git a/modules/Core/classes/Events/GenerateNotificationContentEvent.php b/modules/Core/classes/Events/GenerateNotificationContentEvent.php index 1ac9a3c184..36f4db3be4 100644 --- a/modules/Core/classes/Events/GenerateNotificationContentEvent.php +++ b/modules/Core/classes/Events/GenerateNotificationContentEvent.php @@ -5,13 +5,11 @@ class GenerateNotificationContentEvent extends AbstractEvent { public string $content; public bool $skip_purify; public string $title; - public User $user; - public function __construct(string $content, string $title, User $user, bool $skip_purify = false) { + public function __construct(string $content, string $title, bool $skip_purify = false) { $this->content = $content; $this->skip_purify = $skip_purify; $this->title = $title; - $this->user = $user; } public static function name(): string { diff --git a/modules/Core/classes/Tasks/MassMessage.php b/modules/Core/classes/Tasks/MassMessage.php index 1347a3d65a..9e428b59cd 100644 --- a/modules/Core/classes/Tasks/MassMessage.php +++ b/modules/Core/classes/Tasks/MassMessage.php @@ -32,14 +32,25 @@ public function run(): string { $whereVars ); + $content = $this->getData()['content']; + $title = $this->getData()['title']; + $skipPurify = $this->getData()['skip_purify'] ?? false; + + $event = new GenerateNotificationContentEvent($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( + new LanguageKey('admin', 'mass_message'), + $content, + ), + new MassMessageEmailTemplate( + $content, + ), array_map(static fn ($r) => $r->id, $recipients->results()), $this->getUserId(), - $this->getData()['callback'], - $this->getData()['skip_purify'] ?? false ); $notification->send(); @@ -48,12 +59,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/hooks/MentionsHook.php b/modules/Core/hooks/MentionsHook.php index 7e91a6e5b3..cc099b5a2d 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->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/language/en_UK.json b/modules/Core/language/en_UK.json index a89bbb292e..c8efee2b0c 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -797,10 +797,13 @@ "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", diff --git a/modules/Core/pages/panel/mass_message.php b/modules/Core/pages/panel/mass_message.php index 3f3cc08cc5..e684c47370 100644 --- a/modules/Core/pages/panel/mass_message.php +++ b/modules/Core/pages/panel/mass_message.php @@ -150,10 +150,8 @@ 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'), ], diff --git a/modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php b/modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php new file mode 100644 index 0000000000..6bc05c1980 --- /dev/null +++ b/modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php @@ -0,0 +1,35 @@ +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 id(): int + { + return self::ID; + } + + public function subject(): LanguageKey + { + return new LanguageKey('user', 'user_tag_info', [ + 'author' => $this->author->data()->username + ]); + } +} 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 e93cdbbf08..d214ac33ff 100644 --- a/modules/Forum/pages/forum/new_topic.php +++ b/modules/Forum/pages/forum/new_topic.php @@ -197,14 +197,24 @@ // 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 9839100753..69d23bb126 100644 --- a/modules/Forum/pages/forum/view_topic.php +++ b/modules/Forum/pages/forum/view_topic.php @@ -302,28 +302,23 @@ // Get last post ID $last_post_id = DB::getInstance()->lastId(); - // 'mention_notification_type' => 'forum_topic_mention', - // 'mention_notification_alert_template' => new AlertTemplate( - // new LanguageKey('forum', 'new_reply_in_topic', [ - // 'author' => $user->data()->username, 'topic' => $topic->topic_title - // ], ROOT_PATH . '/modules/Forum/language'), - // null, - // URL::build('/forum/topic/' . urlencode($tid), 'pid=' . urlencode($last_post_id)) - // ), - // 'mention_notification_email_template' => new ForumTopicReplyEmailTemplate( - // $user, - // $topic->topic_title, - // $content, - // URL::build('/forum/topic/' . urlencode($tid), 'pid=' . urlencode($last_post_id)) - // ), + $post_link = 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); @@ -354,15 +349,13 @@ $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); - $replyLink = URL::build('/forum/topic/' . urlencode($tid) . '-' . $forum->titleToURL($topic->topic_title), 'pid=' . $last_post_id); - $notification = new Notification( 'forum_topic_reply', new AlertTemplate( @@ -370,13 +363,13 @@ 'author' => $user->data()->username, 'topic' => $topic->topic_title ], ROOT_PATH . '/modules/Forum/language'), null, - $replyLink + $post_link, ), new ForumTopicReplyEmailTemplate( $user, $topic->topic_title, $original_content, - $replyLink, + $post_link, ), $users_following, $user->data()->id, From 6b1f735ecd806b2487c6c0f47735215edc8f0d0e Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 01:30:08 -0700 Subject: [PATCH 10/23] remove email message editing + previewing --- core/classes/Emails/EmailTemplate.php | 2 +- .../panel_templates/Default/core/emails.tpl | 1 - .../Default/core/emails_edit_messages.tpl | 136 ------------------ .../core/emails_edit_messages_preview.tpl | 1 - modules/Core/classes/Core/Notification.php | 7 +- modules/Core/hooks/MentionsHook.php | 2 +- modules/Core/language/en_UK.json | 5 - modules/Core/pages/panel/emails.php | 118 +++------------ 8 files changed, 22 insertions(+), 250 deletions(-) delete mode 100644 custom/panel_templates/Default/core/emails_edit_messages.tpl delete mode 100644 custom/panel_templates/Default/core/emails_edit_messages_preview.tpl diff --git a/core/classes/Emails/EmailTemplate.php b/core/classes/Emails/EmailTemplate.php index 4c26dea6e5..1e02d088ad 100644 --- a/core/classes/Emails/EmailTemplate.php +++ b/core/classes/Emails/EmailTemplate.php @@ -3,7 +3,7 @@ abstract class EmailTemplate { /** - * @var array Placeholders for this email template + * @var array Placeholders for this email template */ private array $_placeholders = []; 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/modules/Core/classes/Core/Notification.php b/modules/Core/classes/Core/Notification.php index f4b6e907a7..20c80ce224 100644 --- a/modules/Core/classes/Core/Notification.php +++ b/modules/Core/classes/Core/Notification.php @@ -23,13 +23,10 @@ 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 * * @throws NotificationTypeNotFoundException */ diff --git a/modules/Core/hooks/MentionsHook.php b/modules/Core/hooks/MentionsHook.php index cc099b5a2d..a4cc018b8e 100644 --- a/modules/Core/hooks/MentionsHook.php +++ b/modules/Core/hooks/MentionsHook.php @@ -21,7 +21,7 @@ class MentionsHook extends HookBase { public static function preCreate(AbstractEvent $event): void { if (!empty($event->content) && isset($event->user)) { // TODO: better subclassing? ie: $event instanceof MentionableEvent? - if (isset($event->mention_notification_type)) { + 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, diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index c8efee2b0c..90cbb6661f 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -168,8 +168,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", @@ -191,8 +189,6 @@ "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.", @@ -1091,7 +1087,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/pages/panel/emails.php b/modules/Core/pages/panel/emails.php index e948c4e4b1..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') { @@ -91,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 @@ -146,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'); @@ -201,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'), From 94b445ac90962fbd742062182050ebee14d0b78e Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 01:32:39 -0700 Subject: [PATCH 11/23] unused language keys --- modules/Core/language/en_UK.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index 90cbb6661f..ddd0c62110 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -160,7 +160,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.", @@ -179,13 +178,7 @@ "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.", @@ -792,7 +785,6 @@ "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:", From 79932f38deea20d899e1b6c89729d6bc8de8d053 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 11:54:38 -0700 Subject: [PATCH 12/23] convert email error type column to mailer --- core/classes/Database/DB.php | 4 +- ...934_convert_email_error_type_to_string.php | 37 +++++++++++++++++++ dev/scripts/cli_install.php | 13 ++----- 3 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 core/migrations/20250503090934_convert_email_error_type_to_string.php diff --git a/core/classes/Database/DB.php b/core/classes/Database/DB.php index ec12e236f1..7a9f6456fe 100644 --- a/core/classes/Database/DB.php +++ b/core/classes/Database/DB.php @@ -357,11 +357,11 @@ 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|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/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..6abc7d565e --- /dev/null +++ b/core/migrations/20250503090934_convert_email_error_type_to_string.php @@ -0,0 +1,37 @@ + 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/dev/scripts/cli_install.php b/dev/scripts/cli_install.php index 0a34a4bc10..64808c7993 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([ From 7488b318b140672cfb3fdf06930566abf8dbdd89 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 12:07:20 -0700 Subject: [PATCH 13/23] move away from type ints --- core/classes/Emails/Email.php | 21 ++--- core/classes/Emails/EmailTemplate.php | 2 - .../Default/core/emails_errors.tpl | 60 +++++++------- .../Default/core/emails_errors_view.tpl | 19 ++--- .../ForgotPasswordEmailTemplate.php | 7 -- .../MassMessageEmailTemplate.php | 7 -- .../Email_Templates/RegisterEmailTemplate.php | 7 -- .../ReportCreatedEmailTemplate.php | 7 -- modules/Core/language/en_UK.json | 1 + modules/Core/pages/panel/emails_errors.php | 80 +++---------------- .../ForumTopicMentionEmailTemplate.php | 7 -- .../ForumTopicReplyEmailTemplate.php | 7 -- 12 files changed, 55 insertions(+), 170 deletions(-) diff --git a/core/classes/Emails/Email.php b/core/classes/Emails/Email.php index 48e7348145..a69fca9731 100644 --- a/core/classes/Emails/Email.php +++ b/core/classes/Emails/Email.php @@ -16,41 +16,36 @@ class Email { public const EMAIL_MAX_LENGTH = 75000; - public const REGISTRATION = 1; - public const FORGOT_PASSWORD = 3; - public const FORUM_TOPIC_REPLY = 5; - public const MASS_MESSAGE = 6; - public const TEST_EMAIL = 7; - public const REPORT = 8; - public const FORUM_TOPIC_MENTION = 9; + public const TEST_EMAIL = 'TestEmail'; + public const MASS_MESSAGE = 'MassMessage'; public static function send(User $recipient, EmailTemplate $emailTemplate) { $languageCode = DB::getInstance()->get('languages', ['id', '=', $recipient->data()->language_id])->first()->short_code; return self::sendInternal( - $emailTemplate->id(), + str_replace('EmailTemplate', '', $emailTemplate::class), $recipient, $emailTemplate->subject()->translate($languageCode), $emailTemplate->renderContent($languageCode) ); } - public static function sendRaw(int $type, User $recipient, string $subject, string $content) + public static function sendRaw(string $mailer, User $recipient, string $subject, string $content) { - return self::sendInternal($type, $recipient, $subject, $content); + return self::sendInternal($mailer, $recipient, $subject, $content); } /** * Internal helper method to handle common email sending logic * - * @param int $type Email type identifier + * @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 */ - private static function sendInternal(int $type, User $recipient, string $subject, string $content) + private static function sendInternal(string $mailer, User $recipient, string $subject, string $content) { $email = [ 'to' => [ @@ -68,7 +63,7 @@ private static function sendInternal(int $type, User $recipient, string $subject if (isset($result['error'])) { DB::getInstance()->insert('email_errors', [ - 'type' => $type, + 'mailer' => $mailer, 'content' => $result['error'], 'at' => date('U'), 'user_id' => $recipient->data()->id, diff --git a/core/classes/Emails/EmailTemplate.php b/core/classes/Emails/EmailTemplate.php index 1e02d088ad..344451ee5b 100644 --- a/core/classes/Emails/EmailTemplate.php +++ b/core/classes/Emails/EmailTemplate.php @@ -14,8 +14,6 @@ public function __construct() $this->addPlaceholder('[Thanks]', new LanguageKey('emails', 'thanks')); } - public abstract function id(): int; - /** * Returns the snake_case representation of the email template name, * derived from the class name with "EmailTemplate" removed. 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/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php b/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php index 165987b822..ee65e2229e 100644 --- a/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php +++ b/modules/Core/classes/Email_Templates/ForgotPasswordEmailTemplate.php @@ -2,8 +2,6 @@ class ForgotPasswordEmailTemplate extends EmailTemplate { - public const ID = 3; - public function __construct(string $code) { $link = rtrim(URL::getSelfURL(), '/') . URL::build('/forgot_password/', 'c=' . urlencode($code)); @@ -14,11 +12,6 @@ public function __construct(string $code) parent::__construct(); } - public function id(): int - { - return self::ID; - } - 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 index 54099d6d77..9cd3a724bc 100644 --- a/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php +++ b/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php @@ -2,8 +2,6 @@ class MassMessageEmailTemplate extends EmailTemplate { - public const ID = 6; - public function __construct(string $content) { $this->addPlaceholder('[Message]', $content); @@ -11,11 +9,6 @@ public function __construct(string $content) parent::__construct(); } - public function id(): int - { - return self::ID; - } - public function subject(): LanguageKey { return new LanguageKey('admin', 'mass_message'); diff --git a/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php b/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php index 71634fc55d..f9c627362f 100644 --- a/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php +++ b/modules/Core/classes/Email_Templates/RegisterEmailTemplate.php @@ -2,8 +2,6 @@ class RegisterEmailTemplate extends EmailTemplate { - public const ID = 1; - public function __construct(string $code) { // TODO sometimes this needs to be complete_signup? @@ -15,11 +13,6 @@ public function __construct(string $code) parent::__construct(); } - public function id(): int - { - return self::ID; - } - 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 index 3d050e6a2a..1ea2ecf11e 100644 --- a/modules/Core/classes/Email_Templates/ReportCreatedEmailTemplate.php +++ b/modules/Core/classes/Email_Templates/ReportCreatedEmailTemplate.php @@ -2,8 +2,6 @@ class ReportCreatedEmailTemplate extends EmailTemplate { - public const ID = 8; - public function __construct(string $link, User $reported, User $author) { $this->addPlaceholder('[Link]', $link); @@ -15,11 +13,6 @@ public function __construct(string $link, User $reported, User $author) parent::__construct(); } - public function id(): int - { - return self::ID; - } - public function subject(): LanguageKey { return new LanguageKey('emails', 'report_subject'); diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index ddd0c62110..95096f467a 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -771,6 +771,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:", diff --git a/modules/Core/pages/panel/emails_errors.php b/modules/Core/pages/panel/emails_errors.php index 6ba709f15a..43504159d3 100644 --- a/modules/Core/pages/panel/emails_errors.php +++ b/modules/Core/pages/panel/emails_errors.php @@ -62,27 +62,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 +69,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 +83,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 +124,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 +134,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/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php b/modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php index 6bc05c1980..6cffb2413e 100644 --- a/modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php +++ b/modules/Forum/classes/Email_Templates/ForumTopicMentionEmailTemplate.php @@ -2,8 +2,6 @@ class ForumTopicMentionEmailTemplate extends EmailTemplate { - public const ID = 9; - private User $author; public function __construct(User $author, string $content, string $link) @@ -21,11 +19,6 @@ public function __construct(User $author, string $content, string $link) parent::__construct(); } - public function id(): int - { - return self::ID; - } - public function subject(): LanguageKey { return new LanguageKey('user', 'user_tag_info', [ diff --git a/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php b/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php index 1581ab0685..487f7efc6b 100644 --- a/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php +++ b/modules/Forum/classes/Email_Templates/ForumTopicReplyEmailTemplate.php @@ -2,8 +2,6 @@ class ForumTopicReplyEmailTemplate extends EmailTemplate { - public const ID = 5; - private User $author; private string $topicTitle; @@ -23,11 +21,6 @@ public function __construct(User $author, string $topicTitle, string $replyConte parent::__construct(); } - public function id(): int - { - return self::ID; - } - public function subject(): LanguageKey { return new LanguageKey('forum', 'new_reply_in_topic', [ From 9afce6e13b8d792c90550fd366ade8db89cc468b Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 12:09:41 -0700 Subject: [PATCH 14/23] unused terms --- modules/Core/language/en_UK.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index 95096f467a..1f72c7d63b 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)", @@ -243,9 +242,7 @@ "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", @@ -579,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", @@ -637,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}}.", From a8315ab0741c0c7aa0fab50ca430b8321b2cbffd Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 12:44:26 -0700 Subject: [PATCH 15/23] fixes --- core/classes/Alerts/AlertTemplate.php | 2 +- core/classes/Emails/EmailTemplate.php | 2 +- modules/Core/classes/Core/Notification.php | 27 +++++++++++++++++-- .../MassMessageEmailTemplate.php | 10 ++++--- modules/Core/classes/Tasks/MassMessage.php | 6 +++-- modules/Core/classes/Tasks/SendEmail.php | 2 +- modules/Core/language/en_UK.json | 1 + modules/Core/pages/panel/mass_message.php | 1 + modules/Forum/pages/forum/view_topic.php | 2 +- 9 files changed, 42 insertions(+), 11 deletions(-) diff --git a/core/classes/Alerts/AlertTemplate.php b/core/classes/Alerts/AlertTemplate.php index d08ba8bc3e..2f8762f8a5 100644 --- a/core/classes/Alerts/AlertTemplate.php +++ b/core/classes/Alerts/AlertTemplate.php @@ -3,7 +3,7 @@ class AlertTemplate { public function __construct( - public LanguageKey $title, + public LanguageKey|string $title, public LanguageKey|string|null $content = null, public ?string $link = null, ) { diff --git a/core/classes/Emails/EmailTemplate.php b/core/classes/Emails/EmailTemplate.php index 344451ee5b..d5bf250044 100644 --- a/core/classes/Emails/EmailTemplate.php +++ b/core/classes/Emails/EmailTemplate.php @@ -26,7 +26,7 @@ private function name(): string return strtolower(preg_replace('/(?_alertTemplate = $alertTemplate; $this->_emailTemplate = $emailTemplate; $this->_authorId = $authorId; + $this->_bypassNotificationSettings = $bypassNotificationSettings; if (!is_array($recipients)) { $recipients = [$recipients]; @@ -74,6 +78,12 @@ public function send(): void { $userId = $recipient['id']; $languageCode = $recipient['language_code']; + if ($this->_bypassNotificationSettings) { + $this->sendAlert($userId, $languageCode); + $this->sendEmail($userId, $languageCode); + continue; + } + $preferences = DB::getInstance()->query( <<_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) { @@ -103,7 +119,7 @@ private function sendAlert(int $userId, string $languageCode): void { Alert::send( $userId, - $this->_alertTemplate->title->translate($languageCode), + $title, // if the alert has a link set, we don't want to send the content as the alert content $content, $this->_alertTemplate->link, @@ -111,12 +127,19 @@ private function sendAlert(int $userId, string $languageCode): 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', [ - 'subject' => $this->_emailTemplate->subject()->translate($languageCode), + '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/MassMessageEmailTemplate.php b/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php index 9cd3a724bc..b721936b64 100644 --- a/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php +++ b/modules/Core/classes/Email_Templates/MassMessageEmailTemplate.php @@ -2,15 +2,19 @@ class MassMessageEmailTemplate extends EmailTemplate { - public function __construct(string $content) + private string $subject; + + public function __construct(string $subject, string $content) { + $this->subject = $subject; + $this->addPlaceholder('[Message]', $content); parent::__construct(); } - public function subject(): LanguageKey + public function subject(): string { - return new LanguageKey('admin', 'mass_message'); + return $this->subject; } } diff --git a/modules/Core/classes/Tasks/MassMessage.php b/modules/Core/classes/Tasks/MassMessage.php index 9e428b59cd..8e3eda78df 100644 --- a/modules/Core/classes/Tasks/MassMessage.php +++ b/modules/Core/classes/Tasks/MassMessage.php @@ -34,7 +34,7 @@ public function run(): string { $content = $this->getData()['content']; $title = $this->getData()['title']; - $skipPurify = $this->getData()['skip_purify'] ?? false; + $skipPurify = $this->getData()['skip_purify']; $event = new GenerateNotificationContentEvent($content, $title, $skipPurify); EventHandler::executeEvent($event); @@ -43,14 +43,16 @@ public function run(): string { $notification = new Notification( 'mass_message', new AlertTemplate( - new LanguageKey('admin', 'mass_message'), + $title, $content, ), new MassMessageEmailTemplate( + $title, $content, ), array_map(static fn ($r) => $r->id, $recipients->results()), $this->getUserId(), + (bool) $this->getData()['bypass_notification_settings'], ); $notification->send(); diff --git a/modules/Core/classes/Tasks/SendEmail.php b/modules/Core/classes/Tasks/SendEmail.php index 3151d1ca67..7d89149460 100644 --- a/modules/Core/classes/Tasks/SendEmail.php +++ b/modules/Core/classes/Tasks/SendEmail.php @@ -47,7 +47,7 @@ public function run(): string { } $sent = Email::sendRaw( - Email::MASS_MESSAGE, + $this->getData()['mailer'], $user, $this->getData()['subject'], $this->getData()['content'], diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index 1f72c7d63b..6aa03b67a9 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -178,6 +178,7 @@ "admin/email_errors_logged": "Email errors have been logged", "admin/email_errors_purged_successfully": "Email errors have been purged successfully.", "admin/email_logs": "Mass Emails", + "admin/email_message_subject": "Subject", "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.", diff --git a/modules/Core/pages/panel/mass_message.php b/modules/Core/pages/panel/mass_message.php index e684c47370..96c42e70d1 100644 --- a/modules/Core/pages/panel/mass_message.php +++ b/modules/Core/pages/panel/mass_message.php @@ -154,6 +154,7 @@ '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/Forum/pages/forum/view_topic.php b/modules/Forum/pages/forum/view_topic.php index 69d23bb126..0bfe740fcd 100644 --- a/modules/Forum/pages/forum/view_topic.php +++ b/modules/Forum/pages/forum/view_topic.php @@ -302,7 +302,7 @@ // Get last post ID $last_post_id = DB::getInstance()->lastId(); - $post_link = URL::build('/forum/topic/' . urlencode($tid) . '-' . $forum->titleToURL($topic->topic_title), 'pid=' . $last_post_id); + $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, From 60fb6d55011933b2021df9fc7dc3644c7d21b0de Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 12:57:14 -0700 Subject: [PATCH 16/23] fix notification settings in cli install --- dev/scripts/cli_install.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dev/scripts/cli_install.php b/dev/scripts/cli_install.php index 64808c7993..4f94fd93fd 100644 --- a/dev/scripts/cli_install.php +++ b/dev/scripts/cli_install.php @@ -216,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); From 05ff9f78ec7b4cd4501e97783eedb6d42c34a7b5 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 13:14:25 -0700 Subject: [PATCH 17/23] fix forgot password flow messages --- custom/templates/DefaultRevamp/login.tpl | 12 ++++++++++++ modules/Core/pages/forgot_password.php | 22 ++++++++-------------- modules/Core/pages/login.php | 5 ++++- 3 files changed, 24 insertions(+), 15 deletions(-) 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/modules/Core/pages/forgot_password.php b/modules/Core/pages/forgot_password.php index b7aa028aa2..d1b109986c 100644 --- a/modules/Core/pages/forgot_password.php +++ b/modules/Core/pages/forgot_password.php @@ -61,9 +61,7 @@ if (isset($sent['error'])) { $error = $language->get('user', 'unable_to_send_forgot_password_email'); - } - - if (!isset($error)) { + } else { $target_user->update([ 'reset_code' => $code ]); @@ -114,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()) { @@ -142,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) { From 5f14e072168145baad007699f57670c3ab29b6f3 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 13:24:56 -0700 Subject: [PATCH 18/23] rename content event --- ...ntEvent.php => GenerateMassMessageContentEvent.php} | 10 +--------- modules/Core/classes/Tasks/MassMessage.php | 2 +- modules/Core/module.php | 8 ++++---- modules/Core/pages/panel/emails_errors.php | 6 ++++++ 4 files changed, 12 insertions(+), 14 deletions(-) rename modules/Core/classes/Events/{GenerateNotificationContentEvent.php => GenerateMassMessageContentEvent.php} (55%) diff --git a/modules/Core/classes/Events/GenerateNotificationContentEvent.php b/modules/Core/classes/Events/GenerateMassMessageContentEvent.php similarity index 55% rename from modules/Core/classes/Events/GenerateNotificationContentEvent.php rename to modules/Core/classes/Events/GenerateMassMessageContentEvent.php index 36f4db3be4..ca7f27e402 100644 --- a/modules/Core/classes/Events/GenerateNotificationContentEvent.php +++ b/modules/Core/classes/Events/GenerateMassMessageContentEvent.php @@ -1,6 +1,6 @@ title = $title; } - 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/Tasks/MassMessage.php b/modules/Core/classes/Tasks/MassMessage.php index 8e3eda78df..94b54d00fb 100644 --- a/modules/Core/classes/Tasks/MassMessage.php +++ b/modules/Core/classes/Tasks/MassMessage.php @@ -36,7 +36,7 @@ public function run(): string { $title = $this->getData()['title']; $skipPurify = $this->getData()['skip_purify']; - $event = new GenerateNotificationContentEvent($content, $title, $skipPurify); + $event = new GenerateMassMessageContentEvent($content, $title, $skipPurify); EventHandler::executeEvent($event); $content = $event->content; diff --git a/modules/Core/module.php b/modules/Core/module.php index e83f2b5cb0..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); diff --git a/modules/Core/pages/panel/emails_errors.php b/modules/Core/pages/panel/emails_errors.php index 43504159d3..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')); } From b50564c34877fc3a87bd7f40f5ee547ca1da47e9 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 13:29:42 -0700 Subject: [PATCH 19/23] unused term --- modules/Core/language/en_UK.json | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/Core/language/en_UK.json b/modules/Core/language/en_UK.json index 6aa03b67a9..c04093ed5b 100644 --- a/modules/Core/language/en_UK.json +++ b/modules/Core/language/en_UK.json @@ -245,7 +245,6 @@ "admin/force_www": "Force www?", "admin/forum_posts": "Display on Forum", "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.", From a239f45348380d0191b1935ba86f1a74a831ad88 Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sat, 3 May 2025 13:30:16 -0700 Subject: [PATCH 20/23] Apply fixes from StyleCI (#3640) Co-authored-by: StyleCI Bot --- core/classes/Database/DB.php | 8 ++++---- core/classes/Emails/Email.php | 10 +++++----- core/classes/Emails/EmailTemplate.php | 2 +- ...250503090934_convert_email_error_type_to_string.php | 1 + dev/scripts/cli_install.php | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/core/classes/Database/DB.php b/core/classes/Database/DB.php index 7a9f6456fe..dfe2202000 100644 --- a/core/classes/Database/DB.php +++ b/core/classes/Database/DB.php @@ -356,10 +356,10 @@ public function insert(string $table, array $fields = []): bool /** * Perform an UPDATE query on a table. * - * @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. + * @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, array|int $where, array $fields): bool { diff --git a/core/classes/Emails/Email.php b/core/classes/Emails/Email.php index a69fca9731..184e18899c 100644 --- a/core/classes/Emails/Email.php +++ b/core/classes/Emails/Email.php @@ -37,12 +37,12 @@ public static function sendRaw(string $mailer, User $recipient, string $subject, } /** - * Internal helper method to handle common email sending logic + * Internal helper method to handle common email sending logic. * - * @param string $mailer Email mailer identifier - * @param User $recipient Recipient user object - * @param string $subject Email subject - * @param string $content Email content + * @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 */ private static function sendInternal(string $mailer, User $recipient, string $subject, string $content) diff --git a/core/classes/Emails/EmailTemplate.php b/core/classes/Emails/EmailTemplate.php index d5bf250044..7592fe14d4 100644 --- a/core/classes/Emails/EmailTemplate.php +++ b/core/classes/Emails/EmailTemplate.php @@ -26,7 +26,7 @@ private function name(): string return strtolower(preg_replace('/(? $type['defaultPreferences']['alert'] || $type['defaultPreferences']['email'] + static fn ($type) => $type['defaultPreferences']['alert'] || $type['defaultPreferences']['email'] ); foreach ($defaultNotifications as $notificationType) { From 7592d7b642f8ac3b00b6c1322f098fa1cd6308d2 Mon Sep 17 00:00:00 2001 From: partydragen Date: Sun, 4 May 2025 12:16:37 +0200 Subject: [PATCH 21/23] WIP --- .../DefaultRevamp/email/notification.html | 16 +++++++++++ modules/Core/classes/Core/Notification.php | 17 ++++++++--- .../NotificationEmailTemplate.php | 22 +++++++++++++++ modules/Core/classes/Tasks/MassMessage.php | 14 ++++------ modules/Forum/pages/forum/view_topic.php | 28 ++++++++++--------- 5 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 custom/templates/DefaultRevamp/email/notification.html create mode 100644 modules/Core/classes/Email_Templates/NotificationEmailTemplate.php diff --git a/custom/templates/DefaultRevamp/email/notification.html b/custom/templates/DefaultRevamp/email/notification.html new file mode 100644 index 0000000000..794ecd2c83 --- /dev/null +++ b/custom/templates/DefaultRevamp/email/notification.html @@ -0,0 +1,16 @@ + + + + + [Sitename] • [Title] + + + +
+

[Greeting]

+

[Content]

+
+ [Thanks]
[Sitename] +
+ + \ No newline at end of file diff --git a/modules/Core/classes/Core/Notification.php b/modules/Core/classes/Core/Notification.php index 293f706bbe..46fc9dcbf9 100644 --- a/modules/Core/classes/Core/Notification.php +++ b/modules/Core/classes/Core/Notification.php @@ -34,19 +34,20 @@ class Notification { */ public function __construct( string $type, - AlertTemplate $alertTemplate, - EmailTemplate $emailTemplate, + string|LanguageKey $title, + string|LanguageKey $content, int|array $recipients, int $authorId, bool $bypassNotificationSettings = false, + ?string $link = null, ) { if (!in_array($type, array_column(self::getTypes(), 'key'))) { throw new NotificationTypeNotFoundException("Type $type not registered"); } $this->_type = $type; - $this->_alertTemplate = $alertTemplate; - $this->_emailTemplate = $emailTemplate; + $this->_alertTemplate = new AlertTemplate($title, $content, $link); + $this->_emailTemplate = new NotificationEmailTemplate($title, $content, $link); $this->_authorId = $authorId; $this->_bypassNotificationSettings = $bypassNotificationSettings; @@ -72,6 +73,14 @@ public function __construct( }, $recipients); } + public function setAlertTemplate(AlertTemplate $template): void { + $this->_alertTemplate = $template; + } + + public function setEmailTemplate(EmailTemplate $template): void { + $this->_emailTemplate = $template; + } + public function send(): void { /** @var array $recipient */ foreach ($this->_recipients as $recipient) { diff --git a/modules/Core/classes/Email_Templates/NotificationEmailTemplate.php b/modules/Core/classes/Email_Templates/NotificationEmailTemplate.php new file mode 100644 index 0000000000..399e376a77 --- /dev/null +++ b/modules/Core/classes/Email_Templates/NotificationEmailTemplate.php @@ -0,0 +1,22 @@ +subject = $subject; + + $this->addPlaceholder('[Title]', $subject); + $this->addPlaceholder('[Content]', $content); + $this->addPlaceholder('[Link]', $link); + + parent::__construct(); + } + + public function subject(): string + { + return $this->subject; + } +} \ No newline at end of file diff --git a/modules/Core/classes/Tasks/MassMessage.php b/modules/Core/classes/Tasks/MassMessage.php index 94b54d00fb..22c9fb4230 100644 --- a/modules/Core/classes/Tasks/MassMessage.php +++ b/modules/Core/classes/Tasks/MassMessage.php @@ -42,18 +42,16 @@ public function run(): string { $notification = new Notification( 'mass_message', - new AlertTemplate( - $title, - $content, - ), - new MassMessageEmailTemplate( - $title, - $content, - ), + $title, + $content, array_map(static fn ($r) => $r->id, $recipients->results()), $this->getUserId(), (bool) $this->getData()['bypass_notification_settings'], ); + $notification->setEmailTemplate(new MassMessageEmailTemplate( + $title, + $content, + )); $notification->send(); $this->setOutput(['userIds' => $whereVars, 'start' => $start, 'end' => $end, 'next_status' => $nextStatus]); diff --git a/modules/Forum/pages/forum/view_topic.php b/modules/Forum/pages/forum/view_topic.php index 0bfe740fcd..7f6d5a80cb 100644 --- a/modules/Forum/pages/forum/view_topic.php +++ b/modules/Forum/pages/forum/view_topic.php @@ -358,23 +358,25 @@ $notification = new Notification( 'forum_topic_reply', - 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, - ), + $topic->topic_title, + $original_content, $users_following, $user->data()->id, ); + $notification->setAlertTemplate(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, + )); + $notification->setEmailTemplate(new ForumTopicReplyEmailTemplate( + $user, + $topic->topic_title, + $original_content, + $post_link, + )); $notification->send(); if (count($users_following)) { From eb75dae326ba258df71a8f9c17275c57873e541f Mon Sep 17 00:00:00 2001 From: partydragen Date: Sun, 4 May 2025 12:19:07 +0200 Subject: [PATCH 22/23] Revert "WIP" This reverts commit 7592d7b642f8ac3b00b6c1322f098fa1cd6308d2. --- .../DefaultRevamp/email/notification.html | 16 ----------- modules/Core/classes/Core/Notification.php | 17 +++-------- .../NotificationEmailTemplate.php | 22 --------------- modules/Core/classes/Tasks/MassMessage.php | 14 ++++++---- modules/Forum/pages/forum/view_topic.php | 28 +++++++++---------- 5 files changed, 25 insertions(+), 72 deletions(-) delete mode 100644 custom/templates/DefaultRevamp/email/notification.html delete mode 100644 modules/Core/classes/Email_Templates/NotificationEmailTemplate.php diff --git a/custom/templates/DefaultRevamp/email/notification.html b/custom/templates/DefaultRevamp/email/notification.html deleted file mode 100644 index 794ecd2c83..0000000000 --- a/custom/templates/DefaultRevamp/email/notification.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - [Sitename] • [Title] - - - -
-

[Greeting]

-

[Content]

-
- [Thanks]
[Sitename] -
- - \ No newline at end of file diff --git a/modules/Core/classes/Core/Notification.php b/modules/Core/classes/Core/Notification.php index 46fc9dcbf9..293f706bbe 100644 --- a/modules/Core/classes/Core/Notification.php +++ b/modules/Core/classes/Core/Notification.php @@ -34,20 +34,19 @@ class Notification { */ public function __construct( string $type, - string|LanguageKey $title, - string|LanguageKey $content, + AlertTemplate $alertTemplate, + EmailTemplate $emailTemplate, int|array $recipients, int $authorId, bool $bypassNotificationSettings = false, - ?string $link = null, ) { if (!in_array($type, array_column(self::getTypes(), 'key'))) { throw new NotificationTypeNotFoundException("Type $type not registered"); } $this->_type = $type; - $this->_alertTemplate = new AlertTemplate($title, $content, $link); - $this->_emailTemplate = new NotificationEmailTemplate($title, $content, $link); + $this->_alertTemplate = $alertTemplate; + $this->_emailTemplate = $emailTemplate; $this->_authorId = $authorId; $this->_bypassNotificationSettings = $bypassNotificationSettings; @@ -73,14 +72,6 @@ public function __construct( }, $recipients); } - public function setAlertTemplate(AlertTemplate $template): void { - $this->_alertTemplate = $template; - } - - public function setEmailTemplate(EmailTemplate $template): void { - $this->_emailTemplate = $template; - } - public function send(): void { /** @var array $recipient */ foreach ($this->_recipients as $recipient) { diff --git a/modules/Core/classes/Email_Templates/NotificationEmailTemplate.php b/modules/Core/classes/Email_Templates/NotificationEmailTemplate.php deleted file mode 100644 index 399e376a77..0000000000 --- a/modules/Core/classes/Email_Templates/NotificationEmailTemplate.php +++ /dev/null @@ -1,22 +0,0 @@ -subject = $subject; - - $this->addPlaceholder('[Title]', $subject); - $this->addPlaceholder('[Content]', $content); - $this->addPlaceholder('[Link]', $link); - - parent::__construct(); - } - - public function subject(): string - { - return $this->subject; - } -} \ No newline at end of file diff --git a/modules/Core/classes/Tasks/MassMessage.php b/modules/Core/classes/Tasks/MassMessage.php index 22c9fb4230..94b54d00fb 100644 --- a/modules/Core/classes/Tasks/MassMessage.php +++ b/modules/Core/classes/Tasks/MassMessage.php @@ -42,16 +42,18 @@ public function run(): string { $notification = new Notification( 'mass_message', - $title, - $content, + new AlertTemplate( + $title, + $content, + ), + new MassMessageEmailTemplate( + $title, + $content, + ), array_map(static fn ($r) => $r->id, $recipients->results()), $this->getUserId(), (bool) $this->getData()['bypass_notification_settings'], ); - $notification->setEmailTemplate(new MassMessageEmailTemplate( - $title, - $content, - )); $notification->send(); $this->setOutput(['userIds' => $whereVars, 'start' => $start, 'end' => $end, 'next_status' => $nextStatus]); diff --git a/modules/Forum/pages/forum/view_topic.php b/modules/Forum/pages/forum/view_topic.php index 7f6d5a80cb..0bfe740fcd 100644 --- a/modules/Forum/pages/forum/view_topic.php +++ b/modules/Forum/pages/forum/view_topic.php @@ -358,25 +358,23 @@ $notification = new Notification( 'forum_topic_reply', - $topic->topic_title, - $original_content, + 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, ); - $notification->setAlertTemplate(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, - )); - $notification->setEmailTemplate(new ForumTopicReplyEmailTemplate( - $user, - $topic->topic_title, - $original_content, - $post_link, - )); $notification->send(); if (count($users_following)) { From 42c67678eba946bedac6fc56264b78e183a2384c Mon Sep 17 00:00:00 2001 From: tadhgboyle Date: Sun, 4 May 2025 13:19:40 -0700 Subject: [PATCH 23/23] todo --- core/classes/Alerts/Alert.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/classes/Alerts/Alert.php b/core/classes/Alerts/Alert.php index 6c068d5cf5..d36d972742 100644 --- a/core/classes/Alerts/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? } /**
{$TYPE}{$TYPE_VALUE}{$MAILER}{$MAILER_VALUE}
{$DATE}