Commit | Line | Data |
---|---|---|
7f254ad8 AE |
1 | <?php |
2 | /** | |
3 | * File containing the ezcMail 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 | * The main mail class. | |
13 | * | |
14 | * You can use ezcMail together with the other classes derived from ezcMailPart | |
15 | * to build email messages. When the mail is built, use the Transport classes | |
16 | * to send the mail. | |
17 | * | |
18 | * This example builds and sends a simple text mail message: | |
19 | * <code> | |
20 | * $mail = new ezcMail; | |
21 | * $mail->from = new ezcMailAddress( 'sender@example.com', 'Adrian Ripburger' ); | |
22 | * $mail->addTo( new ezcMailAddress( 'receiver@example.com', 'Maureen Corley' ) ); | |
23 | * $mail->subject = "Hi"; | |
24 | * $mail->body = new ezcMailText( "I just mail to say I love you!" ); | |
25 | * $transport = new ezcMailMtaTransport(); | |
26 | * $transport->send( $mail ); | |
27 | * </code> | |
28 | * | |
29 | * You can also derive your own mail classes from this class if you have | |
30 | * special requirements. An example of this is the ezcMailComposer class which | |
31 | * is a convenience class to send simple mail structures and HTML mail. | |
32 | * | |
33 | * There are several headers you can set on the mail object to achieve various | |
34 | * effects: | |
35 | * - Reply-To - Set this to an email address if you want people to reply to an | |
36 | * address other than the from address. | |
37 | * - Errors-To - If the mail can not be delivered the error message will be | |
38 | * sent to this address. | |
39 | * | |
40 | * @property ezcMailAddress $from Contains the from address as an | |
41 | * ezcMailAddress object. | |
42 | * @property array(ezcMailAddress) $to Contains an array of ezcMailAddress objects. | |
43 | * @property array(ezcMailAddress) $cc Contains an array of ezcMailAddress objects. | |
44 | * @property array(ezcMailAddress) $bcc Contains an array of ezcMailAddress objects. | |
45 | * @property string $subject | |
46 | * Contains the subject of the e-mail. | |
47 | * Use setSubject if you require a | |
48 | * special encoding. | |
49 | * @property string $subjectCharset | |
50 | * The encoding of the subject. | |
51 | * @property ezcMailPart $body The body part of the message. | |
52 | * | |
53 | * @property-read string $messageId | |
54 | * The message ID of the message. Treat | |
55 | * as read-only unless you're 100% sure | |
56 | * what you're doing. Also accessible through | |
57 | * the deprecated property messageID. | |
58 | * @property-read integer $timestamp | |
59 | * The date/time of when the message was | |
60 | * sent as Unix Timestamp. | |
61 | * @property ezcMailAddress $returnPath Contains the Return-Path address as an | |
62 | * ezcMailAddress object. | |
63 | * | |
64 | * @apichange Remove the support for the deprecated property messageID. | |
65 | * | |
66 | * @package Mail | |
67 | * @version 1.7beta1 | |
68 | * @mainclass | |
69 | */ | |
70 | class ezcMail extends ezcMailPart | |
71 | { | |
72 | /** | |
73 | * 7 bit encoding. | |
74 | */ | |
75 | const SEVEN_BIT = "7bit"; | |
76 | ||
77 | /** | |
78 | * 8 bit encoding. | |
79 | */ | |
80 | const EIGHT_BIT = "8bit"; | |
81 | ||
82 | /** | |
83 | * Binary encoding. | |
84 | */ | |
85 | const BINARY = "binary"; | |
86 | ||
87 | /** | |
88 | * Quoted printable encoding. | |
89 | */ | |
90 | const QUOTED_PRINTABLE = "quoted-printable"; | |
91 | ||
92 | /** | |
93 | * Base 64 encoding. | |
94 | */ | |
95 | const BASE64 = "base64"; | |
96 | ||
97 | /** | |
98 | * Constructs an empty ezcMail object. | |
99 | */ | |
100 | public function __construct() | |
101 | { | |
102 | parent::__construct(); | |
103 | ||
104 | $this->properties['from'] = null; | |
105 | $this->properties['to'] = array(); | |
106 | $this->properties['cc'] = array(); | |
107 | $this->properties['bcc'] = array(); | |
108 | $this->properties['subject'] = null; | |
109 | $this->properties['subjectCharset'] = 'us-ascii'; | |
110 | $this->properties['body'] = null; | |
111 | $this->properties['messageId'] = null; | |
112 | $this->properties['returnPath'] = null; | |
113 | } | |
114 | ||
115 | /** | |
116 | * Sets the property $name to $value. | |
117 | * | |
118 | * @throws ezcBasePropertyNotFoundException | |
119 | * if the property does not exist. | |
120 | * @throws ezcBasePropertyPermissionException | |
121 | * if the property is read-only. | |
122 | * @param string $name | |
123 | * @param mixed $value | |
124 | * @ignore | |
125 | */ | |
126 | public function __set( $name, $value ) | |
127 | { | |
128 | switch ( $name ) | |
129 | { | |
130 | case 'from': | |
131 | case 'returnPath': | |
132 | if ( $value !== null && !$value instanceof ezcMailAddress ) | |
133 | { | |
134 | throw new ezcBaseValueException( $name, $value, 'ezcMailAddress or null' ); | |
135 | } | |
136 | $this->properties[$name] = $value; | |
137 | break; | |
138 | ||
139 | case 'to': | |
140 | case 'cc': | |
141 | case 'bcc': | |
142 | if ( !is_array( $value ) ) | |
143 | { | |
144 | throw new ezcBaseValueException( $name, $value, 'array( ezcMailAddress )' ); | |
145 | } | |
146 | foreach ( $value as $key => $obj ) | |
147 | { | |
148 | if ( !$obj instanceof ezcMailAddress ) | |
149 | { | |
150 | throw new ezcBaseValueException( "{$name}[{$key}]", $obj, 'ezcMailAddress' ); | |
151 | } | |
152 | } | |
153 | $this->properties[$name] = $value; | |
154 | break; | |
155 | ||
156 | case 'subject': | |
157 | $this->properties['subject'] = trim( $value ); | |
158 | break; | |
159 | ||
160 | case 'subjectCharset': | |
161 | $this->properties['subjectCharset'] = $value; | |
162 | break; | |
163 | ||
164 | case 'body': | |
165 | if ( !$value instanceof ezcMailPart ) | |
166 | { | |
167 | throw new ezcBaseValueException( $name, $value, 'ezcMailPart' ); | |
168 | } | |
169 | $this->properties['body'] = $value; | |
170 | break; | |
171 | ||
172 | case 'messageId': | |
173 | case 'messageID': | |
174 | $this->properties['messageId'] = $value; | |
175 | break; | |
176 | ||
177 | case 'timestamp': | |
178 | throw new ezcBasePropertyPermissionException( $name, ezcBasePropertyPermissionException::READ ); | |
179 | break; | |
180 | ||
181 | default: | |
182 | parent::__set( $name, $value ); | |
183 | break; | |
184 | } | |
185 | } | |
186 | ||
187 | /** | |
188 | * Returns the property $name. | |
189 | * | |
190 | * @throws ezcBasePropertyNotFoundException | |
191 | * if the property does not exist. | |
192 | * @param string $name | |
193 | * @return mixed | |
194 | * @ignore | |
195 | */ | |
196 | public function __get( $name ) | |
197 | { | |
198 | switch ( $name ) | |
199 | { | |
200 | case 'to': | |
201 | case 'cc': | |
202 | case 'bcc': | |
203 | return (array) $this->properties[$name]; | |
204 | ||
205 | case 'from': | |
206 | case 'subject': | |
207 | case 'subjectCharset': | |
208 | case 'body': | |
209 | case 'messageId': | |
210 | case 'returnPath': | |
211 | return $this->properties[$name]; | |
212 | ||
213 | case 'messageID': // deprecated version | |
214 | return $this->properties['messageId']; | |
215 | ||
216 | case 'timestamp': | |
217 | return strtotime( $this->getHeader( "Date" ) ); | |
218 | ||
219 | default: | |
220 | return parent::__get( $name ); | |
221 | } | |
222 | } | |
223 | ||
224 | /** | |
225 | * Returns true if the property $name is set, otherwise false. | |
226 | * | |
227 | * @param string $name | |
228 | * @return bool | |
229 | * @ignore | |
230 | */ | |
231 | public function __isset( $name ) | |
232 | { | |
233 | switch ( $name ) | |
234 | { | |
235 | case 'to': | |
236 | case 'cc': | |
237 | case 'bcc': | |
238 | case 'from': | |
239 | case 'subject': | |
240 | case 'subjectCharset': | |
241 | case 'body': | |
242 | case 'messageId': | |
243 | case 'returnPath': | |
244 | return isset( $this->properties[$name] ); | |
245 | ||
246 | case 'messageID': // deprecated version | |
247 | return isset( $this->properties['messageId'] ); | |
248 | ||
249 | case 'timestamp': | |
250 | return $this->getHeader( "Date" ) != null; | |
251 | ||
252 | default: | |
253 | return parent::__isset( $name ); | |
254 | } | |
255 | } | |
256 | ||
257 | /** | |
258 | * Adds the ezcMailAddress $address to the list of 'to' recipients. | |
259 | * | |
260 | * @param ezcMailAddress $address | |
261 | */ | |
262 | public function addTo( ezcMailAddress $address ) | |
263 | { | |
264 | $this->properties['to'][] = $address; | |
265 | } | |
266 | ||
267 | /** | |
268 | * Adds the ezcMailAddress $address to the list of 'cc' recipients. | |
269 | * | |
270 | * @param ezcMailAddress $address | |
271 | */ | |
272 | public function addCc( ezcMailAddress $address ) | |
273 | { | |
274 | $this->properties['cc'][] = $address; | |
275 | } | |
276 | ||
277 | /** | |
278 | * Adds the ezcMailAddress $address to the list of 'bcc' recipients. | |
279 | * | |
280 | * @param ezcMailAddress $address | |
281 | */ | |
282 | public function addBcc( ezcMailAddress $address ) | |
283 | { | |
284 | $this->properties['bcc'][] = $address; | |
285 | } | |
286 | ||
287 | /** | |
288 | * Returns the generated body part of this mail. | |
289 | * | |
290 | * Returns an empty string if no body has been set. | |
291 | * | |
292 | * @return string | |
293 | */ | |
294 | public function generateBody() | |
295 | { | |
296 | if ( is_subclass_of( $this->body, 'ezcMailPart' ) ) | |
297 | { | |
298 | return $this->body->generateBody(); | |
299 | } | |
300 | return ''; | |
301 | } | |
302 | ||
303 | /** | |
304 | * Returns the generated headers for the mail. | |
305 | * | |
306 | * This method is called automatically when the mail message is built. | |
307 | * You can re-implement this method in subclasses if you wish to set | |
308 | * different mail headers than ezcMail. | |
309 | * | |
310 | * @return string | |
311 | */ | |
312 | public function generateHeaders() | |
313 | { | |
314 | // set our headers first. | |
315 | if ( $this->from !== null ) | |
316 | { | |
317 | $this->setHeader( "From", ezcMailTools::composeEmailAddress( $this->from ) ); | |
318 | } | |
319 | ||
320 | if ( $this->to !== null ) | |
321 | { | |
322 | $this->setHeader( "To", ezcMailTools::composeEmailAddresses( $this->to ) ); | |
323 | } | |
324 | if ( count( $this->cc ) ) | |
325 | { | |
326 | $this->setHeader( "Cc", ezcMailTools::composeEmailAddresses( $this->cc ) ); | |
327 | } | |
328 | if ( count( $this->bcc ) ) | |
329 | { | |
330 | $this->setHeader( "Bcc", ezcMailTools::composeEmailAddresses( $this->bcc ) ); | |
331 | } | |
332 | ||
333 | $this->setHeader( 'Subject', $this->subject, $this->subjectCharset ); | |
334 | ||
335 | $this->setHeader( 'MIME-Version', '1.0' ); | |
336 | $this->setHeader( 'User-Agent', 'eZ Components' ); | |
337 | $this->setHeader( 'Date', date( 'r' ) ); | |
338 | $idhost = $this->from != null && $this->from->email != '' ? $this->from->email : 'localhost'; | |
339 | if ( is_null( $this->messageId ) ) | |
340 | { | |
341 | $this->setHeader( 'Message-Id', '<' . ezcMailTools::generateMessageId( $idhost ) . '>' ); | |
342 | } | |
343 | else | |
344 | { | |
345 | $this->setHeader( 'Message-Id', $this->messageID ); | |
346 | } | |
347 | ||
348 | // if we have a body part, include the headers of the body | |
349 | if ( is_subclass_of( $this->body, "ezcMailPart" ) ) | |
350 | { | |
351 | return parent::generateHeaders() . $this->body->generateHeaders(); | |
352 | } | |
353 | return parent::generateHeaders(); | |
354 | } | |
355 | ||
356 | /** | |
357 | * Returns an array of mail parts from the current mail. | |
358 | * | |
359 | * The array returned contains objects of classes: | |
360 | * - ezcMailText | |
361 | * - ezcMailFile | |
362 | * - ezcMailRfc822Digest | |
363 | * If the method is called with $includeDigests as true, then the returned | |
364 | * array will not contain ezcMailRfc822Digest objects, but instead the mail | |
365 | * parts inside the digests. | |
366 | * The parameter $filter can be used to restrict the returned mail parts, | |
367 | * eg. $filter = array( 'ezcMailFile' ) to return only file mail parts. | |
368 | * | |
369 | * A typical use for this function is to get a list of attachments from a mail. | |
370 | * Example: | |
371 | * <code> | |
372 | * // $mail is an ezcMail object | |
373 | * $parts = $mail->fetchParts(); | |
374 | * // after the above line is executed, $parts will contain an array of mail parts objects, | |
375 | * // for example one ezcMailText object ($parts[0]) and two ezcMailRfc822Digest objects ($parts[1] and $parts[2]). | |
376 | * // the ezcMailText object will be used to render the mail text, and the | |
377 | * // other two objects will be displayed as links ("view attachment") | |
378 | * | |
379 | * // when user clicks on one of the two attachments, the parts of that attachment | |
380 | * // must be retrieved in order to render the attached digest: | |
381 | * $subparts = $parts[1]->mail->fetchParts(); | |
382 | * // after the above line is executed, $subparts will contain an array of mail parts objects, | |
383 | * // for example one ezcMailText object and one ezcMailFile object | |
384 | * </code> | |
385 | * | |
386 | * @param array(string) $filter | |
387 | * @param bool $includeDigests | |
388 | * @return array(ezcMailPart) | |
389 | */ | |
390 | public function fetchParts( $filter = null, $includeDigests = false ) | |
391 | { | |
392 | $context = new ezcMailPartWalkContext( array( __CLASS__, 'collectPart' ) ); | |
393 | $context->includeDigests = $includeDigests; | |
394 | $context->filter = $filter; | |
395 | $context->level = 0; | |
396 | $this->walkParts( $context, $this ); | |
397 | return $context->getParts(); | |
398 | } | |
399 | ||
400 | /** | |
401 | * Walks recursively through the mail parts in the specified mail object. | |
402 | * | |
403 | * $context is an object of class ezcMailPartWalkContext, which must contain | |
404 | * a valid callback function name to be applied to all mail parts. You can use | |
405 | * the collectPart() method, or create your own callback function which can | |
406 | * for example save the mail parts to disk or to a database. | |
407 | * | |
408 | * For the properties you can set to the walk context see: {@link ezcMailPartWalkContext} | |
409 | * | |
410 | * Example: | |
411 | * <code> | |
412 | * class App | |
413 | * { | |
414 | * public static function saveMailPart( $context, $mailPart ) | |
415 | * { | |
416 | * // code to save the $mailPart object to disk | |
417 | * } | |
418 | * } | |
419 | * | |
420 | * // use the saveMailPart() function as a callback in walkParts() | |
421 | * // where $mail is an ezcMail object. | |
422 | * $context = new ezcMailPartWalkContext( array( 'App', 'saveMailPart' ) ); | |
423 | * $context->includeDigests = true; // if you want to go through the digests in the mail | |
424 | * $mail->walkParts( $context, $mail ); | |
425 | * </code> | |
426 | * | |
427 | * @param ezcMailPartWalkContext $context | |
428 | * @param ezcMailPart $mail | |
429 | */ | |
430 | public function walkParts( ezcMailPartWalkContext $context, ezcMailPart $mail ) | |
431 | { | |
432 | $className = get_class( $mail ); | |
433 | $context->level++; | |
434 | switch ( $className ) | |
435 | { | |
436 | case 'ezcMail': | |
437 | case 'ezcMailComposer': | |
438 | if ( $mail->body !== null ) | |
439 | { | |
440 | $this->walkParts( $context, $mail->body ); | |
441 | } | |
442 | break; | |
443 | ||
444 | case 'ezcMailMultipartMixed': | |
445 | case 'ezcMailMultipartAlternative': | |
446 | case 'ezcMailMultipartDigest': | |
447 | case 'ezcMailMultipartReport': | |
448 | foreach ( $mail->getParts() as $part ) | |
449 | { | |
450 | $this->walkParts( $context, $part ); | |
451 | } | |
452 | break; | |
453 | ||
454 | case 'ezcMailMultipartRelated': | |
455 | $this->walkParts( $context, $mail->getMainPart() ); | |
456 | foreach ( $mail->getRelatedParts() as $part ) | |
457 | { | |
458 | $this->walkParts( $context, $part ); | |
459 | } | |
460 | break; | |
461 | ||
462 | case 'ezcMailRfc822Digest': | |
463 | if ( $context->includeDigests ) | |
464 | { | |
465 | $this->walkParts( $context, $mail->mail ); | |
466 | } | |
467 | elseif ( empty( $context->filter ) || in_array( $className, $context->filter ) ) | |
468 | { | |
469 | call_user_func( $context->callbackFunction, $context, $mail ); | |
470 | } | |
471 | break; | |
472 | ||
473 | case 'ezcMailText': | |
474 | case 'ezcMailFile': | |
475 | case 'ezcMailDeliveryStatus': | |
476 | if ( empty( $context->filter ) || in_array( $className, $context->filter ) ) | |
477 | { | |
478 | call_user_func( $context->callbackFunction, $context, $mail ); | |
479 | } | |
480 | break; | |
481 | ||
482 | default: | |
483 | // for cases where a custom mail class has been specified with $parser->options->mailClass | |
484 | if ( in_array( 'ezcMail', class_parents( $className ) ) ) | |
485 | { | |
486 | if ( $mail->body !== null ) | |
487 | { | |
488 | $this->walkParts( $context, $mail->body ); | |
489 | } | |
490 | } | |
491 | ||
492 | // for cases where a custom file class has been specified with $parser->options->fileClass | |
493 | if ( in_array( 'ezcMailFile', class_parents( $className ) ) ) | |
494 | { | |
495 | if ( empty( $context->filter ) || in_array( $className, $context->filter ) ) | |
496 | { | |
497 | call_user_func( $context->callbackFunction, $context, $mail ); | |
498 | } | |
499 | } | |
500 | } | |
501 | $context->level--; | |
502 | } | |
503 | ||
504 | /** | |
505 | * Saves $mail in the $context object. | |
506 | * | |
507 | * This function is used as a callback in the fetchParts() method. | |
508 | * | |
509 | * @param ezcMailPartWalkContext $context | |
510 | * @param ezcMailPart $mail | |
511 | */ | |
512 | protected static function collectPart( ezcMailPartWalkContext $context, ezcMailPart $mail ) | |
513 | { | |
514 | $context->appendPart( $mail ); | |
515 | } | |
516 | } | |
517 | ?> |