MailerTest.php (50,496 bytes)
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @since 3.1.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Test\TestCase\Mailer;
use BadMethodCallException;
use Cake\Core\Configure;
use Cake\Log\Log;
use Cake\Mailer\AbstractTransport;
use Cake\Mailer\Exception\MissingActionException;
use Cake\Mailer\Mailer;
use Cake\Mailer\Message;
use Cake\Mailer\Transport\DebugTransport;
use Cake\Mailer\TransportFactory;
use Cake\TestSuite\TestCase;
use Cake\View\Exception\MissingTemplateException;
use DateTime;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use TestApp\Mailer\TestMailer;
use function Cake\Core\env;
class MailerTest extends TestCase
{
/**
* @var array
*/
protected $transports = [];
/**
* @var \Cake\Mailer\Mailer
*/
protected $mailer;
/**
* setUp
*/
public function setUp(): void
{
parent::setUp();
$this->transports = [
'debug' => [
'className' => 'Debug',
],
'badClassName' => [
'className' => 'TestFalse',
],
];
TransportFactory::setConfig($this->transports);
$this->mailer = new TestMailer();
}
/**
* tearDown method
*/
public function tearDown(): void
{
parent::tearDown();
TransportFactory::drop('debug');
TransportFactory::drop('badClassName');
Mailer::drop('test');
Mailer::drop('default');
Log::drop('email');
}
/**
* testTransport method
*/
public function testTransport(): void
{
$result = $this->mailer->setTransport('debug');
$this->assertSame($this->mailer, $result);
$result = $this->mailer->getTransport();
$this->assertInstanceOf(DebugTransport::class, $result);
$instance = new class extends DebugTransport {
};
$this->mailer->setTransport($instance);
$this->assertSame($instance, $this->mailer->getTransport());
}
/**
* Test that using unknown transports fails.
*/
public function testTransportInvalid(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The `Invalid` transport configuration does not exist');
$this->mailer->setTransport('Invalid');
}
/**
* testMessage function
*
* @deprecated
*/
#[WithoutErrorHandler]
public function testSetMessage(): void
{
$message = $this->mailer->getMessage();
$this->assertInstanceOf(Message::class, $message);
$newMessage = new Message();
$this->deprecated(function () use ($newMessage): void {
$this->mailer->setMessage($newMessage);
});
$this->assertSame($newMessage, $this->mailer->getMessage());
$this->assertNotSame($message, $newMessage);
}
/**
* Test reading/writing configuration profiles.
*/
public function testConfig(): void
{
$settings = [
'to' => 'mark@example.com',
'from' => 'noreply@example.com',
];
Mailer::setConfig('test', $settings);
$this->assertEquals($settings, Mailer::getConfig('test'), 'Should be the same.');
$mailer = new Mailer('test');
$this->assertContains($settings['to'], $mailer->getTo());
}
/**
* Test that exceptions are raised on duplicate config set.
*/
public function testConfigErrorOnDuplicate(): void
{
$this->expectException(BadMethodCallException::class);
$settings = [
'to' => 'mark@example.com',
'from' => 'noreply@example.com',
];
Mailer::setConfig('test', $settings);
Mailer::setConfig('test', $settings);
}
/**
* testConstructWithConfigArray method
*/
public function testConstructWithConfigArray(): void
{
$configs = [
'from' => ['some@example.com' => 'My website'],
'to' => 'test@example.com',
'subject' => 'Test mail subject',
'transport' => 'debug',
];
$this->mailer = new Mailer($configs);
$result = $this->mailer->getTo();
$this->assertEquals([$configs['to'] => $configs['to']], $result);
$result = $this->mailer->getFrom();
$this->assertEquals($configs['from'], $result);
$result = $this->mailer->getSubject();
$this->assertSame($configs['subject'], $result);
$result = $this->mailer->getTransport();
$this->assertInstanceOf(DebugTransport::class, $result);
$result = $this->mailer->deliver('This is the message');
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
}
/**
* testConfigArrayWithLayoutWithoutTemplate method
*/
public function testConfigArrayWithLayoutWithoutTemplate(): void
{
$configs = [
'from' => ['some@example.com' => 'My website'],
'to' => 'test@example.com',
'subject' => 'Test mail subject',
'transport' => 'debug',
'layout' => 'custom',
];
$this->mailer = new Mailer($configs);
$template = $this->mailer->viewBuilder()->getTemplate();
$layout = $this->mailer->viewBuilder()->getLayout();
$this->assertNull($template);
$this->assertSame($configs['layout'], $layout);
}
/**
* testConstructWithConfigString method
*/
public function testConstructWithConfigString(): void
{
$configs = [
'from' => ['some@example.com' => 'My website'],
'to' => 'test@example.com',
'subject' => 'Test mail subject',
'transport' => 'debug',
];
Mailer::setConfig('test', $configs);
$this->mailer = new Mailer('test');
$result = $this->mailer->getTo();
$this->assertEquals([$configs['to'] => $configs['to']], $result);
$result = $this->mailer->getFrom();
$this->assertEquals($configs['from'], $result);
$result = $this->mailer->getSubject();
$this->assertSame($configs['subject'], $result);
$result = $this->mailer->getTransport();
$this->assertInstanceOf(DebugTransport::class, $result);
$result = $this->mailer->deliver('This is the message');
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
}
/**
* test profile method
*/
public function testSetProfile(): void
{
$config = ['to' => 'foo@bar.com'];
$this->mailer->setProfile($config);
$this->assertSame(['foo@bar.com' => 'foo@bar.com'], $this->mailer->getTo());
}
/**
* test that default profile is used by constructor if available.
*/
public function testDefaultProfile(): void
{
$config = ['to' => 'foo@bar.com', 'from' => 'from@bar.com'];
Configure::write('Mailer.default', $config);
Mailer::setConfig(Configure::consume('Mailer'));
$mailer = new Mailer();
$this->assertSame(['foo@bar.com' => 'foo@bar.com'], $mailer->getTo());
$this->assertSame(['from@bar.com' => 'from@bar.com'], $mailer->getFrom());
Configure::delete('Mailer');
Mailer::drop('default');
}
/**
* Test that using an invalid profile fails.
*/
public function testProfileInvalid(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Unknown email configuration `derp`.');
$mailer = new Mailer();
$mailer->setProfile('derp');
}
/**
* testConfigString method
*/
public function testUseConfigString(): void
{
$config = [
'from' => ['some@example.com' => 'My website'],
'to' => ['test@example.com' => 'Testname'],
'subject' => 'Test mail subject',
'transport' => 'debug',
'theme' => 'TestTheme',
'helpers' => ['Html', 'Form'],
'autoLayout' => false,
];
Mailer::setConfig('test', $config);
$this->mailer->setProfile('test');
$result = $this->mailer->getTo();
$this->assertEquals($config['to'], $result);
$result = $this->mailer->getFrom();
$this->assertEquals($config['from'], $result);
$result = $this->mailer->getSubject();
$this->assertSame($config['subject'], $result);
$result = $this->mailer->viewBuilder()->getTheme();
$this->assertSame($config['theme'], $result);
$result = $this->mailer->getTransport();
$this->assertInstanceOf(DebugTransport::class, $result);
$result = $this->mailer->viewBuilder()->getHelpers();
$this->assertEquals(['Html' => [], 'Form' => []], $result);
$this->assertFalse($this->mailer->viewBuilder()->isAutoLayoutEnabled());
Mailer::drop('test');
}
/**
* CakeEmailTest::testDefaultTransport()
*/
public function testDefaultTransport(): void
{
TransportFactory::drop('default');
$instance = new class extends AbstractTransport {
public function send(Message $message): array
{
return [];
}
};
$config = ['from' => 'tester@example.org', 'transport' => 'default'];
Mailer::setConfig('default', $config);
TransportFactory::setConfig('default', $instance);
$em = new Mailer('default');
$this->assertSame($instance, $em->getTransport());
TransportFactory::drop('default');
}
public function testProxies(): void
{
$result = (new Mailer())->setHeaders(['X-Something' => 'nice']);
$this->assertInstanceOf(Mailer::class, $result);
$header = $result->getMessage()->getHeaders();
$this->assertSame('nice', $header['X-Something']);
$result = (new Mailer())->setAttachments([
['file' => __FILE__, 'mimetype' => 'text/plain'],
]);
$this->assertInstanceOf(Mailer::class, $result);
$this->assertSame(
['MailerTest.php' => ['file' => __FILE__, 'mimetype' => 'text/plain']],
$result->getMessage()->getAttachments()
);
}
/**
* Test that get/set methods can be proxied.
*/
public function testGetSetProxies(): void
{
$mailer = new Mailer();
$result = $mailer
->setTo('test@example.com')
->setCc('cc@example.com');
$this->assertSame($result, $mailer);
$this->assertSame(['test@example.com' => 'test@example.com'], $result->getTo());
$this->assertSame(['cc@example.com' => 'cc@example.com'], $result->getCc());
}
public function testSet(): void
{
$result = (new Mailer())->setViewVars('key', 'value');
$this->assertInstanceOf(Mailer::class, $result);
$this->assertSame(['key' => 'value'], $result->getRenderer()->viewBuilder()->getVars());
}
/**
* testRenderWithLayoutAndAttachment method
*/
public function testRenderWithLayoutAndAttachment(): void
{
$this->mailer->setEmailFormat('html');
$this->mailer->viewBuilder()->setTemplate('html');
$this->mailer->setAttachments([__FILE__]);
$this->mailer->render();
$result = $this->mailer->getBody();
$this->assertNotEmpty($result);
$result = $this->mailer->getBoundary();
$this->assertMatchesRegularExpression('/^[0-9a-f]{32}$/', $result);
}
/**
* Calling send() with no parameters should not overwrite the view variables.
*/
public function testSendWithNoContentDoesNotOverwriteViewVar(): void
{
$this->mailer->reset();
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo('you@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setEmailFormat('text');
$this->mailer->viewBuilder()
->setTemplate('default')
->setVars([
'content' => 'A message to you',
]);
$result = $this->mailer->send();
$this->assertStringContainsString('A message to you', $result['message']);
}
/**
* testSendWithContent method
*/
public function testSendWithContent(): void
{
$this->mailer->reset();
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$result = $this->mailer->deliver("Here is my body, with multi lines.\nThis is the second line.\r\n\r\nAnd the last.");
$expected = ['headers', 'message'];
$this->assertEquals($expected, array_keys($result));
$expected = "Here is my body, with multi lines.\r\nThis is the second line.\r\n\r\nAnd the last.\r\n\r\n";
$this->assertSame($expected, $result['message']);
$this->assertStringContainsString('Date: ', $result['headers']);
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
$result = $this->mailer->deliver('Other body');
$expected = "Other body\r\n\r\n";
$this->assertSame($expected, $result['message']);
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
}
/**
* test send without a transport method
*/
public function testSendWithoutTransport(): void
{
$this->expectException(BadMethodCallException::class);
$this->expectExceptionMessage(
'Transport was not defined. You must set on using setTransport() or set `transport` option in your mailer profile.'
);
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->send();
}
/**
* Test send() with no template.
*/
public function testSendNoTemplateWithAttachments(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setEmailFormat('text');
$this->mailer->setAttachments([__FILE__]);
$result = $this->mailer->deliver('Hello');
$boundary = $this->mailer->boundary;
$this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
$expected = "--{$boundary}\r\n" .
"Content-Type: text/plain; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'Hello' .
"\r\n" .
"\r\n" .
"\r\n" .
"--{$boundary}\r\n" .
"Content-Disposition: attachment; filename=\"MailerTest.php\"\r\n" .
"Content-Type: text/x-php\r\n" .
"Content-Transfer-Encoding: base64\r\n" .
"\r\n";
$this->assertStringContainsString($expected, $result['message']);
}
/**
* Test send() with no template and data string attachment
*/
public function testSendNoTemplateWithDataStringAttachment(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setEmailFormat('text');
$data = file_get_contents(TEST_APP . 'webroot/img/cake.power.gif');
$this->mailer->setAttachments(['cake.icon.gif' => [
'data' => $data,
'mimetype' => 'image/gif',
]]);
$result = $this->mailer->deliver('Hello');
$boundary = $this->mailer->boundary;
$this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
$expected = "--{$boundary}\r\n" .
"Content-Type: text/plain; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'Hello' .
"\r\n" .
"\r\n" .
"\r\n" .
"--{$boundary}\r\n" .
"Content-Disposition: attachment; filename=\"cake.icon.gif\"\r\n" .
"Content-Type: image/gif\r\n" .
"Content-Transfer-Encoding: base64\r\n\r\n";
$expected .= chunk_split(base64_encode($data), 76, "\r\n");
$this->assertStringContainsString($expected, $result['message']);
}
/**
* Test send() with no template as both
*/
public function testSendNoTemplateWithAttachmentsAsBoth(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setEmailFormat('both');
$this->mailer->setAttachments([CORE_PATH . 'VERSION.txt']);
$result = $this->mailer->deliver('Hello');
$boundary = $this->mailer->boundary;
$this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
$expected = "--{$boundary}\r\n" .
"Content-Type: multipart/alternative; boundary=\"alt-{$boundary}\"\r\n" .
"\r\n" .
"--alt-{$boundary}\r\n" .
"Content-Type: text/plain; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'Hello' .
"\r\n" .
"\r\n" .
"\r\n" .
"--alt-{$boundary}\r\n" .
"Content-Type: text/html; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'Hello' .
"\r\n" .
"\r\n" .
"\r\n" .
"--alt-{$boundary}--\r\n" .
"\r\n" .
"--{$boundary}\r\n" .
"Content-Disposition: attachment; filename=\"VERSION.txt\"\r\n" .
"Content-Type: text/plain\r\n" .
"Content-Transfer-Encoding: base64\r\n" .
"\r\n";
$this->assertStringContainsString($expected, $result['message']);
}
/**
* Test setting inline attachments and messages.
*/
public function testSendWithInlineAttachments(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setEmailFormat('both');
$this->mailer->setAttachments([
'cake.png' => [
'file' => CORE_PATH . 'VERSION.txt',
'contentId' => 'abc123',
],
]);
$result = $this->mailer->deliver('Hello');
$boundary = $this->mailer->boundary;
$this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
$expected = "--{$boundary}\r\n" .
"Content-Type: multipart/related; boundary=\"rel-{$boundary}\"\r\n" .
"\r\n" .
"--rel-{$boundary}\r\n" .
"Content-Type: multipart/alternative; boundary=\"alt-{$boundary}\"\r\n" .
"\r\n" .
"--alt-{$boundary}\r\n" .
"Content-Type: text/plain; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'Hello' .
"\r\n" .
"\r\n" .
"\r\n" .
"--alt-{$boundary}\r\n" .
"Content-Type: text/html; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'Hello' .
"\r\n" .
"\r\n" .
"\r\n" .
"--alt-{$boundary}--\r\n" .
"\r\n" .
"--rel-{$boundary}\r\n" .
"Content-Disposition: inline; filename=\"cake.png\"\r\n" .
"Content-Type: text/plain\r\n" .
"Content-Transfer-Encoding: base64\r\n" .
"Content-ID: <abc123>\r\n" .
"\r\n";
$this->assertStringContainsString($expected, $result['message']);
$this->assertStringContainsString('--rel-' . $boundary . '--', $result['message']);
$this->assertStringContainsString('--' . $boundary . '--', $result['message']);
}
/**
* Test setting inline attachments and HTML only messages.
*/
public function testSendWithInlineAttachmentsHtmlOnly(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setEmailFormat('html');
$this->mailer->setAttachments([
'cake.png' => [
'file' => CORE_PATH . 'VERSION.txt',
'contentId' => 'abc123',
],
]);
$result = $this->mailer->deliver('Hello');
$boundary = $this->mailer->boundary;
$this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
$expected = "--{$boundary}\r\n" .
"Content-Type: multipart/related; boundary=\"rel-{$boundary}\"\r\n" .
"\r\n" .
"--rel-{$boundary}\r\n" .
"Content-Type: text/html; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'Hello' .
"\r\n" .
"\r\n" .
"\r\n" .
"--rel-{$boundary}\r\n" .
"Content-Disposition: inline; filename=\"cake.png\"\r\n" .
"Content-Type: text/plain\r\n" .
"Content-Transfer-Encoding: base64\r\n" .
"Content-ID: <abc123>\r\n" .
"\r\n";
$this->assertStringContainsString($expected, $result['message']);
$this->assertStringContainsString('--rel-' . $boundary . '--', $result['message']);
$this->assertStringContainsString('--' . $boundary . '--', $result['message']);
}
/**
* Test setting inline attachments with multibyte names
*/
public function testSendWithInlineAttachmentsMultibyteName(): void
{
$name = '日本語の名前';
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setEmailFormat('html');
$this->mailer->setAttachments([
$name => [
'file' => CORE_PATH . 'VERSION.txt',
'contentId' => 'abc123',
],
]);
$result = $this->mailer->deliver('Hello');
$boundary = $this->mailer->boundary;
$this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
$expected = "--{$boundary}\r\n" .
"Content-Type: multipart/related; boundary=\"rel-{$boundary}\"\r\n" .
"\r\n" .
"--rel-{$boundary}\r\n" .
"Content-Type: text/html; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'Hello' .
"\r\n" .
"\r\n" .
"\r\n" .
"--rel-{$boundary}\r\n" .
"Content-Disposition: inline; filename=\"ri ben yuno ming qian\"; filename*=utf-8''%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%81%AE%E5%90%8D%E5%89%8D\r\n" .
"Content-Type: text/plain\r\n" .
"Content-Transfer-Encoding: base64\r\n" .
"Content-ID: <abc123>\r\n" .
"\r\n";
$this->assertStringContainsString($expected, $result['message']);
$this->assertStringContainsString('--rel-' . $boundary . '--', $result['message']);
$this->assertStringContainsString('--' . $boundary . '--', $result['message']);
// Extract the encoded filename and decode it.
preg_match("/utf-8''(.*?)\s/", $result['message'], $matches);
$this->assertEquals(urldecode($matches[1]), $name);
}
/**
* Test disabling content-disposition.
*/
public function testSendWithNoContentDispositionAttachments(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setEmailFormat('text');
$this->mailer->setAttachments([
'cake.png' => [
'file' => CORE_PATH . 'VERSION.txt',
'contentDisposition' => false,
],
]);
$result = $this->mailer->deliver('Hello');
$boundary = $this->mailer->boundary;
$this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
$expected = "--{$boundary}\r\n" .
"Content-Type: text/plain; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'Hello' .
"\r\n" .
"\r\n" .
"\r\n" .
"--{$boundary}\r\n" .
"Content-Type: text/plain\r\n" .
"Content-Transfer-Encoding: base64\r\n" .
"\r\n";
$this->assertStringContainsString($expected, $result['message']);
$this->assertStringContainsString('--' . $boundary . '--', $result['message']);
}
/**
* testSendRender method
*/
public function testSendRender(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()->setTemplate('default');
$result = $this->mailer->send();
$this->assertStringContainsString('This email was sent using the CakePHP Framework', $result['message']);
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
}
/**
* test sending and rendering with no layout
*/
public function testSendRenderNoLayout(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->viewBuilder()
->setTemplate('default')
->setVar('content', 'message body.')
->disableAutoLayout();
$result = $this->mailer->send();
$this->assertStringContainsString('message body.', $result['message']);
$this->assertStringNotContainsString('This email was sent using the CakePHP Framework', $result['message']);
}
/**
* testSendRender both method
*/
public function testSendRenderBoth(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()->setTemplate('default');
$this->mailer->setEmailFormat('both');
$result = $this->mailer->send();
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
$boundary = $this->mailer->boundary;
$this->assertStringContainsString('Content-Type: multipart/alternative; boundary="' . $boundary . '"', $result['headers']);
$expected = "--{$boundary}\r\n" .
"Content-Type: text/plain; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
"\r\n" .
"\r\n" .
'This email was sent using the CakePHP Framework, https://cakephp.org.' .
"\r\n" .
"\r\n" .
"\r\n" .
"--{$boundary}\r\n" .
"Content-Type: text/html; charset=UTF-8\r\n" .
"Content-Transfer-Encoding: 8bit\r\n" .
"\r\n" .
'<!DOCTYPE html';
$this->assertStringStartsWith($expected, $result['message']);
$expected = "</html>\r\n" .
"\r\n" .
"\r\n" .
"\r\n" .
"--{$boundary}--\r\n";
$this->assertStringEndsWith($expected, $result['message']);
}
/**
* testSendRender method for ISO-2022-JP
*/
public function testSendRenderJapanese(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()->setTemplate('default');
$this->mailer->viewBuilder()->setLayout('japanese');
$this->mailer->setCharset('ISO-2022-JP');
$result = $this->mailer->send();
$expected = mb_convert_encoding('CakePHP Framework を使って送信したメールです。 https://cakephp.org.', 'ISO-2022-JP');
$this->assertStringContainsString($expected, $result['message']);
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
}
/**
* testSendRenderThemed method
*/
public function testSendRenderThemed(): void
{
$this->loadPlugins(['TestTheme']);
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()->setTheme('TestTheme');
$this->mailer->viewBuilder()->setTemplate('themed');
$result = $this->mailer->send();
$this->assertStringContainsString('In TestTheme', $result['message']);
$this->assertStringContainsString('/test_theme/img/test.jpg', $result['message']);
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
$this->assertStringContainsString('/test_theme/img/test.jpg', $result['message']);
$this->clearPlugins();
}
/**
* testSendRenderWithHTML method and assert line length is kept below the required limit
*/
public function testSendRenderWithHTML(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->setEmailFormat('html');
$this->mailer->viewBuilder()->setTemplate('html');
$result = $this->mailer->send();
$this->assertTextContains('<h1>HTML Ipsum Presents</h1>', $result['message']);
$this->assertLineLengths($result['message']);
}
/**
* testSendRenderWithVars method
*/
public function testSendRenderWithVars(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()->setTemplate('custom');
$this->mailer->setViewVars(['value' => 12345]);
$result = $this->mailer->send();
$this->assertStringContainsString('Here is your value: 12345', $result['message']);
}
/**
* testSendRenderWithVars method for ISO-2022-JP
*/
public function testSendRenderWithVarsJapanese(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()->setTemplate('japanese');
$this->mailer->setViewVars(['value' => '日本語の差し込み123']);
$this->mailer->setCharset('ISO-2022-JP');
$result = $this->mailer->send();
$expected = mb_convert_encoding('ここにあなたの設定した値が入ります: 日本語の差し込み123', 'ISO-2022-JP');
$this->assertStringContainsString($expected, $result['message']);
}
/**
* testSendRenderWithHelpers method
*/
public function testSendRenderWithHelpers(): void
{
$this->mailer->setTransport('debug');
$timestamp = time();
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()
->setTemplate('custom_helper')
->setLayout('default')
->setHelpers(['Time']);
$this->mailer->setViewVars(['time' => $timestamp]);
$result = $this->mailer->send();
$dateTime = new DateTime();
$dateTime->setTimestamp($timestamp);
$this->assertStringContainsString('Right now: ' . $dateTime->format($dateTime::ATOM), $result['message']);
$result = $this->mailer->viewBuilder()->getHelpers();
$this->assertEquals(['Time' => []], $result);
}
/**
* testSendRenderWithImage method
*/
public function testSendRenderWithImage(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()->setTemplate('image');
$this->mailer->setEmailFormat('html');
$server = env('SERVER_NAME') ?: 'localhost';
if (env('SERVER_PORT') && env('SERVER_PORT') !== 80) {
$server .= ':' . env('SERVER_PORT');
}
$expected = '<img src="http://' . $server . '/img/image.gif" alt="cool image" width="100" height="100"';
$result = $this->mailer->send();
$this->assertStringContainsString($expected, $result['message']);
}
/**
* testSendRenderPlugin method
*/
public function testSendRenderPlugin(): void
{
$this->loadPlugins(['TestPlugin', 'TestPluginTwo', 'TestTheme']);
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()
->setTemplate('TestPlugin.test_plugin_tpl')
->setLayout('default');
$result = $this->mailer->send();
$this->assertStringContainsString('Into TestPlugin.', $result['message']);
$this->assertStringContainsString('This email was sent using the CakePHP Framework', $result['message']);
$this->mailer->viewBuilder()
->setTemplate('TestPlugin.test_plugin_tpl')
->setLayout('TestPlugin.plug_default');
$result = $this->mailer->send();
$this->assertStringContainsString('Into TestPlugin.', $result['message']);
$this->assertStringContainsString('This email was sent using the TestPlugin.', $result['message']);
$this->mailer->viewBuilder()
->setTemplate('TestPlugin.test_plugin_tpl')
->setLayout('plug_default');
$result = $this->mailer->send();
$this->assertStringContainsString('Into TestPlugin.', $result['message']);
$this->assertStringContainsString('This email was sent using the TestPlugin.', $result['message']);
$this->mailer->viewBuilder()
->setTemplate('TestPlugin.test_plugin_tpl')
->setLayout('TestPluginTwo.default');
$result = $this->mailer->send();
$this->assertStringContainsString('Into TestPlugin.', $result['message']);
$this->assertStringContainsString('This email was sent using TestPluginTwo.', $result['message']);
// test plugin template overridden by theme
$this->mailer->viewBuilder()->setTheme('TestTheme');
$result = $this->mailer->send();
$this->assertStringContainsString('Into TestPlugin. (themed)', $result['message']);
$this->mailer->setViewVars(['value' => 12345]);
$this->mailer->viewBuilder()
->setTemplate('custom')
->setLayout('TestPlugin.plug_default');
$result = $this->mailer->send();
$this->assertStringContainsString('Here is your value: 12345', $result['message']);
$this->assertStringContainsString('This email was sent using the TestPlugin.', $result['message']);
$this->clearPlugins();
}
/**
* Test that a MissingTemplateException is thrown
*/
public function testMissingTemplateException(): void
{
$this->expectException(MissingTemplateException::class);
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->viewBuilder()->setTemplate('fooo');
$this->mailer->send();
}
/**
* testSendMultipleMIME method
*/
public function testSendMultipleMIME(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->viewBuilder()->setTemplate('custom');
$this->mailer->setProfile([]);
$this->mailer->setViewVars(['value' => 12345]);
$this->mailer->setEmailFormat('both');
$this->mailer->send();
$message = $this->mailer->getBody();
$boundary = $this->mailer->boundary;
$this->assertNotEmpty($boundary);
$this->assertContains('--' . $boundary, $message);
$this->assertContains('--' . $boundary . '--', $message);
$this->mailer->setAttachments(['fake.php' => __FILE__]);
$this->mailer->send();
$message = $this->mailer->getBody();
$boundary = $this->mailer->boundary;
$this->assertNotEmpty($boundary);
$this->assertContains('--' . $boundary, $message);
$this->assertContains('--' . $boundary . '--', $message);
$this->assertContains('--alt-' . $boundary, $message);
$this->assertContains('--alt-' . $boundary . '--', $message);
}
/**
* testSendAttachment method
*/
public function testSendAttachment(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile([]);
$this->mailer->setAttachments([__FILE__]);
$this->mailer->setBodyText('body');
$result = $this->mailer->send();
$expected = "Content-Disposition: attachment; filename=\"MailerTest.php\"\r\n" .
"Content-Type: text/x-php\r\n" .
"Content-Transfer-Encoding: base64\r\n";
$this->assertStringContainsString($expected, $result['message']);
$this->mailer->setAttachments(['my.file.txt' => __FILE__]);
$this->mailer->setBodyText('body');
$result = $this->mailer->send();
$expected = "Content-Disposition: attachment; filename=\"my.file.txt\"\r\n" .
"Content-Type: text/x-php\r\n" .
"Content-Transfer-Encoding: base64\r\n";
$this->assertStringContainsString($expected, $result['message']);
$this->mailer->setAttachments(['file.txt' => ['file' => __FILE__, 'mimetype' => 'text/plain']]);
$this->mailer->setBodyText('body');
$result = $this->mailer->send();
$expected = "Content-Disposition: attachment; filename=\"file.txt\"\r\n" .
"Content-Type: text/plain\r\n" .
"Content-Transfer-Encoding: base64\r\n";
$this->assertStringContainsString($expected, $result['message']);
$this->mailer->setAttachments(['file2.txt' => ['file' => __FILE__, 'mimetype' => 'text/plain', 'contentId' => 'a1b1c1']]);
$this->mailer->setBodyText('body');
$result = $this->mailer->send();
$expected = "Content-Disposition: inline; filename=\"file2.txt\"\r\n" .
"Content-Type: text/plain\r\n" .
"Content-Transfer-Encoding: base64\r\n" .
"Content-ID: <a1b1c1>\r\n";
$this->assertStringContainsString($expected, $result['message']);
}
/**
* testGetBody method
*/
public function testGetBody(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['empty']);
$this->mailer->viewBuilder()->setTemplate('default');
$this->mailer->setEmailFormat('both');
$this->mailer->send();
$expected = '<p>This email was sent using the <a href="https://cakephp.org">CakePHP Framework</a></p>';
$this->assertStringContainsString($expected, $this->mailer->getBodyHtml());
$expected = 'This email was sent using the CakePHP Framework, https://cakephp.org.';
$this->assertStringContainsString($expected, $this->mailer->getBodyText());
$message = $this->mailer->getBody();
$this->assertContains('Content-Type: text/plain; charset=UTF-8', $message);
$this->assertContains('Content-Type: text/html; charset=UTF-8', $message);
// UTF-8 is 8bit
$this->assertTrue($this->_checkContentTransferEncoding($message, '8bit'));
$this->mailer->setCharset('ISO-2022-JP');
$this->mailer->send();
$message = $this->mailer->getBody();
$this->assertContains('Content-Type: text/plain; charset=ISO-2022-JP', $message);
$this->assertContains('Content-Type: text/html; charset=ISO-2022-JP', $message);
// ISO-2022-JP is 7bit
$this->assertTrue($this->_checkContentTransferEncoding($message, '7bit'));
}
/**
* testZeroOnlyLinesNotBeingEmptied()
*/
public function testZeroOnlyLinesNotBeingEmptied(): void
{
$message = "Lorem\r\n0\r\n0\r\nipsum";
$this->mailer->reset();
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->setSubject('Wordwrap Test');
$result = $this->mailer->deliver($message);
$expected = "{$message}\r\n\r\n";
$this->assertSame($expected, $result['message']);
}
/**
* testReset method
*/
public function testReset(): void
{
$this->mailer->setTo('cake@cakephp.org');
$this->mailer->viewBuilder()->setTheme('TestTheme');
$this->mailer->setEmailPattern('/.+@.+\..+/i');
$this->assertSame(['cake@cakephp.org' => 'cake@cakephp.org'], $this->mailer->getTo());
$this->mailer->reset();
$this->assertSame([], $this->mailer->getTo());
$this->assertNull($this->mailer->viewBuilder()->getTheme());
$this->assertSame(Message::EMAIL_PATTERN, $this->mailer->getEmailPattern());
}
/**
* testSendWithLog method
*/
public function testSendWithLog(): void
{
Log::setConfig('email', [
'className' => 'Array',
]);
$this->mailer->setTransport('debug');
$this->mailer->setTo('me@cakephp.org');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['log' => 'debug']);
$text = 'Logging This';
$result = $this->mailer->deliver($text);
$this->assertNotEmpty($result);
$messages = Log::engine('email')->read();
$this->assertCount(1, $messages);
$this->assertStringContainsString($text, $messages[0]);
$this->assertStringContainsString('cake@cakephp.org', $messages[0]);
$this->assertStringContainsString('me@cakephp.org', $messages[0]);
}
/**
* testSendWithLogAndScope method
*/
public function testSendWithLogAndScope(): void
{
Log::setConfig('email', [
'className' => 'Array',
'scopes' => ['email'],
]);
$this->mailer->setTransport('debug');
$this->mailer->setTo('me@cakephp.org');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['log' => ['scope' => 'email']]);
$text = 'Logging This';
$this->mailer->deliver($text);
$messages = Log::engine('email')->read();
$this->assertCount(1, $messages);
$this->assertStringContainsString($text, $messages[0]);
$this->assertStringContainsString('cake@cakephp.org', $messages[0]);
$this->assertStringContainsString('me@cakephp.org', $messages[0]);
}
/**
* test mail logging to cake.mailer scope
*/
public function testSendWithLogCakeScope(): void
{
Log::setConfig('email', [
'className' => 'Array',
'scopes' => ['cake.mailer'],
]);
$this->mailer->setTransport('debug');
$this->mailer->setTo('me@cakephp.org');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setSubject('My title');
$this->mailer->setProfile(['log' => true]);
$text = 'Logging This';
$this->mailer->deliver($text);
$messages = Log::engine('email')->read();
$this->assertCount(1, $messages);
$this->assertStringContainsString($text, $messages[0]);
$this->assertStringContainsString('cake@cakephp.org', $messages[0]);
$this->assertStringContainsString('me@cakephp.org', $messages[0]);
}
public function testMissingActionThrowsException(): void
{
$this->expectException(MissingActionException::class);
$this->expectExceptionMessage('Mail Cake\Mailer\Mailer::test() could not be found, or is not accessible.');
(new Mailer())->send('test');
}
public function testDeliver(): void
{
$this->mailer->setTransport('debug');
$this->mailer->setFrom('cake@cakephp.org');
$this->mailer->setTo(['you@cakephp.org' => 'You']);
$this->mailer->setSubject('My title');
$result = $this->mailer->deliver("Here is my body, with multi lines.\nThis is the second line.\r\n\r\nAnd the last.");
$expected = ['headers', 'message'];
$this->assertEquals($expected, array_keys($result));
$expected = "Here is my body, with multi lines.\r\nThis is the second line.\r\n\r\nAnd the last.\r\n\r\n";
$this->assertSame($expected, $result['message']);
$this->assertStringContainsString('Date: ', $result['headers']);
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
$result = $this->mailer->deliver('Other body');
$expected = "Other body\r\n\r\n";
$this->assertSame($expected, $result['message']);
$this->assertStringContainsString('Message-ID: ', $result['headers']);
$this->assertStringContainsString('To: ', $result['headers']);
}
protected function assertLineLengths(string $message): void
{
$lines = explode("\r\n", $message);
foreach ($lines as $line) {
$this->assertTrue(
strlen($line) <= Message::LINE_LENGTH_MUST,
'Line length exceeds the max. limit of Message::LINE_LENGTH_MUST'
);
}
}
/**
* @param array|string $message
*/
protected function _checkContentTransferEncoding($message, string $charset): bool
{
$boundary = '--' . $this->mailer->getBoundary();
$result['text'] = false;
$result['html'] = false;
$length = count($message);
for ($i = 0; $i < $length; ++$i) {
if ($message[$i] === $boundary) {
$flag = false;
$type = '';
while (!preg_match('/^$/', $message[$i])) {
if (preg_match('/^Content-Type: text\/plain/', $message[$i])) {
$type = 'text';
}
if (preg_match('/^Content-Type: text\/html/', $message[$i])) {
$type = 'html';
}
if ($message[$i] === 'Content-Transfer-Encoding: ' . $charset) {
$flag = true;
}
++$i;
}
$result[$type] = $flag;
}
}
return $result['text'] && $result['html'];
}
}