1: <?php
2: 3: 4: 5: 6: 7: 8:
9: abstract class QEmailServer extends QBaseClass {
10: 11: 12: 13: 14: 15: 16:
17: public static $SmtpServer = 'localhost';
18:
19: 20: 21: 22: 23:
24: public static $SmtpPort = 25;
25:
26: 27: 28: 29: 30: 31: 32: 33:
34: public static $OriginatingServerIp;
35:
36: 37: 38: 39: 40: 41: 42: 43:
44: public static $TestMode = false;
45:
46: 47: 48: 49: 50: 51: 52: 53:
54: public static $TestModeDirectory = __TMP__;
55:
56: 57: 58: 59: 60:
61: public static $AuthPlain = false;
62:
63: 64: 65: 66: 67:
68: public static $AuthLogin = false;
69:
70: 71: 72: 73: 74:
75: public static $SmtpUsername = '';
76:
77: 78: 79: 80: 81:
82: public static $SmtpPassword = '';
83:
84: 85: 86: 87: 88:
89: public static $EncodingType = null;
90:
91: 92: 93: 94: 95: 96:
97: public static function GetEmailAddresses($strAddresses) {
98: $strAddressArray = null;
99:
100:
101: if ((strpos($strAddresses, "\r") !== false) ||
102: (strpos($strAddresses, "\n") !== false))
103: return null;
104:
105: preg_match_all ("/[a-zA-Z0-9_.'%+-]+[@][\-a-zA-Z0-9_.]+/", $strAddresses, $strAddressArray);
106: if ((is_array($strAddressArray)) &&
107: (array_key_exists(0, $strAddressArray)) &&
108: (is_array($strAddressArray[0])) &&
109: (array_key_exists(0, $strAddressArray[0]))) {
110: return $strAddressArray[0];
111: }
112:
113:
114:
115: return null;
116: }
117:
118: 119: 120: 121: 122: 123:
124: public static function IsEmailValid($strEmailAddress) {
125: $strEmailAddressArray = QEmailServer::GetEmailAddresses($strEmailAddress);
126: return ((count($strEmailAddressArray) == 1) && ($strEmailAddressArray[0] == $strEmailAddress));
127: }
128:
129: 130: 131: 132: 133: 134:
135: private static function QuotedPrintableEncode($strString, $blnSubject = false) {
136: if ( function_exists('quoted_printable_encode') )
137: $strText = quoted_printable_encode($strString);
138: else {
139: $strText = preg_replace( '/[^\x21-\x3C\x3E-\x7E\x09\x20]/e', 'sprintf( "=%02X", ord ( "$0" ) ) ;', $strString );
140: preg_match_all( '/.{1,73}([^=]{0,2})?/', $strText, $arrMatch );
141: $strText = implode( '=' . "\r\n", $arrMatch[0] );
142: }
143:
144: if ($blnSubject) {
145:
146: $strText = str_replace("_", "=5F", $strText);
147: $strText = str_replace(" ", "_", $strText);
148:
149:
150: $strText = str_replace("=\r\n", "", $strText);
151: } else {
152:
153: $strText = str_replace("\n.", "\n..", $strText);
154: }
155:
156: return $strText;
157: }
158:
159:
160: 161: 162: 163: 164: 165: 166: 167: 168: 169:
170: public static function Send(QEmailMessage $objMessage) {
171: $objResource = null;
172:
173: if (QEmailServer::$TestMode) {
174:
175: $strArray = explode(' ', microtime());
176: $strFileName = sprintf('%s/email_%s%s.txt', QEmailServer::$TestModeDirectory, $strArray[1], substr($strArray[0], 1));
177: $objResource = fopen($strFileName, 'w');
178: if (!$objResource)
179: throw new QEmailException(sprintf('Unable to open Test SMTP connection to: %s', $strFileName));
180:
181:
182: if (!feof($objResource))
183: fgets($objResource, 4096);
184:
185:
186: fwrite($objResource, sprintf("telnet %s %s\r\n", QEmailServer::$SmtpServer, QEmailServer::$SmtpPort));
187: } else {
188: $objResource = fsockopen(QEmailServer::$SmtpServer, QEmailServer::$SmtpPort);
189: if (!$objResource)
190: throw new QEmailException(sprintf('Unable to open SMTP connection to: %s %s', QEmailServer::$SmtpServer, QEmailServer::$SmtpPort));
191: }
192:
193:
194: $strResponse = null;
195: if (!feof($objResource)) {
196: $strResponse = fgets($objResource, 4096);
197:
198:
199: while ((substr($strResponse, 0, 3) == "220") && (substr($strResponse, 0, 4) != "220 "))
200: if (!feof($objResource))
201: $strResponse = fgets($objResource, 4096);
202:
203:
204: if (!QEmailServer::$TestMode)
205: if ((strpos($strResponse, "220") === false) || (strpos($strResponse, "220") != 0))
206: throw new QEmailException(sprintf('Error Response on Connect: %s', $strResponse));
207: }
208:
209:
210: fwrite($objResource, sprintf("EHLO %s\r\n", QEmailServer::$OriginatingServerIp));
211: if (!feof($objResource)) {
212: $strResponse = fgets($objResource, 4096);
213:
214:
215: while ((substr($strResponse, 0, 3) == "250") && (substr($strResponse, 0, 4) != "250 "))
216: if (!feof($objResource))
217: $strResponse = fgets($objResource, 4096);
218:
219:
220: if (!QEmailServer::$TestMode)
221: if ((strpos($strResponse, "250") === false) || (strpos($strResponse, "250") != 0))
222: throw new QEmailException(sprintf('Error Response on EHLO: %s', $strResponse));
223: }
224:
225:
226: if (QEmailServer::$AuthPlain) {
227: fwrite($objResource, "AUTH PLAIN " . base64_encode(QEmailServer::$SmtpUsername . "\0" . QEmailServer::$SmtpUsername . "\0" . QEmailServer::$SmtpPassword) . "\r\n");
228: if (!feof($objResource)) {
229: $strResponse = fgets($objResource, 4096);
230: if ((strpos($strResponse, "235") === false) || (strpos($strResponse, "235") != 0))
231: throw new QEmailException(sprintf('Error in response from AUTH PLAIN: %s', $strResponse));
232: }
233: }
234:
235: if (QEmailServer::$AuthLogin) {
236: fwrite($objResource,"AUTH LOGIN\r\n");
237: if (!feof($objResource)) {
238: $strResponse = fgets($objResource, 4096);
239: if (!QEmailServer::$TestMode)
240: if ((strpos($strResponse, "334") === false) || (strpos($strResponse, "334") != 0))
241: throw new QEmailException(sprintf('Error in response from AUTH LOGIN: %s', $strResponse));
242: }
243:
244: fwrite($objResource, base64_encode(QEmailServer::$SmtpUsername) . "\r\n");
245: if (!feof($objResource)) {
246: $strResponse = fgets($objResource, 4096);
247: if (!QEmailServer::$TestMode)
248: if ((strpos($strResponse, "334") === false) || (strpos($strResponse, "334") != 0))
249: throw new QEmailException(sprintf('Error in response from AUTH LOGIN: %s', $strResponse));
250: }
251:
252: fwrite($objResource, base64_encode(QEmailServer::$SmtpPassword) . "\r\n");
253: if (!feof($objResource)) {
254: $strResponse = fgets($objResource, 4096);
255: if (!QEmailServer::$TestMode)
256: if ((strpos($strResponse, "235") === false) || (strpos($strResponse, "235") != 0))
257: throw new QEmailException(sprintf('Error in response from AUTH LOGIN: %s', $strResponse));
258: }
259: }
260:
261:
262: $strAddressArray = QEmailServer::GetEmailAddresses($objMessage->From);
263: if (count($strAddressArray) != 1)
264: throw new QEmailException(sprintf('Not a valid From address: %s', $objMessage->From));
265:
266:
267: fwrite($objResource, sprintf("MAIL FROM: <%s>\r\n", $strAddressArray[0]));
268: if (!feof($objResource)) {
269: $strResponse = fgets($objResource, 4096);
270:
271:
272: if (!QEmailServer::$TestMode)
273: if ((strpos($strResponse, "250") === false) || (strpos($strResponse, "250") != 0))
274: throw new QEmailException(sprintf('Error Response on MAIL FROM: %s', $strResponse));
275: }
276:
277:
278: $strAddressToArray = QEmailServer::GetEmailAddresses($objMessage->To);
279: if (!$strAddressToArray)
280: throw new QEmailException(sprintf('Not a valid To address: %s', $objMessage->To));
281:
282: $strAddressCcArray = QEmailServer::GetEmailAddresses($objMessage->Cc);
283: if (!$strAddressCcArray)
284: $strAddressCcArray = array();
285:
286: $strAddressBccArray = QEmailServer::GetEmailAddresses($objMessage->Bcc);
287: if (!$strAddressBccArray)
288: $strAddressBccArray = array();
289:
290: $strAddressCcBccArray = array_merge($strAddressCcArray, $strAddressBccArray);
291: $strAddressArray = array_merge($strAddressToArray, $strAddressCcBccArray);
292:
293:
294: foreach ($strAddressArray as $strAddress) {
295: fwrite($objResource, sprintf("RCPT TO: <%s>\r\n", $strAddress));
296: if (!feof($objResource)) {
297: $strResponse = fgets($objResource, 4096);
298:
299:
300: if (!QEmailServer::$TestMode)
301: if ((strpos($strResponse, "250") === false) || (strpos($strResponse, "250") != 0))
302: throw new QEmailException(sprintf('Error Response on RCPT TO: %s', $strResponse));
303: }
304: }
305:
306:
307: fwrite($objResource, "DATA\r\n");
308: if (!feof($objResource)) {
309: $strResponse = fgets($objResource, 4096);
310:
311:
312: if (!QEmailServer::$TestMode)
313: if ((strpos($strResponse, "354") === false) || (strpos($strResponse, "354") != 0))
314: throw new QEmailException(sprintf('Error Response on DATA: %s', $strResponse));
315: }
316:
317:
318: fwrite($objResource, sprintf("Date: %s\r\n", QDateTime::NowToString(QDateTime::FormatRfc5322)));
319: fwrite($objResource, sprintf("To: %s\r\n", $objMessage->To));
320: fwrite($objResource, sprintf("From: %s\r\n", $objMessage->From));
321: if($objMessage->ReplyTo) {
322: fwrite($objResource, sprintf("Reply-To: %s\r\n", $objMessage->ReplyTo));
323: }
324: if($objMessage->Sender) {
325: fwrite($objResource, sprintf("Sender: %s\r\n", $objMessage->Sender));
326: }
327:
328:
329: if (!($strEncodingType = QEmailServer::$EncodingType))
330: $strEncodingType = QApplication::$EncodingType;
331:
332:
333: if ($objMessage->Subject) {
334: if ($objMessage->EncodeSubject) {
335: fwrite($objResource, sprintf("Subject: =?%s?Q?%s?=\r\n", $strEncodingType, self::QuotedPrintableEncode($objMessage->Subject, true)));
336: } else {
337: fwrite($objResource, sprintf("Subject: %s\r\n", $objMessage->Subject));
338: }
339: }
340:
341: if ($objMessage->Cc)
342: fwrite($objResource, sprintf("Cc: %s\r\n", $objMessage->Cc));
343:
344:
345:
346:
347: $strBoundary = sprintf('qcubed_mixed_boundary_%s', md5(microtime()));
348: $strAltBoundary = sprintf('qcubed_alt_boundary_%s', md5(microtime()));
349:
350:
351: foreach ($objArray = $objMessage->HeaderArray as $strKey => $strValue)
352: fwrite($objResource, sprintf("%s: %s\r\n", $strKey, $strValue));
353:
354:
355: if ($objMessage->HasFiles || $objMessage->HtmlBody) {
356: fwrite($objResource, "MIME-Version: 1.0\r\n");
357: fwrite($objResource, sprintf("Content-Type: multipart/mixed;\r\n boundary=\"%s\"\r\n", $strBoundary));
358: fwrite($objResource, sprintf("This is a multipart message in MIME format.\r\n\r\n", $strBoundary));
359: fwrite($objResource, sprintf("--%s\r\n", $strBoundary));
360: }
361:
362:
363: if ($objMessage->HtmlBody) {
364: fwrite($objResource, sprintf("Content-Type: multipart/alternative;\r\n boundary=\"%s\"\r\n\r\n", $strAltBoundary));
365: fwrite($objResource, sprintf("--%s\r\n", $strAltBoundary));
366: fwrite($objResource, sprintf("Content-Type: text/plain; charset=\"%s\"\r\n", $strEncodingType));
367: fwrite($objResource, sprintf("Content-Transfer-Encoding: quoted-printable\r\n\r\n"));
368:
369: fwrite($objResource, self::QuotedPrintableEncode($objMessage->Body));
370: fwrite($objResource, "\r\n\r\n");
371:
372: fwrite($objResource, sprintf("--%s\r\n", $strAltBoundary));
373: fwrite($objResource, sprintf("Content-Type: text/html; charset=\"%s\"\r\n", $strEncodingType));
374: fwrite($objResource, sprintf("Content-Transfer-Encoding: quoted-printable\r\n\r\n"));
375:
376: fwrite($objResource, self::QuotedPrintableEncode($objMessage->HtmlBody));
377: fwrite($objResource, "\r\n\r\n");
378:
379: fwrite($objResource, sprintf("--%s--\r\n", $strAltBoundary));
380: } else if($objMessage->HasFiles) {
381: fwrite($objResource, sprintf("Content-Type: multipart/alternative;\r\n boundary=\"%s\"\r\n\r\n", $strAltBoundary));
382: fwrite($objResource, sprintf("--%s\r\n", $strAltBoundary));
383: fwrite($objResource, sprintf("Content-Type: text/plain; charset=\"%s\"\r\n", $strEncodingType));
384: fwrite($objResource, sprintf("Content-Transfer-Encoding: quoted-printable\r\n\r\n"));
385: fwrite($objResource, self::QuotedPrintableEncode($objMessage->Body));
386: fwrite($objResource, "\r\n\r\n");
387: fwrite($objResource, sprintf("--%s--\r\n", $strAltBoundary));
388: } else {
389: fwrite($objResource, sprintf("Content-Type: text/plain; charset=\"%s\"\r\n", $strEncodingType));
390: fwrite($objResource, sprintf("Content-Transfer-Encoding: quoted-printable\r\n\r\n"));
391: fwrite($objResource, "\r\n" . self::QuotedPrintableEncode($objMessage->Body));
392: }
393:
394:
395: if($objMessage->HasFiles) {
396: foreach ($objArray = $objMessage->FileArray as $objFile) {
397: fwrite($objResource, sprintf("--%s\r\n", $strBoundary));
398: fwrite($objResource, sprintf("Content-Type: %s;\r\n", $objFile->MimeType ));
399: fwrite($objResource, sprintf(" name=\"%s\"\r\n", $objFile->FileName ));
400: fwrite($objResource, "Content-Transfer-Encoding: base64\r\n");
401: fwrite($objResource, sprintf("Content-Length: %s\r\n", strlen($objFile->EncodedFileData)));
402: fwrite($objResource, "Content-Disposition: attachment;\r\n");
403: fwrite($objResource, sprintf(" filename=\"%s\"\r\n\r\n", $objFile->FileName));
404: fwrite($objResource, $objFile->EncodedFileData);
405:
406:
407:
408:
409: }
410: }
411:
412:
413: if($objMessage->HasFiles || $objMessage->HtmlBody)
414: fwrite($objResource, sprintf("\r\n\r\n--%s--\r\n", $strBoundary));
415:
416:
417: fwrite($objResource, "\r\n.\r\n");
418: if (!feof($objResource)) {
419: $strResponse = fgets($objResource, 4096);
420:
421:
422: if (!QEmailServer::$TestMode)
423: if ((strpos($strResponse, "250") === false) || (strpos($strResponse, "250") != 0))
424: throw new QEmailException(sprintf('Error Response on DATA finish: %s', $strResponse));
425: }
426:
427:
428: fwrite($objResource, "QUIT\r\n");
429: if (!feof($objResource))
430: $strResponse = fgets($objResource, 4096);
431:
432:
433: fclose($objResource);
434: if (QEmailServer::$TestMode)
435: chmod($strFileName, 0777);
436: }
437: }
438:
439:
440:
441: QEmailServer::$OriginatingServerIp = QApplication::$ServerAddress;
442:
443: class QEmailException extends QCallerException {}
444:
445: class QEmailAttachment extends QBaseClass {
446: protected $strFilePath;
447: protected $strMimeType;
448: protected $strFileName;
449: protected $strEncodedFileData;
450:
451: public function __construct($strFilePath, $strSpecifiedMimeType = null, $strSpecifiedFileName = null) {
452:
453: if (!is_file(realpath($strFilePath)))
454: throw new QCallerException('File Not Found: ' . $strFilePath);
455: $this->strFilePath = realpath($strFilePath);
456:
457:
458:
459: if ($strSpecifiedMimeType)
460: $this->strMimeType = $strSpecifiedMimeType;
461:
462: else
463: $this->strMimeType = QMimeType::GetMimeTypeForFile($this->strFilePath);
464:
465:
466:
467: if ($strSpecifiedFileName)
468: $this->strFileName = $strSpecifiedFileName;
469:
470: else
471: $this->strFileName = basename($this->strFilePath);
472:
473:
474:
475: $strFileContents = file_get_contents($this->strFilePath, false);
476: $this->strEncodedFileData = chunk_split(base64_encode($strFileContents));
477: }
478:
479: public function __get($strName) {
480: switch ($strName) {
481: case 'FilePath': return $this->strFilePath;
482: case 'MimeType': return $this->strMimeType;
483: case 'FileName': return $this->strFileName;
484: case 'EncodedFileData': return $this->strEncodedFileData;
485: default:
486: try {
487: return parent::__get($strName);
488: } catch (QCallerException $objExc) {
489: $objExc->IncrementOffset();
490: throw $objExc;
491: }
492: }
493: }
494: }
495:
496: class QEmailStringAttachment extends QEmailAttachment {
497: public function __construct($strContent, $strSpecifiedMimeType, $strSpecifiedFileName) {
498:
499: if ($strSpecifiedMimeType) {
500: $this->strMimeType = $strSpecifiedMimeType;
501: }
502:
503:
504: if ($strSpecifiedFileName) {
505: $this->strFileName = $strSpecifiedFileName;
506: }
507:
508:
509: $this->strEncodedFileData = chunk_split(base64_encode($strContent));
510: }
511: }
512:
513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526:
527: class QEmailMessage extends QBaseClass {
528: protected $strFrom;
529: protected $strReplyTo;
530: protected $strSender;
531: protected $strTo;
532: protected $strSubject;
533: protected $strBody;
534: protected $strHtmlBody;
535:
536: protected $strCc;
537: protected $strBcc;
538: protected $strHeaderArray = array();
539: protected $objFileArray = array();
540:
541: protected $blnEncodeSubject = true;
542:
543: public function AddAttachment(QEmailAttachment $objFile) {
544: $this->objFileArray[$objFile->FileName] = $objFile;
545: }
546:
547: public function Attach($strFilePath, $strSpecifiedMimeType = null, $strSpecifiedFileName = null) {
548: $this->AddAttachment(new QEmailAttachment($strFilePath, $strSpecifiedMimeType, $strSpecifiedFileName));
549: }
550:
551: public function RemoveAttachment($strName) {
552: if (array_key_exists($strName, $this->objFileArray))
553: unset($this->objFileArray[$strName]);
554: }
555:
556: public function SetHeader($strName, $strValue) {
557: $this->strHeaderArray[$strName] = $strValue;
558: }
559:
560: public function GetHeader($strName) {
561: if (array_key_exists($strName, $this->strHeaderArray))
562: return $this->strHeaderArray[$strName];
563: return null;
564: }
565:
566: public function RemoveHeader($strName) {
567: if (array_key_exists($strName, $this->strHeaderArray))
568: unset($this->strHeaderArray[$strName]);
569: }
570:
571: public function __construct($strFrom = null, $strTo = null, $strSubject = null, $strBody = null) {
572: $this->strFrom = $strFrom;
573: $this->strTo = $strTo;
574:
575:
576: $this->Subject = $strSubject;
577: $this->Body = $strBody;
578: }
579:
580: public function __get($strName) {
581: switch ($strName) {
582: case 'From' : return $this->strFrom;
583: case 'ReplyTo' : return $this->strReplyTo;
584: case 'Sender' : return $this->strSender;
585: case 'To' : return $this->strTo;
586: case 'Subject': return $this->strSubject;
587: case 'Body': return $this->strBody;
588: case 'HtmlBody': return $this->strHtmlBody;
589:
590: case 'Cc': return $this->strCc;
591: case 'Bcc': return $this->strBcc;
592:
593: case 'HeaderArray': return $this->strHeaderArray;
594: case 'FileArray': return $this->objFileArray;
595: case 'HasFiles': return (count($this->objFileArray) > 0) ? true : false;
596: case 'EncodeSubject': return $this->blnEncodeSubject;
597:
598: default:
599: try {
600: return parent::__get($strName);
601: } catch (QCallerException $objExc) {
602: $objExc->IncrementOffset();
603: throw $objExc;
604: }
605: }
606: }
607:
608: public function __set($strName, $mixValue) {
609: try {
610: switch ($strName) {
611: case 'From' : return ($this->strFrom = QType::Cast($mixValue, QType::String));
612: case 'ReplyTo' : return ($this->strReplyTo = QType::Cast($mixValue, QType::String));
613: case 'Sender' : return ($this->strSender = QType::Cast($mixValue, QType::String));
614: case 'To': return ($this->strTo = QType::Cast($mixValue, QType::String));
615: case 'Subject':
616: $strSubject = trim(QType::Cast($mixValue, QType::String));
617: $strSubject = str_replace("\r", "", $strSubject);
618: $strSubject = str_replace("\n", " ", $strSubject);
619: return ($this->strSubject = $strSubject);
620: case 'Body':
621: $strBody = QType::Cast($mixValue, QType::String);
622: $strBody = str_replace("\r", "", $strBody);
623: $strBody = str_replace("\n", "\r\n", $strBody);
624: $strBody = str_replace("\n.", "\n..", $strBody);
625: return ($this->strBody = $strBody);
626: case 'HtmlBody':
627: $strHtmlBody = QType::Cast($mixValue, QType::String);
628: $strHtmlBody = str_replace("\r", "", $strHtmlBody);
629: $strHtmlBody = str_replace("\n", "\r\n", $strHtmlBody);
630: $strHtmlBody = str_replace("\n.", "\n..", $strHtmlBody);
631: return ($this->strHtmlBody = $strHtmlBody);
632:
633: case 'Cc': return ($this->strCc = QType::Cast($mixValue, QType::String));
634: case 'Bcc': return ($this->strBcc = QType::Cast($mixValue, QType::String));
635: case 'EncodeSubject': return ($this->blnEncodeSubject = QType::Cast($mixValue, QType::Boolean));
636:
637: default: return (parent::__set($strName, $mixValue));
638: }
639: } catch (QInvalidCastException $objExc) {
640: $objExc->IncrementOffset();
641: throw $objExc;
642: }
643: }
644: }