From 25c25ceadd490384a03214311111f50d399dbf3e Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 5 Feb 2024 17:04:48 +0100 Subject: [PATCH] make phpstan happy --- .travis.yml | 68 ------------ src/EmailUtils.php | 7 +- src/SparkPostAdmin.php | 169 +++++++++++++++++++++-------- src/SparkPostApiTransport.php | 59 ++++++---- src/SparkPostController.php | 44 ++++++-- src/SparkPostHelper.php | 47 ++++++-- src/api/SparkPostApiClient.php | 192 +++++++++++++++++++-------------- 7 files changed, 352 insertions(+), 234 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f16d7f1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,68 +0,0 @@ -language: php - -os: linux - -dist: xenial - -services: - - mysql - - postgresql - -cache: - directories: - - $HOME/.composer/cache/files - -env: - global: - - SS_ENVIRONMENT_TYPE=dev - - SS_BASE_URL="http://localhost:8080/" - -jobs: - fast_finish: true - include: - - php: 7.4 - env: - - DB=PGSQL - - PDO=1 - - PHPCS_TEST=1 - - PHPUNIT_TEST=1 - - PHPUNIT_COVERAGE_TEST=1 - - php: 8.0 - env: - - DB=MYSQL - - PDO=1 - - php: 8.1 - env: - - DB=MYSQL - - PDO=1 - -before_script: - # COMPOSER - # install $COMPOSER_VERSION if defined, otherwise use Composer v1 with PHP <= 7.3, Composer v2 for >= 7.3 - - if [ $COMPOSER_VERSION ] ; then composer self-update --$COMPOSER_VERSION ; elif [ $(php -r 'echo (int) version_compare(phpversion(), "7.3.0", "<=");') = "1" ] ; then composer self-update --1; else composer self-update --2; fi - - composer --version - - # PHPENV - - phpenv rehash - - phpenv config-rm xdebug.ini || true - - if [[ $PHPUNIT_COVERAGE_TEST ]]; then memlimit='8192M'; else memlimit='4096M'; fi - - echo "memory_limit = ${memlimit}" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - - echo 'always_populate_raw_post_data = -1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini - - # Install composer dependencies - - composer validate - - if [[ $DB == PGSQL ]]; then composer require silverstripe/postgresql:^2 --no-update; fi - - if [[ $DB == SQLITE ]]; then composer require silverstripe/sqlite3:^2 --no-update; fi - - if [[ $PHPCS_TEST ]]; then composer global require squizlabs/php_codesniffer:^3 --prefer-dist --no-interaction --no-progress --no-suggest -o; fi - - composer install --prefer-source --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile - - # Log constants to CI for debugging purposes - - php vendor/silverstripe/framework/tests/dump_constants.php - -script: - - if [[ $PHPUNIT_TEST ]]; then vendor/bin/phpunit; fi - - if [[ $PHPCS_TEST ]]; then composer run-script lint; fi - - if [[ $PHPUNIT_COVERAGE_TEST ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=coverage.xml; fi - -after_success: - - if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi diff --git a/src/EmailUtils.php b/src/EmailUtils.php index 3ce26d8..bbb4706 100644 --- a/src/EmailUtils.php +++ b/src/EmailUtils.php @@ -9,7 +9,7 @@ class EmailUtils { /** - * Inline styles using Pelago Emogrifier V6 + * Inline styles using Pelago Emogrifier V7 * * This is much better than the functionnality provided by SparkPost anyway * @@ -90,10 +90,9 @@ public static function get_email_from_rfc_email($rfc_email_string) } /** - * Useful when dealing with legacy code - * + * @deprecated * @param \SilverStripe\Control\Email\Email $Email - * @return \Symfony\Component\Mime\Header\Headers|Swift_Mime_SimpleHeaderSet + * @return \Symfony\Component\Mime\Header\Headers */ public static function getHeaders($Email) { diff --git a/src/SparkPostAdmin.php b/src/SparkPostAdmin.php index 319656a..40156b2 100644 --- a/src/SparkPostAdmin.php +++ b/src/SparkPostAdmin.php @@ -8,6 +8,7 @@ use SilverStripe\Forms\TabSet; use SilverStripe\ORM\ArrayLib; use SilverStripe\ORM\ArrayList; +use SilverStripe\ORM\DataObject; use SilverStripe\View\ArrayData; use SilverStripe\Control\Session; use SilverStripe\Forms\DateField; @@ -23,9 +24,11 @@ use SilverStripe\View\ViewableData; use SilverStripe\Forms\LiteralField; use SilverStripe\Control\Email\Email; +use SilverStripe\Control\HTTPRequest; use SilverStripe\Forms\DropdownField; use SilverStripe\Security\Permission; use LeKoala\SparkPost\SparkPostHelper; +use SilverStripe\Control\HTTPResponse; use SilverStripe\Forms\CompositeField; use SilverStripe\SiteConfig\SiteConfig; use SilverStripe\Forms\GridField\GridField; @@ -46,15 +49,33 @@ */ class SparkPostAdmin extends LeftAndMain implements PermissionProvider { - const MESSAGE_CACHE_MINUTES = 5; const WEBHOOK_CACHE_MINUTES = 1440; // 1 day const SENDINGDOMAIN_CACHE_MINUTES = 1440; // 1 day + /** + * @var string + */ private static $menu_title = "SparkPost"; + + /** + * @var string + */ private static $url_segment = "sparkpost"; + + /** + * @var string + */ private static $menu_icon = "sparkpost/images/sparkpost-icon.png"; + + /** + * @var string + */ private static $url_rule = '/$Action/$ID/$OtherID'; + + /** + * @var array + */ private static $allowed_actions = [ 'settings', 'SearchForm', @@ -66,6 +87,9 @@ class SparkPostAdmin extends LeftAndMain implements PermissionProvider "send_test", ]; + /** + * @var boolean + */ private static $cache_enabled = true; /** @@ -74,19 +98,19 @@ class SparkPostAdmin extends LeftAndMain implements PermissionProvider protected $subaccountKey = false; /** - * @var Exception + * @var Exception|null */ - protected $lastException; + protected $lastException = null; /** - * @var ViewableData + * @var ViewableData|null */ - protected $currentMessage; + protected $currentMessage = null; /** * Inject public dependencies into the controller * - * @var array + * @var array */ private static $dependencies = [ 'logger' => '%$Psr\Log\LoggerInterface', @@ -94,15 +118,18 @@ class SparkPostAdmin extends LeftAndMain implements PermissionProvider ]; /** - * @var Psr\Log\LoggerInterface + * @var \Psr\Log\LoggerInterface */ public $logger; /** - * @var Psr\SimpleCache\CacheInterface + * @var \Psr\SimpleCache\CacheInterface */ public $cache; + /** + * @return void + */ public function init() { parent::init(); @@ -112,11 +139,19 @@ public function init() } } + /** + * @param HTTPRequest $request + * @return HTTPResponse + */ public function settings($request) { return parent::index($request); } + /** + * @param HTTPRequest $request + * @return HTTPResponse|null + */ public function send_test($request) { if (!$this->CanConfigureApi()) { @@ -132,8 +167,9 @@ public function send_test($request) $email->setBody("Test " . date('Y-m-d H:i:s')); $email->setTo($to); - $result = $email->send(); - var_dump($result); + $email->send(); + var_dump('Email sent!'); + return null; } /** @@ -146,7 +182,9 @@ public function getSession() /** * Returns a GridField of messages - * @return CMSForm + * @param mixed $id + * @param mixed $fields + * @return null|Form */ public function getEditForm($id = null, $fields = null) { @@ -156,6 +194,7 @@ public function getEditForm($id = null, $fields = null) $form = parent::getEditForm($id); + /** @var DataObject|null $record */ $record = $this->getRecord($id); // Check if this record is viewable @@ -208,7 +247,7 @@ public function getEditForm($id = null, $fields = null) } if ($validator) { - /** @var GridFieldDetailForm $detailForm */ + /** @var GridFieldDetailForm|null $detailForm */ $detailForm = $messageListConfig->getComponentByType(GridFieldDetailForm::class); if ($detailForm) { $detailForm->setValidator($validator); @@ -223,16 +262,15 @@ public function getEditForm($id = null, $fields = null) $this->SearchFields(), $messagesList, // necessary for tree node selection in LeftAndMain.EditForm.js - new HiddenField('ID', false, 0) + new HiddenField('ID', null, 0) ); $fields = new FieldList([ $root = new TabSet('Root', $messagesTab) ]); + $settingsTab = new Tab('Settings', _t('SparkPostAdmin.Settings', 'Settings')); if ($this->CanConfigureApi()) { - $settingsTab = new Tab('Settings', _t('SparkPostAdmin.Settings', 'Settings')); - $domainTabData = $this->DomainTab(); $settingsTab->push($domainTabData); @@ -312,7 +350,7 @@ public function getEditForm($id = null, $fields = null) /** * Get logger * - * @return Psr\Log\LoggerInterface + * @return \Psr\Log\LoggerInterface */ public function getLogger() { @@ -322,7 +360,7 @@ public function getLogger() /** * Get the cache * - * @return Psr\SimpleCache\CacheInterface + * @return \Psr\SimpleCache\CacheInterface */ public function getCache() { @@ -351,9 +389,9 @@ public function getCacheEnabled() * A simple cache helper * * @param string $method - * @param array $params + * @param array|null|string|bool $params * @param int $expireInSeconds - * @return array + * @return array|false */ protected function getCachedData($method, $params, $expireInSeconds = 60) { @@ -384,6 +422,9 @@ protected function getCachedData($method, $params, $expireInSeconds = 60) return $data; } + /** + * @return array + */ public function getParams() { $params = $this->config()->default_search_params; @@ -399,9 +440,11 @@ public function getParams() // Respect api formats if (!empty($params['to'])) { + //@phpstan-ignore-next-line $params['to'] = date('Y-m-d', strtotime(str_replace('/', '-', $params['to']))) . 'T00:00'; } if (!empty($params['from'])) { + //@phpstan-ignore-next-line $params['from'] = date('Y-m-d', strtotime(str_replace('/', '-', $params['from']))) . 'T23:59'; } @@ -410,6 +453,11 @@ public function getParams() return $params; } + /** + * @param string $name + * @param mixed $default + * @return mixed + */ public function getParam($name, $default = null) { $data = $this->getSession()->get(__class__ . '.Search'); @@ -419,6 +467,9 @@ public function getParam($name, $default = null) return (isset($data[$name]) && strlen($data[$name])) ? $data[$name] : $default; } + /** + * @return CompositeField + */ public function SearchFields() { $disabled_filters = $this->config()->disabled_search_filters; @@ -467,6 +518,9 @@ public function SearchFields() return $fields; } + /** + * @return Form + */ public function SearchForm() { $SearchForm = new Form($this, 'SearchForm', new FieldList(), new FieldList([ @@ -476,6 +530,11 @@ public function SearchForm() return $SearchForm; } + /** + * @param array $data + * @param Form $form + * @return HTTPResponse + */ public function doSearch($data, Form $form) { $post = $this->getRequest()->postVar('params'); @@ -554,7 +613,7 @@ public function Messages() /** * Provides custom permissions to the Security section * - * @return array + * @return array */ public function providePermissions() { @@ -619,7 +678,7 @@ protected function FormGroupHelper($html) */ public function IsAdmin() { - return Permission::check("ADMIN"); + return boolval(Permission::check("ADMIN")); } /** @@ -635,11 +694,10 @@ public function canView($member = null) if (!($transport instanceof SparkPostApiTransport)) { return false; } - return Permission::check("CMS_ACCESS_SparkPost", 'any', $member); + return boolval(Permission::check("CMS_ACCESS_SparkPost", 'any', $member)); } /** - * * @return bool */ public function CanConfigureApi() @@ -650,7 +708,7 @@ public function CanConfigureApi() /** * Check if webhook is installed * - * @return array|false + * @return array|false */ public function WebhookInstalled() { @@ -689,7 +747,7 @@ public function WebhookInstalled() /** * Hook details for template - * @return \ArrayData + * @return ArrayData|null */ public function WebhookDetails() { @@ -697,6 +755,7 @@ public function WebhookDetails() if ($el) { return new ArrayData($el); } + return null; } /** @@ -710,7 +769,7 @@ public function WebhookTab() if ($webhook) { return $this->UninstallHookForm($webhook); } - return $this->InstallHookForm($webhook); + return $this->InstallHookForm(); } /** @@ -722,7 +781,10 @@ public function WebhookUrl() return rtrim(self::config()->webhook_base_url, '/') . '/sparkpost/incoming'; } if (Director::isLive()) { - return Director::absoluteURL('/sparkpost/incoming'); + $absurl = Director::absoluteURL('/sparkpost/incoming'); + if ($absurl && is_string($absurl)) { + return $absurl; + } } $protocol = Director::protocol(); return $protocol . $this->getDomain() . '/sparkpost/incoming'; @@ -731,10 +793,9 @@ public function WebhookUrl() /** * Install hook form * - * @param array $data * @return FormField */ - public function InstallHookForm($data) + public function InstallHookForm() { $fields = new CompositeField(); $fields->push(new LiteralField('Info', $this->MessageHelper( @@ -748,6 +809,9 @@ public function InstallHookForm($data) return $fields; } + /** + * @return HTTPResponse + */ public function doInstallHook() { if (!$this->CanConfigureApi()) { @@ -784,7 +848,7 @@ public function doInstallHook() /** * Uninstall hook form * - * @param array $data + * @param array $data * @return FormField */ public function UninstallHookForm($data) @@ -824,6 +888,11 @@ public function UninstallHookForm($data) return $fields; } + /** + * @param array $data + * @param Form $form + * @return HTTPResponse + */ public function doUninstallHook($data, Form $form) { if (!$this->CanConfigureApi()) { @@ -848,7 +917,7 @@ public function doUninstallHook($data, Form $form) /** * Check if sending domain is installed * - * @return array + * @return array|false */ public function SendingDomainInstalled() { @@ -863,16 +932,18 @@ public function SendingDomainInstalled() /** * Trigger request to check if sending domain is verified * - * @return array + * @return array|false */ public function VerifySendingDomain() { $client = SparkPostHelper::getClient(); $host = $this->getDomain(); + if (!$host && is_bool($host)) { + return false; + } $verification = $client->verifySendingDomain($host); - if (empty($verification)) { return false; } @@ -929,7 +1000,7 @@ public function DomainTab() } /** - * @return string + * @return ?string */ public function InboundUrl() { @@ -938,7 +1009,7 @@ public function InboundUrl() if ($domain) { return $subdomain . '.' . $domain; } - return false; + return null; } /** @@ -953,6 +1024,9 @@ public function getDomainFromHost() $base = Director::protocolAndHost(); } $host = parse_url($base, PHP_URL_HOST); + if (!$host) { + return false; + } $hostParts = explode('.', $host); $parts = count($hostParts); if ($parts < 2) { @@ -971,7 +1045,10 @@ public function getDomainFromEmail() { $email = SparkPostHelper::resolveDefaultFromEmail(null, false); if ($email) { - $domain = substr(strrchr($email, "@"), 1); + $domain = substr((string)strrchr($email, "@"), 1); + if (!$domain) { + return false; + } return $domain; } return false; @@ -994,8 +1071,8 @@ public function getDomain() /** * Install domain form * - * @param CompositeField $fieldsd - * @return FormField + * @param CompositeField $fields + * @return void */ public function InstallDomainForm(CompositeField $fields) { @@ -1011,6 +1088,9 @@ public function InstallDomainForm(CompositeField $fields) ))); } + /** + * @return HTTPResponse + */ public function doInstallDomain() { if (!$this->CanConfigureApi()) { @@ -1021,7 +1101,7 @@ public function doInstallDomain() $domain = $this->getDomain(); - if (!$domain) { + if (!$domain || is_bool($domain)) { return $this->redirectBack(); } @@ -1038,8 +1118,8 @@ public function doInstallDomain() /** * Uninstall domain form * - * @param CompositeField $fieldsd - * @return FormField + * @param CompositeField $fields + * @return void */ public function UninstallDomainForm(CompositeField $fields) { @@ -1065,6 +1145,11 @@ public function UninstallDomainForm(CompositeField $fields) ))); } + /** + * @param array $data + * @param Form $form + * @return HTTPResponse + */ public function doUninstallDomain($data, Form $form) { if (!$this->CanConfigureApi()) { @@ -1075,7 +1160,7 @@ public function doUninstallDomain($data, Form $form) $domain = $this->getDomain(); - if (!$domain) { + if (!$domain || is_bool($domain)) { return $this->redirectBack(); } diff --git a/src/SparkPostApiTransport.php b/src/SparkPostApiTransport.php index 3eabb3c..1d7239d 100644 --- a/src/SparkPostApiTransport.php +++ b/src/SparkPostApiTransport.php @@ -16,6 +16,7 @@ use Symfony\Contracts\HttpClient\ResponseInterface; use Symfony\Component\Mime\Header\UnstructuredHeader; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Component\Mime\Header\ParameterizedHeader; use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\Mailer\Transport\AbstractApiTransport; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -38,8 +39,17 @@ class SparkPostApiTransport extends AbstractApiTransport */ private $apiClient; + /** + * @var array + */ private $apiResult; + /** + * @param SparkPostApiClient $apiClient + * @param HttpClientInterface|null $client + * @param EventDispatcherInterface|null $dispatcher + * @param LoggerInterface|null $logger + */ public function __construct(SparkPostApiClient $apiClient, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) { $this->apiClient = $apiClient; @@ -98,24 +108,35 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e return $response; } + /** + * @return array + */ public function getApiResult(): array { return $this->apiResult; } - private function getEndpoint(): ?string + /** + * @return string + */ + private function getEndpoint(): string { return ($this->host ?: self::HOST) . ($this->port ? ':' . $this->port : ''); } + /** + * @param Email $email + * @return array> + */ private function buildAttachments(Email $email): array { $result = []; foreach ($email->getAttachments() as $attachment) { + $preparedHeaders = $attachment->getPreparedHeaders(); /** @var ParameterizedHeader $file */ - $file = $attachment->getPreparedHeaders()->get('Content-Disposition'); + $file = $preparedHeaders->get('Content-Disposition'); /** @var ParameterizedHeader $type */ - $type = $attachment->getPreparedHeaders()->get('Content-Type'); + $type = $preparedHeaders->get('Content-Type'); $result[] = [ 'name' => $file->getParameter('filename'), @@ -130,7 +151,7 @@ private function buildAttachments(Email $email): array /** * @param Email $email * @param Envelope $envelope - * @return array + * @return array */ private function getPayload(Email $email, Envelope $envelope): array { @@ -198,7 +219,7 @@ private function getPayload(Email $email, Envelope $envelope): array } if ($emailHeaders->has('X-MC-Metadata')) { $metadataHeader = $emailHeaders->get('X-MC-Metadata'); - $metadata = json_decode(self::getHeaderValue($metadataHeader), JSON_OBJECT_AS_ARRAY); + $metadata = json_decode(self::getHeaderValue($metadataHeader), true); $emailHeaders->remove('X-MC-Metadata'); } if ($emailHeaders->has('X-MC-InlineCSS')) { @@ -213,7 +234,7 @@ private function getPayload(Email $email, Envelope $envelope): array $msysHeader = []; if ($emailHeaders->has('X-MSYS-API')) { $msysHeaderObj = $emailHeaders->get('X-MSYS-API'); - $msysHeader = json_decode(self::getHeaderValue($msysHeaderObj), JSON_OBJECT_AS_ARRAY); + $msysHeader = json_decode(self::getHeaderValue($msysHeaderObj), true); if (!empty($msysHeader['tags'])) { $tags = array_merge($tags, $msysHeader['tags']); } @@ -262,22 +283,23 @@ private function getPayload(Email $email, Envelope $envelope): array $bcc[] = array( 'email' => $bccEmail, 'name' => $bccName, - 'header_to' => $primaryEmail ? $primaryEmail : $ccEmail, + 'header_to' => $primaryEmail ? $primaryEmail : $bccEmail, ); } $bodyHtml = $email->getHtmlBody(); $bodyText = $email->getTextBody(); + if ($bodyHtml && is_string($bodyHtml)) { + // If we ask to provide plain, use our custom method instead of the provided one + if (SparkPostHelper::config()->provide_plain) { + $bodyText = EmailUtils::convert_html_to_text($bodyHtml); + } - // If we ask to provide plain, use our custom method instead of the provided one - if ($bodyHtml && SparkPostHelper::config()->provide_plain) { - $bodyText = EmailUtils::convert_html_to_text($bodyHtml); - } - - // Should we inline css - if (!$inlineCss && SparkPostHelper::config()->inline_styles) { - $bodyHtml = EmailUtils::inline_styles($bodyHtml); + // Should we inline css + if (!$inlineCss && SparkPostHelper::config()->inline_styles) { + $bodyHtml = EmailUtils::inline_styles($bodyHtml); + } } // Custom unsubscribe list @@ -351,8 +373,9 @@ protected static function getHeaderValue(HeaderInterface $header = null) * Log message content * * @param Email $message - * @param array $results Results from the api + * @param array $results Results from the api * @throws Exception + * @return void */ protected function logMessageContent(Email $message, $results = []) { @@ -382,9 +405,7 @@ protected function logMessageContent(Email $message, $results = []) $logContent .= 'Subject : ' . $subject . "\n"; $logContent .= 'From : ' . print_r($message->getFrom(), true) . "\n"; $logContent .= 'Headers:' . "\n" . $emailHeaders->toString() . "\n"; - if (!empty($params['recipients'])) { - $logContent .= 'Recipients : ' . print_r($message->getTo(), true) . "\n"; - } + $logContent .= 'Recipients : ' . print_r($message->getTo(), true) . "\n"; $logContent .= 'Results:' . "\n"; $logContent .= print_r($results, true) . "\n"; $logContent .= ''; diff --git a/src/SparkPostController.php b/src/SparkPostController.php index 07a6620..23c18b7 100644 --- a/src/SparkPostController.php +++ b/src/SparkPostController.php @@ -9,6 +9,7 @@ use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPRequest; use SilverStripe\Security\Permission; +use SilverStripe\Control\HTTPResponse; use LeKoala\SparkPost\Api\SparkPostApiClient; /** @@ -18,8 +19,19 @@ */ class SparkPostController extends Controller { + /** + * @var int + */ protected $eventsCount = 0; + + /** + * @var int + */ protected $skipCount = 0; + + /** + * @var array + */ private static $allowed_actions = [ 'incoming', 'test', @@ -29,17 +41,21 @@ class SparkPostController extends Controller /** * Inject public dependencies into the controller * - * @var array + * @var array */ private static $dependencies = [ 'logger' => '%$Psr\Log\LoggerInterface', ]; /** - * @var Psr\Log\LoggerInterface + * @var \Psr\Log\LoggerInterface */ public $logger; + /** + * @param HTTPRequest $req + * @return HTTPResponse|string + */ public function index(HTTPRequest $req) { return $this->render([ @@ -52,6 +68,7 @@ public function index(HTTPRequest $req) * You can also see /resources/sample.json * * @param HTTPRequest $req + * @return string */ public function test(HTTPRequest $req) { @@ -65,7 +82,10 @@ public function test(HTTPRequest $req) } else { $data = file_get_contents(dirname(__DIR__) . '/resources/sample.json'); } - $payload = json_decode($data, JSON_OBJECT_AS_ARRAY); + if (!$data) { + throw new Exception("Failed to get data"); + } + $payload = json_decode($data, true); $payload['@headers'] = $req->getHeaders(); $this->processPayload($payload, 'TEST'); @@ -195,6 +215,7 @@ public function configure_inbound_emails(HTTPRequest $req) } else { echo "Webhook already configured"; } + return ''; } /** @@ -204,6 +225,7 @@ public function configure_inbound_emails(HTTPRequest $req) * @link https://www.sparkpost.com/blog/webhooks-beyond-the-basics/ * @link https://support.sparkpost.com/customer/portal/articles/1976204-webhook-event-reference * @param HTTPRequest $req + * @return HTTPResponse */ public function incoming(HTTPRequest $req) { @@ -225,7 +247,7 @@ public function incoming(HTTPRequest $req) return $response; } - $payload = json_decode($json, JSON_OBJECT_AS_ARRAY); + $payload = json_decode($json, true); // Check credentials if defined $isAuthenticated = true; @@ -274,10 +296,13 @@ public function incoming(HTTPRequest $req) } $response->setBody('OK'); - return $response; } + /** + * @param HTTPRequest $req + * @return void + */ protected function authRequest(HTTPRequest $req) { $requestUser = $req->getHeader('php_auth_user'); @@ -310,8 +335,9 @@ protected function authRequest(HTTPRequest $req) /** * Process data * - * @param array $payload + * @param array $payload * @param string $batchId + * @return void */ protected function processPayload(array $payload, $batchId = null) { @@ -333,14 +359,14 @@ protected function processPayload(array $payload, $batchId = null) // Invalid payload: it should always be an object with a msys key containing the event if ($ev === null) { - $this->getLogger()->warn("Invalid payload: " . substr(json_encode($r), 0, 100) . '...'); + $this->getLogger()->warning("Invalid payload: " . substr((string)json_encode($r), 0, 100) . '...'); continue; } // Check type: it should be an object with the type as key $type = key($ev); if (!isset($ev[$type])) { - $this->getLogger()->warn("Invalid type $type in SparkPost payload"); + $this->getLogger()->warning("Invalid type $type in SparkPost payload"); continue; } $data = $ev[$type]; @@ -385,7 +411,7 @@ protected function processPayload(array $payload, $batchId = null) /** * Get logger * - * @return Psr\SimpleCache\CacheInterface + * @return \Psr\Log\LoggerInterface */ public function getLogger() { diff --git a/src/SparkPostHelper.php b/src/SparkPostHelper.php index 8cbf920..eac500a 100644 --- a/src/SparkPostHelper.php +++ b/src/SparkPostHelper.php @@ -29,7 +29,7 @@ class SparkPostHelper /** * Client instance * - * @var SparkPostApiClient + * @var ?\LeKoala\SparkPost\Api\SparkPostApiClient */ protected static $client; @@ -45,7 +45,7 @@ public static function getMailer() /** * @param MailerInterface $mailer - * @return AbstractTransport|SparkpostApiTransport + * @return \Symfony\Component\Mailer\Transport\AbstractTransport|SparkpostApiTransport */ public static function getTransportFromMailer($mailer) { @@ -77,6 +77,7 @@ public static function getClient() } self::$client = new SparkPostApiClient($key); if (Director::isDev()) { + //@phpstan-ignore-next-line self::$client->setCurlOption(CURLOPT_VERBOSE, true); } if (Environment::getEnv("SPARKPOST_EU")) { @@ -92,8 +93,7 @@ public static function getClient() /** * Get the api client instance - * @return LeKoala\SparkPost\Api\SparkPostApiClient - * + * @return \LeKoala\SparkPost\Api\SparkPostApiClient * @throws Exception */ public static function getMasterClient() @@ -162,46 +162,73 @@ public static function init() } } + /** + * @return mixed + */ public static function getEnvApiKey() { return Environment::getEnv('SPARKPOST_API_KEY'); } + /** + * @return mixed + */ public static function getEnvMasterApiKey() { return Environment::getEnv('SPARKPOST_MASTER_API_KEY'); } + /** + * @return mixed + */ public static function getEnvSendingDisabled() { return Environment::getEnv('SPARKPOST_SENDING_DISABLED'); } + /** + * @return mixed + */ public static function getEnvEnableLogging() { return Environment::getEnv('SPARKPOST_ENABLE_LOGGING'); } + /** + * @return mixed + */ public static function getEnvSubaccountId() { return Environment::getEnv('SPARKPOST_SUBACCOUNT_ID'); } + /** + * @return mixed + */ public static function getSubaccountId() { return self::config()->subaccount_id; } + /** + * @return mixed + */ public static function getEnvForceSender() { return Environment::getEnv('SPARKPOST_FORCE_SENDER'); } + /** + * @return mixed + */ public static function getWebhookUsername() { return self::config()->webhook_username; } + /** + * @return mixed + */ public static function getWebhookPassword() { return self::config()->webhook_password; @@ -210,7 +237,7 @@ public static function getWebhookPassword() /** * Register the transport with the client * - * @return SparkPostApiTransport The updated mailer + * @return Mailer The updated mailer * @throws Exception */ public static function registerTransport() @@ -279,7 +306,7 @@ public static function isEmailDomainReady($email) * * @param string $from * @param bool $createDefault - * @return string + * @return string|array|false */ public static function resolveDefaultFromEmail($from = null, $createDefault = true) { @@ -302,7 +329,7 @@ public static function resolveDefaultFromEmail($from = null, $createDefault = tr } // Use admin email if set if ($adminEmail = Email::config()->admin_email) { - if (is_array($adminEmail) && count($adminEmail ?? []) > 0) { + if (is_array($adminEmail) && count($adminEmail) > 0) { $email = array_keys($adminEmail)[0]; return [$email => $adminEmail[$email]]; } elseif (is_string($adminEmail)) { @@ -375,8 +402,8 @@ public static function isDefaultEmail($email) /** * Resolve default send to address * - * @param string $to - * @return string + * @param string|array|null $to + * @return string|array|null */ public static function resolveDefaultToEmail($to = null) { @@ -399,7 +426,7 @@ public static function resolveDefaultToEmail($to = null) if ($admin = Email::config()->admin_email) { return $admin; } - return false; + return null; } /** diff --git a/src/api/SparkPostApiClient.php b/src/api/SparkPostApiClient.php index 5193ac2..7850a98 100644 --- a/src/api/SparkPostApiClient.php +++ b/src/api/SparkPostApiClient.php @@ -54,7 +54,7 @@ class SparkPostApiClient /** * Your api key * - * @var string + * @var ?string */ protected $key; @@ -82,7 +82,7 @@ class SparkPostApiClient /** * Results from the api * - * @var array + * @var array */ protected $results = []; @@ -96,7 +96,7 @@ class SparkPostApiClient /** * Client options * - * @var array + * @var array */ protected $curlOpts = []; @@ -105,17 +105,20 @@ class SparkPostApiClient * * @param string $key Specify the string, or it will read env SPARKPOST_API_KEY or constant SPARKPOST_API_KEY * @param int $subaccount Specify a subaccount to limit data sent by the API - * @param array $curlOpts Additionnal options to configure the curl client + * @param array $curlOpts Additionnal options to configure the curl client */ public function __construct($key = null, $subaccount = null, $curlOpts = []) { if ($key) { $this->key = $key; } else { - $this->key = getenv('SPARKPOST_API_KEY'); + $envkey = getenv('SPARKPOST_API_KEY'); + if ($envkey) { + $this->key = $envkey; + } } if (getenv('SPARKPOST_EU')) { - $this->euEndpoint = getenv('SPARKPOST_EU'); + $this->euEndpoint = boolval(getenv('SPARKPOST_EU')); } elseif (defined('SPARKPOST_EU')) { $this->euEndpoint = true; } @@ -126,7 +129,7 @@ public function __construct($key = null, $subaccount = null, $curlOpts = []) /** * Get default options * - * @return array + * @return array */ public function getDefaultCurlOptions() { @@ -156,7 +159,8 @@ public function getCurlOption($name) * Set an option * * @param string $name - * @return mixed + * @param mixed $value + * @return void */ public function setCurlOption($name, $value) { @@ -177,6 +181,7 @@ public function getKey() * Set the current api key * * @param string $key + * @return void */ public function setKey($key) { @@ -186,7 +191,7 @@ public function setKey($key) /** * Get the use of eu endpoint * - * @return string + * @return bool */ public function getEuEndpoint() { @@ -196,7 +201,8 @@ public function getEuEndpoint() /** * Set the use of eu endpoint * - * @param string $euEndpoint + * @param bool $euEndpoint + * @return void */ public function setEuEndpoint($euEndpoint) { @@ -216,7 +222,7 @@ public function getVerboseLog() /** * Get the logger * - * @return type + * @return callable */ public function getLogger() { @@ -227,6 +233,7 @@ public function getLogger() * Set a logging method * * @param callable $logger + * @return void */ public function setLogger(callable $logger) { @@ -247,6 +254,7 @@ public function getSubaccount() * Set subaccount id * * @param int $subaccount + * @return void */ public function setSubaccount($subaccount) { @@ -256,7 +264,7 @@ public function setSubaccount($subaccount) /** * Helper that handles dot notation * - * @param array $arr + * @param array $arr * @param string $path * @param string $val * @return mixed @@ -273,9 +281,9 @@ protected function setMappedValue(array &$arr, $path, $val) /** * Map data using a given mapping array * - * @param array $data - * @param array $map - * @return array + * @param array $data + * @param array $map + * @return array */ protected function mapData($data, $map) { @@ -318,8 +326,8 @@ protected function mapData($data, $map) * 'inlineCss' * * @link https://developers.sparkpost.com/api/transmissions.html - * @param array $data - * @return array An array containing 3 keys: total_rejected_recipients, total_accepted_recipients, id + * @param array $data + * @return array An array containing 3 keys: total_rejected_recipients, total_accepted_recipients, id */ public function createTransmission($data) { @@ -359,7 +367,7 @@ public function createTransmission($data) * Get the detail of a transmission * * @param string $id - * @return array + * @return array */ public function getTransmission($id) { @@ -370,7 +378,7 @@ public function getTransmission($id) * Delete a transmission * * @param string $id - * @return array + * @return array */ public function deleteTransmission($id) { @@ -382,7 +390,7 @@ public function deleteTransmission($id) * * @param string $campaignId * @param string $templateId - * @return array + * @return array */ public function listTransmissions($campaignId = null, $templateId = null) { @@ -446,8 +454,8 @@ public function listTransmissions($campaignId = null, $templateId = null) * [timestamp] => 2050-01-01T11:57:36.000Z * * @deprecated - * @param array $params - * @return array + * @param array $params + * @return array */ public function searchMessageEvents($params = []) { @@ -524,8 +532,8 @@ public function searchMessageEvents($params = []) * "transactional" => "1" * "msg_size" => "48613" * - * @param array $params - * @return array + * @param array $params + * @return array */ public function searchEvents($params = []) { @@ -557,8 +565,8 @@ public function searchEvents($params = []) * ] * } * - * @param string $params - * @return array + * @param array $params + * @return array */ public function createWebhook($params = []) { @@ -570,10 +578,10 @@ public function createWebhook($params = []) * * @param string $name * @param string $target - * @param array $events + * @param array $events * @param bool $auth Should we use basic auth ? - * @param array $credentials An array containing "username" and "password" - * @return type + * @param array $credentials An array containing "username" and "password" + * @return array */ public function createSimpleWebhook($name, $target, array $events = null, $auth = false, $credentials = null) { @@ -603,7 +611,7 @@ public function createSimpleWebhook($name, $target, array $events = null, $auth * List all webhooks * * @param string $timezone - * @return array + * @return array */ public function listAllWebhooks($timezone = null) { @@ -618,7 +626,7 @@ public function listAllWebhooks($timezone = null) * Get a webhook * * @param string $id - * @return array + * @return array */ public function getWebhook($id) { @@ -629,8 +637,8 @@ public function getWebhook($id) * Update a webhook * * @param string $id - * @param array $params - * @return array + * @param array $params + * @return array */ public function updateWebhook($id, $params = []) { @@ -641,7 +649,7 @@ public function updateWebhook($id, $params = []) * Delete a webhook * * @param string $id - * @return array + * @return array */ public function deleteWebhook($id) { @@ -652,7 +660,7 @@ public function deleteWebhook($id) * Validate a webhook * * @param string $id - * @return array + * @return array */ public function validateWebhook($id) { @@ -667,7 +675,7 @@ public function validateWebhook($id) * * @param string $id * @param int $limit - * @return array + * @return array */ public function webhookBatchStatus($id, $limit = 1000) { @@ -678,7 +686,7 @@ public function webhookBatchStatus($id, $limit = 1000) * List an example of the event data that will be posted by a Webhook for the specified events. * * @param string $events bounce, delivery... - * @return array + * @return array */ public function getSampleEvents($events = null) { @@ -692,8 +700,8 @@ public function getSampleEvents($events = null) /** * Create a sending domain * - * @param string $params - * @return array + * @param array $params + * @return array */ public function createSendingDomain($params = []) { @@ -704,7 +712,7 @@ public function createSendingDomain($params = []) * A simpler call to the api * * @param string $name - * @return array + * @return array */ public function createSimpleSendingDomain($name) { @@ -717,7 +725,7 @@ public function createSimpleSendingDomain($name) /** * List all sending domains * - * @return array + * @return array */ public function listAllSendingDomains() { @@ -728,7 +736,7 @@ public function listAllSendingDomains() * Get a sending domain * * @param string $id - * @return array + * @return array */ public function getSendingDomain($id) { @@ -739,7 +747,7 @@ public function getSendingDomain($id) * Verify a sending domain - This will ask SparkPost to check if SPF and DKIM are valid * * @param string $id - * @return array + * @return array */ public function verifySendingDomain($id) { @@ -753,8 +761,8 @@ public function verifySendingDomain($id) * Update a sending domain * * @param string $id - * @param array $params - * @return array + * @param array $params + * @return array */ public function updateSendingDomain($id, $params = []) { @@ -765,7 +773,7 @@ public function updateSendingDomain($id, $params = []) * Delete a sending domain * * @param string $id - * @return array + * @return array */ public function deleteSendingDomain($id) { @@ -776,7 +784,7 @@ public function deleteSendingDomain($id) * Create an inbound domain * * @param string $domain - * @return array + * @return array */ public function createInboundDomain($domain) { @@ -786,7 +794,7 @@ public function createInboundDomain($domain) /** * List all inbound domains * - * @return array + * @return array */ public function listInboundDomains() { @@ -797,7 +805,7 @@ public function listInboundDomains() * Get details of an inbound domain * * @param string $domain - * @return array + * @return array */ public function getInboundDomain($domain) { @@ -808,7 +816,7 @@ public function getInboundDomain($domain) * Delete an inbound domain * * @param string $domain - * @return array + * @return array */ public function deleteInboundDomain($domain) { @@ -826,8 +834,8 @@ public function deleteInboundDomain($domain) * "domain": "email.example.com" * } * - * @param array|string $params - * @return array + * @param array|string $params + * @return array */ public function createRelayWebhook($params) { @@ -837,7 +845,7 @@ public function createRelayWebhook($params) /** * List all relay webhooks * - * @return array + * @return array */ public function listRelayWebhooks() { @@ -848,7 +856,7 @@ public function listRelayWebhooks() * Get the details of a relay webhook * * @param int $id - * @return array + * @return array */ public function getRelayWebhook($id) { @@ -859,8 +867,8 @@ public function getRelayWebhook($id) * Update a relay webhook * * @param int $id - * @param array $params - * @return array + * @param array $params + * @return array */ public function updateRelayWebhook($id, $params) { @@ -871,7 +879,7 @@ public function updateRelayWebhook($id, $params) * Delete a relay webhook * * @param int $id - * @return array + * @return array */ public function deleteRelayWebhook($id) { @@ -881,7 +889,7 @@ public function deleteRelayWebhook($id) /** * Create a valid date for the API * - * @param string $time + * @param string|int $time * @param string $format * @return string Datetime in format of YYYY-MM-DDTHH:MM */ @@ -890,10 +898,16 @@ public function createValidDatetime($time, $format = null) if (!is_int($time)) { $time = strtotime($time); } + if (!$time) { + throw new Exception("Invalid time"); + } if (!$format) { $dt = new DateTime('@' . $time); } else { - $dt = DateTime::createFromFormat($format, $time); + $dt = DateTime::createFromFormat((string)$format, (string)$time); + } + if (!$dt) { + throw new Exception("Invalid datetime"); } return $dt->format(self::DATETIME_FORMAT); } @@ -904,7 +918,7 @@ public function createValidDatetime($time, $format = null) * @param string $email * @param string $name * @param string $header_to - * @return array + * @return array */ public function buildAddress($email, $name = null, $header_to = null) { @@ -925,7 +939,7 @@ public function buildAddress($email, $name = null, $header_to = null) * * @param string $string * @param string $header_to - * @return array + * @return array */ public function buildAddressFromString($string, $header_to = null) { @@ -937,11 +951,11 @@ public function buildAddressFromString($string, $header_to = null) /** * Build a recipient * - * @param string|array $address - * @param array $tags - * @param array $metadata - * @param array $substitution_data - * @return array + * @param string|array $address + * @param array $tags + * @param array $metadata + * @param array $substitution_data + * @return array * @throws Exception */ public function buildRecipient($address, array $tags = null, array $metadata = null, array $substitution_data = null) @@ -973,8 +987,8 @@ public function buildRecipient($address, array $tags = null, array $metadata = n * * @param string $endpoint * @param string $action - * @param array $data - * @return array + * @param array|string $data + * @return array * @throws Exception */ protected function makeRequest($endpoint, $action = null, $data = null) @@ -991,11 +1005,13 @@ protected function makeRequest($endpoint, $action = null, $data = null) $action = strtoupper($action); } - if ($action === self::METHOD_GET && !empty($data)) { - $endpoint .= '?' . http_build_query($data); - } - if ($action === self::METHOD_POST && is_array($data)) { - $data = json_encode($data); + if (is_array($data) && !empty($data)) { + if ($action === self::METHOD_GET) { + $endpoint .= '?' . http_build_query($data); + } + if ($action === self::METHOD_POST) { + $data = json_encode($data); + } } $header = []; @@ -1020,12 +1036,18 @@ protected function makeRequest($endpoint, $action = null, $data = null) if ($this->getCurlOption('verbose')) { curl_setopt($ch, CURLOPT_VERBOSE, true); $verbose = fopen('php://temp', 'w+'); + if ($verbose === false) { + throw new Exception("Failed to open stream"); + } curl_setopt($ch, CURLOPT_STDERR, $verbose); } // This fixes ca cert issues if server is not configured properly - if (strlen(ini_get('curl.cainfo')) === 0) { - curl_setopt($ch, CURLOPT_CAINFO, \Composer\CaBundle\CaBundle::getBundledCaBundlePath()); + $cainfo = ini_get('curl.cainfo'); + if ($cainfo !== false) { + if (strlen($cainfo) === 0) { + curl_setopt($ch, CURLOPT_CAINFO, \Composer\CaBundle\CaBundle::getBundledCaBundlePath()); + } } switch ($action) { @@ -1043,6 +1065,9 @@ protected function makeRequest($endpoint, $action = null, $data = null) if (!$result) { throw new Exception('Error: "' . curl_error($ch) . '" - Code: ' . curl_errno($ch)); } + if (is_bool($result)) { + throw new Exception("CURLOPT_RETURNTRANSFER was not set"); + } if ($this->getCurlOption('verbose')) { rewind($verbose); @@ -1073,15 +1098,18 @@ protected function makeRequest($endpoint, $action = null, $data = null) // For invalid domains, append domain name to make error more useful if ($item['code'] == 7001) { $from = ''; - if (!is_array($data)) { - $data = json_decode($data, JSON_OBJECT_AS_ARRAY); + if (!is_array($data) && is_string($data)) { + $data = json_decode($data, true); } if (isset($data['content']['from'])) { $from = $data['content']['from']; } if ($from && is_string($from)) { - $domain = substr(strrchr($from, "@"), 1); - $message .= ' (' . $domain . ')'; + $fromat = strrchr($from, "@"); + if ($fromat) { + $domain = substr($fromat, 1); + $message .= ' (' . $domain . ')'; + } } } @@ -1091,8 +1119,8 @@ protected function makeRequest($endpoint, $action = null, $data = null) if (empty($data['recipients'])) { $message .= ' (empty recipients list)'; } else { + $addresses = []; if (is_array($data['recipients'])) { - $addresses = []; foreach ($data['recipients'] as $recipient) { $addresses[] = json_encode($recipient['address']); } @@ -1118,7 +1146,7 @@ protected function makeRequest($endpoint, $action = null, $data = null) /** * Get all results from the api * - * @return array + * @return array */ public function getResults() { @@ -1128,7 +1156,7 @@ public function getResults() /** * Get last result * - * @return array + * @return array */ public function getLastResult() {