Campustream 1.0
A social network MQP for WPI
|
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 }