TYPO3 API  SVNRelease
class.t3lib_mail_swiftmaileradapter.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2010-2011 Jigal van Hemert <jigal@xs4all.nl>
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 
00028 /**
00029  * Hook subscriber for using Swift Mailer with the t3lib_utility_mail function
00030  *
00031  * $Id$
00032  *
00033  * @author  Jigal van Hemert <jigal@xs4all.nl>
00034  * @package TYPO3
00035  * @subpackage t3lib
00036  */
00037 class t3lib_mail_SwiftMailerAdapter implements t3lib_mail_MailerAdapter {
00038 
00039     /** @var $mailer t3lib_mail_Mailer */
00040     protected $mailer;
00041 
00042     /** @var $message Swift_Message */
00043     protected $message;
00044 
00045     /** @var $messageHeaders Swift_Mime_HeaderSet */
00046     protected $messageHeaders;
00047 
00048     /** @var string */
00049     protected $boundary = '';
00050 
00051     /**
00052      * Constructor
00053      *
00054      * @return void
00055      */
00056     public function __construct() {
00057             // create mailer object
00058         $this->mailer = t3lib_div::makeInstance('t3lib_mail_Mailer');
00059             // create message object
00060         $this->message = Swift_Message::newInstance();
00061     }
00062 
00063     /**
00064      * Parses parts of the mail message and sends it with the Swift Mailer functions
00065      *
00066      * @param string $to Email address to send the message to
00067      * @param string $subject Subject of mail message
00068      * @param string $messageBody Raw body (may be multipart)
00069      * @param array $additionalHeaders Additional mail headers
00070      * @param array $additionalParameters Extra parameters for the mail() command
00071      * @param bool $fakeSending If set fake sending a mail
00072      * @throws t3lib_exception
00073      * @return bool
00074      */
00075     public function mail($to, $subject, $messageBody, $additionalHeaders = NULL, $additionalParameters = NULL, $fakeSending = FALSE) {
00076 
00077             // report success for fake sending
00078         if ($fakeSending === TRUE) {
00079             return TRUE;
00080         }
00081         $this->message->setSubject($subject);
00082             // handle recipients
00083         $toAddresses = $this->parseAddresses($to);
00084         $this->message->setTo($toAddresses);
00085             // handle additional headers
00086         $headers = t3lib_div::trimExplode(LF, $additionalHeaders, TRUE);
00087         $this->messageHeaders = $this->message->getHeaders();
00088         foreach ($headers as $header) {
00089             list($headerName, $headerValue) = t3lib_div::trimExplode(':', $header, FALSE, 2);
00090             $this->setHeader($headerName, $headerValue);
00091         }
00092             // handle additional parameters (force return path)
00093         if (preg_match('/-f\s*(\S*?)/', $additionalParameters, $matches)) {
00094             $this->message->setReturnPath($this->unescapeShellArguments($matches[1]));
00095         }
00096             // handle from:
00097         $this->fixSender();
00098             // handle message body
00099         $this->setBody($messageBody);
00100             // send mail
00101         $result = $this->mailer->send($this->message);
00102 
00103             // report success/failure
00104         return (bool) $result;
00105     }
00106 
00107     /**
00108      * Tries to undo the action by escapeshellarg()
00109      *
00110      * @param  $escapedString String escaped by escapeshellarg()
00111      * @return string   String with escapeshellarg() action undone as best as possible
00112      */
00113     protected function unescapeShellArguments($escapedString) {
00114         if (TYPO3_OS === 'WIN') {
00115                 // on Windows double quotes are used and % signs are replaced by spaces
00116             if (preg_match('/^"([^"]*)"$/', trim($escapedString), $matches)) {
00117                 $result = str_replace('\"', '"', $matches[1]);
00118                     // % signs are replaced with spaces, so they can't be recovered
00119             }
00120         } else {
00121                 // on Unix-like systems single quotes are escaped
00122             if (preg_match('/^\'([^' . preg_quote('\'') . ']*)\'$/', trim($escapedString), $matches)) {
00123                 $result = str_replace('\\\'', '\'', $matches[1]);
00124             }
00125         }
00126         return $result;
00127     }
00128 
00129     /**
00130      * Handles setting and replacing of mail headers
00131      *
00132      * @param  $headerName Name of header
00133      * @param  $headerValue Value of header
00134      * @return void
00135      */
00136     protected function setHeader($headerName, $headerValue) {
00137             // check for boundary in headers
00138         if (preg_match('/^boundary="(.*)"$/', $headerName, $matches) > 0) {
00139             $this->boundary = $matches[1];
00140             return;
00141         }
00142             // process other, real headers
00143         if ($this->messageHeaders->has($headerName)) {
00144             $header = $this->messageHeaders->get($headerName);
00145             $headerType = $header->getFieldType();
00146             switch ($headerType) {
00147                 case Swift_Mime_Header::TYPE_TEXT:
00148                     $header->setValue($headerValue);
00149                     break;
00150                 case Swift_Mime_Header::TYPE_PARAMETERIZED:
00151                     $header->setValue(rtrim($headerValue, ';'));
00152                     break;
00153                 case Swift_Mime_Header::TYPE_MAILBOX:
00154                     $addressList = $this->parseAddresses($headerValue);
00155                     if (count($addressList) > 0) {
00156                         $header->setNameAddresses($addressList);
00157                     }
00158                     break;
00159                 case Swift_Mime_Header::TYPE_DATE:
00160                     $header->setTimeStamp(strtotime($headerValue));
00161                     break;
00162                 case Swift_Mime_Header::TYPE_ID:
00163                         // remove '<' and '>' from ID headers
00164                     $header->setId(trim($headerValue, '<>'));
00165                     break;
00166                 case Swift_Mime_Header::TYPE_PATH:
00167                     $header->setAddress($headerValue);
00168                     break;
00169             }
00170                 // change value
00171         } else {
00172             switch ($headerName) {
00173                     // mailbox headers
00174                 case 'From':
00175                 case 'To':
00176                 case 'Cc':
00177                 case 'Bcc':
00178                 case 'Reply-To':
00179                 case 'Sender':
00180                     $addressList = $this->parseAddresses($headerValue);
00181                     if (count($addressList) > 0) {
00182                         $this->messageHeaders->addMailboxHeader($headerName, $addressList);
00183                     }
00184                     break;
00185                     // date headers
00186                 case 'Date':
00187                     $this->messageHeaders->addDateHeader($headerName, strtotime($headerValue));
00188                     break;
00189                     // ID headers
00190                 case 'Message-ID':
00191                         // remove '<' and '>' from ID headers
00192                     $this->messageHeaders->addIdHeader($headerName, trim($headerValue, '<>'));
00193                     // path headers
00194                 case 'Return-Path':
00195                     $this->messageHeaders->addPathHeader($headerName, $headerValue);
00196                     break;
00197                     // parameterized headers
00198                 case 'Content-Type':
00199                 case 'Content-Disposition':
00200                     $this->messageHeaders->addParameterizedHeader($headerName, rtrim($headerValue, ';'));
00201                     break;
00202                     // text headers
00203                 default:
00204                     $this->messageHeaders->addTextheader($headerName, $headerValue);
00205                     break;
00206             }
00207         }
00208     }
00209 
00210     /**
00211      * Sets body of mail message. Handles multi-part and single part messages. Encoded body parts are decoded prior to adding
00212      * them to the message object.
00213      *
00214      * @param string $body Raw body, may be multi-part
00215      * @return void
00216      */
00217     protected function setBody($body) {
00218         if ($this->boundary) {
00219                 // handle multi-part
00220             $bodyParts = preg_split('/--' . preg_quote($this->boundary) . '(--)?/m', $body, NULL, PREG_SPLIT_NO_EMPTY);
00221             foreach ($bodyParts as $bodyPart) {
00222                     // skip empty parts
00223                 if (trim($bodyPart) == '') {
00224                     continue;
00225                 }
00226                     // keep leading white space when exploding the text
00227             $lines = explode(LF, $bodyPart);
00228                 // set defaults for this part
00229                 $encoding = '';
00230                 $charset = 'utf-8';
00231                 $contentType = 'text/plain';
00232                     // skip intro messages
00233                 if (trim($lines[0]) == 'This is a multi-part message in MIME format.') {
00234                     continue;
00235                 }
00236                     // first line is empty leftover from splitting
00237                 array_shift($lines);
00238                 while (count($lines) > 0) {
00239                     $line = array_shift($lines);
00240                     if (preg_match('/^content-type:(.*);( charset=(.*))?$/i', $line, $matches)) {
00241                         $contentType = trim($matches[1]);
00242                         if ($matches[2]) {
00243                             $charset = trim($matches[3]);
00244                         }
00245                     } else if (preg_match('/^content-transfer-encoding:(.*)$/i', $line, $matches)) {
00246                         $encoding = trim($matches[1]);
00247                     } else if (strlen(trim($line)) == 0) {
00248                             // empty line before actual content of this part
00249                         break;
00250                     }
00251                 }
00252                     // use rest of part as body, but reverse encoding first
00253                 $bodyPart = $this->decode(implode(LF, $lines), $encoding);
00254                 $this->message->addPart($bodyPart, $contentType, $charset);
00255             }
00256         } else {
00257                 // Handle single body
00258                 // The headers have already been set, so use header information
00259             $contentType = $this->message->getContentType();
00260             $charset = $this->message->getCharset();
00261             $encoding = $this->message->getEncoder()->getName();
00262                 // reverse encoding and set body
00263             $rawBody = $this->decode($body, $encoding);
00264             $this->message->setBody($rawBody, $contentType, $charset);
00265         }
00266     }
00267 
00268     /**
00269      * Reverts encoding of body text
00270      *
00271      * @param string $text Body text to be decoded
00272      * @param string $encoding Encoding type to be reverted
00273      * @return string Decoded message body
00274      */
00275     protected function decode($text, $encoding) {
00276         $result = $text;
00277         switch ($encoding) {
00278             case 'quoted-printable':
00279                 $result = quoted_printable_decode($text);
00280                 break;
00281             case 'base64':
00282                 $result = base64_decode($text);
00283                 break;
00284         }
00285         return $result;
00286     }
00287 
00288     /**
00289      * Parses mailbox headers and turns them into an array.
00290      *
00291      * Mailbox headers are a comma separated list of 'name <email@example.org' combinations or plain email addresses (or a mix
00292      * of these).
00293      * The resulting array has key-value pairs where the key is either a number (no display name in the mailbox header) and the
00294      * value is the email address, or the key is the email address and the value is the display name.
00295      *
00296      * @param string $rawAddresses Comma separated list of email addresses (optionally with display name)
00297      * @return array Parsed list of addresses.
00298      */
00299     protected function parseAddresses($rawAddresses = '') {
00300             /** @var $addressParser t3lib_mail_Rfc822AddressesParser */
00301         $addressParser = t3lib_div::makeInstance('t3lib_mail_Rfc822AddressesParser', $rawAddresses);
00302         $addresses = $addressParser->parseAddressList();
00303         $addressList = array();
00304         foreach ($addresses as $address) {
00305             if ($address->personal) {
00306                 // item with name found ( name <email@example.org> )
00307                 $addressList[$address->mailbox . '@' . $address->host] = $address->personal;
00308             } else {
00309                 // item without name found ( email@example.org )
00310                 $addressList[] = $address->mailbox . '@' . $address->host;
00311             }
00312         }
00313         return $addressList;
00314     }
00315 
00316     /**
00317      * Makes sure there is a correct sender set.
00318      *
00319      * If there is no from header the returnpath will be used. If that also fails a fake address will be used to make sure
00320      * Swift Mailer will be able to send the message. Some SMTP server will not accept mail messages without a valid sender.
00321      *
00322      * @return void
00323      */
00324     protected function fixSender() {
00325         $from = $this->message->getFrom();
00326         if (count($from) > 0) {
00327             reset($from);
00328             list($fromAddress, $fromName) = each($from);
00329         } else {
00330             $fromAddress = $this->message->getReturnPath();
00331             $fromName = $fromAddress;
00332         }
00333         if (strlen($fromAddress) == 0) {
00334             $fromAddress = 'no-reply@example.org';
00335             $fromName = 'TYPO3 CMS';
00336         }
00337         $this->message->setFrom(array($fromAddress => $fromName));
00338     }
00339 }
00340 ?>