I know, I know, this is not how I’m supposed to do it, but I can't think of something better.

This commit is contained in:
2023-11-01 17:03:15 -04:00
commit 3b9d50d949
10 changed files with 1057 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
vendor/
.idea/

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Siteworx Professionals, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

69
README.md Normal file
View File

@@ -0,0 +1,69 @@
## Mail client for the siteworx pro email API
https://email.siteworxpro.com/api
Access to the API is restricted and requires an account.
`composer require siteworx/mail-client`
**Requires** PHP >7.0
Usage
```php
require 'vendor/autoload.php';
$transport = new Siteworx\Mail\Transports\ApiTransport([
'client_id' => 'k4ndk...4kkfa',
'client_secret' => 'Jdv4...4kvD'
]);
$client = new Siteworx\Mail\Client($transport);
$client->setSubject('Test Subject');
$client->setFrom('from@email.com');
$client->addTo('an@email.com');
$client->addTo('another@email.com');
$client->setBody('Test Message!');
$result = $client->send();
```
You can provide a cache to the api transport and your api token will
automatically be cached for it's lifetime.
```php
$memcache = new Memcache;
$memcache->addServer($host);
$transport = new Siteworx\Mail\Transports\ApiTransport([
'client_id' => 'k4ndk...4kkfa',
'client_secret' => 'Jdv4...4kvD'
]);
$transport->setCache($memcache);
```
You can use any cache that implements the PSR-6 CacheInterface.
**Catching Message**
You can catch message if you are testing by passing in the value of `true` to the send method
```php
$client->send(true);
```
The payload will be sent to the api and validated but will be caught before it is sent.
**Delaying Messages**
You can delay message so they are sent at a specific time.
```php
$time = new DateTime();
$time->add(new DateInterval('P1D'));
$client->sendTime($time);
```
This will send the email the next day.

24
composer.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "siteworx/mail-client",
"description": "api client for the siteworx professionals api",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Siteworx Pro",
"email": "websites@siteworxpro.com"
}
],
"autoload": {
"psr-4": {
"Siteworx\\Mail\\": "src/"
}
},
"minimum-stability": "dev",
"require": {
"php": ">=7.0.0",
"psr/log": "^1.0",
"psr/simple-cache": "^1.0",
"guzzlehttp/guzzle": "^6.3"
}
}

346
composer.lock generated Normal file
View File

@@ -0,0 +1,346 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "e6242594ada0bd472c34ec3bbac0645e",
"packages": [
{
"name": "guzzlehttp/guzzle",
"version": "6.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
"reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
"shasum": ""
},
"require": {
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.4",
"php": ">=5.5"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.0 || ^5.0",
"psr/log": "^1.0"
},
"suggest": {
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.2-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle is a PHP HTTP client library",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"rest",
"web service"
],
"time": "2017-06-22T18:50:49+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "09e549f5534380c68761260a71f847644d8f65aa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/09e549f5534380c68761260a71f847644d8f65aa",
"reference": "09e549f5534380c68761260a71f847644d8f65aa",
"shasum": ""
},
"require": {
"php": ">=5.5.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"time": "2017-05-20T23:14:18+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "811b676fbab9c99e359885032e5ebc70e442f5b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/811b676fbab9c99e359885032e5ebc70e442f5b8",
"reference": "811b676fbab9c99e359885032e5ebc70e442f5b8",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"request",
"response",
"stream",
"uri",
"url"
],
"time": "2017-07-17T09:11:21+00:00"
},
{
"name": "psr/http-message",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "psr/log",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2016-10-10T12:19:37+00:00"
},
{
"name": "psr/simple-cache",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/php-fig/simple-cache.git",
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/753fa598e8f3b9966c886fe13f370baa45ef0e24",
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\SimpleCache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interfaces for simple caching",
"keywords": [
"cache",
"caching",
"psr",
"psr-16",
"simple-cache"
],
"time": "2017-01-02T13:31:39+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.0.0"
},
"platform-dev": []
}

283
src/Client.php Normal file
View File

