Source of file SendGrid.php
Size: 16,652 Bytes - Last Modified: 2021-12-23T10:57:21+00:00
/var/www/docs.ssmods.com/process/src/src/SendGrid.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635 | <?php namespace Vulcan\SendGrid; use SendGrid\Personalization; use SendGrid\Response; use SendGrid\SandBoxMode; use SilverStripe\Assets\File; use SilverStripe\Control\Controller; use SilverStripe\Control\Director; use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Injector\Injectable; use SilverStripe\ORM\ArrayLib; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\View\ArrayData; use Vulcan\SendGrid\Exceptions\SendGridException; /** * Class SendGrid * @package Vulcan\SendGrid * * @author Reece Alexander <reece@vulcandigital.co.nz> */ class SendGrid { use Injectable, Configurable; /** * @config * @var bool */ private static $api_key = false; /** * @var \SendGrid */ protected $sendGrid; /** * @var ArrayList */ protected $to; /** * @var string */ protected $subject; /** * @var string */ protected $from; /** * @var null|string */ protected $fromName = null; /** * @var string */ protected $replyTo; /** * @var string */ protected $templateId; /** * @var string */ protected $body; /** * @var ArrayList */ protected $attachments; /** * @var int */ protected $sendAt; /** * @var bool */ protected $sandbox = false; /** * @var ArrayList */ protected $customArgs; /** * SendGrid constructor. */ public function __construct() { $this->to = ArrayList::create(); $this->attachments = ArrayList::create(); $this->customArgs = ArrayList::create(); $this->sendGrid = new \SendGrid($this->getApiKey()); } /** * Send the email * * @throws SendGridException */ public function send() { $this->validate(); $from = new \SendGrid\Email($this->getFromName(), $this->getFrom()); $content = new \SendGrid\Content('text/html', $this->getBody()); $first = new \SendGrid\Email($this->getRecipients()->first()->Name, $this->getRecipients()->first()->Email); $mail = new \SendGrid\Mail($from, $this->getSubject(), $first, $content); if ($this->getReplyTo()) { $mail->setReplyTo($this->getReplyTo()); } $i = 0; foreach ($this->getRecipients() as $recipient) { if ($i == 0) { /** @var Personalization $personalization */ $personalization = $mail->personalization[0]; } else { $personalization = new \SendGrid\Personalization(); $to = new \SendGrid\Email($recipient->Name, $recipient->Email); $personalization->addTo($to); $personalization->setSubject($this->getSubject()); } foreach ($recipient->Personalizations as $map) { $personalization->addSubstitution($map['Key'], $map['Value']); } if ($i !== 0) { $mail->addPersonalization($personalization); } $i++; } foreach ($this->getAttachments() as $attachment) { $mail->addAttachment([ 'content' => $attachment->Content, 'type' => $attachment->Type, 'filename' => $attachment->Filename ]); } if ($this->getSchedule()) { $mail->setSendAt($this->getSchedule()); } if ($this->isSandbox()) { $settings = new \SendGrid\MailSettings(); $sandbox = new SandBoxMode(); $sandbox->setEnable(true); $settings->setSandboxMode($sandbox); $mail->setMailSettings($settings); } if ($this->getCustomArgs()->count()) { foreach ($this->getCustomArgs() as $customArg) { $mail->addCustomArg($customArg->Key, $customArg->Value); } } $mail->setTemplateId($this->getTemplateId()); /** @var Response $response */ $response = $this->sendGrid->client->mail()->send()->post($mail); // 2xx responses indicate success // 200 Your message is valid, but it is not queued to be delivered (sandbox only) // 202 Your message is both valid, and queued to be delivered. if (!in_array($response->statusCode(), [200, 202])) { $definition = $this->getErrorDefinition($response->statusCode()); throw new SendGridException(sprintf('[Response: %s - %s] %s', $definition->Code, $definition->Message, $definition->Reason)); } return true; } /** * Handles adding file attachments to the email * * @param File|string $file The file object to attach to the email, or an absolute path to a file * @param null|string $filename The name of the file, must include extension. Will default to current filename * @param bool $forcePublish Only applicable if the provided file is a {@link File} object. If the provided file is unpublished, * setting this to true will publish it forcibly, immediately * * @return $this */ public function addAttachment($file, $filename = null, $forcePublish = false) { if ($file instanceof File) { return $this->addFileAsAttachment($file, $filename, $forcePublish); } if (!file_exists($file)) { throw new \InvalidArgumentException("That file [$file] does not exist"); } if (!is_readable($file)) { throw new \InvalidArgumentException("That file [$file] exists, but is not readable"); } $this->attachments->push([ 'Content' => base64_encode(file_get_contents($file)), 'Type' => mime_content_type($file), 'Filename' => ($filename) ? $filename : basename($file), 'Size' => filesize($file) ]); return $this; } /** * Handles adding {@link File} objects as attachments * * @param File $file * @param $filename * @param $forcePublish * * @return $this */ private function addFileAsAttachment(File $file, $filename, $forcePublish) { if (!$file->isPublished()) { if (!$forcePublish) { throw new \InvalidArgumentException("That file [$file->Filename] is not published, and won't be visible to the recipient, please publish the image first or toggle the forcePublish parameter"); } $file->publishSingle(); } $path = Controller::join_links(Director::baseFolder(), $file->Link()); if (!file_exists($path)) { throw new \InvalidArgumentException("That attachments represents a file that does not exist [$path]"); } $contents = base64_encode(file_get_contents($path)); $this->attachments->push([ 'Content' => $contents, 'Type' => $file->getMimeType(), 'Filename' => ($filename) ? $filename : basename($file->Filename), 'Size' => $file->getAbsoluteSize() ]); return $this; } /** * Validate that the object is ready to send an email * * @throws \InvalidArgumentException * @return void */ public function validate() { if (!$this->getFrom()) { throw new \InvalidArgumentException('You must set the from address'); } if (!$this->getRecipients()->count()) { throw new \InvalidArgumentException('You must add a recipient'); } if (!$this->getSubject()) { throw new \InvalidArgumentException('You must provide a subject'); } if (!$this->getBody()) { throw new \InvalidArgumentException('You must provide a body'); } if ($this->attachments->count()) { $size = 0; foreach ($this->attachments as $attachment) { $size += $attachment->Size; } if ($size = round(($size / (1024 * 1024)) * 10) / 10 > 30) { throw new \RuntimeException("The total size of your attachments exceed SendGrid's imposed limit of 30 MB [Currently: $size MB]"); } } } /** * Get the recipient list * * @return ArrayList */ public function getRecipients() { return $this->to; } /** * Add a recipient * * @param string $to The recipients email address * * @param null $name The recipients name * @param null $personalizations Template substitutes. Eg ['-button_title-' => 'Log in now'] * * @return $this */ public function addRecipient($to, $name = null, $personalizations = null) { if ($personalizations && !ArrayLib::is_associative($personalizations)) { throw new \InvalidArgumentException('Personalizations should be an associative array'); } if (!filter_var($to, FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException("That is not a valid 'recipient' email address [$to]"); } if ($record = $this->to->find('Email', $to)) { $record->Personalizations = $personalizations; $record->Name = $name; return $this; } $data = []; foreach ($personalizations as $k => $v) { $data[] = [ 'Key' => $k, 'Value' => $v ]; } $this->to->push(ArrayData::create([ 'Email' => $to, 'Name' => $name, 'Personalizations' => $data ])); return $this; } /** * @return string */ public function getTemplateId() { return $this->templateId; } /** * @param string $templateId * * @return $this */ public function setTemplateId($templateId) { $this->templateId = $templateId; return $this; } /** * Delay sending of the email until the specified time. It is important that you have * specified your correct timezone in your SendGrid account's settings, otherwise this may have * unexpected results. * * @param int|DBDatetime $timestamp * * @return $this */ public function setScheduleTo($timestamp) { if ($timestamp instanceof DBDatetime) { $timestamp = $timestamp->getTimestamp(); } if ($timestamp <= DBDatetime::now()->getTimestamp()) { throw new \RuntimeException('The scheduled date must be in the future. Please check your timezone settings'); } $this->sendAt = $timestamp; return $this; } /** * @return int */ public function getSchedule() { return $this->sendAt; } /** * @return string */ public function getSubject() { return $this->subject; } /** * @param string $subject * * @return $this */ public function setSubject($subject) { $this->subject = $subject; return $this; } /** * @return string */ public function getFromName() { return $this->fromName; } /** * @param string $fromName * * @return $this */ public function setFromName($fromName) { $this->fromName = $fromName; return $this; } /** * @return string */ public function getFrom() { return $this->from; } /** * @param $from * * @return $this */ public function setFrom($from) { if (!filter_var($from, FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException("That is not a valid 'from' email address [$from]"); } $this->from = $from; return $this; } /** * @return string */ public function getBody() { return $this->body; } /** * @param string|DBHTMLText $body * * @return $this */ public function setBody($body) { if ($body instanceof DBHTMLText) { $this->body = $body->RAW(); return $this; } $this->body = $body; return $this; } /** * @return string */ public function getReplyTo() { return $this->replyTo; } /** * @param string $replyTo * * @return $this */ public function setReplyTo($replyTo) { if (!filter_var($replyTo, FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException("That is not a valid 'reply-to' email address [$replyTo]"); } $this->replyTo = $replyTo; return $this; } /** * @return string */ public function getApiKey() { $key = $this->config()->get('api_key'); if (!$key) { throw new \RuntimeException('api_key must be defined'); } return $key; } /** * @return ArrayList */ public function getErrorMap() { $map = [ ArrayData::create(['Code' => 200, 'Message' => 'OK', 'Reason' => 'Your message is valid, but it is not queued to be delivered.']), ArrayData::create(['Code' => 202, 'Message' => 'ACCEPTED', 'Reason' => 'Your message is both valid, and queued to be delivered.']), ArrayData::create(['Code' => 400, 'Message' => 'BAD REQUEST', 'Reason' => '']), ArrayData::create(['Code' => 401, 'Message' => 'UNAUTHORIZED', 'Reason' => 'You do not have authorization to make the request.']), ArrayData::create(['Code' => 403, 'Message' => 'FORBIDDEN', 'Reason' => '']), ArrayData::create(['Code' => 404, 'Message' => 'NOT FOUND', 'Reason' => 'The resource you tried to locate could not be found or does not exist.']), ArrayData::create(['Code' => 405, 'Message' => 'METHOD NOT ALLOWED', 'Reason' => '']), ArrayData::create(['Code' => 413, 'Message' => 'PAYLOAD TOO LARGE', 'Reason' => 'The JSON payload you have included in your request is too large.']), ArrayData::create(['Code' => 415, 'Message' => 'UNSUPPORTED MEDIA TYPE', 'Reason' => '']), ArrayData::create(['Code' => 429, 'Message' => 'TOO MANY REQUESTS', 'Reason' => 'The number of requests you have made exceeds SendGrid’s rate limitations']), ArrayData::create(['Code' => 500, 'Message' => 'SERVER UNAVAILABLE', 'Reason' => 'An error occurred on a SendGrid server.']), ArrayData::create(['Code' => 503, 'Message' => 'SERVICE NOT AVAILABLE', 'Reason' => 'The SendGrid v3 Web API is not available.']), ]; return ArrayList::create($map); } /** * @param $code * * @return ArrayData */ public function getErrorDefinition($code) { $record = $this->getErrorMap()->find('Code', $code); if (!$record) { throw new \InvalidArgumentException('That code is not a response code you would receive from SendGrid'); } return $record; } /** * @return ArrayList */ public function getAttachments() { return $this->attachments; } /** * @return bool */ public function isSandbox() { return $this->sandbox; } /** * @param bool $sandbox * * @return $this */ public function setSandboxMode($sandbox) { $this->sandbox = $sandbox; return $this; } /** * Return the customArgs {@link ArrayList} * * @return ArrayList */ public function getCustomArgs() { return $this->customArgs; } /** * Add a global substitution that applies to all recipients unless overridden with * personalization * * @param string $key * @param string $value * * @throws \RuntimeException * * @return $this */ public function addCustomArg($key, $value) { if ($this->customArgs->find('Key', $key)) { throw new \RuntimeException("A customArg already exists with that key [$key]"); } $this->customArgs->push(ArrayData::create([ 'Key' => $key, 'Value' => $value ])); return $this; } } |