commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / packages / ezc / Mail / src / composer.php
1 <?php
2 /**
3 * File containing the ezcMailComposer class
4 *
5 * @package Mail
6 * @version 1.7beta1
7 * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
8 * @license http://ez.no/licenses/new_bsd New BSD License
9 */
10
11 /**
12 * Convenience class for writing mail.
13 *
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.
17 *
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.
26 *
27 * This example shows how to send an HTML mail with a text fallback and
28 * attachments. The HTML message has an inline image.
29 * <code>
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' );
37 * $mail->build();
38 * $transport = new ezcMailMtaTransport();
39 * $transport->send( $mail );
40 * </code>
41 *
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.
44 *
45 * Example:
46 * <code>
47 * <img src="file:///home/me/image.jpg" />
48 * </code>
49 *
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
53 * turned off.
54 *
55 * Example:
56 * <code>
57 * $options = new ezcMailComposerOptions();
58 * $options->automaticImageInclude = false; // default value is true
59 *
60 * $mail = new ezcMailComposer( $options );
61 *
62 * // ... add To, From, Subject, etc to $mail
63 * $mail->htmlText = "<html>Here is the image: <img src="file:///etc/passwd" /></html>";
64 *
65 * // ... send $mail
66 * </code>
67 *
68 * After running the above code, the sent mail will not contain the file specified
69 * in the htmlText property.
70 *
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:
74 * <code>
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>";
81 *
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'
86 *
87 * $mail->addFileAttachment( 'path_to_attachment.file', null, null, $disposition );
88 * $mail->build();
89 *
90 * $transport = new ezcMailMtaTransport();
91 * $transport->send( $mail );
92 * </code>
93 *
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:
97 *
98 * <code>
99 * $contents = 'contents for mail attachment'; // can be a binary string, eg. image file contents
100 * $mail->addStringAttachment( 'filename', $contents );
101 * </code>
102 *
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
120 * that.
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}.
127 *
128 * @package Mail
129 * @version 1.7beta1
130 * @mainclass
131 */
132 class ezcMailComposer extends ezcMail
133 {
134 /**
135 * Holds the attachments filenames.
136 *
137 * The array contains relative or absolute paths to the attachments.
138 *
139 * @var array(string)
140 */
141 private $attachments = array();
142
143 /**
144 * Holds the options for this class.
145 *
146 * @var ezcMailComposerOptions
147 */
148 protected $options;
149
150 /**
151 * Constructs an empty ezcMailComposer object.
152 *
153 * @param ezcMailComposerOptions $options
154 */
155 public function __construct( ezcMailComposerOptions $options = null )
156 {
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 )
162 {
163 $options = new ezcMailComposerOptions();
164 }
165
166 $this->options = $options;
167
168 parent::__construct();
169 }
170
171 /**
172 * Sets the property $name to $value.
173 *
174 * @throws ezcBasePropertyNotFoundException
175 * if the property does not exist.
176 * @param string $name
177 * @param mixed $value
178 * @ignore
179 */
180 public function __set( $name, $value )
181 {
182 switch ( $name )
183 {
184 case 'plainText':
185 case 'htmlText':
186 case 'charset':
187 case 'encoding':
188 $this->properties[$name] = $value;
189 break;
190
191 case 'options':
192 if ( !$value instanceof ezcMailComposerOptions )
193 {
194 throw new ezcBaseValueException( $name, $value, 'ezcMailComposerOptions' );
195 }
196
197 $this->options = $value;
198 break;
199
200 default:
201 parent::__set( $name, $value );
202 }
203 }
204
205 /**
206 * Returns the property $name.
207 *
208 * @throws ezcBasePropertyNotFoundException
209 * if the property does not exist.
210 * @param string $name
211 * @return mixed
212 * @ignore
213 */
214 public function __get( $name )
215 {
216 switch ( $name )
217 {
218 case 'plainText':
219 case 'htmlText':
220 case 'charset':
221 case 'encoding':
222 return $this->properties[$name];
223
224 case 'options':
225 return $this->options;
226
227 default:
228 return parent::__get( $name );
229 }
230 }
231
232 /**
233 * Returns true if the property $name is set, otherwise false.
234 *
235 * @param string $name
236 * @return bool
237 * @ignore
238 */
239 public function __isset( $name )
240 {
241 switch ( $name )
242 {
243 case 'plainText':
244 case 'htmlText':
245 case 'charset':
246 case 'encoding':
247 return isset( $this->properties[$name] );
248
249 case 'options':
250 return isset( $this->options );
251
252 default:
253 return parent::__isset( $name );
254 }
255 }
256
257 /**
258 * Adds the file $fileName to the list of attachments.
259 *
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.
263 *
264 * The $contentType (default = application) and $mimeType (default =
265 * octet-stream) control the complete mime-type of the attachment.
266 *
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.
271 *
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.
284 */
285 public function addAttachment( $fileName, $content = null, $contentType = null, $mimeType = null, ezcMailContentDispositionHeader $contentDisposition = null )
286 {
287 if ( is_null( $content ) )
288 {
289 $this->addFileAttachment( $fileName, $contentType, $mimeType, $contentDisposition );
290 }
291 else
292 {
293 $this->addStringAttachment( $fileName, $content, $contentType, $mimeType, $contentDisposition );
294 }
295 }
296
297 /**
298 * Adds the file $fileName to the list of attachments.
299 *
300 * The $contentType (default = application) and $mimeType (default =
301 * octet-stream) control the complete mime-type of the attachment.
302 *
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.
307 *
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
316 */
317 public function addFileAttachment( $fileName, $contentType = null, $mimeType = null, ezcMailContentDispositionHeader $contentDisposition = null )
318 {
319 if ( is_readable( $fileName ) )
320 {
321 $this->attachments[] = array( $fileName, null, $contentType, $mimeType, $contentDisposition );
322 }
323 else
324 {
325 if ( file_exists( $fileName ) )
326 {
327 throw new ezcBaseFilePermissionException( $fileName, ezcBaseFileException::READ );
328 }
329 else
330 {
331 throw new ezcBaseFileNotFoundException( $fileName );
332 }
333 }
334 }
335
336 /**
337 * Adds the file $fileName to the list of attachments, with contents $content.
338 *
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.
341 *
342 * The $contentType (default = application) and $mimeType (default =
343 * octet-stream) control the complete mime-type of the attachment.
344 *
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.
349 *
350 * @param string $fileName
351 * @param string $content
352 * @param string $contentType
353 * @param string $mimeType
354 * @param ezcMailContentDispositionHeader $contentDisposition
355 */
356 public function addStringAttachment( $fileName, $content, $contentType = null, $mimeType = null, ezcMailContentDispositionHeader $contentDisposition = null )
357 {
358 $this->attachments[] = array( $fileName, $content, $contentType, $mimeType, $contentDisposition );
359 }
360
361 /**
362 * Builds the complete email message in RFC822 format.
363 *
364 * This method must be called before the message is sent.
365 *
366 * @throws ezcBaseFileNotFoundException
367 * if any of the attachment files can not be found.
368 */
369 public function build()
370 {
371 $mainPart = false;
372
373 // create the text part if there is one
374 if ( $this->plainText != '' )
375 {
376 $mainPart = new ezcMailText( $this->plainText, $this->charset );
377 }
378
379 // create the HTML part if there is one
380 $htmlPart = false;
381 if ( $this->htmlText != '' )
382 {
383 $htmlPart = $this->generateHtmlPart();
384
385 // create a MultiPartAlternative if a text part exists
386 if ( $mainPart != false )
387 {
388 $mainPart = new ezcMailMultipartAlternative( $mainPart, $htmlPart );
389 }
390 else
391 {
392 $mainPart = $htmlPart;
393 }
394 }
395
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 )
401 {
402 if ( isset( $this->attachments[0][1] ) )
403 {
404 if ( is_resource( $this->attachments[0][1] ) )
405 {
406 $mainPart = new ezcMailMultipartMixed( new ezcMailStreamFile( $this->attachments[0][0], $this->attachments[0][1], $this->attachments[0][2], $this->attachments[0][3] ) );
407 }
408 else
409 {
410 $mainPart = new ezcMailMultipartMixed( new ezcMailVirtualFile( $this->attachments[0][0], $this->attachments[0][1], $this->attachments[0][2], $this->attachments[0][3] ) );
411 }
412 }
413 else
414 {
415 $mainPart = new ezcMailMultipartMixed( new ezcMailFile( $this->attachments[0][0], $this->attachments[0][2], $this->attachments[0][3] ) );
416 }
417 $mainPart->contentDisposition = $this->attachments[0][4];
418 }
419 else if ( count( $this->attachments ) > 0 )
420 {
421 $mainPart = ( $mainPart == false )
422 ? new ezcMailMultipartMixed()
423 : new ezcMailMultipartMixed( $mainPart );
424
425 // add the attachments to the mixed part
426 foreach ( $this->attachments as $attachment )
427 {
428 if ( isset( $attachment[1] ) )
429 {
430 if ( is_resource( $attachment[1] ) )
431 {
432 $part = new ezcMailStreamFile( $attachment[0], $attachment[1], $attachment[2], $attachment[3] );
433 }
434 else
435 {
436 $part = new ezcMailVirtualFile( $attachment[0], $attachment[1], $attachment[2], $attachment[3] );
437 }
438 }
439 else
440 {
441 $part = new ezcMailFile( $attachment[0], $attachment[2], $attachment[3] );
442 }
443 $part->contentDisposition = $attachment[4];
444 $mainPart->appendPart( $part );
445 }
446 }
447
448 $this->body = $mainPart;
449 }
450
451 /**
452 * Returns an ezcMailPart based on the HTML provided.
453 *
454 * This method adds local files/images to the mail itself using a
455 * {@link ezcMailMultipartRelated} object.
456 *
457 * @throws ezcBaseFileNotFoundException
458 * if $fileName does not exists.
459 * @throws ezcBaseFilePermissionProblem
460 * if $fileName could not be read.
461 * @return ezcMailPart
462 */
463 private function generateHtmlPart()
464 {
465 $result = false;
466 if ( $this->htmlText != '' )
467 {
468 $matches = array();
469 if ( $this->options->automaticImageInclude === true )
470 {
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] );
475 }
476
477 $result = new ezcMailText( $this->htmlText, $this->charset, $this->encoding );
478 $result->subType = "html";
479 if ( count( $matches ) > 0 )
480 {
481 $htmlPart = $result;
482 // wrap already existing message in an alternative part
483 $result = new ezcMailMultipartRelated( $result );
484
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 )
489 {
490 if ( is_readable( $fileName ) )
491 {
492 // @todo waiting for fix of the fileinfo extension
493 // $contents = file_get_contents( $fileName );
494 $mimeType = null;
495 $contentType = null;
496 if ( ezcBaseFeatures::hasExtensionSupport( 'fileinfo' ) )
497 {
498 // if fileinfo extension is available
499 $filePart = new ezcMailFile( $fileName );
500 }
501 elseif ( ezcMailTools::guessContentType( $fileName, $contentType, $mimeType ) )
502 {
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 );
506 }
507 else
508 {
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" );
512 }
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 );
516 }
517 else
518 {
519 if ( file_exists( $fileName ) )
520 {
521 throw new ezcBaseFilePermissionException( $fileName, ezcBaseFileException::READ );
522 }
523 else
524 {
525 throw new ezcBaseFileNotFoundException( $fileName );
526 }
527 // throw
528 }
529 }
530 // update mail, with replaced URLs
531 $htmlPart->text = $this->htmlText;
532 }
533 }
534 return $result;
535 }
536 }
537 ?>