root/trunk/includes/class-easymail.php

Download in other formats: Raw | Text
Revisions
Jonathan Gotti
malko
Aug 17 2011 * 18:33
(6 months ago)

Revision 430

- bug correction in clean_header which may strip spaces between specials chars

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
<?php
/**
* Easy text/HTML Mail with file attachement (possible inline HTML attachement)
* @package MAIL
* @since 2005-08-29
* @licence General Public Licence
* @todo allow extended mail address form (user display name <useradress@domain.com>)
* @todo make some modification on set_address_header to better handle multiple values
* @changelog
* - 2011-08-17 - bug correction in clean_header which may strip spaces between specials chars
* - 2011-07-06 - bug correction in check_address that reject some valid adresses (1 chars third or more level domain part )
* - 2011-04-14 - bug correction in check_address that reject some valid adresses (1 chars local or domain part, xn-- TLDs, no more than 63 chars in domain part (this last one was buggy) )
*              - change version numbering to majorRelease-dateLastModification
* - 2011-01-14 - bug correction in check_address that reject some valid adresses (2 chars local or domain part)
* - 2010-03-25 - add dontSend parameter to mailTpl() method
* - 2009-01-28 - better automated html to plain-text conversion
* - 2008-09-15 - clean_header() now replace words instead of whole string (was problematic with string that contains '?')
* - 2008-08-26 - add static property $xmailer
* - 2008-08-25 - better html to plain text transformation when setting body type as both
* - 2008-03-11 - correct a bug in quoted printable when using mbstring.func_overload with MB_OVERLOAD_STRING and UTF8
* - 2008-03-07 - clean header will now encode the whole string instead of each special chars separatly
* - 2008-02-29 - optimisitation in check_adress set regexp only once.
*              - many code cleaning to work properly with static methods in php5
* - 2007-09-26 - new parameter cleanTO on send method
* - 2007-09-18 - add header encoding on clean header
*/

class easymail{

        static public $dfltHeaderCharset = 'UTF-8';
        static public $preferedEncoding  = 'quoted-printable';
        static public $xmailer = "EasyMail 1-110817";
        #- ~ static public $preferedEncoding  = '7bit';

        function easymail($TO=null,$SUBJECT=null){
                if(!is_null($TO))
                        $this->to($TO);
                if(! is_null($SUBJECT) )
                        $this->subject($SUBJECT);
                $this->parts_encoding = 'base64';
                self::set_header($this->headers,'MIME-Version','1.0');
                if( self::$xmailer )
                        self::set_header($this->headers,'X-Mailer',self::$xmailer);
                $this->boundary = '--main'.md5(uniqid(rand(),true));
        }

        /**
        * remove CR/LF to avoid mail injection.
        * it also encode header when required according to RFC2047 (inspired from htmlMimeMail)
        * @private
        * @param string $headerval the value of the header field
        * @param string $charset  the charset encoding used for the header content (default to $dfltHeaderCharset)
        * @return string
        */
        static public function clean_header($headerval, $charset=null){
                if(is_null($charset))
                        $charset = self::$dfltHeaderCharset;
                $headerval = preg_replace("![\r\n]!",'',$headerval);
                if( preg_match('/[\x80-\xFF]+/', $headerval) ){
                        preg_match_all('/(\w*[\x80-\xFF]+\w*)/', $headerval, $match);
                        foreach ($match[1] as $word) {
                                $replacement = preg_replace('/([\x80-\xFF])/e', '"=".strtoupper(dechex(ord("\1")))', $word);
                                $headerval   = str_replace($word, "=?$charset?Q?$replacement?=", $headerval);
                        }
                        $headerval = preg_replace('/(=\?[A-Za-z0-9_-]+\?Q\?)(.*?)\?=(\s*)\1/','\1\2\3',$headerval);
                }
                return $headerval;
        }

