Skip to content

Commit

Permalink
support silverstripe 4.9 and swift mailer 6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
lekoala committed Oct 5, 2021
1 parent 4707af8 commit d36b172
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 44 deletions.
58 changes: 41 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@

Define in your .env file the following variable

SPARKPOST_API_KEY='YOUR_API_KEY_HERE'
SPARKPOST_API_KEY='YOUR_API_KEY_HERE'

or by defining the api key in your config.yml

```yaml
LeKoala\SparkPost\SparkPostHelper:
api_key: 'YOUR_API_KEY_HERE'
api_key: "YOUR_API_KEY_HERE"
```
This module uses a custom client (not the official PHP SDK).
Expand All @@ -33,7 +33,7 @@ You can also autoconfigure the module with the following environment variables
# Will log emails in the temp folders
SPARKPOST_ENABLE_LOGGING=true
# Will disable sending (useful in development)
SPARKPOST_SENDING_DISABLED=true
SPARKPOST_SENDING_DISABLED=true
By defining the Api Key, the module will register a new transport that will be used to send all emails.
Expand Down Expand Up @@ -64,7 +64,7 @@ with the set value using the following config flag:

```yaml
LeKoala\SparkPost\SparkPostHelper:
override_admin_email: true
override_admin_email: true
```
Make sure to set this after having processed the sparkpost config.
Expand All @@ -90,8 +90,8 @@ or through the YML config.

This module create a new admin section that allows you to:

- List all messages events and allow searching them
- Have a settings tab to list and configure sending domains and webhook
- List all messages events and allow searching them
- Have a settings tab to list and configure sending domains and webhook

NOTE : Make sure that you have a valid api key (not a subaccount key) to access
features related to installation of the webhook through the CMS.
Expand Down Expand Up @@ -125,34 +125,37 @@ $email->getSwiftMessage()->getHeaders()->addTextHeader('X-MSYS-API', json_encode

From the SparkPost Admin, you can setup a webhook for your website. This webhook
will be called and SparkPostController will take care of handling all events
for you. It is registered under the __sparkpost/ route.
for you. It is registered under the \_\_sparkpost/ route.

By default, SparkPostController will do nothing. Feel free to add your own
extensions to SparkPostController to define your own rules, like "Send an
email to the admin when a receive a spam complaint".

SparkPostController provides the following extension point for all events:
- onAnyEvent

- onAnyEvent

And the following extensions points depending on the type of the event:
- onEngagementEvent
- onGenerationEvent
- onMessageEvent
- onUnsubscribeEvent

- onEngagementEvent
- onGenerationEvent
- onMessageEvent
- onUnsubscribeEvent

You can also inspect the whole payload and the batch id with
- beforeProcessPayload : to check if a payload has been processed
- afterProcessPayload : to mark the payload has been processed or log information

You can test if your extension is working properly by visiting /__sparkpost/test
- beforeProcessPayload : to check if a payload has been processed
- afterProcessPayload : to mark the payload has been processed or log information

You can test if your extension is working properly by visiting /\_\_sparkpost/test
if your site is in dev mode. It will load sample data from the API.

Please ensure that the url for the webhook is properly configured if required
by using the following configuration

```yaml
LeKoala\SparkPost\SparkPostAdmin:
webhook_base_url: 'https://my.domain.com/'
webhook_base_url: "https://my.domain.com/"
```

You can also define the following environment variable to log all incoming payload into a given
Expand Down Expand Up @@ -195,10 +198,31 @@ LeKoala\SparkPost\SparkPostHelper:
inlineCss: true
```

## Swift Mailer 6

Swift Mailer 6 introduced quite a lot of breaking changes, make sure you are not using any of those:

- added Swift_Transport::ping()
- removed Swift_Mime_HeaderFactory, Swift_Mime_HeaderSet, Swift_Mime_Message, Swift_Mime_MimeEntity,
and Swift_Mime_ParameterizedHeader interfaces
- removed Swift_MailTransport and Swift_Transport_MailTransport
- removed Swift_Encoding
- removed the Swift_Transport_MailInvoker interface and Swift_Transport_SimpleMailInvoker class
- removed the Swift_SignedMessage class
- removed newInstance() methods everywhere
- methods operating on Date header now use DateTimeImmutable object instead of Unix timestamp;
Swift_Mime_Headers_DateHeader::getTimestamp()/setTimestamp() renamed to getDateTime()/setDateTime()
- bumped minimum version to PHP 7.0
- removed Swift_Validate and replaced by egulias/email-validator

## Compatibility
Tested with 4.x

Tested with SilverStripe 4.9+

For 4.x compatibility, use branch 2

For 3.x compatibility, use branch 1

## Maintainer

LeKoala - thomas@lekoala.be
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
],
"require": {
"php": ">7.2",
"silverstripe/framework": "^4.4",
"silverstripe/framework": "^4.9",
"symbiote/silverstripe-gridfieldextensions": "^3.2",
"pelago/emogrifier": "^5.0",
"composer/ca-bundle": "*"
Expand Down
40 changes: 35 additions & 5 deletions src/SparkPostAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Security\PermissionProvider;
use LeKoala\SparkPost\SparkPostSwiftTransport;
use SilverStripe\Security\DefaultAdminService;
use SilverStripe\Forms\GridField\GridFieldConfig;
use SilverStripe\Forms\GridField\GridFieldFooter;
use SilverStripe\Forms\GridField\GridFieldDetailForm;
Expand Down Expand Up @@ -65,6 +66,7 @@ class SparkPostAdmin extends LeftAndMain implements PermissionProvider
"doUninstallHook",
"doInstallDomain",
"doUninstallDomain",
"send_test",
];

private static $cache_enabled = true;
Expand Down Expand Up @@ -123,6 +125,25 @@ public function settings($request)
return parent::index($request);
}

public function send_test($request)
{
if (!$this->CanConfigureApi()) {
return $this->httpError(404);
}
$service = DefaultAdminService::create();
$to = $request->getVar('to');
if (!$to) {
$to = $service->findOrCreateDefaultAdmin()->Email;
}
$email = Email::create();
$email->setSubject("Test email");
$email->setBody("Test " . date('Y-m-d H:i:s'));
$email->setTo($to);

$result = $email->send();
var_dump($result);
}

/**
* @return Session
*/
Expand Down Expand Up @@ -171,6 +192,7 @@ public function getEditForm($id = null, $fields = null)
$messageListConfig
)->addExtraClass("messages_grid");

/** @var GridFieldDataColumns $columns */
$columns = $messageListConfig->getComponentByType(GridFieldDataColumns::class);
$columns->setDisplayFields([
'transmission_id' => _t('SparkPostAdmin.EventTransmissionId', 'Id'),
Expand All @@ -194,9 +216,11 @@ public function getEditForm($id = null, $fields = null)
}

if ($validator) {
$messageListConfig
->getComponentByType(GridFieldDetailForm::class)
->setValidator($validator);
/** @var GridFieldDetailForm $detailForm */
$detailForm = $messageListConfig->getComponentByType(GridFieldDetailForm::class);
if ($detailForm) {
$detailForm->setValidator($validator);
}
}
}

Expand Down Expand Up @@ -318,6 +342,9 @@ public function getCache()
*/
public function getCacheEnabled()
{
if (isset($_GET['disable_cache'])) {
return false;
}
if (Environment::getEnv('SPARKPOST_DISABLE_CACHE')) {
return false;
}
Expand Down Expand Up @@ -722,9 +749,12 @@ public function doInstallHook()
$url = $this->WebhookUrl();
$description = SiteConfig::current_site_config()->Title;

$defaultAdmin = Environment::getEnv('SS_DEFAULT_ADMIN_USERNAME');
$defaultPassword = Environment::getEnv('SS_DEFAULT_ADMIN_PASSWORD');

try {
if (defined('SS_DEFAULT_ADMIN_USERNAME') && SS_DEFAULT_ADMIN_USERNAME) {
$client->createSimpleWebhook($description, $url, null, true, ['username' => SS_DEFAULT_ADMIN_USERNAME, 'password' => SS_DEFAULT_ADMIN_PASSWORD]);
if ($defaultAdmin) {
$client->createSimpleWebhook($description, $url, null, true, ['username' => $defaultAdmin, 'password' => $defaultPassword]);
} else {
$client->createSimpleWebhook($description, $url);
}
Expand Down
3 changes: 2 additions & 1 deletion src/SparkPostHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use LeKoala\SparkPost\Api\SparkPostApiClient;
use LeKoala\SparkPost\SparkPostSwiftTransport;
use SilverStripe\Core\Config\Config;
use Swift_Mailer;

/**
* This configurable class helps decoupling the api client from SilverStripe
Expand Down Expand Up @@ -194,7 +195,7 @@ public static function registerTransport()
throw new Exception("Mailer must be an instance of " . SwiftMailer::class . " instead of " . get_class($mailer));
}
$transport = new SparkPostSwiftTransport($client);
$newSwiftMailer = $mailer->getSwiftMailer()->newInstance($transport);
$newSwiftMailer = new Swift_Mailer($transport);
$mailer->setSwiftMailer($newSwiftMailer);
return $mailer;
}
Expand Down
59 changes: 42 additions & 17 deletions src/SparkPostSwiftTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
use \Swift_MimePart;
use \Swift_Transport;
use \Swift_Attachment;
use \Swift_Mime_Message;
use \Swift_Mime_SimpleMessage;
use \Swift_Mime_Headers_UnstructuredHeader;
use \Swift_Events_SendEvent;
use \Swift_Mime_Header;
use Psr\Log\LoggerInterface;
use \Swift_Events_EventListener;
use SilverStripe\Control\Director;
Expand Down Expand Up @@ -89,12 +91,17 @@ public function stop()
$this->isStarted = false;
}

public function ping()
{
return true;
}

/**
* @param Swift_Mime_Message $message
* @param null $failedRecipients
* @param Swift_Mime_SimpleMessage $message
* @param string[] $failedRecipients
* @return int Number of messages sent
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$this->resultApi = null;
if ($event = $this->eventDispatcher->createSendEvent($this, $message)) {
Expand Down Expand Up @@ -151,11 +158,11 @@ public function send(Swift_Mime_Message $message, &$failedRecipients = null)
/**
* Log message content
*
* @param Swift_Mime_Message $message
* @param Swift_Mime_SimpleMessage $message
* @param array $results Results from the api
* @return void
*/
protected function logMessageContent(Swift_Mime_Message $message, $results = [])
protected function logMessageContent(Swift_Mime_SimpleMessage $message, $results = [])
{
$subject = $message->getSubject();
$body = $message->getBody();
Expand Down Expand Up @@ -247,18 +254,18 @@ protected function supportsContentType($contentType)
}

/**
* @param Swift_Mime_Message $message
* @param Swift_Mime_SimpleMessage $message
* @return string
*/
protected function getMessagePrimaryContentType(Swift_Mime_Message $message)
protected function getMessagePrimaryContentType(Swift_Mime_SimpleMessage $message)
{
$contentType = $message->getContentType();

if ($this->supportsContentType($contentType)) {
return $contentType;
}

// SwiftMailer hides the content type set in the constructor of Swift_Mime_Message as soon
// SwiftMailer hides the content type set in the constructor of Swift_Mime_SimpleMessage as soon
// as you add another part to the message. We need to access the protected property
// _userContentType to get the original type.
$messageRef = new \ReflectionClass($message);
Expand All @@ -271,14 +278,29 @@ protected function getMessagePrimaryContentType(Swift_Mime_Message $message)
return $contentType;
}

/**
* @param Swift_Mime_Headers_UnstructuredHeader|null $header
* @return string
*/
protected static function getHeaderValue(Swift_Mime_Header $header = null)
{
if (!$header) {
return '';
}
if (method_exists($header, 'getValue')) {
return $header->getValue();
}
return $header->getFieldBody();
}

/**
* Convert a Swift Message to a transmission
*
* @param Swift_Mime_Message $message
* @param Swift_Mime_SimpleMessage $message
* @return array SparkPost Send Message
* @throws \Swift_SwiftException
*/
public function getTransmissionFromMessage(Swift_Mime_Message $message)
public function getTransmissionFromMessage(Swift_Mime_SimpleMessage $message)
{
$contentType = $this->getMessagePrimaryContentType($message);

Expand Down Expand Up @@ -313,19 +335,20 @@ public function getTransmissionFromMessage(Swift_Mime_Message $message)

// Mandrill compatibility
// Data is merge with transmission and removed from headers
// @link https://mandrill.zendesk.com/hc/en-us/articles/205582467-How-to-Use-Tags-in-Mandrill
// @link https://mailchimp.com/developer/transactional/docs/tags-metadata/#tags
if ($message->getHeaders()->has('X-MC-Tags')) {
$tagsHeader = $message->getHeaders()->get('X-MC-Tags');
$tags = explode(',', $tagsHeader->getValue());
$tags = explode(',', self::getHeaderValue($tagsHeader));
$message->getHeaders()->remove('X-MC-Tags');
}
if ($message->getHeaders()->has('X-MC-Metadata')) {
$metadataHeader = $message->getHeaders()->get('X-MC-Metadata');
$metadata = json_decode($metadataHeader->getValue(), JSON_OBJECT_AS_ARRAY);
$metadata = json_decode(self::getHeaderValue($metadataHeader), JSON_OBJECT_AS_ARRAY);
$message->getHeaders()->remove('X-MC-Metadata');
}
if ($message->getHeaders()->has('X-MC-InlineCSS')) {
$inlineCss = $message->getHeaders()->get('X-MC-InlineCSS')->getValue();
$inlineHeader = $message->getHeaders()->get('X-MC-InlineCSS');
$inlineCss = self::getHeaderValue($inlineHeader);
$message->getHeaders()->remove('X-MC-InlineCSS');
}

Expand All @@ -334,7 +357,8 @@ public function getTransmissionFromMessage(Swift_Mime_Message $message)
// @link https://developers.sparkpost.com/api/smtp-api.html
$msysHeader = [];
if ($message->getHeaders()->has('X-MSYS-API')) {
$msysHeader = json_decode($message->getHeaders()->get('X-MSYS-API')->getValue(), JSON_OBJECT_AS_ARRAY);
$msysHeaderObj = $message->getHeaders()->get('X-MSYS-API');
$msysHeader = json_decode(self::getHeaderValue($msysHeaderObj), JSON_OBJECT_AS_ARRAY);
if (!empty($msysHeader['tags'])) {
$tags = array_merge($tags, $msysHeader['tags']);
}
Expand Down Expand Up @@ -434,7 +458,8 @@ public function getTransmissionFromMessage(Swift_Mime_Message $message)

// Custom unsubscribe list
if ($message->getHeaders()->has('List-Unsubscribe')) {
$headers['List-Unsubscribe'] = $message->getHeaders()->get('List-Unsubscribe')->getValue();
$unsubHeader = $message->getHeaders()->get('List-Unsubscribe');
$headers['List-Unsubscribe'] = self::getHeaderValue($unsubHeader);
}

$defaultParams = SparkPostHelper::config()->default_params;
Expand Down
3 changes: 0 additions & 3 deletions src/api/SparkPostApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,6 @@ public function __construct($key = null, $subaccount = null, $curlOpts = [])
$this->key = $key;
} else {
$this->key = getenv('SPARKPOST_API_KEY');
if (!$this->key && defined('SPARKPOST_API_KEY')) {
$this->key = SPARKPOST_API_KEY;
}
}
if (getenv('SPARKPOST_EU')) {
$this->euEndpoint = getenv('SPARKPOST_EU');
Expand Down

0 comments on commit d36b172

Please sign in to comment.