diff --git a/src/AbstractTransport.php b/src/AbstractTransport.php deleted file mode 100644 index 051ee491..00000000 --- a/src/AbstractTransport.php +++ /dev/null @@ -1,336 +0,0 @@ -_mail->getType(); - if (!$type) { - if ($this->_mail->hasAttachments) { - $type = Mime\Mime::MULTIPART_MIXED; - } elseif ($this->_mail->getBodyText() && $this->_mail->getBodyHtml()) { - $type = Mime\Mime::MULTIPART_ALTERNATIVE; - } else { - $type = Mime\Mime::MULTIPART_MIXED; - } - } - - $this->_headers['Content-Type'] = array( - $type . ';' - . $this->EOL - . " " . 'boundary="' . $boundary . '"' - ); - $this->boundary = $boundary; - } - - $this->_headers['MIME-Version'] = array('1.0'); - - return $this->_headers; - } - - /** - * Prepend header name to header value - * - * @param string $item - * @param string $key - * @param string $prefix - * @static - * @access protected - * @return void - */ - protected static function _formatHeader(&$item, $key, $prefix) - { - $item = $prefix . ': ' . $item; - } - - /** - * Prepare header string for use in transport - * - * Prepares and generates {@link $header} based on the headers provided. - * - * @param mixed $headers - * @access protected - * @return void - * @throws \Zend\Mail\Transport\Exception if any header lines exceed 998 - * characters - */ - protected function _prepareHeaders($headers) - { - if (!$this->_mail) { - throw new Transport\Exception\RuntimeException('Missing \Zend\Mail\Mail object in _mail property'); - } - - $this->header = ''; - - foreach ($headers as $header => $content) { - if (isset($content['append'])) { - unset($content['append']); - $value = implode(',' . $this->EOL . ' ', $content); - $this->header .= $header . ': ' . $value . $this->EOL; - } else { - array_walk($content, array(get_class($this), '_formatHeader'), $header); - $this->header .= implode($this->EOL, $content) . $this->EOL; - } - } - - // Sanity check on headers -- should not be > 998 characters - $sane = true; - foreach (explode($this->EOL, $this->header) as $line) { - if (strlen(trim($line)) > 998) { - $sane = false; - break; - } - } - if (!$sane) { - throw new Transport\Exception\RuntimeException('At least one mail header line is too long'); - } - } - - /** - * Generate MIME compliant message from the current configuration - * - * If both a text and HTML body are present, generates a - * multipart/alternative \Zend\Mime\Part\Part containing the headers and contents - * of each. Otherwise, uses whichever of the text or HTML parts present. - * - * The content part is then prepended to the list of \Zend\Mime\Parts\Parts for - * this message. - * - * @return void - */ - protected function _buildBody() - { - if (($text = $this->_mail->getBodyText()) - && ($html = $this->_mail->getBodyHtml())) - { - // Generate unique boundary for multipart/alternative - $mime = new Mime\Mime(null); - $boundaryLine = $mime->boundaryLine($this->EOL); - $boundaryEnd = $mime->mimeEnd($this->EOL); - - $text->disposition = false; - $html->disposition = false; - - $body = $boundaryLine - . $text->getHeaders($this->EOL) - . $this->EOL - . $text->getContent($this->EOL) - . $this->EOL - . $boundaryLine - . $html->getHeaders($this->EOL) - . $this->EOL - . $html->getContent($this->EOL) - . $this->EOL - . $boundaryEnd; - - $mp = new Mime\Part($body); - $mp->type = Mime\Mime::MULTIPART_ALTERNATIVE; - $mp->boundary = $mime->boundary(); - - $this->_isMultipart = true; - - // Ensure first part contains text alternatives - array_unshift($this->_parts, $mp); - - // Get headers - $this->_headers = $this->_mail->getHeaders(); - return; - } - - // If not multipart, then get the body - if (false !== ($body = $this->_mail->getBodyHtml())) { - array_unshift($this->_parts, $body); - } elseif (false !== ($body = $this->_mail->getBodyText())) { - array_unshift($this->_parts, $body); - } - - if (!$body) { - throw new Transport\Exception\RuntimeException('No body specified'); - } - - // Get headers - $this->_headers = $this->_mail->getHeaders(); - $headers = $body->getHeadersArray($this->EOL); - foreach ($headers as $header) { - // Headers in \Zend\Mime\Part\Part are kept as arrays with two elements, a - // key and a value - $this->_headers[$header[0]] = array($header[1]); - } - } - - /** - * Send a mail using this transport - * - * @param \Zend\Mail\Mail $mail - * @access public - * @return void - * @throws \Zend\Mail\Transport\Exception if mail is empty - */ - public function send(Mail $mail) - { - $this->_isMultipart = false; - $this->_mail = $mail; - $this->_parts = $mail->getParts(); - $mime = $mail->getMime(); - - // Build body content - $this->_buildBody(); - - // Determine number of parts and boundary - $count = count($this->_parts); - $boundary = null; - if ($count < 1) { - throw new Transport\Exception\RuntimeException('Empty mail cannot be sent'); - } - - if ($count > 1) { - // Multipart message; create new MIME object and boundary - $mime = new Mime\Mime($this->_mail->getMimeBoundary()); - $boundary = $mime->boundary(); - } elseif ($this->_isMultipart) { - // multipart/alternative -- grab boundary - $boundary = $this->_parts[0]->boundary; - } - - // Determine recipients, and prepare headers - $this->recipients = implode(',', $mail->getRecipients()); - $this->_prepareHeaders($this->_getHeaders($boundary)); - - // Create message body - // This is done so that the same \Zend\Mail\Mail object can be used in - // multiple transports - $message = new Mime\Message(); - $message->setParts($this->_parts); - $message->setMime($mime); - $this->body = $message->generateMessage($this->EOL); - - // Send to transport! - $this->_sendMail(); - } -} diff --git a/src/Address.php b/src/Address.php new file mode 100644 index 00000000..4e60f063 --- /dev/null +++ b/src/Address.php @@ -0,0 +1,90 @@ +email = $email; + $this->name = $name; + } + + /** + * Retrieve email + * + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * Retrieve name + * + * @return null|string + */ + public function getName() + { + return $this->name; + } + + /** + * String representation of address + * + * @return string + */ + public function toString() + { + $string = '<' . $this->getEmail() . '>'; + $name = $this->getName(); + if (null === $name) { + return $string; + } + + $string = $name . ' ' . $string; + return $string; + } +} diff --git a/src/AddressDescription.php b/src/AddressDescription.php new file mode 100644 index 00000000..7344382f --- /dev/null +++ b/src/AddressDescription.php @@ -0,0 +1,34 @@ +createAddress($emailOrAddress, $name); + } + if (!$emailOrAddress instanceof AddressDescription) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects an email address or %s\Address object as its first argument; received "%s"', + __METHOD__, + __NAMESPACE__, + (is_object($emailOrAddress) ? get_class($emailOrAddress) : gettype($emailOrAddress)) + )); + } + + $email = strtolower($emailOrAddress->getEmail()); + if ($this->has($email)) { + return $this; + } + + $this->addresses[$email] = $emailOrAddress; + return $this; + } + + /** + * Add many addresses at once + * + * If an email key is provided, it will be used as the email, and the value + * as the name. Otherwise, the value is passed as the sole argument to add(), + * and, as such, can be either email strings or AddressDescription objects. + * + * @param array $addresses + * @return AddressList + */ + public function addMany(array $addresses) + { + foreach ($addresses as $key => $value) { + if (is_int($key) || is_numeric($key)) { + $this->add($value); + } elseif (is_string($key)) { + $this->add($key, $value); + } else { + throw new Exception\RuntimeException(sprintf( + 'Invalid key type in provided addresses array ("%s")', + (is_object($key) ? get_class($key) : var_export($key, 1)) + )); + } + } + return $this; + } + + /** + * Merge another address list into this one + * + * @param AddressList $addressList + * @return AddressList + */ + public function merge(AddressList $addressList) + { + foreach ($addressList as $address) { + $this->add($address); + } + return $this; + } + + /** + * Does the email exist in this list? + * + * @param string $email + * @return bool + */ + public function has($email) + { + $email = strtolower($email); + return isset($this->addresses[$email]); + } + + /** + * Get an address by email + * + * @param string $email + * @return false|AddressDescription + */ + public function get($email) + { + $email = strtolower($email); + if (!isset($this->addresses[$email])) { + return false; + } + + return $this->addresses[$email]; + } + + /** + * Delete an address from the list + * + * @param string $email + * @return bool + */ + public function delete($email) + { + $email = strtolower($email); + if (!isset($this->addresses[$email])) { + return false; + } + + unset($this->addresses[$email]); + return true; + } + + /** + * Return count of addresses + * + * @return int + */ + public function count() + { + return count($this->addresses); + } + + /** + * Rewind iterator + * + * @return void + */ + public function rewind() + { + return reset($this->addresses); + } + + /** + * Return current item in iteration + * + * @return Address + */ + public function current() + { + return current($this->addresses); + } + + /** + * Return key of current item of iteration + * + * @return string + */ + public function key() + { + return key($this->addresses); + } + + /** + * Move to next item + * + * @return void + */ + public function next() + { + return next($this->addresses); + } + + /** + * Is the current item of iteration valid? + * + * @return bool + */ + public function valid() + { + $key = key($this->addresses); + return ($key !== null && $key !== false); + } + + /** + * Create an address object + * + * @param string $email + * @param string|null $name + * @return Address + */ + protected function createAddress($email, $name) + { + return new Address($email, $name); + } +} diff --git a/src/Exception.php b/src/Exception.php index cbd2f8e2..edfcc704 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -14,7 +14,7 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -26,10 +26,9 @@ /** * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ interface Exception { } - diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index d775df41..ac387697 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -14,8 +14,7 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) - * @version $Id$ + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -28,14 +27,13 @@ /** * Exception for Zend_Mail component. * - * @uses Zend\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class InvalidArgumentException extends \InvalidArgumentException implements Exception { -} \ No newline at end of file +} diff --git a/src/Exception/OutOfBoundsException.php b/src/Exception/OutOfBoundsException.php index 290c41d9..de027ba3 100644 --- a/src/Exception/OutOfBoundsException.php +++ b/src/Exception/OutOfBoundsException.php @@ -14,28 +14,24 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) - * @version $Id$ + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -/** - * @namespace - */ namespace Zend\Mail\Exception; + use Zend\Mail\Exception; /** * Exception for Zend_Mail component. * - * @uses Zend\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class OutOfBoundsException extends \OutOfBoundsException implements Exception { -} \ No newline at end of file +} diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index b53d2cd1..0ccb91a1 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -14,28 +14,24 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) - * @version $Id$ + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -/** - * @namespace - */ namespace Zend\Mail\Exception; + use Zend\Mail\Exception; /** * Exception for Zend_Mail component. * - * @uses Zend\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class RuntimeException extends \RuntimeException implements Exception { -} \ No newline at end of file +} diff --git a/src/Header.php b/src/Header.php new file mode 100755 index 00000000..da180c75 --- /dev/null +++ b/src/Header.php @@ -0,0 +1,39 @@ +getAddressList(); + foreach ($values as $address) { + // split values into name/email + if (!preg_match('/^((?.*?)<(?[^>]+)>|(?.+))$/', $address, $matches)) { + // Should we raise an exception here? + continue; + } + $name = null; + if (isset($matches['name'])) { + $name = trim($matches['name']); + } + if (empty($name)) { + $name = null; + } else { + $name = iconv_mime_decode($name, ICONV_MIME_DECODE_CONTINUE_ON_ERROR); + } + + if (isset($matches['namedEmail'])) { + $email = $matches['namedEmail']; + } + if (isset($matches['email'])) { + $email = $matches['email']; + } + $email = trim($email); // we may have leading whitespace + + // populate address list + $addressList->add($email, $name); + } + + return $header; + } + + /** + * Get field name of this header + * + * @return string + */ + public function getFieldName() + { + return $this->fieldName; + } + + /** + * Get field value of this header + * + * @return string + */ + public function getFieldValue() + { + $emails = array(); + $encoding = $this->getEncoding(); + foreach ($this->getAddressList() as $address) { + $email = $address->getEmail(); + $name = $address->getName(); + if (empty($name)) { + $emails[] = $email; + } else { + if (false !== strstr($name, ',')) { + $name = sprintf('"%s"', $name); + } + + if ('ASCII' !== $encoding) { + $name = HeaderWrap::mimeEncodeValue($name, $encoding, false); + } + $emails[] = sprintf('%s <%s>', $name, $email); + } + } + $string = implode(",\r\n ", $emails); + return $string; + } + + /** + * Set header encoding + * + * @param string $encoding + * @return AbstractAddressList + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Set address list for this header + * + * @param AddressList $addressList + * @return void + */ + public function setAddressList(AddressList $addressList) + { + $this->addressList = $addressList; + } + + /** + * Get address list managed by this header + * + * @return AddressList + */ + public function getAddressList() + { + if (null === $this->addressList) { + $this->setAddressList(new AddressList()); + } + return $this->addressList; + } + + /** + * Serialize to string + * + * @return string + */ + public function toString() + { + $name = $this->getFieldName(); + $value = $this->getFieldValue(); + return sprintf("%s: %s\r\n", $name, $value); + } +} diff --git a/src/Header/Bcc.php b/src/Header/Bcc.php new file mode 100644 index 00000000..6df7fa3e --- /dev/null +++ b/src/Header/Bcc.php @@ -0,0 +1,35 @@ +setType($type); + + if (count($values)) { + foreach ($values as $keyValuePair) { + list($key, $value) = explode('=', $keyValuePair); + $value = trim($value, "\"\' \t\n\r\0\x0B"); + $header->addParameter($key, $value); + } + } + + return $header; + } + + /** + * Get header name + * + * @return string + */ + public function getFieldName() + { + return 'Content-Type'; + } + + /** + * Get header value + * + * @return string + */ + public function getFieldValue() + { + $prepared = $this->type; + if (empty($this->parameters)) { + return $prepared; + } + + $values = array($prepared); + foreach ($this->parameters as $attribute => $value) { + $values[] = sprintf('%s="%s"', $attribute, $value); + } + $value = implode(";\r\n ", $values); + return $value; + } + + /** + * Set header encoding + * + * @param string $encoding + * @return ContentType + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Serialize header to string + * + * @return string + */ + public function toString() + { + return 'Content-Type: ' . $this->getFieldValue() . "\r\n"; + } + + /** + * Set the content type + * + * @param string $type + * @return ContentType + */ + public function setType($type) + { + if (!preg_match('/^[a-z_-]+\/[a-z_-]+$/i', $type)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a value in the format "type/subtype"; received "%s"', + __METHOD__, + (string) $type + )); + } + $this->type = $type; + return $this; + } + + /** + * Retrieve the content type + * + * @return void + */ + public function getType() + { + return $this->type; + } + + /** + * Add a parameter pair + * + * @param string $name + * @param string $value + * @return ContentType + */ + public function addParameter($name, $value) + { + $name = strtolower($name); + $this->parameters[$name] = (string) $value; + return $this; + } + + /** + * Get all parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get a parameter by name + * + * @param string $name + * @return null|string + */ + public function getParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + return $this->parameters[$name]; + } + return null; + } + + /** + * Remove a named parameter + * + * @param string $name + * @return bool + */ + public function removeParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + unset($this->parameters[$name]); + return true; + } + return false; + } +} diff --git a/src/Header/Date.php b/src/Header/Date.php new file mode 100644 index 00000000..9a836630 --- /dev/null +++ b/src/Header/Date.php @@ -0,0 +1,121 @@ +value= $value; + + return $header; + } + + /** + * Get the header name + * + * @return string + */ + public function getFieldName() + { + return 'Date'; + } + + /** + * Get the header value + * + * @return string + */ + public function getFieldValue() + { + return $this->value; + } + + /** + * Set header encoding + * + * @param string $encoding + * @return AbstractAddressList + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Serialize header to string + * + * @return string + */ + public function toString() + { + return 'Date: ' . $this->getFieldValue(); + } +} diff --git a/src/Header/Exception.php b/src/Header/Exception.php new file mode 100644 index 00000000..7e7ba961 --- /dev/null +++ b/src/Header/Exception.php @@ -0,0 +1,35 @@ +setFieldName($fieldName); + } + + if ($fieldValue) { + $this->setFieldValue($fieldValue); + } + } + + /** + * Set header field name + * + * @param string $fieldName + * @return GenericHeader + */ + public function setFieldName($fieldName) + { + if (!is_string($fieldName) || empty($fieldName)) { + throw new Exception\InvalidArgumentException('Header name must be a string'); + } + + // Pre-filter to normalize valid characters, change underscore to dash + $fieldName = str_replace(' ', '-', ucwords(str_replace(array('_', '-'), ' ', $fieldName))); + + // Validate what we have + if (!preg_match('/^[a-z][a-z0-9-]*$/i', $fieldName)) { + throw new Exception\InvalidArgumentException('Header name must start with a letter, and consist of only letters, numbers, and dashes'); + } + + $this->fieldName = $fieldName; + return $this; + } + + /** + * Retrieve header field name + * + * @return string + */ + public function getFieldName() + { + return $this->fieldName; + } + + /** + * Set header field value + * + * @param string $fieldValue + * @return GenericHeader + */ + public function setFieldValue($fieldValue) + { + $fieldValue = (string) $fieldValue; + + if (empty($fieldValue) || preg_match('/^\s+$/', $fieldValue)) { + $fieldValue = ''; + } + + $this->fieldValue = $fieldValue; + return $this; + } + + /** + * Retrieve header field value + * + * @return string + */ + public function getFieldValue() + { + return $this->fieldValue; + } + + /** + * Set header encoding + * + * @param string $encoding + * @return GenericHeader + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Cast to string as a well formed HTTP header line + * + * Returns in form of "NAME: VALUE\r\n" + * + * @return string + */ + public function toString() + { + $name = $this->getFieldName(); + $value = $this->getFieldValue(); + + return $name. ': ' . $value . "\r\n"; + } +} diff --git a/src/Header/GenericMultiHeader.php b/src/Header/GenericMultiHeader.php new file mode 100644 index 00000000..26bafe78 --- /dev/null +++ b/src/Header/GenericMultiHeader.php @@ -0,0 +1,209 @@ +setFieldName($fieldName); + } + + if ($fieldValue) { + $this->setFieldValue($fieldValue); + } + } + + /** + * Set header name + * + * @param string $fieldName + * @return GenericHeader + */ + public function setFieldName($fieldName) + { + if (!is_string($fieldName) || empty($fieldName)) { + throw new Exception\InvalidArgumentException('Header name must be a string'); + } + + // Pre-filter to normalize valid characters, change underscore to dash + $fieldName = str_replace(' ', '-', ucwords(str_replace(array('_', '-'), ' ', $fieldName))); + + // Validate what we have + if (!preg_match('/^[a-z][a-z0-9-]*$/i', $fieldName)) { + throw new Exception\InvalidArgumentException('Header name must start with a letter, and consist of only letters, numbers, and dashes'); + } + + $this->fieldName = $fieldName; + return $this; + } + + /** + * Retrieve header name + * + * @return string + */ + public function getFieldName() + { + return $this->fieldName; + } + + /** + * Set header value + * + * @param string|array $fieldValue + * @return GenericHeader + */ + public function setFieldValue($fieldValue) + { + $fieldValue = (string) $fieldValue; + + if (empty($fieldValue) || preg_match('/^\s+$/', $fieldValue)) { + $fieldValue = ''; + } + + $this->fieldValue = $fieldValue; + return $this; + } + + /** + * Retrieve header value + * + * @return string + */ + public function getFieldValue() + { + return $this->fieldValue; + } + + /** + * Set header encoding + * + * @param string $encoding + * @return GenericMultiHeader + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Cast to string + * + * Returns in form of "NAME: VALUE\r\n" + * + * @return string + */ + public function toString() + { + $name = $this->getFieldName(); + $value = $this->getFieldValue(); + + return $name. ': ' . $value . "\r\n"; + } + + /** + * Cast multiple header objectss to a single string header + * + * @param array $headers + * @return string + */ + public function toStringMultipleHeaders(array $headers) + { + $name = $this->getFieldName(); + $values = array($this->getFieldValue()); + foreach ($headers as $header) { + if (!$header instanceof static) { + throw new Exception\InvalidArgumentException('This method toStringMultipleHeaders was expecting an array of headers of the same type'); + } + $values[] = $header->getFieldValue(); + } + return $name. ': ' . implode(',', $values) . "\r\n"; + } +} diff --git a/src/Header/HeaderWrap.php b/src/Header/HeaderWrap.php new file mode 100644 index 00000000..a553dd6d --- /dev/null +++ b/src/Header/HeaderWrap.php @@ -0,0 +1,125 @@ +getDelimiter(); + + $length = strlen($value); + $lines = array(); + $temp = ''; + for ($i = 0; $i < $length; $i++) { + $temp .= $value[$i]; + if ($value[$i] == $delimiter) { + $lines[] = $temp; + $temp = ''; + } + } + return implode("\r\n ", $lines); + } + + /** + * MIME-encode a value + * + * Performs quoted-printable encoding on a value, setting maximum + * line-length to 998. + * + * @param string $value + * @param string $encoding + * @param bool $splitWords Whether or not to split the $value on whitespace + * and encode each word separately. + * @return string + */ + public static function mimeEncodeValue($value, $encoding, $splitWords = false) + { + if ($splitWords) { + $words = array_map(function($word) use ($encoding) { + $header = iconv_mime_encode('Header', $word, array( + 'scheme' => 'Q', + 'line-length' => 78, + 'output-charset' => $encoding, + )); + return str_replace('Header: ', '', $header); + }, explode(' ', $value)); + return implode("\r\n ", $words); + } + + $header = iconv_mime_encode('Header', $value, array( + 'scheme' => 'Q', + 'line-length' => 998, + 'output-charset' => $encoding, + )); + return str_replace('Header: ', '', $header); + } +} diff --git a/src/Header/MimeVersion.php b/src/Header/MimeVersion.php new file mode 100644 index 00000000..1781abbe --- /dev/null +++ b/src/Header/MimeVersion.php @@ -0,0 +1,136 @@ +\d+\.\d+)$/', $value, $matches)) { + $header->version = $matches['version']; + } + + return $header; + } + + /** + * Get the field name + * + * @return string + */ + public function getFieldName() + { + return 'Mime-Version'; + } + + /** + * Get the field value (version string) + * + * @return string + */ + public function getFieldValue() + { + return $this->version; + } + + /** + * Set character encoding + * + * @param string $encoding + * @return void + */ + public function setEncoding($encoding) + { + // irrelevant to this implementation + } + + /** + * Get character encoding + * + * @return void + */ + public function getEncoding() + { + // irrelevant to this implementation + } + + /** + * Serialize to string + * + * @return string + */ + public function toString() + { + return 'Mime-Version: ' . $this->getFieldValue(); + } + + /** + * Set the version string used in this header + * + * @param string $version + * @return MimeVersion + */ + public function setVersion($version) + { + $this->version = $version; + return $this; + } + + /** + * Retrieve the version string for this header + * + * @return string + */ + public function getVersion() + { + return $this->version; + } +} diff --git a/src/Header/MultipleHeaderDescription.php b/src/Header/MultipleHeaderDescription.php new file mode 100755 index 00000000..c3021b17 --- /dev/null +++ b/src/Header/MultipleHeaderDescription.php @@ -0,0 +1,35 @@ +value= $value; + + return $header; + } + + /** + * Get header name + * + * @return string + */ + public function getFieldName() + { + return 'Received'; + } + + /** + * Get header value + * + * @return string + */ + public function getFieldValue() + { + return $this->value; + } + + /** + * Set header encoding + * + * @param string $encoding + * @return AbstractAddressList + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Serialize to string + * + * @return string + */ + public function toString() + { + return 'Received: ' . $this->getFieldValue(); + } + + /** + * Serialize collection of Received headers to string + * + * @param array $headers + * @return string + */ + public function toStringMultipleHeaders(array $headers) + { + $strings = array($this->toString()); + foreach ($headers as $header) { + if (!$header instanceof Received) { + throw new Exception\RuntimeException( + 'The Received multiple header implementation can only accept an array of Received headers' + ); + } + $strings[] = $header->toString(); + } + return implode("\r\n", $strings); + } +} diff --git a/src/Header/ReplyTo.php b/src/Header/ReplyTo.php new file mode 100644 index 00000000..09583291 --- /dev/null +++ b/src/Header/ReplyTo.php @@ -0,0 +1,35 @@ +.*?)<(?[^>]+)>$', $value, $matches)) { + $name = $matches['name']; + if (empty($name)) { + $name = null; + } else { + $name = iconv_mime_decode($name, ICONV_MIME_DECODE_CONTINUE_ON_ERROR); + } + $header->setAddress($matches['email'], $name); + } + + return $header; + } + + /** + * Get header name + * + * @return string + */ + public function getFieldName() + { + return 'Sender'; + } + + /** + * Get header value + * + * @return string + */ + public function getFieldValue() + { + if (!$this->address instanceof AddressDescription) { + return ''; + } + + $email = sprintf('<%s>', $this->address->getEmail()); + $name = $this->address->getName(); + if (!empty($name)) { + $encoding = $this->getEncoding(); + if ('ASCII' !== $encoding) { + $name = HeaderWrap::mimeEncodeValue($name, $encoding, false); + } + $email = sprintf('%s %s', $name, $email); + } + return $email; + } + + /** + * Set header encoding + * + * @param string $encoding + * @return Sender + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Serialize to string + * + * @return string + */ + public function toString() + { + return 'Sender: ' . $this->getFieldValue(); + } + + /** + * Set the address used in this header + * + * @param string|AddressDescription $emailOrAddress + * @param null|string $name + * @return Sender + */ + public function setAddress($emailOrAddress, $name = null) + { + if (is_string($emailOrAddress)) { + $emailOrAddress = new Address($emailOrAddress, $name); + } + if (!$emailOrAddress instanceof AddressDescription) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string or AddressDescription object; received "%s"', + __METHOD__, + (is_object($emailOrAddress) ? get_class($emailOrAddress) : gettype($emailOrAddress)) + )); + } + $this->address = $emailOrAddress; + return $this; + } + + /** + * Retrieve the internal address from this header + * + * @return AddressDescription|null + */ + public function getAddress() + { + return $this->address; + } +} diff --git a/src/Header/StructuredHeader.php b/src/Header/StructuredHeader.php new file mode 100644 index 00000000..8c210f80 --- /dev/null +++ b/src/Header/StructuredHeader.php @@ -0,0 +1,39 @@ +setSubject($value); + + return $header; + } + + /** + * Get the header name + * + * @return string + */ + public function getFieldName() + { + return 'Subject'; + } + + /** + * Get the header value + * + * @return string + */ + public function getFieldValue() + { + $encoding = $this->getEncoding(); + if ($encoding == 'ASCII') { + return HeaderWrap::wrap($this->subject, $this); + } + return HeaderWrap::mimeEncodeValue($this->subject, $encoding, true); + } + + /** + * Set header encoding + * + * @param string $encoding + * @return Subject + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Set the value of the header + * + * @param string $subject + * @return Subject + */ + public function setSubject($subject) + { + $this->subject = (string) $subject; + return $this; + } + + /** + * String representation of header + * + * @return string + */ + public function toString() + { + return 'Subject: ' . $this->getFieldValue(); + } +} diff --git a/src/Header/To.php b/src/Header/To.php new file mode 100644 index 00000000..b2126e05 --- /dev/null +++ b/src/Header/To.php @@ -0,0 +1,35 @@ +[^()><@,;:\"\\/\[\]?=}{ \t]+):.*$/', $line, $matches)) { + if ($current) { + // a header name was present, then store the current complete line + $headers->headersKeys[] = str_replace(array('-', '_'), '', strtolower($current['name'])); + $headers->headers[] = $current; + } + $current = array( + 'name' => $matches['name'], + 'line' => trim($line) + ); + } elseif (preg_match('/^\s+.*$/', $line, $matches)) { + // continuation: append to current line + $current['line'] .= trim($line); + } elseif (preg_match('/^\s*$/', $line)) { + // empty line indicates end of headers + break; + } else { + // Line does not match header format! + throw new Exception\RuntimeException(sprintf( + 'Line "%s"does not match header format!', + $line + )); + } + } + if ($current) { + $headers->headersKeys[] = str_replace(array('-', '_'), '', strtolower($current['name'])); + $headers->headers[] = $current; + } + return $headers; + } + + /** + * Set an alternate implementation for the PluginClassLoader + * + * @param PluginClassLocator $pluginClassLoader + * @return Headers + */ + public function setPluginClassLoader(PluginClassLocator $pluginClassLoader) + { + $this->pluginClassLoader = $pluginClassLoader; + return $this; + } + + /** + * Return an instance of a PluginClassLocator, lazyload and inject map if necessary + * + * @return PluginClassLocator + */ + public function getPluginClassLoader() + { + if ($this->pluginClassLoader === null) { + $this->pluginClassLoader = new PluginClassLoader(array( + 'bcc' => 'Zend\Mail\Header\Bcc', + 'cc' => 'Zend\Mail\Header\Cc', + 'contenttype' => 'Zend\Mail\Header\ContentType', + 'content_type' => 'Zend\Mail\Header\ContentType', + 'content-type' => 'Zend\Mail\Header\ContentType', + 'date' => 'Zend\Mail\Header\Date', + 'from' => 'Zend\Mail\Header\From', + 'mimeversion' => 'Zend\Mail\Header\MimeVersion', + 'mime_version' => 'Zend\Mail\Header\MimeVersion', + 'mime-version' => 'Zend\Mail\Header\MimeVersion', + 'received' => 'Zend\Mail\Header\Received', + 'replyto' => 'Zend\Mail\Header\ReplyTo', + 'reply_to' => 'Zend\Mail\Header\ReplyTo', + 'reply-to' => 'Zend\Mail\Header\ReplyTo', + 'sender' => 'Zend\Mail\Header\Sender', + 'subject' => 'Zend\Mail\Header\Subject', + 'to' => 'Zend\Mail\Header\To', + )); + } + return $this->pluginClassLoader; + } + + /** + * Set the header encoding + * + * @param string $encoding + * @return Headers + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + foreach ($this as $header) { + $header->setEncoding($encoding); + } + return $this; + } + + /** + * Get the header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Add many headers at once + * + * Expects an array (or Traversable object) of type/value pairs. + * + * @param array|Traversable $headers + * @return Headers + */ + public function addHeaders($headers) + { + if (!is_array($headers) && !$headers instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + 'Expected array or Traversable; received "%s"', + (is_object($headers) ? get_class($headers) : gettype($headers)) + )); + } + + foreach ($headers as $name => $value) { + if (is_int($name)) { + if (is_string($value)) { + $this->addHeaderLine($value); + } elseif (is_array($value) && count($value) == 1) { + $this->addHeaderLine(key($value), current($value)); + } elseif (is_array($value) && count($value) == 2) { + $this->addHeaderLine($value[0], $value[1]); + } elseif ($value instanceof Header) { + $this->addHeader($value); + } + } elseif (is_string($name)) { + $this->addHeaderLine($name, $value); + } + + } + + return $this; + } + + /** + * Add a raw header line, either in name => value, or as a single string 'name: value' + * + * This method allows for lazy-loading in that the parsing and instantiation of Header object + * will be delayed until they are retrieved by either get() or current() + * + * @throws Exception\InvalidArgumentException + * @param string $headerFieldNameOrLine + * @param string $fieldValue optional + * @return Headers + */ + public function addHeaderLine($headerFieldNameOrLine, $fieldValue = null) + { + if (!is_string($headerFieldNameOrLine)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects its first argument to be a string; received "%s"', + (is_object($headerFieldNameOrLine) ? get_class($headerFieldNameOrLine) : gettype($headerFieldNameOrLine)) + )); + } + + $matches = null; + if (preg_match('/^(?P[^()><@,;:\"\\/\[\]?=}{ \t]+):.*$/', $headerFieldNameOrLine, $matches) + && $fieldValue === null + ) { + // is a header + $headerName = $matches['name']; + $headerKey = str_replace(array('-', '_', ' ', '.'), '', strtolower($matches['name'])); + $line = $headerFieldNameOrLine; + } elseif ($fieldValue === null) { + throw new Exception\InvalidArgumentException('A field name was provided without a field value'); + } else { + $headerName = $headerFieldNameOrLine; + $headerKey = str_replace(array('-', '_', ' ', '.'), '', strtolower($headerFieldNameOrLine)); + $line = $headerFieldNameOrLine . ': ' . $fieldValue; + } + + $this->headersKeys[] = $headerKey; + $this->headers[] = array('name' => $headerName, 'line' => $line); + return $this; + } + + /** + * Add a Header to this container, for raw values @see addHeaderLine() and addHeaders() + * + * @param Header $header + * @return Headers + */ + public function addHeader(Header $header) + { + $key = $this->normalizeFieldName($header->getFieldName()); + + $this->headersKeys[] = $key; + $this->headers[] = $header; + $header->setEncoding($this->getEncoding()); + return $this; + } + + /** + * Remove a Header from the container + * + * @param Header $header + * @return bool + */ + public function removeHeader(Header $header) + { + $index = array_search($header, $this->headers, true); + if ($index !== false) { + unset($this->headersKeys[$index]); + unset($this->headers[$index]); + return true; + } + return false; + } + + /** + * Clear all headers + * + * Removes all headers from queue + * + * @return Headers + */ + public function clearHeaders() + { + $this->headers = $this->headersKeys = array(); + return $this; + } + + /** + * Get all headers of a certain name/type + * + * @param string $name + * @return false|Header|ArrayIterator + */ + public function get($name) + { + $key = $this->normalizeFieldName($name); + if (!in_array($key, $this->headersKeys)) { + return false; + } + + $class = ($this->getPluginClassLoader()->load($key)) ?: 'Zend\Mail\Header\GenericHeader'; + + if (in_array('Zend\Mail\Header\MultipleHeaderDescription', class_implements($class, true))) { + $headers = array(); + foreach (array_keys($this->headersKeys, $key) as $index) { + if (is_array($this->headers[$index])) { + $this->lazyLoadHeader($index); + } + } + foreach (array_keys($this->headersKeys, $key) as $index) { + $headers[] = $this->headers[$index]; + } + return new ArrayIterator($headers); + } else { + $index = array_search($key, $this->headersKeys); + if ($index === false) { + return false; + } + if (is_array($this->headers[$index])) { + return $this->lazyLoadHeader($index); + } else { + return $this->headers[$index]; + } + } + } + + /** + * Test for existence of a type of header + * + * @param string $name + * @return bool + */ + public function has($name) + { + $name = $this->normalizeFieldName($name); + return (in_array($name, $this->headersKeys)); + } + + /** + * Advance the pointer for this object as an interator + * + * @return void + */ + public function next() + { + next($this->headers); + } + + /** + * Return the current key for this object as an interator + * + * @return mixed + */ + public function key() + { + return (key($this->headers)); + } + + /** + * Is this iterator still valid? + * + * @return bool + */ + public function valid() + { + return (current($this->headers) !== false); + } + + /** + * Reset the internal pointer for this object as an iterator + * + * @return void + */ + public function rewind() + { + reset($this->headers); + } + + /** + * Return the current value for this iterator, lazy loading it if need be + * + * @return Header + */ + public function current() + { + $current = current($this->headers); + if (is_array($current)) { + $current = $this->lazyLoadHeader(key($this->headers)); + } + return $current; + } + + /** + * Return the number of headers in this contain, if all headers have not been parsed, actual count could + * increase if MultipleHeader objects exist in the Request/Response. If you need an exact count, iterate + * + * @return int count of currently known headers + */ + public function count() + { + return count($this->headers); + } + + /** + * Render all headers at once + * + * This method handles the normal iteration of headers; it is up to the + * concrete classes to prepend with the appropriate status/request line. + * + * @return string + */ + public function toString() + { + $headers = ''; + foreach ($this->toArray() as $fieldName => $fieldValue) { + if (is_array($fieldValue)) { + // Handle multi-value headers + foreach ($fieldValue as $value) { + $headers .= $fieldName . ': ' . $value . "\r\n"; + } + continue; + } + // Handle single-value headers + $headers .= $fieldName . ': ' . $fieldValue . "\r\n"; + } + return $headers; + } + + /** + * Return the headers container as an array + * + * @todo determine how to produce single line headers, if they are supported + * @return array + */ + public function toArray() + { + $headers = array(); + /* @var $header Header */ + foreach ($this->headers as $header) { + if ($header instanceof Header\MultipleHeaderDescription) { + $name = $header->getFieldName(); + if (!isset($headers[$name])) { + $headers[$name] = array(); + } + $headers[$name][] = $header->getFieldValue(); + } elseif ($header instanceof Header) { + $headers[$header->getFieldName()] = $header->getFieldValue(); + } else { + $matches = null; + preg_match('/^(?P[^()><@,;:\"\\/\[\]?=}{ \t]+):\s*(?P.*)$/', $header['line'], $matches); + if ($matches) { + $headers[$matches['name']] = $matches['value']; + } + } + } + return $headers; + } + + /** + * By calling this, it will force parsing and loading of all headers, after this count() will be accurate + * + * @return bool + */ + public function forceLoading() + { + foreach ($this as $item) { + // $item should now be loaded + } + return true; + } + + /** + * @param $index + * @return mixed|void + */ + protected function lazyLoadHeader($index) + { + $current = $this->headers[$index]; + + $key = $this->headersKeys[$index]; + /* @var $class Header */ + $class = ($this->getPluginClassLoader()->load($key)) ?: 'Zend\Mail\Header\GenericHeader'; + + $encoding = $this->getEncoding(); + $headers = $class::fromString($current['line']); + if (is_array($headers)) { + $current = array_shift($headers); + $current->setEncoding($encoding); + $this->headers[$index] = $current; + foreach ($headers as $header) { + $header->setEncoding($encoding); + $this->headersKeys[] = $key; + $this->headers[] = $header; + } + return $current; + } + + $current = $headers; + $current->setEncoding($encoding); + $this->headers[$index] = $current; + return $current; + } + + /** + * Normalize a field name + * + * @param string $fieldName + * @return string + */ + protected function normalizeFieldName($fieldName) + { + return str_replace(array('-', '_', ' ', '.'), '', strtolower($fieldName)); + } +} diff --git a/src/Mail.php b/src/Mail.php deleted file mode 100644 index 3b3c3a9a..00000000 --- a/src/Mail.php +++ /dev/null @@ -1,1226 +0,0 @@ -_charset = $charset; - } - } - - /** - * Set the transport object - * - * @param AbstractTransport $transport - * @return \Zend\Mail\Mail - */ - public function setTransport(AbstractTransport $transport) - { - $this->transport = $transport; - return $this; - } - - /** - * Get transport object - * - * If no transport object is set, will set and return the global default - * transport object - * - * @return \Zend\Mail\AbstractTransport - */ - public function getTransport() - { - if (! $this->transport) { - $this->transport = self::getDefaultTransport(); - } - - return $this->transport; - } - - /** - * Return charset string - * - * @return string - */ - public function getCharset() - { - return $this->_charset; - } - - /** - * Set content type - * - * Should only be used for manually setting multipart content types. - * - * @param string $type Content type - * @return \Zend\Mail\Mail Implements fluent interface - * @throws Zend_Mail_Exception for types not supported by \Zend\Mime\Mime - */ - public function setType($type) - { - $allowed = array( - Mime\Mime::MULTIPART_ALTERNATIVE, - Mime\Mime::MULTIPART_MIXED, - Mime\Mime::MULTIPART_RELATED, - ); - if (!in_array($type, $allowed)) { - throw new Exception\InvalidArgumentException('Invalid content type "' . $type . '"'); - } - - $this->_type = $type; - return $this; - } - - /** - * Get content type of the message - * - * @return string - */ - public function getType() - { - return $this->_type; - } - - /** - * Set an arbitrary mime boundary for the message - * - * If not set, Zend_Mime will generate one. - * - * @param string $boundary - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function setMimeBoundary($boundary) - { - $this->_mimeBoundary = $boundary; - - return $this; - } - - /** - * Return the boundary string used for the message - * - * @return string - */ - public function getMimeBoundary() - { - return $this->_mimeBoundary; - } - - /** - * Return the encoding of mail headers - * - * Either Zend_Mime::ENCODING_QUOTEDPRINTABLE or Zend_Mime::ENCODING_BASE64 - * - * @return string - */ - public function getHeaderEncoding() - { - return $this->_headerEncoding; - } - - /** - * Set the encoding of mail headers - * - * @param string $encoding \Zend\Mime\Mime::ENCODING_QUOTEDPRINTABLE or \Zend\Mime\Mime::ENCODING_BASE64 - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function setHeaderEncoding($encoding) - { - $allowed = array( - Mime\Mime::ENCODING_BASE64, - Mime\Mime::ENCODING_QUOTEDPRINTABLE - ); - if (!in_array($encoding, $allowed)) { - throw new Exception\InvalidArgumentException('Invalid encoding "' . $encoding . '"'); - } - $this->_headerEncoding = $encoding; - - return $this; - } - - /** - * Sets the text body for the message. - * - * @param string $txt - * @param string $charset - * @param string $encoding - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function setBodyText($txt, $charset = null, $encoding = Mime\Mime::ENCODING_QUOTEDPRINTABLE) - { - if ($charset === null) { - $charset = $this->_charset; - } - - $mp = new Mime\Part($txt); - $mp->encoding = $encoding; - $mp->type = Mime\Mime::TYPE_TEXT; - $mp->disposition = Mime\Mime::DISPOSITION_INLINE; - $mp->charset = $charset; - - $this->_bodyText = $mp; - - return $this; - } - - /** - * Return text body Zend_Mime_Part or string - * - * @param bool textOnly Whether to return just the body text content or the MIME part; defaults to false, the MIME part - * @return false|\Zend\Mime\Part|string - */ - public function getBodyText($textOnly = false) - { - if ($textOnly && $this->_bodyText) { - $body = $this->_bodyText; - return $body->getContent(); - } - - return $this->_bodyText; - } - - /** - * Sets the HTML body for the message - * - * @param string $html - * @param string $charset - * @param string $encoding - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function setBodyHtml($html, $charset = null, $encoding = Mime\Mime::ENCODING_QUOTEDPRINTABLE) - { - if ($charset === null) { - $charset = $this->_charset; - } - - $mp = new Mime\Part($html); - $mp->encoding = $encoding; - $mp->type = Mime\Mime::TYPE_HTML; - $mp->disposition = Mime\Mime::DISPOSITION_INLINE; - $mp->charset = $charset; - - $this->_bodyHtml = $mp; - - return $this; - } - - /** - * Return Zend_Mime_Part representing body HTML - * - * @param bool $htmlOnly Whether to return the body HTML only, or the MIME part; defaults to false, the MIME part - * @return false|\Zend\Mime\Part|string - */ - public function getBodyHtml($htmlOnly = false) - { - if ($htmlOnly && $this->_bodyHtml) { - $body = $this->_bodyHtml; - return $body->getContent(); - } - - return $this->_bodyHtml; - } - - /** - * Adds an existing attachment to the mail message - * - * @param \Zend\Mime\Part $attachment - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function addAttachment(Mime\Part $attachment) - { - $this->addPart($attachment); - $this->hasAttachments = true; - - return $this; - } - - /** - * Creates a Zend_Mime_Part attachment - * - * Attachment is automatically added to the mail object after creation. The - * attachment object is returned to allow for further manipulation. - * - * @param string $body - * @param string $mimeType - * @param string $disposition - * @param string $encoding - * @param string $filename OPTIONAL A filename for the attachment - * @return \Zend\Mime\Part Newly created \Zend\Mime\Part object (to allow - * advanced settings) - */ - public function createAttachment($body, - $mimeType = Mime\Mime::TYPE_OCTETSTREAM, - $disposition = Mime\Mime::DISPOSITION_ATTACHMENT, - $encoding = Mime\Mime::ENCODING_BASE64, - $filename = null) - { - - $mp = new Mime\Part($body); - $mp->encoding = $encoding; - $mp->type = $mimeType; - $mp->disposition = $disposition; - $mp->filename = $filename; - - $this->addAttachment($mp); - - return $mp; - } - - /** - * Return a count of message parts - * - * @return integer - */ - public function getPartCount() - { - return count($this->_parts); - } - - /** - * Encode header fields - * - * Encodes header content according to RFC1522 if it contains non-printable - * characters. - * - * @param string $value - * @return string - */ - protected function _encodeHeader($value) - { - if (Mime\Mime::isPrintable($value) === false) { - if ($this->getHeaderEncoding() === Mime\Mime::ENCODING_QUOTEDPRINTABLE) { - $value = Mime\Mime::encodeQuotedPrintableHeader($value, - $this->getCharset(), - Mime\Mime::LINELENGTH, - Mime\Mime::LINEEND); - } else { - $value = Mime\Mime::encodeBase64Header($value, - $this->getCharset(), - Mime\Mime::LINELENGTH, - Mime\Mime::LINEEND); - } - } - - return $value; - } - - /** - * Add a header to the message - * - * Adds a header to this message. If append is true and the header already - * exists, raises a flag indicating that the header should be appended. - * - * @param string $headerName - * @param string $value - * @param bool $append - */ - protected function _storeHeader($headerName, $value, $append = false) - { - if (isset($this->_headers[$headerName])) { - $this->_headers[$headerName][] = $value; - } else { - $this->_headers[$headerName] = array($value); - } - - if ($append) { - $this->_headers[$headerName]['append'] = true; - } - - } - - /** - * Clear header from the message - * - * @param string $headerName - */ - protected function _clearHeader($headerName) - { - if (isset($this->_headers[$headerName])){ - unset($this->_headers[$headerName]); - } - } - - /** - * Helper function for adding a recipient and the corresponding header - * - * @param string $headerName - * @param string $email - * @param string $name - */ - protected function _addRecipientAndHeader($headerName, $email, $name) - { - $email = $this->_filterEmail($email); - $name = $this->_filterName($name); - // prevent duplicates - $this->_recipients[$email] = 1; - $this->_storeHeader($headerName, $this->_formatAddress($email, $name), true); - } - - /** - * Adds To-header and recipient, $email can be an array, or a single string address - * - * @param string|array $email - * @param string $name - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function addTo($email, $name='') - { - if (!is_array($email)) { - $email = array($name => $email); - } - - foreach ($email as $n => $recipient) { - $this->_addRecipientAndHeader('To', $recipient, is_int($n) ? '' : $n); - $this->_to[] = $recipient; - } - - return $this; - } - - /** - * Adds Cc-header and recipient, $email can be an array, or a single string address - * - * @param string|array $email - * @param string $name - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function addCc($email, $name='') - { - if (!is_array($email)) { - $email = array($name => $email); - } - - foreach ($email as $n => $recipient) { - $this->_addRecipientAndHeader('Cc', $recipient, is_int($n) ? '' : $n); - } - - return $this; - } - - /** - * Adds Bcc recipient, $email can be an array, or a single string address - * - * @param string|array $email - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function addBcc($email) - { - if (!is_array($email)) { - $email = array($email); - } - - foreach ($email as $recipient) { - $this->_addRecipientAndHeader('Bcc', $recipient, ''); - } - - return $this; - } - - /** - * Return list of recipient email addresses - * - * @return array (of strings) - */ - public function getRecipients() - { - return array_keys($this->_recipients); - } - - /** - * Clears list of recipient email addresses - * - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function clearRecipients() - { - $this->_recipients = array(); - $this->_to = array(); - - $this->_clearHeader('To'); - $this->_clearHeader('Cc'); - $this->_clearHeader('Bcc'); - - return $this; - } - - /** - * Sets From-header and sender of the message - * - * @param string $email - * @param string $name - * @return \Zend\Mail\Mail Provides fluent interface - * @throws \Zend\Mail\Exception if called subsequent times - */ - public function setFrom($email, $name = null) - { - if (null !== $this->_from) { - throw new Exception\InvalidArgumentException('From Header set twice'); - } - - $email = $this->_filterEmail($email); - $name = $this->_filterName($name); - $this->_from = $email; - $this->_storeHeader('From', $this->_formatAddress($email, $name), true); - - return $this; - } - - /** - * Set Reply-To Header - * - * @param string $email - * @param string $name - * @return \Zend\Mail\Mail - * @throws \Zend\Mail\Exception if called more than one time - */ - public function setReplyTo($email, $name = null) - { - if (null !== $this->_replyTo) { - throw new Exception\InvalidArgumentException('Reply-To Header set twice'); - } - - $email = $this->_filterEmail($email); - $name = $this->_filterName($name); - $this->_replyTo = $email; - $this->_storeHeader('Reply-To', $this->_formatAddress($email, $name), true); - - return $this; - } - - /** - * Returns the sender of the mail - * - * @return string - */ - public function getFrom() - { - return $this->_from; - } - - /** - * Returns the current Reply-To address of the message - * - * @return string|null Reply-To address, null when not set - */ - public function getReplyTo() - { - return $this->_replyTo; - } - - /** - * Clears the sender from the mail - * - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function clearFrom() - { - $this->_from = null; - $this->_clearHeader('From'); - - return $this; - } - - /** - * Clears the current Reply-To address from the message - * - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function clearReplyTo() - { - $this->_replyTo = null; - $this->_clearHeader('Reply-To'); - - return $this; - } - - /** - * Sets Default From-email and name of the message - * - * @param string $email - * @param string Optional $name - * @return void - */ - public static function setDefaultFrom($email, $name = null) - { - self::$_defaultFrom = array('email' => $email, 'name' => $name); - } - - /** - * Returns the default sender of the mail - * - * @return null|array Null if none was set. - */ - public static function getDefaultFrom() - { - return self::$_defaultFrom; - } - - /** - * Clears the default sender from the mail - * - * @return void - */ - public static function clearDefaultFrom() - { - self::$_defaultFrom = null; - } - - /** - * Sets From-name and -email based on the defaults - * - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function setFromToDefaultFrom() { - $from = self::getDefaultFrom(); - if($from === null) { - throw new Exception\RuntimeException( - 'No default From Address set to use'); - } - - $this->setFrom($from['email'], $from['name']); - - return $this; - } - - /** - * Sets Default ReplyTo-address and -name of the message - * - * @param string $email - * @param string Optional $name - * @return void - */ - public static function setDefaultReplyTo($email, $name = null) - { - self::$_defaultReplyTo = array('email' => $email, 'name' => $name); - } - - /** - * Returns the default Reply-To Address and Name of the mail - * - * @return null|array Null if none was set. - */ - public static function getDefaultReplyTo() - { - return self::$_defaultReplyTo; - } - - /** - * Clears the default ReplyTo-address and -name from the mail - * - * @return void - */ - public static function clearDefaultReplyTo() - { - self::$_defaultReplyTo = null; - } - - /** - * Sets ReplyTo-name and -email based on the defaults - * - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function setReplyToFromDefault() { - $replyTo = self::getDefaultReplyTo(); - if($replyTo === null) { - throw new Exception\RuntimeException( - 'No default Reply-To Address set to use'); - } - - $this->setReplyTo($replyTo['email'], $replyTo['name']); - - return $this; - } - - /** - * Sets the Return-Path header of the message - * - * @param string $email - * @return \Zend\Mail\Mail Provides fluent interface - * @throws \Zend\Mail\Exception if set multiple times - */ - public function setReturnPath($email) - { - if ($this->_returnPath === null) { - $email = $this->_filterEmail($email); - $this->_returnPath = $email; - $this->_storeHeader('Return-Path', $email, false); - } else { - throw new Exception\InvalidArgumentException('Return-Path Header set twice'); - } - return $this; - } - - /** - * Returns the current Return-Path address of the message - * - * If no Return-Path header is set, returns the value of {@link $_from}. - * - * @return string - */ - public function getReturnPath() - { - if (null !== $this->_returnPath) { - return $this->_returnPath; - } - - return $this->_from; - } - - /** - * Clears the current Return-Path address from the message - * - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function clearReturnPath() - { - $this->_returnPath = null; - $this->_clearHeader('Return-Path'); - - return $this; - } - - /** - * Sets the subject of the message - * - * @param string $subject - * @return \Zend\Mail\Mail Provides fluent interface - * @throws \Zend\Mail\Exception - */ - public function setSubject($subject) - { - if ($this->_subject === null) { - $subject = $this->_filterOther($subject); - $this->_subject = $this->_encodeHeader($subject); - $this->_storeHeader('Subject', $this->_subject); - } else { - throw new Exception\InvalidArgumentException('Subject set twice'); - } - return $this; - } - - /** - * Returns the encoded subject of the message - * - * @return string - */ - public function getSubject() - { - return $this->_subject; - } - - /** - * Clears the encoded subject from the message - * - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function clearSubject() - { - $this->_subject = null; - $this->_clearHeader('Subject'); - - return $this; - } - - /** - * Sets Date-header - * - * @param timestamp|string|\Zend\Date\Date $date - * @return \Zend\Mail\Mail Provides fluent interface - * @throws \Zend\Mail\Exception if called subsequent times or wrong date format. - */ - public function setDate($date = null) - { - if ($this->_date === null) { - if ($date === null) { - $date = date('r'); - } else if (is_int($date)) { - $date = date('r', $date); - } else if (is_string($date)) { - $date = strtotime($date); - if ($date === false || $date < 0) { - throw new Exception\InvalidArgumentException('String representations of Date Header must be ' . - 'strtotime()-compatible'); - } - $date = date('r', $date); - } else if ($date instanceof Date\Date) { - $date = $date->get(Date\Date::RFC_2822); - } else { - throw new Exception\InvalidArgumentException(__METHOD__ . ' only accepts UNIX timestamps, Zend_Date objects, ' . - ' and strtotime()-compatible strings'); - } - $this->_date = $date; - $this->_storeHeader('Date', $date); - } else { - throw new Exception\InvalidArgumentException('Date Header set twice'); - } - return $this; - } - - /** - * Returns the formatted date of the message - * - * @return string - */ - public function getDate() - { - return $this->_date; - } - - /** - * Clears the formatted date from the message - * - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function clearDate() - { - $this->_date = null; - $this->_clearHeader('Date'); - - return $this; - } - - /** - * Sets the Message-ID of the message - * - * @param boolean|string $id - * true :Auto - * false :No set - * null :No set - * string:Sets given string (Angle brackets is not necessary) - * @return \Zend\Mail\Mail Provides fluent interface - * @throws \Zend\Mail\Exception - */ - public function setMessageId($id = true) - { - if ($id === null || $id === false) { - return $this; - } elseif ($id === true) { - $id = $this->createMessageId(); - } - - if ($this->_messageId === null) { - $id = $this->_filterOther($id); - $this->_messageId = $id; - $this->_storeHeader('Message-Id', '<' . $this->_messageId . '>'); - } else { - throw new Exception\InvalidArgumentException('Message-ID set twice'); - } - - return $this; - } - - /** - * Returns the Message-ID of the message - * - * @return string - */ - public function getMessageId() - { - return $this->_messageId; - } - - - /** - * Clears the Message-ID from the message - * - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function clearMessageId() - { - $this->_messageId = null; - $this->_clearHeader('Message-Id'); - - return $this; - } - - /** - * Creates the Message-ID - * - * @return string - */ - public function createMessageId() { - - $time = time(); - - if ($this->_from !== null) { - $user = $this->_from; - } elseif (isset($_SERVER['REMOTE_ADDR'])) { - $user = $_SERVER['REMOTE_ADDR']; - } else { - $user = getmypid(); - } - - $rand = mt_rand(); - - if ($this->_recipients !== array()) { - $recipient = array_rand($this->_recipients); - } else { - $recipient = 'unknown'; - } - - if (isset($_SERVER["SERVER_NAME"])) { - $hostName = $_SERVER["SERVER_NAME"]; - } else { - $hostName = php_uname('n'); - } - - return sha1($time . $user . $rand . $recipient) . '@' . $hostName; - } - - /** - * Add a custom header to the message - * - * @param string $name - * @param string $value - * @param boolean $append - * @return \Zend\Mail\Mail Provides fluent interface - * @throws \Zend\Mail\Exception on attempts to create standard headers - */ - public function addHeader($name, $value, $append = false) - { - $prohibit = array('to', 'cc', 'bcc', 'from', 'subject', - 'reply-to', 'return-path', - 'date', 'message-id', - ); - if (in_array(strtolower($name), $prohibit)) { - throw new Exception\InvalidArgumentException('Cannot set standard header from addHeader()'); - } - - $value = $this->_filterOther($value); - $value = $this->_encodeHeader($value); - $this->_storeHeader($name, $value, $append); - - return $this; - } - - /** - * Return mail headers - * - * @return void - */ - public function getHeaders() - { - return $this->_headers; - } - - /** - * Sends this email using the given transport or a previously - * set DefaultTransport or the internal mail function if no - * default transport had been set. - * - * @param \Zend\Mail\AbstractTransport $transport - * @return \Zend\Mail\Mail Provides fluent interface - */ - public function send($transport = null) - { - if ($transport === null) { - $transport = $this->getTransport(); - } - - if ($this->_date === null) { - $this->setDate(); - } - - if(null === $this->_from && null !== self::getDefaultFrom()) { - $this->setFromToDefaultFrom(); - } - - if(null === $this->_replyTo && null !== self::getDefaultReplyTo()) { - $this->setReplyToFromDefault(); - } - - $transport->send($this); - - return $this; - } - - /** - * Filter of email data - * - * @param string $email - * @return string - */ - protected function _filterEmail($email) - { - $rule = array("\r" => '', - "\n" => '', - "\t" => '', - '"' => '', - ',' => '', - '<' => '', - '>' => '', - ); - - return strtr($email, $rule); - } - - /** - * Filter of name data - * - * @param string $name - * @return string - */ - protected function _filterName($name) - { - $rule = array("\r" => '', - "\n" => '', - "\t" => '', - '"' => "'", - '<' => '[', - '>' => ']', - ); - - return trim(strtr($name, $rule)); - } - - /** - * Filter of other data - * - * @param string $data - * @return string - */ - protected function _filterOther($data) - { - $rule = array("\r" => '', - "\n" => '', - "\t" => '', - ); - - return strtr($data, $rule); - } - - /** - * Formats e-mail address - * - * @param string $email - * @param string $name - * @return string - */ - protected function _formatAddress($email, $name) - { - if ($name === '' || $name === null || $name === $email) { - return $email; - } else { - $encodedName = $this->_encodeHeader($name); - if ($encodedName === $name && - ((strpos($name, '@') !== false) || (strpos($name, ',') !== false))) { - $format = '"%s" <%s>'; - } else { - $format = '%s <%s>'; - } - return sprintf($format, $encodedName, $email); - } - } - -} diff --git a/src/Message.php b/src/Message.php index 6f7e92ea..64f18901 100644 --- a/src/Message.php +++ b/src/Message.php @@ -14,7 +14,7 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -22,84 +22,532 @@ * @namespace */ namespace Zend\Mail; -use Zend\Mail\Exception; + +use Traversable, + Zend\Mime\Message as MimeMessage; /** - * @uses \Zend\Mail\Exception - * @uses \Zend\Mail\Message\MessageInterface - * @uses \Zend\Mail\Part * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -class Message extends Part implements MailMessage +class Message { /** - * flags for this message - * @var array + * Content of the message + * + * @var null|string|object + */ + protected $body; + + /** + * @var Headers + */ + protected $headers; + + /** + * Message encoding + * + * Used to determine whether or not to encode headers; defaults to ASCII. + * + * @var string + */ + protected $encoding = 'ASCII'; + + /** + * Is the message valid? + * + * If we don't any From addresses, we're invalid, according to RFC2822. + * + * @return bool + */ + public function isValid() + { + $from = $this->from(); + if (!$from instanceof AddressList) { + return false; + } + return (bool) count($from); + } + + /** + * Set the message encoding + * + * @param string $encoding + * @return Message + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + $this->headers()->setEncoding($encoding); + return $this; + } + + /** + * Get the message encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Compose headers + * + * @param Headers $headers + * @return Message + */ + public function setHeaders(Headers $headers) + { + $this->headers = $headers; + $headers->setEncoding($this->getEncoding()); + return $this; + } + + /** + * Access headers collection + * + * Lazy-loads if not already attached. + * + * @return Headers + */ + public function headers() + { + if (null === $this->headers) { + $this->setHeaders(new Headers()); + $this->headers->addHeaderLine('Date', date('r')); + } + return $this->headers; + } + + /** + * Set (overwrite) From addresses + * + * @param string|AddressDescription|array|AddressList|Traversable $emailOrAddressList + * @param string|null $name + * @return Message + */ + public function setFrom($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('from'); + return $this->addFrom($emailOrAddressList, $name); + } + + /** + * Add a "From" address + * + * @param string|Address|array|AddressList|Traversable $emailOrAddressOrList + * @param string|null $name + * @return Message */ - protected $_flags = array(); + public function addFrom($emailOrAddressOrList, $name = null) + { + $addressList = $this->from(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } /** - * Public constructor + * Retrieve list of From senders + * + * @return AddressList + */ + public function from() + { + return $this->getAddressListFromHeader('from', __NAMESPACE__ . '\Header\From'); + } + + /** + * Overwrite the address list in the To recipients + * + * @param string|AddressDescription|array|AddressList|Traversable $emailOrAddressList + * @param null|string $name + * @return Message + */ + public function setTo($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('to'); + return $this->addTo($emailOrAddressList, $name); + } + + /** + * Add one or more addresses to the To recipients * - * In addition to the parameters of Zend_Mail_Part::__construct() this constructor supports: - * - file filename or file handle of a file with raw message content - * - flags array with flags for message, keys are ignored, use constants defined in Zend_Mail_Storage + * Appends to the list. + * + * @param string|AddressDescription|array|AddressList|Traversable $emailOrAddressOrList + * @param null|string $name + * @return Message + */ + public function addTo($emailOrAddressOrList, $name = null) + { + $addressList = $this->to(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Access the address list of the To header + * + * @return AddressList + */ + public function to() + { + return $this->getAddressListFromHeader('to', __NAMESPACE__ . '\Header\To'); + } + + /** + * Set (overwrite) CC addresses + * + * @param string|AddressDescription|array|AddressList|Traversable $emailOrAddressList + * @param string|null $name + * @return Message + */ + public function setCc($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('cc'); + return $this->addCc($emailOrAddressList, $name); + } + + /** + * Add a "Cc" address + * + * @param string|Address|array|AddressList|Traversable $emailOrAddressOrList + * @param string|null $name + * @return Message + */ + public function addCc($emailOrAddressOrList, $name = null) + { + $addressList = $this->cc(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Retrieve list of CC recipients + * + * @return AddressList + */ + public function cc() + { + return $this->getAddressListFromHeader('cc', __NAMESPACE__ . '\Header\Cc'); + } + + /** + * Set (overwrite) BCC addresses + * + * @param string|AddressDescription|array|AddressList|Traversable $emailOrAddressList + * @param string|null $name + * @return Message + */ + public function setBcc($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('bcc'); + return $this->addBcc($emailOrAddressList, $name); + } + + /** + * Add a "Bcc" address + * + * @param string|Address|array|AddressList|Traversable $emailOrAddressOrList + * @param string|null $name + * @return Message + */ + public function addBcc($emailOrAddressOrList, $name = null) + { + $addressList = $this->bcc(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Retrieve list of BCC recipients + * + * @return AddressList + */ + public function bcc() + { + return $this->getAddressListFromHeader('bcc', __NAMESPACE__ . '\Header\Bcc'); + } + + /** + * Overwrite the address list in the Reply-To recipients + * + * @param string|AddressDescription|array|AddressList|Traversable $emailOrAddressList + * @param null|string $name + * @return Message + */ + public function setReplyTo($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('reply-to'); + return $this->addReplyTo($emailOrAddressList, $name); + } + + /** + * Add one or more addresses to the Reply-To recipients * - * @param string $rawMessage full message with or without headers - * @throws \Zend\Mail\Exception + * Appends to the list. + * + * @param string|AddressDescription|array|AddressList|Traversable $emailOrAddressOrList + * @param null|string $name + * @return Message + */ + public function addReplyTo($emailOrAddressOrList, $name = null) + { + $addressList = $this->replyTo(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Access the address list of the Reply-To header + * + * @return AddressList + */ + public function replyTo() + { + return $this->getAddressListFromHeader('reply-to', __NAMESPACE__ . '\Header\ReplyTo'); + } + + /** + * setSender + * + * @param mixed $emailOrAddress + * @param mixed $name + * @return void + */ + public function setSender($emailOrAddress, $name = null) + { + $header = $this->getHeader('sender', __NAMESPACE__ . '\Header\Sender'); + $header->setAddress($emailOrAddress, $name); + return $this; + } + + /** + * Retrieve the sender address, if any + * + * @return null|AddressDescription + */ + public function getSender() + { + $header = $this->getHeader('sender', __NAMESPACE__ . '\Header\Sender'); + return $header->getAddress(); + } + + /** + * Set the message subject header value + * + * @param string $subject + * @return Message + */ + public function setSubject($subject) + { + $headers = $this->headers(); + if (!$headers->has('subject')) { + $header = new Header\Subject(); + $headers->addHeader($header); + } else { + $header = $headers->get('subject'); + } + $header->setSubject($subject); + return $this; + } + + /** + * Get the message subject header value + * + * @return null|string */ - public function __construct(array $params) + public function getSubject() { - if (isset($params['file'])) { - if (!is_resource($params['file'])) { - $params['raw'] = @file_get_contents($params['file']); - if ($params['raw'] === false) { - throw new Exception\RuntimeException('could not open file'); + $headers = $this->headers(); + if (!$headers->has('subject')) { + return null; + } + $header = $headers->get('subject'); + return $header->getFieldValue(); + } + + /** + * Set the message body + * + * @param null|string|MimeMessage|object $body + * @return Message + */ + public function setBody($body) + { + if (!is_string($body) && $body !== null) { + if (!is_object($body)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string or object argument; received "%s"', + __METHOD__, + gettype($body) + )); + } + if (!$body instanceof MimeMessage) { + if (!method_exists($body, '__toString')) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects object arguments of type Zend\Mime\Message or implementing __toString(); object of type "%s" received', + __METHOD__, + get_class($body) + )); } - } else { - $params['raw'] = stream_get_contents($params['file']); } } + $this->body = $body; + + if (!$this->body instanceof MimeMessage) { + return $this; + } + + // Get headers, and set Mime-Version header + $headers = $this->headers(); + $this->getHeader('mime-version', __NAMESPACE__ . '\Header\MimeVersion'); + + // Multipart content headers + if ($this->body->isMultiPart()) { + $mime = $this->body->getMime(); + $header = $this->getHeader('content-type', __NAMESPACE__ . '\Header\ContentType'); + $header->setType('multipart/mixed'); + $header->addParameter('boundary', $mime->boundary()); + return $this; + } + + // MIME single part headers + $parts = $this->body->getParts(); + if (!empty($parts)) { + $part = array_shift($parts); + $headers->addHeaders($part->getHeadersArray()); + } + return $this; + } + + /** + * Return the currently set message body + * + * @return null|object + */ + public function getBody() + { + return $this->body; + } - if (!empty($params['flags'])) { - // set key and value to the same value for easy lookup - $this->_flags = array_combine($params['flags'], $params['flags']); + /** + * Get the string-serialized message body text + * + * @return string + */ + public function getBodyText() + { + if ($this->body instanceof MimeMessage) { + return $this->body->generateMessage(); } - parent::__construct($params); + return (string) $this->body; } /** - * return toplines as found after headers + * Retrieve a header by name * - * @return string toplines + * If not found, instantiates one based on $headerClass. + * + * @param string $headerName + * @param string $headerClass + * @return Header */ - public function getTopLines() + protected function getHeader($headerName, $headerClass) { - return $this->_topLines; + $headers = $this->headers(); + if ($headers->has($headerName)) { + $header = $headers->get($headerName); + } else { + $header = new $headerClass(); + $headers->addHeader($header); + } + return $header; + } + + /** + * Clear a header by name + * + * @param string $headerName + * @return void + */ + protected function clearHeaderByName($headerName) + { + $headers = $this->headers(); + if ($headers->has($headerName)) { + $header = $headers->get($headerName); + $headers->removeHeader($header); + } } /** - * check if flag is set + * Retrieve the AddressList from a named header * - * @param mixed $flag a flag name, use constants defined in \Zend\Mail\Storage\Storage - * @return bool true if set, otherwise false + * Used with To, From, Cc, Bcc, and ReplyTo headers. If the header does not + * exist, instantiates it. + * + * @param string $headerName + * @param string $headerClass + * @return AddressList */ - public function hasFlag($flag) + protected function getAddressListFromHeader($headerName, $headerClass) { - return isset($this->_flags[$flag]); + $header = $this->getHeader($headerName, $headerClass); + if (!$header instanceof Header\AbstractAddressList) { + throw new Exception\DomainException(sprintf( + 'Cannot grab address list from header of type "%s"; not an AbstractAddressList implementation', + get_class($header) + )); + } + return $header->getAddressList(); } /** - * get all set flags + * Update an address list * - * @return array array with flags, key and value are the same for easy lookup + * Proxied to this from addFrom, addTo, addCc, addBcc, and addReplyTo. + * + * @param AddressList $addressList + * @param string|AddressDescription|array|AddressList|Traversable $emailOrAddressOrList + * @param null|string $name + * @param string $callingMethod + * @return void + */ + protected function updateAddressList(AddressList $addressList, $emailOrAddressOrList, $name, $callingMethod) + { + if ($emailOrAddressOrList instanceof Traversable) { + foreach ($emailOrAddressOrList as $address) { + $addressList->add($address); + } + return; + } + if (is_array($emailOrAddressOrList)) { + $addressList->addMany($emailOrAddressOrList); + return; + } + if (!is_string($emailOrAddressOrList) && !$emailOrAddressOrList instanceof AddressDescription) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string, AddressDescription, array, AddressList, or Traversable as its first argument; received "%s"', + $callingMethod, + (is_object($emailOrAddressOrList) ? get_class($emailOrAddressOrList) : gettype($emailOrAddressOrList)) + )); + } + $addressList->add($emailOrAddressOrList, $name); + } + + /** + * Serialize to string + * + * @return string */ - public function getFlags() + public function toString() { - return $this->_flags; + $headers = $this->headers(); + return $headers->toString() . "\r\n" . $this->getBodyText(); } } diff --git a/src/AbstractProtocol.php b/src/Protocol/AbstractProtocol.php similarity index 85% rename from src/AbstractProtocol.php rename to src/Protocol/AbstractProtocol.php index 1772df32..92631383 100644 --- a/src/AbstractProtocol.php +++ b/src/Protocol/AbstractProtocol.php @@ -16,31 +16,27 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace Zend\Mail; +namespace Zend\Mail\Protocol; use Zend\Validator\Hostname as HostnameValidator, - Zend\Validator, - Zend\Mail\Protocol; + Zend\Validator; /** * Zend_Mail_Protocol_Abstract * * Provides low-level methods for concrete adapters to communicate with a remote mail server and track requests and responses. * - * @uses \Zend\Mail\Protocol\Exception - * @uses \Zend\Validator\ValidatorChain - * @uses \Zend\Validator\Hostname\Hostname * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @todo Implement proxy settings */ @@ -80,7 +76,7 @@ abstract class AbstractProtocol /** * Instance of Zend\Validator\ValidatorChain to check hostnames - * @var \Zend\Validator\ValidatorChain + * @var Validator\ValidatorChain */ protected $_validHost; @@ -126,7 +122,7 @@ abstract class AbstractProtocol * * @param string $host OPTIONAL Hostname of remote connection (default: 127.0.0.1) * @param integer $port OPTIONAL Port number (default: null) - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return void */ public function __construct($host = '127.0.0.1', $port = null) @@ -135,7 +131,7 @@ public function __construct($host = '127.0.0.1', $port = null) $this->_validHost->addValidator(new HostnameValidator(HostnameValidator::ALLOW_ALL)); if (!$this->_validHost->isValid($host)) { - throw new Protocol\Exception\RuntimeException(implode(', ', $this->_validHost->getMessages())); + throw new Exception\RuntimeException(implode(', ', $this->_validHost->getMessages())); } $this->_host = $host; @@ -248,7 +244,7 @@ protected function _addLog($value) * An example $remote string may be 'tcp://mail.example.com:25' or 'ssh://hostname.com:2222' * * @param string $remote Remote - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return boolean */ protected function _connect($remote) @@ -263,11 +259,11 @@ protected function _connect($remote) if ($errorNum == 0) { $errorStr = 'Could not open socket'; } - throw new Protocol\Exception\RuntimeException($errorStr); + throw new Exception\RuntimeException($errorStr); } if (($result = stream_set_timeout($this->_socket, self::TIMEOUT_CONNECTION)) === false) { - throw new Protocol\Exception\RuntimeException('Could not set stream timeout'); + throw new Exception\RuntimeException('Could not set stream timeout'); } return $result; @@ -291,13 +287,13 @@ protected function _disconnect() * Send the given request followed by a LINEEND to the server. * * @param string $request - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return integer|boolean Number of bytes written to remote host */ protected function _send($request) { if (!is_resource($this->_socket)) { - throw new Protocol\Exception\RuntimeException('No connection has been established to ' . $this->_host); + throw new Exception\RuntimeException('No connection has been established to ' . $this->_host); } $this->_request = $request; @@ -308,7 +304,7 @@ protected function _send($request) $this->_addLog($request . self::EOL); if ($result === false) { - throw new Protocol\Exception\RuntimeException('Could not send request to ' . $this->_host); + throw new Exception\RuntimeException('Could not send request to ' . $this->_host); } return $result; @@ -319,13 +315,13 @@ protected function _send($request) * Get a line from the stream. * * @var integer $timeout Per-request timeout value if applicable - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return string */ protected function _receive($timeout = null) { if (!is_resource($this->_socket)) { - throw new Protocol\Exception\RuntimeException('No connection has been established to ' . $this->_host); + throw new Exception\RuntimeException('No connection has been established to ' . $this->_host); } // Adapters may wish to supply per-commend timeouts according to appropriate RFC @@ -343,11 +339,11 @@ protected function _receive($timeout = null) $info = stream_get_meta_data($this->_socket); if (!empty($info['timed_out'])) { - throw new Protocol\Exception\RuntimeException($this->_host . ' has timed out'); + throw new Exception\RuntimeException($this->_host . ' has timed out'); } if ($reponse === false) { - throw new Protocol\Exception\RuntimeException('Could not read from ' . $this->_host); + throw new Exception\RuntimeException('Could not read from ' . $this->_host); } return $reponse; @@ -361,7 +357,7 @@ protected function _receive($timeout = null) * Throws a Zend_Mail_Protocol_Exception if an unexpected code is returned. * * @param string|array $code One or more codes that indicate a successful response - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return string Last line of response string */ protected function _expect($code, $timeout = null) @@ -389,7 +385,7 @@ protected function _expect($code, $timeout = null) } while (strpos($more, '-') === 0); // The '-' message prefix indicates an information string instead of a response string. if ($errMsg !== '') { - throw new Protocol\Exception\RuntimeException($errMsg); + throw new Exception\RuntimeException($errMsg); } return $msg; diff --git a/src/Protocol/Exception.php b/src/Protocol/Exception.php index c1d2e282..c4023fca 100644 --- a/src/Protocol/Exception.php +++ b/src/Protocol/Exception.php @@ -14,7 +14,7 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -28,7 +28,7 @@ * @uses \Zend\Mail\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ interface Exception extends Mail\Exception diff --git a/src/Protocol/Exception/InvalidArgumentException.php b/src/Protocol/Exception/InvalidArgumentException.php index 6b83158e..707c306a 100644 --- a/src/Protocol/Exception/InvalidArgumentException.php +++ b/src/Protocol/Exception/InvalidArgumentException.php @@ -14,7 +14,7 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @version $Id$ * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -31,7 +31,7 @@ * @uses Zend\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class InvalidArgumentException diff --git a/src/Protocol/Exception/RuntimeException.php b/src/Protocol/Exception/RuntimeException.php index 1cee1e21..4cd67720 100644 --- a/src/Protocol/Exception/RuntimeException.php +++ b/src/Protocol/Exception/RuntimeException.php @@ -14,7 +14,7 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @version $Id$ * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -31,7 +31,7 @@ * @uses Zend\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class RuntimeException diff --git a/src/Protocol/Imap.php b/src/Protocol/Imap.php index b89c0d5b..91dba566 100644 --- a/src/Protocol/Imap.php +++ b/src/Protocol/Imap.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -30,7 +30,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Imap diff --git a/src/Protocol/Pop3.php b/src/Protocol/Pop3.php index f2990399..12ea241a 100644 --- a/src/Protocol/Pop3.php +++ b/src/Protocol/Pop3.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -29,7 +29,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Pop3 diff --git a/src/Protocol/Smtp.php b/src/Protocol/Smtp.php index 01ef19b8..fb85efdb 100644 --- a/src/Protocol/Smtp.php +++ b/src/Protocol/Smtp.php @@ -16,7 +16,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -25,21 +25,17 @@ */ namespace Zend\Mail\Protocol; -use Zend\Mail\AbstractProtocol, - Zend\Mail\Protocol\Exception; +use Zend\Mime\Mime; /** - * Smtp implementation of Zend_Mail_Protocol_Abstract + * Smtp implementation of Zend\Mail\Protocol\AbstractProtocol * * Minimum implementation according to RFC2821: EHLO, MAIL FROM, RCPT TO, DATA, RSET, NOOP, QUIT * - * @uses \Zend\Mail\Protocol\AbstractProtocol - * @uses \Zend\Mail\Protocol\Exception - * @uses \Zend\Mime\Mime * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Smtp extends AbstractProtocol @@ -111,14 +107,47 @@ class Smtp extends AbstractProtocol /** * Constructor. * - * @param string $host - * @param integer $port - * @param array $config + * The first argument may be an array of all options. If so, it must include + * the 'host' and 'port' keys in order to ensure that all required values + * are present. + * + * @param string|array $host + * @param null|integer $port + * @param null|array $config * @return void - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\InvalidArgumentException */ - public function __construct($host = '127.0.0.1', $port = null, array $config = array()) + public function __construct($host = '127.0.0.1', $port = null, array $config = null) { + // Did we receive a configuration array? + if (is_array($host)) { + // Merge config array with principal array, if provided + if (is_array($config)) { + $config = array_replace_recursive($host, $config); + } else { + $config = $host; + } + + // Look for a host key; if none found, use default value + if (isset($config['host'])) { + $host = $config['host']; + } else { + $host = '127.0.0.1'; + } + + // Look for a port key; if none found, use default value + if (isset($config['port'])) { + $port = $config['port']; + } else { + $port = null; + } + } + + // If we don't have a config array, initialize it + if (null === $config) { + $config = array(); + } + if (isset($config['ssl'])) { switch (strtolower($config['ssl'])) { case 'tls': @@ -165,7 +194,7 @@ public function connect() * Initiate HELO/EHLO sequence and set flag to indicate valid smtp session * * @param string $host The client hostname or IP address (default: 127.0.0.1) - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return void */ public function helo($host = '127.0.0.1') @@ -203,7 +232,7 @@ public function helo($host = '127.0.0.1') * Send EHLO or HELO depending on capabilities of smtp host * * @param string $host The client hostname or IP address (default: 127.0.0.1) - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception * @return void */ protected function _ehlo($host) @@ -215,7 +244,7 @@ protected function _ehlo($host) } catch (Exception $e) { $this->_send('HELO ' . $host); $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 - } catch (Exception $e) { + } catch (\Exception $e) { throw $e; } } @@ -225,7 +254,7 @@ protected function _ehlo($host) * Issues MAIL command * * @param string $from Sender mailbox - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return void */ public function mail($from) @@ -248,7 +277,7 @@ public function mail($from) * Issues RCPT command * * @param string $to Receiver(s) mailbox - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return void */ public function rcpt($to) @@ -268,7 +297,7 @@ public function rcpt($to) * Issues DATA command * * @param string $data - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return void */ public function data($data) @@ -281,7 +310,7 @@ public function data($data) $this->_send('DATA'); $this->_expect(354, 120); // Timeout set for 2 minutes as per RFC 2821 4.5.3.2 - foreach (explode(\Zend\Mime\Mime::LINEEND, $data) as $line) { + foreach (explode(Mime::LINEEND, $data) as $line) { if (strpos($line, '.') === 0) { // Escape lines prefixed with a '.' $line = '.' . $line; @@ -363,7 +392,7 @@ public function quit() * * This default method is implemented by AUTH adapters to properly authenticate to a remote host. * - * @throws \Zend\Mail\Protocol\Exception + * @throws Exception\RuntimeException * @return void */ public function auth() diff --git a/src/Protocol/Smtp/Auth/Crammd5.php b/src/Protocol/Smtp/Auth/Crammd5.php index 16eaf1a1..88d6aa80 100644 --- a/src/Protocol/Smtp/Auth/Crammd5.php +++ b/src/Protocol/Smtp/Auth/Crammd5.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -33,31 +33,58 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Crammd5 extends Smtp { + /** + * @var string + */ + protected $username; + + + /** + * @var string + */ + protected $password; + + /** * Constructor. * - * @param string $host (Default: 127.0.0.1) - * @param int $port (Default: null) - * @param array $config Auth-specific parameters + * All parameters may be passed as an array to the first argument of the + * constructor. If so, + * + * @param string|array $host (Default: 127.0.0.1) + * @param null|int $port (Default: null) + * @param null|array $config Auth-specific parameters * @return void */ public function __construct($host = '127.0.0.1', $port = null, $config = null) { + // Did we receive a configuration array? + $origConfig = $config; + if (is_array($host)) { + // Merge config array with principal array, if provided + if (is_array($config)) { + $config = array_replace_recursive($host, $config); + } else { + $config = $host; + } + } + if (is_array($config)) { if (isset($config['username'])) { - $this->_username = $config['username']; + $this->setUsername($config['username']); } if (isset($config['password'])) { - $this->_password = $config['password']; + $this->setPassword($config['password']); } } - parent::__construct($host, $port, $config); + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); } @@ -74,12 +101,55 @@ public function auth() $this->_send('AUTH CRAM-MD5'); $challenge = $this->_expect(334); $challenge = base64_decode($challenge); - $digest = $this->_hmacMd5($this->_password, $challenge); - $this->_send(base64_encode($this->_username . ' ' . $digest)); + $digest = $this->_hmacMd5($this->getPassword(), $challenge); + $this->_send(base64_encode($this->getUsername() . ' ' . $digest)); $this->_expect(235); $this->_auth = true; } + /** + * Set value for username + * + * @param string $value + * @return Crammd5 + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Get username + * + * @return null|string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set value for password + * + * @param string $value + * @return Crammd5 + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Get password + * + * @return null|string + */ + public function getPassword() + { + return $this->password; + } /** * Prepare CRAM-MD5 response to server's ticket diff --git a/src/Protocol/Smtp/Auth/Login.php b/src/Protocol/Smtp/Auth/Login.php index ff0c6bbe..add8da42 100644 --- a/src/Protocol/Smtp/Auth/Login.php +++ b/src/Protocol/Smtp/Auth/Login.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -23,6 +23,7 @@ * @namespace */ namespace Zend\Mail\Protocol\Smtp\Auth; + use Zend\Mail\Protocol\Smtp; /** @@ -32,7 +33,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Login extends Smtp @@ -42,7 +43,7 @@ class Login extends Smtp * * @var string */ - protected $_username; + protected $username; /** @@ -50,7 +51,7 @@ class Login extends Smtp * * @var string */ - protected $_password; + protected $password; /** @@ -63,16 +64,28 @@ class Login extends Smtp */ public function __construct($host = '127.0.0.1', $port = null, $config = null) { + // Did we receive a configuration array? + $origConfig = $config; + if (is_array($host)) { + // Merge config array with principal array, if provided + if (is_array($config)) { + $config = array_replace_recursive($host, $config); + } else { + $config = $host; + } + } + if (is_array($config)) { if (isset($config['username'])) { - $this->_username = $config['username']; + $this->setUsername($config['username']); } if (isset($config['password'])) { - $this->_password = $config['password']; + $this->setPassword($config['password']); } } - parent::__construct($host, $port, $config); + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); } @@ -88,10 +101,54 @@ public function auth() $this->_send('AUTH LOGIN'); $this->_expect(334); - $this->_send(base64_encode($this->_username)); + $this->_send(base64_encode($this->getUsername())); $this->_expect(334); - $this->_send(base64_encode($this->_password)); + $this->_send(base64_encode($this->getPassword())); $this->_expect(235); $this->_auth = true; } + + /** + * Set value for username + * + * @param string $value + * @return Login + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Get username + * + * @return null|string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set value for password + * + * @param string $value + * @return Login + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Get password + * + * @return null|string + */ + public function getPassword() + { + return $this->password; + } } diff --git a/src/Protocol/Smtp/Auth/Plain.php b/src/Protocol/Smtp/Auth/Plain.php index 05c099dc..73ef1fda 100644 --- a/src/Protocol/Smtp/Auth/Plain.php +++ b/src/Protocol/Smtp/Auth/Plain.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -32,7 +32,7 @@ * @category Zend * @package Zend_Mail * @subpackage Protocol - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Plain extends Smtp @@ -42,7 +42,7 @@ class Plain extends Smtp * * @var string */ - protected $_username; + protected $username; /** @@ -50,7 +50,7 @@ class Plain extends Smtp * * @var string */ - protected $_password; + protected $password; /** @@ -63,16 +63,28 @@ class Plain extends Smtp */ public function __construct($host = '127.0.0.1', $port = null, $config = null) { + // Did we receive a configuration array? + $origConfig = $config; + if (is_array($host)) { + // Merge config array with principal array, if provided + if (is_array($config)) { + $config = array_replace_recursive($host, $config); + } else { + $config = $host; + } + } + if (is_array($config)) { if (isset($config['username'])) { - $this->_username = $config['username']; + $this->setUsername($config['username']); } if (isset($config['password'])) { - $this->_password = $config['password']; + $this->setPassword($config['password']); } } - parent::__construct($host, $port, $config); + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); } @@ -88,8 +100,52 @@ public function auth() $this->_send('AUTH PLAIN'); $this->_expect(334); - $this->_send(base64_encode("\0" . $this->_username . "\0" . $this->_password)); + $this->_send(base64_encode("\0" . $this->getUsername() . "\0" . $this->getPassword())); $this->_expect(235); $this->_auth = true; } + + /** + * Set value for username + * + * @param string $value + * @return Plain + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Get username + * + * @return null|string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set value for password + * + * @param string $value + * @return Plain + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Get password + * + * @return null|string + */ + public function getPassword() + { + return $this->password; + } } diff --git a/src/Protocol/SmtpBroker.php b/src/Protocol/SmtpBroker.php new file mode 100644 index 00000000..1c68de80 --- /dev/null +++ b/src/Protocol/SmtpBroker.php @@ -0,0 +1,58 @@ + 'Zend\Mail\Protocol\Smtp\Auth\Crammd5', + 'login' => 'Zend\Mail\Protocol\Smtp\Auth\Login', + 'plain' => 'Zend\Mail\Protocol\Smtp\Auth\Plain', + 'smtp' => 'Zend\Mail\Protocol\Smtp', + ); +} diff --git a/src/Storage.php b/src/Storage.php index 0e9be642..e0c6495b 100644 --- a/src/Storage.php +++ b/src/Storage.php @@ -14,7 +14,7 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -26,7 +26,7 @@ /** * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Storage diff --git a/src/AbstractStorage.php b/src/Storage/AbstractStorage.php similarity index 89% rename from src/AbstractStorage.php rename to src/Storage/AbstractStorage.php index 68646862..8c37ca4d 100644 --- a/src/AbstractStorage.php +++ b/src/Storage/AbstractStorage.php @@ -15,29 +15,28 @@ * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace Zend\Mail; -use Zend\Mail\Storage; +namespace Zend\Mail\Storage; + +use ArrayAccess, + Countable, + SeekableIterator, + Zend\Mail\Storage; /** - * @uses ArrayAccess - * @uses Countable - * @uses OutOfBoundsException - * @uses SeekableIterator - * @uses \Zend\Mail\Storage\Exception * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -abstract class AbstractStorage implements \Countable, \ArrayAccess, \SeekableIterator +abstract class AbstractStorage implements Countable, ArrayAccess, SeekableIterator { /** * class capabilities with default values @@ -66,7 +65,7 @@ abstract class AbstractStorage implements \Countable, \ArrayAccess, \SeekableIte * used message class, change it in an extened class to extend the returned message class * @var string */ - protected $_messageClass = '\Zend\Mail\Message'; + protected $_messageClass = 'Zend\Mail\Storage\Message'; /** * Getter for has-properties. The standard has properties @@ -79,7 +78,7 @@ abstract class AbstractStorage implements \Countable, \ArrayAccess, \SeekableIte * * @param string $var property name * @return bool supported or not - * @throws \Zend\Mail\Storage\Exception + * @throws Exception */ public function __get($var) { @@ -88,7 +87,7 @@ public function __get($var) return isset($this->_has[$var]) ? $this->_has[$var] : null; } - throw new Storage\Exception\InvalidArgumentException($var . ' not found'); + throw new Exception\InvalidArgumentException($var . ' not found'); } @@ -107,7 +106,7 @@ public function getCapabilities() * Count messages messages in current box/folder * * @return int number of messages - * @throws \Zend\Mail\Storage\Exception + * @throws Exception */ abstract public function countMessages(); @@ -125,7 +124,7 @@ abstract public function getSize($id = 0); * Get a message with headers and body * * @param $id int number of message - * @return \Zend\Mail\Message\Message + * @return Message */ abstract public function getMessage($id); @@ -153,7 +152,7 @@ abstract public function getRawContent($id, $part = null); * Create instance with parameters * * @param array $params mail reader specific parameters - * @throws \Zend\Mail\Storage\Exception + * @throws Exception */ abstract public function __construct($params); @@ -197,7 +196,7 @@ abstract public function removeMessage($id); * * @param int|null $id message number * @return array|string message number for given message or all messages as array - * @throws \Zend\Mail\Storage\Exception + * @throws Exception */ abstract public function getUniqueId($id = null); @@ -209,7 +208,7 @@ abstract public function getUniqueId($id = null); * * @param string $id unique id * @return int message number - * @throws \Zend\Mail\Storage\Exception + * @throws Exception */ abstract public function getNumberByUniqueId($id); @@ -266,7 +265,7 @@ public function offsetGet($id) */ public function offsetSet($id, $value) { - throw new Storage\Exception\RuntimeException('cannot write mail messages via array access'); + throw new Exception\RuntimeException('cannot write mail messages via array access'); } @@ -301,7 +300,7 @@ public function rewind() /** * Iterator::current() * - * @return \Zend\Mail\Message\Message current message + * @return Message current message */ public function current() { @@ -350,7 +349,7 @@ public function valid() * * @param int $pos * @return void - * @throws OutOfBoundsException + * @throws Exception\OutOfBoundsException */ public function seek($pos) { diff --git a/src/Storage/Exception.php b/src/Storage/Exception.php index 66758e6b..24bd7b84 100644 --- a/src/Storage/Exception.php +++ b/src/Storage/Exception.php @@ -14,7 +14,7 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -28,7 +28,7 @@ * @uses \Zend\Mail\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ interface Exception extends Mail\Exception diff --git a/src/Storage/Exception/InvalidArgumentException.php b/src/Storage/Exception/InvalidArgumentException.php index 4cc1b6ec..8694118c 100644 --- a/src/Storage/Exception/InvalidArgumentException.php +++ b/src/Storage/Exception/InvalidArgumentException.php @@ -14,7 +14,7 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @version $Id$ * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -31,7 +31,7 @@ * @uses Zend\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class InvalidArgumentException diff --git a/src/Storage/Exception/OutOfBoundsException.php b/src/Storage/Exception/OutOfBoundsException.php new file mode 100644 index 00000000..d9c3dacc --- /dev/null +++ b/src/Storage/Exception/OutOfBoundsException.php @@ -0,0 +1,44 @@ + firstPart, partname => value) - * @throws Zend_Exception, \Zend\Mail\Exception + * @throws Exception */ public function getHeaderField($name, $wantedPart = 0, $firstName = 0); @@ -120,13 +121,13 @@ public function getHeaderField($name, $wantedPart = 0, $firstName = 0); /** * Getter for mail headers - name is matched in lowercase * - * This getter is short for Zend_Mail_Part::getHeader($name, 'string') + * This getter is short for Part::getHeader($name, 'string') * - * @see \Zend\Mail\Part::getHeader() + * @see Part::getHeader() * * @param string $name header name * @return string value of header - * @throws \Zend\Mail\Exception + * @throws Exception */ public function __get($name); diff --git a/src/Storage/Maildir.php b/src/Storage/Maildir.php index 719b28c2..50612828 100644 --- a/src/Storage/Maildir.php +++ b/src/Storage/Maildir.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -24,19 +24,11 @@ */ namespace Zend\Mail\Storage; -use Zend\Mail\AbstractStorage, - Zend\Mail\Storage\Exception, - Zend\Mail\Storage; - /** - * @uses \Zend\Mail\Message\File - * @uses \Zend\Mail\Storage\Storage - * @uses \Zend\Mail\Storage\AbstractStorage - * @uses \Zend\Mail\Storage\Exception * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Maildir extends AbstractStorage diff --git a/src/Storage/Mbox.php b/src/Storage/Mbox.php index 16fc7e89..2c517d3a 100644 --- a/src/Storage/Mbox.php +++ b/src/Storage/Mbox.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -24,17 +24,11 @@ */ namespace Zend\Mail\Storage; -use Zend\Mail\AbstractStorage, - Zend\Mail\Storage\Exception; - /** - * @uses \Zend\Mail\Message\File - * @uses \Zend\Mail\Storage\AbstractStorage - * @uses \Zend\Mail\Storage\Exception * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Mbox extends AbstractStorage diff --git a/src/Storage/Message.php b/src/Storage/Message.php new file mode 100644 index 00000000..37657270 --- /dev/null +++ b/src/Storage/Message.php @@ -0,0 +1,102 @@ +_flags = array_combine($params['flags'], $params['flags']); + } + + parent::__construct($params); + } + + /** + * return toplines as found after headers + * + * @return string toplines + */ + public function getTopLines() + { + return $this->_topLines; + } + + /** + * check if flag is set + * + * @param mixed $flag a flag name, use constants defined in \Zend\Mail\Storage + * @return bool true if set, otherwise false + */ + public function hasFlag($flag) + { + return isset($this->_flags[$flag]); + } + + /** + * get all set flags + * + * @return array array with flags, key and value are the same for easy lookup + */ + public function getFlags() + { + return $this->_flags; + } +} diff --git a/src/Message/File.php b/src/Storage/Message/File.php similarity index 87% rename from src/Message/File.php rename to src/Storage/Message/File.php index d384115e..62010f0d 100644 --- a/src/Message/File.php +++ b/src/Storage/Message/File.php @@ -14,24 +14,23 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace Zend\Mail\Message; +namespace Zend\Mail\Storage\Message; -use Zend\Mail\MailMessage, - Zend\Mail\Part\File as FilePart; +use Zend\Mail\Storage\MailMessage, + Zend\Mail\Storage\Part\File as FilePart; /** - * @uses \Zend\Mail\MailMessage - * @uses \Zend\Mail\Part\File * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class File extends FilePart implements MailMessage @@ -49,7 +48,7 @@ class File extends FilePart implements MailMessage * - flags array with flags for message, keys are ignored, use constants defined in Zend_Mail_Storage * * @param string $rawMessage full message with or without headers - * @throws \Zend\Mail\Exception + * @throws \Zend\Mail\Storage\Exception */ public function __construct(array $params) { diff --git a/src/Part.php b/src/Storage/Part.php similarity index 89% rename from src/Part.php rename to src/Storage/Part.php index 55dbeb5c..44fac60b 100644 --- a/src/Part.php +++ b/src/Storage/Part.php @@ -14,30 +14,27 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace Zend\Mail; +namespace Zend\Mail\Storage; -use Zend\Mime, - Zend\Mail\Exception; +use RecursiveIterator, + Zend\Mime; /** - * @uses RecursiveIterator - * @uses \Zend\Mail\Exception - * @uses \Zend\Mail\MailPart - * @uses \Zend\Mime\Mime - * @uses \Zend\Mime\Decode * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -class Part implements \RecursiveIterator, MailPart +class Part implements RecursiveIterator, MailPart { /** * headers of part as array @@ -77,7 +74,7 @@ class Part implements \RecursiveIterator, MailPart /** * mail handler, if late fetch is active - * @var null|\Zend\Mail\AbstractStorage + * @var null|AbstractStorage */ protected $_mail; @@ -90,8 +87,8 @@ class Part implements \RecursiveIterator, MailPart /** * Public constructor * - * Zend_Mail_Part supports different sources for content. The possible params are: - * - handler a instance of Zend_Mail_Storage_Abstract for late fetch + * Part supports different sources for content. The possible params are: + * - handler an instance of AbstractStorage for late fetch * - id number of message for handler * - raw raw content with header and body as string * - headers headers as array (name => value) or string, if a content part is found it's used as toplines @@ -99,7 +96,7 @@ class Part implements \RecursiveIterator, MailPart * - content content as string * * @param array $params full message with or without headers - * @throws \Zend\Mail\Exception + * @throws Exception */ public function __construct(array $params) { @@ -155,7 +152,7 @@ public function isMultipart() * If part is multipart the raw content of this part with all sub parts is returned * * @return string body - * @throws \Zend\Mail\Exception + * @throws Exception */ public function getContent() { @@ -177,7 +174,8 @@ public function getContent() * * @return int size */ - public function getSize() { + public function getSize() + { return strlen($this->getContent()); } @@ -186,7 +184,7 @@ public function getSize() { * Cache content and split in parts if multipart * * @return null - * @throws \Zend\Mail\Exception + * @throws Exception */ protected function _cacheContent() { @@ -218,8 +216,8 @@ protected function _cacheContent() * Get part of multipart message * * @param int $num number of part starting with 1 for first part - * @return \Zend\Mail\Part wanted part - * @throws \Zend\Mail\Exception + * @return Part wanted part + * @throws Exception */ public function getPart($num) { @@ -300,12 +298,12 @@ public function getHeaders() * Get a header in specificed format * * Internally headers that occur more than once are saved as array, all other as string. If $format - * is set to string implode is used to concat the values (with Zend_Mime::LINEEND as delim). + * is set to string implode is used to concat the values (with Mime::LINEEND as delim). * * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes * @param string $format change type of return value to 'string' or 'array' * @return string|array value of header in wanted or internal format - * @throws \Zend\Mail\Exception + * @throws Exception */ public function getHeader($name, $format = null) { @@ -362,14 +360,14 @@ public function headerExists($name) * If the header occurs more than once, only the value from the first header * is returned. * - * Throws a Zend_Mail_Exception if the requested header does not exist. If + * Throws an Exception if the requested header does not exist. If * the specific header field does not exist, returns null. * * @param string $name name of header, like in getHeader() * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned * @param string $firstName key name for the first part * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) - * @throws Zend_Exception, \Zend\Mail\Exception + * @throws Exception */ public function getHeaderField($name, $wantedPart = 0, $firstName = 0) { @@ -380,13 +378,13 @@ public function getHeaderField($name, $wantedPart = 0, $firstName = 0) /** * Getter for mail headers - name is matched in lowercase * - * This getter is short for Zend_Mail_Part::getHeader($name, 'string') + * This getter is short for Part::getHeader($name, 'string') * - * @see \Zend\Mail\Part::getHeader() + * @see Part::getHeader() * * @param string $name header name * @return string value of header - * @throws \Zend\Mail\Exception + * @throws Exception */ public function __get($name) { @@ -396,9 +394,9 @@ public function __get($name) /** * Isset magic method proxy to hasHeader * - * This method is short syntax for Zend_Mail_Part::hasHeader($name); + * This method is short syntax for Part::hasHeader($name); * - * @see \Zend\Mail\Part::hasHeader + * @see Part::hasHeader * * @param string * @return boolean @@ -432,7 +430,7 @@ public function hasChildren() /** * implements RecursiveIterator::getChildren() * - * @return \Zend\Mail\Part\Part same as self::current() + * @return Part same as self::current() */ public function getChildren() { @@ -475,7 +473,7 @@ public function key() /** * implements Iterator::current() * - * @return \Zend\Mail\Part current part + * @return Part current part */ public function current() { diff --git a/src/Part/Exception.php b/src/Storage/Part/Exception.php similarity index 74% rename from src/Part/Exception.php rename to src/Storage/Part/Exception.php index c14e6746..b816aac2 100644 --- a/src/Part/Exception.php +++ b/src/Storage/Part/Exception.php @@ -14,7 +14,8 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ @@ -22,16 +23,17 @@ /** * @namespace */ -namespace Zend\Mail\Part; -use Zend\Mail; +namespace Zend\Mail\Storage\Part; + +use Zend\Mail\Storage; /** - * @uses \Zend\Mail\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -interface Exception extends Mail\Exception +interface Exception extends Storage\Exception { -} \ No newline at end of file +} diff --git a/src/Part/Exception/InvalidArgumentException.php b/src/Storage/Part/Exception/InvalidArgumentException.php similarity index 79% rename from src/Part/Exception/InvalidArgumentException.php rename to src/Storage/Part/Exception/InvalidArgumentException.php index 69e93949..311702a4 100644 --- a/src/Part/Exception/InvalidArgumentException.php +++ b/src/Storage/Part/Exception/InvalidArgumentException.php @@ -14,7 +14,8 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @version $Id$ * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -22,8 +23,9 @@ /** * @namespace */ -namespace Zend\Mail\Part\Exception; -use Zend\Mail\Part\Exception; +namespace Zend\Mail\Storage\Part\Exception; + +use Zend\Mail\Storage\Part\Exception; /** * Exception for Zend_Mail component. @@ -31,11 +33,12 @@ * @uses Zend\Exception * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class InvalidArgumentException extends \InvalidArgumentException implements Exception { -} \ No newline at end of file +} diff --git a/src/Part/Exception/RuntimeException.php b/src/Storage/Part/Exception/RuntimeException.php similarity index 78% rename from src/Part/Exception/RuntimeException.php rename to src/Storage/Part/Exception/RuntimeException.php index 6b9e892e..08d93c35 100644 --- a/src/Part/Exception/RuntimeException.php +++ b/src/Storage/Part/Exception/RuntimeException.php @@ -14,7 +14,8 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @version $Id$ * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -22,20 +23,21 @@ /** * @namespace */ -namespace Zend\Mail\Part\Exception; -use Zend\Mail\Part\Exception; +namespace Zend\Mail\Storage\Part\Exception; + +use Zend\Mail\Storage\Part\Exception; /** * Exception for Zend_Mail component. * - * @uses Zend\Exception * @category Zend + * @subpackage Storage * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class RuntimeException extends \RuntimeException implements Exception { -} \ No newline at end of file +} diff --git a/src/Part/File.php b/src/Storage/Part/File.php similarity index 91% rename from src/Part/File.php rename to src/Storage/Part/File.php index f54ed4ef..bfe10b8c 100644 --- a/src/Part/File.php +++ b/src/Storage/Part/File.php @@ -14,25 +14,24 @@ * * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace Zend\Mail\Part; +namespace Zend\Mail\Storage\Part; -use Zend\Mail\Part, +use Zend\Mail\Storage\Part, Zend\Mime; /** - * @uses \Zend\Mail\Exception - * @uses \Zend\Mail\Part - * @uses \Zend\Mime\Decode * @category Zend * @package Zend_Mail - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @subpackage Storage + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class File extends Part @@ -50,7 +49,7 @@ class File extends Part * - endPos end position of message or part in file (default: end of file) * * @param array $params full message with or without headers - * @throws \Zend\Mail\Exception + * @throws Exception */ public function __construct(array $params) { @@ -134,7 +133,7 @@ public function __construct(array $params) * If part is multipart the raw content of this part with all sub parts is returned * * @return string body - * @throws \Zend\Mail\Exception + * @throws Exception */ public function getContent($stream = null) { @@ -153,7 +152,8 @@ public function getContent($stream = null) * * @return int size */ - public function getSize() { + public function getSize() + { return $this->_contentPos[1] - $this->_contentPos[0]; } @@ -161,8 +161,8 @@ public function getSize() { * Get part of multipart message * * @param int $num number of part starting with 1 for first part - * @return \Zend\Mail\Part wanted part - * @throws \Zend\Mail\Exception + * @return Part wanted part + * @throws Exception */ public function getPart($num) { diff --git a/src/Storage/Pop3.php b/src/Storage/Pop3.php index 3dcd8264..6e41e7f1 100644 --- a/src/Storage/Pop3.php +++ b/src/Storage/Pop3.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -23,22 +23,16 @@ * @namespace */ namespace Zend\Mail\Storage; -use Zend\Mail\AbstractStorage, - Zend\Mail\Storage\Exception, - Zend\Mail\Protocol, + +use Zend\Mail\Protocol, Zend\Mail, Zend\Mime; /** - * @uses \Zend\Mail\Message\Message - * @uses \Zend\Mail\Protocol\Pop3 - * @uses \Zend\Mail\Storage\AbstractStorage - * @uses \Zend\Mail\Storage\Exception - * @uses \Zend\Mime\Decode * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Pop3 extends AbstractStorage diff --git a/src/Storage/Writable.php b/src/Storage/Writable.php index a72c2920..641284a1 100644 --- a/src/Storage/Writable.php +++ b/src/Storage/Writable.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -28,7 +28,7 @@ * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ interface Writable diff --git a/src/Storage/Writable/Maildir.php b/src/Storage/Writable/Maildir.php index 41ce47c7..38fc65c7 100644 --- a/src/Storage/Writable/Maildir.php +++ b/src/Storage/Writable/Maildir.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -43,7 +43,7 @@ * @category Zend * @package Zend_Mail * @subpackage Storage - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Maildir extends MaildirFolder implements Writable diff --git a/src/Transport.php b/src/Transport.php new file mode 100644 index 00000000..26aa4a92 --- /dev/null +++ b/src/Transport.php @@ -0,0 +1,42 @@ +toArray(); - } elseif (!is_array($options)) { - $options = array(); - } - - // Making sure we have some defaults to work with - if (!isset($options['path'])) { - $options['path'] = sys_get_temp_dir(); - } - if (!isset($options['callback'])) { - $options['callback'] = $this->getDefaultCallback(); - } - - $this->setOptions($options); - } - - /** - * Sets options - * - * @param array $options - * @return void - */ - public function setOptions(array $options) - { - if (isset($options['path'])) { - $this->path = $options['path']; - } - if (isset($options['callback'])) { - $this->callback = $options['callback']; - } - } - - /** - * Saves e-mail message to a file - * - * @return void - * @throws \Zend\Mail\Transport\Exception on not writable target directory - * @throws \Zend\Mail\Transport\Exception on file_put_contents() failure - */ - protected function _sendMail() - { - $file = $this->getPath() . DIRECTORY_SEPARATOR . call_user_func($this->getCallback(), $this); - - if (!is_writable(dirname($file))) { - throw new Exception\RuntimeException(sprintf( - 'Target directory "%s" does not exist or is not writable', - dirname($file) - )); - } - - $email = $this->header . $this->EOL . $this->body; - - if (!file_put_contents($file, $email)) { - throw new Exception\RuntimeException('Unable to send mail'); - } - } - - /** - * Returns the default callback for generating file names - * - * @return callback - */ - public function getDefaultCallback() - { - return function($transport) { - return 'ZendMail_' . time() . '_' . mt_rand() . '.tmp'; - }; - } - - /** - * Retrieve registered path - * - * @return string - */ - public function getPath() - { - return $this->path; - } - - /** - * Get the registered callback for generating file names - * - * @return callback - */ - public function getCallback() - { - return $this->callback; - } -} +setOptions($options); + } + + /** + * Sets options + * + * @param FileOptions $options + * @return void + */ + public function setOptions(FileOptions $options) + { + $this->options = $options; + } + + /** + * Saves e-mail message to a file + * + * @return void + * @throws \Zend\Mail\Transport\Exception on not writable target directory + * @throws \Zend\Mail\Transport\Exception on file_put_contents() failure + */ + public function send(Message $message) + { + $options = $this->options; + $filename = call_user_func($options->getCallback(), $this); + $file = $options->getPath() . DIRECTORY_SEPARATOR . $filename; + $email = $message->toString(); + + if (false === file_put_contents($file, $email)) { + throw new Exception\RuntimeException(sprintf( + 'Unable to write mail to file (directory "%s")', + $options->getPath() + )); + } + + $this->lastFile = $file; + } + + /** + * Get the name of the last file written to + * + * @return null|string + */ + public function getLastFile() + { + return $this->lastFile; + } +} diff --git a/src/Transport/FileOptions.php b/src/Transport/FileOptions.php new file mode 100644 index 00000000..9c4c9695 --- /dev/null +++ b/src/Transport/FileOptions.php @@ -0,0 +1,113 @@ +path = $path; + return $this; + } + + /** + * Get path + * + * If none is set, uses value from sys_get_temp_dir() + * + * @return string + */ + public function getPath() + { + if (null === $this->path) { + $this->setPath(sys_get_temp_dir()); + } + return $this->path; + } + + /** + * Set callback used to generate a file name + * + * @param Callable $callback + * @return FileOptions + */ + public function setCallback($callback) + { + if (!is_callable($callback)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a valid callback; received "%s"', + __METHOD__, + (is_object($callback) ? get_class($callback) : gettype($callback)) + )); + } + $this->callback = $callback; + return $this; + } + + /** + * Get callback used to generate a file name + * + * @return Callable + */ + public function getCallback() + { + if (null === $this->callback) { + $this->setCallback(function($transport) { + return 'ZendMail_' . time() . '_' . mt_rand() . '.tmp'; + }); + } + return $this->callback; + } +} diff --git a/src/Transport/Sendmail.php b/src/Transport/Sendmail.php index 1fb576c7..219a19b2 100644 --- a/src/Transport/Sendmail.php +++ b/src/Transport/Sendmail.php @@ -15,173 +15,293 @@ * @category Zend * @package Zend_Mail * @subpackage Transport - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -/** - * @namespace - */ namespace Zend\Mail\Transport; -use Zend\Config, - Zend\Mail\Transport\Exception, - Zend\Mail\AbstractTransport; + +use Traversable, + Zend\Mail\AddressDescription, + Zend\Mail\AddressList, + Zend\Mail\Exception, + Zend\Mail\Header, + Zend\Mail\Headers, + Zend\Mail\Message, + Zend\Mail\Transport; /** - * Class for sending eMails via the PHP internal mail() function + * Class for sending email via the PHP internal mail() function * - * @uses \Zend\Mail\AbstractTransport - * @uses \Zend\Mail\Transport\Exception * @category Zend * @package Zend_Mail * @subpackage Transport - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -class Sendmail extends AbstractTransport +class Sendmail implements Transport { /** - * Subject + * Config options for sendmail parameters + * * @var string - * @access public */ - public $subject = null; - + protected $parameters; /** - * Config options for sendmail parameters - * - * @var string + * Callback to use when sending mail; typically, {@link mailHandler()} + * + * @var callable */ - public $parameters; + protected $callable; /** - * EOL character string + * error information * @var string - * @access public */ - public $EOL = PHP_EOL; + protected $errstr; /** - * error information * @var string */ - protected $_errstr; + protected $operatingSystem; /** * Constructor. * - * @param string|array|\Zend\Config\Config $parameters OPTIONAL (Default: null) + * @param null|string|array|Traversable $parameters OPTIONAL (Default: null) * @return void */ public function __construct($parameters = null) { - if ($parameters instanceof Config\Config) { - $parameters = $parameters->toArray(); + if ($parameters !== null) { + $this->setParameters($parameters); + } + $this->callable = array($this, 'mailHandler'); + } + + /** + * Set sendmail parameters + * + * Used to populate the additional_parameters argument to mail() + * + * @param null|string|array|Traversable $parameters + * @return Sendmail + */ + public function setParameters($parameters) + { + if ($parameters === null || is_string($parameters)) { + $this->parameters = $parameters; + return $this; } - if (is_array($parameters)) { - $parameters = implode(' ', $parameters); + if (!is_array($parameters) && !$parameters instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string, array, or Traversable object of paremeters; received "%s"', + __METHOD__, + (is_object($parameters) ? get_class($parameters) : gettype($parameters)) + )); } - $this->parameters = $parameters; - } + $string = ''; + foreach ($parameters as $param) { + $string .= ' ' . $param; + } + trim($string); + $this->parameters = $string; + return $this; + } /** - * Send mail using PHP native mail() + * Set callback to use for mail * - * @access public + * Primarily for testing purposes, but could be used to curry arguments. + * + * @param callable $callable + * @return Sendmail + */ + public function setCallable($callable) + { + if (!is_callable($callable)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a callable argument; received "%s"', + __METHOD__, + (is_object($callable) ? get_class($callable) : gettype($callable)) + )); + } + $this->callable = $callable; + return $this; + } + + /** + * Send a message + * + * @param Message $message * @return void - * @throws \Zend\Mail\Transport\Exception if parameters is set - * but not a string - * @throws \Zend\Mail\Transport\Exception on mail() failure */ - public function _sendMail() + public function send(Message $message) { - if ($this->parameters === null) { - set_error_handler(array($this, '_handleMailErrors')); - $result = mail( - $this->recipients, - $this->_mail->getSubject(), - $this->body, - $this->header); - restore_error_handler(); - } else { - if(!is_string($this->parameters)) { - /** - * Exception is thrown here because - * $parameters is a public property - */ - throw new Exception\RuntimeException( - 'Parameters were set but are not a string' - ); - } + $to = $this->prepareRecipients($message); + $subject = $this->prepareSubject($message); + $body = $this->prepareBody($message); + $headers = $this->prepareHeaders($message); + $params = $this->prepareParameters($message); + + call_user_func($this->callable, $to, $subject, $body, $headers, $params); + } + + /** + * Prepare recipients list + * + * @param Message $message + * @return string + */ + protected function prepareRecipients(Message $message) + { + $headers = $message->headers(); - set_error_handler(array($this, '_handleMailErrors')); - $result = mail( - $this->recipients, - $this->_mail->getSubject(), - $this->body, - $this->header, - $this->parameters); - restore_error_handler(); + if (!$headers->has('to')) { + throw new Exception\RuntimeException('Invalid email; contains no "To" header'); } - if ($this->_errstr !== null || !$result) { - throw new Exception\RuntimeException('Unable to send mail. ' . $this->_errstr); + $to = $headers->get('to'); + $list = $to->getAddressList(); + if (0 == count($list)) { + throw new Exception\RuntimeException('Invalid "To" header; contains no addresses'); } + + // If not on Windows, return normal string + if (!$this->isWindowsOs()) { + return $to->getFieldValue(); + } + + // Otherwise, return list of emails + $addresses = array(); + foreach ($list as $address) { + $addresses[] = $address->getEmail(); + } + $addresses = implode(', ', $addresses); + return $addresses; } + /** + * Prepare the subject line string + * + * @param Message $message + * @return string + */ + protected function prepareSubject(Message $message) + { + return $message->getSubject(); + } /** - * Format and fix headers - * - * mail() uses its $to and $subject arguments to set the To: and Subject: - * headers, respectively. This method strips those out as a sanity check to - * prevent duplicate header entries. - * - * @access protected - * @param array $headers - * @return void - * @throws \Zend\Mail\Transport\Exception + * Prepare the body string + * + * @param Message $message + * @return string */ - protected function _prepareHeaders($headers) + protected function prepareBody(Message $message) { - if (!$this->_mail) { - throw new Exception\RuntimeException('_prepareHeaders requires a registered \Zend\Mail\Mail object'); + if (!$this->isWindowsOs()) { + // *nix platforms can simply return the body text + return $message->getBodyText(); } - // mail() uses its $to parameter to set the To: header, and the $subject - // parameter to set the Subject: header. We need to strip them out. - if (0 === strpos(PHP_OS, 'WIN')) { - // If the current recipients list is empty, throw an error - if (empty($this->recipients)) { - throw new Exception\RuntimeException('Missing To addresses'); - } - } else { - // All others, simply grab the recipients and unset the To: header - if (!isset($headers['To'])) { - throw new Exception\RuntimeException('Missing To header'); + // On windows, lines beginning with a full stop need to be fixed + $text = $message->getBodyText(); + $text = str_replace("\n.", "\n..", $text); + return $text; + } + + /** + * Prepare the textual representation of headers + * + * @param Message $message + * @return string + */ + protected function prepareHeaders(Message $message) + { + $headers = $message->headers(); + + // On Windows, simply return verbatim + if ($this->isWindowsOs()) { + return $headers->toString(); + } + + // On *nix platforms, strip the "to" header + $headersToSend = new Headers(); + foreach ($headers as $header) { + if ('To' == $header->getFieldName()) { + continue; } + $headersToSend->addHeader($header); + } + return $headersToSend->toString(); + } - unset($headers['To']['append']); - $this->recipients = implode(',', $headers['To']); + /** + * Prepare additional_parameters argument + * + * Basically, overrides the MAIL FROM envelope with either the Sender or + * From address. + * + * @param Message $message + * @return string + */ + protected function prepareParameters(Message $message) + { + if ($this->isWindowsOs()) { + return null; } - // Remove recipient header - unset($headers['To']); + $parameters = (string) $this->parameters; + + $sender = $message->getSender(); + if ($sender instanceof AddressDescription) { + $parameters .= ' -r ' . $sender->getEmail(); + return $parameters; + } - // Remove subject header, if present - if (isset($headers['Subject'])) { - unset($headers['Subject']); + $from = $message->from(); + if (count($from)) { + $from->rewind(); + $sender = $from->current(); + $parameters .= ' -r ' . $sender->getEmail(); + return $parameters; } - // Prepare headers - parent::_prepareHeaders($headers); + return $parameters; + } + + /** + * Send mail using PHP native mail() + * + * @param string $to + * @param string $subject + * @param string $message + * @param string $headers + * @return void + * @throws Exception\RuntimeException on mail failure + */ + public function mailHandler($to, $subject, $message, $headers, $parameters) + { + set_error_handler(array($this, 'handleMailErrors')); + if ($parameters === null) { + $result = mail($to, $subject, $message, $headers); + } else { + $result = mail($to, $subject, $message, $headers, $parameters); + } + restore_error_handler(); - // Fix issue with empty blank line ontop when using Sendmail Trnasport - $this->header = rtrim($this->header); + if ($this->errstr !== null || !$result) { + $errstr = $this->errstr; + if (empty($errstr)) { + $errstr = 'Unknown error'; + } + throw new Exception\RuntimeException('Unable to send mail: ' . $errstr); + } } /** @@ -194,10 +314,22 @@ protected function _prepareHeaders($headers) * @param array $errcontext * @return true */ - public function _handleMailErrors($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null) + public function handleMailErrors($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null) { - $this->_errstr = $errstr; + $this->errstr = $errstr; return true; } + /** + * Is this a windows OS? + * + * @return bool + */ + protected function isWindowsOs() + { + if (!$this->operatingSystem) { + $this->operatingSystem = strtoupper(substr(PHP_OS, 0, 3)); + } + return ($this->operatingSystem == 'WIN'); + } } diff --git a/src/Transport/Smtp.php b/src/Transport/Smtp.php index 8d355089..ee512aae 100644 --- a/src/Transport/Smtp.php +++ b/src/Transport/Smtp.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage Transport - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -23,129 +23,123 @@ * @namespace */ namespace Zend\Mail\Transport; -use Zend\Mail\AbstractProtocol, - Zend\Mail\AbstractTransport, - Zend\Mail\Transport\Exception, - Zend\Mail\Protocol\Smtp as SmtpProtocol, + +use Zend\Loader\Pluggable, + Zend\Mail\AddressDescription, + Zend\Mail\Headers, + Zend\Mail\Message, + Zend\Mail\Transport, Zend\Mail\Protocol, - Zend\Mime; + Zend\Mail\Protocol\AbstractProtocol, + Zend\Mail\Protocol\Smtp as SmtpProtocol, + Zend\Mail\Protocol\SmtpBroker; /** * SMTP connection object * * Loads an instance of \Zend\Mail\Protocol\Smtp and forwards smtp transactions * - * @uses \Zend\Loader - * @uses \Zend\Mail\Protocol\Smtp - * @uses \Zend\Mail\AbstractTransport - * @uses \Zend\Mail\Transport\Exception - * @uses \Zend\Mime\Mime * @category Zend * @package Zend_Mail * @subpackage Transport - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ -class Smtp extends AbstractTransport +class Smtp implements Transport, Pluggable { /** - * EOL character string used by transport - * @var string - * @access public + * @var SmtpOptions */ - public $EOL = "\n"; + protected $options; /** - * Remote smtp hostname or i.p. - * - * @var string + * @var SmtpProtocol */ - protected $_host; - + protected $connection; /** - * Port number - * - * @var integer|null + * @var SmtpBroker */ - protected $_port; - + protected $broker; /** - * Local client hostname or i.p. + * Constructor. * - * @var string + * @param null|SmtpOptions $options + * @return void */ - protected $_name = 'localhost'; - + public function __construct(SmtpOptions $options = null) + { + if (!$options instanceof SmtpOptions) { + $options = new SmtpOptions(); + } + $this->setOptions($options); + } /** - * Authentication type OPTIONAL + * Set options * - * @var string + * @param SmtpOptions $options + * @return Smtp */ - protected $_auth; - + public function setOptions(SmtpOptions $options) + { + $this->options = $options; + return $this; + } /** - * Config options for authentication - * - * @var array + * Get options + * + * @return SmtpOptions */ - protected $_config; - + public function getOptions() + { + return $this->options; + } /** - * Instance of \Zend\Mail\Protocol\Smtp + * Set broker for obtaining SMTP protocol connection * - * @var \Zend\Mail\Protocol\Smtp + * @param SmtpBroker $value + * @return $this */ - protected $_connection; - - + public function setBroker($broker) + { + if (!$broker instanceof SmtpBroker) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects an SmtpBroker argument; received "%s"', + __METHOD__, + (is_object($broker) ? get_class($broker) : gettype($broker)) + )); + } + $this->broker = $broker; + return $this; + } + /** - * Constructor. - * - * @param string $host OPTIONAL (Default: 127.0.0.1) - * @param array|null $config OPTIONAL (Default: null) - * @return void + * Get broker for loading SMTP protocol connection * - * @todo Someone please make this compatible - * with the SendMail transport class. + * @return SmtpBroker */ - public function __construct($host = '127.0.0.1', Array $config = array()) + public function getBroker() { - if ($host) { - $config['host'] = $host; + if (null === $this->broker) { + $this->setBroker(new SmtpBroker()); } - - $this->setConfig($config); + return $this->broker; } /** - * Set configuration - * - * @param array $config - * @return \Zend\Mail\Transport\Smtp + * Return an SMTP connection + * + * @param string $name + * @param array|null $options + * @return \Zend\Mail\Protocol\Smtp */ - public function setConfig(array $config) + public function plugin($name, array $options = null) { - if (isset($config['name'])) { - $this->_name = $config['name']; - } - if (isset($config['port'])) { - $this->_port = $config['port']; - } - if (isset($config['auth'])) { - $this->_auth = $config['auth']; - } - if (isset($config['host'])) { - $this->_host = $config['host']; - } - - $this->_config = $config; - - return $this; + return $this->getBroker()->load($name, $options); } /** @@ -155,13 +149,13 @@ public function setConfig(array $config) */ public function __destruct() { - if ($this->_connection instanceof SmtpProtocol) { + if ($this->connection instanceof SmtpProtocol) { try { - $this->_connection->quit(); + $this->connection->quit(); } catch (Protocol\Exception $e) { // ignore } - $this->_connection->disconnect(); + $this->connection->disconnect(); } } @@ -169,24 +163,24 @@ public function __destruct() /** * Sets the connection protocol instance * - * @param \Zend\Mail\Protocol\AbstractProtocol $client + * @param AbstractProtocol $client * * @return void */ public function setConnection(AbstractProtocol $connection) { - $this->_connection = $connection; + $this->connection = $connection; } /** * Gets the connection protocol instance * - * @return \Zend\Mail\Protocol|null + * @return Protocol|null */ public function getConnection() { - return $this->_connection; + return $this->connection; } /** @@ -196,56 +190,131 @@ public function getConnection() * developer to add a custom adapter if required before mail is sent. * * @return void - * @todo Rename this to sendMail, it's a public method... */ - public function _sendMail() + public function send(Message $message) { // If sending multiple messages per session use existing adapter - if (!($this->_connection instanceof SmtpProtocol)) { - // Check if authentication is required and determine required class - $connectionClass = '\Zend\Mail\Protocol\Smtp'; - if ($this->_auth) { - $connectionClass .= '\Auth\\' . ucwords($this->_auth); - } - $this->setConnection(new $connectionClass($this->_host, $this->_port, $this->_config)); - $this->_connection->connect(); - $this->_connection->helo($this->_name); + $connection = $this->getConnection(); + + if (!($connection instanceof SmtpProtocol)) { + // First time connecting + $connection = $this->lazyLoadConnection(); } else { // Reset connection to ensure reliable transaction - $this->_connection->rset(); + $connection->rset(); } + // Prepare message + $from = $this->prepareFromAddress($message); + $recipients = $this->prepareRecipients($message); + $headers = $this->prepareHeaders($message); + $body = $this->prepareBody($message); + // Set sender email address - $this->_connection->mail($this->_mail->getFrom()); + $connection->mail($from); // Set recipient forward paths - foreach ($this->_mail->getRecipients() as $recipient) { - $this->_connection->rcpt($recipient); + foreach ($recipients as $recipient) { + $connection->rcpt($recipient); } // Issue DATA command to client - $this->_connection->data($this->header . Mime\Mime::LINEEND . $this->body); + $connection->data($headers . "\r\n" . $body); } /** - * Format and fix headers - * - * Some SMTP servers do not strip BCC headers. Most clients do it themselves as do we. - * - * @access protected - * @param array $headers - * @return void - * @throws \Zend\Transport\Exception + * Retrieve email address for envelope FROM + * + * @param Message $message + * @return string */ - protected function _prepareHeaders($headers) + protected function prepareFromAddress(Message $message) { - if (!$this->_mail) { - throw new Exception\RuntimeException('_prepareHeaders requires a registered \Zend\Mail\Mail object'); + $sender = $message->getSender(); + if ($sender instanceof AddressDescription) { + return $sender->getEmail(); } - unset($headers['Bcc']); + $from = $message->from(); + if (!count($from)) { + throw new Exception\RuntimeException(sprintf( + '%s transport expects either a Sender or at least one From address in the Message; none provided', + __CLASS__ + )); + } + + $from->rewind(); + $sender = $from->current(); + return $sender->getEmail(); + } - // Prepare headers - parent::_prepareHeaders($headers); + /** + * Prepare array of email address recipients + * + * @param Message $message + * @return array + */ + protected function prepareRecipients(Message $message) + { + $recipients = array(); + foreach ($message->to() as $address) { + $recipients[] = $address->getEmail(); + } + foreach ($message->cc() as $address) { + $recipients[] = $address->getEmail(); + } + foreach ($message->bcc() as $address) { + $recipients[] = $address->getEmail(); + } + $recipients = array_unique($recipients); + return $recipients; + } + + /** + * Prepare header string from message + * + * @param Message $message + * @return string + */ + protected function prepareHeaders(Message $message) + { + $headers = new Headers(); + foreach ($message->headers() as $header) { + if ('Bcc' == $header->getFieldName()) { + continue; + } + $headers->addHeader($header); + } + return $headers->toString(); + } + + /** + * Prepare body string from message + * + * @param Message $message + * @return string + */ + protected function prepareBody(Message $message) + { + return $message->getBodyText(); + } + + /** + * Lazy load the connection, and pass it helo + * + * @return SmtpProtocol + */ + protected function lazyLoadConnection() + { + // Check if authentication is required and determine required class + $options = $this->getOptions(); + $host = $options->getHost(); + $port = $options->getPort(); + $config = $options->getConnectionConfig(); + $connection = $this->plugin($options->getConnectionClass(), array($host, $port, $config)); + $this->connection = $connection; + $this->connection->connect(); + $this->connection->helo($options->getName()); + return $this->connection; } } diff --git a/src/Transport/SmtpOptions.php b/src/Transport/SmtpOptions.php new file mode 100644 index 00000000..0af488e0 --- /dev/null +++ b/src/Transport/SmtpOptions.php @@ -0,0 +1,196 @@ +name; + } + + /** + * Set the local client hostname or IP + * + * @todo hostname/IP validation + * @param string $name + * @return SmtpOptions + */ + public function setName($name) + { + if (!is_string($name) && $name !== null) { + throw new Exception\InvalidArgumentException(sprintf( + 'Name must be a string or null; argument of type "%s" provided', + (is_object($name) ? get_class($name) : gettype($name)) + )); + } + $this->name = $name; + return $this; + } + + /** + * Get connection class + * + * This should be either the class Zend\Mail\Protocol\Smtp or a class + * extending it -- typically a class in the Zend\Mail\Protocol\Smtp\Auth + * namespace. + * + * @return null|string + */ + public function getConnectionClass() + { + return $this->connectionClass; + } + + /** + * Set connection class + * + * @param string $connectionClass the value to be set + * @return SmtpOptions + */ + public function setConnectionClass($connectionClass) + { + if (!is_string($connectionClass) && $connectionClass !== null) { + throw new Exception\InvalidArgumentException(sprintf( + 'Connection class must be a string or null; argument of type "%s" provided', + (is_object($connectionClass) ? get_class($connectionClass) : gettype($connectionClass)) + )); + } + $this->connectionClass = $connectionClass; + return $this; + } + + /** + * Get connection configuration array + * + * @return array + */ + public function getConnectionConfig() + { + return $this->connectionConfig; + } + + /** + * Set connection configuration array + * + * @param array $connectionConfig + * @return SmtpOptions + */ + public function setConnectionConfig(array $connectionConfig) + { + $this->connectionConfig = $connectionConfig; + return $this; + } + + /** + * Get the host name + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the SMTP host + * + * @todo hostname/IP validation + * @param string $host + * @return SmtpOptions + */ + public function setHost($host) + { + $this->host = (string) $host; + } + + /** + * Get the port the SMTP server runs on + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Set the port the SMTP server runs on + * + * @param int $port + * @return SmtpOptions + */ + public function setPort($port) + { + $port = (int) $port; + if ($port < 1) { + throw new Exception\InvalidArgumentException(sprintf( + 'Port must be greater than 1; received "%d"', + $port + )); + } + $this->port = $port; + return $this; + } +} diff --git a/test/AddressListTest.php b/test/AddressListTest.php new file mode 100644 index 00000000..a3729385 --- /dev/null +++ b/test/AddressListTest.php @@ -0,0 +1,123 @@ +list = new AddressList(); + } + + public function testImplementsCountable() + { + $this->assertInstanceOf('Countable', $this->list); + } + + public function testIsEmptyByDefault() + { + $this->assertEquals(0, count($this->list)); + } + + public function testAddingEmailsIncreasesCount() + { + $this->list->add('zf-devteam@zend.com'); + $this->assertEquals(1, count($this->list)); + } + + public function testImplementsTraversable() + { + $this->assertInstanceOf('Traversable', $this->list); + } + + public function testHasReturnsFalseWhenAddressNotInList() + { + $this->assertFalse($this->list->has('foo@example.com')); + } + + public function testHasReturnsTrueWhenAddressInList() + { + $this->list->add('zf-devteam@zend.com'); + $this->assertTrue($this->list->has('zf-devteam@zend.com')); + } + + public function testGetReturnsFalseWhenEmailNotFound() + { + $this->assertFalse($this->list->get('foo@example.com')); + } + + public function testGetReturnsAddressObjectWhenEmailFound() + { + $this->list->add('zf-devteam@zend.com'); + $address = $this->list->get('zf-devteam@zend.com'); + $this->assertInstanceOf('Zend\Mail\Address', $address); + $this->assertEquals('zf-devteam@zend.com', $address->getEmail()); + } + + public function testCanAddAddressWithName() + { + $this->list->add('zf-devteam@zend.com', 'ZF DevTeam'); + $address = $this->list->get('zf-devteam@zend.com'); + $this->assertInstanceOf('Zend\Mail\Address', $address); + $this->assertEquals('zf-devteam@zend.com', $address->getEmail()); + $this->assertEquals('ZF DevTeam', $address->getName()); + } + + public function testCanAddManyAddressesAtOnce() + { + $addresses = array( + 'zf-devteam@zend.com', + 'zf-contributors@lists.zend.com' => 'ZF Contributors List', + new Address('fw-announce@lists.zend.com', 'ZF Announce List'), + ); + $this->list->addMany($addresses); + $this->assertEquals(3, count($this->list)); + $this->assertTrue($this->list->has('zf-devteam@zend.com')); + $this->assertTrue($this->list->has('zf-contributors@lists.zend.com')); + $this->assertTrue($this->list->has('fw-announce@lists.zend.com')); + } + + public function testDoesNotStoreDuplicatesAndFirstWins() + { + $addresses = array( + 'zf-devteam@zend.com', + new Address('zf-devteam@zend.com', 'ZF DevTeam'), + ); + $this->list->addMany($addresses); + $this->assertEquals(1, count($this->list)); + $this->assertTrue($this->list->has('zf-devteam@zend.com')); + $address = $this->list->get('zf-devteam@zend.com'); + $this->assertNull($address->getName()); + } +} diff --git a/test/AddressTest.php b/test/AddressTest.php new file mode 100644 index 00000000..8b393ec9 --- /dev/null +++ b/test/AddressTest.php @@ -0,0 +1,56 @@ +assertEquals('zf-devteam@zend.com', $address->getEmail()); + $this->assertNull($address->getName()); + } + + public function testAcceptsNameViaConstructor() + { + $address = new Address('zf-devteam@zend.com', 'ZF DevTeam'); + $this->assertEquals('zf-devteam@zend.com', $address->getEmail()); + $this->assertEquals('ZF DevTeam', $address->getName()); + } + + public function testToStringCreatesStringRepresentation() + { + $address = new Address('zf-devteam@zend.com', 'ZF DevTeam'); + $this->assertEquals('ZF DevTeam ', $address->toString()); + } +} diff --git a/test/FileTest.php b/test/FileTest.php deleted file mode 100644 index 316ae8e3..00000000 --- a/test/FileTest.php +++ /dev/null @@ -1,176 +0,0 @@ -_tmpdir = TESTS_ZEND_MAIL_TEMPDIR; - } else { - $this->_tmpdir = dirname(__FILE__) . '/_files/test.file/'; - } - - if (!file_exists($this->_tmpdir)) { - mkdir($this->_tmpdir); - } - - $this->_cleanDir($this->_tmpdir); - } - - public function tearDown() - { - $this->_cleanDir($this->_tmpdir); - } - - protected function _cleanDir($dir) - { - $entries = scandir($dir); - foreach ($entries as $entry) { - if ($entry == '.' || $entry == '..') { - continue; - } - - $fullname = $dir . DIRECTORY_SEPARATOR . $entry; - - if (is_dir($fullname)) { - $this->_cleanDir($fullname); - rmdir($fullname); - } else { - unlink($fullname); - } - } - } - - public function testTransportSetup() - { - $transport = new Mail\Transport\File(); - - $callback = function() { - return 'test'; - }; - - $transport = new Mail\Transport\File(array( - 'path' => $this->_tmpdir, - 'callback' => $callback, - )); - - $this->assertEquals($this->_tmpdir, $transport->getPath()); - $this->assertSame($callback, $transport->getCallback()); - } - - protected function _prepareMail() - { - $mail = new Mail\Mail(); - $mail->setBodyText('This is the text of the mail.'); - $mail->setFrom('alexander@example.com', 'Alexander Steshenko'); - $mail->addTo('oleg@example.com', 'Oleg Lobach'); - $mail->setSubject('TestSubject'); - - return $mail; - } - - public function testNotWritablePathFailure() - { - $transport = new Mail\Transport\File(array( - 'path' => $this->_tmpdir . '/not_existing/directory' - )); - - $mail = $this->_prepareMail(); - - $this->setExpectedException('Zend\Mail\Transport\Exception\RuntimeException', 'not writable'); - $mail->send($transport); - } - - public function testTransportSendMail() - { - $transport = new Mail\Transport\File(array('path' => $this->_tmpdir)); - - $mail = $this->_prepareMail(); - $mail->send($transport); - - $entries = scandir($this->_tmpdir); - $this->assertTrue(count($entries) == 3); - foreach ($entries as $entry) { - if ($entry == '.' || $entry == '..') { - continue; - } - $filename = $this->_tmpdir . DIRECTORY_SEPARATOR . $entry; - } - - $email = file_get_contents($filename); - $this->assertContains('To: Oleg Lobach ', $email); - $this->assertContains('Subject: TestSubject', $email); - $this->assertContains('From: Alexander Steshenko ', $email); - $this->assertContains("This is the text of the mail.", $email); - } - - public function testPrependToClosure() - { - // callback utilizes default callback and prepends recipient email - $callback = function($transport) { - $defaultCallback = $transport->getDefaultCallback(); - return $transport->recipients . '_' . $defaultCallback($transport); - }; - - $transport = new Mail\Transport\File(array( - 'path' => $this->_tmpdir, - 'callback' => $callback - )); - - $mail = $this->_prepareMail(); - $mail->send($transport); - - $entries = scandir($this->_tmpdir); - $this->assertTrue(count($entries) == 3); - foreach ($entries as $entry) { - if ($entry == '.' || $entry == '..') { - continue; - } else { - break; - } - } - - // file name should now contain recipient email address - $this->assertContains('oleg@example.com', $entry); - // and default callback part - $this->assertContains('ZendMail', $entry); - } -} diff --git a/test/Header/AddressListHeaderTest.php b/test/Header/AddressListHeaderTest.php new file mode 100644 index 00000000..ec8a428e --- /dev/null +++ b/test/Header/AddressListHeaderTest.php @@ -0,0 +1,150 @@ +assertInstanceOf('Zend\Mail\Header\AbstractAddressList', $header); + } + + /** + * @dataProvider getHeaderInstances + */ + public function testConcreteHeaderFieldNamesAreDiscrete($header, $type) + { + $this->assertEquals($type, $header->getFieldName()); + } + + /** + * @dataProvider getHeaderInstances + */ + public function testConcreteHeadersComposeAddressLists($header) + { + $list = $header->getAddressList(); + $this->assertInstanceOf('Zend\Mail\AddressList', $list); + } + + public function testFieldValueIsEmptyByDefault() + { + $header = new To(); + $this->assertEquals('', $header->getFieldValue()); + } + + public function testFieldValueIsCreatedFromAddressList() + { + $header = new To(); + $list = $header->getAddressList(); + $this->populateAddressList($list); + $expected = $this->getExpectedFieldValue(); + $this->assertEquals($expected, $header->getFieldValue()); + } + + public function populateAddressList(AddressList $list) + { + $address = new Address('zf-devteam@zend.com', 'ZF DevTeam'); + $list->add($address); + $list->add('zf-contributors@lists.zend.com'); + $list->add('fw-announce@lists.zend.com', 'ZF Announce List'); + } + + public function getExpectedFieldValue() + { + return "ZF DevTeam ,\r\n zf-contributors@lists.zend.com,\r\n ZF Announce List "; + } + + /** + * @dataProvider getHeaderInstances + */ + public function testStringRepresentationIncludesHeaderAndFieldValue($header, $type) + { + $this->populateAddressList($header->getAddressList()); + $expected = sprintf("%s: %s\r\n", $type, $this->getExpectedFieldValue()); + $this->assertEquals($expected, $header->toString()); + } + + public function getStringHeaders() + { + $value = $this->getExpectedFieldValue(); + return array( + array('Cc: ' . $value, 'Zend\Mail\Header\Cc'), + array('Bcc: ' . $value, 'Zend\Mail\Header\Bcc'), + array('From: ' . $value, 'Zend\Mail\Header\From'), + array('Reply-To: ' . $value, 'Zend\Mail\Header\ReplyTo'), + array('To: ' . $value, 'Zend\Mail\Header\To'), + ); + } + + /** + * @dataProvider getStringHeaders + */ + public function testDeserializationFromString($headerLine, $class) + { + $callback = sprintf('%s::fromString', $class); + $header = call_user_func($callback, $headerLine); + $this->assertInstanceOf($class, $header); + $list = $header->getAddressList(); + $this->assertEquals(3, count($list)); + $this->assertTrue($list->has('zf-devteam@zend.com')); + $this->assertTrue($list->has('zf-contributors@lists.zend.com')); + $this->assertTrue($list->has('fw-announce@lists.zend.com')); + $address = $list->get('zf-devteam@zend.com'); + $this->assertEquals('ZF DevTeam', $address->getName()); + $address = $list->get('zf-contributors@lists.zend.com'); + $this->assertNull($address->getName()); + $address = $list->get('fw-announce@lists.zend.com'); + $this->assertEquals('ZF Announce List', $address->getName()); + } +} diff --git a/test/Header/ContentTypeTest.php b/test/Header/ContentTypeTest.php new file mode 100644 index 00000000..82155c82 --- /dev/null +++ b/test/Header/ContentTypeTest.php @@ -0,0 +1,76 @@ +assertInstanceOf('Zend\Mail\Header', $contentTypeHeader); + $this->assertInstanceOf('Zend\Mail\Header\ContentType', $contentTypeHeader); + } + + public function testContentTypeGetFieldNameReturnsHeaderName() + { + $contentTypeHeader = new ContentType(); + $this->assertEquals('Content-Type', $contentTypeHeader->getFieldName()); + } + + public function testContentTypeGetFieldValueReturnsProperValue() + { + $contentTypeHeader = new ContentType(); + $contentTypeHeader->setType('foo/bar'); + $this->assertEquals('foo/bar', $contentTypeHeader->getFieldValue()); + } + + public function testContentTypeToStringReturnsHeaderFormattedString() + { + $contentTypeHeader = new ContentType(); + $contentTypeHeader->setType('foo/bar'); + $this->assertEquals("Content-Type: foo/bar\r\n", $contentTypeHeader->toString()); + } + + public function testProvidingParametersIntroducesHeaderFolding() + { + $header = new ContentType(); + $header->setType('application/x-unit-test'); + $header->addParameter('charset', 'us-ascii'); + $string = $header->toString(); + + $this->assertContains("Content-Type: application/x-unit-test;\r\n", $string); + $this->assertContains(";\r\n charset=\"us-ascii\"", $string); + } + +} + diff --git a/test/Header/ReceivedTest.php b/test/Header/ReceivedTest.php new file mode 100644 index 00000000..cc485ece --- /dev/null +++ b/test/Header/ReceivedTest.php @@ -0,0 +1,71 @@ +assertInstanceOf('Zend\Mail\Header', $receivedHeader); + $this->assertInstanceOf('Zend\Mail\Header\Received', $receivedHeader); + } + + public function testGetFieldNameReturnsHeaderName() + { + $receivedHeader = new Received(); + $this->assertEquals('Received', $receivedHeader->getFieldName()); + } + + public function testReceivedGetFieldValueReturnsProperValue() + { + $this->markTestIncomplete('Received needs to be completed'); + + $receivedHeader = new Received(); + $this->assertEquals('xxx', $receivedHeader->getFieldValue()); + } + + public function testReceivedToStringReturnsHeaderFormattedString() + { + $this->markTestIncomplete('Received needs to be completed'); + + $receivedHeader = new Received(); + + // @todo set some values, then test output + $this->assertEmpty('Received: xxx', $receivedHeader->toString()); + } + + /** Implementation specific tests here */ + +} + diff --git a/test/Header/SubjectTest.php b/test/Header/SubjectTest.php new file mode 100644 index 00000000..0863ce3a --- /dev/null +++ b/test/Header/SubjectTest.php @@ -0,0 +1,47 @@ +setSubject($string); + + $expected = wordwrap($string, 78, "\r\n "); + $test = $subject->getFieldValue(); + $this->assertEquals($expected, $test); + } +} diff --git a/test/Header/ToTest.php b/test/Header/ToTest.php new file mode 100644 index 00000000..5fbc6456 --- /dev/null +++ b/test/Header/ToTest.php @@ -0,0 +1,51 @@ +getAddressList(); + for ($i = 0; $i < 10; $i++) { + $list->add(uniqid() . '@zend.com'); + } + $string = $header->getFieldValue(); + $emails = explode("\r\n ", $string); + $this->assertEquals(10, count($emails)); + } +} diff --git a/test/HeadersTest.php b/test/HeadersTest.php new file mode 100644 index 00000000..7c360ace --- /dev/null +++ b/test/HeadersTest.php @@ -0,0 +1,321 @@ +assertInstanceOf('Iterator', $headers); + $this->assertInstanceOf('Countable', $headers); + } + + public function testHeadersFromStringFactoryCreatesSingleObject() + { + $headers = Headers::fromString("Fake: foo-bar"); + $this->assertEquals(1, $headers->count()); + + $header = $headers->get('fake'); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $header); + $this->assertEquals('Fake', $header->getFieldName()); + $this->assertEquals('foo-bar', $header->getFieldValue()); + } + + public function testHeadersFromStringFactoryCreatesSingleObjectWithContinuationLine() + { + $headers = Headers::fromString("Fake: foo-bar,\r\n blah-blah"); + $this->assertEquals(1, $headers->count()); + + $header = $headers->get('fake'); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $header); + $this->assertEquals('Fake', $header->getFieldName()); + $this->assertEquals('foo-bar,blah-blah', $header->getFieldValue()); + } + + public function testHeadersFromStringFactoryCreatesSingleObjectWithHeaderBreakLine() + { + $headers = Headers::fromString("Fake: foo-bar\r\n\r\n"); + $this->assertEquals(1, $headers->count()); + + $header = $headers->get('fake'); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $header); + $this->assertEquals('Fake', $header->getFieldName()); + $this->assertEquals('foo-bar', $header->getFieldValue()); + } + + public function testHeadersFromStringFactoryThrowsExceptionOnMalformedHeaderLine() + { + $this->setExpectedException('Zend\Mail\Exception\RuntimeException', 'does not match'); + Headers::fromString("Fake = foo-bar\r\n\r\n"); + } + + public function testHeadersFromStringFactoryCreatesMultipleObjects() + { + $headers = Headers::fromString("Fake: foo-bar\r\nAnother-Fake: boo-baz"); + $this->assertEquals(2, $headers->count()); + + $header = $headers->get('fake'); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $header); + $this->assertEquals('Fake', $header->getFieldName()); + $this->assertEquals('foo-bar', $header->getFieldValue()); + + $header = $headers->get('anotherfake'); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $header); + $this->assertEquals('Another-Fake', $header->getFieldName()); + $this->assertEquals('boo-baz', $header->getFieldValue()); + } + + public function testHeadersFromStringMultiHeaderWillAggregateLazyLoadedHeaders() + { + $headers = new Headers(); + /* @var $pcl \Zend\Loader\PluginClassLoader */ + $pcl = $headers->getPluginClassLoader(); + $pcl->registerPlugin('foo', 'Zend\Mail\Header\GenericMultiHeader'); + $headers->addHeaderLine('foo: bar1,bar2,bar3'); + $headers->forceLoading(); + $this->assertEquals(3, $headers->count()); + } + + public function testHeadersHasAndGetWorkProperly() + { + $headers = new Headers(); + $headers->addHeaders(array($f = new Header\GenericHeader('Foo', 'bar'), new Header\GenericHeader('Baz', 'baz'))); + $this->assertFalse($headers->has('foobar')); + $this->assertTrue($headers->has('foo')); + $this->assertTrue($headers->has('Foo')); + $this->assertSame($f, $headers->get('foo')); + } + + public function testHeadersAggregatesHeaderObjects() + { + $fakeHeader = new Header\GenericHeader('Fake', 'bar'); + $headers = new Headers(); + $headers->addHeader($fakeHeader); + $this->assertEquals(1, $headers->count()); + $this->assertSame($fakeHeader, $headers->get('Fake')); + } + + public function testHeadersAggregatesHeaderThroughAddHeader() + { + $headers = new Headers(); + $headers->addHeader(new Header\GenericHeader('Fake', 'bar')); + $this->assertEquals(1, $headers->count()); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $headers->get('Fake')); + } + + public function testHeadersAggregatesHeaderThroughAddHeaderLine() + { + $headers = new Headers(); + $headers->addHeaderLine('Fake', 'bar'); + $this->assertEquals(1, $headers->count()); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $headers->get('Fake')); + } + + public function testHeadersAddHeaderLineThrowsExceptionOnMissingFieldValue() + { + $this->setExpectedException('Zend\Mail\Exception\InvalidArgumentException', 'without a field'); + $headers = new Headers(); + $headers->addHeaderLine('Foo'); + } + + public function testHeadersAggregatesHeadersThroughAddHeaders() + { + $headers = new Headers(); + $headers->addHeaders(array(new Header\GenericHeader('Foo', 'bar'), new Header\GenericHeader('Baz', 'baz'))); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + + $headers = new Headers(); + $headers->addHeaders(array('Foo: bar', 'Baz: baz')); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + + $headers = new Headers(); + $headers->addHeaders(array(array('Foo' => 'bar'), array('Baz' => 'baz'))); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + + $headers = new Headers(); + $headers->addHeaders(array(array('Foo', 'bar'), array('Baz', 'baz'))); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $this->assertEquals(2, $headers->count()); + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $headers->get('Foo')); + $this->assertEquals('bar', $headers->get('foo')->getFieldValue()); + $this->assertEquals('baz', $headers->get('baz')->getFieldValue()); + } + + public function testHeadersAddHeadersThrowsExceptionOnInvalidArguments() + { + $this->setExpectedException('Zend\Mail\Exception\InvalidArgumentException', 'Expected array or Trav'); + $headers = new Headers(); + $headers->addHeaders('foo'); + } + + public function testHeadersCanRemoveHeader() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $header = $headers->get('foo'); + $this->assertEquals(2, $headers->count()); + $headers->removeHeader($header); + $this->assertEquals(1, $headers->count()); + $this->assertFalse($headers->get('foo')); + } + + public function testHeadersCanClearAllHeaders() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $this->assertEquals(2, $headers->count()); + $headers->clearHeaders(); + $this->assertEquals(0, $headers->count()); + } + + public function testHeadersCanBeIterated() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $iterations = 0; + foreach ($headers as $index => $header) { + $iterations++; + $this->assertInstanceOf('Zend\Mail\Header\GenericHeader', $header); + switch ($index) { + case 0: + $this->assertEquals('bar', $header->getFieldValue()); + break; + case 1: + $this->assertEquals('baz', $header->getFieldValue()); + break; + default: + $this->fail('Invalid index returned from iterator'); + } + } + $this->assertEquals(2, $iterations); + } + + public function testHeadersCanBeCastToString() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $this->assertEquals('Foo: bar' . "\r\n" . 'Baz: baz' . "\r\n", $headers->toString()); + } + + public function testHeadersCanBeCastToArray() + { + $headers = new Headers(); + $headers->addHeaders(array('Foo' => 'bar', 'Baz' => 'baz')); + $this->assertEquals(array('Foo' => 'bar', 'Baz' => 'baz'), $headers->toArray()); + } + + public function testCastingToArrayReturnsMultiHeadersAsArrays() + { + $headers = new Headers(); + $received1 = Header\Received::fromString("Received: from framework (localhost [127.0.0.1])\r\nby framework (Postfix) with ESMTP id BBBBBBBBBBB\r\nfor ; Mon, 21 Nov 2011 12:50:27 -0600 (CST)"); + $received2 = Header\Received::fromString("Received: from framework (localhost [127.0.0.1])\r\nby framework (Postfix) with ESMTP id AAAAAAAAAAA\r\nfor ; Mon, 21 Nov 2011 12:50:29 -0600 (CST)"); + $headers->addHeader($received1); + $headers->addHeader($received2); + $array = $headers->toArray(); + $expected = array( + 'Received' => array( + $received1->getFieldValue(), + $received2->getFieldValue(), + ), + ); + $this->assertEquals($expected, $array); + } + + public function testCastingToStringReturnsAllMultiHeaderValues() + { + $headers = new Headers(); + $received1 = Header\Received::fromString("Received: from framework (localhost [127.0.0.1])\r\nby framework (Postfix) with ESMTP id BBBBBBBBBBB\r\nfor ; Mon, 21 Nov 2011 12:50:27 -0600 (CST)"); + $received2 = Header\Received::fromString("Received: from framework (localhost [127.0.0.1])\r\nby framework (Postfix) with ESMTP id AAAAAAAAAAA\r\nfor ; Mon, 21 Nov 2011 12:50:29 -0600 (CST)"); + $headers->addHeader($received1); + $headers->addHeader($received2); + $string = $headers->toString(); + $expected = array( + 'Received: ' . $received1->getFieldValue(), + 'Received: ' . $received2->getFieldValue(), + ); + $expected = implode("\r\n", $expected) . "\r\n"; + $this->assertEquals($expected, $string); + } + + public static function expectedHeaders() + { + return array( + array('bcc', 'Zend\Mail\Header\Bcc'), + array('cc', 'Zend\Mail\Header\Cc'), + array('contenttype', 'Zend\Mail\Header\ContentType'), + array('content_type', 'Zend\Mail\Header\ContentType'), + array('content-type', 'Zend\Mail\Header\ContentType'), + array('from', 'Zend\Mail\Header\From'), + array('mimeversion', 'Zend\Mail\Header\MimeVersion'), + array('mime_version', 'Zend\Mail\Header\MimeVersion'), + array('mime-version', 'Zend\Mail\Header\MimeVersion'), + array('origdate', 'Zend\Mail\Header\OrigDate'), + array('orig_date', 'Zend\Mail\Header\OrigDate'), + array('orig-date', 'Zend\Mail\Header\OrigDate'), + array('received', 'Zend\Mail\Header\Received'), + array('replyto', 'Zend\Mail\Header\ReplyTo'), + array('reply_to', 'Zend\Mail\Header\ReplyTo'), + array('reply-to', 'Zend\Mail\Header\ReplyTo'), + array('sender', 'Zend\Mail\Header\Sender'), + array('subject', 'Zend\Mail\Header\Subject'), + array('to', 'Zend\Mail\Header\To'), + ); + } + + /** + * @dataProvider expectedHeaders + */ + public function testDefaultPluginLoaderIsSeededWithHeaders($plugin, $class) + { + $headers = new Headers(); + $loader = $headers->getPluginClassLoader(); + $test = $loader->load($plugin); + $this->assertEquals($class, $test); + } +} diff --git a/test/MailTest.php b/test/MailTest.php deleted file mode 100644 index e001061b..00000000 --- a/test/MailTest.php +++ /dev/null @@ -1,1068 +0,0 @@ -_originaltimezone = date_default_timezone_get(); - // Set timezone to avoid "date(): It is not safe to rely on the system's timezone settings." - // message. - date_default_timezone_set('GMT'); - } - - public function tearDown() - { - Mail\Mail::clearDefaultFrom(); - Mail\Mail::clearDefaultReplyTo(); - date_default_timezone_set($this->_originaltimezone); - } - - /** - * Test case for a simple email text message with - * multiple recipients. - * - */ - public function testOnlyText() - { - $mail = new Mail\Mail(); - $res = $mail->setBodyText('This is a test.'); - $mail->setFrom('testmail@example.com', 'test Mail User'); - $mail->setSubject('My Subject'); - $mail->addTo('recipient1@example.com'); - $mail->addTo('recipient2@example.com'); - $mail->addBcc('recipient1_bcc@example.com'); - $mail->addBcc('recipient2_bcc@example.com'); - $mail->addCc('recipient1_cc@example.com', 'Example no. 1 for cc'); - $mail->addCc('recipient2_cc@example.com', 'Example no. 2 for cc'); - - $mock = new TransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertEquals('My Subject', $mock->subject); - $this->assertEquals('testmail@example.com', $mock->from); - $this->assertContains('recipient1@example.com', $mock->recipients); - $this->assertContains('recipient2@example.com', $mock->recipients); - $this->assertContains('recipient1_bcc@example.com', $mock->recipients); - $this->assertContains('recipient2_bcc@example.com', $mock->recipients); - $this->assertContains('recipient1_cc@example.com', $mock->recipients); - $this->assertContains('recipient2_cc@example.com', $mock->recipients); - $this->assertContains('This is a test.', $mock->body); - $this->assertContains('Content-Transfer-Encoding: quoted-printable', $mock->header); - $this->assertContains('Content-Type: text/plain', $mock->header); - $this->assertContains('From: test Mail User ', $mock->header); - $this->assertContains('Subject: My Subject', $mock->header); - $this->assertContains('To: recipient1@example.com', $mock->header); - $this->assertContains('Cc: Example no. 1 for cc ', $mock->header); - } - - /** - * Test sending in arrays of recipients - */ - public function testArrayRecipients() - { - $mail = new Mail\Mail(); - $res = $mail->setBodyText('Test #2'); - $mail->setFrom('eli@example.com', 'test Mail User'); - $mail->setSubject('Subject #2'); - $mail->addTo(array('heather@example.com', 'Ramsey White' => 'ramsey@example.com')); - $mail->addCc(array('keith@example.com', 'Cal Evans' => 'cal@example.com')); - $mail->addBcc(array('ralph@example.com', 'matthew@example.com')); - - $mock = new TransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertEquals('eli@example.com', $mock->from); - $this->assertContains('heather@example.com', $mock->recipients); - $this->assertContains('ramsey@example.com', $mock->recipients); - $this->assertContains('ralph@example.com', $mock->recipients); - $this->assertContains('matthew@example.com', $mock->recipients); - $this->assertContains('keith@example.com', $mock->recipients); - $this->assertContains('cal@example.com', $mock->recipients); - $this->assertContains('Test #2', $mock->body); - $this->assertContains('From: test Mail User ', $mock->header); - $this->assertContains('Subject: Subject #2', $mock->header); - $this->assertContains('To: heather@example.com', $mock->header); - $this->assertContains('Ramsey White ', $mock->header); - $this->assertContains('Cal Evans ', $mock->header); - } - - /** - * @group ZF-8503 Test recipients Header format. - */ - public function testRecipientsHeaderFormat() - { - $mail = new Mail\Mail(); - $res = $mail->setBodyText('Test recipients Header format.'); - $mail->setFrom('yoshida@example.com', 'test Mail User'); - $mail->setSubject('Test recipients Header format.'); - $mail->addTo('address_to1@example.com', 'name_to@example.com'); - $mail->addTo('address_to2@example.com', 'noinclude comma nor at mark'); - $mail->addCc('address_cc@example.com', 'include, name_cc'); - - $mock = new TransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertEquals('yoshida@example.com', $mock->from); - $this->assertContains('Test recipients Header format.', $mock->body); - $this->assertContains('To: "name_to@example.com" ', $mock->header); - $this->assertContains('noinclude comma nor at mark ', $mock->header); - $this->assertContains('Cc: "include, name_cc" ', $mock->header); - } - - /** - * Check if Header Fields are encoded correctly and if - * header injection is prevented. - */ - public function testHeaderEncoding() - { - $mail = new Mail\Mail("UTF-8"); - $mail->setBodyText('My Nice Test Text'); - // try header injection: - $mail->addTo("testmail@example.com\nCc:foobar@example.com"); - $mail->addHeader('X-MyTest', "Test\nCc:foobar2@example.com", true); - // try special Chars in Header Fields: - $mail->setFrom('mymail@example.com', "\xC6\x98\xC6\x90\xC3\xA4\xC4\xB8"); - $mail->addTo('testmail2@example.com', "\xC4\xA7\xC4\xAF\xC7\xAB"); - $mail->addCc('testmail3@example.com', "\xC7\xB6\xC7\xB7"); - $mail->setSubject("\xC7\xB1\xC7\xAE"); - $mail->addHeader('X-MyTest', "Test-\xC7\xB1", true); - - $mock = new TransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertContains( - 'From: =?UTF-8?Q?=C6=98=C6=90=C3=A4=C4=B8?=', - $mock->header, - "From: Header was encoded unexpectedly." - ); - $this->assertContains( - "Cc:foobar@example.com", - $mock->header - ); - $this->assertNotContains( - "\nCc:foobar@example.com", - $mock->header, - "Injection into From: header is possible." - ); - $this->assertContains( - '=?UTF-8?Q?=C4=A7=C4=AF=C7=AB?= ', - $mock->header - ); - $this->assertContains( - 'Cc: =?UTF-8?Q?=C7=B6=C7=B7?= ', - $mock->header - ); - $this->assertContains( - 'Subject: =?UTF-8?Q?=C7=B1=C7=AE?=', - $mock->header - ); - $this->assertContains( - 'X-MyTest:', - $mock->header - ); - $this->assertNotContains( - "\nCc:foobar2@example.com", - $mock->header - ); - $this->assertContains( - '=?UTF-8?Q?Test-=C7=B1?=', - $mock->header - ); - } - - /** - * @group ZF-7799 - */ - public function testHeaderSendMailTransportHaveNoRightTrim() - { - $mail = new Mail\Mail("UTF-8"); - $mail->setBodyText('My Nice Test Text'); - $mail->addTo("foobar@example.com"); - $mail->setSubject("hello world!"); - - $transportMock = new SendmailTransportMock(); - $mail->send($transportMock); - - $this->assertEquals($transportMock->header, rtrim($transportMock->header)); - } - - /** - * Check if Header Fields are stripped accordingly in sendmail transport; - * also check for header injection - * @todo Determine why this fails in Windows (testmail3@example.com example) - */ - public function testHeaderEncoding2() - { - $mail = new Mail\Mail("UTF-8"); - $mail->setBodyText('My Nice Test Text'); - // try header injection: - $mail->addTo("testmail@example.com\nCc:foobar@example.com"); - $mail->addHeader('X-MyTest', "Test\nCc:foobar2@example.com", true); - // try special Chars in Header Fields: - $mail->setFrom('mymail@example.com', "\xC6\x98\xC6\x90\xC3\xA4\xC4\xB8"); - $mail->addTo('testmail2@example.com', "\xC4\xA7\xC4\xAF\xC7\xAB"); - $mail->addCc('testmail3@example.com', "\xC7\xB6\xC7\xB7"); - $mail->setSubject("\xC7\xB1\xC7\xAE"); - $mail->addHeader('X-MyTest', "Test-\xC7\xB1", true); - - $mock = new SendmailTransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertContains( - 'From: =?UTF-8?Q?=C6=98=C6=90=C3=A4=C4=B8?=', - $mock->header, - "From: Header was encoded unexpectedly." - ); - $this->assertNotContains( - "\nCc:foobar@example.com", - $mock->header, - "Injection into From: header is possible." - ); - // To is done by mail() not in headers - $this->assertNotContains( - 'To: =?UTF-8?Q?=C4=A7=C4=AF=C7=AB?= ', - $mock->header - ); - $this->assertContains( - 'Cc: =?UTF-8?Q?=C7=B6=C7=B7?= ', - $mock->header - ); - // Subject is done by mail() not in headers - $this->assertNotContains( - 'Subject: =?UTF-8?Q?=C7=B1=C7=AE?=', - $mock->header - ); - $this->assertContains( - 'X-MyTest:', - $mock->header - ); - $this->assertNotContains( - "\nCc:foobar2@example.com", - $mock->header - ); - $this->assertContains( - '=?UTF-8?Q?Test-=C7=B1?=', - $mock->header - ); - } - - /** - * Check if Mails with HTML and Text Body are generated correctly. - * - */ - public function testMultipartAlternative() - { - $mail = new Mail\Mail(); - $mail->setBodyText('My Nice Test Text'); - $mail->setBodyHtml('My Nice Test Text'); - $mail->addTo('testmail@example.com', 'Test Recipient'); - $mail->setFrom('mymail@example.com', 'Test Sender'); - $mail->setSubject('Test: Alternate Mail with Zend_Mail'); - - $mock = new TransportMock(); - $mail->send($mock); - - // check headers - $this->assertTrue($mock->called); - $this->assertContains('multipart/alternative', $mock->header); - $boundary = $mock->boundary; - $this->assertContains('boundary="' . $boundary . '"', $mock->header); - $this->assertContains('MIME-Version: 1.0', $mock->header); - - // check body - // search for first boundary - $p1 = strpos($mock->body, "--$boundary\n"); - $this->assertNotNull($p1, $boundary . ': ' . $mock->body); - - // cut out first (Text) part - $start1 = $p1 + 3 + strlen($boundary); - $p2 = strpos($mock->body, "--$boundary\n", $start1); - $this->assertNotNull($p2); - - $partBody1 = substr($mock->body, $start1, ($p2 - $start1)); - $this->assertContains('Content-Type: text/plain', $partBody1); - $this->assertContains('My Nice Test Text', $partBody1); - - // check second (HTML) part - // search for end boundary - $start2 = $p2 + 3 + strlen($boundary); - $p3 = strpos($mock->body, "--$boundary--"); - $this->assertNotNull($p3); - - $partBody2 = substr($mock->body, $start2, ($p3 - $start2)); - $this->assertContains('Content-Type: text/html', $partBody2); - $this->assertContains('My Nice Test Text', $partBody2); - } - - /** - * check if attachment handling works - * - */ - public function testAttachment() - { - $mail = new Mail\Mail(); - $mail->setBodyText('My Nice Test Text'); - $mail->addTo('testmail@example.com', 'Test Recipient'); - $mail->setFrom('mymail@example.com', 'Test Sender'); - $mail->setSubject('Test: Attachment Test with Zend_Mail'); - $at = $mail->createAttachment('abcdefghijklmnopqrstuvexyz'); - $at->type = 'image/gif'; - $at->id = 12; - $at->filename = 'test.gif'; - $mock = new TransportMock(); - $mail->send($mock); - - // now check what was generated by Zend_Mail. - // first the mail headers: - $this->assertContains('Content-Type: multipart/mixed', $mock->header, $mock->header); - $boundary = $mock->boundary; - $this->assertContains('boundary="' . $boundary . '"', $mock->header); - $this->assertContains('MIME-Version: 1.0', $mock->header); - - // check body - // search for first boundary - $p1 = strpos($mock->body, "--$boundary\n"); - $this->assertNotNull($p1); - - // cut out first (Text) part - $start1 = $p1 + 3 + strlen($boundary); - $p2 = strpos($mock->body, "--$boundary\n", $start1); - $this->assertNotNull($p2); - - $partBody1 = substr($mock->body, $start1, ($p2 - $start1)); - $this->assertContains('Content-Type: text/plain', $partBody1); - $this->assertContains('My Nice Test Text', $partBody1); - - // check second (HTML) part - // search for end boundary - $start2 = $p2 + 3 + strlen($boundary); - $p3 = strpos($mock->body, "--$boundary--"); - $this->assertNotNull($p3); - - $partBody2 = substr($mock->body, $start2, ($p3 - $start2)); - $this->assertContains('Content-Type: image/gif', $partBody2); - $this->assertContains('Content-Transfer-Encoding: base64', $partBody2); - $this->assertContains('Content-ID: <12>', $partBody2); - } - - /** - * Check if Mails with HTML and Text Body are generated correctly. - * - */ - public function testMultipartAlternativePlusAttachment() - { - $mail = new Mail\Mail(); - $mail->setBodyText('My Nice Test Text'); - $mail->setBodyHtml('My Nice Test Text'); - $mail->addTo('testmail@example.com', 'Test Recipient'); - $mail->setFrom('mymail@example.com', 'Test Sender'); - $mail->setSubject('Test: Alternate Mail with Zend_Mail'); - - $at = $mail->createAttachment('abcdefghijklmnopqrstuvexyz'); - $at->type = 'image/gif'; - $at->id = 12; - $at->filename = 'test.gif'; - - $mock = new TransportMock(); - $mail->send($mock); - - // check headers - $this->assertTrue($mock->called); - $this->assertContains('multipart/mixed', $mock->header); - $boundary = $mock->boundary; - $this->assertContains('boundary="' . $boundary . '"', $mock->header); - $this->assertContains('MIME-Version: 1.0', $mock->header); - - // check body - // search for first boundary - $p1 = strpos($mock->body, "--$boundary\n"); - $this->assertNotNull($p1); - - // cut out first (multipart/alternative) part - $start1 = $p1 + 3 + strlen($boundary); - $p2 = strpos($mock->body, "--$boundary\n", $start1); - $this->assertNotNull($p2); - - $partBody1 = substr($mock->body, $start1, ($p2 - $start1)); - $this->assertContains('Content-Type: multipart/alternative', $partBody1); - $this->assertContains('Content-Type: text/plain', $partBody1); - $this->assertContains('Content-Type: text/html', $partBody1); - $this->assertContains('My Nice Test Text', $partBody1); - $this->assertContains('My Nice Test Text', $partBody1); - - // check second (image) part - // search for end boundary - $start2 = $p2 + 3 + strlen($boundary); - $p3 = strpos($mock->body, "--$boundary--"); - $this->assertNotNull($p3); - - $partBody2 = substr($mock->body, $start2, ($p3 - $start2)); - $this->assertContains('Content-Type: image/gif', $partBody2); - $this->assertContains('Content-Transfer-Encoding: base64', $partBody2); - $this->assertContains('Content-ID: <12>', $partBody2); - } - - public function testReturnPath() - { - $mail = new Mail\Mail(); - $res = $mail->setBodyText('This is a test.'); - $mail->setFrom('testmail@example.com', 'test Mail User'); - $mail->setSubject('My Subject'); - $mail->addTo('recipient1@example.com'); - $mail->addTo('recipient2@example.com'); - $mail->addBcc('recipient1_bcc@example.com'); - $mail->addBcc('recipient2_bcc@example.com'); - $mail->addCc('recipient1_cc@example.com', 'Example no. 1 for cc'); - $mail->addCc('recipient2_cc@example.com', 'Example no. 2 for cc'); - - // First example: from and return-path should be equal - $mock = new TransportMock(); - $mail->send($mock); - $this->assertTrue($mock->called); - $this->assertEquals($mail->getFrom(), $mock->returnPath); - - // Second example: from and return-path should not be equal - $mail->setReturnPath('sender2@example.com'); - $mock = new TransportMock(); - $mail->send($mock); - $this->assertTrue($mock->called); - $this->assertNotEquals($mail->getFrom(), $mock->returnPath); - $this->assertEquals($mail->getReturnPath(), $mock->returnPath); - $this->assertNotEquals($mock->returnPath, $mock->from); - } - - public function testNoBody() - { - $mail = new Mail\Mail(); - $mail->setFrom('testmail@example.com', 'test Mail User'); - $mail->setSubject('My Subject'); - $mail->addTo('recipient1@example.com'); - - // First example: from and return-path should be equal - $mock = new TransportMock(); - try { - $mail->send($mock); - $this->assertTrue($mock->called); - } catch (\Exception $e) { - // success - $this->assertContains('No body specified', $e->getMessage()); - } - } - - /** - * Helper method for {@link testZf928ToAndBccHeadersShouldNotMix()}; extracts individual header lines - * - * @param \Zend\Mail\AbstractTransport $mock - * @param string $type - * @return string - */ - protected function _getHeader(Mail\AbstractTransport $mock, $type = 'To') - { - $headers = str_replace("\r\n", "\n", $mock->header); - $headers = explode("\n", $mock->header); - $return = ''; - foreach ($headers as $header) { - if (!empty($return)) { - // Check for header continuation - if (!preg_match('/^[a-z-]+:/i', $header)) { - $return .= "\r\n" . $header; - continue; - } else { - break; - } - } - if (preg_match('/^' . $type . ': /', $header)) { - $return = $header; - } - } - - return $return; - } - - public function testZf928ToAndBccHeadersShouldNotMix() - { - $mail = new Mail\Mail(); - $mail->setSubject('my subject'); - $mail->setBodyText('my body'); - $mail->setFrom('info@onlime.ch'); - $mail->addTo('to.address@email.com'); - $mail->addBcc('first.bcc@email.com'); - $mail->addBcc('second.bcc@email.com'); - - // test with generic transport - $mock = new TransportMock(); - $mail->send($mock); - $to = $this->_getHeader($mock); - $bcc = $this->_getHeader($mock, 'Bcc'); - $this->assertContains('to.address@email.com', $to, $to); - $this->assertNotContains('second.bcc@email.com', $to, $bcc); - - // test with sendmail-like transport - $mock = new SendmailTransportMock(); - $mail->send($mock); - $to = $this->_getHeader($mock); - $bcc = $this->_getHeader($mock, 'Bcc'); - // Remove the following line due to fixes by Simon - // $this->assertNotContains('to.address@email.com', $to, $mock->header); - $this->assertNotContains('second.bcc@email.com', $to, $bcc); - } - - public function testZf927BlankLinesShouldPersist() - { - $mail = new Mail\Mail(); - $mail->setSubject('my subject'); - $mail->setBodyText("my body\r\n\r\n...after two newlines"); - $mail->setFrom('test@email.com'); - $mail->addTo('test@email.com'); - - // test with generic transport - $mock = new SendmailTransportMock(); - $mail->send($mock); - $body = quoted_printable_decode($mock->body); - $this->assertContains("\r\n\r\n...after", $body, $body); - } - - public function testGetJustBodyText() - { - $text = "my body\r\n\r\n...after two newlines"; - $mail = new Mail\Mail(); - $mail->setBodyText($text); - - $this->assertContains('my body', $mail->getBodyText(true)); - $this->assertContains('after two newlines', $mail->getBodyText(true)); - } - - public function testGetJustBodyHtml() - { - $text = "

Some body text

"; - $mail = new Mail\Mail(); - $mail->setBodyHtml($text); - - $this->assertContains('Some body text', $mail->getBodyHtml(true)); - } - - public function testTypeAccessor() - { - $mail = new Mail\Mail(); - $this->assertNull($mail->getType()); - - $mail->setType(Mime\Mime::MULTIPART_ALTERNATIVE); - $this->assertEquals(Mime\Mime::MULTIPART_ALTERNATIVE, $mail->getType()); - - $mail->setType(Mime\Mime::MULTIPART_RELATED); - $this->assertEquals(Mime\Mime::MULTIPART_RELATED, $mail->getType()); - - try { - $mail->setType('text/plain'); - $this->fail('Invalid Mime type should throw an exception'); - } catch (\Exception $e) { - } - } - - public function testDateSet() - { - $mail = new Mail\Mail(); - $res = $mail->setBodyText('Date Test'); - $mail->setFrom('testmail@example.com', 'test Mail User'); - $mail->setSubject('Date Test'); - $mail->addTo('recipient@example.com'); - - $mock = new TransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertTrue(isset($mock->headers['Date'])); - $this->assertTrue(isset($mock->headers['Date'][0])); - $this->assertTrue(strlen($mock->headers['Date'][0]) > 0); - } - - public function testSetDateInt() - { - $mail = new Mail\Mail(); - $res = $mail->setBodyText('Date Test'); - $mail->setFrom('testmail@example.com', 'test Mail User'); - $mail->setSubject('Date Test'); - $mail->addTo('recipient@example.com'); - $mail->setDate(362656800); - - $mock = new TransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertTrue(strpos(implode('', $mock->headers['Date']), 'Mon, 29 Jun 1981') === 0); - } - - public function testSetDateString() - { - $mail = new Mail\Mail(); - $res = $mail->setBodyText('Date Test'); - $mail->setFrom('testmail@example.com', 'test Mail User'); - $mail->setSubject('Date Test'); - $mail->addTo('recipient@example.com'); - $mail->setDate('1981-06-29T12:00:00'); - - $mock = new TransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertTrue(strpos(implode('', $mock->headers['Date']), 'Mon, 29 Jun 1981') === 0); - } - - public function testSetDateObject() - { - $mail = new Mail\Mail(); - $res = $mail->setBodyText('Date Test'); - $mail->setFrom('testmail@example.com', 'test Mail User'); - $mail->setSubject('Date Test'); - $mail->addTo('recipient@example.com'); - $mail->setDate(new Date\Date('1981-06-29T12:00:00', Date\Date::ISO_8601)); - - $mock = new TransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertTrue(strpos(implode('', $mock->headers['Date']), 'Mon, 29 Jun 1981') === 0); - } - - public function testSetDateInvalidString() - { - $mail = new Mail\Mail(); - - try { - $mail->setDate('invalid date'); - $this->fail('Invalid date should throw an exception'); - } catch (\Exception $e) { - } - } - - public function testSetDateInvalidType() - { - $mail = new Mail\Mail(); - - try { - $mail->setDate(true); - $this->fail('Invalid date should throw an exception'); - } catch (\Exception $e) { - } - } - - public function testSetDateInvalidObject() - { - $mail = new Mail\Mail(); - - try { - $mail->setDate($mail); - $this->fail('Invalid date should throw an exception'); - } catch (\Exception $e) { - } - } - - public function testSetDateTwice() - { - $mail = new Mail\Mail(); - - $mail->setDate(); - try { - $mail->setDate(123456789); - $this->fail('setting date twice should throw an exception'); - } catch (\Exception $e) { - } - } - - public function testClearDate() - { - $mail = new Mail\Mail(); - - $mail->setDate(); - $mail->clearDate(); - $this->assertFalse(isset($mock->headers['Date'])); - } - - public function testAutoMessageId() - { - $mail = new Mail\Mail(); - $res = $mail->setBodyText('Message ID Test'); - $mail->setFrom('testmail@example.com', 'test Mail User'); - $mail->setSubject('Message ID Test'); - $mail->setMessageId(); - $mail->addTo('recipient@example.com'); - - $mock = new TransportMock(); - $mail->send($mock); - - $this->assertTrue($mock->called); - $this->assertTrue(isset($mock->headers['Message-Id'])); - $this->assertTrue(isset($mock->headers['Message-Id'][0])); - $this->assertTrue(strlen($mock->headers['Message-Id'][0]) > 0); - } - - public function testSetMessageIdTwice() - { - $mail = new Mail\Mail(); - - $mail->setMessageId(); - try { - $mail->setMessageId(); - $this->fail('setting message-id twice should throw an exception'); - } catch (\Exception $e) { - } - } - - public function testClearMessageId() - { - $mail = new Mail\Mail(); - - $mail->setMessageId(); - $mail->clearMessageId(); - $this->assertFalse(isset($mock->headers['Message-Id'])); - } - - /** - * @group ZF-6872 - */ - public function testSetReplyTo() - { - $mail = new Mail\Mail('UTF-8'); - $mail->setReplyTo("foo@zend.com", "\xe2\x82\xa0!"); - $headers = $mail->getHeaders(); - - $this->assertEquals("=?UTF-8?Q?=E2=82=A0!?= ", $headers["Reply-To"][0]); - } - - /** - * @group ZF-1688 - * @group ZF-2559 - */ - public function testSetHeaderEncoding() - { - $mail = new Mail\Mail(); - $this->assertEquals(Mime\Mime::ENCODING_QUOTEDPRINTABLE, $mail->getHeaderEncoding()); - $mail->setHeaderEncoding(Mime\Mime::ENCODING_BASE64); - $this->assertEquals(Mime\Mime::ENCODING_BASE64, $mail->getHeaderEncoding()); - } - - /** - * @group ZF-1688 - * @dataProvider dataSubjects - */ - public function testIfLongSubjectsHaveCorrectLineBreaksAndEncodingMarks($subject) - { - $mail = new Mail\Mail("UTF-8"); - $mail->setSubject($subject); - $headers = $mail->getHeaders(); - $this->assertMailHeaderConformsToRfc($headers['Subject'][0]); - } - - /** - * @group ZF-7702 - */ - public function testReplyToIsNoRecipient() { - $mail = new Mail\Mail(); - $mail->setReplyTo('foo@example.com','foobar'); - $this->assertEquals(0, count($mail->getRecipients())); - } - - public function testGetReplyToReturnsReplyTo() { - $mail = new Mail\Mail(); - $mail->setReplyTo('foo@example.com'); - $this->assertEquals('foo@example.com',$mail->getReplyTo()); - } - - /** - * @expectedException \Zend\Mail\Exception - */ - public function testReplyToCantBeSetTwice() { - $mail = new Mail\Mail(); - $mail->setReplyTo('user@example.com'); - $mail->setReplyTo('user2@example.com'); - } - - public function testDefaultFrom() { - Mail\Mail::setDefaultFrom('john@example.com','John Doe'); - $this->assertEquals(array('email' => 'john@example.com','name' =>'John Doe'), Mail\Mail::getDefaultFrom()); - - Mail\Mail::clearDefaultFrom(); - $this->assertEquals(null, Mail\Mail::getDefaultFrom()); - - Mail\Mail::setDefaultFrom('john@example.com'); - $this->assertEquals(array('email' => 'john@example.com','name' => null), Mail\Mail::getDefaultFrom()); - } - - public function testDefaultReplyTo() { - Mail\Mail::setDefaultReplyTo('john@example.com','John Doe'); - $this->assertEquals(array('email' => 'john@example.com','name' =>'John Doe'), Mail\Mail::getDefaultReplyTo()); - - Mail\Mail::clearDefaultReplyTo(); - $this->assertEquals(null, Mail\Mail::getDefaultReplyTo()); - - Mail\Mail::setDefaultReplyTo('john@example.com'); - $this->assertEquals(array('email' => 'john@example.com','name' => null), Mail\Mail::getDefaultReplyTo()); - } - - public function testSettingFromDefaults() { - Mail\Mail::setDefaultFrom('john@example.com', 'John Doe'); - Mail\Mail::setDefaultReplyTo('foo@example.com','Foo Bar'); - - $mail = new Mail\Mail(); - $headers = $mail->setFromToDefaultFrom() // test fluent interface - ->setReplyToFromDefault() - ->getHeaders(); - - $this->assertEquals('john@example.com', $mail->getFrom()); - $this->assertEquals('foo@example.com', $mail->getReplyTo()); - $this->assertEquals('John Doe ', $headers['From'][0]); - $this->assertEquals('Foo Bar ', $headers['Reply-To'][0]); - } - - public function testMethodSendUsesDefaults() - { - Mail\Mail::setDefaultFrom('john@example.com', 'John Doe'); - Mail\Mail::setDefaultReplyTo('foo@example.com','Foo Bar'); - - $mail = new Mail\Mail(); - $mail->setBodyText('Defaults Test'); - - $mock = new TransportMock(); - $mail->send($mock); - $headers = $mock->headers; - - $this->assertTrue($mock->called); - $this->assertEquals($mock->from, 'john@example.com'); - $this->assertEquals($headers['From'][0], 'John Doe '); - $this->assertEquals($headers['Reply-To'][0], 'Foo Bar '); - } - - /** - * @group ZF-9011 - */ - public function testSendmailTransportShouldAcceptConfigAndArrayAsConstructor() - { - $mail = new Mail\Mail("UTF-8"); - $mail->setBodyText('My Nice Test Text'); - $mail->addTo('foobar@example.com'); - $mail->setSubject('hello world!'); - - $params = array('envelope'=> '-tjohn@example.com', 'foo' => '-fbar'); - $expected = '-tjohn@example.com -fbar'; - - $transportMock = new SendmailTransportMock($params); - $this->assertEquals($expected, $transportMock->parameters); - - $transportMock = new SendmailTransportMock(new \Zend\Config\Config($params)); - $this->assertEquals($expected, $transportMock->parameters); - } - - /** - * @group ZF-9011 - * - */ - public function testSendmailTransportThrowsExceptionWithInvalidParams() - { - $mail = new Mail\Mail("UTF-8"); - $mail->setBodyText('My Nice Test Text'); - $mail->addTo('foobar@example.com'); - $mail->setSubject('hello world!'); - - $transport = new Transport\Sendmail(); - $transport->parameters = true; - try { - $mail->send($transport); - $this->fail('Exception should have been thrown, but wasn\'t'); - } catch(Transport\Exception $e) { - // do nothing - } - } - - public static function dataSubjects() - { - return array( - array("Simple Ascii Subject"), - array("Subject with US Specialchars: &%$/()"), - array("Gimme more \xe2\x82\xa0!"), - array("This is \xc3\xa4n germ\xc3\xa4n multiline s\xc3\xbcbject with rand\xc3\xb6m \xc3\xbcml\xc3\xa4uts."), - array("Alle meine Entchen schwimmen in dem See, schwimmen in dem See, K\xc3\xb6pfchen in das Wasser, Schw\xc3\xa4nzchen in die H\xc3\xb6h!"), - array("\xc3\xa4\xc3\xa4xxxxx\xc3\xa4\xc3\xa4\xc3\xa4\xc3\xa4\xc3\xa4\xc3\xa4\xc3\xa4"), - array("\xd0\x90\xd0\x91\xd0\x92\xd0\x93\xd0\x94\xd0\x95 \xd0\x96\xd0\x97\xd0\x98\xd0\x99 \xd0\x9a\xd0\x9b\xd0\x9c\xd0\x9d"), - array("Ich. Denke. Also. Bin. Ich! (Ein \xc3\xbcml\xc3\xa4\xc3\xbctautomat!)"), - ); - } - - /** - * Assertion that checks if a given mailing header string is RFC conform. - * - * @param string $header - * @return void - */ - protected function assertMailHeaderConformsToRfc($header) - { - $this->numAssertions++; - $parts = explode(Mime\Mime::LINEEND, $header); - if(count($parts) > 0) { - for($i = 0; $i < count($parts); $i++) { - if(preg_match('/(=?[a-z0-9-_]+\?[q|b]{1}\?)/i', $parts[$i], $matches)) { - $dce = sprintf("=?%s", $matches[0]); - // Check that Delimiter, Charset, Encoding are at the front of the string - if(substr(trim($parts[$i]), 0, strlen($dce)) != $dce) { - $this->fail(sprintf( - "Header-Part '%s' in line '%d' has missing or malformated delimiter, charset, encoding information.", - $parts[$i], - $i+1 - )); - } - // check that the encoded word is not too long.); - // this is only some kind of suggestion by the standard, in PHP its hard to hold it, so we do not enforce it here. - /*if(strlen($parts[$i]) > 75) { - $this->fail(sprintf( - "Each encoded-word is only allowed to be 75 chars long, but line %d is %s chars long: %s", - $i+1, - strlen($parts[$i]), - $parts[$i] - )); - }*/ - // Check that the end-delmiter ?= is correctly placed - if(substr(trim($parts[$i]), -2, 2) != "?=") { - $this->fail(sprintf( - "Lines with an encoded-word have to end in ?=, but line %d does not: %s", - $i+1, - substr(trim($parts[$i]), -2, 2) - )); - } - - // Check that only one encoded-word can be found per line. - if(substr_count($parts[$i], "=?") != 1) { - $this->fail(sprintf( - "Only one encoded-word is allowed per line in the header. It seems line %d contains more: %s", - $i+1, - $parts[$i] - )); - } - - // Check that the encoded-text only contains US-ASCII chars, and no space - $encodedText = substr(trim($parts[$i]), strlen($dce), -2); - if(preg_match('/([\s]+)/', $encodedText)) { - $this->fail(sprintf( - "No whitespace characters allowed in encoded-text of line %d: %s", - $i+1, - $parts[$i] - )); - } - for($i = 0; $i < strlen($encodedText); $i++) { - if(ord($encodedText[$i]) > 127) { - $this->fail(sprintf( - "No non US-ASCII characters allowed, but line %d has them: %s", - $i+1, - $parts[$i] - )); - } - } - } else if(Mime\Mime::isPrintable($parts[$i]) == false) { - $this->fail(sprintf( - "Encoded-word in line %d contains non printable characters.", - $i+1 - )); - } - } - } - } - -} - - -/** - * Test helper - */ - -/** - * Mock mail transport class for testing purposes - */ -class TransportMock extends Mail\AbstractTransport -{ - /** - * @var \Zend\Mail\Mail - */ - public $mail = null; - public $returnPath = null; - public $subject = null; - public $from = null; - public $headers = null; - public $called = false; - - public function _sendMail() - { - $this->mail = $this->_mail; - $this->subject = $this->_mail->getSubject(); - $this->from = $this->_mail->getFrom(); - $this->returnPath = $this->_mail->getReturnPath(); - $this->headers = $this->_headers; - $this->called = true; - } -} - -/** - * Mock mail transport class for testing Sendmail transport - */ -class SendmailTransportMock extends Transport\Sendmail -{ - /** - * @var Zend_Mail - */ - public $mail = null; - public $from = null; - public $subject = null; - public $called = false; - - public function _sendMail() - { - $this->mail = $this->_mail; - $this->from = $this->_mail->getFrom(); - $this->subject = $this->_mail->getSubject(); - $this->called = true; - } -} diff --git a/test/MessageTest.php b/test/MessageTest.php index 6ad3f02b..0546a12d 100644 --- a/test/MessageTest.php +++ b/test/MessageTest.php @@ -15,7 +15,7 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ @@ -23,422 +23,644 @@ * @namespace */ namespace ZendTest\Mail; -use Zend\Mail\Message; -use Zend\Mime; -use Zend\Mail\Storage; -use Zend\Mail\Exception; + +use stdClass, + Zend\Mail\Address, + Zend\Mail\AddressList, + Zend\Mail\Header, + Zend\Mail\Message, + Zend\Mime\Message as MimeMessage, + Zend\Mime\Mime, + Zend\Mime\Part as MimePart; /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ class MessageTest extends \PHPUnit_Framework_TestCase { - protected $_file; - public function setUp() { - $this->_file = __DIR__ . '/_files/mail.txt'; + $this->message = new Message(); } - public function testInvalidFile() + public function testInvalidByDefault() { - try { - $message = new Message(array('file' => '/this/file/does/not/exists')); - } catch (\Exception $e) { - return; // ok - } + $this->assertFalse($this->message->isValid()); + } - $this->fail('no exception raised while loading unknown file'); + public function testSetsOrigDateHeaderByDefault() + { + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); + $this->assertTrue($headers->has('date')); + $header = $headers->get('date'); + $date = date('r'); + $date = substr($date, 0, 16); + $test = $header->getFieldValue(); + $test = substr($test, 0, 16); + $this->assertEquals($date, $test); } - public function testIsMultipart() + public function testAddingFromAddressMarksAsValid() { - $message = new Message(array('file' => $this->_file)); + $this->message->addFrom('zf-devteam@zend.com'); + $this->assertTrue($this->message->isValid()); + } - $this->assertTrue($message->isMultipart()); + public function testHeadersMethodReturnsHeadersObject() + { + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); } - public function testGetHeader() + public function testToMethodReturnsAddressListObject() { - $message = new Message(array('file' => $this->_file)); + $this->message->addTo('zf-devteam@zend.com'); + $to = $this->message->to(); + $this->assertInstanceOf('Zend\Mail\AddressList', $to); + } - $this->assertEquals($message->subject, 'multipart'); + public function testToAddressListLivesInHeaders() + { + $this->message->addTo('zf-devteam@zend.com'); + $to = $this->message->to(); + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); + $this->assertTrue($headers->has('to')); + $header = $headers->get('to'); + $this->assertSame($header->getAddressList(), $to); } - public function testGetDecodedHeader() + public function testFromMethodReturnsAddressListObject() { - $message = new Message(array('file' => $this->_file)); + $this->message->addFrom('zf-devteam@zend.com'); + $from = $this->message->from(); + $this->assertInstanceOf('Zend\Mail\AddressList', $from); + } - $this->assertEquals($message->from, iconv('UTF-8', iconv_get_encoding('internal_encoding'), - '"Peter Müller" ')); + public function testFromAddressListLivesInHeaders() + { + $this->message->addFrom('zf-devteam@zend.com'); + $from = $this->message->from(); + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); + $this->assertTrue($headers->has('from')); + $header = $headers->get('from'); + $this->assertSame($header->getAddressList(), $from); } - public function testGetHeaderAsArray() + public function testCcMethodReturnsAddressListObject() { - $message = new Message(array('file' => $this->_file)); + $this->message->addCc('zf-devteam@zend.com'); + $cc = $this->message->cc(); + $this->assertInstanceOf('Zend\Mail\AddressList', $cc); + } - $this->assertEquals($message->getHeader('subject', 'array'), array('multipart')); + public function testCcAddressListLivesInHeaders() + { + $this->message->addCc('zf-devteam@zend.com'); + $cc = $this->message->cc(); + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); + $this->assertTrue($headers->has('cc')); + $header = $headers->get('cc'); + $this->assertSame($header->getAddressList(), $cc); } - public function testGetHeaderFromOpenFile() + public function testBccMethodReturnsAddressListObject() { - $fh = fopen($this->_file, 'r'); - $message = new Message(array('file' => $fh)); + $this->message->addBcc('zf-devteam@zend.com'); + $bcc = $this->message->bcc(); + $this->assertInstanceOf('Zend\Mail\AddressList', $bcc); + } - $this->assertEquals($message->subject, 'multipart'); + public function testBccAddressListLivesInHeaders() + { + $this->message->addBcc('zf-devteam@zend.com'); + $bcc = $this->message->bcc(); + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); + $this->assertTrue($headers->has('bcc')); + $header = $headers->get('bcc'); + $this->assertSame($header->getAddressList(), $bcc); } - public function testGetFirstPart() + public function testReplyToMethodReturnsAddressListObject() { - $message = new Message(array('file' => $this->_file)); + $this->message->addReplyTo('zf-devteam@zend.com'); + $replyTo = $this->message->replyTo(); + $this->assertInstanceOf('Zend\Mail\AddressList', $replyTo); + } - $this->assertEquals(substr($message->getPart(1)->getContent(), 0, 14), 'The first part'); + public function testReplyToAddressListLivesInHeaders() + { + $this->message->addReplyTo('zf-devteam@zend.com'); + $replyTo = $this->message->replyTo(); + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); + $this->assertTrue($headers->has('reply-to')); + $header = $headers->get('reply-to'); + $this->assertSame($header->getAddressList(), $replyTo); } - public function testGetFirstPartTwice() + public function testSenderIsNullByDefault() { - $message = new Message(array('file' => $this->_file)); + $this->assertNull($this->message->getSender()); + } - $message->getPart(1); - $this->assertEquals(substr($message->getPart(1)->getContent(), 0, 14), 'The first part'); + public function testSettingSenderCreatesAddressObject() + { + $this->message->setSender('zf-devteam@zend.com'); + $sender = $this->message->getSender(); + $this->assertInstanceOf('Zend\Mail\Address', $sender); } + public function testCanSpecifyNameWhenSettingSender() + { + $this->message->setSender('zf-devteam@zend.com', 'ZF DevTeam'); + $sender = $this->message->getSender(); + $this->assertInstanceOf('Zend\Mail\Address', $sender); + $this->assertEquals('ZF DevTeam', $sender->getName()); + } - public function testGetWrongPart() + public function testCanProvideAddressObjectWhenSettingSender() { - $message = new Message(array('file' => $this->_file)); + $sender = new Address('zf-devteam@zend.com'); + $this->message->setSender($sender); + $test = $this->message->getSender(); + $this->assertSame($sender, $test); + } - try { - $message->getPart(-1); - } catch (\Exception $e) { - return; // ok - } + public function testSenderAccessorsProxyToSenderHeader() + { + $header = new Header\Sender(); + $this->message->headers()->addHeader($header); + $address = new Address('zf-devteam@zend.com', 'ZF DevTeam'); + $this->message->setSender($address); + $this->assertSame($address, $header->getAddress()); + } - $this->fail('no exception raised while fetching unknown part'); + public function testCanAddFromAddressUsingName() + { + $this->message->addFrom('zf-devteam@zend.com', 'ZF DevTeam'); + $addresses = $this->message->from(); + $this->assertEquals(1, count($addresses)); + $address = $addresses->current(); + $this->assertEquals('zf-devteam@zend.com', $address->getEmail()); + $this->assertEquals('ZF DevTeam', $address->getName()); } - public function testNoHeaderMessage() + public function testCanAddFromAddressUsingAddressObject() { - $message = new Message(array('file' => __FILE__)); + $address = new Address('zf-devteam@zend.com', 'ZF DevTeam'); + $this->message->addFrom($address); - $this->assertEquals(substr($message->getContent(), 0, 5), 'message->from(); + $this->assertEquals(1, count($addresses)); + $test = $addresses->current(); + $this->assertSame($address, $test); + } - $raw = file_get_contents(__FILE__); - $raw = "\t" . $raw; - $message = new Message(array('raw' => $raw)); + public function testCanAddManyFromAddressesUsingArray() + { + $addresses = array( + 'zf-devteam@zend.com', + 'zf-contributors@lists.zend.com' => 'ZF Contributors List', + new Address('fw-announce@lists.zend.com', 'ZF Announce List'), + ); + $this->message->addFrom($addresses); + + $from = $this->message->from(); + $this->assertEquals(3, count($from)); - $this->assertEquals(substr($message->getContent(), 0, 6), "\tassertTrue($from->has('zf-devteam@zend.com')); + $this->assertTrue($from->has('zf-contributors@lists.zend.com')); + $this->assertTrue($from->has('fw-announce@lists.zend.com')); } - public function testMultipleHeader() + public function testCanAddManyFromAddressesUsingAddressListObject() { - $raw = file_get_contents($this->_file); - $raw = "sUBject: test\nSubJect: test2\n" . $raw; - $message = new Message(array('raw' => $raw)); + $list = new AddressList(); + $list->add('zf-devteam@zend.com'); - $this->assertEquals($message->getHeader('subject', 'string'), - 'test' . Mime\Mime::LINEEND . 'test2' . Mime\Mime::LINEEND . 'multipart'); - $this->assertEquals($message->getHeader('subject'), array('test', 'test2', 'multipart')); + $this->message->addFrom('fw-announce@lists.zend.com'); + $this->message->addFrom($list); + $from = $this->message->from(); + $this->assertEquals(2, count($from)); + $this->assertTrue($from->has('fw-announce@lists.zend.com')); + $this->assertTrue($from->has('zf-devteam@zend.com')); } - public function testContentTypeDecode() + public function testCanSetFromListFromAddressList() { - $message = new Message(array('file' => $this->_file)); + $list = new AddressList(); + $list->add('zf-devteam@zend.com'); - $this->assertEquals(Mime\Decode::splitContentType($message->ContentType), - array('type' => 'multipart/alternative', 'boundary' => 'crazy-multipart')); + $this->message->addFrom('fw-announce@lists.zend.com'); + $this->message->setFrom($list); + $from = $this->message->from(); + $this->assertEquals(1, count($from)); + $this->assertFalse($from->has('fw-announce@lists.zend.com')); + $this->assertTrue($from->has('zf-devteam@zend.com')); } - public function testSplitEmptyMessage() + public function testCanAddCcAddressUsingName() { - $this->assertEquals(Mime\Decode::splitMessageStruct('', 'xxx'), null); + $this->message->addCc('zf-devteam@zend.com', 'ZF DevTeam'); + $addresses = $this->message->cc(); + $this->assertEquals(1, count($addresses)); + $address = $addresses->current(); + $this->assertEquals('zf-devteam@zend.com', $address->getEmail()); + $this->assertEquals('ZF DevTeam', $address->getName()); } - public function testSplitInvalidMessage() + public function testCanAddCcAddressUsingAddressObject() { - try { - Mime\Decode::splitMessageStruct("--xxx\n", 'xxx'); - } catch (\Zend\Mime\Exception $e) { - return; // ok - } + $address = new Address('zf-devteam@zend.com', 'ZF DevTeam'); + $this->message->addCc($address); - $this->fail('no exception raised while decoding invalid message'); + $addresses = $this->message->cc(); + $this->assertEquals(1, count($addresses)); + $test = $addresses->current(); + $this->assertSame($address, $test); } - public function testInvalidMailHandler() + public function testCanAddManyCcAddressesUsingArray() { - try { - $message = new Message(array('handler' => 1)); - } catch (Exception\InvalidArgumentException $e) { - return; // ok - } + $addresses = array( + 'zf-devteam@zend.com', + 'zf-contributors@lists.zend.com' => 'ZF Contributors List', + new Address('fw-announce@lists.zend.com', 'ZF Announce List'), + ); + $this->message->addCc($addresses); - $this->fail('no exception raised while using invalid mail handler'); + $cc = $this->message->cc(); + $this->assertEquals(3, count($cc)); + $this->assertTrue($cc->has('zf-devteam@zend.com')); + $this->assertTrue($cc->has('zf-contributors@lists.zend.com')); + $this->assertTrue($cc->has('fw-announce@lists.zend.com')); } - public function testMissingId() + public function testCanAddManyCcAddressesUsingAddressListObject() { - $mail = new Storage\Mbox(array('filename' => __DIR__ . '/_files/test.mbox/INBOX')); - - try { - $message = new Message(array('handler' => $mail)); - } catch (Exception\InvalidArgumentException $e) { - return; // ok - } - - $this->fail('no exception raised while mail handler without id'); + $list = new AddressList(); + $list->add('zf-devteam@zend.com'); + $this->message->addCc('fw-announce@lists.zend.com'); + $this->message->addCc($list); + $cc = $this->message->cc(); + $this->assertEquals(2, count($cc)); + $this->assertTrue($cc->has('fw-announce@lists.zend.com')); + $this->assertTrue($cc->has('zf-devteam@zend.com')); } - public function testIterator() + public function testCanSetCcListFromAddressList() { - $message = new Message(array('file' => $this->_file)); - foreach (new \RecursiveIteratorIterator($message) as $num => $part) { - if ($num == 1) { - // explicit call of __toString() needed for PHP < 5.2 - $this->assertEquals(substr($part->__toString(), 0, 14), 'The first part'); - } - } - $this->assertEquals($part->contentType, 'text/x-vertical'); - } + $list = new AddressList(); + $list->add('zf-devteam@zend.com'); - public function testDecodeString() - { - $is = Mime\Decode::decodeQuotedPrintable('=?UTF-8?Q?"Peter M=C3=BCller"?= '); - $should = iconv('UTF-8', iconv_get_encoding('internal_encoding'), - '"Peter Müller" '); - $this->assertEquals($is, $should); + $this->message->addCc('fw-announce@lists.zend.com'); + $this->message->setCc($list); + $cc = $this->message->cc(); + $this->assertEquals(1, count($cc)); + $this->assertFalse($cc->has('fw-announce@lists.zend.com')); + $this->assertTrue($cc->has('zf-devteam@zend.com')); } - public function testSplitHeader() + public function testCanAddBccAddressUsingName() { - $header = 'foo; x=y; y="x"'; - $this->assertEquals(Mime\Decode::splitHeaderField($header), array('foo', 'x' => 'y', 'y' => 'x')); - $this->assertEquals(Mime\Decode::splitHeaderField($header, 'x'), 'y'); - $this->assertEquals(Mime\Decode::splitHeaderField($header, 'y'), 'x'); - $this->assertEquals(Mime\Decode::splitHeaderField($header, 'foo', 'foo'), 'foo'); - $this->assertEquals(Mime\Decode::splitHeaderField($header, 'foo'), null); + $this->message->addBcc('zf-devteam@zend.com', 'ZF DevTeam'); + $addresses = $this->message->bcc(); + $this->assertEquals(1, count($addresses)); + $address = $addresses->current(); + $this->assertEquals('zf-devteam@zend.com', $address->getEmail()); + $this->assertEquals('ZF DevTeam', $address->getName()); } - public function testSplitInvalidHeader() + public function testCanAddBccAddressUsingAddressObject() { - $header = ''; - try { - Mime\Decode::splitHeaderField($header); - } catch (\Zend\Mime\Exception $e) { - return; // ok - } + $address = new Address('zf-devteam@zend.com', 'ZF DevTeam'); + $this->message->addBcc($address); - $this->fail('no exception raised while decoding invalid header field'); + $addresses = $this->message->bcc(); + $this->assertEquals(1, count($addresses)); + $test = $addresses->current(); + $this->assertSame($address, $test); } - public function testSplitMessage() + public function testCanAddManyBccAddressesUsingArray() { - $header = 'Test: test'; - $body = 'body'; - $newlines = array("\r\n", "\n\r", "\n", "\r"); + $addresses = array( + 'zf-devteam@zend.com', + 'zf-contributors@lists.zend.com' => 'ZF Contributors List', + new Address('fw-announce@lists.zend.com', 'ZF Announce List'), + ); + $this->message->addBcc($addresses); - $decoded_body = null; // "Declare" variable befor first "read" usage to avoid IDEs warning - $decoded_header = null; // "Declare" variable befor first "read" usage to avoid IDEs warning + $bcc = $this->message->bcc(); + $this->assertEquals(3, count($bcc)); - foreach ($newlines as $contentEOL) { - foreach ($newlines as $decodeEOL) { - $content = $header . $contentEOL . $contentEOL . $body; - $decoded = Mime\Decode::splitMessage($content, $decoded_header, $decoded_body, $decodeEOL); - $this->assertEquals(array('test' => 'test'), $decoded_header); - $this->assertEquals($body, $decoded_body); - } - } + $this->assertTrue($bcc->has('zf-devteam@zend.com')); + $this->assertTrue($bcc->has('zf-contributors@lists.zend.com')); + $this->assertTrue($bcc->has('fw-announce@lists.zend.com')); } - public function testToplines() + public function testCanAddManyBccAddressesUsingAddressListObject() { - $message = new Message(array('headers' => file_get_contents($this->_file))); - $this->assertTrue(strpos($message->getToplines(), 'multipart message') === 0); + $list = new AddressList(); + $list->add('zf-devteam@zend.com'); + + $this->message->addBcc('fw-announce@lists.zend.com'); + $this->message->addBcc($list); + $bcc = $this->message->bcc(); + $this->assertEquals(2, count($bcc)); + $this->assertTrue($bcc->has('fw-announce@lists.zend.com')); + $this->assertTrue($bcc->has('zf-devteam@zend.com')); } - public function testNoContent() + public function testCanSetBccListFromAddressList() { - $message = new Message(array('raw' => 'Subject: test')); + $list = new AddressList(); + $list->add('zf-devteam@zend.com'); - try { - $message->getContent(); - } catch (Exception\RuntimeException $e) { - return; // ok - } + $this->message->addBcc('fw-announce@lists.zend.com'); + $this->message->setBcc($list); + $bcc = $this->message->bcc(); + $this->assertEquals(1, count($bcc)); + $this->assertFalse($bcc->has('fw-announce@lists.zend.com')); + $this->assertTrue($bcc->has('zf-devteam@zend.com')); + } - $this->fail('no exception raised while getting content of message without body'); + public function testCanAddReplyToAddressUsingName() + { + $this->message->addReplyTo('zf-devteam@zend.com', 'ZF DevTeam'); + $addresses = $this->message->replyTo(); + $this->assertEquals(1, count($addresses)); + $address = $addresses->current(); + $this->assertEquals('zf-devteam@zend.com', $address->getEmail()); + $this->assertEquals('ZF DevTeam', $address->getName()); } - public function testEmptyHeader() + public function testCanAddReplyToAddressUsingAddressObject() { - $message = new Message(array()); - $this->assertEquals(array(), $message->getHeaders()); + $address = new Address('zf-devteam@zend.com', 'ZF DevTeam'); + $this->message->addReplyTo($address); - $message = new Message(array()); - $subject = null; - try { - $subject = $message->subject; - } catch (Exception\InvalidArgumentException $e) { - // ok - } - if ($subject) { - $this->fail('no exception raised while getting header from empty message'); - } + $addresses = $this->message->replyTo(); + $this->assertEquals(1, count($addresses)); + $test = $addresses->current(); + $this->assertSame($address, $test); } - public function testEmptyBody() + public function testCanAddManyReplyToAddressesUsingArray() { - $message = new Message(array()); - $part = null; - try { - $part = $message->getPart(1); - } catch (Exception\RuntimeException $e) { - // ok - } - if ($part) { - $this->fail('no exception raised while getting part from empty message'); - } + $addresses = array( + 'zf-devteam@zend.com', + 'zf-contributors@lists.zend.com' => 'ZF Contributors List', + new Address('fw-announce@lists.zend.com', 'ZF Announce List'), + ); + $this->message->addReplyTo($addresses); + + $replyTo = $this->message->replyTo(); + $this->assertEquals(3, count($replyTo)); - $message = new Message(array()); - $this->assertTrue($message->countParts() == 0); + $this->assertTrue($replyTo->has('zf-devteam@zend.com')); + $this->assertTrue($replyTo->has('zf-contributors@lists.zend.com')); + $this->assertTrue($replyTo->has('fw-announce@lists.zend.com')); } - /** - * @group ZF-5209 - */ - public function testCheckingHasHeaderFunctionality() + public function testCanAddManyReplyToAddressesUsingAddressListObject() { - $message = new Message(array('headers' => array('subject' => 'foo'))); + $list = new AddressList(); + $list->add('zf-devteam@zend.com'); - $this->assertTrue( $message->headerExists('subject')); - $this->assertTrue( isset($message->subject) ); - $this->assertTrue( $message->headerExists('SuBject')); - $this->assertTrue( isset($message->suBjeCt) ); - $this->assertFalse($message->headerExists('From')); + $this->message->addReplyTo('fw-announce@lists.zend.com'); + $this->message->addReplyTo($list); + $replyTo = $this->message->replyTo(); + $this->assertEquals(2, count($replyTo)); + $this->assertTrue($replyTo->has('fw-announce@lists.zend.com')); + $this->assertTrue($replyTo->has('zf-devteam@zend.com')); } - public function testWrongMultipart() + public function testCanSetReplyToListFromAddressList() { - $message = new Message(array('raw' => "Content-Type: multipart/mixed\r\n\r\ncontent")); + $list = new AddressList(); + $list->add('zf-devteam@zend.com'); - try { - $message->getPart(1); - } catch (Exception\RuntimeException $e) { - return; // ok - } - $this->fail('no exception raised while getting part from message without boundary'); + $this->message->addReplyTo('fw-announce@lists.zend.com'); + $this->message->setReplyTo($list); + $replyTo = $this->message->replyTo(); + $this->assertEquals(1, count($replyTo)); + $this->assertFalse($replyTo->has('fw-announce@lists.zend.com')); + $this->assertTrue($replyTo->has('zf-devteam@zend.com')); } - public function testLateFetch() + public function testSubjectIsEmptyByDefault() { - $mail = new Storage\Mbox(array('filename' => __DIR__ . '/_files/test.mbox/INBOX')); - - $message = new Message(array('handler' => $mail, 'id' => 5)); - $this->assertEquals($message->countParts(), 2); - $this->assertEquals($message->countParts(), 2); + $this->assertNull($this->message->getSubject()); + } - $message = new Message(array('handler' => $mail, 'id' => 5)); - $this->assertEquals($message->subject, 'multipart'); + public function testSubjectIsMutable() + { + $this->message->setSubject('test subject'); + $subject = $this->message->getSubject(); + $this->assertEquals('test subject', $subject); + } - $message = new Message(array('handler' => $mail, 'id' => 5)); - $this->assertTrue(strpos($message->getContent(), 'multipart message') === 0); + public function testSettingSubjectProxiesToHeader() + { + $this->message->setSubject('test subject'); + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); + $this->assertTrue($headers->has('subject')); + $header = $headers->get('subject'); + $this->assertEquals('test subject', $header->getFieldValue()); } - public function testManualIterator() + public function testBodyIsEmptyByDefault() { - $message = new Message(array('file' => $this->_file)); + $this->assertNull($this->message->getBody()); + } - $this->assertTrue($message->valid()); - $this->assertEquals($message->getChildren(), $message->current()); - $this->assertEquals($message->key(), 1); + public function testMaySetBodyFromString() + { + $this->message->setBody('body'); + $this->assertEquals('body', $this->message->getBody()); + } - $message->next(); - $this->assertTrue($message->valid()); - $this->assertEquals($message->getChildren(), $message->current()); - $this->assertEquals($message->key(), 2); + public function testMaySetBodyFromStringSerializableObject() + { + $object = new TestAsset\StringSerializableObject('body'); + $this->message->setBody($object); + $this->assertSame($object, $this->message->getBody()); + $this->assertEquals('body', $this->message->getBodyText()); + } - $message->next(); - $this->assertFalse($message->valid()); + public function testMaySetBodyFromMimeMessage() + { + $body = new MimeMessage(); + $this->message->setBody($body); + $this->assertSame($body, $this->message->getBody()); + } - $message->rewind(); - $this->assertTrue($message->valid()); - $this->assertEquals($message->getChildren(), $message->current()); - $this->assertEquals($message->key(), 1); + public function testMaySetNullBody() + { + $this->message->setBody(null); + $this->assertNull($this->message->getBody()); } - public function testMessageFlagsAreSet() + public static function invalidBodyValues() { - $origFlags = array( - 'foo' => 'bar', - 'baz' => 'bat' + return array( + array(array('foo')), + array(true), + array(false), + array(new stdClass), ); - $message = new Message(array('flags' => $origFlags)); - - $messageFlags = $message->getFlags(); - $this->assertTrue($message->hasFlag('bar'), var_export($messageFlags, 1)); - $this->assertTrue($message->hasFlag('bat'), var_export($messageFlags, 1)); - $this->assertEquals(array('bar' => 'bar', 'bat' => 'bat'), $messageFlags); } - public function testGetHeaderFieldSingle() + /** + * @dataProvider invalidBodyValues + */ + public function testSettingNonScalarNonMimeNonStringSerializableValueForBodyRaisesException($body) { - $message = new Message(array('file' => $this->_file)); - $this->assertEquals($message->getHeaderField('subject'), 'multipart'); + $this->setExpectedException('Zend\Mail\Exception\InvalidArgumentException'); + $this->message->setBody($body); } - public function testGetHeaderFieldDefault() + public function testSettingBodyFromSinglePartMimeMessageSetsAppropriateHeaders() { - $message = new Message(array('file' => $this->_file)); - $this->assertEquals($message->getHeaderField('content-type'), 'multipart/alternative'); + $mime = new Mime('foo-bar'); + $part = new MimePart('foo'); + $part->type = 'text/html'; + $body = new MimeMessage(); + $body->setMime($mime); + $body->addPart($part); + + $this->message->setBody($body); + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); + + $this->assertTrue($headers->has('mime-version')); + $header = $headers->get('mime-version'); + $this->assertEquals('1.0', $header->getFieldValue()); + + $this->assertTrue($headers->has('content-type')); + $header = $headers->get('content-type'); + $this->assertEquals('text/html', $header->getFieldValue()); } - public function testGetHeaderFieldNamed() + public function testSettingBodyFromMultiPartMimeMessageSetsAppropriateHeaders() { - $message = new Message(array('file' => $this->_file)); - $this->assertEquals($message->getHeaderField('content-type', 'boundary'), 'crazy-multipart'); + $mime = new Mime('foo-bar'); + $text = new MimePart('foo'); + $text->type = 'text/plain'; + $html = new MimePart('foo'); + $html->type = 'text/html'; + $body = new MimeMessage(); + $body->setMime($mime); + $body->addPart($text); + $body->addPart($html); + + $this->message->setBody($body); + $headers = $this->message->headers(); + $this->assertInstanceOf('Zend\Mail\Headers', $headers); + + $this->assertTrue($headers->has('mime-version')); + $header = $headers->get('mime-version'); + $this->assertEquals('1.0', $header->getFieldValue()); + + $this->assertTrue($headers->has('content-type')); + $header = $headers->get('content-type'); + $this->assertEquals("multipart/mixed;\r\n boundary=\"foo-bar\"", $header->getFieldValue()); } - public function testGetHeaderFieldMissing() + public function testRetrievingBodyTextFromMessageWithMultiPartMimeBodyReturnsMimeSerialization() { - $message = new Message(array('file' => $this->_file)); - $this->assertNull($message->getHeaderField('content-type', 'foo')); + $mime = new Mime('foo-bar'); + $text = new MimePart('foo'); + $text->type = 'text/plain'; + $html = new MimePart('foo'); + $html->type = 'text/html'; + $body = new MimeMessage(); + $body->setMime($mime); + $body->addPart($text); + $body->addPart($html); + + $this->message->setBody($body); + + $text = $this->message->getBodyText(); + $this->assertEquals($body->generateMessage(), $text); + $this->assertContains('--foo-bar', $text); + $this->assertContains('--foo-bar--', $text); + $this->assertContains('Content-Type: text/plain', $text); + $this->assertContains('Content-Type: text/html', $text); } - public function testGetHeaderFieldInvalid() + public function testEncodingIsAsciiByDefault() { - $message = new Message(array('file' => $this->_file)); - try { - $message->getHeaderField('fake-header-name', 'foo'); - } catch (\Zend\Mail\Exception $e) { - return; - } - $this->fail('No exception thrown while requesting invalid field name'); + $this->assertEquals('ASCII', $this->message->getEncoding()); } - public function testCaseInsensitiveMultipart() + public function testEncodingIsMutable() { - $message = new Message(array('raw' => "coNTent-TYpe: muLTIpaRT/x-empty\r\n\r\n")); - $this->assertTrue($message->isMultipart()); + $this->message->setEncoding('UTF-8'); + $this->assertEquals('UTF-8', $this->message->getEncoding()); } - public function testCaseInsensitiveField() + public function testSettingNonAsciiEncodingForcesMimeEncodingOfSomeHeaders() { - $header = 'test; fOO="this is a test"'; - $this->assertEquals(Mime\Decode::splitHeaderField($header, 'Foo'), 'this is a test'); - $this->assertEquals(Mime\Decode::splitHeaderField($header, 'bar'), null); + if (!function_exists('iconv_mime_encode')) { + $this->markTestSkipped('Encoding relies on iconv extension'); + } + + $this->message->addTo('zf-devteam@zend.com', 'ZF DevTeam'); + $this->message->addFrom('matthew@zend.com', "Matthew Weier O'Phinney"); + $this->message->addCc('zf-contributors@lists.zend.com', 'ZF Contributors List'); + $this->message->addBcc('zf-crteam@lists.zend.com', 'ZF CR Team'); + $this->message->setSubject('This is a subject'); + $this->message->setEncoding('UTF-8'); + + $test = $this->message->headers()->toString(); + + $expected = $this->encodeString('ZF DevTeam', 'UTF-8'); + $this->assertContains($expected, $test); + $this->assertContains('', $test); + + $expected = $this->encodeString("Matthew Weier O'Phinney", 'UTF-8'); + $this->assertContains($expected, $test, $test); + $this->assertContains('', $test); + + $expected = $this->encodeString("ZF Contributors List", 'UTF-8'); + $this->assertContains($expected, $test); + $this->assertContains('', $test); + + $expected = $this->encodeString("ZF CR Team", 'UTF-8'); + $this->assertContains($expected, $test); + $this->assertContains('', $test); + + $self = $this; + $words = array_map(function($word) use ($self) { + return $self->encodeString($word, 'UTF-8'); + }, explode(' ', 'This is a subject')); + $expected = 'Subject: ' . implode("\r\n ", $words); + $this->assertContains($expected, $test, $test); } - public function testSpaceInFieldName() + public function encodeString($string, $charset) { - $header = 'test; foo =bar; baz =42'; - $this->assertEquals(Mime\Decode::splitHeaderField($header, 'foo'), 'bar'); - $this->assertEquals(Mime\Decode::splitHeaderField($header, 'baz'), 42); + $encoded = iconv_mime_encode('Header', $string, array( + 'scheme' => 'Q', + 'output-charset' => $charset, + 'line-length' => 998, + )); + $encoded = str_replace('Header: ', '', $encoded); + return $encoded; } } diff --git a/test/SmtpTest.php b/test/SmtpTest.php deleted file mode 100644 index 47e95ff9..00000000 --- a/test/SmtpTest.php +++ /dev/null @@ -1,70 +0,0 @@ -_params = array('host' => TESTS_ZEND_MAIL_SMTP_HOST, - 'port' => TESTS_ZEND_MAIL_SMTP_PORT, - 'username' => TESTS_ZEND_MAIL_SMTP_USER, - 'password' => TESTS_ZEND_MAIL_SMTP_PASSWORD, - 'auth' => TESTS_ZEND_MAIL_SMTP_AUTH); - } - - public function testTransportSetup() - { - try { - $this->_transport = new \Zend\Mail\Transport\Smtp($this->_params['host'], $this->_params); - } catch (\Exception $e) { - $this->fail('exception raised while creating smtp transport'); - } - - try { - $this->_connection = new \Zend\Mail\Protocol\Smtp($this->_params['host'], $this->_params['port']); - $this->_transport->setConnection($this->_connection); - } catch (\Exception $e) { - $this->fail('exception raised while setting smtp transport connection'); - } - - $this->_connection = $this->_transport->getConnection(); - if (!($this->_connection instanceof \Zend\Mail\AbstractProtocol)) { - $this->fail('smtp transport connection is not an instance of protocol abstract'); - } - } -} diff --git a/test/ImapTest.php b/test/Storage/ImapTest.php similarity index 98% rename from test/ImapTest.php rename to test/Storage/ImapTest.php index 878ac9ab..5c0c3ab1 100644 --- a/test/ImapTest.php +++ b/test/Storage/ImapTest.php @@ -15,22 +15,23 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; -use Zend\Mail\Storage; -use Zend\Mail\Protocol; +namespace ZendTest\Mail\Storage; + +use Zend\Mail\Protocol, + Zend\Mail\Storage; /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -56,7 +57,7 @@ public function setUp() } $this->_cleanDir(TESTS_ZEND_MAIL_SERVER_TESTDIR); - $this->_copyDir(__DIR__ . '/_files/test.' . TESTS_ZEND_MAIL_SERVER_FORMAT, + $this->_copyDir(__DIR__ . '/../_files/test.' . TESTS_ZEND_MAIL_SERVER_FORMAT, TESTS_ZEND_MAIL_SERVER_TESTDIR); } } diff --git a/test/MaildirFolderTest.php b/test/Storage/MaildirFolderTest.php similarity index 97% rename from test/MaildirFolderTest.php rename to test/Storage/MaildirFolderTest.php index 1f3307ff..96e955a2 100644 --- a/test/MaildirFolderTest.php +++ b/test/Storage/MaildirFolderTest.php @@ -15,14 +15,15 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; +namespace ZendTest\Mail\Storage; + use Zend\Mail\Storage\Folder; @@ -30,7 +31,7 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -43,7 +44,7 @@ class MaildirFolderTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->_originalDir = __DIR__ . '/_files/test.maildir/'; + $this->_originalDir = __DIR__ . '/../_files/test.maildir/'; if (!is_dir($this->_originalDir . '/cur/')) { $this->markTestSkipped('You have to unpack maildir.tar in Zend/Mail/_files/test.maildir/ ' @@ -55,7 +56,7 @@ public function setUp() if (TESTS_ZEND_MAIL_TEMPDIR != null) { $this->_tmpdir = TESTS_ZEND_MAIL_TEMPDIR; } else { - $this->_tmpdir = __DIR__ . '/_files/test.tmp/'; + $this->_tmpdir = __DIR__ . '/../_files/test.tmp/'; } if (!file_exists($this->_tmpdir)) { mkdir($this->_tmpdir); diff --git a/test/MaildirMessageOldTest.php b/test/Storage/MaildirMessageOldTest.php similarity index 92% rename from test/MaildirMessageOldTest.php rename to test/Storage/MaildirMessageOldTest.php index dd112143..a6a65fbc 100644 --- a/test/MaildirMessageOldTest.php +++ b/test/Storage/MaildirMessageOldTest.php @@ -15,14 +15,15 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; +namespace ZendTest\Mail\Storage; + use Zend\Mail\Storage; /** @@ -31,7 +32,7 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class MaildirOldMessage extends Storage\Maildir @@ -40,14 +41,14 @@ class MaildirOldMessage extends Storage\Maildir * used message class * @var string */ - protected $_messageClass = '\Zend\Mail\Message'; + protected $_messageClass = 'Zend\Mail\Storage\Message'; } /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -59,7 +60,7 @@ class MaildirMessageOldTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->_originalMaildir = __DIR__ . '/_files/test.maildir/'; + $this->_originalMaildir = __DIR__ . '/../_files/test.maildir/'; if (!is_dir($this->_originalMaildir . '/cur/')) { $this->markTestSkipped('You have to unpack maildir.tar in Zend/Mail/_files/test.maildir/ ' . 'directory before enabling the maildir tests'); @@ -70,7 +71,7 @@ public function setUp() if (TESTS_ZEND_MAIL_TEMPDIR != null) { $this->_tmpdir = TESTS_ZEND_MAIL_TEMPDIR; } else { - $this->_tmpdir = __DIR__ . '/_files/test.tmp/'; + $this->_tmpdir = __DIR__ . '/../_files/test.tmp/'; } if (!file_exists($this->_tmpdir)) { mkdir($this->_tmpdir); diff --git a/test/MaildirTest.php b/test/Storage/MaildirTest.php similarity index 97% rename from test/MaildirTest.php rename to test/Storage/MaildirTest.php index c8ed3cce..136c6056 100644 --- a/test/MaildirTest.php +++ b/test/Storage/MaildirTest.php @@ -15,22 +15,22 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; -use Zend\Mail\Storage; +namespace ZendTest\Mail\Storage; +use Zend\Mail\Storage; /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -42,7 +42,7 @@ class MaildirTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->_originalMaildir = __DIR__ . '/_files/test.maildir/'; + $this->_originalMaildir = __DIR__ . '/../_files/test.maildir/'; if (!is_dir($this->_originalMaildir . '/cur/')) { $this->markTestSkipped('You have to unpack maildir.tar in Zend/Mail/_files/test.maildir/ ' . 'directory before enabling the maildir tests'); @@ -53,7 +53,7 @@ public function setUp() if (TESTS_ZEND_MAIL_TEMPDIR != null) { $this->_tmpdir = TESTS_ZEND_MAIL_TEMPDIR; } else { - $this->_tmpdir = __DIR__ . '/_files/test.tmp/'; + $this->_tmpdir = __DIR__ . '/../_files/test.tmp/'; } if (!file_exists($this->_tmpdir)) { mkdir($this->_tmpdir); diff --git a/test/MaildirWritableTest.php b/test/Storage/MaildirWritableTest.php similarity index 98% rename from test/MaildirWritableTest.php rename to test/Storage/MaildirWritableTest.php index 447fc0b8..b98b2fc1 100644 --- a/test/MaildirWritableTest.php +++ b/test/Storage/MaildirWritableTest.php @@ -15,24 +15,25 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; -use Zend\Mail\Storage\Writable; -use Zend\Mail\Storage; -use Zend\Mail; +namespace ZendTest\Mail\Storage; + +use Zend\Mail, + Zend\Mail\Storage, + Zend\Mail\Storage\Writable; /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -45,7 +46,7 @@ class MaildirWritableTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->_originalDir = __DIR__ . '/_files/test.maildir/'; + $this->_originalDir = __DIR__ . '/../_files/test.maildir/'; if (!is_dir($this->_originalDir . '/cur/')) { $this->markTestSkipped('You have to unpack maildir.tar in Zend/Mail/_files/test.maildir/ ' @@ -57,7 +58,7 @@ public function setUp() if (TESTS_ZEND_MAIL_TEMPDIR != null) { $this->_tmpdir = TESTS_ZEND_MAIL_TEMPDIR; } else { - $this->_tmpdir = __DIR__ . '/_files/test.tmp/'; + $this->_tmpdir = __DIR__ . '/../_files/test.tmp/'; } if (!file_exists($this->_tmpdir)) { mkdir($this->_tmpdir); diff --git a/test/MboxFolderTest.php b/test/Storage/MboxFolderTest.php similarity index 97% rename from test/MboxFolderTest.php rename to test/Storage/MboxFolderTest.php index 11a53519..ffced0d6 100644 --- a/test/MboxFolderTest.php +++ b/test/Storage/MboxFolderTest.php @@ -15,22 +15,22 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; -use Zend\Mail\Storage\Folder; +namespace ZendTest\Mail\Storage; +use Zend\Mail\Storage\Folder; /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -43,13 +43,13 @@ class MboxFolderTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->_originalDir = __DIR__ . '/_files/test.mbox/'; + $this->_originalDir = __DIR__ . '/../_files/test.mbox/'; if ($this->_tmpdir == null) { if (TESTS_ZEND_MAIL_TEMPDIR != null) { $this->_tmpdir = TESTS_ZEND_MAIL_TEMPDIR; } else { - $this->_tmpdir = __DIR__ . '/_files/test.tmp/'; + $this->_tmpdir = __DIR__ . '/../_files/test.tmp/'; } if (!file_exists($this->_tmpdir)) { mkdir($this->_tmpdir); diff --git a/test/InterfaceTest.php b/test/Storage/MboxInterfaceTest.php similarity index 95% rename from test/InterfaceTest.php rename to test/Storage/MboxInterfaceTest.php index 666e93ec..f5c82782 100644 --- a/test/InterfaceTest.php +++ b/test/Storage/MboxInterfaceTest.php @@ -15,22 +15,22 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; -use Zend\Mail\Storage; +namespace ZendTest\Mail\Storage; +use Zend\Mail\Storage; /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -40,7 +40,7 @@ class InterfaceTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->_mboxFile = __DIR__ . '/_files/test.mbox/INBOX'; + $this->_mboxFile = __DIR__ . '/../_files/test.mbox/INBOX'; } public function testCount() diff --git a/test/MboxMessageOldTest.php b/test/Storage/MboxMessageOldTest.php similarity index 91% rename from test/MboxMessageOldTest.php rename to test/Storage/MboxMessageOldTest.php index e7e0587d..9b365a26 100644 --- a/test/MboxMessageOldTest.php +++ b/test/Storage/MboxMessageOldTest.php @@ -15,14 +15,15 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; +namespace ZendTest\Mail\Storage; + use Zend\Mail\Storage; @@ -35,14 +36,14 @@ class MboxOldMessage extends Storage\Mbox * used message class * @var string */ - protected $_messageClass = '\Zend\Mail\Message'; + protected $_messageClass = 'Zend\Mail\Storage\Message'; } /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -58,7 +59,7 @@ public function setUp() if (TESTS_ZEND_MAIL_TEMPDIR != null) { $this->_tmpdir = TESTS_ZEND_MAIL_TEMPDIR; } else { - $this->_tmpdir = __DIR__ . '/_files/test.tmp/'; + $this->_tmpdir = __DIR__ . '/../_files/test.tmp/'; } if (!file_exists($this->_tmpdir)) { mkdir($this->_tmpdir); @@ -75,7 +76,7 @@ public function setUp() } } - $this->_mboxOriginalFile = __DIR__ . '/_files/test.mbox/INBOX'; + $this->_mboxOriginalFile = __DIR__ . '/../_files/test.mbox/INBOX'; $this->_mboxFile = $this->_tmpdir . 'INBOX'; copy($this->_mboxOriginalFile, $this->_mboxFile); diff --git a/test/MboxTest.php b/test/Storage/MboxTest.php similarity index 97% rename from test/MboxTest.php rename to test/Storage/MboxTest.php index 3f24bacd..5ddc8217 100644 --- a/test/MboxTest.php +++ b/test/Storage/MboxTest.php @@ -15,21 +15,22 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; +namespace ZendTest\Mail\Storage; + use Zend\Mail\Storage; /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -45,7 +46,7 @@ public function setUp() if (TESTS_ZEND_MAIL_TEMPDIR != null) { $this->_tmpdir = TESTS_ZEND_MAIL_TEMPDIR; } else { - $this->_tmpdir = __DIR__ . '/_files/test.tmp/'; + $this->_tmpdir = __DIR__ . '/../_files/test.tmp/'; } if (!file_exists($this->_tmpdir)) { mkdir($this->_tmpdir); @@ -62,7 +63,7 @@ public function setUp() } } - $this->_mboxOriginalFile = __DIR__ . '/_files/test.mbox/INBOX'; + $this->_mboxOriginalFile = __DIR__ . '/../_files/test.mbox/INBOX'; $this->_mboxFile = $this->_tmpdir . 'INBOX'; copy($this->_mboxOriginalFile, $this->_mboxFile); diff --git a/test/Storage/MessageTest.php b/test/Storage/MessageTest.php new file mode 100644 index 00000000..5627a83e --- /dev/null +++ b/test/Storage/MessageTest.php @@ -0,0 +1,445 @@ +_file = __DIR__ . '/../_files/mail.txt'; + } + + public function testInvalidFile() + { + try { + $message = new Message(array('file' => '/this/file/does/not/exists')); + } catch (\Exception $e) { + return; // ok + } + + $this->fail('no exception raised while loading unknown file'); + } + + public function testIsMultipart() + { + $message = new Message(array('file' => $this->_file)); + + $this->assertTrue($message->isMultipart()); + } + + public function testGetHeader() + { + $message = new Message(array('file' => $this->_file)); + + $this->assertEquals($message->subject, 'multipart'); + } + + public function testGetDecodedHeader() + { + $message = new Message(array('file' => $this->_file)); + + $this->assertEquals($message->from, iconv('UTF-8', iconv_get_encoding('internal_encoding'), + '"Peter Müller" ')); + } + + public function testGetHeaderAsArray() + { + $message = new Message(array('file' => $this->_file)); + + $this->assertEquals($message->getHeader('subject', 'array'), array('multipart')); + } + + public function testGetHeaderFromOpenFile() + { + $fh = fopen($this->_file, 'r'); + $message = new Message(array('file' => $fh)); + + $this->assertEquals($message->subject, 'multipart'); + } + + public function testGetFirstPart() + { + $message = new Message(array('file' => $this->_file)); + + $this->assertEquals(substr($message->getPart(1)->getContent(), 0, 14), 'The first part'); + } + + public function testGetFirstPartTwice() + { + $message = new Message(array('file' => $this->_file)); + + $message->getPart(1); + $this->assertEquals(substr($message->getPart(1)->getContent(), 0, 14), 'The first part'); + } + + + public function testGetWrongPart() + { + $message = new Message(array('file' => $this->_file)); + + try { + $message->getPart(-1); + } catch (\Exception $e) { + return; // ok + } + + $this->fail('no exception raised while fetching unknown part'); + } + + public function testNoHeaderMessage() + { + $message = new Message(array('file' => __FILE__)); + + $this->assertEquals(substr($message->getContent(), 0, 5), ' $raw)); + + $this->assertEquals(substr($message->getContent(), 0, 6), "\t_file); + $raw = "sUBject: test\nSubJect: test2\n" . $raw; + $message = new Message(array('raw' => $raw)); + + $this->assertEquals($message->getHeader('subject', 'string'), + 'test' . Mime\Mime::LINEEND . 'test2' . Mime\Mime::LINEEND . 'multipart'); + $this->assertEquals($message->getHeader('subject'), array('test', 'test2', 'multipart')); + } + + public function testContentTypeDecode() + { + $message = new Message(array('file' => $this->_file)); + + $this->assertEquals(Mime\Decode::splitContentType($message->ContentType), + array('type' => 'multipart/alternative', 'boundary' => 'crazy-multipart')); + } + + public function testSplitEmptyMessage() + { + $this->assertEquals(Mime\Decode::splitMessageStruct('', 'xxx'), null); + } + + public function testSplitInvalidMessage() + { + try { + Mime\Decode::splitMessageStruct("--xxx\n", 'xxx'); + } catch (\Zend\Mime\Exception $e) { + return; // ok + } + + $this->fail('no exception raised while decoding invalid message'); + } + + public function testInvalidMailHandler() + { + try { + $message = new Message(array('handler' => 1)); + } catch (Exception\InvalidArgumentException $e) { + return; // ok + } + + $this->fail('no exception raised while using invalid mail handler'); + + } + + public function testMissingId() + { + $mail = new Storage\Mbox(array('filename' => __DIR__ . '/../_files/test.mbox/INBOX')); + + try { + $message = new Message(array('handler' => $mail)); + } catch (Exception\InvalidArgumentException $e) { + return; // ok + } + + $this->fail('no exception raised while mail handler without id'); + + } + + public function testIterator() + { + $message = new Message(array('file' => $this->_file)); + foreach (new \RecursiveIteratorIterator($message) as $num => $part) { + if ($num == 1) { + // explicit call of __toString() needed for PHP < 5.2 + $this->assertEquals(substr($part->__toString(), 0, 14), 'The first part'); + } + } + $this->assertEquals($part->contentType, 'text/x-vertical'); + } + + public function testDecodeString() + { + $is = Mime\Decode::decodeQuotedPrintable('=?UTF-8?Q?"Peter M=C3=BCller"?= '); + $should = iconv('UTF-8', iconv_get_encoding('internal_encoding'), + '"Peter Müller" '); + $this->assertEquals($is, $should); + } + + public function testSplitHeader() + { + $header = 'foo; x=y; y="x"'; + $this->assertEquals(Mime\Decode::splitHeaderField($header), array('foo', 'x' => 'y', 'y' => 'x')); + $this->assertEquals(Mime\Decode::splitHeaderField($header, 'x'), 'y'); + $this->assertEquals(Mime\Decode::splitHeaderField($header, 'y'), 'x'); + $this->assertEquals(Mime\Decode::splitHeaderField($header, 'foo', 'foo'), 'foo'); + $this->assertEquals(Mime\Decode::splitHeaderField($header, 'foo'), null); + } + + public function testSplitInvalidHeader() + { + $header = ''; + try { + Mime\Decode::splitHeaderField($header); + } catch (\Zend\Mime\Exception $e) { + return; // ok + } + + $this->fail('no exception raised while decoding invalid header field'); + } + + public function testSplitMessage() + { + $header = 'Test: test'; + $body = 'body'; + $newlines = array("\r\n", "\n\r", "\n", "\r"); + + $decoded_body = null; // "Declare" variable befor first "read" usage to avoid IDEs warning + $decoded_header = null; // "Declare" variable befor first "read" usage to avoid IDEs warning + + foreach ($newlines as $contentEOL) { + foreach ($newlines as $decodeEOL) { + $content = $header . $contentEOL . $contentEOL . $body; + $decoded = Mime\Decode::splitMessage($content, $decoded_header, $decoded_body, $decodeEOL); + $this->assertEquals(array('test' => 'test'), $decoded_header); + $this->assertEquals($body, $decoded_body); + } + } + } + + public function testToplines() + { + $message = new Message(array('headers' => file_get_contents($this->_file))); + $this->assertTrue(strpos($message->getToplines(), 'multipart message') === 0); + } + + public function testNoContent() + { + $message = new Message(array('raw' => 'Subject: test')); + + try { + $message->getContent(); + } catch (Exception\RuntimeException $e) { + return; // ok + } + + $this->fail('no exception raised while getting content of message without body'); + } + + public function testEmptyHeader() + { + $message = new Message(array()); + $this->assertEquals(array(), $message->getHeaders()); + + $message = new Message(array()); + $subject = null; + try { + $subject = $message->subject; + } catch (Exception\InvalidArgumentException $e) { + // ok + } + if ($subject) { + $this->fail('no exception raised while getting header from empty message'); + } + } + + public function testEmptyBody() + { + $message = new Message(array()); + $part = null; + try { + $part = $message->getPart(1); + } catch (Exception\RuntimeException $e) { + // ok + } + if ($part) { + $this->fail('no exception raised while getting part from empty message'); + } + + $message = new Message(array()); + $this->assertTrue($message->countParts() == 0); + } + + /** + * @group ZF-5209 + */ + public function testCheckingHasHeaderFunctionality() + { + $message = new Message(array('headers' => array('subject' => 'foo'))); + + $this->assertTrue( $message->headerExists('subject')); + $this->assertTrue( isset($message->subject) ); + $this->assertTrue( $message->headerExists('SuBject')); + $this->assertTrue( isset($message->suBjeCt) ); + $this->assertFalse($message->headerExists('From')); + } + + public function testWrongMultipart() + { + $message = new Message(array('raw' => "Content-Type: multipart/mixed\r\n\r\ncontent")); + + try { + $message->getPart(1); + } catch (Exception\RuntimeException $e) { + return; // ok + } + $this->fail('no exception raised while getting part from message without boundary'); + } + + public function testLateFetch() + { + $mail = new Storage\Mbox(array('filename' => __DIR__ . '/../_files/test.mbox/INBOX')); + + $message = new Message(array('handler' => $mail, 'id' => 5)); + $this->assertEquals($message->countParts(), 2); + $this->assertEquals($message->countParts(), 2); + + $message = new Message(array('handler' => $mail, 'id' => 5)); + $this->assertEquals($message->subject, 'multipart'); + + $message = new Message(array('handler' => $mail, 'id' => 5)); + $this->assertTrue(strpos($message->getContent(), 'multipart message') === 0); + } + + public function testManualIterator() + { + $message = new Message(array('file' => $this->_file)); + + $this->assertTrue($message->valid()); + $this->assertEquals($message->getChildren(), $message->current()); + $this->assertEquals($message->key(), 1); + + $message->next(); + $this->assertTrue($message->valid()); + $this->assertEquals($message->getChildren(), $message->current()); + $this->assertEquals($message->key(), 2); + + $message->next(); + $this->assertFalse($message->valid()); + + $message->rewind(); + $this->assertTrue($message->valid()); + $this->assertEquals($message->getChildren(), $message->current()); + $this->assertEquals($message->key(), 1); + } + + public function testMessageFlagsAreSet() + { + $origFlags = array( + 'foo' => 'bar', + 'baz' => 'bat' + ); + $message = new Message(array('flags' => $origFlags)); + + $messageFlags = $message->getFlags(); + $this->assertTrue($message->hasFlag('bar'), var_export($messageFlags, 1)); + $this->assertTrue($message->hasFlag('bat'), var_export($messageFlags, 1)); + $this->assertEquals(array('bar' => 'bar', 'bat' => 'bat'), $messageFlags); + } + + public function testGetHeaderFieldSingle() + { + $message = new Message(array('file' => $this->_file)); + $this->assertEquals($message->getHeaderField('subject'), 'multipart'); + } + + public function testGetHeaderFieldDefault() + { + $message = new Message(array('file' => $this->_file)); + $this->assertEquals($message->getHeaderField('content-type'), 'multipart/alternative'); + } + + public function testGetHeaderFieldNamed() + { + $message = new Message(array('file' => $this->_file)); + $this->assertEquals($message->getHeaderField('content-type', 'boundary'), 'crazy-multipart'); + } + + public function testGetHeaderFieldMissing() + { + $message = new Message(array('file' => $this->_file)); + $this->assertNull($message->getHeaderField('content-type', 'foo')); + } + + public function testGetHeaderFieldInvalid() + { + $message = new Message(array('file' => $this->_file)); + try { + $message->getHeaderField('fake-header-name', 'foo'); + } catch (\Zend\Mail\Exception $e) { + return; + } + $this->fail('No exception thrown while requesting invalid field name'); + } + + public function testCaseInsensitiveMultipart() + { + $message = new Message(array('raw' => "coNTent-TYpe: muLTIpaRT/x-empty\r\n\r\n")); + $this->assertTrue($message->isMultipart()); + } + + public function testCaseInsensitiveField() + { + $header = 'test; fOO="this is a test"'; + $this->assertEquals(Mime\Decode::splitHeaderField($header, 'Foo'), 'this is a test'); + $this->assertEquals(Mime\Decode::splitHeaderField($header, 'bar'), null); + } + + public function testSpaceInFieldName() + { + $header = 'test; foo =bar; baz =42'; + $this->assertEquals(Mime\Decode::splitHeaderField($header, 'foo'), 'bar'); + $this->assertEquals(Mime\Decode::splitHeaderField($header, 'baz'), 42); + } +} diff --git a/test/Pop3Test.php b/test/Storage/Pop3Test.php similarity index 97% rename from test/Pop3Test.php rename to test/Storage/Pop3Test.php index 8f0dc6f8..57dc6793 100644 --- a/test/Pop3Test.php +++ b/test/Storage/Pop3Test.php @@ -15,22 +15,23 @@ * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ /** * @namespace */ -namespace ZendTest\Mail; -use Zend\Mail\Storage; -use Zend\Mail\Protocol; +namespace ZendTest\Mail\Storage; + +use Zend\Mail\Protocol, + Zend\Mail\Storage; /** * @category Zend * @package Zend_Mail * @subpackage UnitTests - * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @group Zend_Mail */ @@ -58,7 +59,7 @@ public function setUp() } $this->_cleanDir(TESTS_ZEND_MAIL_SERVER_TESTDIR); - $this->_copyDir(__DIR__ . '/_files/test.' . TESTS_ZEND_MAIL_SERVER_FORMAT, + $this->_copyDir(__DIR__ . '/../_files/test.' . TESTS_ZEND_MAIL_SERVER_FORMAT, TESTS_ZEND_MAIL_SERVER_TESTDIR); } } diff --git a/test/TestAsset/SmtpProtocolSpy.php b/test/TestAsset/SmtpProtocolSpy.php new file mode 100644 index 00000000..92d2cb89 --- /dev/null +++ b/test/TestAsset/SmtpProtocolSpy.php @@ -0,0 +1,187 @@ +connect = true; + } + + /** + * Set server name we're talking to + * + * @param string $serverName + * @return void + */ + public function helo($serverName = '127.0.0.1') + { + $this->helo = $serverName; + } + + /** + * quit implementation + * + * Resets helo value and calls rset + * + * @return void + */ + public function quit() + { + $this->helo = null; + $this->rset(); + } + + /** + * Disconnect implementation + * + * Resets connect flag and calls rset + * + * @return void + */ + public function disconnect() + { + $this->helo = null; + $this->connect = false; + $this->rset(); + } + + /** + * "Reset" connection + * + * Resets state of mail, rcpt, and data properties + * + * @return void + */ + public function rset() + { + $this->mail = null; + $this->rcpt = array(); + $this->data = null; + } + + /** + * Set envelope FROM + * + * @param string $from + * @return void + */ + public function mail($from) + { + $this->mail = $from; + } + + /** + * Add recipient + * + * @param string $to + * @return void + */ + public function rcpt($to) + { + $this->rcpt[] = $to; + } + + /** + * Set data + * + * @param string $data + * @return void + */ + public function data($data) + { + $this->data = $data; + } + + /** + * Are we connected? + * + * @return bool + */ + public function isConnected() + { + return $this->connect; + } + + /** + * Get server name we opened a connection with + * + * @return null|string + */ + public function getHelo() + { + return $this->helo; + } + + /** + * Get value of mail property + * + * @return null|string + */ + public function getMail() + { + return $this->mail; + } + + /** + * Get recipients + * + * @return array + */ + public function getRecipients() + { + return $this->rcpt; + } + + /** + * Get data value + * + * @return null|string + */ + public function getData() + { + return $this->data; + } +} diff --git a/test/TestAsset/StringSerializableObject.php b/test/TestAsset/StringSerializableObject.php new file mode 100644 index 00000000..532d2002 --- /dev/null +++ b/test/TestAsset/StringSerializableObject.php @@ -0,0 +1,16 @@ +message = $message; + } + + public function __toString() + { + return $this->message; + } +} diff --git a/test/Transport/FileOptionsTest.php b/test/Transport/FileOptionsTest.php new file mode 100644 index 00000000..0a0b85d8 --- /dev/null +++ b/test/Transport/FileOptionsTest.php @@ -0,0 +1,73 @@ +options = new FileOptions(); + } + + public function testPathIsSysTempDirByDefault() + { + $this->assertEquals(sys_get_temp_dir(), $this->options->getPath()); + } + + public function testDefaultCallbackIsSetByDefault() + { + $callback = $this->options->getCallback(); + $this->assertTrue(is_callable($callback)); + $test = call_user_func($callback, ''); + $this->assertRegExp('#^ZendMail_\d+_\d+\.tmp$#', $test); + } + + public function testPathIsMutable() + { + $original = $this->options->getPath(); + $this->options->setPath(__DIR__); + $test = $this->options->getPath(); + $this->assertNotEquals($original, $test); + $this->assertEquals(__DIR__, $test); + } + + public function testCallbackIsMutable() + { + $original = $this->options->getCallback(); + $new = function($transport) {}; + $this->options->setCallback($new); + $test = $this->options->getCallback(); + $this->assertNotSame($original, $test); + $this->assertSame($new, $test); + } +} diff --git a/test/Transport/FileTest.php b/test/Transport/FileTest.php new file mode 100644 index 00000000..19f661a9 --- /dev/null +++ b/test/Transport/FileTest.php @@ -0,0 +1,98 @@ +tempDir = sys_get_temp_dir() . '/mail_file_transport'; + if (!is_dir($this->tempDir)) { + mkdir($this->tempDir); + } else { + $this->cleanup($this->tempDir); + } + + $fileOptions = new FileOptions(array( + 'path' => $this->tempDir, + )); + $this->transport = new FileTransport($fileOptions); + } + + public function tearDown() + { + $this->cleanup($this->tempDir); + rmdir($this->tempDir); + } + + protected function cleanup($dir) + { + foreach (glob($dir . '/*.*') as $file) { + unlink($file); + } + } + + public function getMessage() + { + $message = new Message(); + $message->addTo('zf-devteam@zend.com', 'ZF DevTeam') + ->addCc('matthew@zend.com') + ->addBcc('zf-crteam@lists.zend.com', 'CR-Team, ZF Project') + ->addFrom(array( + 'zf-devteam@zend.com', + 'Matthew' => 'matthew@zend.com', + )) + ->setSender('ralph.schindler@zend.com', 'Ralph Schindler') + ->setSubject('Testing Zend\Mail\Transport\Sendmail') + ->setBody('This is only a test.'); + $message->headers()->addHeaders(array( + 'X-Foo-Bar' => 'Matthew', + )); + return $message; + } + + public function testReceivesMailArtifacts() + { + $message = $this->getMessage(); + $this->transport->send($message); + + $this->assertNotNull($this->transport->getLastFile()); + $file = $this->transport->getLastFile(); + $test = file_get_contents($file); + + $this->assertEquals($message->toString(), $test); + } +} diff --git a/test/Transport/SendmailTest.php b/test/Transport/SendmailTest.php new file mode 100644 index 00000000..8e3cc292 --- /dev/null +++ b/test/Transport/SendmailTest.php @@ -0,0 +1,142 @@ +transport = new Sendmail(); + $self = $this; + $this->transport->setCallable(function($to, $subject, $message, $additional_headers, $additional_parameters = null) use ($self) { + $self->to = $to; + $self->subject = $subject; + $self->message = $message; + $self->additional_headers = $additional_headers; + $self->additional_parameters = $additional_parameters; + }); + $this->operating_system = strtoupper(substr(PHP_OS, 0, 3)); + } + + public function tearDown() + { + $this->to = null; + $this->subject = null; + $this->message = null; + $this->additional_headers = null; + $this->additional_parameters = null; + } + + public function getMessage() + { + $message = new Message(); + $message->addTo('zf-devteam@zend.com', 'ZF DevTeam') + ->addCc('matthew@zend.com') + ->addBcc('zf-crteam@lists.zend.com', 'CR-Team, ZF Project') + ->addFrom(array( + 'zf-devteam@zend.com', + 'matthew@zend.com' => 'Matthew', + )) + ->setSender('ralph.schindler@zend.com', 'Ralph Schindler') + ->setSubject('Testing Zend\Mail\Transport\Sendmail') + ->setBody('This is only a test.'); + $message->headers()->addHeaders(array( + 'X-Foo-Bar' => 'Matthew', + )); + return $message; + } + + public function testReceivesMailArtifactsOnUnixSystems() + { + if ($this->operating_system == 'WIN') { + $this->markTestSkipped('This test is *nix-specific'); + } + + $message = $this->getMessage(); + $this->transport->setParameters('-R hdrs'); + + $this->transport->send($message); + $this->assertEquals('ZF DevTeam ', $this->to); + $this->assertEquals('Testing Zend\Mail\Transport\Sendmail', $this->subject); + $this->assertEquals('This is only a test.', trim($this->message)); + $this->assertNotContains("To: ZF DevTeam \r\n", $this->additional_headers); + $this->assertContains("Cc: matthew@zend.com\r\n", $this->additional_headers); + $this->assertContains("Bcc: \"CR-Team, ZF Project\" \r\n", $this->additional_headers); + $this->assertContains("From: zf-devteam@zend.com,\r\n Matthew \r\n", $this->additional_headers); + $this->assertContains("X-Foo-Bar: Matthew\r\n", $this->additional_headers); + $this->assertContains("Sender: Ralph Schindler \r\n", $this->additional_headers); + $this->assertEquals('-R hdrs -r ralph.schindler@zend.com', $this->additional_parameters); + } + + public function testReceivesMailArtifactsOnWindowsSystems() + { + if ($this->operating_system != 'WIN') { + $this->markTestSkipped('This test is Windows-specific'); + } + + $message = $this->getMessage(); + + $this->transport->send($message); + $this->assertEquals('zf-devteam@zend.com', $this->to); + $this->assertEquals('Testing Zend\Mail\Transport\Sendmail', $this->subject); + $this->assertEquals('This is only a test.', trim($this->message)); + $this->assertContains("To: ZF DevTeam \r\n", $this->additional_headers); + $this->assertContains("Cc: matthew@zend.com\r\n", $this->additional_headers); + $this->assertContains("Bcc: \"CR-Team, ZF Project\" \r\n", $this->additional_headers); + $this->assertContains("From: zf-devteam@zend.com,\r\n Matthew \r\n", $this->additional_headers); + $this->assertContains("X-Foo-Bar: Matthew\r\n", $this->additional_headers); + $this->assertContains("Sender: Ralph Schindler \r\n", $this->additional_headers); + $this->assertNull($this->additional_parameters); + } + + public function testLinesStartingWithFullStopsArePreparedProperlyForWindows() + { + if ($this->operating_system != 'WIN') { + $this->markTestSkipped('This test is Windows-specific'); + } + + $message = $this->getMessage(); + $message->setBody("This is the first line.\n. This is the second"); + $this->transport->send($message); + $this->assertContains("line.\n.. This", trim($this->message)); + } +} diff --git a/test/Transport/SmtpTest.php b/test/Transport/SmtpTest.php new file mode 100644 index 00000000..3a3412c8 --- /dev/null +++ b/test/Transport/SmtpTest.php @@ -0,0 +1,106 @@ +transport = new Smtp(); + $this->connection = new SmtpProtocolSpy(); + $this->transport->setConnection($this->connection); + } + + public function getMessage() + { + $message = new Message(); + $message->addTo('zf-devteam@zend.com', 'ZF DevTeam') + ->addCc('matthew@zend.com') + ->addBcc('zf-crteam@lists.zend.com', 'CR-Team, ZF Project') + ->addFrom(array( + 'zf-devteam@zend.com', + 'matthew@zend.com' => 'Matthew', + )) + ->setSender('ralph.schindler@zend.com', 'Ralph Schindler') + ->setSubject('Testing Zend\Mail\Transport\Sendmail') + ->setBody('This is only a test.'); + $message->headers()->addHeaders(array( + 'X-Foo-Bar' => 'Matthew', + )); + return $message; + } + + public function testReceivesMailArtifacts() + { + $message = $this->getMessage(); + $this->transport->send($message); + + $this->assertEquals('ralph.schindler@zend.com', $this->connection->getMail()); + $expectedRecipients = array('zf-devteam@zend.com', 'matthew@zend.com', 'zf-crteam@lists.zend.com'); + $this->assertEquals($expectedRecipients, $this->connection->getRecipients()); + + $data = $this->connection->getData(); + $this->assertContains('To: ZF DevTeam ', $data); + $this->assertContains('Subject: Testing Zend\Mail\Transport\Sendmail', $data); + $this->assertContains("Cc: matthew@zend.com\r\n", $data); + $this->assertNotContains("Bcc: \"CR-Team, ZF Project\" \r\n", $data); + $this->assertNotContains("zf-crteam@lists.zend.com", $data); + $this->assertContains("From: zf-devteam@zend.com,\r\n Matthew \r\n", $data); + $this->assertContains("X-Foo-Bar: Matthew\r\n", $data); + $this->assertContains("Sender: Ralph Schindler \r\n", $data); + $this->assertContains("\r\n\r\nThis is only a test.", $data, $data); + } + + public function testCanUseAuthenticationExtensionsViaPluginBroker() + { + $options = new SmtpOptions(array( + 'connection_class' => 'login', + )); + $transport = new Smtp($options); + $connection = $transport->plugin($options->getConnectionClass(), array(array( + 'username' => 'matthew', + 'password' => 'password', + 'host' => 'localhost', + ))); + $this->assertInstanceOf('Zend\Mail\Protocol\Smtp\Auth\Login', $connection); + $this->assertEquals('matthew', $connection->getUsername()); + $this->assertEquals('password', $connection->getPassword()); + } +}