From 1559f250844709a8d0265802f078a572485eec23 Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Fri, 3 Apr 2026 09:38:48 -0400 Subject: [PATCH 1/6] enable exceptions --- resources/lib/UnityMailer.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/resources/lib/UnityMailer.php b/resources/lib/UnityMailer.php index 73f59e32e..19fda6718 100644 --- a/resources/lib/UnityMailer.php +++ b/resources/lib/UnityMailer.php @@ -6,8 +6,6 @@ use Exception; use Twig\TwigFunction; -class UnityMailerException extends Exception {} - /** * This is a class that uses PHPmailer to send emails based on templates */ @@ -27,7 +25,7 @@ class UnityMailer extends PHPMailer public function __construct() { - parent::__construct(); + parent::__construct(exceptions: true); $this->isSMTP(); $this->MSG_SENDER_EMAIL = CONFIG["mail"]["sender"]; @@ -122,10 +120,7 @@ public function sendMail(string|array $recipients, string $template, ?array $dat } } - $output = parent::send(); - if ($output === false) { - throw new UnityMailerException($this->ErrorInfo); - } + parent::send(); $this->clearAllRecipients(); } } From 5455b77611fed8f0c05d05ea9cc29cc872fd0dfe Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Fri, 3 Apr 2026 09:47:52 -0400 Subject: [PATCH 2/6] reset state after message send --- resources/lib/UnityMailer.php | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/resources/lib/UnityMailer.php b/resources/lib/UnityMailer.php index 19fda6718..0f1e94131 100644 --- a/resources/lib/UnityMailer.php +++ b/resources/lib/UnityMailer.php @@ -93,6 +93,26 @@ public function __construct() $this->twig->addGlobal("CONFIG", CONFIG); } + private function resetMessageState(): void + { + $this->clearAllRecipients(); + $this->clearAttachments(); + $this->clearCustomHeaders(); + $this->CharSet = self::CHARSET_ISO88591; + $this->ContentType = self::CONTENT_TYPE_PLAINTEXT; + $this->Encoding = self::ENCODING_8BIT; + $this->From = ""; + $this->FromName = ""; + $this->Sender = ""; + $this->Subject = ""; + $this->Body = ""; + $this->AltBody = ""; + $this->Ical = ""; + $this->ConfirmReadingTo = ""; + $this->MessageID = ""; + $this->MessageDate = ""; + } + /** * @param string|string[] $recipients * @param ?mixed[] $data @@ -119,8 +139,10 @@ public function sendMail(string|array $recipients, string $template, ?array $dat $this->addAddress($recipients); } } - - parent::send(); - $this->clearAllRecipients(); + try { + parent::send(); + } finally { + $this->resetMessageState(); + } } } From ade52d54a11485812d5650e8fb85beac43f4dd58 Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Fri, 3 Apr 2026 11:26:50 -0400 Subject: [PATCH 3/6] construct a new PHPMailer object for each message --- resources/lib/UnityMailer.php | 101 ++++++++++++++-------------------- 1 file changed, 41 insertions(+), 60 deletions(-) diff --git a/resources/lib/UnityMailer.php b/resources/lib/UnityMailer.php index 0f1e94131..6758fd770 100644 --- a/resources/lib/UnityMailer.php +++ b/resources/lib/UnityMailer.php @@ -9,7 +9,7 @@ /** * This is a class that uses PHPmailer to send emails based on templates */ -class UnityMailer extends PHPMailer +class UnityMailer { private string $MSG_SENDER_EMAIL; private string $MSG_SENDER_NAME; @@ -25,9 +25,6 @@ class UnityMailer extends PHPMailer public function __construct() { - parent::__construct(exceptions: true); - $this->isSMTP(); - $this->MSG_SENDER_EMAIL = CONFIG["mail"]["sender"]; $this->MSG_SENDER_NAME = CONFIG["mail"]["sender_name"]; $this->MSG_SUPPORT_EMAIL = CONFIG["mail"]["support"]; @@ -36,15 +33,37 @@ public function __construct() $this->MSG_ADMIN_NAME = CONFIG["mail"]["admin_name"]; $this->MSG_PI_APPROVAL_EMAIL = CONFIG["mail"]["pi_approve"]; $this->MSG_PI_APPROVAL_NAME = CONFIG["mail"]["pi_approve_name"]; + $this->loader = new \Twig\Loader\FilesystemLoader([ + __DIR__ . "/../../deployment/mail_overrides", + __DIR__ . "/../mail", + ]); + $this->twig = new \Twig\Environment($this->loader, ["strict_variables" => true]); + $functions = [ + new TwigFunction("setSubject", fn($x) => ($this->Subject = $x)), + new TwigFunction("getRelativeHyperlink", getRelativeHyperlink(...)), + new TwigFunction("formatHyperlink", formatHyperlink(...)), + new TwigFunction("throw", fn($x) => throw new Exception($x)), + ]; + foreach ($functions as $function) { + $this->twig->addFunction($function); + } + $this->twig->addGlobal("CONFIG", CONFIG); + } + + public function constructPHPMailer(): PHPMailer + { + $mailer = new PHPMailer(exceptions: true); + $mailer->isSMTP(); + if (empty(CONFIG["smtp"]["host"])) { throw new Exception("SMTP server hostname not set"); } - $this->Host = CONFIG["smtp"]["host"]; + $mailer->Host = CONFIG["smtp"]["host"]; if (empty(CONFIG["smtp"]["port"])) { throw new Exception("SMTP server port not set"); } - $this->Port = CONFIG["smtp"]["port"]; + $mailer->Port = CONFIG["smtp"]["port"]; $security = CONFIG["smtp"]["security"]; $security_conf_valid = empty($security) || $security == "tls" || $security == "ssl"; @@ -53,21 +72,21 @@ public function __construct() "SMTP security is not set correctly, leave empty, use 'tls', or 'ssl'", ); } - $this->SMTPSecure = $security; + $mailer->SMTPSecure = $security; if (!empty(CONFIG["smtp"]["user"])) { - $this->SMTPAuth = true; - $this->Username = CONFIG["smtp"]["user"]; + $mailer->SMTPAuth = true; + $mailer->Username = CONFIG["smtp"]["user"]; } else { - $this->SMTPAuth = false; + $mailer->SMTPAuth = false; } if (!empty(CONFIG["smtp"]["pass"])) { - $this->Password = CONFIG["smtp"]["pass"]; + $mailer->Password = CONFIG["smtp"]["pass"]; } if (CONFIG["smtp"]["ssl_verify"] == "false") { - $this->SMTPOptions = [ + $mailer->SMTPOptions = [ "ssl" => [ "verify_peer" => false, "verify_peer_name" => false, @@ -75,42 +94,7 @@ public function __construct() ], ]; } - - $this->loader = new \Twig\Loader\FilesystemLoader([ - __DIR__ . "/../../deployment/mail_overrides", - __DIR__ . "/../mail", - ]); - $this->twig = new \Twig\Environment($this->loader, ["strict_variables" => true]); - $functions = [ - new TwigFunction("setSubject", fn($x) => ($this->Subject = $x)), - new TwigFunction("getRelativeHyperlink", getRelativeHyperlink(...)), - new TwigFunction("formatHyperlink", formatHyperlink(...)), - new TwigFunction("throw", fn($x) => throw new Exception($x)), - ]; - foreach ($functions as $function) { - $this->twig->addFunction($function); - } - $this->twig->addGlobal("CONFIG", CONFIG); - } - - private function resetMessageState(): void - { - $this->clearAllRecipients(); - $this->clearAttachments(); - $this->clearCustomHeaders(); - $this->CharSet = self::CHARSET_ISO88591; - $this->ContentType = self::CONTENT_TYPE_PLAINTEXT; - $this->Encoding = self::ENCODING_8BIT; - $this->From = ""; - $this->FromName = ""; - $this->Sender = ""; - $this->Subject = ""; - $this->Body = ""; - $this->AltBody = ""; - $this->Ical = ""; - $this->ConfirmReadingTo = ""; - $this->MessageID = ""; - $this->MessageDate = ""; + return $mailer; } /** @@ -120,29 +104,26 @@ private function resetMessageState(): void public function sendMail(string|array $recipients, string $template, ?array $data = null): void { $data ??= []; - $this->setFrom($this->MSG_SENDER_EMAIL, $this->MSG_SENDER_NAME); - $this->addReplyTo($this->MSG_SUPPORT_EMAIL, $this->MSG_SUPPORT_NAME); + $mailer = $this->constructPHPMailer(); + $mailer->setFrom($this->MSG_SENDER_EMAIL, $this->MSG_SENDER_NAME); + $mailer->addReplyTo($this->MSG_SUPPORT_EMAIL, $this->MSG_SUPPORT_NAME); $mes_html = $this->twig->render("$template.html.twig", $data); - $this->msgHTML($mes_html); + $mailer->msgHTML($mes_html); if ($recipients == "admin") { - $this->addBCC($this->MSG_ADMIN_EMAIL, $this->MSG_ADMIN_NAME); + $mailer->addBCC($this->MSG_ADMIN_EMAIL, $this->MSG_ADMIN_NAME); } elseif ($recipients == "pi_approve") { - $this->addBCC($this->MSG_PI_APPROVAL_EMAIL, $this->MSG_PI_APPROVAL_NAME); + $mailer->addBCC($this->MSG_PI_APPROVAL_EMAIL, $this->MSG_PI_APPROVAL_NAME); } else { if (is_array($recipients)) { foreach ($recipients as $addr) { - $this->addBCC($addr); + $mailer->addBCC($addr); } } else { - $this->addAddress($recipients); + $mailer->addAddress($recipients); } } - try { - parent::send(); - } finally { - $this->resetMessageState(); - } + $mailer->send(); } } From d2659aeb43d6c19aa8cf8475045609bae2eb5fb4 Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Fri, 3 Apr 2026 13:07:02 -0400 Subject: [PATCH 4/6] create a new twig environment each message --- resources/lib/UnityMailer.php | 43 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/resources/lib/UnityMailer.php b/resources/lib/UnityMailer.php index 6758fd770..f0b72bf49 100644 --- a/resources/lib/UnityMailer.php +++ b/resources/lib/UnityMailer.php @@ -20,9 +20,6 @@ class UnityMailer private string $MSG_PI_APPROVAL_EMAIL; private string $MSG_PI_APPROVAL_NAME; - private \Twig\Loader\FilesystemLoader $loader; - private \Twig\Environment $twig; - public function __construct() { $this->MSG_SENDER_EMAIL = CONFIG["mail"]["sender"]; @@ -33,21 +30,6 @@ public function __construct() $this->MSG_ADMIN_NAME = CONFIG["mail"]["admin_name"]; $this->MSG_PI_APPROVAL_EMAIL = CONFIG["mail"]["pi_approve"]; $this->MSG_PI_APPROVAL_NAME = CONFIG["mail"]["pi_approve_name"]; - $this->loader = new \Twig\Loader\FilesystemLoader([ - __DIR__ . "/../../deployment/mail_overrides", - __DIR__ . "/../mail", - ]); - $this->twig = new \Twig\Environment($this->loader, ["strict_variables" => true]); - $functions = [ - new TwigFunction("setSubject", fn($x) => ($this->Subject = $x)), - new TwigFunction("getRelativeHyperlink", getRelativeHyperlink(...)), - new TwigFunction("formatHyperlink", formatHyperlink(...)), - new TwigFunction("throw", fn($x) => throw new Exception($x)), - ]; - foreach ($functions as $function) { - $this->twig->addFunction($function); - } - $this->twig->addGlobal("CONFIG", CONFIG); } public function constructPHPMailer(): PHPMailer @@ -97,6 +79,25 @@ public function constructPHPMailer(): PHPMailer return $mailer; } + public function constructTwigEnvironment(): \Twig\Environment + { + $loader = new \Twig\Loader\FilesystemLoader([ + __DIR__ . "/../../deployment/mail_overrides", + __DIR__ . "/../mail", + ]); + $twig = new \Twig\Environment($loader, ["strict_variables" => true]); + $functions = [ + new TwigFunction("getRelativeHyperlink", getRelativeHyperlink(...)), + new TwigFunction("formatHyperlink", formatHyperlink(...)), + new TwigFunction("throw", fn($x) => throw new Exception($x)), + ]; + foreach ($functions as $function) { + $twig->addFunction($function); + } + $twig->addGlobal("CONFIG", CONFIG); + return $twig; + } + /** * @param string|string[] $recipients * @param ?mixed[] $data @@ -105,10 +106,14 @@ public function sendMail(string|array $recipients, string $template, ?array $dat { $data ??= []; $mailer = $this->constructPHPMailer(); + + $twig = $this->constructTwigEnvironment(); + $twig->addFunction(new TwigFunction("setSubject", fn($x) => ($mailer->Subject = $x))); + $mailer->setFrom($this->MSG_SENDER_EMAIL, $this->MSG_SENDER_NAME); $mailer->addReplyTo($this->MSG_SUPPORT_EMAIL, $this->MSG_SUPPORT_NAME); - $mes_html = $this->twig->render("$template.html.twig", $data); + $mes_html = $twig->render("$template.html.twig", $data); $mailer->msgHTML($mes_html); if ($recipients == "admin") { From 17040a5e923430a42c31f84ebfdc91023d4262dc Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Fri, 3 Apr 2026 13:11:35 -0400 Subject: [PATCH 5/6] reorder for diff --- resources/lib/UnityMailer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/UnityMailer.php b/resources/lib/UnityMailer.php index f0b72bf49..e1fcf5924 100644 --- a/resources/lib/UnityMailer.php +++ b/resources/lib/UnityMailer.php @@ -104,12 +104,12 @@ public function constructTwigEnvironment(): \Twig\Environment */ public function sendMail(string|array $recipients, string $template, ?array $data = null): void { - $data ??= []; $mailer = $this->constructPHPMailer(); $twig = $this->constructTwigEnvironment(); $twig->addFunction(new TwigFunction("setSubject", fn($x) => ($mailer->Subject = $x))); + $data ??= []; $mailer->setFrom($this->MSG_SENDER_EMAIL, $this->MSG_SENDER_NAME); $mailer->addReplyTo($this->MSG_SUPPORT_EMAIL, $this->MSG_SUPPORT_NAME); From 9783ac35b80b8a37f8f9fe93481d60111c3567fd Mon Sep 17 00:00:00 2001 From: Simon Leary Date: Fri, 3 Apr 2026 13:15:45 -0400 Subject: [PATCH 6/6] private --- resources/lib/UnityMailer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/UnityMailer.php b/resources/lib/UnityMailer.php index e1fcf5924..106cb722b 100644 --- a/resources/lib/UnityMailer.php +++ b/resources/lib/UnityMailer.php @@ -32,7 +32,7 @@ public function __construct() $this->MSG_PI_APPROVAL_NAME = CONFIG["mail"]["pi_approve_name"]; } - public function constructPHPMailer(): PHPMailer + private function constructPHPMailer(): PHPMailer { $mailer = new PHPMailer(exceptions: true); $mailer->isSMTP(); @@ -79,7 +79,7 @@ public function constructPHPMailer(): PHPMailer return $mailer; } - public function constructTwigEnvironment(): \Twig\Environment + private function constructTwigEnvironment(): \Twig\Environment { $loader = new \Twig\Loader\FilesystemLoader([ __DIR__ . "/../../deployment/mail_overrides",