Campustream 1.0
A social network MQP for WPI
application/lib/postmark/Postmark.php
Go to the documentation of this file.
00001 <?php
00002 
00033 class Mail_Postmark
00034 {
00035         const DEBUG_OFF = 0;
00036         const DEBUG_VERBOSE = 1;
00037         const DEBUG_RETURN = 2;
00038         const TESTING_API_KEY = 'POSTMARK_API_TEST';
00039         const MAX_ATTACHMENT_SIZE = 10485760;   // 10 MB
00040         
00041         static $_mimeTypes = array('ai' => 'application/postscript', 'avi' => 'video/x-msvideo', 'doc' => 'application/msword', 'eps' => 'application/postscript', 'gif' => 'image/gif', 'htm' => 'text/html', 'html' => 'text/html', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'mov' => 'video/quicktime', 'mp3' => 'audio/mpeg', 'mpg' => 'video/mpeg', 'pdf' => 'application/pdf', 'ppt' => 'application/vnd.ms-powerpoint', 'ps' => 'application/postscript', 'rtf' => 'application/rtf', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'txt' => 'text/plain', 'xls' => 'application/vnd.ms-excel', 'csv' => 'text/comma-separated-values', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'flv' => 'video/x-flv', 'ics' => 'text/calendar', 'log' => 'text/plain', 'png' => 'image/png', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'psd' => 'image/photoshop', 'rm' => 'application/vnd.rn-realmedia', 'swf' => 'application/x-shockwave-flash', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xml' => 'text/xml');
00042         
00043         private $_apiKey;
00044         private $_from;
00045         private $_to = array();
00046         private $_cc = array();
00047         private $_bcc = array();
00048         private $_replyTo;
00049         private $_subject;
00050         private $_tag;
00051         private $_messagePlain;
00052         private $_messageHtml;
00053         private $_headers = array();
00054         private $_attachments = array();
00055         private $_debugMode = self::DEBUG_OFF;
00056         
00060         public function __construct()
00061         {
00062                 if (class_exists('Mail_Postmark_Adapter')) {
00063                         require_once('Adapter_Interface.php');
00064                         
00065                         $reflection = new ReflectionClass('Mail_Postmark_Adapter');
00066                         
00067                         if (!$reflection->implementsInterface('Mail_Postmark_Adapter_Interface')) {
00068                                 trigger_error('Mail_Postmark_Adapter must implement interface Mail_Postmark_Adapter_Interface', E_USER_ERROR);
00069                         }
00070                         
00071                         $this->_apiKey = Mail_Postmark_Adapter::getApiKey();
00072                         
00073                         Mail_Postmark_Adapter::setupDefaults($this);
00074                         
00075                 } else {
00076                         if (!defined('POSTMARKAPP_API_KEY')) {
00077                                 trigger_error('Postmark API key is not set', E_USER_ERROR);
00078                         }
00079                         
00080                         $this->_apiKey = POSTMARKAPP_API_KEY;
00081                         
00082                         if (defined('POSTMARKAPP_MAIL_FROM_ADDRESS')) {
00083                                 $this->from(
00084                                         POSTMARKAPP_MAIL_FROM_ADDRESS,
00085                                         defined('POSTMARKAPP_MAIL_FROM_NAME') ? POSTMARKAPP_MAIL_FROM_NAME : null
00086                                 );
00087                         }
00088                 }
00089                 
00090                 $this->messageHtml(null)->messagePlain(null);
00091         }
00092         
00104         public function &addAttachment($filename, $options = array())
00105         {
00106                 if (!is_file($filename)) {
00107                         throw new InvalidArgumentException("File \"{$filename}\" does not exist");
00108                 }
00109                 
00110                 $this->addCustomAttachment(
00111                         isset($options['filenameAlias']) ? $options['filenameAlias'] : basename($filename),
00112                         file_get_contents($filename),
00113                         $this->_getMimeType($filename)
00114                 );
00115                 
00116                 return $this;
00117         }
00118         
00128         public function &addBcc($address, $name = null)
00129         {
00130                 $this->_addRecipient('bcc', $address, $name);
00131                 return $this;
00132         }
00133         
00143         public function &addCc($address, $name = null)
00144         {
00145                 $this->_addRecipient('cc', $address, $name);
00146                 return $this;
00147         }
00148         
00158         public function &addCustomAttachment($filename, $content, $mimeType)
00159         {
00160                 $length = strlen($content);
00161                 $lengthSum = 0;
00162                 
00163                 foreach ($this->_attachments as $file) {
00164                         $lengthSum += $file['length'];
00165                 }
00166                 
00167                 if ($lengthSum + $length > self::MAX_ATTACHMENT_SIZE) {
00168                         throw new OverflowException("Maximum attachment size reached");
00169                 }
00170                 
00171                 $this->_attachments[$filename] = array(
00172                         'content' => base64_encode($content),
00173                         'mimeType' => $mimeType,
00174                         'length' => $length
00175                 );
00176                 
00177                 return $this;
00178         }
00179         
00187         public function &addHeader($name, $value)
00188         {
00189                 $this->_headers[$name] = $value;
00190                 return $this;
00191         }
00192         
00202         public function &addTo($address, $name = null)
00203         {
00204                 $this->_addRecipient('to', $address, $name);
00205                 return $this;
00206         }
00207         
00213         public static function compose()
00214         {
00215                 return new self();
00216         }
00217         
00224         public function &debug($mode = self::DEBUG_VERBOSE)
00225         {
00226                 $this->_debugMode = $mode;
00227                 return $this;
00228         }
00229         
00239         public function &from($address, $name = null)
00240         {
00241                 if (!$this->_validateAddress($address)) {
00242                         throw new InvalidArgumentException("From address \"{$address}\" is invalid");
00243                 }
00244                 
00245                 $this->_from = array('address' => $address, 'name' => $name);
00246                 return $this;
00247         }
00248         
00255         public function &fromName($name)
00256         {
00257                 $this->_from['name'] = $name;
00258                 return $this;
00259         }
00260         
00267         public function &messageHtml($message)
00268         {
00269                 $this->_messageHtml = $message;
00270                 return $this;
00271         }
00272         
00278         public function &messagePlain($message)
00279         {
00280                 $this->_messagePlain = $message;
00281                 return $this;
00282         }
00283         
00292         public function &replyTo($address, $name = null)
00293         {
00294                 if (!$this->_validateAddress($address)) {
00295                         throw new InvalidArgumentException("Reply To address \"{$address}\" is invalid");
00296                 }
00297                 
00298                 $this->_replyTo = array('address' => $address, 'name' => $name);
00299                 return $this;
00300         }
00301         
00309         public function send()
00310         {
00311                 $this->_validateData();
00312                 $data = $this->_prepareData();
00313                 $headers = array(
00314                         'Accept: application/json',
00315                         'Content-Type: application/json',
00316                         'X-Postmark-Server-Token: ' . $this->_apiKey
00317                 );
00318                 
00319                 $ch = curl_init();
00320                 curl_setopt($ch, CURLOPT_URL, 'https://api.postmarkapp.com/email');
00321                 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
00322                 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
00323                 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
00324                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
00325                 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
00326                 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
00327                 curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/Certificate/cacert.pem');
00328                 
00329                 $return = curl_exec($ch);
00330                 $curlError = curl_error($ch);
00331                 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
00332                 
00333                 $this->_log(array(
00334                         'messageData' => $data,
00335                         'return' => $return,
00336                         'curlError' => $curlError,
00337                         'httpCode' => $httpCode
00338                 ));
00339                 
00340                 if (($this->_debugMode & self::DEBUG_VERBOSE) === self::DEBUG_VERBOSE) {
00341                         echo "JSON: " . json_encode($data)
00342                                 . "\nHeaders: \n\t" . implode("\n\t", $headers) 
00343                                 . "\nReturn:\n{$return}"
00344                                 . "\nCurl error: {$curlError}"
00345                                 . "\nHTTP code: {$httpCode}";
00346                 }
00347                 
00348                 if ($curlError !== '') {
00349                         throw new Exception($curlError);
00350                 }
00351                 
00352                 if (!$this->_isTwoHundred($httpCode)) {
00353                         if ($httpCode == 422) {
00354                                 $return = json_decode($return);
00355                                 throw new Exception($return->Message, $return->ErrorCode);
00356                         } else {
00357                                 throw new Exception("Error while mailing. Postmark returned HTTP code {$httpCode} with message \"{$return}\"", $httpCode);
00358                         }
00359                 }
00360                 
00361                 if (($this->_debugMode & self::DEBUG_RETURN) === self::DEBUG_RETURN) {
00362                         return array(
00363                                 'json' => json_encode($data),
00364                                 'headers' => $headers,
00365                                 'return' => $return,
00366                                 'curlError' => $curlError,
00367                                 'httpCode' => $httpCode
00368                         );
00369                 }
00370                 
00371                 return true;
00372         }
00373         
00380         public function &subject($subject)
00381         {
00382                 $this->_subject = $subject;
00383                 return $this;
00384         }
00385 
00396         public function &tag($tag)
00397         {
00398                 $this->_tag = $tag;
00399                 return $this;
00400         }
00401         
00410         public function &to($address, $name = null)
00411         {
00412                 $this->_to = array();
00413                 $this->addTo($address, $name);
00414                 return $this;
00415         }
00416         
00424         public function _addRecipient($type, $address, $name = null)
00425         {
00426                 if (!$this->_validateAddress($address)) {
00427                         throw new InvalidArgumentException("Address \"{$address}\" is invalid");
00428                 }
00429                 
00430                 if (count($this->_to) + count($this->_cc) + count($this->_bcc) === 20) {
00431                         throw new OverflowException('Too many email recipients');
00432                 }
00433                 
00434                 $data = array('address' => $address, 'name' => $name);
00435                 
00436                 switch ($type) {
00437                         case 'to':
00438                                 $this->_to[] = $data;
00439                         break;
00440                         
00441                         case 'cc':
00442                                 $this->_cc[] = $data;
00443                         break;
00444                         
00445                         case 'bcc':
00446                                 $this->_bcc[] = $data;
00447                         break;
00448                 }
00449         }
00450         
00454         private function _getMimeType($filename)
00455         {
00456                 $extension = pathinfo($filename, PATHINFO_EXTENSION);
00457                 
00458                 if (isset(self::$_mimeTypes[$extension])) {
00459                         return self::$_mimeTypes[$extension];
00460                         
00461                 } else if (function_exists('mime_content_type')) {
00462                          return mime_content_type($filename);
00463                 
00464                 } else if (function_exists('finfo_file')) {
00465                          $fh = finfo_open(FILEINFO_MIME);
00466                          $mime = finfo_file($fh, $filename);
00467                          finfo_close($fh);
00468                          return $mime;
00469                 
00470                 } else if ($image = getimagesize($filename)) {
00471                         return $image[2];
00472                         
00473                 } else {
00474                         return 'application/octet-stream';
00475                 }
00476         }
00477         
00481         private function _isTwoHundred($value)
00482         {
00483                 return intval($value / 100) == 2;
00484         }
00485         
00489         private function _prepareData()
00490         {
00491                 $data = array(
00492                         'Subject' => $this->_subject
00493                 );
00494                 
00495                 $data['From'] = $this->_from['name'] === null ? $this->_from['address'] : "{$this->_from['name']} <{$this->_from['address']}>";
00496                 $data['To'] = array();
00497                 $data['Cc'] = array();
00498                 $data['Bcc'] = array();
00499                 
00500                 foreach ($this->_to as $to) {
00501                         $data['To'][] = $to['name'] === null ? $to['address'] : "{$to['name']} <{$to['address']}>";
00502                 }
00503                 
00504                 foreach ($this->_cc as $cc) {
00505                         $data['Cc'][] = $cc['name'] === null ? $cc['address'] : "{$cc['name']} <{$cc['address']}>";
00506                 }
00507                 
00508                 foreach ($this->_bcc as $bcc) {
00509                         $data['Bcc'][] = $bcc['name'] === null ? $bcc['address'] : "{$bcc['name']} <{$bcc['address']}>";
00510                 }
00511                 
00512                 $data['To'] = implode(', ', $data['To']);
00513                 
00514                 if (empty($data['Cc'])) {
00515                         unset($data['Cc']);
00516                 } else {
00517                         $data['Cc'] = implode(', ', $data['Cc']);
00518                 }
00519                 
00520                 if (empty($data['Bcc'])) {
00521                         unset($data['Bcc']);
00522                 } else {
00523                         $data['Bcc'] = implode(', ', $data['Bcc']);
00524                 }
00525                 
00526                 if ($this->_replyTo !== null) {
00527                         $data['ReplyTo'] = $this->_replyTo['name'] === null ? $this->_replyTo['address'] : "{$this->_replyTo['name']} <{$this->_replyTo['address']}>";
00528                 }
00529                 
00530                 if ($this->_messageHtml !== null) {
00531                         $data['HtmlBody'] = $this->_messageHtml;
00532                 }
00533                 
00534                 if ($this->_messagePlain !== null) {
00535                         $data['TextBody'] = $this->_messagePlain;
00536                 }
00537 
00538                 if ($this->_tag !== null) {
00539                         $data['Tag'] = $this->_tag;
00540                 }
00541                 
00542                 if (!empty($this->_headers)) {
00543                         $data['Headers'] = array();
00544                         
00545                         foreach ($this->_headers as $name => $value) {
00546                                 $data['Headers'][] = array('Name' => $name, 'Value' => $value);
00547                         }
00548                 }
00549                 
00550                 if (!empty($this->_attachments)) {
00551                         $data['Attachments'] = array();
00552                         
00553                         foreach ($this->_attachments as $filename => $file) {
00554                                 $data['Attachments'][] = array(
00555                                         'Name' => $filename,
00556                                         'Content' => $file['content'],
00557                                         'ContentType' => $file['mimeType']
00558                                 );
00559                         }
00560                 }
00561                 
00562                 return $data;
00563         }
00564         
00570         private function _log($logData)
00571         {
00572                 if (class_exists('Mail_Postmark_Adapter')) {
00573                         Mail_Postmark_Adapter::log($logData);
00574                 }
00575         }
00576         
00580         private function _validateAddress($email)
00581         {
00582                 // http://php.net/manual/en/function.filter-var.php
00583                 // return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
00584                 // filter_var proved to be unworthy (passed foo..bar@domain.com as valid),
00585                 // and was therefore replace with
00586                 $regex = "/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i";
00587                 // from http://fightingforalostcause.net/misc/2006/compare-email-regex.php
00588                 return preg_match($regex, $email) === 1;
00589         }
00590         
00596         private function _validateData()
00597         {
00598                 if ($this->_from['address'] === null) {
00599                         throw new BadMethodCallException('From address is not set');
00600                 }
00601                 
00602                 if (empty($this->_to)) {
00603                         throw new BadMethodCallException('No To address is set');
00604                 }
00605                 
00606                 if (!isset($this->_subject)) {
00607                         throw new BadMethodCallException('Subject is not set');
00608                 }
00609         }
00610 }