500608e590326717c32b5363d627206608a9fcce
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Mail / SwiftMailerAdapter.php
1 <?php
2 namespace TYPO3\CMS\Core\Mail;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2013 Jigal van Hemert <jigal@xs4all.nl>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Hook subscriber for using Swift Mailer with the t3lib_utility_mail function
31 *
32 * @author Jigal van Hemert <jigal@xs4all.nl>
33 */
34 class SwiftMailerAdapter implements \TYPO3\CMS\Core\Mail\MailerAdapterInterface {
35
36 /**
37 * @var $mailer \TYPO3\CMS\Core\Mail\Mailer
38 */
39 protected $mailer;
40
41 /**
42 * @var $message Swift_Message
43 */
44 protected $message;
45
46 /**
47 * @var $messageHeaders Swift_Mime_HeaderSet
48 */
49 protected $messageHeaders;
50
51 /**
52 * @var string
53 */
54 protected $boundary = '';
55
56 /**
57 * Constructor
58 *
59 * @return void
60 */
61 public function __construct() {
62 // create mailer object
63 $this->mailer = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\Mailer');
64 // create message object
65 $this->message = \Swift_Message::newInstance();
66 }
67
68 /**
69 * Parses parts of the mail message and sends it with the Swift Mailer functions
70 *
71 * @param string $to Email address to send the message to
72 * @param string $subject Subject of mail message
73 * @param string $messageBody Raw body (may be multipart)
74 * @param array $additionalHeaders Additional mail headers
75 * @param array $additionalParameters Extra parameters for the mail() command
76 * @param bool $fakeSending If set fake sending a mail
77 * @throws \TYPO3\CMS\Core\Exception
78 * @return bool
79 */
80 public function mail($to, $subject, $messageBody, $additionalHeaders = NULL, $additionalParameters = NULL, $fakeSending = FALSE) {
81 // report success for fake sending
82 if ($fakeSending === TRUE) {
83 return TRUE;
84 }
85 $this->message->setSubject($subject);
86 // handle recipients
87 $toAddresses = $this->parseAddresses($to);
88 $this->message->setTo($toAddresses);
89 // handle additional headers
90 $headers = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(LF, $additionalHeaders, TRUE);
91 $this->messageHeaders = $this->message->getHeaders();
92 foreach ($headers as $header) {
93 list($headerName, $headerValue) = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(':', $header, FALSE, 2);
94 $this->setHeader($headerName, $headerValue);
95 }
96 // handle additional parameters (force return path)
97 if (preg_match('/-f\\s*(\\S*?)/', $additionalParameters, $matches)) {
98 $this->message->setReturnPath($this->unescapeShellArguments($matches[1]));
99 }
100 // handle from:
101 $this->fixSender();
102 // handle message body
103 $this->setBody($messageBody);
104 // send mail
105 $result = $this->mailer->send($this->message);
106 // report success/failure
107 return (bool) $result;
108 }
109
110 /**
111 * Tries to undo the action by escapeshellarg()
112 *
113 * @param string $escapedString String escaped by escapeshellarg()
114 * @return string String with escapeshellarg() action undone as best as possible
115 */
116 protected function unescapeShellArguments($escapedString) {
117 if (TYPO3_OS === 'WIN') {
118 // on Windows double quotes are used and % signs are replaced by spaces
119 if (preg_match('/^"([^"]*)"$/', trim($escapedString), $matches)) {
120 $result = str_replace('\\"', '"', $matches[1]);
121 }
122 } else {
123 // on Unix-like systems single quotes are escaped
124 if (preg_match('/^\'([^' . preg_quote('\'') . ']*)\'$/', trim($escapedString), $matches)) {
125 $result = str_replace('\\\'', '\'', $matches[1]);
126 }
127 }
128 return $result;
129 }
130
131 /**
132 * Handles setting and replacing of mail headers
133 *
134 * @param string $headerName Name of header
135 * @param string $headerValue Value of header
136 * @return void
137 */
138 protected function setHeader($headerName, $headerValue) {
139 // check for boundary in headers
140 if (preg_match('/^boundary="(.*)"$/', $headerName, $matches) > 0) {
141 $this->boundary = $matches[1];
142 return;
143 }
144 // process other, real headers
145 if ($this->messageHeaders->has($headerName)) {
146 $header = $this->messageHeaders->get($headerName);
147 $headerType = $header->getFieldType();
148 switch ($headerType) {
149 case \Swift_Mime_Header::TYPE_TEXT:
150 $header->setValue($headerValue);
151 break;
152 case \Swift_Mime_Header::TYPE_PARAMETERIZED:
153 $header->setValue(rtrim($headerValue, ';'));
154 break;
155 case \Swift_Mime_Header::TYPE_MAILBOX:
156 $addressList = $this->parseAddresses($headerValue);
157 if (count($addressList) > 0) {
158 $header->setNameAddresses($addressList);
159 }
160 break;
161 case \Swift_Mime_Header::TYPE_DATE:
162 $header->setTimeStamp(strtotime($headerValue));
163 break;
164 case \Swift_Mime_Header::TYPE_ID:
165 // remove '<' and '>' from ID headers
166 $header->setId(trim($headerValue, '<>'));
167 break;
168 case \Swift_Mime_Header::TYPE_PATH:
169 $header->setAddress($headerValue);
170 break;
171 }
172 } else {
173 switch ($headerName) {
174 case 'From':
175
176 case 'To':
177
178 case 'Cc':
179
180 case 'Bcc':
181
182 case 'Reply-To':
183
184 case 'Sender':
185 $addressList = $this->parseAddresses($headerValue);
186 if (count($addressList) > 0) {
187 $this->messageHeaders->addMailboxHeader($headerName, $addressList);
188 }
189 break;
190 case 'Date':
191 $this->messageHeaders->addDateHeader($headerName, strtotime($headerValue));
192 break;
193 case 'Message-ID':
194 // remove '<' and '>' from ID headers
195 $this->messageHeaders->addIdHeader($headerName, trim($headerValue, '<>'));
196 case 'Return-Path':
197 $this->messageHeaders->addPathHeader($headerName, $headerValue);
198 break;
199 case 'Content-Type':
200
201 case 'Content-Disposition':
202 $this->messageHeaders->addParameterizedHeader($headerName, rtrim($headerValue, ';'));
203 break;
204 default:
205 $this->messageHeaders->addTextheader($headerName, $headerValue);
206 break;
207 }
208 }
209 }
210
211 /**
212 * Sets body of mail message. Handles multi-part and single part messages. Encoded body parts are decoded prior to adding
213 * them to the message object.
214 *
215 * @param string $body Raw body, may be multi-part
216 * @return void
217 */
218 protected function setBody($body) {
219 if ($this->boundary) {
220 // handle multi-part
221 $bodyParts = preg_split('/--' . preg_quote($this->boundary) . '(--)?/m', $body, NULL, PREG_SPLIT_NO_EMPTY);
222 foreach ($bodyParts as $bodyPart) {
223 // skip empty parts
224 if (trim($bodyPart) == '') {
225 continue;
226 }
227 // keep leading white space when exploding the text
228 $lines = explode(LF, $bodyPart);
229 // set defaults for this part
230 $encoding = '';
231 $charset = 'utf-8';
232 $contentType = 'text/plain';
233 // skip intro messages
234 if (trim($lines[0]) == 'This is a multi-part message in MIME format.') {
235 continue;
236 }
237 // first line is empty leftover from splitting
238 array_shift($lines);
239 while (count($lines) > 0) {
240 $line = array_shift($lines);
241 if (preg_match('/^content-type:(.*);( charset=(.*))?$/i', $line, $matches)) {
242 $contentType = trim($matches[1]);
243 if ($matches[2]) {
244 $charset = trim($matches[3]);
245 }
246 } elseif (preg_match('/^content-transfer-encoding:(.*)$/i', $line, $matches)) {
247 $encoding = trim($matches[1]);
248 } elseif (strlen(trim($line)) == 0) {
249 // empty line before actual content of this part
250 break;
251 }
252 }
253 // use rest of part as body, but reverse encoding first
254 $bodyPart = $this->decode(implode(LF, $lines), $encoding);
255 $this->message->addPart($bodyPart, $contentType, $charset);
256 }
257 } else {
258 // Handle single body
259 // The headers have already been set, so use header information
260 $contentType = $this->message->getContentType();
261 $charset = $this->message->getCharset();
262 $encoding = $this->message->getEncoder()->getName();
263 // reverse encoding and set body
264 $rawBody = $this->decode($body, $encoding);
265 $this->message->setBody($rawBody, $contentType, $charset);
266 }
267 }
268
269 /**
270 * Reverts encoding of body text
271 *
272 * @param string $text Body text to be decoded
273 * @param string $encoding Encoding type to be reverted
274 * @return string Decoded message body
275 */
276 protected function decode($text, $encoding) {
277 $result = $text;
278 switch ($encoding) {
279 case 'quoted-printable':
280 $result = quoted_printable_decode($text);
281 break;
282 case 'base64':
283 $result = base64_decode($text);
284 break;
285 }
286 return $result;
287 }
288
289 /**
290 * Parses mailbox headers and turns them into an array.
291 *
292 * Mailbox headers are a comma separated list of 'name <email@example.org' combinations or plain email addresses (or a mix
293 * of these).
294 * The resulting array has key-value pairs where the key is either a number (no display name in the mailbox header) and the
295 * value is the email address, or the key is the email address and the value is the display name.
296 *
297 * @param string $rawAddresses Comma separated list of email addresses (optionally with display name)
298 * @return array Parsed list of addresses.
299 */
300 protected function parseAddresses($rawAddresses = '') {
301 /** @var $addressParser \TYPO3\CMS\Core\Mail\Rfc822AddressesParser */
302 $addressParser = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\Rfc822AddressesParser', $rawAddresses);
303 $addresses = $addressParser->parseAddressList();
304 $addressList = array();
305 foreach ($addresses as $address) {
306 if ($address->personal) {
307 // item with name found ( name <email@example.org> )
308 $addressList[$address->mailbox . '@' . $address->host] = $address->personal;
309 } else {
310 // item without name found ( email@example.org )
311 $addressList[] = $address->mailbox . '@' . $address->host;
312 }
313 }
314 return $addressList;
315 }
316
317 /**
318 * Makes sure there is a correct sender set.
319 *
320 * If there is no from header the returnpath will be used. If that also fails a fake address will be used to make sure
321 * Swift Mailer will be able to send the message. Some SMTP server will not accept mail messages without a valid sender.
322 *
323 * @return void
324 */
325 protected function fixSender() {
326 $from = $this->message->getFrom();
327 if (count($from) > 0) {
328 reset($from);
329 list($fromAddress, $fromName) = each($from);
330 } else {
331 $fromAddress = $this->message->getReturnPath();
332 $fromName = $fromAddress;
333 }
334 if (strlen($fromAddress) == 0) {
335 $fromAddress = 'no-reply@example.org';
336 $fromName = 'TYPO3 CMS';
337 }
338 $this->message->setFrom(array($fromAddress => $fromName));
339 }
340
341 }
342
343
344 ?>