Email.php 79 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 2.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Mailer;
  16. use BadMethodCallException;
  17. use Cake\Core\App;
  18. use Cake\Core\Configure;
  19. use Cake\Core\StaticConfigTrait;
  20. use Cake\Filesystem\File;
  21. use Cake\Http\Client\FormDataPart;
  22. use Cake\Log\Log;
  23. use Cake\Utility\Hash;
  24. use Cake\Utility\Security;
  25. use Cake\Utility\Text;
  26. use Cake\View\ViewVarsTrait;
  27. use Closure;
  28. use Exception;
  29. use InvalidArgumentException;
  30. use JsonSerializable;
  31. use LogicException;
  32. use PDO;
  33. use RuntimeException;
  34. use Serializable;
  35. use SimpleXmlElement;
  36. /**
  37. * CakePHP Email class.
  38. *
  39. * This class is used for sending Internet Message Format based
  40. * on the standard outlined in https://www.rfc-editor.org/rfc/rfc2822.txt
  41. *
  42. * ### Configuration
  43. *
  44. * Configuration for Email is managed by Email::config() and Email::configTransport().
  45. * Email::config() can be used to add or read a configuration profile for Email instances.
  46. * Once made configuration profiles can be used to re-use across various email messages your
  47. * application sends.
  48. */
  49. class Email implements JsonSerializable, Serializable
  50. {
  51. use StaticConfigTrait;
  52. use ViewVarsTrait;
  53. /**
  54. * Line length - no should more - RFC 2822 - 2.1.1
  55. *
  56. * @var int
  57. */
  58. const LINE_LENGTH_SHOULD = 78;
  59. /**
  60. * Line length - no must more - RFC 2822 - 2.1.1
  61. *
  62. * @var int
  63. */
  64. const LINE_LENGTH_MUST = 998;
  65. /**
  66. * Type of message - HTML
  67. *
  68. * @var string
  69. */
  70. const MESSAGE_HTML = 'html';
  71. /**
  72. * Type of message - TEXT
  73. *
  74. * @var string
  75. */
  76. const MESSAGE_TEXT = 'text';
  77. /**
  78. * Holds the regex pattern for email validation
  79. *
  80. * @var string
  81. */
  82. const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-._]+)$/ui';
  83. /**
  84. * Recipient of the email
  85. *
  86. * @var array
  87. */
  88. protected $_to = [];
  89. /**
  90. * The mail which the email is sent from
  91. *
  92. * @var array
  93. */
  94. protected $_from = [];
  95. /**
  96. * The sender email
  97. *
  98. * @var array
  99. */
  100. protected $_sender = [];
  101. /**
  102. * The email the recipient will reply to
  103. *
  104. * @var array
  105. */
  106. protected $_replyTo = [];
  107. /**
  108. * The read receipt email
  109. *
  110. * @var array
  111. */
  112. protected $_readReceipt = [];
  113. /**
  114. * The mail that will be used in case of any errors like
  115. * - Remote mailserver down
  116. * - Remote user has exceeded his quota
  117. * - Unknown user
  118. *
  119. * @var array
  120. */
  121. protected $_returnPath = [];
  122. /**
  123. * Carbon Copy
  124. *
  125. * List of email's that should receive a copy of the email.
  126. * The Recipient WILL be able to see this list
  127. *
  128. * @var array
  129. */
  130. protected $_cc = [];
  131. /**
  132. * Blind Carbon Copy
  133. *
  134. * List of email's that should receive a copy of the email.
  135. * The Recipient WILL NOT be able to see this list
  136. *
  137. * @var array
  138. */
  139. protected $_bcc = [];
  140. /**
  141. * Message ID
  142. *
  143. * @var bool|string
  144. */
  145. protected $_messageId = true;
  146. /**
  147. * Domain for messageId generation.
  148. * Needs to be manually set for CLI mailing as env('HTTP_HOST') is empty
  149. *
  150. * @var string
  151. */
  152. protected $_domain;
  153. /**
  154. * The subject of the email
  155. *
  156. * @var string
  157. */
  158. protected $_subject = '';
  159. /**
  160. * Associative array of a user defined headers
  161. * Keys will be prefixed 'X-' as per RFC2822 Section 4.7.5
  162. *
  163. * @var array
  164. */
  165. protected $_headers = [];
  166. /**
  167. * Text message
  168. *
  169. * @var string
  170. */
  171. protected $_textMessage = '';
  172. /**
  173. * Html message
  174. *
  175. * @var string
  176. */
  177. protected $_htmlMessage = '';
  178. /**
  179. * Final message to send
  180. *
  181. * @var array
  182. */
  183. protected $_message = [];
  184. /**
  185. * Available formats to be sent.
  186. *
  187. * @var array
  188. */
  189. protected $_emailFormatAvailable = ['text', 'html', 'both'];
  190. /**
  191. * What format should the email be sent in
  192. *
  193. * @var string
  194. */
  195. protected $_emailFormat = 'text';
  196. /**
  197. * The transport instance to use for sending mail.
  198. *
  199. * @var \Cake\Mailer\AbstractTransport|null
  200. */
  201. protected $_transport;
  202. /**
  203. * Charset the email body is sent in
  204. *
  205. * @var string
  206. */
  207. public $charset = 'utf-8';
  208. /**
  209. * Charset the email header is sent in
  210. * If null, the $charset property will be used as default
  211. *
  212. * @var string|null
  213. */
  214. public $headerCharset;
  215. /**
  216. * The email transfer encoding used.
  217. * If null, the $charset property is used for determined the transfer encoding.
  218. *
  219. * @var string|null
  220. */
  221. protected $transferEncoding;
  222. /**
  223. * Available encoding to be set for transfer.
  224. *
  225. * @var array
  226. */
  227. protected $_transferEncodingAvailable = [
  228. '7bit',
  229. '8bit',
  230. 'base64',
  231. 'binary',
  232. 'quoted-printable'
  233. ];
  234. /**
  235. * The application wide charset, used to encode headers and body
  236. *
  237. * @var string|null
  238. */
  239. protected $_appCharset;
  240. /**
  241. * List of files that should be attached to the email.
  242. *
  243. * Only absolute paths
  244. *
  245. * @var array
  246. */
  247. protected $_attachments = [];
  248. /**
  249. * If set, boundary to use for multipart mime messages
  250. *
  251. * @var string|null
  252. */
  253. protected $_boundary;
  254. /**
  255. * Contains the optional priority of the email.
  256. *
  257. * @var int|null
  258. */
  259. protected $_priority;
  260. /**
  261. * An array mapping url schemes to fully qualified Transport class names
  262. *
  263. * @var array
  264. */
  265. protected static $_dsnClassMap = [
  266. 'debug' => 'Cake\Mailer\Transport\DebugTransport',
  267. 'mail' => 'Cake\Mailer\Transport\MailTransport',
  268. 'smtp' => 'Cake\Mailer\Transport\SmtpTransport',
  269. ];
  270. /**
  271. * Configuration profiles for transports.
  272. *
  273. * @var array
  274. */
  275. protected static $_transportConfig = [];
  276. /**
  277. * A copy of the configuration profile for this
  278. * instance. This copy can be modified with Email::profile().
  279. *
  280. * @var array
  281. */
  282. protected $_profile = [];
  283. /**
  284. * 8Bit character sets
  285. *
  286. * @var array
  287. */
  288. protected $_charset8bit = ['UTF-8', 'SHIFT_JIS'];
  289. /**
  290. * Define Content-Type charset name
  291. *
  292. * @var array
  293. */
  294. protected $_contentTypeCharset = [
  295. 'ISO-2022-JP-MS' => 'ISO-2022-JP'
  296. ];
  297. /**
  298. * Regex for email validation
  299. *
  300. * If null, filter_var() will be used. Use the emailPattern() method
  301. * to set a custom pattern.'
  302. *
  303. * @var string
  304. */
  305. protected $_emailPattern = self::EMAIL_PATTERN;
  306. /**
  307. * Constructor
  308. *
  309. * @param array|string|null $config Array of configs, or string to load configs from email.php
  310. */
  311. public function __construct($config = null)
  312. {
  313. $this->_appCharset = Configure::read('App.encoding');
  314. if ($this->_appCharset !== null) {
  315. $this->charset = $this->_appCharset;
  316. }
  317. $this->_domain = preg_replace('/\:\d+$/', '', env('HTTP_HOST'));
  318. if (empty($this->_domain)) {
  319. $this->_domain = php_uname('n');
  320. }
  321. $this->viewBuilder()
  322. ->setClassName('Cake\View\View')
  323. ->setTemplate('')
  324. ->setLayout('default')
  325. ->setHelpers(['Html']);
  326. if ($config === null) {
  327. $config = static::getConfig('default');
  328. }
  329. if ($config) {
  330. $this->setProfile($config);
  331. }
  332. if (empty($this->headerCharset)) {
  333. $this->headerCharset = $this->charset;
  334. }
  335. }
  336. /**
  337. * Clone ViewBuilder instance when email object is cloned.
  338. *
  339. * @return void
  340. */
  341. public function __clone()
  342. {
  343. $this->_viewBuilder = clone $this->viewBuilder();
  344. }
  345. /**
  346. * Sets "from" address.
  347. *
  348. * @param string|array $email Null to get, String with email,
  349. * Array with email as key, name as value or email as value (without name)
  350. * @param string|null $name Name
  351. * @return $this
  352. * @throws \InvalidArgumentException
  353. */
  354. public function setFrom($email, $name = null)
  355. {
  356. return $this->_setEmailSingle('_from', $email, $name, 'From requires only 1 email address.');
  357. }
  358. /**
  359. * Gets "from" address.
  360. *
  361. * @return array
  362. */
  363. public function getFrom()
  364. {
  365. return $this->_from;
  366. }
  367. /**
  368. * From
  369. *
  370. * @deprecated 3.4.0 Use setFrom()/getFrom() instead.
  371. * @param string|array|null $email Null to get, String with email,
  372. * Array with email as key, name as value or email as value (without name)
  373. * @param string|null $name Name
  374. * @return array|$this
  375. * @throws \InvalidArgumentException
  376. */
  377. public function from($email = null, $name = null)
  378. {
  379. if ($email === null) {
  380. return $this->getFrom();
  381. }
  382. return $this->setFrom($email, $name);
  383. }
  384. /**
  385. * Sets "sender" address.
  386. *
  387. * @param string|array $email String with email,
  388. * Array with email as key, name as value or email as value (without name)
  389. * @param string|null $name Name
  390. * @return $this
  391. * @throws \InvalidArgumentException
  392. */
  393. public function setSender($email, $name = null)
  394. {
  395. return $this->_setEmailSingle('_sender', $email, $name, 'Sender requires only 1 email address.');
  396. }
  397. /**
  398. * Gets "sender" address.
  399. *
  400. * @return array
  401. */
  402. public function getSender()
  403. {
  404. return $this->_sender;
  405. }
  406. /**
  407. * Sender
  408. *
  409. * @deprecated 3.4.0 Use setSender()/getSender() instead.
  410. * @param string|array|null $email Null to get, String with email,
  411. * Array with email as key, name as value or email as value (without name)
  412. * @param string|null $name Name
  413. * @return array|$this
  414. * @throws \InvalidArgumentException
  415. */
  416. public function sender($email = null, $name = null)
  417. {
  418. if ($email === null) {
  419. return $this->getSender();
  420. }
  421. return $this->setSender($email, $name);
  422. }
  423. /**
  424. * Sets "Reply-To" address.
  425. *
  426. * @param string|array $email String with email,
  427. * Array with email as key, name as value or email as value (without name)
  428. * @param string|null $name Name
  429. * @return $this
  430. * @throws \InvalidArgumentException
  431. */
  432. public function setReplyTo($email, $name = null)
  433. {
  434. return $this->_setEmailSingle('_replyTo', $email, $name, 'Reply-To requires only 1 email address.');
  435. }
  436. /**
  437. * Gets "Reply-To" address.
  438. *
  439. * @return array
  440. */
  441. public function getReplyTo()
  442. {
  443. return $this->_replyTo;
  444. }
  445. /**
  446. * Reply-To
  447. *
  448. * @deprecated 3.4.0 Use setReplyTo()/getReplyTo() instead.
  449. * @param string|array|null $email Null to get, String with email,
  450. * Array with email as key, name as value or email as value (without name)
  451. * @param string|null $name Name
  452. * @return array|$this
  453. * @throws \InvalidArgumentException
  454. */
  455. public function replyTo($email = null, $name = null)
  456. {
  457. if ($email === null) {
  458. return $this->getReplyTo();
  459. }
  460. return $this->setReplyTo($email, $name);
  461. }
  462. /**
  463. * Sets Read Receipt (Disposition-Notification-To header).
  464. *
  465. * @param string|array $email String with email,
  466. * Array with email as key, name as value or email as value (without name)
  467. * @param string|null $name Name
  468. * @return $this
  469. * @throws \InvalidArgumentException
  470. */
  471. public function setReadReceipt($email, $name = null)
  472. {
  473. return $this->_setEmailSingle('_readReceipt', $email, $name, 'Disposition-Notification-To requires only 1 email address.');
  474. }
  475. /**
  476. * Gets Read Receipt (Disposition-Notification-To header).
  477. *
  478. * @return array
  479. */
  480. public function getReadReceipt()
  481. {
  482. return $this->_readReceipt;
  483. }
  484. /**
  485. * Read Receipt (Disposition-Notification-To header)
  486. *
  487. * @deprecated 3.4.0 Use setReadReceipt()/getReadReceipt() instead.
  488. * @param string|array|null $email Null to get, String with email,
  489. * Array with email as key, name as value or email as value (without name)
  490. * @param string|null $name Name
  491. * @return array|$this
  492. * @throws \InvalidArgumentException
  493. */
  494. public function readReceipt($email = null, $name = null)
  495. {
  496. if ($email === null) {
  497. return $this->getReadReceipt();
  498. }
  499. return $this->setReadReceipt($email, $name);
  500. }
  501. /**
  502. * Return Path
  503. *
  504. * @param string|array $email String with email,
  505. * Array with email as key, name as value or email as value (without name)
  506. * @param string|null $name Name
  507. * @return $this
  508. * @throws \InvalidArgumentException
  509. */
  510. public function setReturnPath($email, $name = null)
  511. {
  512. return $this->_setEmailSingle('_returnPath', $email, $name, 'Return-Path requires only 1 email address.');
  513. }
  514. /**
  515. * Gets return path.
  516. *
  517. * @return array
  518. */
  519. public function getReturnPath()
  520. {
  521. return $this->_returnPath;
  522. }
  523. /**
  524. * Return Path
  525. *
  526. * @deprecated 3.4.0 Use setReturnPath()/getReturnPath() instead.
  527. * @param string|array|null $email Null to get, String with email,
  528. * Array with email as key, name as value or email as value (without name)
  529. * @param string|null $name Name
  530. * @return array|$this
  531. * @throws \InvalidArgumentException
  532. */
  533. public function returnPath($email = null, $name = null)
  534. {
  535. if ($email === null) {
  536. return $this->getReturnPath();
  537. }
  538. return $this->setReturnPath($email, $name);
  539. }
  540. /**
  541. * Sets "to" address.
  542. *
  543. * @param string|array $email String with email,
  544. * Array with email as key, name as value or email as value (without name)
  545. * @param string|null $name Name
  546. * @return $this
  547. */
  548. public function setTo($email, $name = null)
  549. {
  550. return $this->_setEmail('_to', $email, $name);
  551. }
  552. /**
  553. * Gets "to" address
  554. *
  555. * @return array
  556. */
  557. public function getTo()
  558. {
  559. return $this->_to;
  560. }
  561. /**
  562. * To
  563. *
  564. * @deprecated 3.4.0 Use setTo()/getTo() instead.
  565. * @param string|array|null $email Null to get, String with email,
  566. * Array with email as key, name as value or email as value (without name)
  567. * @param string|null $name Name
  568. * @return array|$this
  569. */
  570. public function to($email = null, $name = null)
  571. {
  572. if ($email === null) {
  573. return $this->getTo();
  574. }
  575. return $this->setTo($email, $name);
  576. }
  577. /**
  578. * Add To
  579. *
  580. * @param string|array $email Null to get, String with email,
  581. * Array with email as key, name as value or email as value (without name)
  582. * @param string|null $name Name
  583. * @return $this
  584. */
  585. public function addTo($email, $name = null)
  586. {
  587. return $this->_addEmail('_to', $email, $name);
  588. }
  589. /**
  590. * Sets "cc" address.
  591. *
  592. * @param string|array $email String with email,
  593. * Array with email as key, name as value or email as value (without name)
  594. * @param string|null $name Name
  595. * @return $this
  596. */
  597. public function setCc($email, $name = null)
  598. {
  599. return $this->_setEmail('_cc', $email, $name);
  600. }
  601. /**
  602. * Gets "cc" address.
  603. *
  604. * @return array
  605. */
  606. public function getCc()
  607. {
  608. return $this->_cc;
  609. }
  610. /**
  611. * Cc
  612. *
  613. * @deprecated 3.4.0 Use setCc()/getCc() instead.
  614. * @param string|array|null $email Null to get, String with email,
  615. * Array with email as key, name as value or email as value (without name)
  616. * @param string|null $name Name
  617. * @return array|$this
  618. */
  619. public function cc($email = null, $name = null)
  620. {
  621. if ($email === null) {
  622. return $this->getCc();
  623. }
  624. return $this->setCc($email, $name);
  625. }
  626. /**
  627. * Add Cc
  628. *
  629. * @param string|array $email Null to get, String with email,
  630. * Array with email as key, name as value or email as value (without name)
  631. * @param string|null $name Name
  632. * @return $this
  633. */
  634. public function addCc($email, $name = null)
  635. {
  636. return $this->_addEmail('_cc', $email, $name);
  637. }
  638. /**
  639. * Sets "bcc" address.
  640. *
  641. * @param string|array $email String with email,
  642. * Array with email as key, name as value or email as value (without name)
  643. * @param string|null $name Name
  644. * @return $this
  645. */
  646. public function setBcc($email, $name = null)
  647. {
  648. return $this->_setEmail('_bcc', $email, $name);
  649. }
  650. /**
  651. * Gets "bcc" address.
  652. *
  653. * @return array
  654. */
  655. public function getBcc()
  656. {
  657. return $this->_bcc;
  658. }
  659. /**
  660. * Bcc
  661. *
  662. * @deprecated 3.4.0 Use setBcc()/getBcc() instead.
  663. * @param string|array|null $email Null to get, String with email,
  664. * Array with email as key, name as value or email as value (without name)
  665. * @param string|null $name Name
  666. * @return array|$this
  667. */
  668. public function bcc($email = null, $name = null)
  669. {
  670. if ($email === null) {
  671. return $this->getBcc();
  672. }
  673. return $this->setBcc($email, $name);
  674. }
  675. /**
  676. * Add Bcc
  677. *
  678. * @param string|array $email Null to get, String with email,
  679. * Array with email as key, name as value or email as value (without name)
  680. * @param string|null $name Name
  681. * @return $this
  682. */
  683. public function addBcc($email, $name = null)
  684. {
  685. return $this->_addEmail('_bcc', $email, $name);
  686. }
  687. /**
  688. * Charset setter.
  689. *
  690. * @param string|null $charset Character set.
  691. * @return $this
  692. */
  693. public function setCharset($charset)
  694. {
  695. $this->charset = $charset;
  696. if (!$this->headerCharset) {
  697. $this->headerCharset = $charset;
  698. }
  699. return $this;
  700. }
  701. /**
  702. * Charset getter.
  703. *
  704. * @return string Charset
  705. */
  706. public function getCharset()
  707. {
  708. return $this->charset;
  709. }
  710. /**
  711. * Charset setter/getter
  712. *
  713. * @deprecated 3.4.0 Use setCharset()/getCharset() instead.
  714. * @param string|null $charset Character set.
  715. * @return string Charset
  716. */
  717. public function charset($charset = null)
  718. {
  719. if ($charset === null) {
  720. return $this->getCharset();
  721. }
  722. $this->setCharset($charset);
  723. return $this->charset;
  724. }
  725. /**
  726. * HeaderCharset setter.
  727. *
  728. * @param string|null $charset Character set.
  729. * @return $this
  730. */
  731. public function setHeaderCharset($charset)
  732. {
  733. $this->headerCharset = $charset;
  734. return $this;
  735. }
  736. /**
  737. * HeaderCharset getter.
  738. *
  739. * @return string Charset
  740. */
  741. public function getHeaderCharset()
  742. {
  743. return $this->headerCharset;
  744. }
  745. /**
  746. * HeaderCharset setter/getter
  747. *
  748. * @deprecated 3.4.0 Use setHeaderCharset()/getHeaderCharset() instead.
  749. * @param string|null $charset Character set.
  750. * @return string Charset
  751. */
  752. public function headerCharset($charset = null)
  753. {
  754. if ($charset === null) {
  755. return $this->getHeaderCharset();
  756. }
  757. $this->setHeaderCharset($charset);
  758. return $this->headerCharset;
  759. }
  760. /**
  761. * TransferEncoding setter.
  762. *
  763. * @param string|null $encoding Encoding set.
  764. * @return $this
  765. */
  766. public function setTransferEncoding($encoding)
  767. {
  768. $encoding = strtolower($encoding);
  769. if (!in_array($encoding, $this->_transferEncodingAvailable)) {
  770. throw new InvalidArgumentException(
  771. sprintf(
  772. 'Transfer encoding not available. Can be : %s.',
  773. implode(', ', $this->_transferEncodingAvailable)
  774. )
  775. );
  776. }
  777. $this->transferEncoding = $encoding;
  778. return $this;
  779. }
  780. /**
  781. * TransferEncoding getter.
  782. *
  783. * @return string|null Encoding
  784. */
  785. public function getTransferEncoding()
  786. {
  787. return $this->transferEncoding;
  788. }
  789. /**
  790. * EmailPattern setter/getter
  791. *
  792. * @param string|null $regex The pattern to use for email address validation,
  793. * null to unset the pattern and make use of filter_var() instead.
  794. * @return $this
  795. */
  796. public function setEmailPattern($regex)
  797. {
  798. $this->_emailPattern = $regex;
  799. return $this;
  800. }
  801. /**
  802. * EmailPattern setter/getter
  803. *
  804. * @return string
  805. */
  806. public function getEmailPattern()
  807. {
  808. return $this->_emailPattern;
  809. }
  810. /**
  811. * EmailPattern setter/getter
  812. *
  813. * @deprecated 3.4.0 Use setEmailPattern()/getEmailPattern() instead.
  814. * @param string|bool|null $regex The pattern to use for email address validation,
  815. * null to unset the pattern and make use of filter_var() instead, false or
  816. * nothing to return the current value
  817. * @return string|$this
  818. */
  819. public function emailPattern($regex = false)
  820. {
  821. if ($regex === false) {
  822. return $this->getEmailPattern();
  823. }
  824. return $this->setEmailPattern($regex);
  825. }
  826. /**
  827. * Set email
  828. *
  829. * @param string $varName Property name
  830. * @param string|array $email String with email,
  831. * Array with email as key, name as value or email as value (without name)
  832. * @param string $name Name
  833. * @return $this
  834. * @throws \InvalidArgumentException
  835. */
  836. protected function _setEmail($varName, $email, $name)
  837. {
  838. if (!is_array($email)) {
  839. $this->_validateEmail($email, $varName);
  840. if ($name === null) {
  841. $name = $email;
  842. }
  843. $this->{$varName} = [$email => $name];
  844. return $this;
  845. }
  846. $list = [];
  847. foreach ($email as $key => $value) {
  848. if (is_int($key)) {
  849. $key = $value;
  850. }
  851. $this->_validateEmail($key, $varName);
  852. $list[$key] = $value;
  853. }
  854. $this->{$varName} = $list;
  855. return $this;
  856. }
  857. /**
  858. * Validate email address
  859. *
  860. * @param string $email Email address to validate
  861. * @param string $context Which property was set
  862. * @return void
  863. * @throws \InvalidArgumentException If email address does not validate
  864. */
  865. protected function _validateEmail($email, $context)
  866. {
  867. if ($this->_emailPattern === null) {
  868. if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
  869. return;
  870. }
  871. } elseif (preg_match($this->_emailPattern, $email)) {
  872. return;
  873. }
  874. $context = ltrim($context, '_');
  875. if ($email == '') {
  876. throw new InvalidArgumentException(sprintf('The email set for "%s" is empty.', $context));
  877. }
  878. throw new InvalidArgumentException(sprintf('Invalid email set for "%s". You passed "%s".', $context, $email));
  879. }
  880. /**
  881. * Set only 1 email
  882. *
  883. * @param string $varName Property name
  884. * @param string|array $email String with email,
  885. * Array with email as key, name as value or email as value (without name)
  886. * @param string $name Name
  887. * @param string $throwMessage Exception message
  888. * @return $this
  889. * @throws \InvalidArgumentException
  890. */
  891. protected function _setEmailSingle($varName, $email, $name, $throwMessage)
  892. {
  893. $current = $this->{$varName};
  894. $this->_setEmail($varName, $email, $name);
  895. if (count($this->{$varName}) !== 1) {
  896. $this->{$varName} = $current;
  897. throw new InvalidArgumentException($throwMessage);
  898. }
  899. return $this;
  900. }
  901. /**
  902. * Add email
  903. *
  904. * @param string $varName Property name
  905. * @param string|array $email String with email,
  906. * Array with email as key, name as value or email as value (without name)
  907. * @param string $name Name
  908. * @return $this
  909. * @throws \InvalidArgumentException
  910. */
  911. protected function _addEmail($varName, $email, $name)
  912. {
  913. if (!is_array($email)) {
  914. $this->_validateEmail($email, $varName);
  915. if ($name === null) {
  916. $name = $email;
  917. }
  918. $this->{$varName}[$email] = $name;
  919. return $this;
  920. }
  921. $list = [];
  922. foreach ($email as $key => $value) {
  923. if (is_int($key)) {
  924. $key = $value;
  925. }
  926. $this->_validateEmail($key, $varName);
  927. $list[$key] = $value;
  928. }
  929. $this->{$varName} = array_merge($this->{$varName}, $list);
  930. return $this;
  931. }
  932. /**
  933. * Sets subject.
  934. *
  935. * @param string $subject Subject string.
  936. * @return $this
  937. */
  938. public function setSubject($subject)
  939. {
  940. $this->_subject = $this->_encode((string)$subject);
  941. return $this;
  942. }
  943. /**
  944. * Gets subject.
  945. *
  946. * @return string
  947. */
  948. public function getSubject()
  949. {
  950. return $this->_subject;
  951. }
  952. /**
  953. * Get/Set Subject.
  954. *
  955. * @deprecated 3.4.0 Use setSubject()/getSubject() instead.
  956. * @param string|null $subject Subject string.
  957. * @return string|$this
  958. */
  959. public function subject($subject = null)
  960. {
  961. if ($subject === null) {
  962. return $this->getSubject();
  963. }
  964. return $this->setSubject($subject);
  965. }
  966. /**
  967. * Get original subject without encoding
  968. *
  969. * @return string Original subject
  970. */
  971. public function getOriginalSubject()
  972. {
  973. return $this->_decode($this->_subject);
  974. }
  975. /**
  976. * Sets headers for the message
  977. *
  978. * @param array $headers Associative array containing headers to be set.
  979. * @return $this
  980. */
  981. public function setHeaders(array $headers)
  982. {
  983. $this->_headers = $headers;
  984. return $this;
  985. }
  986. /**
  987. * Add header for the message
  988. *
  989. * @param array $headers Headers to set.
  990. * @return $this
  991. */
  992. public function addHeaders(array $headers)
  993. {
  994. $this->_headers = array_merge($this->_headers, $headers);
  995. return $this;
  996. }
  997. /**
  998. * Get list of headers
  999. *
  1000. * ### Includes:
  1001. *
  1002. * - `from`
  1003. * - `replyTo`
  1004. * - `readReceipt`
  1005. * - `returnPath`
  1006. * - `to`
  1007. * - `cc`
  1008. * - `bcc`
  1009. * - `subject`
  1010. *
  1011. * @param array $include List of headers.
  1012. * @return array
  1013. */
  1014. public function getHeaders(array $include = [])
  1015. {
  1016. if ($include == array_values($include)) {
  1017. $include = array_fill_keys($include, true);
  1018. }
  1019. $defaults = array_fill_keys(
  1020. [
  1021. 'from', 'sender', 'replyTo', 'readReceipt', 'returnPath',
  1022. 'to', 'cc', 'bcc', 'subject'],
  1023. false
  1024. );
  1025. $include += $defaults;
  1026. $headers = [];
  1027. $relation = [
  1028. 'from' => 'From',
  1029. 'replyTo' => 'Reply-To',
  1030. 'readReceipt' => 'Disposition-Notification-To',
  1031. 'returnPath' => 'Return-Path'
  1032. ];
  1033. foreach ($relation as $var => $header) {
  1034. if ($include[$var]) {
  1035. $var = '_' . $var;
  1036. $headers[$header] = current($this->_formatAddress($this->{$var}));
  1037. }
  1038. }
  1039. if ($include['sender']) {
  1040. if (key($this->_sender) === key($this->_from)) {
  1041. $headers['Sender'] = '';
  1042. } else {
  1043. $headers['Sender'] = current($this->_formatAddress($this->_sender));
  1044. }
  1045. }
  1046. foreach (['to', 'cc', 'bcc'] as $var) {
  1047. if ($include[$var]) {
  1048. $classVar = '_' . $var;
  1049. $headers[ucfirst($var)] = implode(', ', $this->_formatAddress($this->{$classVar}));
  1050. }
  1051. }
  1052. $headers += $this->_headers;
  1053. if (!isset($headers['Date'])) {
  1054. $headers['Date'] = date(DATE_RFC2822);
  1055. }
  1056. if ($this->_messageId !== false) {
  1057. if ($this->_messageId === true) {
  1058. $headers['Message-ID'] = '<' . str_replace('-', '', Text::uuid()) . '@' . $this->_domain . '>';
  1059. } else {
  1060. $headers['Message-ID'] = $this->_messageId;
  1061. }
  1062. }
  1063. if ($this->_priority) {
  1064. $headers['X-Priority'] = $this->_priority;
  1065. }
  1066. if ($include['subject']) {
  1067. $headers['Subject'] = $this->_subject;
  1068. }
  1069. $headers['MIME-Version'] = '1.0';
  1070. if ($this->_attachments) {
  1071. $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"';
  1072. } elseif ($this->_emailFormat === 'both') {
  1073. $headers['Content-Type'] = 'multipart/alternative; boundary="' . $this->_boundary . '"';
  1074. } elseif ($this->_emailFormat === 'text') {
  1075. $headers['Content-Type'] = 'text/plain; charset=' . $this->_getContentTypeCharset();
  1076. } elseif ($this->_emailFormat === 'html') {
  1077. $headers['Content-Type'] = 'text/html; charset=' . $this->_getContentTypeCharset();
  1078. }
  1079. $headers['Content-Transfer-Encoding'] = $this->_getContentTransferEncoding();
  1080. return $headers;
  1081. }
  1082. /**
  1083. * Format addresses
  1084. *
  1085. * If the address contains non alphanumeric/whitespace characters, it will
  1086. * be quoted as characters like `:` and `,` are known to cause issues
  1087. * in address header fields.
  1088. *
  1089. * @param array $address Addresses to format.
  1090. * @return array
  1091. */
  1092. protected function _formatAddress($address)
  1093. {
  1094. $return = [];
  1095. foreach ($address as $email => $alias) {
  1096. if ($email === $alias) {
  1097. $return[] = $email;
  1098. } else {
  1099. $encoded = $this->_encode($alias);
  1100. if ($encoded === $alias && preg_match('/[^a-z0-9 ]/i', $encoded)) {
  1101. $encoded = '"' . str_replace('"', '\"', $encoded) . '"';
  1102. }
  1103. $return[] = sprintf('%s <%s>', $encoded, $email);
  1104. }
  1105. }
  1106. return $return;
  1107. }
  1108. /**
  1109. * Sets template.
  1110. *
  1111. * @param string|null $template Template name or null to not use.
  1112. * @return $this
  1113. */
  1114. public function setTemplate($template)
  1115. {
  1116. $this->viewBuilder()->setTemplate($template ?: '');
  1117. return $this;
  1118. }
  1119. /**
  1120. * Gets template.
  1121. *
  1122. * @return string
  1123. */
  1124. public function getTemplate()
  1125. {
  1126. return $this->viewBuilder()->getTemplate();
  1127. }
  1128. /**
  1129. * Sets layout.
  1130. *
  1131. * @param string|null $layout Layout name or null to not use
  1132. * @return $this
  1133. */
  1134. public function setLayout($layout)
  1135. {
  1136. $this->viewBuilder()->setLayout($layout ?: false);
  1137. return $this;
  1138. }
  1139. /**
  1140. * Gets layout.
  1141. *
  1142. * @return string
  1143. */
  1144. public function getLayout()
  1145. {
  1146. return $this->viewBuilder()->getLayout();
  1147. }
  1148. /**
  1149. * Template and layout
  1150. *
  1151. * @deprecated 3.4.0 Use setTemplate()/getTemplate() and setLayout()/getLayout() instead.
  1152. * @param bool|string $template Template name or null to not use
  1153. * @param bool|string $layout Layout name or null to not use
  1154. * @return array|$this
  1155. */
  1156. public function template($template = false, $layout = false)
  1157. {
  1158. if ($template === false) {
  1159. return [
  1160. 'template' => $this->getTemplate(),
  1161. 'layout' => $this->getLayout()
  1162. ];
  1163. }
  1164. $this->setTemplate($template);
  1165. if ($layout !== false) {
  1166. $this->setLayout($layout);
  1167. }
  1168. return $this;
  1169. }
  1170. /**
  1171. * Sets view class for render.
  1172. *
  1173. * @param string $viewClass View class name.
  1174. * @return $this
  1175. */
  1176. public function setViewRenderer($viewClass)
  1177. {
  1178. $this->viewBuilder()->setClassName($viewClass);
  1179. return $this;
  1180. }
  1181. /**
  1182. * Gets view class for render.
  1183. *
  1184. * @return string
  1185. */
  1186. public function getViewRenderer()
  1187. {
  1188. return $this->viewBuilder()->getClassName();
  1189. }
  1190. /**
  1191. * View class for render
  1192. *
  1193. * @deprecated 3.4.0 Use setViewRenderer()/getViewRenderer() instead.
  1194. * @param string|null $viewClass View class name.
  1195. * @return string|$this
  1196. */
  1197. public function viewRender($viewClass = null)
  1198. {
  1199. if ($viewClass === null) {
  1200. return $this->getViewRenderer();
  1201. }
  1202. $this->setViewRenderer($viewClass);
  1203. return $this;
  1204. }
  1205. /**
  1206. * Sets variables to be set on render.
  1207. *
  1208. * @param array $viewVars Variables to set for view.
  1209. * @return $this
  1210. */
  1211. public function setViewVars($viewVars)
  1212. {
  1213. $this->set((array)$viewVars);
  1214. return $this;
  1215. }
  1216. /**
  1217. * Gets variables to be set on render.
  1218. *
  1219. * @return array
  1220. */
  1221. public function getViewVars()
  1222. {
  1223. return $this->viewVars;
  1224. }
  1225. /**
  1226. * Variables to be set on render
  1227. *
  1228. * @deprecated 3.4.0 Use setViewVars()/getViewVars() instead.
  1229. * @param array|null $viewVars Variables to set for view.
  1230. * @return array|$this
  1231. */
  1232. public function viewVars($viewVars = null)
  1233. {
  1234. if ($viewVars === null) {
  1235. return $this->getViewVars();
  1236. }
  1237. return $this->setViewVars($viewVars);
  1238. }
  1239. /**
  1240. * Sets theme to use when rendering.
  1241. *
  1242. * @param string $theme Theme name.
  1243. * @return $this
  1244. */
  1245. public function setTheme($theme)
  1246. {
  1247. $this->viewBuilder()->setTheme($theme);
  1248. return $this;
  1249. }
  1250. /**
  1251. * Gets theme to use when rendering.
  1252. *
  1253. * @return string
  1254. */
  1255. public function getTheme()
  1256. {
  1257. return $this->viewBuilder()->getTheme();
  1258. }
  1259. /**
  1260. * Theme to use when rendering
  1261. *
  1262. * @deprecated 3.4.0 Use setTheme()/getTheme() instead.
  1263. * @param string|null $theme Theme name.
  1264. * @return string|$this
  1265. */
  1266. public function theme($theme = null)
  1267. {
  1268. if ($theme === null) {
  1269. return $this->getTheme();
  1270. }
  1271. return $this->setTheme($theme);
  1272. }
  1273. /**
  1274. * Sets helpers to be used when rendering.
  1275. *
  1276. * @param array $helpers Helpers list.
  1277. * @return $this
  1278. */
  1279. public function setHelpers(array $helpers)
  1280. {
  1281. $this->viewBuilder()->setHelpers($helpers, false);
  1282. return $this;
  1283. }
  1284. /**
  1285. * Gets helpers to be used when rendering.
  1286. *
  1287. * @return array
  1288. */
  1289. public function getHelpers()
  1290. {
  1291. return $this->viewBuilder()->getHelpers();
  1292. }
  1293. /**
  1294. * Helpers to be used in render
  1295. *
  1296. * @deprecated 3.4.0 Use setHelpers()/getHelpers() instead.
  1297. * @param array|null $helpers Helpers list.
  1298. * @return array|$this
  1299. */
  1300. public function helpers($helpers = null)
  1301. {
  1302. if ($helpers === null) {
  1303. return $this->getHelpers();
  1304. }
  1305. return $this->setHelpers((array)$helpers);
  1306. }
  1307. /**
  1308. * Sets email format.
  1309. *
  1310. * @param string $format Formatting string.
  1311. * @return $this
  1312. * @throws \InvalidArgumentException
  1313. */
  1314. public function setEmailFormat($format)
  1315. {
  1316. if (!in_array($format, $this->_emailFormatAvailable)) {
  1317. throw new InvalidArgumentException('Format not available.');
  1318. }
  1319. $this->_emailFormat = $format;
  1320. return $this;
  1321. }
  1322. /**
  1323. * Gets email format.
  1324. *
  1325. * @return string
  1326. */
  1327. public function getEmailFormat()
  1328. {
  1329. return $this->_emailFormat;
  1330. }
  1331. /**
  1332. * Email format
  1333. *
  1334. * @deprecated 3.4.0 Use setEmailFormat()/getEmailFormat() instead.
  1335. * @param string|null $format Formatting string.
  1336. * @return string|$this
  1337. * @throws \InvalidArgumentException
  1338. */
  1339. public function emailFormat($format = null)
  1340. {
  1341. if ($format === null) {
  1342. return $this->getEmailFormat();
  1343. }
  1344. return $this->setEmailFormat($format);
  1345. }
  1346. /**
  1347. * Sets the transport.
  1348. *
  1349. * When setting the transport you can either use the name
  1350. * of a configured transport or supply a constructed transport.
  1351. *
  1352. * @param string|\Cake\Mailer\AbstractTransport $name Either the name of a configured
  1353. * transport, or a transport instance.
  1354. * @return $this
  1355. * @throws \LogicException When the chosen transport lacks a send method.
  1356. * @throws \InvalidArgumentException When $name is neither a string nor an object.
  1357. */
  1358. public function setTransport($name)
  1359. {
  1360. if (is_string($name)) {
  1361. $transport = $this->_constructTransport($name);
  1362. } elseif (is_object($name)) {
  1363. $transport = $name;
  1364. } else {
  1365. throw new InvalidArgumentException(
  1366. sprintf('The value passed for the "$name" argument must be either a string, or an object, %s given.', gettype($name))
  1367. );
  1368. }
  1369. if (!method_exists($transport, 'send')) {
  1370. throw new LogicException(sprintf('The "%s" do not have send method.', get_class($transport)));
  1371. }
  1372. $this->_transport = $transport;
  1373. return $this;
  1374. }
  1375. /**
  1376. * Gets the transport.
  1377. *
  1378. * @return \Cake\Mailer\AbstractTransport
  1379. */
  1380. public function getTransport()
  1381. {
  1382. return $this->_transport;
  1383. }
  1384. /**
  1385. * Get/set the transport.
  1386. *
  1387. * When setting the transport you can either use the name
  1388. * of a configured transport or supply a constructed transport.
  1389. *
  1390. * @deprecated 3.4.0 Use setTransport()/getTransport() instead.
  1391. * @param string|\Cake\Mailer\AbstractTransport|null $name Either the name of a configured
  1392. * transport, or a transport instance.
  1393. * @return \Cake\Mailer\AbstractTransport|$this
  1394. * @throws \LogicException When the chosen transport lacks a send method.
  1395. * @throws \InvalidArgumentException When $name is neither a string nor an object.
  1396. */
  1397. public function transport($name = null)
  1398. {
  1399. if ($name === null) {
  1400. return $this->getTransport();
  1401. }
  1402. return $this->setTransport($name);
  1403. }
  1404. /**
  1405. * Build a transport instance from configuration data.
  1406. *
  1407. * @param string $name The transport configuration name to build.
  1408. * @return \Cake\Mailer\AbstractTransport
  1409. * @throws \InvalidArgumentException When transport configuration is missing or invalid.
  1410. */
  1411. protected function _constructTransport($name)
  1412. {
  1413. if (!isset(static::$_transportConfig[$name])) {
  1414. throw new InvalidArgumentException(sprintf('Transport config "%s" is missing.', $name));
  1415. }
  1416. if (!isset(static::$_transportConfig[$name]['className'])) {
  1417. throw new InvalidArgumentException(
  1418. sprintf('Transport config "%s" is invalid, the required `className` option is missing', $name)
  1419. );
  1420. }
  1421. $config = static::$_transportConfig[$name];
  1422. if (is_object($config['className'])) {
  1423. return $config['className'];
  1424. }
  1425. $className = App::className($config['className'], 'Mailer/Transport', 'Transport');
  1426. if (!$className) {
  1427. $className = App::className($config['className'], 'Network/Email', 'Transport');
  1428. if ($className) {
  1429. trigger_error(
  1430. 'Transports in "Network/Email" are deprecated, use "Mailer/Transport" instead.',
  1431. E_USER_DEPRECATED
  1432. );
  1433. }
  1434. }
  1435. if (!$className) {
  1436. throw new InvalidArgumentException(sprintf('Transport class "%s" not found.', $config['className']));
  1437. }
  1438. if (!method_exists($className, 'send')) {
  1439. throw new InvalidArgumentException(sprintf('The "%s" does not have a send() method.', $className));
  1440. }
  1441. unset($config['className']);
  1442. return new $className($config);
  1443. }
  1444. /**
  1445. * Sets message ID.
  1446. *
  1447. * @param bool|string $message True to generate a new Message-ID, False to ignore (not send in email), String to set as Message-ID.
  1448. * @return $this
  1449. * @throws \InvalidArgumentException
  1450. */
  1451. public function setMessageId($message)
  1452. {
  1453. if (is_bool($message)) {
  1454. $this->_messageId = $message;
  1455. } else {
  1456. if (!preg_match('/^\<.+@.+\>$/', $message)) {
  1457. throw new InvalidArgumentException('Invalid format to Message-ID. The text should be something like "<uuid@server.com>"');
  1458. }
  1459. $this->_messageId = $message;
  1460. }
  1461. return $this;
  1462. }
  1463. /**
  1464. * Gets message ID.
  1465. *
  1466. * @return bool|string
  1467. */
  1468. public function getMessageId()
  1469. {
  1470. return $this->_messageId;
  1471. }
  1472. /**
  1473. * Message-ID
  1474. *
  1475. * @deprecated 3.4.0 Use setMessageId()/getMessageId() instead.
  1476. * @param bool|string|null $message True to generate a new Message-ID, False to ignore (not send in email), String to set as Message-ID
  1477. * @return bool|string|$this
  1478. * @throws \InvalidArgumentException
  1479. */
  1480. public function messageId($message = null)
  1481. {
  1482. if ($message === null) {
  1483. return $this->getMessageId();
  1484. }
  1485. return $this->setMessageId($message);
  1486. }
  1487. /**
  1488. * Sets domain.
  1489. *
  1490. * Domain as top level (the part after @).
  1491. *
  1492. * @param string $domain Manually set the domain for CLI mailing.
  1493. * @return $this
  1494. */
  1495. public function setDomain($domain)
  1496. {
  1497. $this->_domain = $domain;
  1498. return $this;
  1499. }
  1500. /**
  1501. * Gets domain.
  1502. *
  1503. * @return string
  1504. */
  1505. public function getDomain()
  1506. {
  1507. return $this->_domain;
  1508. }
  1509. /**
  1510. * Domain as top level (the part after @)
  1511. *
  1512. * @deprecated 3.4.0 Use setDomain()/getDomain() instead.
  1513. * @param string|null $domain Manually set the domain for CLI mailing
  1514. * @return string|$this
  1515. */
  1516. public function domain($domain = null)
  1517. {
  1518. if ($domain === null) {
  1519. return $this->getDomain();
  1520. }
  1521. return $this->setDomain($domain);
  1522. }
  1523. /**
  1524. * Add attachments to the email message
  1525. *
  1526. * Attachments can be defined in a few forms depending on how much control you need:
  1527. *
  1528. * Attach a single file:
  1529. *
  1530. * ```
  1531. * $email->attachments('path/to/file');
  1532. * ```
  1533. *
  1534. * Attach a file with a different filename:
  1535. *
  1536. * ```
  1537. * $email->attachments(['custom_name.txt' => 'path/to/file.txt']);
  1538. * ```
  1539. *
  1540. * Attach a file and specify additional properties:
  1541. *
  1542. * ```
  1543. * $email->attachments(['custom_name.png' => [
  1544. * 'file' => 'path/to/file',
  1545. * 'mimetype' => 'image/png',
  1546. * 'contentId' => 'abc123',
  1547. * 'contentDisposition' => false
  1548. * ]
  1549. * ]);
  1550. * ```
  1551. *
  1552. * Attach a file from string and specify additional properties:
  1553. *
  1554. * ```
  1555. * $email->attachments(['custom_name.png' => [
  1556. * 'data' => file_get_contents('path/to/file'),
  1557. * 'mimetype' => 'image/png'
  1558. * ]
  1559. * ]);
  1560. * ```
  1561. *
  1562. * The `contentId` key allows you to specify an inline attachment. In your email text, you
  1563. * can use `<img src="cid:abc123" />` to display the image inline.
  1564. *
  1565. * The `contentDisposition` key allows you to disable the `Content-Disposition` header, this can improve
  1566. * attachment compatibility with outlook email clients.
  1567. *
  1568. * @param string|array $attachments String with the filename or array with filenames
  1569. * @return $this
  1570. * @throws \InvalidArgumentException
  1571. */
  1572. public function setAttachments($attachments)
  1573. {
  1574. $attach = [];
  1575. foreach ((array)$attachments as $name => $fileInfo) {
  1576. if (!is_array($fileInfo)) {
  1577. $fileInfo = ['file' => $fileInfo];
  1578. }
  1579. if (!isset($fileInfo['file'])) {
  1580. if (!isset($fileInfo['data'])) {
  1581. throw new InvalidArgumentException('No file or data specified.');
  1582. }
  1583. if (is_int($name)) {
  1584. throw new InvalidArgumentException('No filename specified.');
  1585. }
  1586. $fileInfo['data'] = chunk_split(base64_encode($fileInfo['data']), 76, "\r\n");
  1587. } else {
  1588. $fileName = $fileInfo['file'];
  1589. $fileInfo['file'] = realpath($fileInfo['file']);
  1590. if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) {
  1591. throw new InvalidArgumentException(sprintf('File not found: "%s"', $fileName));
  1592. }
  1593. if (is_int($name)) {
  1594. $name = basename($fileInfo['file']);
  1595. }
  1596. }
  1597. if (!isset($fileInfo['mimetype']) && function_exists('mime_content_type')) {
  1598. $fileInfo['mimetype'] = mime_content_type($fileInfo['file']);
  1599. }
  1600. if (!isset($fileInfo['mimetype'])) {
  1601. $fileInfo['mimetype'] = 'application/octet-stream';
  1602. }
  1603. $attach[$name] = $fileInfo;
  1604. }
  1605. $this->_attachments = $attach;
  1606. return $this;
  1607. }
  1608. /**
  1609. * Gets attachments to the email message.
  1610. *
  1611. * @return array Array of attachments.
  1612. */
  1613. public function getAttachments()
  1614. {
  1615. return $this->_attachments;
  1616. }
  1617. /**
  1618. * Add attachments to the email message
  1619. *
  1620. * Attachments can be defined in a few forms depending on how much control you need:
  1621. *
  1622. * Attach a single file:
  1623. *
  1624. * ```
  1625. * $email->attachments('path/to/file');
  1626. * ```
  1627. *
  1628. * Attach a file with a different filename:
  1629. *
  1630. * ```
  1631. * $email->attachments(['custom_name.txt' => 'path/to/file.txt']);
  1632. * ```
  1633. *
  1634. * Attach a file and specify additional properties:
  1635. *
  1636. * ```
  1637. * $email->attachments(['custom_name.png' => [
  1638. * 'file' => 'path/to/file',
  1639. * 'mimetype' => 'image/png',
  1640. * 'contentId' => 'abc123',
  1641. * 'contentDisposition' => false
  1642. * ]
  1643. * ]);
  1644. * ```
  1645. *
  1646. * Attach a file from string and specify additional properties:
  1647. *
  1648. * ```
  1649. * $email->attachments(['custom_name.png' => [
  1650. * 'data' => file_get_contents('path/to/file'),
  1651. * 'mimetype' => 'image/png'
  1652. * ]
  1653. * ]);
  1654. * ```
  1655. *
  1656. * The `contentId` key allows you to specify an inline attachment. In your email text, you
  1657. * can use `<img src="cid:abc123" />` to display the image inline.
  1658. *
  1659. * The `contentDisposition` key allows you to disable the `Content-Disposition` header, this can improve
  1660. * attachment compatibility with outlook email clients.
  1661. *
  1662. * @deprecated 3.4.0 Use setAttachments()/getAttachments() instead.
  1663. * @param string|array|null $attachments String with the filename or array with filenames
  1664. * @return array|$this Either the array of attachments when getting or $this when setting.
  1665. * @throws \InvalidArgumentException
  1666. */
  1667. public function attachments($attachments = null)
  1668. {
  1669. if ($attachments === null) {
  1670. return $this->getAttachments();
  1671. }
  1672. return $this->setAttachments($attachments);
  1673. }
  1674. /**
  1675. * Add attachments
  1676. *
  1677. * @param string|array $attachments String with the filename or array with filenames
  1678. * @return $this
  1679. * @throws \InvalidArgumentException
  1680. * @see \Cake\Mailer\Email::attachments()
  1681. */
  1682. public function addAttachments($attachments)
  1683. {
  1684. $current = $this->_attachments;
  1685. $this->setAttachments($attachments);
  1686. $this->_attachments = array_merge($current, $this->_attachments);
  1687. return $this;
  1688. }
  1689. /**
  1690. * Get generated message (used by transport classes)
  1691. *
  1692. * @param string|null $type Use MESSAGE_* constants or null to return the full message as array
  1693. * @return string|array String if type is given, array if type is null
  1694. */
  1695. public function message($type = null)
  1696. {
  1697. switch ($type) {
  1698. case static::MESSAGE_HTML:
  1699. return $this->_htmlMessage;
  1700. case static::MESSAGE_TEXT:
  1701. return $this->_textMessage;
  1702. }
  1703. return $this->_message;
  1704. }
  1705. /**
  1706. * Sets priority.
  1707. *
  1708. * @param int|null $priority 1 (highest) to 5 (lowest)
  1709. * @return $this
  1710. */
  1711. public function setPriority($priority)
  1712. {
  1713. $this->_priority = $priority;
  1714. return $this;
  1715. }
  1716. /**
  1717. * Gets priority.
  1718. *
  1719. * @return int
  1720. */
  1721. public function getPriority()
  1722. {
  1723. return $this->_priority;
  1724. }
  1725. /**
  1726. * Sets transport configuration.
  1727. *
  1728. * Use this method to define transports to use in delivery profiles.
  1729. * Once defined you cannot edit the configurations, and must use
  1730. * Email::dropTransport() to flush the configuration first.
  1731. *
  1732. * When using an array of configuration data a new transport
  1733. * will be constructed for each message sent. When using a Closure, the
  1734. * closure will be evaluated for each message.
  1735. *
  1736. * The `className` is used to define the class to use for a transport.
  1737. * It can either be a short name, or a fully qualified class name
  1738. *
  1739. * @param string|array $key The configuration name to write. Or
  1740. * an array of multiple transports to set.
  1741. * @param array|\Cake\Mailer\AbstractTransport|null $config Either an array of configuration
  1742. * data, or a transport instance. Null when using key as array.
  1743. * @return void
  1744. * @throws \BadMethodCallException When modifying an existing configuration.
  1745. */
  1746. public static function setConfigTransport($key, $config = null)
  1747. {
  1748. if (is_array($key)) {
  1749. foreach ($key as $name => $settings) {
  1750. static::setConfigTransport($name, $settings);
  1751. }
  1752. return;
  1753. }
  1754. if (isset(static::$_transportConfig[$key])) {
  1755. throw new BadMethodCallException(sprintf('Cannot modify an existing config "%s"', $key));
  1756. }
  1757. if (is_object($config)) {
  1758. $config = ['className' => $config];
  1759. }
  1760. if (isset($config['url'])) {
  1761. $parsed = static::parseDsn($config['url']);
  1762. unset($config['url']);
  1763. $config = $parsed + $config;
  1764. }
  1765. static::$_transportConfig[$key] = $config;
  1766. }
  1767. /**
  1768. * Gets current transport configuration.
  1769. *
  1770. * @param string $key The configuration name to read.
  1771. * @return array|null Transport config.
  1772. */
  1773. public static function getConfigTransport($key)
  1774. {
  1775. return isset(static::$_transportConfig[$key]) ? static::$_transportConfig[$key] : null;
  1776. }
  1777. /**
  1778. * Add or read transport configuration.
  1779. *
  1780. * Use this method to define transports to use in delivery profiles.
  1781. * Once defined you cannot edit the configurations, and must use
  1782. * Email::dropTransport() to flush the configuration first.
  1783. *
  1784. * When using an array of configuration data a new transport
  1785. * will be constructed for each message sent. When using a Closure, the
  1786. * closure will be evaluated for each message.
  1787. *
  1788. * The `className` is used to define the class to use for a transport.
  1789. * It can either be a short name, or a fully qualified classname
  1790. *
  1791. * @deprecated 3.4.0 Use setConfigTransport()/getConfigTransport() instead.
  1792. * @param string|array $key The configuration name to read/write. Or
  1793. * an array of multiple transports to set.
  1794. * @param array|\Cake\Mailer\AbstractTransport|null $config Either an array of configuration
  1795. * data, or a transport instance.
  1796. * @return array|null Either null when setting or an array of data when reading.
  1797. * @throws \BadMethodCallException When modifying an existing configuration.
  1798. */
  1799. public static function configTransport($key, $config = null)
  1800. {
  1801. if ($config === null && is_string($key)) {
  1802. return static::getConfigTransport($key);
  1803. }
  1804. if ($config === null && is_array($key)) {
  1805. static::setConfigTransport($key);
  1806. return null;
  1807. }
  1808. static::setConfigTransport($key, $config);
  1809. }
  1810. /**
  1811. * Returns an array containing the named transport configurations
  1812. *
  1813. * @return array Array of configurations.
  1814. */
  1815. public static function configuredTransport()
  1816. {
  1817. return array_keys(static::$_transportConfig);
  1818. }
  1819. /**
  1820. * Delete transport configuration.
  1821. *
  1822. * @param string $key The transport name to remove.
  1823. * @return void
  1824. */
  1825. public static function dropTransport($key)
  1826. {
  1827. unset(static::$_transportConfig[$key]);
  1828. }
  1829. /**
  1830. * Sets the configuration profile to use for this instance.
  1831. *
  1832. * @param string|array $config String with configuration name, or
  1833. * an array with config.
  1834. * @return $this
  1835. */
  1836. public function setProfile($config)
  1837. {
  1838. if (!is_array($config)) {
  1839. $config = (string)$config;
  1840. }
  1841. $this->_applyConfig($config);
  1842. return $this;
  1843. }
  1844. /**
  1845. * Gets the configuration profile to use for this instance.
  1846. *
  1847. * @return string|array
  1848. */
  1849. public function getProfile()
  1850. {
  1851. return $this->_profile;
  1852. }
  1853. /**
  1854. * Get/Set the configuration profile to use for this instance.
  1855. *
  1856. * @deprecated 3.4.0 Use setProfile()/getProfile() instead.
  1857. * @param null|string|array $config String with configuration name, or
  1858. * an array with config or null to return current config.
  1859. * @return string|array|$this
  1860. */
  1861. public function profile($config = null)
  1862. {
  1863. if ($config === null) {
  1864. return $this->getProfile();
  1865. }
  1866. return $this->setProfile($config);
  1867. }
  1868. /**
  1869. * Send an email using the specified content, template and layout
  1870. *
  1871. * @param string|array|null $content String with message or array with messages
  1872. * @return array
  1873. * @throws \BadMethodCallException
  1874. */
  1875. public function send($content = null)
  1876. {
  1877. if (empty($this->_from)) {
  1878. throw new BadMethodCallException('From is not specified.');
  1879. }
  1880. if (empty($this->_to) && empty($this->_cc) && empty($this->_bcc)) {
  1881. throw new BadMethodCallException('You need specify one destination on to, cc or bcc.');
  1882. }
  1883. if (is_array($content)) {
  1884. $content = implode("\n", $content) . "\n";
  1885. }
  1886. $this->_message = $this->_render($this->_wrap($content));
  1887. $transport = $this->getTransport();
  1888. if (!$transport) {
  1889. $msg = 'Cannot send email, transport was not defined. Did you call transport() or define ' .
  1890. ' a transport in the set profile?';
  1891. throw new BadMethodCallException($msg);
  1892. }
  1893. $contents = $transport->send($this);
  1894. $this->_logDelivery($contents);
  1895. return $contents;
  1896. }
  1897. /**
  1898. * Log the email message delivery.
  1899. *
  1900. * @param array $contents The content with 'headers' and 'message' keys.
  1901. * @return void
  1902. */
  1903. protected function _logDelivery($contents)
  1904. {
  1905. if (empty($this->_profile['log'])) {
  1906. return;
  1907. }
  1908. $config = [
  1909. 'level' => 'debug',
  1910. 'scope' => 'email'
  1911. ];
  1912. if ($this->_profile['log'] !== true) {
  1913. if (!is_array($this->_profile['log'])) {
  1914. $this->_profile['log'] = ['level' => $this->_profile['log']];
  1915. }
  1916. $config = $this->_profile['log'] + $config;
  1917. }
  1918. Log::write(
  1919. $config['level'],
  1920. PHP_EOL . $this->flatten($contents['headers']) . PHP_EOL . PHP_EOL . $this->flatten($contents['message']),
  1921. $config['scope']
  1922. );
  1923. }
  1924. /**
  1925. * Converts given value to string
  1926. *
  1927. * @param string|array $value The value to convert
  1928. * @return string
  1929. */
  1930. protected function flatten($value)
  1931. {
  1932. return is_array($value) ? implode(';', $value) : (string)$value;
  1933. }
  1934. /**
  1935. * Static method to fast create an instance of \Cake\Mailer\Email
  1936. *
  1937. * @param string|array|null $to Address to send (see Cake\Mailer\Email::to()). If null, will try to use 'to' from transport config
  1938. * @param string|null $subject String of subject or null to use 'subject' from transport config
  1939. * @param string|array|null $message String with message or array with variables to be used in render
  1940. * @param string|array $transportConfig String to use config from EmailConfig or array with configs
  1941. * @param bool $send Send the email or just return the instance pre-configured
  1942. * @return static Instance of Cake\Mailer\Email
  1943. * @throws \InvalidArgumentException
  1944. */
  1945. public static function deliver($to = null, $subject = null, $message = null, $transportConfig = 'default', $send = true)
  1946. {
  1947. $class = __CLASS__;
  1948. if (is_array($transportConfig) && !isset($transportConfig['transport'])) {
  1949. $transportConfig['transport'] = 'default';
  1950. }
  1951. /* @var \Cake\Mailer\Email $instance */
  1952. $instance = new $class($transportConfig);
  1953. if ($to !== null) {
  1954. $instance->setTo($to);
  1955. }
  1956. if ($subject !== null) {
  1957. $instance->setSubject($subject);
  1958. }
  1959. if (is_array($message)) {
  1960. $instance->setViewVars($message);
  1961. $message = null;
  1962. } elseif ($message === null && array_key_exists('message', $config = $instance->getProfile())) {
  1963. $message = $config['message'];
  1964. }
  1965. if ($send === true) {
  1966. $instance->send($message);
  1967. }
  1968. return $instance;
  1969. }
  1970. /**
  1971. * Apply the config to an instance
  1972. *
  1973. * @param string|array $config Configuration options.
  1974. * @return void
  1975. * @throws \InvalidArgumentException When using a configuration that doesn't exist.
  1976. */
  1977. protected function _applyConfig($config)
  1978. {
  1979. if (is_string($config)) {
  1980. $name = $config;
  1981. $config = static::getConfig($name);
  1982. if (empty($config)) {
  1983. throw new InvalidArgumentException(sprintf('Unknown email configuration "%s".', $name));
  1984. }
  1985. unset($name);
  1986. }
  1987. $this->_profile = array_merge($this->_profile, $config);
  1988. $simpleMethods = [
  1989. 'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath',
  1990. 'cc', 'bcc', 'messageId', 'domain', 'subject', 'attachments',
  1991. 'transport', 'emailFormat', 'emailPattern', 'charset', 'headerCharset'
  1992. ];
  1993. foreach ($simpleMethods as $method) {
  1994. if (isset($config[$method])) {
  1995. $this->$method($config[$method]);
  1996. }
  1997. }
  1998. if (empty($this->headerCharset)) {
  1999. $this->headerCharset = $this->charset;
  2000. }
  2001. if (isset($config['headers'])) {
  2002. $this->setHeaders($config['headers']);
  2003. }
  2004. $viewBuilderMethods = [
  2005. 'template', 'layout', 'theme'
  2006. ];
  2007. foreach ($viewBuilderMethods as $method) {
  2008. if (array_key_exists($method, $config)) {
  2009. $this->viewBuilder()->$method($config[$method]);
  2010. }
  2011. }
  2012. if (array_key_exists('helpers', $config)) {
  2013. $this->viewBuilder()->setHelpers($config['helpers'], false);
  2014. }
  2015. if (array_key_exists('viewRender', $config)) {
  2016. $this->viewBuilder()->setClassName($config['viewRender']);
  2017. }
  2018. if (array_key_exists('viewVars', $config)) {
  2019. $this->set($config['viewVars']);
  2020. }
  2021. }
  2022. /**
  2023. * Reset all the internal variables to be able to send out a new email.
  2024. *
  2025. * @return $this
  2026. */
  2027. public function reset()
  2028. {
  2029. $this->_to = [];
  2030. $this->_from = [];
  2031. $this->_sender = [];
  2032. $this->_replyTo = [];
  2033. $this->_readReceipt = [];
  2034. $this->_returnPath = [];
  2035. $this->_cc = [];
  2036. $this->_bcc = [];
  2037. $this->_messageId = true;
  2038. $this->_subject = '';
  2039. $this->_headers = [];
  2040. $this->_textMessage = '';
  2041. $this->_htmlMessage = '';
  2042. $this->_message = [];
  2043. $this->_emailFormat = 'text';
  2044. $this->_transport = null;
  2045. $this->_priority = null;
  2046. $this->charset = 'utf-8';
  2047. $this->headerCharset = null;
  2048. $this->transferEncoding = null;
  2049. $this->_attachments = [];
  2050. $this->_profile = [];
  2051. $this->_emailPattern = self::EMAIL_PATTERN;
  2052. $this->viewBuilder()->setLayout('default');
  2053. $this->viewBuilder()->setTemplate('');
  2054. $this->viewBuilder()->setClassName('Cake\View\View');
  2055. $this->viewVars = [];
  2056. $this->viewBuilder()->setTheme(false);
  2057. $this->viewBuilder()->setHelpers(['Html'], false);
  2058. return $this;
  2059. }
  2060. /**
  2061. * Encode the specified string using the current charset
  2062. *
  2063. * @param string $text String to encode
  2064. * @return string Encoded string
  2065. */
  2066. protected function _encode($text)
  2067. {
  2068. $restore = mb_internal_encoding();
  2069. mb_internal_encoding($this->_appCharset);
  2070. if (empty($this->headerCharset)) {
  2071. $this->headerCharset = $this->charset;
  2072. }
  2073. $return = mb_encode_mimeheader($text, $this->headerCharset, 'B');
  2074. mb_internal_encoding($restore);
  2075. return $return;
  2076. }
  2077. /**
  2078. * Decode the specified string
  2079. *
  2080. * @param string $text String to decode
  2081. * @return string Decoded string
  2082. */
  2083. protected function _decode($text)
  2084. {
  2085. $restore = mb_internal_encoding();
  2086. mb_internal_encoding($this->_appCharset);
  2087. $return = mb_decode_mimeheader($text);
  2088. mb_internal_encoding($restore);
  2089. return $return;
  2090. }
  2091. /**
  2092. * Translates a string for one charset to another if the App.encoding value
  2093. * differs and the mb_convert_encoding function exists
  2094. *
  2095. * @param string $text The text to be converted
  2096. * @param string $charset the target encoding
  2097. * @return string
  2098. */
  2099. protected function _encodeString($text, $charset)
  2100. {
  2101. if ($this->_appCharset === $charset) {
  2102. return $text;
  2103. }
  2104. return mb_convert_encoding($text, $charset, $this->_appCharset);
  2105. }
  2106. /**
  2107. * Wrap the message to follow the RFC 2822 - 2.1.1
  2108. *
  2109. * @param string $message Message to wrap
  2110. * @param int $wrapLength The line length
  2111. * @return array Wrapped message
  2112. */
  2113. protected function _wrap($message, $wrapLength = Email::LINE_LENGTH_MUST)
  2114. {
  2115. if (strlen($message) === 0) {
  2116. return [''];
  2117. }
  2118. $message = str_replace(["\r\n", "\r"], "\n", $message);
  2119. $lines = explode("\n", $message);
  2120. $formatted = [];
  2121. $cut = ($wrapLength == Email::LINE_LENGTH_MUST);
  2122. foreach ($lines as $line) {
  2123. if (empty($line) && $line !== '0') {
  2124. $formatted[] = '';
  2125. continue;
  2126. }
  2127. if (strlen($line) < $wrapLength) {
  2128. $formatted[] = $line;
  2129. continue;
  2130. }
  2131. if (!preg_match('/<[a-z]+.*>/i', $line)) {
  2132. $formatted = array_merge(
  2133. $formatted,
  2134. explode("\n", wordwrap($line, $wrapLength, "\n", $cut))
  2135. );
  2136. continue;
  2137. }
  2138. $tagOpen = false;
  2139. $tmpLine = $tag = '';
  2140. $tmpLineLength = 0;
  2141. for ($i = 0, $count = strlen($line); $i < $count; $i++) {
  2142. $char = $line[$i];
  2143. if ($tagOpen) {
  2144. $tag .= $char;
  2145. if ($char === '>') {
  2146. $tagLength = strlen($tag);
  2147. if ($tagLength + $tmpLineLength < $wrapLength) {
  2148. $tmpLine .= $tag;
  2149. $tmpLineLength += $tagLength;
  2150. } else {
  2151. if ($tmpLineLength > 0) {
  2152. $formatted = array_merge(
  2153. $formatted,
  2154. explode("\n", wordwrap(trim($tmpLine), $wrapLength, "\n", $cut))
  2155. );
  2156. $tmpLine = '';
  2157. $tmpLineLength = 0;
  2158. }
  2159. if ($tagLength > $wrapLength) {
  2160. $formatted[] = $tag;
  2161. } else {
  2162. $tmpLine = $tag;
  2163. $tmpLineLength = $tagLength;
  2164. }
  2165. }
  2166. $tag = '';
  2167. $tagOpen = false;
  2168. }
  2169. continue;
  2170. }
  2171. if ($char === '<') {
  2172. $tagOpen = true;
  2173. $tag = '<';
  2174. continue;
  2175. }
  2176. if ($char === ' ' && $tmpLineLength >= $wrapLength) {
  2177. $formatted[] = $tmpLine;
  2178. $tmpLineLength = 0;
  2179. continue;
  2180. }
  2181. $tmpLine .= $char;
  2182. $tmpLineLength++;
  2183. if ($tmpLineLength === $wrapLength) {
  2184. $nextChar = $line[$i + 1];
  2185. if ($nextChar === ' ' || $nextChar === '<') {
  2186. $formatted[] = trim($tmpLine);
  2187. $tmpLine = '';
  2188. $tmpLineLength = 0;
  2189. if ($nextChar === ' ') {
  2190. $i++;
  2191. }
  2192. } else {
  2193. $lastSpace = strrpos($tmpLine, ' ');
  2194. if ($lastSpace === false) {
  2195. continue;
  2196. }
  2197. $formatted[] = trim(substr($tmpLine, 0, $lastSpace));
  2198. $tmpLine = substr($tmpLine, $lastSpace + 1);
  2199. $tmpLineLength = strlen($tmpLine);
  2200. }
  2201. }
  2202. }
  2203. if (!empty($tmpLine)) {
  2204. $formatted[] = $tmpLine;
  2205. }
  2206. }
  2207. $formatted[] = '';
  2208. return $formatted;
  2209. }
  2210. /**
  2211. * Create unique boundary identifier
  2212. *
  2213. * @return void
  2214. */
  2215. protected function _createBoundary()
  2216. {
  2217. if ($this->_attachments || $this->_emailFormat === 'both') {
  2218. $this->_boundary = md5(Security::randomBytes(16));
  2219. }
  2220. }
  2221. /**
  2222. * Attach non-embedded files by adding file contents inside boundaries.
  2223. *
  2224. * @param string|null $boundary Boundary to use. If null, will default to $this->_boundary
  2225. * @return array An array of lines to add to the message
  2226. */
  2227. protected function _attachFiles($boundary = null)
  2228. {
  2229. if ($boundary === null) {
  2230. $boundary = $this->_boundary;
  2231. }
  2232. $msg = [];
  2233. foreach ($this->_attachments as $filename => $fileInfo) {
  2234. if (!empty($fileInfo['contentId'])) {
  2235. continue;
  2236. }
  2237. $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
  2238. $hasDisposition = (
  2239. !isset($fileInfo['contentDisposition']) ||
  2240. $fileInfo['contentDisposition']
  2241. );
  2242. $part = new FormDataPart(false, $data, false);
  2243. if ($hasDisposition) {
  2244. $part->disposition('attachment');
  2245. $part->filename($filename);
  2246. }
  2247. $part->transferEncoding('base64');
  2248. $part->type($fileInfo['mimetype']);
  2249. $msg[] = '--' . $boundary;
  2250. $msg[] = (string)$part;
  2251. $msg[] = '';
  2252. }
  2253. return $msg;
  2254. }
  2255. /**
  2256. * Read the file contents and return a base64 version of the file contents.
  2257. *
  2258. * @param string $path The absolute path to the file to read.
  2259. * @return string File contents in base64 encoding
  2260. */
  2261. protected function _readFile($path)
  2262. {
  2263. $File = new File($path);
  2264. return chunk_split(base64_encode($File->read()));
  2265. }
  2266. /**
  2267. * Attach inline/embedded files to the message.
  2268. *
  2269. * @param string|null $boundary Boundary to use. If null, will default to $this->_boundary
  2270. * @return array An array of lines to add to the message
  2271. */
  2272. protected function _attachInlineFiles($boundary = null)
  2273. {
  2274. if ($boundary === null) {
  2275. $boundary = $this->_boundary;
  2276. }
  2277. $msg = [];
  2278. foreach ($this->_attachments as $filename => $fileInfo) {
  2279. if (empty($fileInfo['contentId'])) {
  2280. continue;
  2281. }
  2282. $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']);
  2283. $msg[] = '--' . $boundary;
  2284. $part = new FormDataPart(false, $data, 'inline');
  2285. $part->type($fileInfo['mimetype']);
  2286. $part->transferEncoding('base64');
  2287. $part->contentId($fileInfo['contentId']);
  2288. $part->filename($filename);
  2289. $msg[] = (string)$part;
  2290. $msg[] = '';
  2291. }
  2292. return $msg;
  2293. }
  2294. /**
  2295. * Render the body of the email.
  2296. *
  2297. * @param array $content Content to render
  2298. * @return array Email body ready to be sent
  2299. */
  2300. protected function _render($content)
  2301. {
  2302. $this->_textMessage = $this->_htmlMessage = '';
  2303. $content = implode("\n", $content);
  2304. $rendered = $this->_renderTemplates($content);
  2305. $this->_createBoundary();
  2306. $msg = [];
  2307. $contentIds = array_filter((array)Hash::extract($this->_attachments, '{s}.contentId'));
  2308. $hasInlineAttachments = count($contentIds) > 0;
  2309. $hasAttachments = !empty($this->_attachments);
  2310. $hasMultipleTypes = count($rendered) > 1;
  2311. $multiPart = ($hasAttachments || $hasMultipleTypes);
  2312. $boundary = $relBoundary = $textBoundary = $this->_boundary;
  2313. if ($hasInlineAttachments) {
  2314. $msg[] = '--' . $boundary;
  2315. $msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"';
  2316. $msg[] = '';
  2317. $relBoundary = $textBoundary = 'rel-' . $boundary;
  2318. }
  2319. if ($hasMultipleTypes && $hasAttachments) {
  2320. $msg[] = '--' . $relBoundary;
  2321. $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"';
  2322. $msg[] = '';
  2323. $textBoundary = 'alt-' . $boundary;
  2324. }
  2325. if (isset($rendered['text'])) {
  2326. if ($multiPart) {
  2327. $msg[] = '--' . $textBoundary;
  2328. $msg[] = 'Content-Type: text/plain; charset=' . $this->_getContentTypeCharset();
  2329. $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
  2330. $msg[] = '';
  2331. }
  2332. $this->_textMessage = $rendered['text'];
  2333. $content = explode("\n", $this->_textMessage);
  2334. $msg = array_merge($msg, $content);
  2335. $msg[] = '';
  2336. }
  2337. if (isset($rendered['html'])) {
  2338. if ($multiPart) {
  2339. $msg[] = '--' . $textBoundary;
  2340. $msg[] = 'Content-Type: text/html; charset=' . $this->_getContentTypeCharset();
  2341. $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding();
  2342. $msg[] = '';
  2343. }
  2344. $this->_htmlMessage = $rendered['html'];
  2345. $content = explode("\n", $this->_htmlMessage);
  2346. $msg = array_merge($msg, $content);
  2347. $msg[] = '';
  2348. }
  2349. if ($textBoundary !== $relBoundary) {
  2350. $msg[] = '--' . $textBoundary . '--';
  2351. $msg[] = '';
  2352. }
  2353. if ($hasInlineAttachments) {
  2354. $attachments = $this->_attachInlineFiles($relBoundary);
  2355. $msg = array_merge($msg, $attachments);
  2356. $msg[] = '';
  2357. $msg[] = '--' . $relBoundary . '--';
  2358. $msg[] = '';
  2359. }
  2360. if ($hasAttachments) {
  2361. $attachments = $this->_attachFiles($boundary);
  2362. $msg = array_merge($msg, $attachments);
  2363. }
  2364. if ($hasAttachments || $hasMultipleTypes) {
  2365. $msg[] = '';
  2366. $msg[] = '--' . $boundary . '--';
  2367. $msg[] = '';
  2368. }
  2369. return $msg;
  2370. }
  2371. /**
  2372. * Gets the text body types that are in this email message
  2373. *
  2374. * @return array Array of types. Valid types are 'text' and 'html'
  2375. */
  2376. protected function _getTypes()
  2377. {
  2378. $types = [$this->_emailFormat];
  2379. if ($this->_emailFormat === 'both') {
  2380. $types = ['html', 'text'];
  2381. }
  2382. return $types;
  2383. }
  2384. /**
  2385. * Build and set all the view properties needed to render the templated emails.
  2386. * If there is no template set, the $content will be returned in a hash
  2387. * of the text content types for the email.
  2388. *
  2389. * @param string $content The content passed in from send() in most cases.
  2390. * @return array The rendered content with html and text keys.
  2391. */
  2392. protected function _renderTemplates($content)
  2393. {
  2394. $types = $this->_getTypes();
  2395. $rendered = [];
  2396. $template = $this->viewBuilder()->getTemplate();
  2397. if (empty($template)) {
  2398. foreach ($types as $type) {
  2399. $rendered[$type] = $this->_encodeString($content, $this->charset);
  2400. }
  2401. return $rendered;
  2402. }
  2403. $View = $this->createView();
  2404. list($templatePlugin) = pluginSplit($View->getTemplate());
  2405. list($layoutPlugin) = pluginSplit($View->getLayout());
  2406. if ($templatePlugin) {
  2407. $View->plugin = $templatePlugin;
  2408. } elseif ($layoutPlugin) {
  2409. $View->plugin = $layoutPlugin;
  2410. }
  2411. if ($View->get('content') === null) {
  2412. $View->set('content', $content);
  2413. }
  2414. foreach ($types as $type) {
  2415. $View->hasRendered = false;
  2416. $View->setTemplatePath('Email' . DIRECTORY_SEPARATOR . $type);
  2417. $View->setLayoutPath('Email' . DIRECTORY_SEPARATOR . $type);
  2418. $render = $View->render();
  2419. $render = str_replace(["\r\n", "\r"], "\n", $render);
  2420. $rendered[$type] = $this->_encodeString($render, $this->charset);
  2421. }
  2422. foreach ($rendered as $type => $content) {
  2423. $rendered[$type] = $this->_wrap($content);
  2424. $rendered[$type] = implode("\n", $rendered[$type]);
  2425. $rendered[$type] = rtrim($rendered[$type], "\n");
  2426. }
  2427. return $rendered;
  2428. }
  2429. /**
  2430. * Return the Content-Transfer Encoding value based
  2431. * on the set transferEncoding or set charset.
  2432. *
  2433. * @return string
  2434. */
  2435. protected function _getContentTransferEncoding()
  2436. {
  2437. if ($this->transferEncoding) {
  2438. return $this->transferEncoding;
  2439. }
  2440. $charset = strtoupper($this->charset);
  2441. if (in_array($charset, $this->_charset8bit)) {
  2442. return '8bit';
  2443. }
  2444. return '7bit';
  2445. }
  2446. /**
  2447. * Return charset value for Content-Type.
  2448. *
  2449. * Checks fallback/compatibility types which include workarounds
  2450. * for legacy japanese character sets.
  2451. *
  2452. * @return string
  2453. */
  2454. protected function _getContentTypeCharset()
  2455. {
  2456. $charset = strtoupper($this->charset);
  2457. if (array_key_exists($charset, $this->_contentTypeCharset)) {
  2458. return strtoupper($this->_contentTypeCharset[$charset]);
  2459. }
  2460. return strtoupper($this->charset);
  2461. }
  2462. /**
  2463. * Serializes the email object to a value that can be natively serialized and re-used
  2464. * to clone this email instance.
  2465. *
  2466. * It has certain limitations for viewVars that are good to know:
  2467. *
  2468. * - ORM\Query executed and stored as resultset
  2469. * - SimpleXmlElements stored as associative array
  2470. * - Exceptions stored as strings
  2471. * - Resources, \Closure and \PDO are not supported.
  2472. *
  2473. * @return array Serializable array of configuration properties.
  2474. * @throws \Exception When a view var object can not be properly serialized.
  2475. */
  2476. public function jsonSerialize()
  2477. {
  2478. $properties = [
  2479. '_to', '_from', '_sender', '_replyTo', '_cc', '_bcc', '_subject',
  2480. '_returnPath', '_readReceipt', '_emailFormat', '_emailPattern', '_domain',
  2481. '_attachments', '_messageId', '_headers', '_appCharset', 'viewVars', 'charset', 'headerCharset'
  2482. ];
  2483. $array = ['viewConfig' => $this->viewBuilder()->jsonSerialize()];
  2484. foreach ($properties as $property) {
  2485. $array[$property] = $this->{$property};
  2486. }
  2487. array_walk($array['_attachments'], function (&$item, $key) {
  2488. if (!empty($item['file'])) {
  2489. $item['data'] = $this->_readFile($item['file']);
  2490. unset($item['file']);
  2491. }
  2492. });
  2493. array_walk_recursive($array['viewVars'], [$this, '_checkViewVars']);
  2494. return array_filter($array, function ($i) {
  2495. return !is_array($i) && strlen($i) || !empty($i);
  2496. });
  2497. }
  2498. /**
  2499. * Iterates through hash to clean up and normalize.
  2500. *
  2501. * @param mixed $item Reference to the view var value.
  2502. * @param string $key View var key.
  2503. * @return void
  2504. */
  2505. protected function _checkViewVars(&$item, $key)
  2506. {
  2507. if ($item instanceof Exception) {
  2508. $item = (string)$item;
  2509. }
  2510. if (is_resource($item) ||
  2511. $item instanceof Closure ||
  2512. $item instanceof PDO
  2513. ) {
  2514. throw new RuntimeException(sprintf(
  2515. 'Failed serializing the `%s` %s in the `%s` view var',
  2516. is_resource($item) ? get_resource_type($item) : get_class($item),
  2517. is_resource($item) ? 'resource' : 'object',
  2518. $key
  2519. ));
  2520. }
  2521. }
  2522. /**
  2523. * Configures an email instance object from serialized config.
  2524. *
  2525. * @param array $config Email configuration array.
  2526. * @return $this Configured email instance.
  2527. */
  2528. public function createFromArray($config)
  2529. {
  2530. if (isset($config['viewConfig'])) {
  2531. $this->viewBuilder()->createFromArray($config['viewConfig']);
  2532. unset($config['viewConfig']);
  2533. }
  2534. foreach ($config as $property => $value) {
  2535. $this->{$property} = $value;
  2536. }
  2537. return $this;
  2538. }
  2539. /**
  2540. * Serializes the Email object.
  2541. *
  2542. * @return string
  2543. */
  2544. public function serialize()
  2545. {
  2546. $array = $this->jsonSerialize();
  2547. array_walk_recursive($array, function (&$item, $key) {
  2548. if ($item instanceof SimpleXmlElement) {
  2549. $item = json_decode(json_encode((array)$item), true);
  2550. }
  2551. });
  2552. return serialize($array);
  2553. }
  2554. /**
  2555. * Unserializes the Email object.
  2556. *
  2557. * @param string $data Serialized string.
  2558. * @return static Configured email instance.
  2559. */
  2560. public function unserialize($data)
  2561. {
  2562. return $this->createFromArray(unserialize($data));
  2563. }
  2564. }