        /**
        * simple check of the email validity based on rfc3696 rules
        * @private
        * @param string $email
        * @return string
        */
        static public function check_address($email){
                static $exp;
                if( ! isset($exp) ){
                        $quotable       = '@,"\[\]\\x5c\\x00-\\x20\\x7f-\\xff';
                        $local_quoted   = '"(?:[^"]|(?<=\\x5c)"){1,62}"';
                        $local_unquoted =  '(?:(?:[^'.$quotable.'\.]|\\x5c(?=['.$quotable.']))'
                                                                                         .'(?:[^'.$quotable.'\.]|(?<=\\x5c)['.$quotable.']|\\x5c(?=['.$quotable.'])|\.(?=[^\.])){1,62}'
                                                                                         .'(?:[^'.$quotable.'\.]|(?<=\\x5c)['.$quotable.'])|[^'.$quotable.'\.]{1,2})';
                        $local          = '('.$local_unquoted.'|'.$local_quoted.')';

                        $_0_255         = '(?:[0-1]?\d?\d|2[0-4]\d|25[0-5])';
                        $domain_ip      = '\['.$_0_255.'(?:\.'.$_0_255.'){3}\]';
                        $domain_name    = '(?!.{64})(?:[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.?|[a-zA-Z0-9]\.?)+\.(?:xn--[a-zA-Z0-9]+|[a-zA-Z]{2,6})';

                        $exp = "/^(?:$local_unquoted|$local_quoted)@(?:$domain_name|$domain_ip)$/";
                }
                return (preg_match($exp,$email)?true:false);
        }
        /**
        * Set the mail subject
        * @param string $SUBJECT the subject
        * @param string $charset header encoding if required (default to $dfltHeaderCharset)
        */
        function subject($SUBJECT,$charset=null){
                return $this->_subject = self::clean_header($SUBJECT,$charset);
        }
        /**
        * Add 'TO' recipient to the mail
        * @param mixed $TO string or array of recipient(s) mail adress
        */
        function to($TO){
                if(is_array($TO)){
                        foreach($TO as $mail){
                                if(easymail::check_address($mail))
                                        $this->_to[] = $mail;
                                else
                                        return FALSE;
                        }
                        return TRUE;
                }elseif(easymail::check_address($TO)){
                        $this->_to[] = $TO;
                        return TRUE;
                }
                return FALSE;
        }
        /**
        * Add 'CC' recipient to the mail
        * @param mixed $CC string or array of recipient(s) mail adress
        */
        function cc($CC){
                return $this->set_address_header('Cc',$CC);
        }
        /**
        * Add 'BCC' recipient to the mail
        * @param mixed $BCC string or array of recipient(s) mail adress
        */
        function bcc($BCC){
                return $this->set_address_header('Bcc',$BCC);
        }
        /**
        * Set the FROM adress
        * @param string $FROM
        */
        function from($FROM){
                if(preg_match('!\s*([^<]+)?<\s*([^@]+@[^>]+)\s*>\s*$!',$FROM,$m)){
                        if(! self::check_address($m[2]) )
                                return FALSE;
                        $FROM = self::clean_header($m[1])."<$m[2]>";
                }elseif(! self::check_address($FROM)){
                        return FALSE;
                }
                # if(! isset($this->headers['return-path'])) # ensure return path
                        # self::set_header($this->headers,'Return-Path',$FROM);
                return self::set_header($this->headers,'From',$FROM);
        }

        /**
        * set the RETURN-PATH adress
        * @param $RETURN-PATH
        */
        function return_path($RETURN_PATH){
                if(! self::check_address($RETURN_PATH))
                        return FALSE;
                return self::set_header($this->headers,'Return-Path',$RETURN_PATH);
        }

