1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11: if (!function_exists ('quoted_printable_encode')) {
12: public function quoted_printable_encode ($sStr)
13: {
14: return str_replace ("%", "=", rawurlencode ($sStr));
15: }
16: }
17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
30: class SimpleMail
31: {
32: 33: 34: 35:
36: private $_aProperties = array ( 'XPriority' => 3,
37: 'Sender' => '',
38: 'ReplyTo' => '',
39: 'ReturnPath' => '',
40: 'From' => '',
41: 'To' => array (),
42: 'Cc' => array (),
43: 'Bcc' => array (),
44: 'DispositionNotificationTo' => '',
45: 'XMailer' => 'MyPrettySociety Mailer',
46: 'Organisation' => 'MyPrettySociety',
47: 'Date' => '',
48: 'MimeVersion' => '1.0',
49: 'Subject' => '',
50: 'AbuseContact' => '',
51: 'Charset' => 'Utf-8',
52: 'Bodies' => array (),
53: 'Attachment' => array ()
54: );
55:
56: 57: 58: 59:
60: private $_sBreakLine = "\n";
61:
62: 63: 64: 65: 66:
67: public function __construct ($aProperties = null)
68: {
69: if (!function_exists ('mail'))
70: throw new Exception ('Function "mail" must exists to use this class');
71:
72: $this->_aProperties['Date'] = date("D, j M Y H:i:s -0600");
73:
74: if (isset ($aProperties)) {
75: foreach ($aProperties as $sKey=>$mProperty) {
76: $this->__set ($sKey, $mProperty);
77: }
78: }
79: }
80:
81: 82: 83: 84: 85: 86: 87: 88: 89:
90: public function __set ($sKey, $mValue)
91: {
92: if (is_int ($this->_aProperties[$sKey]) && !is_int ($mValue))
93: throw new Exception ('Invalid type, must be an Int');
94: else if (is_string ($this->_aProperties[$sKey]) && !is_string ($mValue))
95: throw new Exception ('Invalid type, must be an String');
96: else if (is_array ($this->_aProperties[$sKey]) && !is_array ($mValue))
97: throw new Exception ('Invalid type, must be an String');
98: else {
99: $this->_aProperties[$sKey] = $mValue;
100: }
101: }
102:
103: 104: 105: 106: 107: 108: 109: 110:
111: public function __get ($sKey)
112: {
113: if (!isset ($this->_aProperties[$sKey]))
114: throw new Exception ('Invalid key "'.$sKey.'"');
115:
116: return $this->_aProperties[$sKey];
117: }
118:
119: 120: 121: 122: 123: 124: 125: 126: 127: 128:
129: public function addAttachment ($sFilePath, $sType = 'Application/Octet-Stream', $sFileName = null, iCompression $oCompress = null, $sCid = null)
130: {
131: if (!file_exists ($sFilePath) || !is_readable ($sFilePath))
132: throw new Exception ('The file "'.$sFilePath.'" is unreadable.');
133:
134: if (!is_string ($sType))
135: throw new Exception ('Type must be a String');
136:
137: if (!isset ($sFileName))
138: $sFileName = substr ($sFilePath, strrpos ($sFilePath, '/'));
139: else if (!is_string ($sFileName))
140: throw new Exception ('Filename must be a String');
141:
142: $sContent = file_get_contents ($sFilePath);
143:
144: if (isset ($oCompress))
145: $sContent = $oCompress->compress ($sContent);
146:
147: $iCountAttachments = count ($this->_aProperties['Attachment']);
148:
149: $this->_aProperties['Attachment'][$iCountAttachments]['ContentType'] = $sType;
150: $this->_aProperties['Attachment'][$iCountAttachments]['ContentTransfertEncoding'] = 'base64';
151: $this->_aProperties['Attachment'][$iCountAttachments]['ContentDisposition'] = (isset ($sCid)) ? 'inline' : 'attachment';
152: $this->_aProperties['Attachment'][$iCountAttachments]['Filename'] = $sFileName;
153:
154: if (isset ($sCid))
155: $this->_aProperties['Attachment'][$iCountAttachments]['Content-ID'] = $sCid;
156:
157: $this->_aProperties['Attachment'][$iCountAttachments]['Content'] = chunk_split (base64_encode ($sContent));
158: }
159:
160: 161: 162: 163: 164: 165: 166: 167: 168:
169: public function addBody ($sBody, $sType = 'text/plain', $sCharset = null)
170: {
171: if (!is_string ($sBody))
172: throw new Exception ('Body must be a String');
173:
174: if (!is_string ($sType))
175: throw new Exception ('Type must be a String');
176:
177: if (!isset ($sCharset))
178: $sCharset = $this->_aProperties['Charset'];
179:
180: if (!is_string ($sCharset))
181: throw new Exception ('Charset must be a String');
182:
183: $iCountBodies = count ($this->_aProperties['Bodies']);
184:
185: $this->_aProperties['Bodies'][$iCountBodies]['ContentType'] = $sType;
186: $this->_aProperties['Bodies'][$iCountBodies]['Charset'] = $sCharset;
187: $this->_aProperties['Bodies'][$iCountBodies]['ContentTransfertEncoding'] = 'quoted-printable';
188: $this->_aProperties['Bodies'][$iCountBodies]['ContentDisposition'] = 'inline';
189:
190: if ($sType == 'text/html')
191: $sBody = preg_replace_callback ('#src[\ ]*=[\ ]*["|\']([^"|\']*)["|\']#i', array ($this, 'inlineAttachment'), $sBody);
192:
193: $this->_aProperties['Bodies'][$iCountBodies]['Content'] = quoted_printable_encode (chunk_split ($sBody, 76, $this->_sBreakLine));
194: }
195:
196: 197: 198: 199: 200:
201: public function send ()
202: {
203: if (count ($this->_aProperties['To']) == 0 && count ($this->_aProperties['Cc']) == 0 && count ($this->_aProperties['Bcc']) == 0)
204: throw new Exception ('You need to specify at least one recipient (To, Cc or Bcc)');
205:
206: $iCountBodies = count ($this->_aProperties['Bodies']);
207: $iCountAttachments = count ($this->_aProperties['Attachment']);
208:
209: if (($iCountBodies + $iCountAttachments) == 1) {
210: $sHeaders = $this->_createHeaders ();
211:
212: if ($iCountBodies == 1)
213: $sHeaders .= $this->_createSection ($this->_aProperties['Bodies'][0]);
214: else
215: $sHeaders .= $this->_createSection ($this->_aProperties['Attachment'][0]);
216:
217: $sHeaders .= $this->_sBreakLine.$this->_sBreakLine;
218:
219: } elseif ($iCountBodies == 1 && $iCountAttachments > 0) {
220: $sBoundary = $this->_boundaryGenerate ();
221:
222: $sHeaders = $this->_createHeaders ();
223:
224: $sHeaders .= 'Content-Type: multipart/mixed;'.$this->_sBreakLine."\t".'boundary="'.$sBoundary.'"'.$this->_sBreakLine.$this->_sBreakLine;
225:
226: $sHeaders .= $this->_createSection ($this->_aProperties['Bodies'][0], $sBoundary);
227:
228: foreach ($this->_aProperties['Attachment'] as $aAttachment) {
229: $sHeaders .= $this->_createSection ($aAttachment, $sBoundary);
230: }
231:
232: $sHeaders .= '--'.$sBoundary.'--'.$this->_sBreakLine.$this->_sBreakLine;
233: } elseif ($iCountBodies > 1 && $iCountAttachments == 0) {
234: $sBoundary = $this->_boundaryGenerate ();
235:
236: $sHeaders = $this->_createHeaders ();
237:
238: $sHeaders .= 'Content-Type: multipart/alternative;'.$this->_sBreakLine."\t".'boundary="'.$sBoundary.'"'.$this->_sBreakLine.$this->_sBreakLine;
239:
240: foreach ($this->_aProperties['Bodies'] as $aBody) {
241: $sHeaders .= $this->_createSection ($aBody, $sBoundary);
242: }
243:
244: $sHeaders .= '--'.$sBoundary.'--'.$this->_sBreakLine.$this->_sBreakLine;
245: } elseif ($iCountBodies > 1 && $iCountAttachments >= 1) {
246: $sMixedBoundary = $this->_boundaryGenerate ();
247: $sAlternativeBoundary = $this->_boundaryGenerate ();
248:
249: $sHeaders = $this->_createHeaders ();
250:
251: $sAlternativeBody = 'Content-Type: multipart/alternative;'.$this->_sBreakLine."\t".'boundary="'.$sAlternativeBoundary.'"'.$this->_sBreakLine.$this->_sBreakLine;
252:
253: foreach ($this->_aProperties['Bodies'] as $aBody) {
254: $sAlternativeBody .= $this->_createSection ($aBody, $sAlternativeBoundary);
255: }
256:
257: $sAlternativeBody .= '--'.$sAlternativeBoundary.'--'.$this->_sBreakLine.$this->_sBreakLine;
258:
259: $sHeaders .= 'Content-Type: multipart/mixed;'.$this->_sBreakLine."\t".'boundary="'.$sMixedBoundary.'"'.$this->_sBreakLine.$this->_sBreakLine;
260:
261: $sHeaders .= '--'.$sMixedBoundary.$this->_sBreakLine;
262: $sHeaders .= $sAlternativeBody;
263:
264: foreach ($this->_aProperties['Attachment'] as $aAttachment) {
265: $sHeaders .= $this->_createSection ($aAttachment, $sMixedBoundary);
266: }
267:
268: $sHeaders .= '--'.$sMixedBoundary.'--'.$this->_sBreakLine.$this->_sBreakLine;
269: } else
270: throw new Exception ('Invalid Email structure');
271:
272: $sSubject = '=?'.$this->_aProperties['Charset'].'?q?'.quoted_printable_encode ($this->_aProperties['Subject']).'?=';
273:
274: if (@mail (implode (', ', $this->_aProperties['To']), $sSubject, '', $sHeaders) === false)
275: throw new Exception ('An error occured while sending the email');
276: }
277:
278: 279: 280: 281: 282: 283: 284:
285: public function inlineAttachment ($aMatches)
286: {
287: $sCID = $this->_cidGenerate ();
288: try {
289: $this->addAttachment ($aMatches[1], MimeType::get ($aMatches[1]), null, null, $sCID);
290:
291: return 'src="cid:'.$sCID.'"';
292: } catch (Exception $oE) {
293: return $aMatches[0];
294: }
295: }
296:
297: 298: 299: 300: 301:
302: private function _createHeaders ()
303: {
304: if (empty ($this->_aProperties['From']))
305: throw new Exception ('From must be set !');
306:
307: if (empty ($this->_aProperties['ReplyTo']))
308: $this->_aProperties['ReplyTo'] = $this->_aProperties['From'];
309:
310: if (empty ($this->_aProperties['ReturnPath']))
311: $this->_aProperties['ReturnPath'] = $this->_aProperties['From'];
312:
313: if (empty ($this->_aProperties['Sender']))
314: $this->_aProperties['Sender'] = $this->_aProperties['From'];
315:
316: $sHeaders = 'X-Priority: '.$this->_aProperties['XPriority'].$this->_sBreakLine;
317: $sHeaders .= 'X-Mailer: '.$this->_aProperties['XMailer'].$this->_sBreakLine;
318: $sHeaders .= 'Organisation: '.$this->_aProperties['Organisation'].$this->_sBreakLine;
319: $sHeaders .= 'Date: '.$this->_aProperties['Date'].$this->_sBreakLine;
320: $sHeaders .= 'MIME-version: '.$this->_aProperties['MimeVersion'].$this->_sBreakLine;
321: $sHeaders .= 'From: '.$this->_aProperties['From'].$this->_sBreakLine;
322: $sHeaders .= 'Reply-To: '.$this->_aProperties['ReplyTo'].$this->_sBreakLine;
323: $sHeaders .= 'Return-Path: '.$this->_aProperties['ReturnPath'].$this->_sBreakLine;
324: $sHeaders .= 'Sender: '.$this->_aProperties['Sender'].$this->_sBreakLine;
325: $sHeaders .= 'X-Sender: '.$this->_aProperties['Sender'].$this->_sBreakLine;
326:
327:
328: if (!empty ($this->_aProperties['DispositionNotificationTo'])) {
329: $sHeaders .= 'Disposition-Notification-To: '.$this->_aProperties['DispositionNotificationTo'].$this->_sBreakLine;
330: $sHeaders .= 'X-Confirm-Reading-To: '.$this->_aProperties['DispositionNotificationTo'].$this->_sBreakLine;
331: $sHeaders .= 'Return-receipt-to: '.$this->_aProperties['DispositionNotificationTo'].$this->_sBreakLine;
332: }
333:
334: if (!empty ($this->_aProperties['AbuseContact'])) {
335: $sHeaders .= 'X-abuse-contact: '.$this->_aProperties['AbuseContact'].$this->_sBreakLine;
336: }
337: 338: 339: 340:
341: if (count ($this->_aProperties['Cc']) > 0)
342: $sHeaders .= 'Cc:'.implode (',', $this->_aProperties['Cc']).$this->_sBreakLine;
343:
344: if (count ($this->_aProperties['Bcc']) > 0)
345: $sHeaders .= 'Bcc:'.implode (',', $this->_aProperties['Bcc']).$this->_sBreakLine;
346:
347: return $sHeaders;
348: }
349:
350: 351: 352: 353: 354: 355: 356: 357:
358: private function _createSection ($aElement, $sBoundary = null)
359: {
360: $sMessage = '';
361: if (isset ($sBoundary))
362: $sMessage = '--'.$sBoundary.$this->_sBreakLine;
363:
364: $sMessage .= 'Content-Type: '.$aElement['ContentType'];
365:
366: if (!empty ($aElement['Charset']))
367: $sMessage .= '; charset="'.$aElement['Charset'].'"';
368: else if ($aElement['ContentTransfertEncoding'] == 'base64')
369: $sMessage .= '; name="'.$aElement['Filename'].'"';
370:
371: $sMessage .= $this->_sBreakLine;
372: $sMessage .= 'Content-Transfer-Encoding: '.$aElement['ContentTransfertEncoding'].$this->_sBreakLine;
373: $sMessage .= 'Content-Disposition: '.$aElement['ContentDisposition'];
374:
375: if (!empty ($aElement['Filename']))
376: $sMessage .= '; filename="'.$aElement['Filename'].'"';
377:
378: $sMessage .= $this->_sBreakLine;
379:
380: if (!empty ($aElement['Content-ID']))
381: $sMessage .= 'Content-ID: <'.$aElement['Content-ID'].'>'.$this->_sBreakLine;
382:
383:
384: $sMessage .= $this->_sBreakLine;
385: $sMessage .= $aElement['Content'].$this->_sBreakLine.$this->_sBreakLine;
386:
387: return $sMessage;
388: }
389:
390: 391: 392: 393: 394:
395: private function _boundaryGenerate ()
396: {
397: return '---=Part_'.md5 (uniqid (mt_rand ()));
398: }
399:
400: 401: 402: 403: 404:
405: private function _cidGenerate ()
406: {
407: return 'CID_'.md5 (uniqid (mt_rand ()));
408: }
409: }
410: