diff --git a/README.md b/README.md index 3341d24..e5c339b 100644 --- a/README.md +++ b/README.md @@ -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). @@ -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. @@ -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. @@ -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. @@ -125,26 +125,29 @@ $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 @@ -152,7 +155,7 @@ 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 @@ -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 diff --git a/composer.json b/composer.json index 14e5035..7d13cc1 100644 --- a/composer.json +++ b/composer.json @@ -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": "*" diff --git a/src/SparkPostAdmin.php b/src/SparkPostAdmin.php index 01a5942..f4c826c 100644 --- a/src/SparkPostAdmin.php +++ b/src/SparkPostAdmin.php @@ -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; @@ -65,6 +66,7 @@ class SparkPostAdmin extends LeftAndMain implements PermissionProvider "doUninstallHook", "doInstallDomain", "doUninstallDomain", + "send_test", ]; private static $cache_enabled = true; @@ -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 */ @@ -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'), @@ -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); + } } } @@ -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; } @@ -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); } diff --git a/src/SparkPostHelper.php b/src/SparkPostHelper.php index 5706baa..1d697de 100644 --- a/src/SparkPostHelper.php +++ b/src/SparkPostHelper.php @@ -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 @@ -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; } diff --git a/src/SparkPostSwiftTransport.php b/src/SparkPostSwiftTransport.php index 47d8132..0fbbf4e 100644 --- a/src/SparkPostSwiftTransport.php +++ b/src/SparkPostSwiftTransport.php @@ -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; @@ -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)) { @@ -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(); @@ -247,10 +254,10 @@ 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(); @@ -258,7 +265,7 @@ protected function getMessagePrimaryContentType(Swift_Mime_Message $message) 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); @@ -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); @@ -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'); } @@ -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']); } @@ -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; diff --git a/src/api/SparkPostApiClient.php b/src/api/SparkPostApiClient.php index ff37391..5193ac2 100644 --- a/src/api/SparkPostApiClient.php +++ b/src/api/SparkPostApiClient.php @@ -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');