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