nyroFwk  0.2
email.class.php
Go to the documentation of this file.
1 <?php
10 class helper_email extends object {
11 
17  protected $attachment = array();
18 
19  protected function afterInit() {
20  $this->parseHtml();
21  }
22 
29  public function to($addr, $add = false) {
30  $this->addr($addr, 'to', $add);
31  }
32 
39  public function cc($addr, $add = false) {
40  $this->addr($addr, 'cc', $add);
41  }
42 
49  public function bcc($addr, $add = false) {
50  $this->addr($addr, 'bcc', $add);
51  }
52 
60  public function addr($addr, $type, $add = false) {
61  $tmp = $this->cfg->get($type);
62  if ($add) {
63  if (!is_array($tmp))
64  $tmp = array($tmp);
65  } else
66  $tmp = array();
67  $tmp[] = $addr;
68  $this->cfg->set($type, $tmp);
69  }
70 
80  public function attach($prm) {
81  if (!is_array($prm))
82  $prm = array('file'=>$prm);
83 
84  if (!array_key_exists('file', $prm))
85  return false;
86 
87  $ret = false;
88  if (file::exists($prm['file'])) {
89  if (!array_key_exists('name', $prm))
90  $prm['name'] = file::name($prm['file']);
91  if (!array_key_exists('type', $prm))
92  $prm['type'] = file::getType($prm['file']);
93  $this->attachment[] = $prm;
94  $ret = true;
95  }
96  return $ret;
97  }
98 
104  public function send() {
105  if (!is_array($this->cfg->to))
106  $this->cfg->to = array($this->cfg->to);
107  if (!is_array($this->cfg->cc))
108  $this->cfg->cc = array($this->cfg->cc);
109  if (!is_array($this->cfg->bcc))
110  $this->cfg->bcc = array($this->cfg->bcc);
111 
112  if (empty($this->cfg->serverName))
113  $this->cfg->serverName = request::get('serverName');
114 
115  $headers = '';
116 
117  $headers.= $this->headerLine('Message-ID', '<'.uniqid().'@'.$this->cfg->serverName.'>');
118  $headers.= $this->headerLine('Date', date("D, d M Y G:i:s O"));
119  $headers.= $this->headerLine('From', $this->formatAddr(array($this->cfg->from, $this->cfg->fromName)));
120 
121  if (empty($this->cfg->replyTo))
122  $this->cfg->replyTo = $this->cfg->from;
123  $headers.= $this->headerLine('Reply-To', $this->formatAddr($this->cfg->replyTo));
124 
125  $headers.= $this->headerLine('Return-Path', $this->cfg->from);
126  $headers.= $this->headerLine('X-Sender', $this->cfg->from);
127  $headers.= $this->headerLine('X-Priority', $this->cfg->priority);
128  $headers.= $this->headerLine('X-Mailer', $this->cfg->xMailer);
129 
130  if (!empty($this->cfg->confirmReading))
131  $headers.= $this->headerLine('Disposition-Notification-To', $this->formatAddr($this->cfg->confirmReading));
132 
133  if (!empty($this->cfg->cc))
134  $headers.= $this->headerLine('Cc', $this->headerAddr($this->cfg->cc));
135 
136  if (!empty($this->cfg->bcc))
137  $headers.= $this->headerLine('Bcc', $this->headerAddr($this->cfg->bcc));
138 
139  if (is_array($this->cfg->customHeader))
140  foreach($this->cfg->customHeader as $k=>$v)
141  $headers.= $this->headerLine($k, $this->encodeHeader($v));
142 
143  $headers.= $this->headerLine('MIME-Version', '1.0');
144 
145  if (empty($this->attachment) && strlen($this->cfg->html) == 0)
146  $message_type = 'simpleText';
147  else {
148  if (strlen($this->cfg->text) > 0 && strlen($this->cfg->html) > 0
149  && !empty($this->attachment))
150  $message_type = 'altAttach';
151  else if (!empty($this->attachment) > 0)
152  $message_type = 'attach';
153  else
154  $message_type = 'alt';
155  }
156 
157  switch($message_type) {
158  case 'simpleText':
159  $headers.= $this->headerLine('Content-Type', 'text/plain; charset='.$this->cfg->charset);
160  $headers.= $this->headerLine('Content-Transfer-Encoding', $this->cfg->encoding);
161 
162  $body = $this->encode($this->wrapText($this->cfg->text));
163  break;
164  case 'attach':
165  case 'altAttach':
166  $boundaryMix = $this->getBoundary();
167  $headers.= $this->headerLine('Content-Type', 'multipart/mixed;'.$this->cfg->crlf.' boundary="'.$boundaryMix.'"');
168 
169  // Content Part
170  $body = $this->textLine('--'.$boundaryMix);
171  $body.= $this->getBody();
172 
173  $body.= $this->textLine(null);
174  // Attachement Part
175  foreach($this->attachment as $at) {
176  $body.= $this->textLine('--'.$boundaryMix);
177  $body.= $this->headerLine('Content-Type', $at['type'].'; name="'.$at['name'].'"');
178  $body.= $this->headerLine('Content-Transfer-Encoding', $this->cfg->fileEncoding);
179  $body.= $this->headerLine('Content-Disposition', 'attachment; name="'.$at['name'].'"');
180  $body.= $this->textLine(null);
181  $body.= $this->encode(file::read($at['file']), $this->cfg->fileEncoding);
182  $body.= $this->textLine(null);
183  }
184  $body.= $this->textLine('--'.$boundaryMix.'--');
185  break;
186  case 'alt':
187  $body = $this->getBody($headers);
188  break;
189  }
190 
191  $addr = array();
192  foreach($this->cfg->to as $v)
193  $addr[] = $this->formatAddr($v);
194  $to = implode(', ', $addr);
195 
196  $param = $this->cfg->addParam;
197  if ($this->cfg->addParamSender)
198  $param.= $this->cfg->addParamSender.$this->cfg->from.' '.$param;
199  return mail($to, $this->encodeHeader($this->cfg->subject), $body, $headers, $param);
200  }
201 
207  protected function getBoundary($ln = 24) {
208  $tmp = '';
209  for($i = 0; $i < $ln; $i++)
210  $tmp.= rand(0, 9);
211  return $tmp;
212  }
213 
220  protected function quotePrintable($str) {
221  $ln=strlen($str);
222  for($w=$e='', $n=0, $l=0, $i=0; $i<$ln; $i++) {
223  $c = $str[$i];
224  $o = ord($c);
225  $en = 0;
226  switch($o) {
227  case 9:
228  case 32:
229  $w = $c;
230  $c = '';
231  break;
232  case 10:
233  case 13:
234  if (strlen($w)) {
235  if ($l+3>75) {
236  $e.= '='.$this->cfg->crlf;
237  $l = 0;
238  }
239  $e.= sprintf('=%02X', ord($w));
240  $l+= 3;
241  $w = '';
242  }
243  $e.= $c;
244  $l = 0;
245  continue 2;
246  case 46:
247  case 70:
248  case 102:
249  $en = ($l==0 || $l+1>75);
250  break;
251  default:
252  if ($o>127 || $o<32 || !strcmp($c,'='))
253  $en = 1;
254  break;
255  }
256  if (strlen($w)) {
257  if ($l+1>75) {
258  $e.= '='.$this->cfg->crlf;
259  $l = 0;
260  }
261  $e.= $w;
262  $l++;
263  $w = '';
264  }
265  if (strlen($c)) {
266  if ($en) {
267  $c = sprintf('=%02X', $o);
268  $el = 3;
269  $n = 1;
270  $b = 1;
271  }
272  else
273  $el=1;
274  if ($l+$el>75) {
275  $e.= '='.$this->cfg->crlf;
276  $l = 0;
277  }
278  $e.= $c;
279  $l+= $el;
280  }
281  }
282  if (strlen($w)) {
283  if ($l+3>75)
284  $e.= '='.$this->cfg->crlf;
285  $e.= sprintf('=%02X', ord($w));
286  }
287  return $e;
288  }
289 
290 
297  protected function getBody(&$headers=null) {
298  $body = null;
299 
300  //$text = $this->quotePrintable($this->cfg->text);
301  $text = $this->cfg->text;
302 
303  if ($this->cfg->html) {
304  if (empty($this->cfg->text))
305  //$text = $this->quotePrintable(utils::html2Text($this->cfg->html));
306  $text = utils::html2Text($this->cfg->html);
307 
308  $boundary = '------------'.$this->getBoundary();
309 
310  if ($headers) {
311  $headers.= $this->headerLine('Content-Type', 'multipart/alternative;'.$this->cfg->crlf.' boundary="'.$boundary.'"');
312  //$headers.= $this->textLine(' boundary="'.$boundary.'"');
313  } else {
314  $body.= $this->headerLine('Content-Type', 'multipart/alternative;'.$this->cfg->crlf.' boundary="'.$boundary.'"');
315  //$body.= $this->textLine(' boundary="'.$boundary.'"');
316  $body.= $this->textLine('');
317  }
318 
319  // Text part
320  $body.= $this->textLine('This is a multi-part message in MIME format.');
321  $body.= $this->textLine('--'.$boundary);
322  }
323  $body.= $this->headerLine('Content-Type', 'text/plain; charset='.$this->cfg->charset);
324  //$body.= $this->textLine(' charset="'.$this->cfg->charset.'"');
325  $body.= $this->headerLine('Content-Transfer-Encoding', $this->cfg->encoding);
326  //$body.= $this->headerLine('Content-Disposition', 'inline');
327  $body.= $this->textLine(null);
328  $body.= $this->textLine($this->encode($this->wrapText($text)));
329 
330  if ($this->cfg->html) {
331  // HTML part
332  $body.= $this->textLine('--'.$boundary);
333 
334  $html = $this->cfg->html;
335 
336  $inlineImages = false;
337  if ($this->cfg->htmlInlineImage) {
338  $rootUri = request::get('rootUri');
339  preg_match_all('@src="('.$rootUri.'|/)(.+)"@siU', $html, $matches);
340  if (!empty($matches)) {
341  $images = array_unique($matches[2]);
342  $inlineImages = array();
343  $i = 1;
344  foreach($images as $img) {
345  if (file::webExists($img)) {
346  $file = WEBROOT.str_replace('/', DS, $img);
347  $cid = 'part'.$i.'.'.$this->getBoundary(16).'@'.$this->cfg->serverName;
348  $inlineImages[] = array(
349  'cid'=>$cid,
350  'file'=>$file,
351  'name'=>file::name($file),
352  'type'=>file::getType($file)
353  );
354  $i++;
355  $html = preg_replace('@src="('.$rootUri.'|/)('.$img.')"@siU', 'src="cid:'.$cid.'"', $html);
356  }
357  }
358  }
359  }
360 
361  if (!empty($inlineImages)) {
362  $boundaryRel = '------------'.$this->getBoundary();
363  $body.= $this->headerLine('Content-Type', 'multipart/related;'.$this->cfg->crlf.' boundary="'.$boundaryRel.'"');
364  //$body.= $this->textLine(' boundary="'.$boundaryRel.'"');
365  $body.= $this->textLine(null);
366  $body.= $this->textLine(null);
367  $body.= $this->textLine('--'.$boundaryRel);
368  }
369 
370  $body.= $this->headerLine('Content-Type', 'text/html; charset='.$this->cfg->charset.'');
371  //$body.= $this->textLine(' charset="'.$this->cfg->charset.'"');
372  $body.= $this->headerLine('Content-Transfer-Encoding', $this->cfg->encoding);
373  //$body.= $this->headerLine('Content-Disposition', 'inline');
374  $body.= $this->textLine(null);
375  //$body.= $this->textLine($this->quotePrintable($html));
376  $body.= $this->textLine($this->encode($this->wrapText($html)));
377 
378  if (!empty($inlineImages)) {
379  foreach($inlineImages as $img) {
380  $body.= $this->textLine('--'.$boundaryRel);
381  $body.= $this->headerLine('Content-Type', $img['type']); //.'; name="'.$img['name'].'"');
382  $body.= $this->headerLine('Content-Transfer-Encoding', $this->cfg->fileEncoding);
383  $body.= $this->headerLine('Content-ID', '<'.$img['cid'].'>');
384  //$body.= $this->headerLine('Content-Disposition', 'inline; filename="'.$img['name'].'"');
385  $body.= $this->textLine(null);
386  $body.= $this->encode(file::read($img['file']), $this->cfg->fileEncoding);
387  }
388  $body.= $this->textLine('--'.$boundaryRel.'--');
389  $body.= $this->textLine(null);
390  }
391 
392  $body.= '--'.$boundary.'--';
393  }
394 
395  return $body;
396  }
397 
405  protected function encode($val, $encoding=null) {
406  $encoding = is_null($encoding) ? $this->cfg->encoding : $encoding;
407  switch ($encoding) {
408  case 'base64':
409  return chunk_split(base64_encode($val), 72, $this->cfg->crlf);
410  case '8bit':
411  $val = $this->fixEOL($val);
412  if (substr($val, -(strlen($this->cfg->crlf))) != $this->cfg->crlf)
413  $val.= $this->cfg->crlf;
414  break;
415  }
416  return $val;
417  }
418 
419 
428  public function wrapText($message, $qp_mode = false) {
429  $length = $this->cfg->wrapLength;
430  $soft_break = ($qp_mode) ? sprintf(" =%s", $this->cfg->crlf) : $this->cfg->crlf;
431  // If utf-8 encoding is used, we will need to make sure we don't
432  // split multibyte characters when we wrap
433  $is_utf8 = (strtolower($this->cfg->charset) == "utf-8");
434 
435  $message = $this->fixEOL($message);
436  if (substr($message, -1) == $this->cfg->crlf)
437  $message = substr($message, 0, -1);
438 
439  $line = explode($this->cfg->crlf, $message);
440  $cpt = count($line);
441  $message = '';
442  for ($i = 0 ;$i < $cpt; $i++) {
443  $line_part = explode(' ', $line[$i]);
444  $cptLine = count($line_part);
445  $buf = '';
446  for ($e = 0; $e<$cptLine; $e++) {
447  $word = $line_part[$e];
448  if ($qp_mode and (strlen($word) > $length)) {
449  $space_left = $length - strlen($buf) - 1;
450  if ($e != 0) {
451  if ($space_left > 20) {
452  $len = $space_left;
453  if ($is_utf8) {
454  $len = $this->UTF8CharBoundary($word, $len);
455  } elseif (substr($word, $len - 1, 1) == "=") {
456  $len--;
457  } elseif (substr($word, $len - 2, 1) == "=") {
458  $len -= 2;
459  }
460  $part = substr($word, 0, $len);
461  $word = substr($word, $len);
462  $buf .= ' ' . $part;
463  $message .= $buf . sprintf("=%s", $this->cfg->crlf);
464  } else {
465  $message .= $buf . $soft_break;
466  }
467  $buf = '';
468  }
469  while (strlen($word) > 0) {
470  $len = $length;
471  if ($is_utf8) {
472  $len = $this->UTF8CharBoundary($word, $len);
473  } elseif (substr($word, $len - 1, 1) == "=") {
474  $len--;
475  } elseif (substr($word, $len - 2, 1) == "=") {
476  $len -= 2;
477  }
478  $part = substr($word, 0, $len);
479  $word = substr($word, $len);
480 
481  if (strlen($word) > 0) {
482  $message .= $part . sprintf("=%s", $this->cfg->crlf);
483  } else {
484  $buf = $part;
485  }
486  }
487  } else {
488  $buf_o = $buf;
489  $buf .= ($e == 0) ? $word : (' ' . $word);
490 
491  if (strlen($buf) > $length and $buf_o != '') {
492  $message .= $buf_o . $soft_break;
493  $buf = $word;
494  }
495  }
496  }
497  $message .= $buf . $this->cfg->crlf;
498  }
499 
500  return $message;
501  }
502 
508  public function fixEOL($str) {
509  $str = str_replace("\r\n", "\n", $str);
510  $str = str_replace("\r", "\n", $str);
511  $str = str_replace("\n", $this->cfg->crlf, $str);
512  return $str;
513  }
514 
523  public function UTF8CharBoundary($encodedText, $maxLength) {
524  $foundSplitPos = false;
525  $lookBack = 3;
526  while (!$foundSplitPos) {
527  $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
528  $encodedCharPos = strpos($lastChunk, "=");
529  if ($encodedCharPos !== false) {
530  // Found start of encoded character byte within $lookBack block.
531  // Check the encoded byte value (the 2 chars after the '=')
532  $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
533  $dec = hexdec($hex);
534  if ($dec < 128) { // Single byte character.
535  // If the encoded char was found at pos 0, it will fit
536  // otherwise reduce maxLength to start of the encoded char
537  $maxLength = ($encodedCharPos == 0) ? $maxLength :
538  $maxLength - ($lookBack - $encodedCharPos);
539  $foundSplitPos = true;
540  } elseif ($dec >= 192) { // First byte of a multi byte character
541  // Reduce maxLength to split at start of character
542  $maxLength = $maxLength - ($lookBack - $encodedCharPos);
543  $foundSplitPos = true;
544  } elseif ($dec < 192) { // Middle byte of a multi byte character, look further back
545  $lookBack += 3;
546  }
547  } else {
548  // No encoded character found
549  $foundSplitPos = true;
550  }
551  }
552  return $maxLength;
553  }
554 
562  protected function headerLine($name, $val) {
563  return $this->textLine($name.': '.$val);
564  }
565 
572  protected function textLine($val) {
573  return $val.$this->cfg->crlf;
574  }
575 
583  protected function headerAddr(array $val) {
584  $addr = array();
585  foreach($val as $v)
586  $addr[] = $this->formatAddr($v);
587 
588  return implode(', ', $addr);
589  }
590 
597  protected function formatAddr($addr) {
598  if (is_array($addr)) {
599  if (array_key_exists(1, $addr))
600  return $this->encodeHeader($addr[1]).' <'.$addr[0].'>';
601  else
602  return $addr[0];
603  } else
604  return $addr;
605  }
606 
614  protected function encodeHeader($val) {
615  $nb = 0;
616 
617  if (!preg_match('/[\200-\377]/', $val)) {
618  return addcslashes($val, "\0..\37\177\\\"");
619  /*
620  if (($val == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $val))
621  return $encoded;
622  else
623  return $encoded;
624  */
625  }
626  $nb = preg_match_all('/[^\040\041\043-\133\135-\176]/', $val, $matches);
627 
628  if ($nb == 0)
629  return $val;
630 
631  $maxlen = 75;
632  $maxlen = 75 - 7 - strlen($this->cfg->charset) - $maxlen % 4;
633  $encoded = trim(chunk_split(base64_encode($val), $maxlen, "\n"));
634  $encoded = preg_replace('/^(.*)$/m', ' =?'.$this->cfg->charset.'?B?\\1?=', $encoded);
635  $encoded = trim(str_replace("\n", $this->cfg->crlf, $encoded));
636 
637  return $encoded;
638  }
639 
643  protected function parseHtml() {
644  if (is_array($this->cfg->html))
645  $this->cfg->html = utils::render($this->cfg->html);
646  }
647 
648  public function __get($name) {
649  return $this->cfg->get($name);
650  }
651 
652  public function __set($name, $val) {
653  $this->cfg->set($name, $val);
654  if ($name == 'html')
655  $this->parseHtml();
656  }
657 
658 }
headerAddr(array $val)
wrapText($message, $qp_mode=false)
static get($get=null)
formatAddr($addr)
static read($file)
Definition: file.class.php:242
quotePrintable($str)
if(!defined('NYROROOT')) define('NYROROOT' ROOT DS
Definition: start.inc.php:56
__set($name, $val)
static exists($file)
Definition: file.class.php:97
static webExists($file)
Definition: file.class.php:232
static html2Text($html)
Definition: utils.class.php:33
UTF8CharBoundary($encodedText, $maxLength)
bcc($addr, $add=false)
Definition: email.class.php:49
static name($file)
Definition: file.class.php:118
static getType($file)
Definition: file.class.php:413
getBoundary($ln=24)
getBody(&$headers=null)
to($addr, $add=false)
Definition: email.class.php:29
encode($val, $encoding=null)
encodeHeader($val)
headerLine($name, $val)
static render(array $prm)
addr($addr, $type, $add=false)
Definition: email.class.php:60
cc($addr, $add=false)
Definition: email.class.php:39
Generated on Sun Oct 15 2017 22:25:20 for nyroFwk by doxygen 1.8.13