MailerTest.php 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @since 3.1.0
  12. * @license https://opensource.org/licenses/mit-license.php MIT License
  13. */
  14. namespace Cake\Test\TestCase\Mailer;
  15. use BadMethodCallException;
  16. use Cake\Core\Configure;
  17. use Cake\Log\Log;
  18. use Cake\Mailer\AbstractTransport;
  19. use Cake\Mailer\Exception\MissingActionException;
  20. use Cake\Mailer\Mailer;
  21. use Cake\Mailer\Message;
  22. use Cake\Mailer\Transport\DebugTransport;
  23. use Cake\Mailer\TransportFactory;
  24. use Cake\TestSuite\TestCase;
  25. use Cake\View\Exception\MissingTemplateException;
  26. use DateTime;
  27. use InvalidArgumentException;
  28. use PHPUnit\Framework\Attributes\WithoutErrorHandler;
  29. use TestApp\Mailer\TestMailer;
  30. use function Cake\Core\env;
  31. class MailerTest extends TestCase
  32. {
  33. /**
  34. * @var array
  35. */
  36. protected $transports = [];
  37. /**
  38. * @var \Cake\Mailer\Mailer
  39. */
  40. protected $mailer;
  41. /**
  42. * setUp
  43. */
  44. public function setUp(): void
  45. {
  46. parent::setUp();
  47. $this->transports = [
  48. 'debug' => [
  49. 'className' => 'Debug',
  50. ],
  51. 'badClassName' => [
  52. 'className' => 'TestFalse',
  53. ],
  54. ];
  55. TransportFactory::setConfig($this->transports);
  56. $this->mailer = new TestMailer();
  57. }
  58. /**
  59. * tearDown method
  60. */
  61. public function tearDown(): void
  62. {
  63. parent::tearDown();
  64. TransportFactory::drop('debug');
  65. TransportFactory::drop('badClassName');
  66. Mailer::drop('test');
  67. Mailer::drop('default');
  68. Log::drop('email');
  69. }
  70. /**
  71. * testTransport method
  72. */
  73. public function testTransport(): void
  74. {
  75. $result = $this->mailer->setTransport('debug');
  76. $this->assertSame($this->mailer, $result);
  77. $result = $this->mailer->getTransport();
  78. $this->assertInstanceOf(DebugTransport::class, $result);
  79. $instance = new class extends DebugTransport {
  80. };
  81. $this->mailer->setTransport($instance);
  82. $this->assertSame($instance, $this->mailer->getTransport());
  83. }
  84. /**
  85. * Test that using unknown transports fails.
  86. */
  87. public function testTransportInvalid(): void
  88. {
  89. $this->expectException(InvalidArgumentException::class);
  90. $this->expectExceptionMessage('The `Invalid` transport configuration does not exist');
  91. $this->mailer->setTransport('Invalid');
  92. }
  93. /**
  94. * testMessage function
  95. *
  96. * @deprecated
  97. */
  98. #[WithoutErrorHandler]
  99. public function testSetMessage(): void
  100. {
  101. $message = $this->mailer->getMessage();
  102. $this->assertInstanceOf(Message::class, $message);
  103. $newMessage = new Message();
  104. $this->deprecated(function () use ($newMessage): void {
  105. $this->mailer->setMessage($newMessage);
  106. });
  107. $this->assertSame($newMessage, $this->mailer->getMessage());
  108. $this->assertNotSame($message, $newMessage);
  109. }
  110. /**
  111. * Test reading/writing configuration profiles.
  112. */
  113. public function testConfig(): void
  114. {
  115. $settings = [
  116. 'to' => 'mark@example.com',
  117. 'from' => 'noreply@example.com',
  118. ];
  119. Mailer::setConfig('test', $settings);
  120. $this->assertEquals($settings, Mailer::getConfig('test'), 'Should be the same.');
  121. $mailer = new Mailer('test');
  122. $this->assertContains($settings['to'], $mailer->getTo());
  123. }
  124. /**
  125. * Test that exceptions are raised on duplicate config set.
  126. */
  127. public function testConfigErrorOnDuplicate(): void
  128. {
  129. $this->expectException(BadMethodCallException::class);
  130. $settings = [
  131. 'to' => 'mark@example.com',
  132. 'from' => 'noreply@example.com',
  133. ];
  134. Mailer::setConfig('test', $settings);
  135. Mailer::setConfig('test', $settings);
  136. }
  137. /**
  138. * testConstructWithConfigArray method
  139. */
  140. public function testConstructWithConfigArray(): void
  141. {
  142. $configs = [
  143. 'from' => ['some@example.com' => 'My website'],
  144. 'to' => 'test@example.com',
  145. 'subject' => 'Test mail subject',
  146. 'transport' => 'debug',
  147. ];
  148. $this->mailer = new Mailer($configs);
  149. $result = $this->mailer->getTo();
  150. $this->assertEquals([$configs['to'] => $configs['to']], $result);
  151. $result = $this->mailer->getFrom();
  152. $this->assertEquals($configs['from'], $result);
  153. $result = $this->mailer->getSubject();
  154. $this->assertSame($configs['subject'], $result);
  155. $result = $this->mailer->getTransport();
  156. $this->assertInstanceOf(DebugTransport::class, $result);
  157. $result = $this->mailer->deliver('This is the message');
  158. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  159. $this->assertStringContainsString('To: ', $result['headers']);
  160. }
  161. /**
  162. * testConfigArrayWithLayoutWithoutTemplate method
  163. */
  164. public function testConfigArrayWithLayoutWithoutTemplate(): void
  165. {
  166. $configs = [
  167. 'from' => ['some@example.com' => 'My website'],
  168. 'to' => 'test@example.com',
  169. 'subject' => 'Test mail subject',
  170. 'transport' => 'debug',
  171. 'layout' => 'custom',
  172. ];
  173. $this->mailer = new Mailer($configs);
  174. $template = $this->mailer->viewBuilder()->getTemplate();
  175. $layout = $this->mailer->viewBuilder()->getLayout();
  176. $this->assertNull($template);
  177. $this->assertSame($configs['layout'], $layout);
  178. }
  179. /**
  180. * testConstructWithConfigString method
  181. */
  182. public function testConstructWithConfigString(): void
  183. {
  184. $configs = [
  185. 'from' => ['some@example.com' => 'My website'],
  186. 'to' => 'test@example.com',
  187. 'subject' => 'Test mail subject',
  188. 'transport' => 'debug',
  189. ];
  190. Mailer::setConfig('test', $configs);
  191. $this->mailer = new Mailer('test');
  192. $result = $this->mailer->getTo();
  193. $this->assertEquals([$configs['to'] => $configs['to']], $result);
  194. $result = $this->mailer->getFrom();
  195. $this->assertEquals($configs['from'], $result);
  196. $result = $this->mailer->getSubject();
  197. $this->assertSame($configs['subject'], $result);
  198. $result = $this->mailer->getTransport();
  199. $this->assertInstanceOf(DebugTransport::class, $result);
  200. $result = $this->mailer->deliver('This is the message');
  201. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  202. $this->assertStringContainsString('To: ', $result['headers']);
  203. }
  204. /**
  205. * test profile method
  206. */
  207. public function testSetProfile(): void
  208. {
  209. $config = ['to' => 'foo@bar.com'];
  210. $this->mailer->setProfile($config);
  211. $this->assertSame(['foo@bar.com' => 'foo@bar.com'], $this->mailer->getTo());
  212. }
  213. /**
  214. * test that default profile is used by constructor if available.
  215. */
  216. public function testDefaultProfile(): void
  217. {
  218. $config = ['to' => 'foo@bar.com', 'from' => 'from@bar.com'];
  219. Configure::write('Mailer.default', $config);
  220. Mailer::setConfig(Configure::consume('Mailer'));
  221. $mailer = new Mailer();
  222. $this->assertSame(['foo@bar.com' => 'foo@bar.com'], $mailer->getTo());
  223. $this->assertSame(['from@bar.com' => 'from@bar.com'], $mailer->getFrom());
  224. Configure::delete('Mailer');
  225. Mailer::drop('default');
  226. }
  227. /**
  228. * Test that using an invalid profile fails.
  229. */
  230. public function testProfileInvalid(): void
  231. {
  232. $this->expectException(InvalidArgumentException::class);
  233. $this->expectExceptionMessage('Unknown email configuration `derp`.');
  234. $mailer = new Mailer();
  235. $mailer->setProfile('derp');
  236. }
  237. /**
  238. * testConfigString method
  239. */
  240. public function testUseConfigString(): void
  241. {
  242. $config = [
  243. 'from' => ['some@example.com' => 'My website'],
  244. 'to' => ['test@example.com' => 'Testname'],
  245. 'subject' => 'Test mail subject',
  246. 'transport' => 'debug',
  247. 'theme' => 'TestTheme',
  248. 'helpers' => ['Html', 'Form'],
  249. 'autoLayout' => false,
  250. ];
  251. Mailer::setConfig('test', $config);
  252. $this->mailer->setProfile('test');
  253. $result = $this->mailer->getTo();
  254. $this->assertEquals($config['to'], $result);
  255. $result = $this->mailer->getFrom();
  256. $this->assertEquals($config['from'], $result);
  257. $result = $this->mailer->getSubject();
  258. $this->assertSame($config['subject'], $result);
  259. $result = $this->mailer->viewBuilder()->getTheme();
  260. $this->assertSame($config['theme'], $result);
  261. $result = $this->mailer->getTransport();
  262. $this->assertInstanceOf(DebugTransport::class, $result);
  263. $result = $this->mailer->viewBuilder()->getHelpers();
  264. $this->assertEquals(['Html' => [], 'Form' => []], $result);
  265. $this->assertFalse($this->mailer->viewBuilder()->isAutoLayoutEnabled());
  266. Mailer::drop('test');
  267. }
  268. /**
  269. * CakeEmailTest::testDefaultTransport()
  270. */
  271. public function testDefaultTransport(): void
  272. {
  273. TransportFactory::drop('default');
  274. $instance = new class extends AbstractTransport {
  275. public function send(Message $message): array
  276. {
  277. return [];
  278. }
  279. };
  280. $config = ['from' => 'tester@example.org', 'transport' => 'default'];
  281. Mailer::setConfig('default', $config);
  282. TransportFactory::setConfig('default', $instance);
  283. $em = new Mailer('default');
  284. $this->assertSame($instance, $em->getTransport());
  285. TransportFactory::drop('default');
  286. }
  287. public function testProxies(): void
  288. {
  289. $result = (new Mailer())->setHeaders(['X-Something' => 'nice']);
  290. $this->assertInstanceOf(Mailer::class, $result);
  291. $header = $result->getMessage()->getHeaders();
  292. $this->assertSame('nice', $header['X-Something']);
  293. $result = (new Mailer())->setAttachments([
  294. ['file' => APP . 'ApplicationWithDefaultRoutes.php', 'mimetype' => 'text/plain'],
  295. ]);
  296. $this->assertInstanceOf(Mailer::class, $result);
  297. $this->assertSame(
  298. ['ApplicationWithDefaultRoutes.php' => ['file' => APP . 'ApplicationWithDefaultRoutes.php', 'mimetype' => 'text/plain']],
  299. $result->getMessage()->getAttachments()
  300. );
  301. }
  302. /**
  303. * Test that get/set methods can be proxied.
  304. */
  305. public function testGetSetProxies(): void
  306. {
  307. $mailer = new Mailer();
  308. $result = $mailer
  309. ->setTo('test@example.com')
  310. ->setCc('cc@example.com');
  311. $this->assertSame($result, $mailer);
  312. $this->assertSame(['test@example.com' => 'test@example.com'], $result->getTo());
  313. $this->assertSame(['cc@example.com' => 'cc@example.com'], $result->getCc());
  314. }
  315. public function testSet(): void
  316. {
  317. $result = (new Mailer())->setViewVars('key', 'value');
  318. $this->assertInstanceOf(Mailer::class, $result);
  319. $this->assertSame(['key' => 'value'], $result->getRenderer()->viewBuilder()->getVars());
  320. }
  321. /**
  322. * testRenderWithLayoutAndAttachment method
  323. */
  324. public function testRenderWithLayoutAndAttachment(): void
  325. {
  326. $this->mailer->setEmailFormat('html');
  327. $this->mailer->viewBuilder()->setTemplate('html');
  328. $this->mailer->setAttachments([APP . 'ApplicationWithDefaultRoutes.php']);
  329. $this->mailer->render();
  330. $result = $this->mailer->getBody();
  331. $this->assertNotEmpty($result);
  332. $result = $this->mailer->getBoundary();
  333. $this->assertMatchesRegularExpression('/^[0-9a-f]{32}$/', $result);
  334. }
  335. /**
  336. * Calling send() with no parameters should not overwrite the view variables.
  337. */
  338. public function testSendWithNoContentDoesNotOverwriteViewVar(): void
  339. {
  340. $this->mailer->reset();
  341. $this->mailer->setTransport('debug');
  342. $this->mailer->setFrom('cake@cakephp.org');
  343. $this->mailer->setTo('you@cakephp.org');
  344. $this->mailer->setSubject('My title');
  345. $this->mailer->setEmailFormat('text');
  346. $this->mailer->viewBuilder()
  347. ->setTemplate('default')
  348. ->setVars([
  349. 'content' => 'A message to you',
  350. ]);
  351. $result = $this->mailer->send();
  352. $this->assertStringContainsString('A message to you', $result['message']);
  353. }
  354. /**
  355. * testSendWithContent method
  356. */
  357. public function testSendWithContent(): void
  358. {
  359. $this->mailer->reset();
  360. $this->mailer->setTransport('debug');
  361. $this->mailer->setFrom('cake@cakephp.org');
  362. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  363. $this->mailer->setSubject('My title');
  364. $this->mailer->setProfile(['empty']);
  365. $result = $this->mailer->deliver("Here is my body, with multi lines.\nThis is the second line.\r\n\r\nAnd the last.");
  366. $expected = ['headers', 'message'];
  367. $this->assertEquals($expected, array_keys($result));
  368. $expected = "Here is my body, with multi lines.\r\nThis is the second line.\r\n\r\nAnd the last.\r\n\r\n";
  369. $this->assertSame($expected, $result['message']);
  370. $this->assertStringContainsString('Date: ', $result['headers']);
  371. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  372. $this->assertStringContainsString('To: ', $result['headers']);
  373. $result = $this->mailer->deliver('Other body');
  374. $expected = "Other body\r\n\r\n";
  375. $this->assertSame($expected, $result['message']);
  376. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  377. $this->assertStringContainsString('To: ', $result['headers']);
  378. }
  379. /**
  380. * test send without a transport method
  381. */
  382. public function testSendWithoutTransport(): void
  383. {
  384. $this->expectException(BadMethodCallException::class);
  385. $this->expectExceptionMessage(
  386. 'Transport was not defined. You must set on using setTransport() or set `transport` option in your mailer profile.'
  387. );
  388. $this->mailer->setTo('cake@cakephp.org');
  389. $this->mailer->setFrom('cake@cakephp.org');
  390. $this->mailer->setSubject('My title');
  391. $this->mailer->send();
  392. }
  393. /**
  394. * Test send() with no template.
  395. */
  396. public function testSendNoTemplateWithAttachments(): void
  397. {
  398. $this->mailer->setTransport('debug');
  399. $this->mailer->setFrom('cake@cakephp.org');
  400. $this->mailer->setTo('cake@cakephp.org');
  401. $this->mailer->setSubject('My title');
  402. $this->mailer->setEmailFormat('text');
  403. $this->mailer->setAttachments([APP . 'ApplicationWithDefaultRoutes.php']);
  404. $result = $this->mailer->deliver('Hello');
  405. $boundary = $this->mailer->boundary;
  406. $this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
  407. $expected = "--{$boundary}\r\n" .
  408. "Content-Type: text/plain; charset=UTF-8\r\n" .
  409. "Content-Transfer-Encoding: 8bit\r\n" .
  410. "\r\n" .
  411. 'Hello' .
  412. "\r\n" .
  413. "\r\n" .
  414. "\r\n" .
  415. "--{$boundary}\r\n" .
  416. "Content-Disposition: attachment; filename=\"ApplicationWithDefaultRoutes.php\"\r\n" .
  417. "Content-Type: text/x-php\r\n" .
  418. "Content-Transfer-Encoding: base64\r\n" .
  419. "\r\n";
  420. $this->assertStringContainsString($expected, $result['message']);
  421. }
  422. /**
  423. * Test send() with no template and data string attachment
  424. */
  425. public function testSendNoTemplateWithDataStringAttachment(): void
  426. {
  427. $this->mailer->setTransport('debug');
  428. $this->mailer->setFrom('cake@cakephp.org');
  429. $this->mailer->setTo('cake@cakephp.org');
  430. $this->mailer->setSubject('My title');
  431. $this->mailer->setEmailFormat('text');
  432. $data = file_get_contents(TEST_APP . 'webroot/img/cake.power.gif');
  433. $this->mailer->setAttachments(['cake.icon.gif' => [
  434. 'data' => $data,
  435. 'mimetype' => 'image/gif',
  436. ]]);
  437. $result = $this->mailer->deliver('Hello');
  438. $boundary = $this->mailer->boundary;
  439. $this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
  440. $expected = "--{$boundary}\r\n" .
  441. "Content-Type: text/plain; charset=UTF-8\r\n" .
  442. "Content-Transfer-Encoding: 8bit\r\n" .
  443. "\r\n" .
  444. 'Hello' .
  445. "\r\n" .
  446. "\r\n" .
  447. "\r\n" .
  448. "--{$boundary}\r\n" .
  449. "Content-Disposition: attachment; filename=\"cake.icon.gif\"\r\n" .
  450. "Content-Type: image/gif\r\n" .
  451. "Content-Transfer-Encoding: base64\r\n\r\n";
  452. $expected .= chunk_split(base64_encode($data), 76, "\r\n");
  453. $this->assertStringContainsString($expected, $result['message']);
  454. }
  455. /**
  456. * Test send() with no template as both
  457. */
  458. public function testSendNoTemplateWithAttachmentsAsBoth(): void
  459. {
  460. $this->mailer->setTransport('debug');
  461. $this->mailer->setFrom('cake@cakephp.org');
  462. $this->mailer->setTo('cake@cakephp.org');
  463. $this->mailer->setSubject('My title');
  464. $this->mailer->setEmailFormat('both');
  465. $this->mailer->setAttachments([CORE_PATH . 'VERSION.txt']);
  466. $result = $this->mailer->deliver('Hello');
  467. $boundary = $this->mailer->boundary;
  468. $this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
  469. $expected = "--{$boundary}\r\n" .
  470. "Content-Type: multipart/alternative; boundary=\"alt-{$boundary}\"\r\n" .
  471. "\r\n" .
  472. "--alt-{$boundary}\r\n" .
  473. "Content-Type: text/plain; charset=UTF-8\r\n" .
  474. "Content-Transfer-Encoding: 8bit\r\n" .
  475. "\r\n" .
  476. 'Hello' .
  477. "\r\n" .
  478. "\r\n" .
  479. "\r\n" .
  480. "--alt-{$boundary}\r\n" .
  481. "Content-Type: text/html; charset=UTF-8\r\n" .
  482. "Content-Transfer-Encoding: 8bit\r\n" .
  483. "\r\n" .
  484. 'Hello' .
  485. "\r\n" .
  486. "\r\n" .
  487. "\r\n" .
  488. "--alt-{$boundary}--\r\n" .
  489. "\r\n" .
  490. "--{$boundary}\r\n" .
  491. "Content-Disposition: attachment; filename=\"VERSION.txt\"\r\n" .
  492. "Content-Type: text/plain\r\n" .
  493. "Content-Transfer-Encoding: base64\r\n" .
  494. "\r\n";
  495. $this->assertStringContainsString($expected, $result['message']);
  496. }
  497. /**
  498. * Test setting inline attachments and messages.
  499. */
  500. public function testSendWithInlineAttachments(): void
  501. {
  502. $this->mailer->setTransport('debug');
  503. $this->mailer->setFrom('cake@cakephp.org');
  504. $this->mailer->setTo('cake@cakephp.org');
  505. $this->mailer->setSubject('My title');
  506. $this->mailer->setEmailFormat('both');
  507. $this->mailer->setAttachments([
  508. 'cake.png' => [
  509. 'file' => CORE_PATH . 'VERSION.txt',
  510. 'contentId' => 'abc123',
  511. ],
  512. ]);
  513. $result = $this->mailer->deliver('Hello');
  514. $boundary = $this->mailer->boundary;
  515. $this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
  516. $expected = "--{$boundary}\r\n" .
  517. "Content-Type: multipart/related; boundary=\"rel-{$boundary}\"\r\n" .
  518. "\r\n" .
  519. "--rel-{$boundary}\r\n" .
  520. "Content-Type: multipart/alternative; boundary=\"alt-{$boundary}\"\r\n" .
  521. "\r\n" .
  522. "--alt-{$boundary}\r\n" .
  523. "Content-Type: text/plain; charset=UTF-8\r\n" .
  524. "Content-Transfer-Encoding: 8bit\r\n" .
  525. "\r\n" .
  526. 'Hello' .
  527. "\r\n" .
  528. "\r\n" .
  529. "\r\n" .
  530. "--alt-{$boundary}\r\n" .
  531. "Content-Type: text/html; charset=UTF-8\r\n" .
  532. "Content-Transfer-Encoding: 8bit\r\n" .
  533. "\r\n" .
  534. 'Hello' .
  535. "\r\n" .
  536. "\r\n" .
  537. "\r\n" .
  538. "--alt-{$boundary}--\r\n" .
  539. "\r\n" .
  540. "--rel-{$boundary}\r\n" .
  541. "Content-Disposition: inline; filename=\"cake.png\"\r\n" .
  542. "Content-Type: text/plain\r\n" .
  543. "Content-Transfer-Encoding: base64\r\n" .
  544. "Content-ID: <abc123>\r\n" .
  545. "\r\n";
  546. $this->assertStringContainsString($expected, $result['message']);
  547. $this->assertStringContainsString('--rel-' . $boundary . '--', $result['message']);
  548. $this->assertStringContainsString('--' . $boundary . '--', $result['message']);
  549. }
  550. /**
  551. * Test setting inline attachments and HTML only messages.
  552. */
  553. public function testSendWithInlineAttachmentsHtmlOnly(): void
  554. {
  555. $this->mailer->setTransport('debug');
  556. $this->mailer->setFrom('cake@cakephp.org');
  557. $this->mailer->setTo('cake@cakephp.org');
  558. $this->mailer->setSubject('My title');
  559. $this->mailer->setEmailFormat('html');
  560. $this->mailer->setAttachments([
  561. 'cake.png' => [
  562. 'file' => CORE_PATH . 'VERSION.txt',
  563. 'contentId' => 'abc123',
  564. ],
  565. ]);
  566. $result = $this->mailer->deliver('Hello');
  567. $boundary = $this->mailer->boundary;
  568. $this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
  569. $expected = "--{$boundary}\r\n" .
  570. "Content-Type: multipart/related; boundary=\"rel-{$boundary}\"\r\n" .
  571. "\r\n" .
  572. "--rel-{$boundary}\r\n" .
  573. "Content-Type: text/html; charset=UTF-8\r\n" .
  574. "Content-Transfer-Encoding: 8bit\r\n" .
  575. "\r\n" .
  576. 'Hello' .
  577. "\r\n" .
  578. "\r\n" .
  579. "\r\n" .
  580. "--rel-{$boundary}\r\n" .
  581. "Content-Disposition: inline; filename=\"cake.png\"\r\n" .
  582. "Content-Type: text/plain\r\n" .
  583. "Content-Transfer-Encoding: base64\r\n" .
  584. "Content-ID: <abc123>\r\n" .
  585. "\r\n";
  586. $this->assertStringContainsString($expected, $result['message']);
  587. $this->assertStringContainsString('--rel-' . $boundary . '--', $result['message']);
  588. $this->assertStringContainsString('--' . $boundary . '--', $result['message']);
  589. }
  590. /**
  591. * Test setting inline attachments with multibyte names
  592. */
  593. public function testSendWithInlineAttachmentsMultibyteName(): void
  594. {
  595. $name = '日本語の名前';
  596. $this->mailer->setTransport('debug');
  597. $this->mailer->setFrom('cake@cakephp.org');
  598. $this->mailer->setTo('cake@cakephp.org');
  599. $this->mailer->setSubject('My title');
  600. $this->mailer->setEmailFormat('html');
  601. $this->mailer->setAttachments([
  602. $name => [
  603. 'file' => CORE_PATH . 'VERSION.txt',
  604. 'contentId' => 'abc123',
  605. ],
  606. ]);
  607. $result = $this->mailer->deliver('Hello');
  608. $boundary = $this->mailer->boundary;
  609. $this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
  610. $expected = "--{$boundary}\r\n" .
  611. "Content-Type: multipart/related; boundary=\"rel-{$boundary}\"\r\n" .
  612. "\r\n" .
  613. "--rel-{$boundary}\r\n" .
  614. "Content-Type: text/html; charset=UTF-8\r\n" .
  615. "Content-Transfer-Encoding: 8bit\r\n" .
  616. "\r\n" .
  617. 'Hello' .
  618. "\r\n" .
  619. "\r\n" .
  620. "\r\n" .
  621. "--rel-{$boundary}\r\n" .
  622. "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" .
  623. "Content-Type: text/plain\r\n" .
  624. "Content-Transfer-Encoding: base64\r\n" .
  625. "Content-ID: <abc123>\r\n" .
  626. "\r\n";
  627. $this->assertStringContainsString($expected, $result['message']);
  628. $this->assertStringContainsString('--rel-' . $boundary . '--', $result['message']);
  629. $this->assertStringContainsString('--' . $boundary . '--', $result['message']);
  630. // Extract the encoded filename and decode it.
  631. preg_match("/utf-8''(.*?)\s/", $result['message'], $matches);
  632. $this->assertEquals(urldecode($matches[1]), $name);
  633. }
  634. /**
  635. * Test disabling content-disposition.
  636. */
  637. public function testSendWithNoContentDispositionAttachments(): void
  638. {
  639. $this->mailer->setTransport('debug');
  640. $this->mailer->setFrom('cake@cakephp.org');
  641. $this->mailer->setTo('cake@cakephp.org');
  642. $this->mailer->setSubject('My title');
  643. $this->mailer->setEmailFormat('text');
  644. $this->mailer->setAttachments([
  645. 'cake.png' => [
  646. 'file' => CORE_PATH . 'VERSION.txt',
  647. 'contentDisposition' => false,
  648. ],
  649. ]);
  650. $result = $this->mailer->deliver('Hello');
  651. $boundary = $this->mailer->boundary;
  652. $this->assertStringContainsString('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']);
  653. $expected = "--{$boundary}\r\n" .
  654. "Content-Type: text/plain; charset=UTF-8\r\n" .
  655. "Content-Transfer-Encoding: 8bit\r\n" .
  656. "\r\n" .
  657. 'Hello' .
  658. "\r\n" .
  659. "\r\n" .
  660. "\r\n" .
  661. "--{$boundary}\r\n" .
  662. "Content-Type: text/plain\r\n" .
  663. "Content-Transfer-Encoding: base64\r\n" .
  664. "\r\n";
  665. $this->assertStringContainsString($expected, $result['message']);
  666. $this->assertStringContainsString('--' . $boundary . '--', $result['message']);
  667. }
  668. /**
  669. * testSendRender method
  670. */
  671. public function testSendRender(): void
  672. {
  673. $this->mailer->setTransport('debug');
  674. $this->mailer->setFrom('cake@cakephp.org');
  675. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  676. $this->mailer->setSubject('My title');
  677. $this->mailer->setProfile(['empty']);
  678. $this->mailer->viewBuilder()->setTemplate('default');
  679. $result = $this->mailer->send();
  680. $this->assertStringContainsString('This email was sent using the CakePHP Framework', $result['message']);
  681. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  682. $this->assertStringContainsString('To: ', $result['headers']);
  683. }
  684. /**
  685. * test sending and rendering with no layout
  686. */
  687. public function testSendRenderNoLayout(): void
  688. {
  689. $this->mailer->setTransport('debug');
  690. $this->mailer->setFrom('cake@cakephp.org');
  691. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  692. $this->mailer->setSubject('My title');
  693. $this->mailer->viewBuilder()
  694. ->setTemplate('default')
  695. ->setVar('content', 'message body.')
  696. ->disableAutoLayout();
  697. $result = $this->mailer->send();
  698. $this->assertStringContainsString('message body.', $result['message']);
  699. $this->assertStringNotContainsString('This email was sent using the CakePHP Framework', $result['message']);
  700. }
  701. /**
  702. * testSendRender both method
  703. */
  704. public function testSendRenderBoth(): void
  705. {
  706. $this->mailer->setTransport('debug');
  707. $this->mailer->setFrom('cake@cakephp.org');
  708. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  709. $this->mailer->setSubject('My title');
  710. $this->mailer->setProfile(['empty']);
  711. $this->mailer->viewBuilder()->setTemplate('default');
  712. $this->mailer->setEmailFormat('both');
  713. $result = $this->mailer->send();
  714. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  715. $this->assertStringContainsString('To: ', $result['headers']);
  716. $boundary = $this->mailer->boundary;
  717. $this->assertStringContainsString('Content-Type: multipart/alternative; boundary="' . $boundary . '"', $result['headers']);
  718. $expected = "--{$boundary}\r\n" .
  719. "Content-Type: text/plain; charset=UTF-8\r\n" .
  720. "Content-Transfer-Encoding: 8bit\r\n" .
  721. "\r\n" .
  722. "\r\n" .
  723. "\r\n" .
  724. 'This email was sent using the CakePHP Framework, https://cakephp.org.' .
  725. "\r\n" .
  726. "\r\n" .
  727. "\r\n" .
  728. "--{$boundary}\r\n" .
  729. "Content-Type: text/html; charset=UTF-8\r\n" .
  730. "Content-Transfer-Encoding: 8bit\r\n" .
  731. "\r\n" .
  732. '<!DOCTYPE html';
  733. $this->assertStringStartsWith($expected, $result['message']);
  734. $expected = "</html>\r\n" .
  735. "\r\n" .
  736. "\r\n" .
  737. "\r\n" .
  738. "--{$boundary}--\r\n";
  739. $this->assertStringEndsWith($expected, $result['message']);
  740. }
  741. /**
  742. * testSendRender method for ISO-2022-JP
  743. */
  744. public function testSendRenderJapanese(): void
  745. {
  746. $this->mailer->setTransport('debug');
  747. $this->mailer->setFrom('cake@cakephp.org');
  748. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  749. $this->mailer->setSubject('My title');
  750. $this->mailer->setProfile(['empty']);
  751. $this->mailer->viewBuilder()->setTemplate('default');
  752. $this->mailer->viewBuilder()->setLayout('japanese');
  753. $this->mailer->setCharset('ISO-2022-JP');
  754. $result = $this->mailer->send();
  755. $expected = mb_convert_encoding('CakePHP Framework を使って送信したメールです。 https://cakephp.org.', 'ISO-2022-JP');
  756. $this->assertStringContainsString($expected, $result['message']);
  757. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  758. $this->assertStringContainsString('To: ', $result['headers']);
  759. }
  760. /**
  761. * testSendRenderThemed method
  762. */
  763. public function testSendRenderThemed(): void
  764. {
  765. $this->loadPlugins(['TestTheme']);
  766. $this->mailer->setTransport('debug');
  767. $this->mailer->setFrom('cake@cakephp.org');
  768. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  769. $this->mailer->setSubject('My title');
  770. $this->mailer->setProfile(['empty']);
  771. $this->mailer->viewBuilder()->setTheme('TestTheme');
  772. $this->mailer->viewBuilder()->setTemplate('themed');
  773. $result = $this->mailer->send();
  774. $this->assertStringContainsString('In TestTheme', $result['message']);
  775. $this->assertStringContainsString('/test_theme/img/test.jpg', $result['message']);
  776. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  777. $this->assertStringContainsString('To: ', $result['headers']);
  778. $this->assertStringContainsString('/test_theme/img/test.jpg', $result['message']);
  779. $this->clearPlugins();
  780. }
  781. /**
  782. * testSendRenderWithHTML method and assert line length is kept below the required limit
  783. */
  784. public function testSendRenderWithHTML(): void
  785. {
  786. $this->mailer->setTransport('debug');
  787. $this->mailer->setFrom('cake@cakephp.org');
  788. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  789. $this->mailer->setSubject('My title');
  790. $this->mailer->setProfile(['empty']);
  791. $this->mailer->setEmailFormat('html');
  792. $this->mailer->viewBuilder()->setTemplate('html');
  793. $result = $this->mailer->send();
  794. $this->assertTextContains('<h1>HTML Ipsum Presents</h1>', $result['message']);
  795. $this->assertLineLengths($result['message']);
  796. }
  797. /**
  798. * testSendRenderWithVars method
  799. */
  800. public function testSendRenderWithVars(): void
  801. {
  802. $this->mailer->setTransport('debug');
  803. $this->mailer->setFrom('cake@cakephp.org');
  804. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  805. $this->mailer->setSubject('My title');
  806. $this->mailer->setProfile(['empty']);
  807. $this->mailer->viewBuilder()->setTemplate('custom');
  808. $this->mailer->setViewVars(['value' => 12345]);
  809. $result = $this->mailer->send();
  810. $this->assertStringContainsString('Here is your value: 12345', $result['message']);
  811. }
  812. /**
  813. * testSendRenderWithVars method for ISO-2022-JP
  814. */
  815. public function testSendRenderWithVarsJapanese(): void
  816. {
  817. $this->mailer->setTransport('debug');
  818. $this->mailer->setFrom('cake@cakephp.org');
  819. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  820. $this->mailer->setSubject('My title');
  821. $this->mailer->setProfile(['empty']);
  822. $this->mailer->viewBuilder()->setTemplate('japanese');
  823. $this->mailer->setViewVars(['value' => '日本語の差し込み123']);
  824. $this->mailer->setCharset('ISO-2022-JP');
  825. $result = $this->mailer->send();
  826. $expected = mb_convert_encoding('ここにあなたの設定した値が入ります: 日本語の差し込み123', 'ISO-2022-JP');
  827. $this->assertStringContainsString($expected, $result['message']);
  828. }
  829. /**
  830. * testSendRenderWithHelpers method
  831. */
  832. public function testSendRenderWithHelpers(): void
  833. {
  834. $this->mailer->setTransport('debug');
  835. $timestamp = time();
  836. $this->mailer->setFrom('cake@cakephp.org');
  837. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  838. $this->mailer->setSubject('My title');
  839. $this->mailer->setProfile(['empty']);
  840. $this->mailer->viewBuilder()
  841. ->setTemplate('custom_helper')
  842. ->setLayout('default')
  843. ->setHelpers(['Time']);
  844. $this->mailer->setViewVars(['time' => $timestamp]);
  845. $result = $this->mailer->send();
  846. $dateTime = new DateTime();
  847. $dateTime->setTimestamp($timestamp);
  848. $this->assertStringContainsString('Right now: ' . $dateTime->format($dateTime::ATOM), $result['message']);
  849. $result = $this->mailer->viewBuilder()->getHelpers();
  850. $this->assertEquals(['Time' => []], $result);
  851. }
  852. /**
  853. * testSendRenderWithImage method
  854. */
  855. public function testSendRenderWithImage(): void
  856. {
  857. $this->mailer->setTransport('debug');
  858. $this->mailer->setFrom('cake@cakephp.org');
  859. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  860. $this->mailer->setSubject('My title');
  861. $this->mailer->setProfile(['empty']);
  862. $this->mailer->viewBuilder()->setTemplate('image');
  863. $this->mailer->setEmailFormat('html');
  864. $server = env('SERVER_NAME') ?: 'localhost';
  865. if (env('SERVER_PORT') && env('SERVER_PORT') !== 80) {
  866. $server .= ':' . env('SERVER_PORT');
  867. }
  868. $expected = '<img src="http://' . $server . '/img/image.gif" alt="cool image" width="100" height="100"';
  869. $result = $this->mailer->send();
  870. $this->assertStringContainsString($expected, $result['message']);
  871. }
  872. /**
  873. * testSendRenderPlugin method
  874. */
  875. public function testSendRenderPlugin(): void
  876. {
  877. $this->loadPlugins(['TestPlugin', 'TestPluginTwo', 'TestTheme']);
  878. $this->mailer->setTransport('debug');
  879. $this->mailer->setFrom('cake@cakephp.org');
  880. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  881. $this->mailer->setSubject('My title');
  882. $this->mailer->setProfile(['empty']);
  883. $this->mailer->viewBuilder()
  884. ->setTemplate('TestPlugin.test_plugin_tpl')
  885. ->setLayout('default');
  886. $result = $this->mailer->send();
  887. $this->assertStringContainsString('Into TestPlugin.', $result['message']);
  888. $this->assertStringContainsString('This email was sent using the CakePHP Framework', $result['message']);
  889. $this->mailer->viewBuilder()
  890. ->setTemplate('TestPlugin.test_plugin_tpl')
  891. ->setLayout('TestPlugin.plug_default');
  892. $result = $this->mailer->send();
  893. $this->assertStringContainsString('Into TestPlugin.', $result['message']);
  894. $this->assertStringContainsString('This email was sent using the TestPlugin.', $result['message']);
  895. $this->mailer->viewBuilder()
  896. ->setTemplate('TestPlugin.test_plugin_tpl')
  897. ->setLayout('plug_default');
  898. $result = $this->mailer->send();
  899. $this->assertStringContainsString('Into TestPlugin.', $result['message']);
  900. $this->assertStringContainsString('This email was sent using the TestPlugin.', $result['message']);
  901. $this->mailer->viewBuilder()
  902. ->setTemplate('TestPlugin.test_plugin_tpl')
  903. ->setLayout('TestPluginTwo.default');
  904. $result = $this->mailer->send();
  905. $this->assertStringContainsString('Into TestPlugin.', $result['message']);
  906. $this->assertStringContainsString('This email was sent using TestPluginTwo.', $result['message']);
  907. // test plugin template overridden by theme
  908. $this->mailer->viewBuilder()->setTheme('TestTheme');
  909. $result = $this->mailer->send();
  910. $this->assertStringContainsString('Into TestPlugin. (themed)', $result['message']);
  911. $this->mailer->setViewVars(['value' => 12345]);
  912. $this->mailer->viewBuilder()
  913. ->setTemplate('custom')
  914. ->setLayout('TestPlugin.plug_default');
  915. $result = $this->mailer->send();
  916. $this->assertStringContainsString('Here is your value: 12345', $result['message']);
  917. $this->assertStringContainsString('This email was sent using the TestPlugin.', $result['message']);
  918. $this->clearPlugins();
  919. }
  920. /**
  921. * Test that a MissingTemplateException is thrown
  922. */
  923. public function testMissingTemplateException(): void
  924. {
  925. $this->expectException(MissingTemplateException::class);
  926. $this->mailer->setTransport('debug');
  927. $this->mailer->setFrom('cake@cakephp.org');
  928. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  929. $this->mailer->setSubject('My title');
  930. $this->mailer->viewBuilder()->setTemplate('fooo');
  931. $this->mailer->send();
  932. }
  933. /**
  934. * testSendMultipleMIME method
  935. */
  936. public function testSendMultipleMIME(): void
  937. {
  938. $this->mailer->setTransport('debug');
  939. $this->mailer->setFrom('cake@cakephp.org');
  940. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  941. $this->mailer->setSubject('My title');
  942. $this->mailer->viewBuilder()->setTemplate('custom');
  943. $this->mailer->setProfile([]);
  944. $this->mailer->setViewVars(['value' => 12345]);
  945. $this->mailer->setEmailFormat('both');
  946. $this->mailer->send();
  947. $message = $this->mailer->getBody();
  948. $boundary = $this->mailer->boundary;
  949. $this->assertNotEmpty($boundary);
  950. $this->assertContains('--' . $boundary, $message);
  951. $this->assertContains('--' . $boundary . '--', $message);
  952. $this->mailer->setAttachments(['fake.php' => APP . 'ApplicationWithDefaultRoutes.php']);
  953. $this->mailer->send();
  954. $message = $this->mailer->getBody();
  955. $boundary = $this->mailer->boundary;
  956. $this->assertNotEmpty($boundary);
  957. $this->assertContains('--' . $boundary, $message);
  958. $this->assertContains('--' . $boundary . '--', $message);
  959. $this->assertContains('--alt-' . $boundary, $message);
  960. $this->assertContains('--alt-' . $boundary . '--', $message);
  961. }
  962. /**
  963. * testSendAttachment method
  964. */
  965. public function testSendAttachment(): void
  966. {
  967. $this->mailer->setTransport('debug');
  968. $this->mailer->setFrom('cake@cakephp.org');
  969. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  970. $this->mailer->setSubject('My title');
  971. $this->mailer->setProfile([]);
  972. $this->mailer->setAttachments([APP . 'ApplicationWithDefaultRoutes.php']);
  973. $this->mailer->setBodyText('body');
  974. $result = $this->mailer->send();
  975. $expected = "Content-Disposition: attachment; filename=\"ApplicationWithDefaultRoutes.php\"\r\n" .
  976. "Content-Type: text/x-php\r\n" .
  977. "Content-Transfer-Encoding: base64\r\n";
  978. $this->assertStringContainsString($expected, $result['message']);
  979. $this->mailer->setAttachments(['my.file.txt' => APP . 'ApplicationWithDefaultRoutes.php']);
  980. $this->mailer->setBodyText('body');
  981. $result = $this->mailer->send();
  982. $expected = "Content-Disposition: attachment; filename=\"my.file.txt\"\r\n" .
  983. "Content-Type: text/x-php\r\n" .
  984. "Content-Transfer-Encoding: base64\r\n";
  985. $this->assertStringContainsString($expected, $result['message']);
  986. $this->mailer->setAttachments(['file.txt' => ['file' => APP . 'ApplicationWithDefaultRoutes.php', 'mimetype' => 'text/plain']]);
  987. $this->mailer->setBodyText('body');
  988. $result = $this->mailer->send();
  989. $expected = "Content-Disposition: attachment; filename=\"file.txt\"\r\n" .
  990. "Content-Type: text/plain\r\n" .
  991. "Content-Transfer-Encoding: base64\r\n";
  992. $this->assertStringContainsString($expected, $result['message']);
  993. $this->mailer->setAttachments(['file2.txt' => ['file' => APP . 'ApplicationWithDefaultRoutes.php', 'mimetype' => 'text/plain', 'contentId' => 'a1b1c1']]);
  994. $this->mailer->setBodyText('body');
  995. $result = $this->mailer->send();
  996. $expected = "Content-Disposition: inline; filename=\"file2.txt\"\r\n" .
  997. "Content-Type: text/plain\r\n" .
  998. "Content-Transfer-Encoding: base64\r\n" .
  999. "Content-ID: <a1b1c1>\r\n";
  1000. $this->assertStringContainsString($expected, $result['message']);
  1001. }
  1002. /**
  1003. * testGetBody method
  1004. */
  1005. public function testGetBody(): void
  1006. {
  1007. $this->mailer->setTransport('debug');
  1008. $this->mailer->setFrom('cake@cakephp.org');
  1009. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  1010. $this->mailer->setSubject('My title');
  1011. $this->mailer->setProfile(['empty']);
  1012. $this->mailer->viewBuilder()->setTemplate('default');
  1013. $this->mailer->setEmailFormat('both');
  1014. $this->mailer->send();
  1015. $expected = '<p>This email was sent using the <a href="https://cakephp.org">CakePHP Framework</a></p>';
  1016. $this->assertStringContainsString($expected, $this->mailer->getBodyHtml());
  1017. $expected = 'This email was sent using the CakePHP Framework, https://cakephp.org.';
  1018. $this->assertStringContainsString($expected, $this->mailer->getBodyText());
  1019. $message = $this->mailer->getBody();
  1020. $this->assertContains('Content-Type: text/plain; charset=UTF-8', $message);
  1021. $this->assertContains('Content-Type: text/html; charset=UTF-8', $message);
  1022. // UTF-8 is 8bit
  1023. $this->assertTrue($this->_checkContentTransferEncoding($message, '8bit'));
  1024. $this->mailer->setCharset('ISO-2022-JP');
  1025. $this->mailer->send();
  1026. $message = $this->mailer->getBody();
  1027. $this->assertContains('Content-Type: text/plain; charset=ISO-2022-JP', $message);
  1028. $this->assertContains('Content-Type: text/html; charset=ISO-2022-JP', $message);
  1029. // ISO-2022-JP is 7bit
  1030. $this->assertTrue($this->_checkContentTransferEncoding($message, '7bit'));
  1031. }
  1032. /**
  1033. * testZeroOnlyLinesNotBeingEmptied()
  1034. */
  1035. public function testZeroOnlyLinesNotBeingEmptied(): void
  1036. {
  1037. $message = "Lorem\r\n0\r\n0\r\nipsum";
  1038. $this->mailer->reset();
  1039. $this->mailer->setTransport('debug');
  1040. $this->mailer->setFrom('cake@cakephp.org');
  1041. $this->mailer->setTo('cake@cakephp.org');
  1042. $this->mailer->setSubject('Wordwrap Test');
  1043. $result = $this->mailer->deliver($message);
  1044. $expected = "{$message}\r\n\r\n";
  1045. $this->assertSame($expected, $result['message']);
  1046. }
  1047. /**
  1048. * testReset method
  1049. */
  1050. public function testReset(): void
  1051. {
  1052. $this->mailer->setTo('cake@cakephp.org');
  1053. $this->mailer->viewBuilder()->setTheme('TestTheme');
  1054. $this->mailer->setEmailPattern('/.+@.+\..+/i');
  1055. $this->assertSame(['cake@cakephp.org' => 'cake@cakephp.org'], $this->mailer->getTo());
  1056. $this->mailer->reset();
  1057. $this->assertSame([], $this->mailer->getTo());
  1058. $this->assertNull($this->mailer->viewBuilder()->getTheme());
  1059. $this->assertSame(Message::EMAIL_PATTERN, $this->mailer->getEmailPattern());
  1060. }
  1061. public function testRestore(): void
  1062. {
  1063. $this->expectNotToPerformAssertions();
  1064. $this->mailer->send('dummy');
  1065. }
  1066. /**
  1067. * testSendWithLog method
  1068. */
  1069. public function testSendWithLog(): void
  1070. {
  1071. Log::setConfig('email', [
  1072. 'className' => 'Array',
  1073. ]);
  1074. $this->mailer->setTransport('debug');
  1075. $this->mailer->setTo('me@cakephp.org');
  1076. $this->mailer->setFrom('cake@cakephp.org');
  1077. $this->mailer->setSubject('My title');
  1078. $this->mailer->setProfile(['log' => 'debug']);
  1079. $text = 'Logging This';
  1080. $result = $this->mailer->deliver($text);
  1081. $this->assertNotEmpty($result);
  1082. $messages = Log::engine('email')->read();
  1083. $this->assertCount(1, $messages);
  1084. $this->assertStringContainsString($text, $messages[0]);
  1085. $this->assertStringContainsString('cake@cakephp.org', $messages[0]);
  1086. $this->assertStringContainsString('me@cakephp.org', $messages[0]);
  1087. }
  1088. /**
  1089. * testSendWithLogAndScope method
  1090. */
  1091. public function testSendWithLogAndScope(): void
  1092. {
  1093. Log::setConfig('email', [
  1094. 'className' => 'Array',
  1095. 'scopes' => ['email'],
  1096. ]);
  1097. $this->mailer->setTransport('debug');
  1098. $this->mailer->setTo('me@cakephp.org');
  1099. $this->mailer->setFrom('cake@cakephp.org');
  1100. $this->mailer->setSubject('My title');
  1101. $this->mailer->setProfile(['log' => ['scope' => 'email']]);
  1102. $text = 'Logging This';
  1103. $this->mailer->deliver($text);
  1104. $messages = Log::engine('email')->read();
  1105. $this->assertCount(1, $messages);
  1106. $this->assertStringContainsString($text, $messages[0]);
  1107. $this->assertStringContainsString('cake@cakephp.org', $messages[0]);
  1108. $this->assertStringContainsString('me@cakephp.org', $messages[0]);
  1109. }
  1110. /**
  1111. * test mail logging to cake.mailer scope
  1112. */
  1113. public function testSendWithLogCakeScope(): void
  1114. {
  1115. Log::setConfig('email', [
  1116. 'className' => 'Array',
  1117. 'scopes' => ['cake.mailer'],
  1118. ]);
  1119. $this->mailer->setTransport('debug');
  1120. $this->mailer->setTo('me@cakephp.org');
  1121. $this->mailer->setFrom('cake@cakephp.org');
  1122. $this->mailer->setSubject('My title');
  1123. $this->mailer->setProfile(['log' => true]);
  1124. $text = 'Logging This';
  1125. $this->mailer->deliver($text);
  1126. $messages = Log::engine('email')->read();
  1127. $this->assertCount(1, $messages);
  1128. $this->assertStringContainsString($text, $messages[0]);
  1129. $this->assertStringContainsString('cake@cakephp.org', $messages[0]);
  1130. $this->assertStringContainsString('me@cakephp.org', $messages[0]);
  1131. }
  1132. public function testMissingActionThrowsException(): void
  1133. {
  1134. $this->expectException(MissingActionException::class);
  1135. $this->expectExceptionMessage('Mail Cake\Mailer\Mailer::test() could not be found, or is not accessible.');
  1136. (new Mailer())->send('test');
  1137. }
  1138. public function testDeliver(): void
  1139. {
  1140. $this->mailer->setTransport('debug');
  1141. $this->mailer->setFrom('cake@cakephp.org');
  1142. $this->mailer->setTo(['you@cakephp.org' => 'You']);
  1143. $this->mailer->setSubject('My title');
  1144. $result = $this->mailer->deliver("Here is my body, with multi lines.\nThis is the second line.\r\n\r\nAnd the last.");
  1145. $expected = ['headers', 'message'];
  1146. $this->assertEquals($expected, array_keys($result));
  1147. $expected = "Here is my body, with multi lines.\r\nThis is the second line.\r\n\r\nAnd the last.\r\n\r\n";
  1148. $this->assertSame($expected, $result['message']);
  1149. $this->assertStringContainsString('Date: ', $result['headers']);
  1150. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  1151. $this->assertStringContainsString('To: ', $result['headers']);
  1152. $result = $this->mailer->deliver('Other body');
  1153. $expected = "Other body\r\n\r\n";
  1154. $this->assertSame($expected, $result['message']);
  1155. $this->assertStringContainsString('Message-ID: ', $result['headers']);
  1156. $this->assertStringContainsString('To: ', $result['headers']);
  1157. }
  1158. protected function assertLineLengths(string $message): void
  1159. {
  1160. $lines = explode("\r\n", $message);
  1161. foreach ($lines as $line) {
  1162. $this->assertTrue(
  1163. strlen($line) <= Message::LINE_LENGTH_MUST,
  1164. 'Line length exceeds the max. limit of Message::LINE_LENGTH_MUST'
  1165. );
  1166. }
  1167. }
  1168. /**
  1169. * @param array|string $message
  1170. */
  1171. protected function _checkContentTransferEncoding($message, string $charset): bool
  1172. {
  1173. $boundary = '--' . $this->mailer->getBoundary();
  1174. $result['text'] = false;
  1175. $result['html'] = false;
  1176. $length = count($message);
  1177. for ($i = 0; $i < $length; ++$i) {
  1178. if ($message[$i] === $boundary) {
  1179. $flag = false;
  1180. $type = '';
  1181. while (!preg_match('/^$/', $message[$i])) {
  1182. if (preg_match('/^Content-Type: text\/plain/', $message[$i])) {
  1183. $type = 'text';
  1184. }
  1185. if (preg_match('/^Content-Type: text\/html/', $message[$i])) {
  1186. $type = 'html';
  1187. }
  1188. if ($message[$i] === 'Content-Transfer-Encoding: ' . $charset) {
  1189. $flag = true;
  1190. }
  1191. ++$i;
  1192. }
  1193. $result[$type] = $flag;
  1194. }
  1195. }
  1196. return $result['text'] && $result['html'];
  1197. }
  1198. }