@@ -0,0 +1,283 @@
<?php
namespace Siteworx\Mail;
use Siteworx\Mail\Exceptions\ValidationException;
use Siteworx\Mail\Transports\TransportInterface;
/**
* Class Client
*
* @package Siteworx
*/
class Client
{
/**
* @var TransportInterface
*/
private $_transport;
/**
* @var array
*/
private $_to = [];
/**
* @var array
*/
private $_cc = [];
/**
* @var array
*/
private $_bcc = [];
/**
* @var array
*/
private $_files = [];
/**
* @var string
*/
private $_from = '';
/**
* @var string
*/
private $_subject = '(No Subject)';
/**
* @var string
*/
private $_body = '';
/**
* @var bool
*/
private $_isHtml = false;
/**
* @var bool
*/
private $_catch = false;
/**
* @var bool|\DateTimeInterface
*/
private $_sendTime = false;
/**
* Client constructor.
*
* @param TransportInterface $transport
*/
public function __construct(TransportInterface $transport)
{
$this->_transport = $transport;
}
/**
* @param string $to
* @throws ValidationException
*/
public function addTo(string $to)
{
if (!Validator::validateEmailAddress($to)) {
throw new ValidationException('Email address is invalid');
}
$this->_to[] = $to;
}
/**
* @param array $to
* @throws ValidationException
*/
public function setAllTo(array $to)
{
foreach ($to as $item) {
if (!Validator::validateEmailAddress($item)) {
throw new ValidationException('Email address is invalid');
}
}
$this->_to = $to;
}
/**
* @param string $cc
* @throws ValidationException
*/
public function addCc(string $cc)
{
if (!Validator::validateEmailAddress($cc)) {
throw new ValidationException('Email address is invalid');
}
$this->_cc[] = $cc;
}
/**
* @param string $bcc
* @throws ValidationException
*/
public function addBcc(string $bcc)
{
if (!Validator::validateEmailAddress($bcc)) {
throw new ValidationException('Email address is invalid');
}
$this->_bcc[] = $bcc;
}
/**
* @param string $body
* @param bool $isHtml
*/
public function setBody(string $body, bool $isHtml = false)
{
$this->_body = $body;
$this->_isHtml = $isHtml;
}
/**
* @param string $subject
*/
public function setSubject(string $subject)
{
$this->_subject = $subject;
}
/**
* @param string $from
*
* @throws ValidationException
*/
public function setFrom(string $from)
{
if (!Validator::validateEmailAddress($from)) {
throw new ValidationException('Email address is invalid');
}
$this->_from = $from;
}
/**
* @param bool $catch
* @throws ValidationException
* @return mixed
*/
public function send(bool $catch = false)
{
$this->_catch = $catch;
$payload = $this->_buildPayload();
return $this->_transport->sentMailPayload($payload);
}
/**
* @param string $uuid
* @return bool
*/
public function delete(string $uuid): bool
{
return $this->_transport->deleteEmail($uuid);
}
/**
* @throws ValidationException
* @return array
*/
private function _buildPayload(): array
{
$this->_validateFields();
$mailPayload = [
'Destination' => [
'ToAddresses' => $this->_to
],
'Message' => [
'Subject' => [
'Data' => $this->_subject
]
],
'Source' => $this->_from
];
if (!empty($this->_cc)) {
$mailPayload['Destination']['CcAddresses'] = $this->_cc;
}
if (!empty($this->_bcc)) {
$mailPayload['Destination']['BccAddresses'] = $this->_bcc;
}
if ($this->_isHtml) {
$mailPayload['Message']['Body']['Html']['Data'] = $this->_body;
$mailPayload['Message']['Body']['Text']['Data'] = htmlentities($this->_body);
} else {
$mailPayload['Message']['Body']['Text']['Data'] = $this->_body;
}
if ($this->_catch) {
$mailPayload['Catch'] = true;
}
if ($this->_sendTime !== false) {
$mailPayload['ScheduledTime'] = $this->_sendTime->format('Y-m-d H:i:s');
}
if (\count($this->_files) > 0) {
$files = [];
foreach ($this->_files as $file) {
$files[] = [
'FileName' => basename($file),
'Content' => base64_encode(file_get_contents($file))
];
}
$mailPayload['Attachments'] = $files;
}
return $mailPayload;
}
/**
* @throws ValidationException
*/
private function _validateFields()
{
if (empty($this->_to)) {
throw new ValidationException('To Address is required');
}
if (empty($this->_from)) {
throw new ValidationException('From Address is required');
}
}
/**
* @param string $fileLocation
*
* @throws ValidationException
*/
public function addAttachment(string $fileLocation)
{
if (!file_exists($fileLocation)) {
throw new ValidationException('File does not exist.');
}
$this->_files[] = $fileLocation;
}
/**
* @param \DateTimeInterface $sendTime
*/
public function sendTime(\DateTimeInterface $sendTime)
{
$this->_sendTime = $sendTime;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Siteworx\Mail\Exceptions;
use Throwable;
class ValidationException extends \Exception
{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,249 @@
<?php
namespace Siteworx\Mail\Transports;
use GuzzleHttp\Client as Guzzle;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ServerException;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
class ApiTransport implements TransportInterface
{
/**
* @var string
*/
private $_apiEndpoint = 'https://email.siteworxpro.com';
/**
* @var string
*/
private $_clientId = '';
/**
* @var string
*/
private $_clientSecret = '';
/**
* @var string
*/
private $_accessToken = '';
/**
* @var Guzzle
*/
private $_client;
/**
* @var CacheInterface
*/
private $_cache = null;
/**
* @var LoggerInterface
*/
private $_logger = null;
/**
* Client constructor.
*
* @param array $config
* @param Guzzle|null $guzzle
* @throws \Exception
*/
public function __construct(array $config = [], Guzzle $guzzle = null)
{
if (!isset($config['client_id'])) {
throw new \Exception('Client ID is missing.');
}
if (!isset($config['client_secret'])) {
throw new \Exception('Client Secret missing.');
}
$this->_client = $guzzle ?? new Guzzle();
$this->_clientId = $config['client_id'];
$this->_clientSecret = $config['client_secret'];
}
/**
* @param mixed $Cache
*/
public function setCache(CacheInterface $Cache)
{
$this->_cache = $Cache;
}
/**
* @param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger)
{
$this->_logger = $logger;
}
/**
* @return string
*/
public function getAccessToken(): string
{
return $this->_accessToken;
}
/**
* @param string $clientId
*/
public function setClientId(string $clientId)
{
$this->_clientId = $clientId;
}
/**
* @param string $clientSecret
*/
public function setClientSecret(string $clientSecret)
{
$this->_clientSecret = $clientSecret;
}
/**
* @param array $payload
* @return \stdClass
*/
public function sentMailPayload(array $payload)
{
$this->setToken();
if ($this->_logger !== null) {
$this->_logger->info('Sending Email.');
}
try {
$result = $this->_client->post($this->_apiEndpoint . '/api/email', [
'form_params' => $payload,
'headers' => [
'Authorization' => 'Bearer ' . $this->_accessToken
]
]);
$body = $result->getBody()->getContents();
$data = json_decode($body);
if ($this->_logger !== null) {
$this->_logger->info('Success!');
$this->_logger->debug(\json_encode($body));
}
} catch (ServerException $exception) {
$result = $exception->getResponse();
$body = $result->getBody()->getContents();
$data = json_decode($body);
if ($this->_logger !== null) {
$this->_logger->warning('An error occurred sending the email! (' . $result->getStatusCode() . ')');
$this->_logger->debug(\json_encode($body));
}
} catch (RequestException $exception) {
$result = $exception->getResponse();
$body = $result->getBody()->getContents();
$data = json_decode($body);
if ($this->_logger !== null) {
$this->_logger->warning('An error occurred sending the email! (' . $result->getStatusCode() . ')');
$this->_logger->debug(\json_encode($body));
}
}
return $data;
}
private function setToken()
{
if ($this->_cache !== null) {
$this->_accessToken = $this->_cache->get('access_token');
} else {
if ($this->_logger !== null) {
$this->_logger->notice('No cache available for client. Providing a cache interface can improve client response times.');
}
}
if (empty($this->_accessToken)) {
$this->refreshToken();
}
}
/**
* @return \stdClass
*/
private function refreshToken()
{
$params = [
'scope' => 'default',
'grant_type' => 'client_credentials',
'client_id' => $this->_clientId,
'client_secret' => $this->_clientSecret
];
try {
$result = $this->_client->post($this->_apiEndpoint . '/access_token', [
'form_params' => $params
]);
$body = $result->getBody()->getContents();
$data = json_decode($body);
$this->_accessToken = $data->access_token;
if ($this->_cache !== null) {
$this->_cache->set('access_token', $this->_accessToken, $data->expires_in);
}
} catch (ServerException $exception) {
$result = $exception->getResponse();
$body = $result->getBody()->getContents();
$data = json_decode($body);
} catch (RequestException $exception) {
$result = $exception->getResponse();
$body = $result->getBody()->getContents();
$data = json_decode($body);
}
return $data;
}
public function deleteEmail(string $uuid): bool
{
$this->setToken();
try {
$response = $this->_client->delete($this->_apiEndpoint .'/api/email/' . $uuid, [
'headers' => [
'Authorization' => 'Bearer ' . $this->_accessToken
]
]);
return $response->getStatusCode() === 200;
} catch (RequestException $exception) {
return false;
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Siteworx\Mail\Transports;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
interface TransportInterface
{
public function setCache(CacheInterface $cache);
public function setLogger(LoggerInterface $logger);
public function sentMailPayload(array $payload);
public function deleteEmail(string $uuid): bool;
}

31
src/Validator.php Normal file
View File

@@ -0,0 +1,31 @@
<?php
namespace Siteworx\Mail;
/**
* Class Validator
*
* @package Siteworx
*/
class Validator
{
/**
* @param string $email
* @return bool
*/
public static function validateEmailAddress(string $email): bool
{
return filter_var(self::extractEmailAddress($email), FILTER_VALIDATE_EMAIL) !== false;
}
private static function extractEmailAddress(string $email): string
{
$matches = [];
preg_match('^<[a-zA-Z@.\-_]+>^', $email, $matches);
return \count($matches) > 0 ? str_replace(['<', '>'], '', $matches[0]) : $email;
}
}