From 02d7b5d6fd4f5d4d4352007c6ff3033546f95200 Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 Apr 2026 19:58:41 +0200 Subject: [PATCH 1/2] Fix EmailTask undefined-method errors for serialized Message settings Keys coming from Message::__serialize()/jsonSerialize (htmlMessage, textMessage, appCharset) don't map to set() methods on Mailer or Message, so passing them via queued JSON payloads failed with 'Call to undefined method'. Route these keys to their real setters explicitly before the generic loop: - htmlMessage -> Message::setBodyHtml() - textMessage -> Message::setBodyText() - appCharset -> Message::setCharset() when no explicit charset is given - readReceipt -> Mailer::setReadReceipt() (address-shape, named-param safe) --- src/Queue/Task/EmailTask.php | 24 ++++++++++++- tests/TestCase/Queue/Task/EmailTaskTest.php | 37 +++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/Queue/Task/EmailTask.php b/src/Queue/Task/EmailTask.php index 900b0c95..c4debc37 100644 --- a/src/Queue/Task/EmailTask.php +++ b/src/Queue/Task/EmailTask.php @@ -164,7 +164,7 @@ public function run(array $data, int $jobId): void { $settings = $data['settings'] + $this->defaults; - foreach (['to', 'from', 'cc', 'bcc', 'replyTo', 'sender', 'returnPath'] as $addressMethod) { + foreach (['to', 'from', 'cc', 'bcc', 'replyTo', 'sender', 'returnPath', 'readReceipt'] as $addressMethod) { if (!array_key_exists($addressMethod, $settings)) { continue; } @@ -174,6 +174,28 @@ public function run(array $data, int $jobId): void { unset($settings[$addressMethod]); } + // Message body keys from a serialized Message payload use different names than + // their setter methods. Route them explicitly so the generic loop does not try + // to call nonexistent `setHtmlMessage`/`setTextMessage` on the Mailer. + if (array_key_exists('htmlMessage', $settings)) { + $this->mailer->getMessage()->setBodyHtml((string)$settings['htmlMessage']); + unset($settings['htmlMessage']); + } + if (array_key_exists('textMessage', $settings)) { + $this->mailer->getMessage()->setBodyText((string)$settings['textMessage']); + unset($settings['textMessage']); + } + + // `appCharset` has no setter on Mailer or Message. Fall back to the Message + // charset when a dedicated `charset` value was not also provided. + if (array_key_exists('appCharset', $settings)) { + $appCharset = (string)$settings['appCharset']; + unset($settings['appCharset']); + if (!array_key_exists('charset', $settings)) { + $this->mailer->getMessage()->setCharset($appCharset); + } + } + foreach ($settings as $method => $setting) { $setter = 'set' . ucfirst($method); if (in_array($method, ['theme', 'template', 'layout'], true)) { diff --git a/tests/TestCase/Queue/Task/EmailTaskTest.php b/tests/TestCase/Queue/Task/EmailTaskTest.php index 4eb79b16..a2e78f6f 100644 --- a/tests/TestCase/Queue/Task/EmailTaskTest.php +++ b/tests/TestCase/Queue/Task/EmailTaskTest.php @@ -253,6 +253,43 @@ public function testRunArrayAssociativeAddressMap() { $this->assertSame(['reply@test.de' => 'Reply Name'], $mailer->getReplyTo()); } + /** + * Settings keys coming from a JSON-round-tripped `Message::__serialize()` payload + * (e.g. `htmlMessage`, `textMessage`, `appCharset`) must not be routed to + * nonexistent `set()` methods on the Mailer. + * + * @return void + */ + public function testRunArrayMessageSerializableProperties() { + $settings = [ + 'from' => 'sender@test.de', + 'to' => 'recipient@test.de', + 'subject' => 'Message Subject', + 'domain' => 'example.com', + 'charset' => 'utf-8', + 'headerCharset' => 'utf-8', + 'appCharset' => 'UTF-8', + 'emailFormat' => 'html', + 'messageId' => true, + 'htmlMessage' => '

Hello

', + 'textMessage' => 'Hello', + ]; + + $data = [ + 'settings' => $settings, + ]; + $this->Task->run($data, 0); + + $this->assertInstanceOf(Mailer::class, $this->Task->mailer); + + $message = $this->Task->mailer->getMessage(); + $this->assertSame('Message Subject', $message->getSubject()); + $this->assertSame('example.com', $message->getDomain()); + $this->assertSame('html', $message->getEmailFormat()); + $this->assertSame('utf-8', $message->getCharset()); + $this->assertSame('utf-8', $message->getHeaderCharset()); + } + /** * @return void */ From 5e29c240b992c0d2cd84fe42025d92b71a87b3fc Mon Sep 17 00:00:00 2001 From: mscherer Date: Fri, 17 Apr 2026 20:00:58 +0200 Subject: [PATCH 2/2] Route headers setting through explicit setHeaders call An associative `headers` map inside the settings array hits the same PHP 8 named-parameter problem as the address maps: string keys would otherwise be interpreted as named arguments when the generic loop spreads them into `setHeaders()`. Forward the map as a single positional argument instead. --- src/Queue/Task/EmailTask.php | 7 +++++ tests/TestCase/Queue/Task/EmailTaskTest.php | 29 +++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/Queue/Task/EmailTask.php b/src/Queue/Task/EmailTask.php index c4debc37..2e4f10d6 100644 --- a/src/Queue/Task/EmailTask.php +++ b/src/Queue/Task/EmailTask.php @@ -186,6 +186,13 @@ public function run(array $data, int $jobId): void { unset($settings['textMessage']); } + // `headers` must be passed as a single positional argument — the map's string + // keys would otherwise be interpreted as named parameters under PHP 8. + if (array_key_exists('headers', $settings)) { + $this->mailer->getMessage()->setHeaders((array)$settings['headers']); + unset($settings['headers']); + } + // `appCharset` has no setter on Mailer or Message. Fall back to the Message // charset when a dedicated `charset` value was not also provided. if (array_key_exists('appCharset', $settings)) { diff --git a/tests/TestCase/Queue/Task/EmailTaskTest.php b/tests/TestCase/Queue/Task/EmailTaskTest.php index a2e78f6f..e6523879 100644 --- a/tests/TestCase/Queue/Task/EmailTaskTest.php +++ b/tests/TestCase/Queue/Task/EmailTaskTest.php @@ -290,6 +290,35 @@ public function testRunArrayMessageSerializableProperties() { $this->assertSame('utf-8', $message->getHeaderCharset()); } + /** + * An associative `headers` map inside `settings` must not be expanded into named + * parameters when calling `Message::setHeaders()`. + * + * @return void + */ + public function testRunArrayHeadersInSettings() { + $settings = [ + 'from' => 'sender@test.de', + 'to' => 'recipient@test.de', + 'headers' => [ + 'X-Custom' => 'queued', + 'X-Other' => 'value', + ], + ]; + + $data = [ + 'settings' => $settings, + 'content' => 'Foo Bar', + ]; + $this->Task->run($data, 0); + + $this->assertInstanceOf(Mailer::class, $this->Task->mailer); + + $headers = $this->Task->mailer->getMessage()->getHeaders(['_headers']); + $this->assertSame('queued', $headers['X-Custom']); + $this->assertSame('value', $headers['X-Other']); + } + /** * @return void */