3 * File containing the ezcMailComposer class
7 * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
8 * @license http://ez.no/licenses/new_bsd New BSD License
12 * Convenience class for writing mail.
14 * This class allows you to create
15 * text and/or HTML mail with attachments. If you need to create more
16 * advanced mail use the ezcMail class and build the body from scratch.
18 * ezcMailComposer is used with the following steps:
19 * 1. Create a composer object.
20 * 2. Set the subject and recipients.
21 * 3. Set the plainText and htmlText message parts. You can set only one
22 * or both. If you set both, the client will display the htmlText if it
23 * supports HTML. Otherwise the client will display plainText.
24 * 4. Add any attachments (addFileAttachment() or addStringAttachment()).
25 * 5. Call the build method.
27 * This example shows how to send an HTML mail with a text fallback and
28 * attachments. The HTML message has an inline image.
30 * $mail = new ezcMailComposer();
31 * $mail->from = new ezcMailAddress( 'john@example.com', 'John Doe' );
32 * $mail->addTo( new ezcMailAddress( 'cindy@example.com', 'Cindy Doe' ) );
33 * $mail->subject = "Example of an HTML email with attachments";
34 * $mail->plainText = "Here is the text version of the mail. This is displayed if the client can not understand HTML";
35 * $mail->htmlText = "<html>Here is the HTML version of your mail with an image: <img src='file://path_to_image.jpg' /></html>";
36 * $mail->addFileAttachment( 'path_to_attachment.file' );
38 * $transport = new ezcMailMtaTransport();
39 * $transport->send( $mail );
42 * By default, if the htmlText property contains an HTML image tag with file://
43 * in href, that file will be included in the created message.
47 * <img src="file:///home/me/image.jpg" />
50 * This can be a security risk if a user links to another file, for example logs
51 * or password files. With the automaticImageInclude option (default value true)
52 * from {@link ezcMailComposerOptions}, the automatic inclusion of files can be
57 * $options = new ezcMailComposerOptions();
58 * $options->automaticImageInclude = false; // default value is true
60 * $mail = new ezcMailComposer( $options );
62 * // ... add To, From, Subject, etc to $mail
63 * $mail->htmlText = "<html>Here is the image: <img src="file:///etc/passwd" /></html>";
68 * After running the above code, the sent mail will not contain the file specified
69 * in the htmlText property.
71 * The file name in the attachment can be different than the file name on disk, by
72 * passing an {@link ezcMailContentDispositionHeader} object to the function
73 * addFileAttachment(). Example:
75 * $mail = new ezcMailComposer();
76 * $mail->from = new ezcMailAddress( 'john@example.com', 'John Doe' );
77 * $mail->addTo( new ezcMailAddress( 'cindy@example.com', 'Cindy Doe' ) );
78 * $mail->subject = "Example of an HTML email with attachments and custom attachment file name";
79 * $mail->plainText = "Here is the text version of the mail. This is displayed if the client can not understand HTML";
80 * $mail->htmlText = "<html>Here is the HTML version of your mail with an image: <img src='file://path_to_image.jpg' /></html>";
82 * $disposition = new ezcMailContentDispositionHeader();
83 * $disposition->fileName = 'custom name for attachment.txt';
84 * $disposition->fileNameCharSet = 'utf-8'; // if using non-ascii characters in the file name
85 * $disposition->disposition = 'attachment'; // default value is 'inline'
87 * $mail->addFileAttachment( 'path_to_attachment.file', null, null, $disposition );
90 * $transport = new ezcMailMtaTransport();
91 * $transport->send( $mail );
94 * Use the function addStringAttachment() if you want to add an attachment which
95 * is stored in a string variable. A file name for a string attachment needs to
96 * be specified as well. Example:
99 * $contents = 'contents for mail attachment'; // can be a binary string, eg. image file contents
100 * $mail->addStringAttachment( 'filename', $contents );
103 * @property string $plainText
104 * Contains the message of the mail in plain text.
105 * @property string $htmlText
106 * Contains the message of the mail in HTML. You should also provide
107 * the text of the HTML message in the plainText property. Both will
108 * be sent and the receiver will see the HTML message if his/her
109 * client supports HTML. If the HTML message contains links to
110 * local images and/or files these will be included into the mail
111 * when generateBody is called. Links to local files must start with
112 * "file://" in order to be recognized. You can use the option
113 * automaticImageInclude (default value is true) from
114 * {@link ezcMailComposerOptions} to turn off the
115 * automatic inclusion of files in the generated mail.
116 * @property string $charset
117 * Contains the character set for both $plainText and $htmlText.
118 * Default value is 'us-ascii'. This does not set any specific
119 * charset for the subject, you need the subjectCharset property for
121 * @property string $encoding
122 * Contains the encoding for both $plainText and $htmlText.
123 * Default value is ezcMail::EIGHT_BIT. Other values are found
124 * as constants in the class {@link ezcMail}.
125 * @property ezcMailComposerOptions $options
126 * Options for composing mail. See {@link ezcMailComposerOptions}.
132 class ezcMailComposer
extends ezcMail
135 * Holds the attachments filenames.
137 * The array contains relative or absolute paths to the attachments.
141 private $attachments = array();
144 * Holds the options for this class.
146 * @var ezcMailComposerOptions
151 * Constructs an empty ezcMailComposer object.
153 * @param ezcMailComposerOptions $options
155 public function __construct( ezcMailComposerOptions
$options = null )
157 $this->properties
['plainText'] = null;
158 $this->properties
['htmlText'] = null;
159 $this->properties
['charset'] = 'us-ascii';
160 $this->properties
['encoding'] = ezcMail
::EIGHT_BIT
;
161 if ( $options === null )
163 $options = new ezcMailComposerOptions();
166 $this->options
= $options;
168 parent
::__construct();
172 * Sets the property $name to $value.
174 * @throws ezcBasePropertyNotFoundException
175 * if the property does not exist.
176 * @param string $name
177 * @param mixed $value
180 public function __set( $name, $value )
188 $this->properties
[$name] = $value;
192 if ( !$value instanceof ezcMailComposerOptions
)
194 throw new ezcBaseValueException( $name, $value, 'ezcMailComposerOptions' );
197 $this->options
= $value;
201 parent
::__set( $name, $value );
206 * Returns the property $name.
208 * @throws ezcBasePropertyNotFoundException
209 * if the property does not exist.
210 * @param string $name
214 public function __get( $name )
222 return $this->properties
[$name];
225 return $this->options
;
228 return parent
::__get( $name );
233 * Returns true if the property $name is set, otherwise false.
235 * @param string $name
239 public function __isset( $name )
247 return isset( $this->properties
[$name] );
250 return isset( $this->options
);
253 return parent
::__isset( $name );
258 * Adds the file $fileName to the list of attachments.
260 * If $content is specified, $fileName is not checked if it exists.
261 * $this->attachments will also contain in this case the $content,
262 * $contentType and $mimeType.
264 * The $contentType (default = application) and $mimeType (default =
265 * octet-stream) control the complete mime-type of the attachment.
267 * If $contentDisposition is specified, the attached file will have its
268 * Content-Disposition header set according to the $contentDisposition
269 * object and the filename of the attachment in the generated mail will be
270 * the one from the $contentDisposition object.
272 * @throws ezcBaseFileNotFoundException
273 * if $fileName does not exists.
274 * @throws ezcBaseFilePermissionProblem
275 * if $fileName could not be read.
276 * @param string $fileName
277 * @param string $content
278 * @param string $contentType
279 * @param string $mimeType
280 * @param ezcMailContentDispositionHeader $contentDisposition
281 * @apichange This function might be removed in a future iteration of
282 * the Mail component. Use addFileAttachment() and
283 * addStringAttachment() instead.
285 public function addAttachment( $fileName, $content = null, $contentType = null, $mimeType = null, ezcMailContentDispositionHeader
$contentDisposition = null )
287 if ( is_null( $content ) )
289 $this->addFileAttachment( $fileName, $contentType, $mimeType, $contentDisposition );
293 $this->addStringAttachment( $fileName, $content, $contentType, $mimeType, $contentDisposition );
298 * Adds the file $fileName to the list of attachments.
300 * The $contentType (default = application) and $mimeType (default =
301 * octet-stream) control the complete mime-type of the attachment.
303 * If $contentDisposition is specified, the attached file will have its
304 * Content-Disposition header set according to the $contentDisposition
305 * object and the filename of the attachment in the generated mail will be
306 * the one from the $contentDisposition object.
308 * @throws ezcBaseFileNotFoundException
309 * if $fileName does not exists.
310 * @throws ezcBaseFilePermissionProblem
311 * if $fileName could not be read.
312 * @param string $fileName
313 * @param string $contentType
314 * @param string $mimeType
315 * @param ezcMailContentDispositionHeader $contentDisposition
317 public function addFileAttachment( $fileName, $contentType = null, $mimeType = null, ezcMailContentDispositionHeader
$contentDisposition = null )
319 if ( is_readable( $fileName ) )
321 $this->attachments
[] = array( $fileName, null, $contentType, $mimeType, $contentDisposition );
325 if ( file_exists( $fileName ) )
327 throw new ezcBaseFilePermissionException( $fileName, ezcBaseFileException
::READ
);
331 throw new ezcBaseFileNotFoundException( $fileName );
337 * Adds the file $fileName to the list of attachments, with contents $content.
339 * The file $fileName is not checked if it exists. An attachment is added
340 * to the mail, with the name $fileName, and the contents $content.
342 * The $contentType (default = application) and $mimeType (default =
343 * octet-stream) control the complete mime-type of the attachment.
345 * If $contentDisposition is specified, the attached file will have its
346 * Content-Disposition header set according to the $contentDisposition
347 * object and the filename of the attachment in the generated mail will be
348 * the one from the $contentDisposition object.
350 * @param string $fileName
351 * @param string $content
352 * @param string $contentType
353 * @param string $mimeType
354 * @param ezcMailContentDispositionHeader $contentDisposition
356 public function addStringAttachment( $fileName, $content, $contentType = null, $mimeType = null, ezcMailContentDispositionHeader
$contentDisposition = null )
358 $this->attachments
[] = array( $fileName, $content, $contentType, $mimeType, $contentDisposition );
362 * Builds the complete email message in RFC822 format.
364 * This method must be called before the message is sent.
366 * @throws ezcBaseFileNotFoundException
367 * if any of the attachment files can not be found.
369 public function build()
373 // create the text part if there is one
374 if ( $this->plainText
!= '' )
376 $mainPart = new ezcMailText( $this->plainText
, $this->charset
);
379 // create the HTML part if there is one
381 if ( $this->htmlText
!= '' )
383 $htmlPart = $this->generateHtmlPart();
385 // create a MultiPartAlternative if a text part exists
386 if ( $mainPart != false )
388 $mainPart = new ezcMailMultipartAlternative( $mainPart, $htmlPart );
392 $mainPart = $htmlPart;
396 // build all attachments
397 // special case, mail with no text and one attachment.
398 // A fix for issue #14220 was added by wrapping the attachment in
399 // an ezcMailMultipartMixed part
400 if ( $mainPart == false && count( $this->attachments
) == 1 )
402 if ( isset( $this->attachments
[0][1] ) )
404 if ( is_resource( $this->attachments
[0][1] ) )
406 $mainPart = new ezcMailMultipartMixed( new ezcMailStreamFile( $this->attachments
[0][0], $this->attachments
[0][1], $this->attachments
[0][2], $this->attachments
[0][3] ) );
410 $mainPart = new ezcMailMultipartMixed( new ezcMailVirtualFile( $this->attachments
[0][0], $this->attachments
[0][1], $this->attachments
[0][2], $this->attachments
[0][3] ) );
415 $mainPart = new ezcMailMultipartMixed( new ezcMailFile( $this->attachments
[0][0], $this->attachments
[0][2], $this->attachments
[0][3] ) );
417 $mainPart->contentDisposition
= $this->attachments
[0][4];
419 else if ( count( $this->attachments
) > 0 )
421 $mainPart = ( $mainPart == false )
422 ?
new ezcMailMultipartMixed()
423 : new ezcMailMultipartMixed( $mainPart );
425 // add the attachments to the mixed part
426 foreach ( $this->attachments
as $attachment )
428 if ( isset( $attachment[1] ) )
430 if ( is_resource( $attachment[1] ) )
432 $part = new ezcMailStreamFile( $attachment[0], $attachment[1], $attachment[2], $attachment[3] );
436 $part = new ezcMailVirtualFile( $attachment[0], $attachment[1], $attachment[2], $attachment[3] );
441 $part = new ezcMailFile( $attachment[0], $attachment[2], $attachment[3] );
443 $part->contentDisposition
= $attachment[4];
444 $mainPart->appendPart( $part );
448 $this->body
= $mainPart;
452 * Returns an ezcMailPart based on the HTML provided.
454 * This method adds local files/images to the mail itself using a
455 * {@link ezcMailMultipartRelated} object.
457 * @throws ezcBaseFileNotFoundException
458 * if $fileName does not exists.
459 * @throws ezcBaseFilePermissionProblem
460 * if $fileName could not be read.
461 * @return ezcMailPart
463 private function generateHtmlPart()
466 if ( $this->htmlText
!= '' )
469 if ( $this->options
->automaticImageInclude
=== true )
471 // recognize file:// and file:///, pick out the image, add it as a part and then..:)
472 preg_match_all( "/<img[\s\*\s]src=[\'\"]file:\/\/([^ >\'\"]+)/i", $this->htmlText
, $matches );
473 // pictures/files can be added multiple times. We only need them once.
474 $matches = array_unique( $matches[1] );
477 $result = new ezcMailText( $this->htmlText
, $this->charset
, $this->encoding
);
478 $result->subType
= "html";
479 if ( count( $matches ) > 0 )
482 // wrap already existing message in an alternative part
483 $result = new ezcMailMultipartRelated( $result );
485 // create a filepart and add it to the related part
486 // also store the ID for each part since we need those
487 // when we replace the originals in the HTML message.
488 foreach ( $matches as $fileName )
490 if ( is_readable( $fileName ) )
492 // @todo waiting for fix of the fileinfo extension
493 // $contents = file_get_contents( $fileName );
496 if ( ezcBaseFeatures
::hasExtensionSupport( 'fileinfo' ) )
498 // if fileinfo extension is available
499 $filePart = new ezcMailFile( $fileName );
501 elseif ( ezcMailTools
::guessContentType( $fileName, $contentType, $mimeType ) )
503 // if fileinfo extension is not available try to get content/mime type
504 // from the file extension
505 $filePart = new ezcMailFile( $fileName, $contentType, $mimeType );
509 // fallback in case fileinfo is not available and could not get content/mime
510 // type from file extension
511 $filePart = new ezcMailFile( $fileName, "application", "octet-stream" );
513 $cid = $result->addRelatedPart( $filePart );
514 // replace the original file reference with a reference to the cid
515 $this->htmlText
= str_replace( 'file://' . $fileName, 'cid:' . $cid, $this->htmlText
);
519 if ( file_exists( $fileName ) )
521 throw new ezcBaseFilePermissionException( $fileName, ezcBaseFileException
::READ
);
525 throw new ezcBaseFileNotFoundException( $fileName );
530 // update mail, with replaced URLs
531 $htmlPart->text
= $this->htmlText
;