        /**
        * set a mail header containing mail address
        * @param string $field header fieldname
        * @param mixed $adresses header value (string or array)
        * @return bool
        * @private
        */
        function set_address_header($field,$value){
                if(is_array($value)){
                        foreach($value as $mail){
                                if(self::check_address($mail))
                                        self::set_header($this->headers,$field,$mail,TRUE);
                                else
                                        return FALSE;
                        }
                        return TRUE;
                }elseif(self::check_address($value)){
                        return self::set_header($this->headers,$field,$value);
                }
                return FALSE;
 }
        /**
        * set an header field
        * @param array $headers this can be used to passed another array to work on instead $this->headers
        *              (note that the given array will be modified as it is passed by reference)
        * @param string $field header fieldname
        * @param string $value header value
        * @param bool $append if set to true will append to an existing header
        * @return string
        */
        static public function set_header(&$headers,$field,$value,$append=FALSE,$charset=null){
                $fieldname = strtolower($field);
                if((! isset($headers[$fieldname]) || (! $append) ) )
                        $headers[$fieldname] = self::clean_header("$field: $value",$charset);
                else
                        $headers[$fieldname] .= "\n\t".self::clean_header($value,$charset);
                return $headers[$fieldname];
        }

        /**
        * Add a file attachment to the mail
        * @param string $file filepath
        * @param bool $inline set to true to attach file as inline part
        * @param string $ctype default is 'application/octet-stream' you can set it to null to force mime_content_type() detection (not recommended but possible)
        * @param string $name set the part name manualluy instead of file basename.
        * @return string part name
        */
        function attach($file,$inline=FALSE,$ctype='application/octet-stream',$name=null){
                if(! file_exists($file)) return FALSE;
                $datas = array(); # must do this to use anonymous method set_header()
                $datas['name'] = is_null($name)?basename($file):$name;
                self::set_header(
                        $datas,'Content-Type',
                        ((is_null($ctype) && function_exists('mime_content_type'))?mime_content_type($file):$ctype).';'
                );
                self::set_header($datas,'Content-Type',"name=\"$datas[name]\"",TRUE);
                self::set_header($datas,'Content-Transfer-Encoding',$this->parts_encoding,FALSE);
                self::set_header($datas,'Content-Disposition',($inline?'inline':'attachment').';',FALSE);
                self::set_header($datas,'Content-Disposition',"filename=\"$datas[name]\"",TRUE);
                if($inline){
                        $datas['cid'] = md5($datas['name'].uniqid(rand()));
                        self::set_header($datas,'Content-ID',"<$datas[cid]>",FALSE);
                }
                if(!($f = fopen($file,'r')) )
                        return FALSE;
                #@todo correctly handle encoding regarding the parts_encoding property
                $datas['datas'] = chunk_split(base64_encode(fread($f,filesize($file))),76,"\n");
                fclose($f);

                $this->parts[($inline?'inline':'attachment')][] = $datas;
                return $inline?$datas['cid']:$datas['name'];
        }
        /**
        * prepare the part of an attached file for message inclusion
        * @private Be Aware that the part parameter order is important to get this method work!
        * @param array $part array as $datas set in attach
        * @param string $boundary set to an alternate boundary (hmmm not sure that's usefull to you a day)
        * @return string
        */
        function get_part($part=null,$boundary=null){
                if(! is_array($part))
                        return FALSE;
                $datas = $part['datas'];
                unset($part['name'],$part['cid'],$part['datas']);
                return '--'.(is_null($boundary)?$this->boundary:$boundary)."\n".implode("\n",$part)."\n\n$datas\n";
        }
        /**
        * set the message body
        * @param string $body  the message content
        * @param string $ctype the message type plain/html
        * @param bool   $copyasplain set it to true to copy the html part to a plain one
        * @param string $charset
        */
        function body($body,$ctype='plain',$copyasplain=FALSE,$charset=null){
                if( is_null($charset) )
                        $charset = self::$dfltHeaderCharset;
                $ctype = strtolower($ctype);
                $body.="\n";
                if($ctype === 'html'){
                        $this->msg_html = array();
                        self::set_header($this->msg_html,'Content-Type',"text/html; charset=$charset",FALSE,$charset);
                        if(self::$preferedEncoding==='quoted-printable'){
                                easymail::set_header($this->msg_html,'Content-Transfer-Encoding','quoted-printable',FALSE,$charset);
                                $this->msg_html['datas'] = $this->quoted_printable_encode($body);
                        }else{
                                self::set_header($this->msg_html,'Content-Transfer-Encoding',self::$preferedEncoding,FALSE,$charset);
                                $this->msg_html['datas'] = $body;
                        }

                        if($copyasplain){
                                $ctype = 'plain';
                                # replace image with their alt atribute and minimize blank space between tags
                                $body  = preg_replace(
                                        array(
                                                '!<img[^>]+? alt=([\'"])?((?(1).*?|\S*))(?(1)\1).*?'.'>!si', #- remplace les images par leur balises alt (si présente)
                                                '!(/?>)\s*(</?\w+)!', #- assure un espace entre chaque balise html
                                                '!<a[^>]+?href=([\'"])?((?(1).*?(?=\1)|\S*)).*?'.'>(.*?)</a>!sie', # remplace les liens par leur adresses html
                                        ),
                                        array(
                                                '\\2',
                                                '\\1 \\2',
                                                '("\\3"==="\\2")?"\\2":"\\3 \\2"',
                                        ),
                                        $body
                                );
                                $body  = strip_tags($body,'<br><p><tr>');
                                # get plain text from body (next 5 lines originaly came from php documentation of html_entity_decode)
                                $body   = preg_replace(array('!&#x([0-9a-f]+);!ei','!&#([0-9]+);!e','!  +!'), array('chr(hexdec("\\1"))','chr(\\1)',' '), $body);
                                $trans_tbl = get_html_translation_table (HTML_ENTITIES);
                                $trans_tbl = array_flip ($trans_tbl);
                                if(strtoupper($charset)==='UTF-8')
                                        $trans_tbl = array_map('utf8_encode',$trans_tbl);
                                $body = strtr ($body, $trans_tbl);
                                $body = trim(preg_replace(array("/<(br|\/?(p|tr))(?![a-z0-9])[^>]*?".">/i",'!^[ \t]+|[ \t]+$!m',"!(\r?\n){2,}!"),array("\n",'',"\n\n"),$body));
                        }
                }
                if(in_array($ctype,array('plain','txt','text'))){ # in array used only for user facility
                        $this->msg_plain = array();
                        self::set_header($this->msg_plain,'Content-Type',"text/plain; charset=$charset;",FALSE,$charset);
                        self::set_header($this->msg_plain,'Content-Type',"format=flowed",TRUE,$charset);
                        if(self::$preferedEncoding==='quoted-printable'){
                                self::set_header($this->msg_plain,'Content-Transfer-Encoding','quoted-printable',FALSE,$charset);
                                $this->msg_plain['datas'] = $this->quoted_printable_encode($body);
                        }else{
                                self::set_header($this->msg_plain,'Content-Transfer-Encoding',self::$preferedEncoding,FALSE,$charset);
                                $this->msg_plain['datas'] = $body;
                        }
                }
        }
        /**
        * function by Allan Hansen contributed to HTML Mime Mail class from Richard Heyes <richard.heyes@heyes-computing.net>
        *
        */
        function quoted_printable_encode($input , $line_max = 76){
                $lines  = preg_split("/(?:\r\n|\r|\n)/", $input);
                $eol    = "\n";
                $escape = '=';
                $output = '';
                $strlenFunc = 'strlen';
                if( defined('MB_OVERLOAD_STRING') ){
                        $mbOverLoading = ini_get('mbstring.func_overload');
                        if( ($mbOverLoading & MB_OVERLOAD_STRING) === MB_OVERLOAD_STRING){
                                $strlenFunc = create_function('$str','return mb_strlen($str,"iso-8859-1");');
                        }
                }

                while(list(, $line) = each($lines)){
                        $linlen  = strlen($line);
                        $newline = '';
                        for($i = 0; $i < $linlen; $i++){
                                $char = substr($line, $i, 1);
                                $dec  = ord($char);
                                if(($dec == 32) AND ($i == ($linlen - 1))){          // convert space at eol only
                                        $char = '=20';
                                }elseif($dec == 9){
                                        ;                                                  // Do nothing if a tab.
                                }elseif(($dec == 61) OR ($dec < 32 ) OR ($dec > 126)){
                                        if($strlenFunc!=='strlen' && ($clen=$strlenFunc($char)) > 1){ #- special case when using mbstring.func_overload with utf8 encoding
                                                $_char = $char;
                                                $char = '';
                                                for($y=0;$y<$clen;$y++)
                                                        $char .= $escape.strtoupper(sprintf('%02X', ord($_char{$y})));
                                        }else{
                                                $char = $escape.strtoupper(sprintf('%02X', $dec));
                                        }
                                }
                                if((strlen($newline) + strlen($char)) >= $line_max){ // CRLF is not counted
                                        $output  .= $newline.$escape.$eol;                 // soft line break; " =\r\n" is okay
                                        $newline  = '';
                                }
                                $newline .= $char;
                        }                                                      // end of for
                        $output .= $newline.$eol;
                }
                return $output;
        }

