blob: 107f13eb95fcb75daf0203d67876031433e46af5 [file] [log] [blame]
<?php
/**
* File containing the ezcMailComposer class
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Mail
* @version //autogen//
* @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved.
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* Convenience class for writing mail.
*
* This class allows you to create
* text and/or HTML mail with attachments. If you need to create more
* advanced mail use the ezcMail class and build the body from scratch.
*
* ezcMailComposer is used with the following steps:
* 1. Create a composer object.
* 2. Set the subject and recipients.
* 3. Set the plainText and htmlText message parts. You can set only one
* or both. If you set both, the client will display the htmlText if it
* supports HTML. Otherwise the client will display plainText.
* 4. Add any attachments (addFileAttachment() or addStringAttachment()).
* 5. Call the build method.
*
* This example shows how to send an HTML mail with a text fallback and
* attachments. The HTML message has an inline image.
* <code>
* $mail = new ezcMailComposer();
* $mail->from = new ezcMailAddress( 'john@example.com', 'John Doe' );
* $mail->addTo( new ezcMailAddress( 'cindy@example.com', 'Cindy Doe' ) );
* $mail->subject = "Example of an HTML email with attachments";
* $mail->plainText = "Here is the text version of the mail. This is displayed if the client can not understand HTML";
* $mail->htmlText = "<html>Here is the HTML version of your mail with an image: <img src='file://path_to_image.jpg' /></html>";
* $mail->addFileAttachment( 'path_to_attachment.file' );
* $mail->build();
* $transport = new ezcMailMtaTransport();
* $transport->send( $mail );
* </code>
*
* By default, if the htmlText property contains an HTML image tag with file://
* in href, that file will be included in the created message.
*
* Example:
* <code>
* <img src="file:///home/me/image.jpg" />
* </code>
*
* This can be a security risk if a user links to another file, for example logs
* or password files. With the automaticImageInclude option (default value true)
* from {@link ezcMailComposerOptions}, the automatic inclusion of files can be
* turned off.
*
* Example:
* <code>
* $options = new ezcMailComposerOptions();
* $options->automaticImageInclude = false; // default value is true
*
* $mail = new ezcMailComposer( $options );
*
* // ... add To, From, Subject, etc to $mail
* $mail->htmlText = "<html>Here is the image: <img src="file:///etc/passwd" /></html>";
*
* // ... send $mail
* </code>
*
* After running the above code, the sent mail will not contain the file specified
* in the htmlText property.
*
* The file name in the attachment can be different than the file name on disk, by
* passing an {@link ezcMailContentDispositionHeader} object to the function
* addFileAttachment(). Example:
* <code>
* $mail = new ezcMailComposer();
* $mail->from = new ezcMailAddress( 'john@example.com', 'John Doe' );
* $mail->addTo( new ezcMailAddress( 'cindy@example.com', 'Cindy Doe' ) );
* $mail->subject = "Example of an HTML email with attachments and custom attachment file name";
* $mail->plainText = "Here is the text version of the mail. This is displayed if the client can not understand HTML";
* $mail->htmlText = "<html>Here is the HTML version of your mail with an image: <img src='file://path_to_image.jpg' /></html>";
*
* $disposition = new ezcMailContentDispositionHeader();
* $disposition->fileName = 'custom name for attachment.txt';
* $disposition->fileNameCharSet = 'utf-8'; // if using non-ascii characters in the file name
* $disposition->disposition = 'attachment'; // default value is 'inline'
*
* $mail->addFileAttachment( 'path_to_attachment.file', null, null, $disposition );
* $mail->build();
*
* $transport = new ezcMailMtaTransport();
* $transport->send( $mail );
* </code>
*
* Use the function addStringAttachment() if you want to add an attachment which
* is stored in a string variable. A file name for a string attachment needs to
* be specified as well. Example:
*
* <code>
* $contents = 'contents for mail attachment'; // can be a binary string, eg. image file contents
* $mail->addStringAttachment( 'filename', $contents );
* </code>
*
* @property string $plainText
* Contains the message of the mail in plain text.
* @property string $htmlText
* Contains the message of the mail in HTML. You should also provide
* the text of the HTML message in the plainText property. Both will
* be sent and the receiver will see the HTML message if his/her
* client supports HTML. If the HTML message contains links to
* local images and/or files these will be included into the mail
* when generateBody is called. Links to local files must start with
* "file://" in order to be recognized. You can use the option
* automaticImageInclude (default value is true) from
* {@link ezcMailComposerOptions} to turn off the
* automatic inclusion of files in the generated mail.
* @property string $charset
* Contains the character set for both $plainText and $htmlText.
* Default value is 'us-ascii'. This does not set any specific
* charset for the subject, you need the subjectCharset property for
* that.
* @property string $encoding
* Contains the encoding for both $plainText and $htmlText.
* Default value is ezcMail::EIGHT_BIT. Other values are found
* as constants in the class {@link ezcMail}.
* @property ezcMailComposerOptions $options
* Options for composing mail. See {@link ezcMailComposerOptions}.
*
* @package Mail
* @version //autogen//
* @mainclass
*/
class ezcMailComposer extends ezcMail
{
/**
* Holds the attachments filenames.
*
* The array contains relative or absolute paths to the attachments.
*
* @var array(string)
*/
private $attachments = array();
/**
* Holds the options for this class.
*
* @var ezcMailComposerOptions
*/
protected $options;
/**
* Constructs an empty ezcMailComposer object.
*
* @param ezcMailComposerOptions $options
*/
public function __construct( ezcMailComposerOptions $options = null )
{
$this->properties['plainText'] = null;
$this->properties['htmlText'] = null;
$this->properties['charset'] = 'us-ascii';
$this->properties['encoding'] = ezcMail::EIGHT_BIT;
if ( $options === null )
{
$options = new ezcMailComposerOptions();
}
$this->options = $options;
parent::__construct( $options );
}
/**
* Sets the property $name to $value.
*
* @throws ezcBasePropertyNotFoundException
* if the property does not exist.
* @param string $name
* @param mixed $value
* @ignore
*/
public function __set( $name, $value )
{
switch ( $name )
{
case 'plainText':
case 'htmlText':
case 'charset':
case 'encoding':
$this->properties[$name] = $value;
break;
case 'options':
if ( !$value instanceof ezcMailComposerOptions )
{
throw new ezcBaseValueException( $name, $value, 'ezcMailComposerOptions' );
}
$this->options = $value;
break;
default:
parent::__set( $name, $value );
}
}
/**
* Returns the property $name.
*
* @throws ezcBasePropertyNotFoundException
* if the property does not exist.
* @param string $name
* @return mixed
* @ignore
*/
public function __get( $name )
{
switch ( $name )
{
case 'plainText':
case 'htmlText':
case 'charset':
case 'encoding':
return $this->properties[$name];
case 'options':
return $this->options;
default:
return parent::__get( $name );
}
}
/**
* Returns true if the property $name is set, otherwise false.
*
* @param string $name
* @return bool
* @ignore
*/
public function __isset( $name )
{
switch ( $name )
{
case 'plainText':
case 'htmlText':
case 'charset':
case 'encoding':
return isset( $this->properties[$name] );
case 'options':
return isset( $this->options );
default:
return parent::__isset( $name );
}
}
/**
* Adds the file $fileName to the list of attachments.
*
* If $content is specified, $fileName is not checked if it exists.
* $this->attachments will also contain in this case the $content,
* $contentType and $mimeType.
*
* The $contentType (default = application) and $mimeType (default =
* octet-stream) control the complete mime-type of the attachment.
*
* If $contentDisposition is specified, the attached file will have its
* Content-Disposition header set according to the $contentDisposition
* object and the filename of the attachment in the generated mail will be
* the one from the $contentDisposition object.
*
* @throws ezcBaseFileNotFoundException
* if $fileName does not exists.
* @throws ezcBaseFilePermissionProblem
* if $fileName could not be read.
* @param string $fileName
* @param string $content
* @param string $contentType
* @param string $mimeType
* @param ezcMailContentDispositionHeader $contentDisposition
* @apichange This function might be removed in a future iteration of
* the Mail component. Use addFileAttachment() and
* addStringAttachment() instead.
*/
public function addAttachment( $fileName, $content = null, $contentType = null, $mimeType = null, ezcMailContentDispositionHeader $contentDisposition = null )
{
if ( is_null( $content ) )
{
$this->addFileAttachment( $fileName, $contentType, $mimeType, $contentDisposition );
}
else
{
$this->addStringAttachment( $fileName, $content, $contentType, $mimeType, $contentDisposition );
}
}
/**
* Adds the file $fileName to the list of attachments.
*
* The $contentType (default = application) and $mimeType (default =
* octet-stream) control the complete mime-type of the attachment.
*
* If $contentDisposition is specified, the attached file will have its
* Content-Disposition header set according to the $contentDisposition
* object and the filename of the attachment in the generated mail will be
* the one from the $contentDisposition object.
*
* @throws ezcBaseFileNotFoundException
* if $fileName does not exists.
* @throws ezcBaseFilePermissionProblem
* if $fileName could not be read.
* @param string $fileName
* @param string $contentType
* @param string $mimeType
* @param ezcMailContentDispositionHeader $contentDisposition
*/
public function addFileAttachment( $fileName, $contentType = null, $mimeType = null, ezcMailContentDispositionHeader $contentDisposition = null )
{
if ( is_readable( $fileName ) )
{
$this->attachments[] = array( $fileName, null, $contentType, $mimeType, $contentDisposition );
}
else
{
if ( file_exists( $fileName ) )
{
throw new ezcBaseFilePermissionException( $fileName, ezcBaseFileException::READ );
}
else
{
throw new ezcBaseFileNotFoundException( $fileName );
}
}
}
/**
* Adds the file $fileName to the list of attachments, with contents $content.
*
* The file $fileName is not checked if it exists. An attachment is added
* to the mail, with the name $fileName, and the contents $content.
*
* The $contentType (default = application) and $mimeType (default =
* octet-stream) control the complete mime-type of the attachment.
*
* If $contentDisposition is specified, the attached file will have its
* Content-Disposition header set according to the $contentDisposition
* object and the filename of the attachment in the generated mail will be
* the one from the $contentDisposition object.
*
* @param string $fileName
* @param string $content
* @param string $contentType
* @param string $mimeType
* @param ezcMailContentDispositionHeader $contentDisposition
*/
public function addStringAttachment( $fileName, $content, $contentType = null, $mimeType = null, ezcMailContentDispositionHeader $contentDisposition = null )
{
$this->attachments[] = array( $fileName, $content, $contentType, $mimeType, $contentDisposition );
}
/**
* Builds the complete email message in RFC822 format.
*
* This method must be called before the message is sent.
*
* @throws ezcBaseFileNotFoundException
* if any of the attachment files can not be found.
*/
public function build()
{
$mainPart = false;
// create the text part if there is one
if ( $this->plainText != '' )
{
$mainPart = new ezcMailText( $this->plainText, $this->charset );
}
// create the HTML part if there is one
$htmlPart = false;
if ( $this->htmlText != '' )
{
$htmlPart = $this->generateHtmlPart();
// create a MultiPartAlternative if a text part exists
if ( $mainPart != false )
{
$mainPart = new ezcMailMultipartAlternative( $mainPart, $htmlPart );
}
else
{
$mainPart = $htmlPart;
}
}
// build all attachments
// special case, mail with no text and one attachment.
// A fix for issue #14220 was added by wrapping the attachment in
// an ezcMailMultipartMixed part
if ( $mainPart == false && count( $this->attachments ) == 1 )
{
if ( isset( $this->attachments[0][1] ) )
{
if ( is_resource( $this->attachments[0][1] ) )
{
$mainPart = new ezcMailMultipartMixed( new ezcMailStreamFile( $this->attachments[0][0], $this->attachments[0][1], $this->attachments[0][2], $this->attachments[0][3] ) );
}
else
{
$mainPart = new ezcMailMultipartMixed( new ezcMailVirtualFile( $this->attachments[0][0], $this->attachments[0][1], $this->attachments[0][2], $this->attachments[0][3] ) );
}
}
else
{
$mainPart = new ezcMailMultipartMixed( new ezcMailFile( $this->attachments[0][0], $this->attachments[0][2], $this->attachments[0][3] ) );
}
$mainPart->contentDisposition = $this->attachments[0][4];
}
else if ( count( $this->attachments ) > 0 )
{
$mainPart = ( $mainPart == false )
? new ezcMailMultipartMixed()
: new ezcMailMultipartMixed( $mainPart );
// add the attachments to the mixed part
foreach ( $this->attachments as $attachment )
{
if ( isset( $attachment[1] ) )
{
if ( is_resource( $attachment[1] ) )
{
$part = new ezcMailStreamFile( $attachment[0], $attachment[1], $attachment[2], $attachment[3] );
}
else
{
$part = new ezcMailVirtualFile( $attachment[0], $attachment[1], $attachment[2], $attachment[3] );
}
}
else
{
$part = new ezcMailFile( $attachment[0], $attachment[2], $attachment[3] );
}
$part->contentDisposition = $attachment[4];
$mainPart->appendPart( $part );
}
}
$this->body = $mainPart;
}
/**
* Returns an ezcMailPart based on the HTML provided.
*
* This method adds local files/images to the mail itself using a
* {@link ezcMailMultipartRelated} object.
*
* @throws ezcBaseFileNotFoundException
* if $fileName does not exists.
* @throws ezcBaseFilePermissionProblem
* if $fileName could not be read.
* @return ezcMailPart
*/
private function generateHtmlPart()
{
$result = false;
if ( $this->htmlText != '' )
{
$matches = array();
if ( $this->options->automaticImageInclude === true )
{
// recognize file:// and file:///, pick out the image, add it as a part and then..:)
preg_match_all( '(
<img \\s+[^>]*
src\\s*=\\s*
(?:
(?# Match quoted attribute)
([\'"])file://(?P<quoted>[^>]+)\\1
(?# Match unquoted attribute, which may not contain spaces)
| file://(?P<unquoted>[^>\\s]+)
)
[^>]* >)ix', $this->htmlText, $matches );
// pictures/files can be added multiple times. We only need them once.
$matches = array_filter( array_unique( array_merge( $matches['quoted'], $matches['unquoted'] ) ) );
}
$result = new ezcMailText( $this->htmlText, $this->charset, $this->encoding );
$result->subType = "html";
if ( count( $matches ) > 0 )
{
$htmlPart = $result;
// wrap already existing message in an alternative part
$result = new ezcMailMultipartRelated( $result );
// create a filepart and add it to the related part
// also store the ID for each part since we need those
// when we replace the originals in the HTML message.
foreach ( $matches as $fileName )
{
if ( is_readable( $fileName ) )
{
// @todo waiting for fix of the fileinfo extension
// $contents = file_get_contents( $fileName );
$mimeType = null;
$contentType = null;
if ( ezcBaseFeatures::hasExtensionSupport( 'fileinfo' ) )
{
// if fileinfo extension is available
$filePart = new ezcMailFile( $fileName );
}
elseif ( ezcMailTools::guessContentType( $fileName, $contentType, $mimeType ) )
{
// if fileinfo extension is not available try to get content/mime type
// from the file extension
$filePart = new ezcMailFile( $fileName, $contentType, $mimeType );
}
else
{
// fallback in case fileinfo is not available and could not get content/mime
// type from file extension
$filePart = new ezcMailFile( $fileName, "application", "octet-stream" );
}
$cid = $result->addRelatedPart( $filePart );
// replace the original file reference with a reference to the cid
$this->htmlText = str_replace( 'file://' . $fileName, 'cid:' . $cid, $this->htmlText );
}
else
{
if ( file_exists( $fileName ) )
{
throw new ezcBaseFilePermissionException( $fileName, ezcBaseFileException::READ );
}
else
{
throw new ezcBaseFileNotFoundException( $fileName );
}
// throw
}
}
// update mail, with replaced URLs
$htmlPart->text = $this->htmlText;
}
}
return $result;
}
}
?>