123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- <?php
- // Copyright (C) 2010-2016 Combodo SARL
- //
- // This file is part of iTop.
- //
- // iTop is free software; you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // iTop is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with iTop. If not, see <http://www.gnu.org/licenses/>
- /**
- * Send an email (abstraction for synchronous/asynchronous modes)
- *
- * @copyright Copyright (C) 2010-2016 Combodo SARL
- * @license http://opensource.org/licenses/AGPL-3.0
- */
- require_once(APPROOT.'/lib/swiftmailer/lib/swift_required.php');
- Swift_Preferences::getInstance()->setCharset('UTF-8');
- define ('EMAIL_SEND_OK', 0);
- define ('EMAIL_SEND_PENDING', 1);
- define ('EMAIL_SEND_ERROR', 2);
- class EMail
- {
- // Serialization formats
- const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object.
- // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string
- const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed)
-
- protected static $m_oConfig = null;
- protected $m_aData; // For storing data to serialize
- public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE)
- {
- if (is_null(self::$m_oConfig))
- {
- self::$m_oConfig = new Config($sConfigFile);
- }
- }
- protected $m_oMessage;
- public function __construct()
- {
- $this->m_aData = array();
- $this->m_oMessage = Swift_Message::newInstance();
- $oEncoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit');
- $this->m_oMessage->setEncoder($oEncoder);
- }
- /**
- * Custom serialization method
- * No longer use the brute force "serialize" method since
- * 1) It does not work with binary attachments (since they cannot be stored in a UTF-8 text field)
- * 2) The size tends to be quite big (sometimes ten times the size of the email)
- */
- public function SerializeV2()
- {
- return serialize($this->m_aData);
- }
-
- /**
- * Custom de-serialization method
- * @param string $sSerializedMessage The serialized representation of the message
- */
- static public function UnSerializeV2($sSerializedMessage)
- {
- $aData = unserialize($sSerializedMessage);
- $oMessage = new Email();
-
- if (array_key_exists('body', $aData))
- {
- $oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']);
- }
- if (array_key_exists('message_id', $aData))
- {
- $oMessage->SetMessageId($aData['message_id']);
- }
- if (array_key_exists('bcc', $aData))
- {
- $oMessage->SetRecipientBCC($aData['bcc']);
- }
- if (array_key_exists('cc', $aData))
- {
- $oMessage->SetRecipientCC($aData['cc']);
- }
- if (array_key_exists('from', $aData))
- {
- $oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']);
- }
- if (array_key_exists('reply_to', $aData))
- {
- $oMessage->SetRecipientReplyTo($aData['reply_to']);
- }
- if (array_key_exists('to', $aData))
- {
- $oMessage->SetRecipientTO($aData['to']);
- }
- if (array_key_exists('subject', $aData))
- {
- $oMessage->SetSubject($aData['subject']);
- }
-
-
- if (array_key_exists('headers', $aData))
- {
- foreach($aData['headers'] as $sKey => $sValue)
- {
- $oMessage->AddToHeader($sKey, $sValue);
- }
- }
- if (array_key_exists('parts', $aData))
- {
- foreach($aData['parts'] as $aPart)
- {
- $oMessage->AddPart($aPart['text'], $aPart['mimeType']);
- }
- }
- if (array_key_exists('attachments', $aData))
- {
- foreach($aData['attachments'] as $aAttachment)
- {
- $oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']);
- }
- }
- return $oMessage;
- }
-
- protected function SendAsynchronous(&$aIssues, $oLog = null)
- {
- try
- {
- AsyncSendEmail::AddToQueue($this, $oLog);
- }
- catch(Exception $e)
- {
- $aIssues = array($e->GetMessage());
- return EMAIL_SEND_ERROR;
- }
- $aIssues = array();
- return EMAIL_SEND_PENDING;
- }
- protected function SendSynchronous(&$aIssues, $oLog = null)
- {
- // If the body of the message is in HTML, embed all images based on attachments
- $this->EmbedInlineImages();
-
- $this->LoadConfig();
- $sTransport = self::$m_oConfig->Get('email_transport');
- switch ($sTransport)
- {
- case 'SMTP':
- $sHost = self::$m_oConfig->Get('email_transport_smtp.host');
- $sPort = self::$m_oConfig->Get('email_transport_smtp.port');
- $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption');
- $sUserName = self::$m_oConfig->Get('email_transport_smtp.username');
- $sPassword = self::$m_oConfig->Get('email_transport_smtp.password');
- $oTransport = Swift_SmtpTransport::newInstance($sHost, $sPort, $sEncryption);
- if (strlen($sUserName) > 0)
- {
- $oTransport->setUsername($sUserName);
- $oTransport->setPassword($sPassword);
- }
- break;
- case 'Null':
- $oTransport = Swift_NullTransport::newInstance();
- break;
-
- case 'LogFile':
- $oTransport = Swift_LogFileTransport::newInstance();
- $oTransport->setLogFile(APPROOT.'log/mail.log');
- break;
-
- case 'PHPMail':
- default:
- $oTransport = Swift_MailTransport::newInstance();
- }
- $oMailer = Swift_Mailer::newInstance($oTransport);
- $aFailedRecipients = array();
- $iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients);
- if ($iSent === 0)
- {
- // Beware: it seems that $aFailedRecipients sometimes contains the recipients that actually received the message !!!
- IssueLog::Warning('Email sending failed: Some recipients were invalid, aFailedRecipients contains: '.implode(', ', $aFailedRecipients));
- $aIssues = array('Some recipients were invalid.');
- return EMAIL_SEND_ERROR;
- }
- else
- {
- $aIssues = array();
- return EMAIL_SEND_OK;
- }
- }
-
- /**
- * Reprocess the body of the message (if it is an HTML message)
- * to replace the URL of images based on attachments by a link
- * to an embedded image (i.e. cid:....)
- */
- protected function EmbedInlineImages()
- {
- if ($this->m_aData['body']['mimeType'] == 'text/html')
- {
- $oDOMDoc = new DOMDocument();
- $oDOMDoc->preserveWhitespace = true;
- @$oDOMDoc->loadHTML('<?xml encoding="UTF-8"?>'.$this->m_aData['body']['body']); // For loading HTML chunks where the character set is not specified
-
- $oXPath = new DOMXPath($oDOMDoc);
- $sXPath = "//img[@data-img-id]";
- $oImagesList = $oXPath->query($sXPath);
-
- if ($oImagesList->length != 0)
- {
- foreach($oImagesList as $oImg)
- {
- $iAttId = $oImg->getAttribute('data-img-id');
- $oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */);
- if ($oAttachment)
- {
- $oDoc = $oAttachment->Get('contents');
- $oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType());
- $sCid = $this->m_oMessage->embed($oSwiftImage);
- $oImg->setAttribute('src', $sCid);
- }
- }
- }
- $sHtmlBody = $oDOMDoc->saveHTML();
- $this->m_oMessage->setBody($sHtmlBody, 'text/html', 'UTF-8');
- }
- }
- public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null)
- {
- if ($bForceSynchronous)
- {
- return $this->SendSynchronous($aIssues, $oLog);
- }
- else
- {
- $bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous');
- if ($bConfigASYNC)
- {
- return $this->SendAsynchronous($aIssues, $oLog);
- }
- else
- {
- return $this->SendSynchronous($aIssues, $oLog);
- }
- }
- }
- public function AddToHeader($sKey, $sValue)
- {
- if (!array_key_exists('headers', $this->m_aData))
- {
- $this->m_aData['headers'] = array();
- }
- $this->m_aData['headers'][$sKey] = $sValue;
-
- if (strlen($sValue) > 0)
- {
- $oHeaders = $this->m_oMessage->getHeaders();
- switch(strtolower($sKey))
- {
- default:
- $oHeaders->addTextHeader($sKey, $sValue);
- }
- }
- }
- public function SetMessageId($sId)
- {
- $this->m_aData['message_id'] = $sId;
-
- // Note: Swift will add the angle brackets for you
- // so let's remove the angle brackets if present, for historical reasons
- $sId = str_replace(array('<', '>'), '', $sId);
-
- $oMsgId = $this->m_oMessage->getHeaders()->get('Message-ID');
- $oMsgId->SetId($sId);
- }
-
- public function SetReferences($sReferences)
- {
- $this->AddToHeader('References', $sReferences);
- }
- public function SetBody($sBody, $sMimeType = 'text/html')
- {
- $this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType);
- $this->m_oMessage->setBody($sBody, $sMimeType);
- }
- public function AddPart($sText, $sMimeType = 'text/html')
- {
- if (!array_key_exists('parts', $this->m_aData))
- {
- $this->m_aData['parts'] = array();
- }
- $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType);
- $this->m_oMessage->addPart($sText, $sMimeType);
- }
- public function AddAttachment($data, $sFileName, $sMimeType)
- {
- if (!array_key_exists('attachments', $this->m_aData))
- {
- $this->m_aData['attachments'] = array();
- }
- $this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType);
- $this->m_oMessage->attach(Swift_Attachment::newInstance($data, $sFileName, $sMimeType));
- }
- public function SetSubject($sSubject)
- {
- $this->m_aData['subject'] = $sSubject;
- $this->m_oMessage->setSubject($sSubject);
- }
- public function GetSubject()
- {
- return $this->m_oMessage->getSubject();
- }
- /**
- * Helper to transform and sanitize addresses
- * - get rid of empty addresses
- */
- protected function AddressStringToArray($sAddressCSVList)
- {
- $aAddresses = array();
- foreach(explode(',', $sAddressCSVList) as $sAddress)
- {
- $sAddress = trim($sAddress);
- if (strlen($sAddress) > 0)
- {
- $aAddresses[] = $sAddress;
- }
- }
- return $aAddresses;
- }
-
- public function SetRecipientTO($sAddress)
- {
- $this->m_aData['to'] = $sAddress;
- if (!empty($sAddress))
- {
- $aAddresses = $this->AddressStringToArray($sAddress);
- $this->m_oMessage->setTo($aAddresses);
- }
- }
- public function GetRecipientTO($bAsString = false)
- {
- $aRes = $this->m_oMessage->getTo();
- if ($aRes === null)
- {
- // There is no "To" header field
- $aRes = array();
- }
- if ($bAsString)
- {
- $aStrings = array();
- foreach ($aRes as $sEmail => $sName)
- {
- if (is_null($sName))
- {
- $aStrings[] = $sEmail;
- }
- else
- {
- $sName = str_replace(array('<', '>'), '', $sName);
- $aStrings[] = "$sName <$sEmail>";
- }
- }
- return implode(', ', $aStrings);
- }
- else
- {
- return $aRes;
- }
- }
- public function SetRecipientCC($sAddress)
- {
- $this->m_aData['cc'] = $sAddress;
- if (!empty($sAddress))
- {
- $aAddresses = $this->AddressStringToArray($sAddress);
- $this->m_oMessage->setCc($aAddresses);
- }
- }
- public function SetRecipientBCC($sAddress)
- {
- $this->m_aData['bcc'] = $sAddress;
- if (!empty($sAddress))
- {
- $aAddresses = $this->AddressStringToArray($sAddress);
- $this->m_oMessage->setBcc($aAddresses);
- }
- }
- public function SetRecipientFrom($sAddress, $sLabel = '')
- {
- $this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel);
- if ($sLabel != '')
- {
- $this->m_oMessage->setFrom(array($sAddress => $sLabel));
- }
- else if (!empty($sAddress))
- {
- $this->m_oMessage->setFrom($sAddress);
- }
- }
- public function SetRecipientReplyTo($sAddress)
- {
- $this->m_aData['reply_to'] = $sAddress;
- if (!empty($sAddress))
- {
- $this->m_oMessage->setReplyTo($sAddress);
- }
- }
- }
- /////////////////////////////////////////////////////////////////////////////////////
- /**
- * Extension to SwiftMailer: "debug" transport that pretends messages have been sent,
- * but just log them to a file.
- *
- * @package Swift
- * @author Denis Flaven
- */
- class Swift_Transport_LogFileTransport extends Swift_Transport_NullTransport
- {
- protected $sLogFile;
-
- /**
- * Sends the given message.
- *
- * @param Swift_Mime_Message $message
- * @param string[] $failedRecipients An array of failures by-reference
- *
- * @return int The number of sent emails
- */
- public function send(Swift_Mime_Message $message, &$failedRecipients = null)
- {
- $hFile = @fopen($this->sLogFile, 'a');
- if ($hFile)
- {
- $sTxt = "================== ".date('Y-m-d H:i:s')." ==================\n";
- $sTxt .= $message->toString()."\n";
-
- @fwrite($hFile, $sTxt);
- @fclose($hFile);
- }
-
- return parent::send($message, $failedRecipients);
- }
-
- public function setLogFile($sFilename)
- {
- $this->sLogFile = $sFilename;
- }
- }
- /**
- * Pretends messages have been sent, but just log them to a file.
- *
- * @package Swift
- * @author Denis Flaven
- */
- class Swift_LogFileTransport extends Swift_Transport_LogFileTransport
- {
- /**
- * Create a new LogFileTransport.
- */
- public function __construct()
- {
- call_user_func_array(
- array($this, 'Swift_Transport_LogFileTransport::__construct'),
- Swift_DependencyContainer::getInstance()
- ->createDependenciesFor('transport.null')
- );
- }
- /**
- * Create a new LogFileTransport instance.
- *
- * @return Swift_LogFileTransport
- */
- public static function newInstance()
- {
- return new self();
- }
- }
|