 /**
 * prepare the mail header before sending.
 * generally only used as an internal method
 * @param array $headers key/value pair of headers. If given then only thoose headers will be returned (used for parts headers)
 *                            if null then the mail headers will be returned
 */
        function make_header($headers=null){
                if(is_null($headers))
                        $headers = &$this->headers;
                if( @is_array($this->parts['attachment']))
                        $ctype = 'mixed';
                elseif(isset($this->msg_html) && isset($this->msg_plain))
                        $ctype = 'alternative';
                elseif(isset($this->parts['inline']))
                        $ctype = 'related';
                else
                        $ctype = (isset($this->msg_html)?'html':'plain');

                # set the mime type
                self::set_header($headers,'MIME-Version','1.0',false);
                if( in_array($ctype,array('mixed','alternative','related')) ){
                        self::set_header($headers,'Content-Type',"multipart/$ctype;",false);
                        self::set_header($headers,'Content-Type',"boundary=$this->boundary",true);
                }else{
                        self::set_header($headers,'Content-Type','text/'.$ctype.'; charset="'.self::$dfltHeaderCharset.'"',false);
                        self::set_header($headers,'Content-Transfer-Encoding',self::$preferedEncoding,false);
                        # self::set_header($headers,'Content-Transfer-Encoding','8bit',false);
                }
                if(! is_array($headers)) # no reason 4 this 2 happen or there's some really weird thing in this world
                        return FALSE;

                return  implode("\n",$headers)."\n";
        }

