291 lines
7.8 KiB
PHP
291 lines
7.8 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Email
|
|
*
|
|
* A simple email handling class which supports
|
|
* multiple email services. Check out the email subfolder
|
|
* for all available services
|
|
*
|
|
* @package Kirby Toolkit
|
|
* @author Bastian Allgeier <bastian@getkirby.com>
|
|
* @link http://getkirby.com
|
|
* @copyright Bastian Allgeier
|
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
|
*/
|
|
class Email extends Obj {
|
|
|
|
const ERROR_INVALID_RECIPIENT = 0;
|
|
const ERROR_INVALID_SENDER = 1;
|
|
const ERROR_INVALID_REPLY_TO = 2;
|
|
const ERROR_INVALID_SUBJECT = 3;
|
|
const ERROR_INVALID_BODY = 4;
|
|
const ERROR_INVALID_SERVICE = 5;
|
|
const ERROR_DISABLED = 6;
|
|
|
|
public static $defaults = array(
|
|
'service' => 'mail',
|
|
'options' => array(),
|
|
'to' => null,
|
|
'from' => null,
|
|
'replyTo' => null,
|
|
'subject' => null,
|
|
'body' => null
|
|
);
|
|
|
|
public static $services = array();
|
|
public static $disabled = false;
|
|
|
|
public $error;
|
|
public $service;
|
|
public $options;
|
|
public $to;
|
|
public $from;
|
|
public $replyTo;
|
|
public $subject;
|
|
public $body;
|
|
|
|
public function __construct($params = array()) {
|
|
$options = a::merge(static::$defaults, $params);
|
|
parent::__construct($options);
|
|
}
|
|
|
|
public function __set($key, $value) {
|
|
$this->$key = $value;
|
|
}
|
|
|
|
/**
|
|
* Validates the constructed email
|
|
* to make sure it can be sent at all
|
|
*/
|
|
public function validate() {
|
|
if(!v::email($this->extractAddress($this->to))) throw new Error('Invalid recipient', static::ERROR_INVALID_RECIPIENT);
|
|
if(!v::email($this->extractAddress($this->from))) throw new Error('Invalid sender', static::ERROR_INVALID_SENDER);
|
|
if(!v::email($this->extractAddress($this->replyTo))) throw new Error('Invalid reply address', static::ERROR_INVALID_REPLY_TO);
|
|
if(empty($this->subject)) throw new Error('Missing subject', static::ERROR_INVALID_SUBJECT);
|
|
if(empty($this->body)) throw new Error('Missing body', static::ERROR_INVALID_BODY);
|
|
}
|
|
|
|
/**
|
|
* Public getter for the error exception
|
|
*
|
|
* @return Exception
|
|
*/
|
|
public function error() {
|
|
return $this->error;
|
|
}
|
|
|
|
/**
|
|
* Extracts the email address from an address string
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function extractAddress($string) {
|
|
if(v::email($string)) return $string;
|
|
preg_match('/<(.*?)>/i', $string, $array);
|
|
return (empty($array[1])) ? $string : $array[1];
|
|
}
|
|
|
|
/**
|
|
* Sends the constructed email
|
|
*
|
|
* @param array $params Optional way to set values for the email
|
|
* @return boolean
|
|
*/
|
|
public function send($params = null) {
|
|
|
|
try {
|
|
|
|
// fail silently if sending emails is disabled
|
|
if(static::$disabled) throw new Error('Sending emails is disabled', static::ERROR_DISABLED);
|
|
|
|
// overwrite already set values
|
|
if(is_array($params) && !empty($params)) {
|
|
foreach(a::merge($this->toArray(), $params) as $key => $val) {
|
|
$this->set($key, $val);
|
|
}
|
|
}
|
|
|
|
// reset all errors
|
|
$this->error = null;
|
|
|
|
// default service
|
|
if(empty($this->service)) $this->service = 'mail';
|
|
|
|
// if there's no dedicated reply to address, use the from address
|
|
if(empty($this->replyTo)) $this->replyTo = $this->from;
|
|
|
|
// validate the email
|
|
$this->validate();
|
|
|
|
// check if the email service is available
|
|
if(!isset(static::$services[$this->service])) {
|
|
throw new Error('The email service is not available: ' . $this->service, static::ERROR_INVALID_SERVICE);
|
|
}
|
|
|
|
// run the service
|
|
call(static::$services[$this->service], $this);
|
|
|
|
// reset the error
|
|
$this->error = null;
|
|
return true;
|
|
|
|
} catch(Exception $e) {
|
|
$this->error = $e;
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Default mail driver
|
|
*/
|
|
email::$services['mail'] = function($email) {
|
|
|
|
$headers = array(
|
|
'From: ' . $email->from,
|
|
'Reply-To: ' . $email->replyTo,
|
|
'Return-Path: ' . $email->replyTo,
|
|
'Message-ID: <' . time() . '-' . $email->from . '>',
|
|
'X-Mailer: PHP v' . phpversion(),
|
|
'Content-Type: text/plain; charset=utf-8',
|
|
'Content-Transfer-Encoding: 8bit',
|
|
);
|
|
|
|
ini_set('sendmail_from', $email->from);
|
|
$send = mail($email->to, str::utf8($email->subject), str::utf8($email->body), implode(PHP_EOL, $headers));
|
|
ini_restore('sendmail_from');
|
|
|
|
if(!$send) {
|
|
throw new Error('The email could not be sent');
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Amazon mail driver
|
|
*/
|
|
email::$services['amazon'] = function($email) {
|
|
|
|
if(empty($email->options['key'])) throw new Error('Missing Amazon API key');
|
|
if(empty($email->options['secret'])) throw new Error('Missing Amazon API secret');
|
|
|
|
$setup = array(
|
|
'Action' => 'SendEmail',
|
|
'Destination.ToAddresses.member.1' => $email->to,
|
|
'ReplyToAddresses.member.1' => $email->replyTo,
|
|
'ReturnPath' => $email->replyTo,
|
|
'Source' => $email->from,
|
|
'Message.Subject.Data' => $email->subject,
|
|
'Message.Body.Text.Data' => $email->body
|
|
);
|
|
|
|
$params = array();
|
|
|
|
foreach($setup as $key => $value) {
|
|
$params[] = $key . '=' . str_replace('%7E', '~', rawurlencode($value));
|
|
}
|
|
|
|
sort($params, SORT_STRING);
|
|
|
|
$host = a::get($email->options, 'host', 'email.us-east-1.amazonaws.com');
|
|
$url = 'https://' . $host . '/';
|
|
$date = gmdate('D, d M Y H:i:s e');
|
|
$signature = base64_encode(hash_hmac('sha256', $date, $email->options['secret'], true));
|
|
$query = implode('&', $params);
|
|
$headers = array();
|
|
$auth = 'AWS3-HTTPS AWSAccessKeyId=' . $email->options['key'];
|
|
$auth .= ',Algorithm=HmacSHA256,Signature=' . $signature;
|
|
|
|
$headers[] = 'Date: ' . $date;
|
|
$headers[] = 'Host: ' . $host;
|
|
$headers[] = 'X-Amzn-Authorization: '. $auth;
|
|
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
|
|
|
|
$email->response = remote::post($url, array(
|
|
'data' => $query,
|
|
'headers' => $headers
|
|
));
|
|
|
|
if(!in_array($email->response->code(), array(200, 201, 202, 204))) {
|
|
throw new Error('The mail could not be sent!', $email->response->code());
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Mailgun mail driver
|
|
*/
|
|
email::$services['mailgun'] = function($email) {
|
|
|
|
if(empty($email->options['key'])) throw new Error('Missing Mailgun API key');
|
|
if(empty($email->options['domain'])) throw new Error('Missing Mailgun API domain');
|
|
|
|
$url = 'https://api.mailgun.net/v2/' . $email->options['domain'] . '/messages';
|
|
$auth = base64_encode('api:' . $email->options['key']);
|
|
|
|
$headers = array(
|
|
'Accept: application/json',
|
|
'Authorization: Basic ' . $auth
|
|
);
|
|
|
|
$data = array(
|
|
'from' => $email->from,
|
|
'to' => $email->to,
|
|
'subject' => $email->subject,
|
|
'text' => $email->body,
|
|
'h:Reply-To' => $email->replyTo,
|
|
);
|
|
|
|
$email->response = remote::post($url, array(
|
|
'data' => $data,
|
|
'headers' => $headers
|
|
));
|
|
|
|
if($email->response->code() != 200) {
|
|
throw new Error('The mail could not be sent!');
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
* Postmark mail driver
|
|
*/
|
|
email::$services['postmark'] = function($email) {
|
|
|
|
if(empty($email->options['key'])) throw new Error('Invalid Postmark API Key');
|
|
|
|
// reset the api key if we are in test mode
|
|
if(a::get($email->options, 'test')) $email->options['key'] = 'POSTMARK_API_TEST';
|
|
|
|
// the url for postmarks api
|
|
$url = 'https://api.postmarkapp.com/email';
|
|
|
|
$headers = array(
|
|
'Accept: application/json',
|
|
'Content-Type: application/json',
|
|
'X-Postmark-Server-Token: ' . $email->options['key']
|
|
);
|
|
|
|
$data = array(
|
|
'From' => $email->from,
|
|
'To' => $email->to,
|
|
'ReplyTo' => $email->replyTo,
|
|
'Subject' => $email->subject,
|
|
'TextBody' => $email->body
|
|
);
|
|
|
|
// fetch the response
|
|
$email->response = remote::post($url, array(
|
|
'data' => json_encode($data),
|
|
'headers' => $headers
|
|
));
|
|
|
|
if($email->response->code() != 200) {
|
|
throw new Error('The mail could not be sent');
|
|
}
|
|
|
|
};
|