You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
524 lines
15 KiB
524 lines
15 KiB
<?php
|
|
/**
|
|
* Copyright 2007-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (LGPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
|
|
*
|
|
* @category Horde
|
|
* @copyright 2007-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Mime
|
|
*/
|
|
|
|
/**
|
|
* The Horde_Mime_Mail:: class wraps around the various MIME library classes
|
|
* to provide a simple interface for creating and sending MIME messages.
|
|
*
|
|
* All content has to be passed UTF-8 encoded. The charset parameters is used
|
|
* for the generated message only.
|
|
*
|
|
* @author Jan Schneider <jan@horde.org>
|
|
* @category Horde
|
|
* @copyright 2007-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Mime
|
|
*/
|
|
class Horde_Mime_Mail
|
|
{
|
|
/**
|
|
* The message headers.
|
|
*
|
|
* @var Horde_Mime_Headers
|
|
*/
|
|
protected $_headers;
|
|
|
|
/**
|
|
* The base MIME part.
|
|
*
|
|
* @var Horde_Mime_Part
|
|
*/
|
|
protected $_base;
|
|
|
|
/**
|
|
* The main body part.
|
|
*
|
|
* @var Horde_Mime_Part
|
|
*/
|
|
protected $_body;
|
|
|
|
/**
|
|
* The main HTML body part.
|
|
*
|
|
* @var Horde_Mime_Part
|
|
*/
|
|
protected $_htmlBody;
|
|
|
|
/**
|
|
* The message recipients.
|
|
*
|
|
* @var Horde_Mail_Rfc822_List
|
|
*/
|
|
protected $_recipients;
|
|
|
|
/**
|
|
* Bcc recipients.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_bcc;
|
|
|
|
/**
|
|
* All MIME parts except the main body part.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_parts = array();
|
|
|
|
/**
|
|
* The Mail driver name.
|
|
*
|
|
* @link http://pear.php.net/Mail
|
|
* @var string
|
|
*/
|
|
protected $_mailer_driver = 'smtp';
|
|
|
|
/**
|
|
* The charset to use for the message.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_charset = 'UTF-8';
|
|
|
|
/**
|
|
* The Mail driver parameters.
|
|
*
|
|
* @link http://pear.php.net/Mail
|
|
* @var array
|
|
*/
|
|
protected $_mailer_params = array();
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param array $params A hash with basic message information. 'charset'
|
|
* is the character set of the message. 'body' is
|
|
* the message body. All other parameters are
|
|
* assumed to be message headers.
|
|
*
|
|
* @throws Horde_Mime_Exception
|
|
*/
|
|
public function __construct($params = array())
|
|
{
|
|
/* Set SERVER_NAME. */
|
|
if (!isset($_SERVER['SERVER_NAME'])) {
|
|
$_SERVER['SERVER_NAME'] = php_uname('n');
|
|
}
|
|
|
|
$this->_headers = new Horde_Mime_Headers();
|
|
|
|
if (isset($params['charset'])) {
|
|
$this->_charset = $params['charset'];
|
|
unset($params['charset']);
|
|
}
|
|
|
|
if (isset($params['body'])) {
|
|
$this->setBody($params['body'], $this->_charset);
|
|
unset($params['body']);
|
|
}
|
|
|
|
$this->addHeaders($params);
|
|
|
|
$this->clearRecipients();
|
|
}
|
|
|
|
/**
|
|
* Adds several message headers at once.
|
|
*
|
|
* @param array $header Hash with header names as keys and header
|
|
* contents as values.
|
|
*
|
|
* @throws Horde_Mime_Exception
|
|
*/
|
|
public function addHeaders($headers = array())
|
|
{
|
|
foreach ($headers as $header => $value) {
|
|
$this->addHeader($header, $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a message header.
|
|
*
|
|
* @param string $header The header name.
|
|
* @param string $value The header value.
|
|
* @param boolean $overwrite If true, an existing header of the same name
|
|
* is being overwritten; if false, multiple
|
|
* headers are added; if null, the correct
|
|
* behaviour is automatically chosen depending
|
|
* on the header name.
|
|
*
|
|
* @throws Horde_Mime_Exception
|
|
*/
|
|
public function addHeader($header, $value, $overwrite = null)
|
|
{
|
|
$lc_header = Horde_String::lower($header);
|
|
|
|
if (is_null($overwrite) &&
|
|
in_array($lc_header, $this->_headers->singleFields(true))) {
|
|
$overwrite = true;
|
|
}
|
|
|
|
if ($overwrite) {
|
|
$this->_headers->removeHeader($header);
|
|
}
|
|
|
|
if ($lc_header === 'bcc') {
|
|
$this->_bcc = $value;
|
|
} else {
|
|
$this->_headers->addHeader($header, $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a Horde_Mime_Headers_Element object to the current header list.
|
|
*
|
|
* @since 2.5.0
|
|
*
|
|
* @param Horde_Mime_Headers_Element $ob Header object to add.
|
|
*
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function addHeaderOb(Horde_Mime_Headers_Element $ob)
|
|
{
|
|
$this->_headers->addHeaderOb($ob, true);
|
|
}
|
|
|
|
/**
|
|
* Removes a message header.
|
|
*
|
|
* @param string $header The header name.
|
|
*/
|
|
public function removeHeader($header)
|
|
{
|
|
if (Horde_String::lower($header) === 'bcc') {
|
|
unset($this->_bcc);
|
|
} else {
|
|
$this->_headers->removeHeader($header);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the message body text.
|
|
*
|
|
* @param string $body The message content.
|
|
* @param string $charset The character set of the message.
|
|
* @param boolean|integer $wrap If true, wrap the message at column 76;
|
|
* If an integer wrap the message at that
|
|
* column. Don't use wrapping if sending
|
|
* flowed messages.
|
|
*/
|
|
public function setBody($body, $charset = null, $wrap = false)
|
|
{
|
|
if (!$charset) {
|
|
$charset = $this->_charset;
|
|
}
|
|
$body = Horde_String::convertCharset($body, 'UTF-8', $charset);
|
|
if ($wrap) {
|
|
$body = Horde_String::wrap($body, $wrap === true ? 76 : $wrap);
|
|
}
|
|
$this->_body = new Horde_Mime_Part();
|
|
$this->_body->setType('text/plain');
|
|
$this->_body->setCharset($charset);
|
|
$this->_body->setContents($body);
|
|
$this->_base = null;
|
|
}
|
|
|
|
/**
|
|
* Sets the HTML message body text.
|
|
*
|
|
* @param string $body The message content.
|
|
* @param string $charset The character set of the message.
|
|
* @param boolean $alternative If true, a multipart/alternative message is
|
|
* created and the text/plain part is
|
|
* generated automatically. If false, a
|
|
* text/html message is generated.
|
|
*/
|
|
public function setHtmlBody($body, $charset = null, $alternative = true)
|
|
{
|
|
if (!$charset) {
|
|
$charset = $this->_charset;
|
|
}
|
|
$this->_htmlBody = new Horde_Mime_Part();
|
|
$this->_htmlBody->setType('text/html');
|
|
$this->_htmlBody->setCharset($charset);
|
|
$this->_htmlBody->setContents($body);
|
|
if ($alternative) {
|
|
$this->setBody(Horde_Text_Filter::filter($body, 'Html2text', array('charset' => $charset, 'wrap' => false)), $charset);
|
|
}
|
|
$this->_base = null;
|
|
}
|
|
|
|
/**
|
|
* Adds a message part.
|
|
*
|
|
* @param string $mime_type The content type of the part.
|
|
* @param string $content The content of the part.
|
|
* @param string $charset The character set of the part.
|
|
* @param string $disposition The content disposition of the part.
|
|
*
|
|
* @return integer The part number.
|
|
*/
|
|
public function addPart($mime_type, $content, $charset = 'us-ascii',
|
|
$disposition = null)
|
|
{
|
|
$part = new Horde_Mime_Part();
|
|
$part->setType($mime_type);
|
|
$part->setCharset($charset);
|
|
$part->setDisposition($disposition);
|
|
$part->setContents($content);
|
|
return $this->addMimePart($part);
|
|
}
|
|
|
|
/**
|
|
* Adds a MIME message part.
|
|
*
|
|
* @param Horde_Mime_Part $part A Horde_Mime_Part object.
|
|
*
|
|
* @return integer The part number.
|
|
*/
|
|
public function addMimePart($part)
|
|
{
|
|
$this->_parts[] = $part;
|
|
return count($this->_parts) - 1;
|
|
}
|
|
|
|
/**
|
|
* Sets the base MIME part.
|
|
*
|
|
* If the base part is set, any text bodies will be ignored when building
|
|
* the message.
|
|
*
|
|
* @param Horde_Mime_Part $part A Horde_Mime_Part object.
|
|
*/
|
|
public function setBasePart($part)
|
|
{
|
|
$this->_base = $part;
|
|
}
|
|
|
|
/**
|
|
* Adds an attachment.
|
|
*
|
|
* @param string $file The path to the file.
|
|
* @param string $name The file name to use for the attachment.
|
|
* @param string $type The content type of the file.
|
|
* @param string $charset The character set of the part (only relevant for
|
|
* text parts.
|
|
*
|
|
* @return integer The part number.
|
|
*/
|
|
public function addAttachment($file, $name = null, $type = null,
|
|
$charset = 'us-ascii')
|
|
{
|
|
if (empty($name)) {
|
|
$name = basename($file);
|
|
}
|
|
|
|
if (empty($type)) {
|
|
$type = Horde_Mime_Magic::filenameToMime($file, false);
|
|
}
|
|
|
|
$num = $this->addPart($type, file_get_contents($file), $charset, 'attachment');
|
|
$this->_parts[$num]->setName($name);
|
|
return $num;
|
|
}
|
|
|
|
/**
|
|
* Removes a message part.
|
|
*
|
|
* @param integer $part The part number.
|
|
*/
|
|
public function removePart($part)
|
|
{
|
|
if (isset($this->_parts[$part])) {
|
|
unset($this->_parts[$part]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes all (additional) message parts but leaves the body parts
|
|
* untouched.
|
|
*/
|
|
public function clearParts()
|
|
{
|
|
$this->_parts = array();
|
|
}
|
|
|
|
/**
|
|
* Adds message recipients.
|
|
*
|
|
* Recipients specified by To:, Cc:, or Bcc: headers are added
|
|
* automatically.
|
|
*
|
|
* @param string|array List of recipients, either as a comma separated
|
|
* list or as an array of email addresses.
|
|
*
|
|
* @throws Horde_Mime_Exception
|
|
*/
|
|
public function addRecipients($recipients)
|
|
{
|
|
$this->_recipients->add($recipients);
|
|
}
|
|
|
|
/**
|
|
* Removes message recipients.
|
|
*
|
|
* @param string|array List of recipients, either as a comma separated
|
|
* list or as an array of email addresses.
|
|
*
|
|
* @throws Horde_Mime_Exception
|
|
*/
|
|
public function removeRecipients($recipients)
|
|
{
|
|
$this->_recipients->remove($recipients);
|
|
}
|
|
|
|
/**
|
|
* Removes all message recipients.
|
|
*/
|
|
public function clearRecipients()
|
|
{
|
|
$this->_recipients = new Horde_Mail_Rfc822_List();
|
|
}
|
|
|
|
/**
|
|
* Sends this message.
|
|
*
|
|
* @param Mail $mailer A Mail object.
|
|
* @param boolean $resend If true, the message id and date are re-used;
|
|
* If false, they will be updated.
|
|
* @param boolean $flowed Send message in flowed text format.
|
|
*
|
|
* @throws Horde_Mime_Exception
|
|
*/
|
|
public function send($mailer, $resend = false, $flowed = true)
|
|
{
|
|
/* Add mandatory headers if missing. */
|
|
if (!$resend || !isset($this->_headers['Message-ID'])) {
|
|
$this->_headers->addHeaderOb(
|
|
Horde_Mime_Headers_MessageId::create()
|
|
);
|
|
}
|
|
if (!isset($this->_headers['User-Agent'])) {
|
|
$this->_headers->addHeaderOb(
|
|
Horde_Mime_Headers_UserAgent::create()
|
|
);
|
|
}
|
|
if (!$resend || !isset($this->_headers['Date'])) {
|
|
$this->_headers->addHeaderOb(Horde_Mime_Headers_Date::create());
|
|
}
|
|
|
|
if (isset($this->_base)) {
|
|
$basepart = $this->_base;
|
|
} else {
|
|
/* Send in flowed format. */
|
|
if ($flowed && !empty($this->_body)) {
|
|
$flowed = new Horde_Text_Flowed($this->_body->getContents(), $this->_body->getCharset());
|
|
$flowed->setDelSp(true);
|
|
$this->_body->setContentTypeParameter('format', 'flowed');
|
|
$this->_body->setContentTypeParameter('DelSp', 'Yes');
|
|
$this->_body->setContents($flowed->toFlowed());
|
|
}
|
|
|
|
/* Build mime message. */
|
|
$body = new Horde_Mime_Part();
|
|
if (!empty($this->_body) && !empty($this->_htmlBody)) {
|
|
$body->setType('multipart/alternative');
|
|
$this->_body->setDescription(Horde_Mime_Translation::t("Plaintext Version of Message"));
|
|
$body[] = $this->_body;
|
|
$this->_htmlBody->setDescription(Horde_Mime_Translation::t("HTML Version of Message"));
|
|
$body[] = $this->_htmlBody;
|
|
} elseif (!empty($this->_htmlBody)) {
|
|
$body = $this->_htmlBody;
|
|
} elseif (!empty($this->_body)) {
|
|
$body = $this->_body;
|
|
}
|
|
if (count($this->_parts)) {
|
|
$basepart = new Horde_Mime_Part();
|
|
$basepart->setType('multipart/mixed');
|
|
$basepart->isBasePart(true);
|
|
if ($body) {
|
|
$basepart[] = $body;
|
|
}
|
|
foreach ($this->_parts as $mime_part) {
|
|
$basepart[] = $mime_part;
|
|
}
|
|
} else {
|
|
$basepart = $body;
|
|
$basepart->isBasePart(true);
|
|
}
|
|
}
|
|
$basepart->setHeaderCharset($this->_charset);
|
|
|
|
/* Build recipients. */
|
|
$recipients = clone $this->_recipients;
|
|
foreach (array('to', 'cc') as $header) {
|
|
if ($h = $this->_headers[$header]) {
|
|
$recipients->add($h->getAddressList());
|
|
}
|
|
}
|
|
if ($this->_bcc) {
|
|
$recipients->add($this->_bcc);
|
|
}
|
|
|
|
/* Trick Horde_Mime_Part into re-generating the message headers. */
|
|
$this->_headers->removeHeader('MIME-Version');
|
|
|
|
/* Send message. */
|
|
$recipients->unique();
|
|
$basepart->send($recipients->writeAddress(), $this->_headers, $mailer);
|
|
|
|
/* Remember the basepart */
|
|
$this->_base = $basepart;
|
|
}
|
|
|
|
/**
|
|
* Get the raw email data sent by this object.
|
|
*
|
|
* @param boolean $stream If true, return a stream resource, otherwise
|
|
* a string is returned.
|
|
*
|
|
* @return stream|string The raw email data.
|
|
* @since 2.4.0
|
|
*/
|
|
public function getRaw($stream = true)
|
|
{
|
|
if ($stream) {
|
|
$hdr = new Horde_Stream();
|
|
$hdr->add($this->_headers->toString(), true);
|
|
return Horde_Stream_Wrapper_Combine::getStream(
|
|
array($hdr->stream,
|
|
$this->getBasePart()->toString(
|
|
array('stream' => true, 'encode' => Horde_Mime_Part::ENCODE_7BIT | Horde_Mime_Part::ENCODE_8BIT | Horde_Mime_Part::ENCODE_BINARY))
|
|
)
|
|
);
|
|
}
|
|
|
|
return $this->_headers->toString() . $this->getBasePart()->toString();
|
|
}
|
|
|
|
/**
|
|
* Return the base MIME part.
|
|
*
|
|
* @return Horde_Mime_Part
|
|
*/
|
|
public function getBasePart()
|
|
{
|
|
if (empty($this->_base)) {
|
|
throw new Horde_Mail_Exception('No base part set.');
|
|
}
|
|
|
|
return $this->_base;
|
|
}
|
|
|
|
}
|
|
|