        /**
        * send the mail accordingly to previously defined recipient(s) or/and the optionnaly given one
        * @param string $SUBJECT  optionnal subject
        * @param string $TO       optionnal email adress to send the mail to (must be passed if you haven't use easymail::to() before)
        * @param bool   $cleanTO  if set to true then empty the $_to array before adding new recipients.
        * @return bool
        */
        function send($SUBJECT=null,$TO=null,$cleanTO=false){
                if($cleanTO)
                        $this->_to = array();
                if(!is_null($TO))
                        $this->to($TO);
                if(! isset($this->_to[0]))
                        return FALSE; # we need one valid TO address at least
                $To = implode(",",$this->_to);
                # The subject part
                if(! is_null($SUBJECT) )
                        $this->subject($SUBJECT);

                # then the message part
                $headers = $this->make_header();

                $_alternative   = ( (isset($this->msg_html) && isset($this->msg_plain) )? TRUE:FALSE );
                $is_multipart   = (@is_array($this->parts) || $_alternative )?TRUE:FALSE;
                $_inline_parts  = isset($this->parts['inline']);
                $msg = ($is_multipart?"This is a multi-part message in MIME format.\n\n":'');
                # first check for additional multipart message headers
                if( (!empty($this->parts['attachment'])) && ($_alternative || $_inline_parts)){ # we need some extra header to encapsulate the message
                        $msg_type  = ($_alternative?'multipart/alternative':($_inline_parts?'multipart/related':'text'));
                        $msg .= "--$this->boundary\nContent-Type: ";
                        if($msg_type !== 'text'){
                                $msg_bound = '--msg'.md5(uniqid(rand(),true));
                                $msg .= "$msg_type;\n\tboundary=$msg_bound\n\n";
                        }else{
                                if( isset($this->msg_html) ){
                                        $msg.= $this->msg_html['content-type']."\nContent-Transfer-Encoding: ".$this->msg_html['content-transfer-encoding']."\n\n";
                                }else{
                                        $msg.= $this->msg_plain['content-type']."\nContent-Transfer-Encoding: ".$this->msg_plain['content-transfer-encoding']."\n\n";
                                }
                        }
                }
                # set message part boundary
                $msg_bound = (isset($msg_bound)?$msg_bound:$this->boundary);

                # add the plain text msg
                if(isset($this->msg_plain)){
                        $msg .= ($is_multipart?$this->get_part($this->msg_plain,$msg_bound):$this->msg_plain['datas']."\n");
                }

                # then the html part
                if( isset($this->msg_html)){
                        if( $_alternative && isset($this->parts['inline'])){ # we need some extra headers for related inline parts
                                $rel_bound = '--rel'.md5(uniqid(rand(),true));
                                $msg .= "--$msg_bound\nContent-Type: multipart/related;\n\tboundary=$rel_bound\n\n";
                        }
                        $msg .= ($is_multipart?$this->get_part($this->msg_html,(isset($rel_bound)?$rel_bound:$msg_bound)):$this->msg_html['datas']."\n");
                }

                # then inline parts
                if(isset($this->parts['inline'])){
                        foreach($this->parts['inline'] as $part)
                                $msg .= $this->get_part($part,(isset($rel_bound)?$rel_bound:$msg_bound));
                }

                if(isset($rel_bound)) $msg .= "--$rel_bound--\n\n";
                if(isset($msg_bound) && $msg_bound != $this->boundary) $msg .= "--$msg_bound--\n\n";

                # then the attachment parts
                if(isset($this->parts['attachment'])){
                        foreach($this->parts['attachment'] as $part)
                                $msg .= $this->get_part($part,$this->boundary);
                }

                if($is_multipart)
                        $msg .= "--$this->boundary--\n";

                return mail($To,$this->_subject,$msg,$headers);
        }

        /**
        * send a template email after a replacement inside the mail
        * @param string $to        a single mail adress
        * @param string $subject   mail subject
        * @param string $doc       the path to the mail template or the template as a string itself
        * @param string $from      mail adress to use as sender adress
        * @param array  $datas     list of keys/values pair of replacement to make inside the template.
        * @param string $type      one of html|plain|both
        * @param bool   $dontSend  if true then mail won't be send and easymail object will be returned
        * @return bool or easymail if $dontSend is true and all is ok
        */
        static public function mailTpl($to,$subject,$doc,$from=null,array $datas=null,$type='html',$dontSend=false){
                static $prepareKeys;
                if(! isset($prepareKeys))
                        $prepareKeys = create_function('$e','return "---$e---";');

                //test du document
                if(strlen($doc) <255 && is_file($doc))
                        $doc = file_get_contents($doc);
                if( !empty($datas)){
                        $keys   = array_map($prepareKeys, array_keys($datas));
                        $values = array_values($datas);
                        $doc   = str_replace($keys,$values,$doc);
                }
                $easymail = new easymail();
                if(null!==$from && ! $easymail->from($from) )
                        return false;

                $easymail->body(
                        $doc,
                        ($type!=='plain'?'html':'plain'),
                        ($type==='both'?true:false),
                        self::$dfltHeaderCharset
                );
                if(! $dontSend )
                        return $easymail->send($subject,$to);
                $easymail->subject($subject);
                $easymail->to($to);
                return $easymail;
        }

}