diff --git a/.gitattributes b/.gitattributes index ee50f57..6d32411 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,12 @@ -/tests export-ignore -/client/src export-ignore -/.gitattributes export-ignore +/tests export-ignore +/docs export-ignore +/client/src export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.php-cs-fixer.dist.php export-ignore +/phpunit.xml.dist export-ignore +/.waratah export-ignore +/code-of-conduct.md export-ignore +/CONTRIBUTING.md export-ignore +/README.md export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..56dc775 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,13 @@ +name: CI + +on: + pull_request: null + +jobs: + Silverstripe: + name: 'Silverstripe (bundle)' + uses: nswdpc/ci-files/.github/workflows/silverstripe.yml@v-1 + PHPStan: + name: 'PHPStan (analyse)' + uses: nswdpc/ci-files/.github/workflows/phpstan.silverstripe.yml@v-1 + needs: Silverstripe diff --git a/.gitignore b/.gitignore index 8687317..9499621 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ /client/node_modules /vendor/ .DS_Store -.php_cs.cache +/.php-cs-fixer.cache +/public/ +/resources/ +/composer.lock +node_modules diff --git a/.waratah b/.waratah index 1cf07b8..f93462e 100644 --- a/.waratah +++ b/.waratah @@ -1,46 +1,68 @@ -+--------------------------------------------------------------------------------+ -|oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| -|oooooooooooooooooooooooooooo+ooooooooo+.~oooooooooo+oooooooooooooooooooooooooooo| -|ooooooooooooooooooooooooooo. ~:+ooooo+.. .+ooooo+:. ~ooooooooooooooooooooooooooo| -|oooooooooooooooooooooooooo: .. .::oo: .....+oo:~. .. :oooooooooooooooooooooooooo| -|ooooooooooooooo+~~~:+ooooo. .... ~o: .. .. +o. .... .ooooo+::~~oooooooooooooooo| -|ooooooooooooooo: . .:+o+~. . .o+ ..... ...+o... ~:oo:~~ :ooooooooooooooo| -|ooooooooooooooo: ..... :oo+~ o+... . .... .o+ .~+oo~. .... :ooooooooooooooo| -|ooooooooooooooo~ ... ... ++::o++o~ ....... ...~o++o:~o: ....... :ooooooooooooooo| -|ooooooooooooooo~ ... .. ~o: .:o+. ..... .. ~oo:. :o..... .. :ooooooooooooooo| -|ooooo~.~~~~~~:o: ...... :o... .:o:. .. ... .+o:. .. ~o: ...... :o:~~~~~~.:ooooo| -|ooooo. . .o: . . .. +o ..... ~+o~ .. :o+~ .....o: .. . . :o. . ~ooooo| -|oooooo. . ....o+ ..... .++ .... :o:.. .+o: ... .. ++ ...... o+ .. .. ~oooooo| -|ooooooo~ . .+o........o: ... .... ~++..oo~ . ..... ++ . .. ..o:. . ~ooooooo| -|oooooooo:.::+++o~ . .. .o+ ..... .....o+o+. ..... . . +o...... :o+++::.:oooooooo| -|ooooooooo++:~..o+ . ...o+ .. ... . . .oo. .......... o+ . .. .o+.~~:++ooooooooo| -|oooooo+:~.. :o: ... +o.... ...... :o~ ... . . . .o: .... :o~ .~:+oooooo| -|ooo:~.. ...... +o. ... :o~ .. ..... .o+ .. ...... . ~o: ... ~o: .. ... ~:+ooo| -|o+ ..... . . ..+o. .. .o: .... . . :o~ ..... ..... +o .. ~o: . .... ... .+o| -|oo:~. ... .......+o~ .. :o~ ....... :o. ... .. ... :o: . ~o+ .......... .~:oo| -|oooo+:~.. . .. .:o:. :o~ ... . :o... ...... :o: ~:o: .... ..::+oooo| -|oooooooo++::~... .:+:.. :+:~ .~o: . . ~++~ .~:+:. .~~::++oooooooo| -|ooooooooooooooo++++::::ooo+::oo+::~...:o:~...~::+o+::+ooo:::+++ooooooooooooooooo| -|ooooooooooooooo:~~~::::::::::::::+++++oooo+++:+::::::::::::::~~.+ooooooooooooooo| -|ooooooooooooooo+~~... ....~:::++++o++ooooo+oo++++:::~~... .~~+ooooooooooooooo| -|oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| -|oooo:::::+ooooooooooo+::::+oooooo::~~~~:::+ooo+:::::oooooo+::::+oooooo+:::::oooo| -|ooo+ ~oooooooooo: ~ooo+. .~++ +ooooo~ +ooooo. :oooo| -|oooo :oooooooo: ~oo: ~~~~. ~+o: ~oooo+ ~oooo+ .ooooo| -|ooo+ .:oooooo: ~oo +oooooo:::oooo +ooo~ +ooo~ :ooooo| -|ooo+ ~+oooo: ~oo ~:++oooooooooo: ~oo+ ~oo+ .oooooo| -|oooo +: :ooo: ~oo: ..~:+oooooo +o~ ~: +o~ +oooooo| -|ooo+ +o+. :o: ~ooo+:. ~+ooo: :+ :o ~o .ooooooo| -|ooo+ +ooo: ~~ ~ooooooo+:::~ oooo . oo: ~ +ooooooo| -|oooo oooooo: ~ooo+:oooooooo+. :ooo: :ooo ~oooooooo| -|ooo+ ooooooo+~ ~oo: .~:++oo++ +oooo. .oooo: +oooooooo| -|ooo+ +oooooooo:. ~o: :ooooo: :ooooo ~ooooooooo| -|oooo .ooooooooooo: . ~ooo+:~. ~:oooooooo. .oooooo: +ooooooooo| -|oooooooooooooooooooooooooooooooooooo++++oooooooooooooooooooooooooooooooooooooooo| -|ooooo+:::ooo+:::+o:+ooo++o:::::o+:::+oo++ooo:o+:oooo:+o:::::o:+ooo:o+:+:::oooooo| -|ooo+.:+::+o~~:::~:: oo+ :+ ::::o ~:+~ o ~oo +: ~++~ :: :::+o :oo ++:..:+oooooo| -|ooo~.oo::~: oooo: o:.o :o+ :::+o.~:~~:o ::.: +:~:..+.:: :::+o ::.: :oo:.oooooooo| -|oooo~~::~.o:.:::.:oo~ ~oo+ ::::o.~+:.:o :o+~ +:.o+oo :: ::::o :o+~ +oo:.oooooooo| -|ooooo+:++oooo:++ooooo+oooo++:++o+oooo:o+oooo+oo+oooo+oo+++:+o+oooo+ooo++oooooooo| -|oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| -+--------------------------------------------------------------------------------+ ++------------------------------------------------------------------------------------------------------------------------+ +|oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| +|ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo:+ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| +|ooooooooooooooooooooooooooooooooooooooooo++ooooooooooooooo~ ~oooooooooooooo+++ooooooooooooooooooooooooooooooooooooooooo| +|oooooooooooooooooooooooooooooooooooooooo+ .~+oooooooooo+. .. ~oooooooooo++~. .+oooooooooooooooooooooooooooooooooooooooo| +|oooooooooooooooooooooooooooooooooooooooo~ .. ~:+oooooo+. .... ~+oooooo+:. .. :oooooooooooooooooooooooooooooooooooooooo| +|ooooooooooooooooooooooooooooooooooooooo+ .. .. :+ooo+.... . . .oooo:~. ......+ooooooooooooooooooooooooooooooooooooooo| +|oooooooooooooooooooooooo~~:+++ooooooooo.......... ~oo+ ..... .. .+o+ .........:oooooooooo++~~:oooooooooooooooooooooooo| +|ooooooooooooooooooooooo: . .~:+ooooo+ .........+o+ .............oo+........ +oooo++:~ . . +ooooooooooooooooooooooo| +|ooooooooooooooooooooooo~ .... . .~++oo+~ ..... +o+... . .... .. ~oo+ .... .~+oo++~. ...... ~ooooooooooooooooooooooo| +|ooooooooooooooooooooooo........... ~+oo++. ..+oo~ ............ . ~oo+ . .~+ooo:~. ......... ~ooooooooooooooooooooooo| +|ooooooooooooooooooooooo.............. :ooooo+~ :oo~ ..... .......... +oo~.~+ooooo~ ..... . .... .ooooooooooooooooooooooo| +|oooooooooooooooooooooo+. . ... .. .. :oo~ ~+oo++o+ ........... .......+oo+oo+~.~oo~ . ...........ooooooooooooooooooooooo| +|ooooooooooooooooooooooo... . ....... +o+ .~+ooo. ................. ~ooo+~ ..+o: ........... ~ooooooooooooooooooooooo| +|oooooooo+++++++++++oooo......... ....oo: ... ~+oo:. .. .. .. .... .+oo+~ ... :o+ .. .... . ..~oo+o+++++++++++oooooooo| +|ooooooo: . .. ~oo............ ~oo....... ~+oo: ... ..... .. :oo+~ ...... .oo............ ~oo.... . +ooooooo| +|ooooooo+ .......... ~oo~ . ... .... :o+ ... .... :+o+~ ... .... ~+oo:. .. ......oo~ ... ...... ~oo. ...........+ooooooo| +|oooooooo~ ... .......oo: .. ..... ..+o+ ... ...... .+oo: .... .:oo+. .... ... +o: . .. ..... +o+........... +oooooooo| +|ooooooooo~ ..........+o+ ..... .... +o+ ...... ..... :oo+.... ~+oo~ ............ +o+ ....... ...+o+ ...... .. :ooooooooo| +|oooooooooo: ....... :o+ .. ........oo: ............. .+o+~ ~oo+~ .. ....... .. +o+....... ....oo~ ...... +oooooooooo| +|ooooooooooo+. .~:+oo~.... .... .oo+ . ... ... ... .+oo.:oo+. ....... ...... +o+ .. ...... ~oo+:~. . .+ooooooooooo| +|oooooooooooo+~.:++ooo+oo+ .. .. ...+o: .... ...... .... +oooo+ ..... ..... . . +o+.... ..... +oo+ooo++~.:ooooooooooooo| +|ooooooooooooooooo+:~. :oo...........oo+ .. ...... .... . +oo+.......... ....... +o+ ..... ...~oo~ .~:+ooooooooooooooooo| +|oooooooooooo++:. ....oo: .... ....+o+ .......... ...... +o+........ ....... ...+o+ ........ +o+ .. ~:++oooooooooooo| +|ooooooooo+:.. ...... :oo~ .... .. +o+. ..... ...... .. :oo~ ... ..... .. ......oo~ ... ... ~oo~....... . ~:+ooooooooo| +|ooooo++~.. ...... .... +o+ ....... ~oo~... .... ........+o+ ..... ............ :oo... ......oo+ .. ....... ..~+oooooo| +|ooo+~. ..... ...........oo+ ........oo+ ..... .... ... :oo. . ...... .... .....+o+ ...... .+o+ ...... ......... ..~+ooo| +|oo+ ..... .............. ~+o+ . . .. +oo............. . +o+. ................. ~oo~ ..... .+o+...... ....... ...... +oo| +|ooo:.. ............ .... .oo+. ......+o+ ... ..........+o+ .... ..... .. . .. +o+ .. .. ~+o+. ... ...... ....... ..+ooo| +|ooooo+~. ...... ........ .+oo~ ... ~oo: ... ....... +o+ ................ +oo~..... :oo+...... .......... .~:+ooooo| +|oooooooo++~.. ............ +oo+~ .. :oo+ ... .......+o+ .. .. . ...... .+oo. .. ~+o+~ ......... ..:++oooooooo| +|oooooooooooo++:~.. . .. .+oo+~ ~+oo: ......... :oo............. .:oo+. .~+oo+. .. .~::++oooooooooooo| +|oooooooooooooooooo+++::~~~.... .:+o++~~. ~+o+:.. ... +o+. .... . ~++o+~ ..:++o+: ....~~~::+++ooooooooooooooooooo| +|ooooooooooooooooooooooooooooooo+++++ooooo+++oooo++:~~....~oo+:.....~~:+oooo++++ooooo+++++ooooooooooooooooooooooooooooooo| +|ooooooooooooooooooooooo:~~~:::++++o++++++oo+++++++o+++o+oooooooo++o++oo++++oooo++++oo++++++::~~~+ooooooooooooooooooooooo| +|oooooooooooooooooooooo+. . .~~~:::++++++oooooo+++++::~~~... . .. . . :ooooooooooooooooooooooo| +|oooooooooooooooooooooooo+:~~~....~~.~~:++++ooooooooooooooooooooooooooooooooo+++::~~..........~:+oooooooooooooooooooooooo| +|oooooooooooooooooooooooooooooooo+ooooooooooooooooooooooooooooooooooooooooooooooooooooo+o+oo+oooooooooooooooooooooooooooo| +|oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| +|oooooo:~:~::::+ooooooooooooooooo+~::~:::oooooooo++:~.. ..~~:+oooooo~:::~:::oooooooooo:~:~::~+oooooooooo::::~:~+oooooo| +|ooooo+ :oooooooooooooooo~ oooooo+. .:+oo~ +oooooooo+ ooooooooo~ +oooooo| +|ooooo+ +oooooooooooooo~ oooo+~ .+o+ ~oooooooo. ~oooooooo ~ooooooo| +|ooooo+ .+oooooooooooo~ oooo. .::+::~. ~+ooo: +oooooo+ ooooooo+ +ooooooo| +|ooooo+ ~ooooooooooo~ ooo+ +ooooooooo++. :ooooo+ ~oooooo. :oooooo ~oooooooo| +|ooooo+ :ooooooooo~ +oo: +oooooooooooooooooooo: +oooo+ ooooo+ +oooooooo| +|ooooo+ . .+ooooooo~ ooo+ .:++oooooooooooooooo+ :oooo. ~oooo. ~ooooooooo| +|ooooo+ :+. ~+ooooo~ .oooo~ ..~:++oooooooooo: ooo+ +~ ooo+ oooooooooo| +|ooooo+ :oo+ :oooo~ +oooo: ~:oooooooo :oo o+ :oo. :oooooooooo| +|ooooo+ :oooo: +oo. ooooooo+~. ~oooooo: o+ +oo. o+ ooooooooooo| +|ooooo+ :ooooo+~ .+~ ooooooooooo++:~.. .oooooo :. ooo+ :~ :ooooooooooo| +|ooooo+ :ooooooo+. oooooooooooooooooo++~ +ooooo: :oooo. oooooooooooo| +|ooooo+ ~ooooooooo: +ooooo:+ooooooooooooo+ :oooooo. ooooo+ +oooooooooooo| +|ooooo+ :ooooooooooo~ oooo+ .~++ooooooooo+ :oooooo+ :oooooo. .ooooooooooooo| +|ooooo+ :oooooooooooo+. oo+. .~~::::~. .oooooooo. ooooooo+ +ooooooooooooo| +|ooooo+ :oooooooooooooo+ +o+~ ~ooooooooo+ :oooooooo. .oooooooooooooo| +|ooooo+ ~oooooooooooooooo: ooooo+:. .~+ooooooooooo. ooooooooo+ +oooooooooooooo| +|oooooo:+::+::+ooooooooooooooooo+::+:+:::oooooooo+++:~~.~.~.~::++oooooooooooooo+::+::+:+oooooooooo+:::+:++ooooooooooooooo| +|oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| +|ooooooooo+++ooooooooo+ooooooooooooooooo+o+o++oooooo++oooooo+ooooooooooooooooooooooooo++o+ooooooooooo+ooo+ooo+ooooooooooo| +|oooooo+~.~~..~+oo+~..~...+o: :ooooo. o+ .~.~~.+o~ .~...~+o+ .+oooo..o+ +oooo: ++ ..~~..:o+ .+oooo..o:.~. ...~ooooooooo| +|ooooo+ ~+oooo+oo+ .+ooo+~ :o. +ooo. +o+ :ooooooo~ +ooo+ o+ . ~ooo o+ ~oo~ oo .ooooooo+ ~ooo oooo+ ~oooooooooooo| +|ooooo ooo+~~:~o ooooooo. oo oo: +oo+ ~...~oo~ ::++~ ~o+ ++. +o .o+ :+ .. o~ +o ~~.~~+o+ ++ +o .oooo+ :oooooooooooo| +|ooooo: :ooo++ o~ :ooooo+ .oo+ ~+ :ooo+ :ooooooo~ ~:~ +oo+ :oo: ~ .o+ :oo.~oo~ +o .o++oooo+ :oo: ~ .oooo+ :oooooooooooo| +|oooooo+..~::~ .oo: .~:~. ~oooo+ ~oooo+ .:~::~+o~ +oo+. +o+ :ooo+. o+ ~oooooo. oo :~::~:o+ :ooo+. oooo+ ~oooooooooooo| +|oooooooo++:++oooooo++:++ooooooo++ooooo+++++++++o++ooooo++oo++ooooo++oo++oooooo++oo++++++++o+++ooooo++oooo+++oooooooooooo| +|oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| +|oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| ++------------------------------------------------------------------------------------------------------------------------+ diff --git a/composer.json b/composer.json index 713cb40..766d41f 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,12 @@ ] } }, + "repositories": [ + { + "type": "git", + "url": "https://github.com/nswdpc/ci-files.git" + } + ], "require": { "symbiote/silverstripe-queuedjobs": "^4.9", "mailgun/mailgun-php": "^3", @@ -43,7 +49,18 @@ "silverstripe/framework" : "^4.10" }, "require-dev": { + "cambis/silverstripe-rector": "^0.5.1", "phpunit/phpunit": "^9.5", - "friendsofphp/php-cs-fixer": "^3" + "syntro/silverstripe-phpstan": "^1", + "nswdpc/ci-files": "dev-v-1" + }, + "config": { + "allow-plugins": { + "composer/installers": true, + "php-http/discovery": true, + "silverstripe/vendor-plugin": true, + "silverstripe/recipe-plugin": true, + "phpstan/extension-installer": true + } } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist deleted file mode 100644 index fe58f6f..0000000 --- a/phpcs.xml.dist +++ /dev/null @@ -1,11 +0,0 @@ - - - CodeSniffer ruleset for SilverStripe coding conventions. - - - - - - - - diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a5ec7a1..4168a92 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ - - + + tests/ diff --git a/src/Connector/Base.php b/src/Connector/Base.php index 951d663..b4b98f8 100644 --- a/src/Connector/Base.php +++ b/src/Connector/Base.php @@ -1,4 +1,5 @@ getApiKey(); } + $api_endpoint = $this->config()->get('api_endpoint_region'); $this->api_endpoint_url = ''; - switch($api_endpoint) { + switch ($api_endpoint) { case 'API_ENDPOINT_EU': $this->api_endpoint_url = self::API_ENDPOINT_EU; $client = Mailgun::create($api_key, $this->api_endpoint_url); @@ -115,17 +105,18 @@ public function getClient($api_key = null) $client = Mailgun::create($api_key); break; } + return $client; } - public function getApiEndpointRegion() { + public function getApiEndpointRegion() + { return $this->api_endpoint_url; } public function getApiKey() { - $mailgun_api_key = $this->config()->get('api_key'); - return $mailgun_api_key; + return $this->config()->get('api_key'); } public function getWebhookSigningKey() @@ -143,19 +134,20 @@ public function getWebhookPreviousFilterVariable() return $this->config()->get('webhook_previous_filter_variable'); } - public function getWebhooksEnabled() { + public function getWebhooksEnabled() + { return $this->config()->get('webhooks_enabled'); } public function getApiDomain() { - $mailgun_api_domain = $this->config()->get('api_domain'); - return $mailgun_api_domain; + return $this->config()->get('api_domain'); } - public function isSandbox() { + public function isSandbox() + { $api_domain = $this->getApiDomain(); - $result = preg_match("/^sandbox[a-z0-9]+\.mailgun\.org$/i", $api_domain); + $result = preg_match("/^sandbox[a-z0-9]+\.mailgun\.org$/i", (string) $api_domain); return $result == 1; } @@ -178,7 +170,7 @@ final protected function alwaysSetSender() /** * Prior to any send/sendMime action, check config and set testmode if config says so */ - final protected function applyTestMode(&$parameters) + final protected function applyTestMode(array &$parameters) { $mailgun_testmode = $this->config()->get('api_testmode'); if ($mailgun_testmode) { diff --git a/src/Connector/Bounce.php b/src/Connector/Bounce.php index 6467c66..0ceed77 100644 --- a/src/Connector/Bounce.php +++ b/src/Connector/Bounce.php @@ -1,4 +1,5 @@ getApiKey(); $client = Mailgun::create($api_key); $domain = $this->getApiDomain(); - $response = $client->suppressions()->bounces()->delete($domain, $email_address); - return $response; + return $client->suppressions()->bounces()->delete($domain, $email_address); } /** * See: https://documentation.mailgun.com/en/latest/api-suppressions.html#add-a-single-bounce */ - public function add($email_address, $code = 550, $error = "", $created_at = "") + public function add($email_address, $code = 550, $error = "", $created_at = ""): ?\Mailgun\Model\Suppression\Bounce\CreateResponse { $valid = Email::is_valid_address($email_address); if (!$valid) { @@ -57,8 +57,6 @@ public function add($email_address, $code = 550, $error = "", $created_at = "") $params['created_at'] = $created_at; } - $response = $client->suppressions()->bounces()->create($domain, $email_address, $params); - - return $response; + return $client->suppressions()->bounces()->create($domain, $email_address, $params); } } diff --git a/src/Connector/Event.php b/src/Connector/Event.php index 03c5b37..ede42df 100644 --- a/src/Connector/Event.php +++ b/src/Connector/Event.php @@ -1,4 +1,5 @@ getApiKey(); $client = Mailgun::create($api_key); @@ -59,6 +59,7 @@ public function pollEvents($begin = null, $event_filter = "", $extra_params = ar // recursively retrieve the events based on pagination $this->getNextPage($client, $response); } + return $this->results; } @@ -89,8 +90,9 @@ private function getNextPage($client, $response) $items = $response->getItems(); if (empty($items)) { // no more items - nothing to do - return; + return null; } + // add to results $this->results = array_merge($this->results, $items); return $this->getNextPage($client, $response); diff --git a/src/Connector/Message.php b/src/Connector/Message.php index e0204a8..f837c96 100644 --- a/src/Connector/Message.php +++ b/src/Connector/Message.php @@ -1,4 +1,5 @@ StorageURL)) { throw new Exception("No StorageURL found on MailgunEvent #{$event->ID}"); } + // Get the mime encoded message, by passing the Accept header $message = $client->messages()->show($event->StorageURL, true); return $message; @@ -110,20 +111,19 @@ public function send($parameters) $this->applyDefaultRecipient($parameters); // apply the webhook_filter_variable, if webhooks are enabled - if($this->getWebhooksEnabled() && ($variable = $this->getWebhookFilterVariable())) { + if ($this->getWebhooksEnabled() && ($variable = $this->getWebhookFilterVariable())) { $parameters["v:wfv"] = $variable; } // Send a message defined by the parameters provided return $this->sendMessage($parameters); - } /** * Sends a message - * @param array $parameters */ - protected function sendMessage(array $parameters) { + protected function sendMessage(array $parameters) + { /** * @var \Mailgun\Mailgun @@ -137,21 +137,12 @@ protected function sendMessage(array $parameters) { // send options $send_via_job = $this->sendViaJob(); $in = $this->getSendIn();// seconds - switch ($send_via_job) { - case 'yes': - return $this->queueAndSend($domain, $parameters, $in); - break; - case 'when-attachments': - if (!empty($parameters['attachment'])) { - return $this->queueAndSend($domain, $parameters, $in); - break; - } - // fallback to direct - // no break - case 'no': - default: - return $client->messages()->send($domain, $parameters); - break; + if ($send_via_job == 'yes') { + return $this->queueAndSend($domain, $parameters, $in); + } elseif ($send_via_job == 'when-attachments' && !empty($parameters['attachment'])) { + return $this->queueAndSend($domain, $parameters, $in); + } else { + return $client->messages()->send($domain, $parameters); } } @@ -163,7 +154,7 @@ public function encodeAttachments(&$parameters) { if (!empty($parameters['attachment']) && is_array($parameters['attachment'])) { foreach ($parameters['attachment'] as $k=>$attachment) { - $parameters['attachment'][$k]['fileContent'] = base64_encode($attachment['fileContent']); + $parameters['attachment'][$k]['fileContent'] = base64_encode((string) $attachment['fileContent']); } } } @@ -176,7 +167,7 @@ public function decodeAttachments(&$parameters) { if (!empty($parameters['attachment']) && is_array($parameters['attachment'])) { foreach ($parameters['attachment'] as $k=>$attachment) { - $parameters['attachment'][$k]['fileContent'] = base64_decode($attachment['fileContent']); + $parameters['attachment'][$k]['fileContent'] = base64_decode((string) $attachment['fileContent']); } } } @@ -185,16 +176,18 @@ public function decodeAttachments(&$parameters) * Returns a DateTime being when the queued job should be started after * @param string $in See:http://php.net/manual/en/datetime.formats.relative.php */ - private function getSendDateTime($in) : ?\DateTime + private function getSendDateTime($in): ?\DateTime { try { - $dt = $default = null; - if($in > 0) { + $dt = null; + $default = null; + if ($in > 0) { $dt = new \DateTime("now +{$in} seconds"); } - } catch (\Exception $e) { + } catch (\Exception) { } - return $dt ? $dt : $default; + + return $dt instanceof \DateTime ? $dt : $default; } /** @@ -208,20 +201,22 @@ protected function queueAndSend($domain, $parameters, $in) { $this->encodeAttachments($parameters); $startAfter = null; - if($start = $this->getSendDateTime($in)) { + if (($start = $this->getSendDateTime($in)) instanceof \DateTime) { $startAfter = $start->format('Y-m-d H:i:s'); } + $job = new SendJob($domain, $parameters); - if($job_id = QueuedJobService::singleton()->queueJob($job, $startAfter)) { + if ($job_id = QueuedJobService::singleton()->queueJob($job, $startAfter)) { return QueuedJobDescriptor::get()->byId($job_id); } + return false; } /** * Lookup all events for the submission linked to this event */ - public function isDelivered(MailgunEvent $event, $cleanup = true) + public function isDelivered(MailgunEvent $event, $cleanup = true): bool { // Query will be for this MessageId and a delivered status @@ -234,8 +229,7 @@ public function isDelivered(MailgunEvent $event, $cleanup = true) $timeframe = 'now -30 days'; $begin = Base::DateTime($timeframe); - $event_filter = MailgunEvent::DELIVERED; - $resubmit = false;// no we don't want to resubmit + $event_filter = MailgunEvent::DELIVERED;// no we don't want to resubmit $extra_params = [ 'limit' => 25, 'message-id' => $event->MessageId, @@ -243,20 +237,16 @@ public function isDelivered(MailgunEvent $event, $cleanup = true) ]; $events = $connector->pollEvents($begin, $event_filter, $extra_params); - - $is_delivered = !empty($events); - return $is_delivered; + return $events !== []; } /** * Trim < and > from message id - * @return string * @param string $message_id */ - public static function cleanMessageId($message_id) + public static function cleanMessageId($message_id): string { - $message_id = trim($message_id, "<>"); - return $message_id; + return trim($message_id, "<>"); } /** @@ -264,12 +254,14 @@ public static function cleanMessageId($message_id) * This is not the "o:deliverytime" option ("Messages can be scheduled for a maximum of 3 days in the future.") * To set "deliverytime" set it as an option to setOptions() */ - public function setSendIn(float $seconds) { + public function setSendIn(float $seconds): static + { $this->send_in_seconds = $seconds; return $this; } - public function getSendIn() { + public function getSendIn() + { return $this->send_in_seconds; } @@ -278,7 +270,8 @@ public function getSendIn() { * and value is a dictionary with variables * that can be referenced in the message body. */ - public function setRecipientVariables(array $recipient_variables) { + public function setRecipientVariables(array $recipient_variables): static + { $this->recipient_variables = $recipient_variables; return $this; } @@ -286,117 +279,127 @@ public function setRecipientVariables(array $recipient_variables) { /** * @returns string|null */ - public function getRecipientVariables() { + public function getRecipientVariables() + { return $this->recipient_variables; } - public function setAmpHtml(string $html) { + public function setAmpHtml(string $html): static + { $this->amp_html = $html; return $this; } - public function getAmpHtml() { + public function getAmpHtml() + { return $this->amp_html; } - public function setTemplate($template, $version = "", $include_in_text = "") { - if($template) { + public function setTemplate($template, $version = "", $include_in_text = ""): static + { + if ($template) { $this->template = [ 'template' => $template, 'version' => $version, 'text' => $include_in_text == "yes" ? "yes" : "", ]; } + return $this; } - public function getTemplate() { + public function getTemplate() + { return $this->template; } /** * Keys are not prefixed with "o:" */ - public function setOptions(array $options) { + public function setOptions(array $options): static + { $this->options = $options; return $this; } - public function getOptions() { + public function getOptions() + { return $this->options; } /** * Keys are not prefixed with "h:" */ - public function setCustomHeaders(array $headers) { + public function setCustomHeaders(array $headers): static + { $this->headers = $headers; return $this; } - public function getCustomHeaders() { + public function getCustomHeaders() + { return $this->headers; } /** * Keys are not prefixed with "v:" */ - public function setVariables(array $variables) { + public function setVariables(array $variables): static + { $this->variables = $variables; return $this; } - public function getVariables() { + public function getVariables() + { return $this->variables; } /** * Based on options set in {@link NSWDPC\Messaging\Mailgun\MailgunEmail} set Mailgun options, params, headers and variables - * @param array $parameters */ - protected function addCustomParameters(&$parameters) + protected function addCustomParameters(array &$parameters) { // VARIABLES $variables = $this->getVariables(); - foreach($variables as $k=>$v) { + foreach ($variables as $k=>$v) { $parameters["v:{$k}"] = $v; } // OPTIONS $options = $this->getOptions(); - foreach($options as $k=>$v) { + foreach ($options as $k=>$v) { $parameters["o:{$k}"] = $v; } // TEMPLATE $template = $this->getTemplate(); - if(!empty($template['template'])) { + if (!empty($template['template'])) { $parameters["template"] = $template['template']; - if(!empty($template['version'])) { + if (!empty($template['version'])) { $parameters["t:version"] = $template['version']; } - if(isset($template['text']) && $template['text'] == "yes") { + + if (isset($template['text']) && $template['text'] == "yes") { $parameters["t:text"] = $template['text']; } } // AMP HTML handling - if($amp_html = $this->getAmpHtml()) { + if ($amp_html = $this->getAmpHtml()) { $parameters["amp-html"] = $amp_html; } // HEADERS $headers = $this->getCustomHeaders(); - foreach($headers as $k=>$v) { + foreach ($headers as $k=>$v) { $parameters["h:{$k}"] = $v; } // RECIPIENT VARIABLES - if($recipient_variables = $this->getRecipientVariables()) { + if ($recipient_variables = $this->getRecipientVariables()) { $parameters["recipient-variables"] = json_encode($recipient_variables); } - } - } diff --git a/src/Connector/Webhook.php b/src/Connector/Webhook.php index 65dee7c..4fb9c71 100644 --- a/src/Connector/Webhook.php +++ b/src/Connector/Webhook.php @@ -10,42 +10,43 @@ /** * Webhook integration with Mailgun PHP SDK */ -class Webhook extends Base { - +class Webhook extends Base +{ /** * verify signature * @return bool returns true if signature is valid - * @param array $signature */ - public function verify_signature($signature) + public function verify_signature(array $signature) { - if($this->is_valid_signature($signature)) { - return hash_equals( $this->sign_token($signature), $signature['signature']); + if ($this->is_valid_signature($signature)) { + return hash_equals($this->sign_token($signature), $signature['signature']); } + return false; } /** * Sign the token based on timestamp and signature in request - * @param array $signature */ - public function sign_token($signature) { + public function sign_token(array $signature): string + { $webhook_signing_key = $this->getWebhookSigningKey(); - if(!$webhook_signing_key) { + if (!$webhook_signing_key) { throw new \Exception("Please set a webhook signing key in configuration"); } - return hash_hmac( 'sha256', $signature['timestamp'] . $signature['token'], $webhook_signing_key ); + + return hash_hmac('sha256', $signature['timestamp'] . $signature['token'], (string) $webhook_signing_key); } /** * Based on Mailgun docs, determine if the signature is correct * @param array $signature */ - public function is_valid_signature($signature) { + public function is_valid_signature($signature): bool + { return isset($signature['timestamp']) && isset($signature['token']) - && strlen($signature['token']) == 50 + && strlen((string) $signature['token']) == 50 && isset($signature['signature']); } - } diff --git a/src/Controllers/MailgunModelAdmin.php b/src/Controllers/MailgunModelAdmin.php index 7fd9242..17b2e5f 100644 --- a/src/Controllers/MailgunModelAdmin.php +++ b/src/Controllers/MailgunModelAdmin.php @@ -1,4 +1,5 @@ Fields()->dataFieldByName($this->sanitiseClassName($this->modelClass)); - if($grid instanceof GridField) { + if ($grid instanceof GridField) { $config = $grid->getConfig(); $config->removeComponentsByType(GridFieldAddNewButton::class); $config->removeComponentsByType(GridFieldPrintButton::class); - if(! Permission::check( MailgunEvent::PERMISSIONS_DELETE, 'any', Member::currentUser()) ) { + if (! Permission::check(MailgunEvent::PERMISSIONS_DELETE, 'any', Member::currentUser())) { $config->removeComponentsByType(GridFieldEditButton::class); $config->removeComponentsByType(GridFieldDeleteAction::class); } diff --git a/src/Controllers/MailgunWebHook.php b/src/Controllers/MailgunWebHook.php index df468b7..d22ece2 100644 --- a/src/Controllers/MailgunWebHook.php +++ b/src/Controllers/MailgunWebHook.php @@ -13,31 +13,27 @@ * @see https://documentation.mailgun.com/en/latest/user_manual.html#webhooks * @author James */ -class MailgunWebHook extends Controller { +class MailgunWebHook extends Controller +{ + private static bool $webhooks_enabled = true; - /** - * @var bool - */ - private static $webhooks_enabled = true; - - /** - * @var array - */ - private static $allowed_actions = [ + private static array $allowed_actions = [ 'submit' => true ]; /** * Retrieve webook signing key from config */ - protected function getConnector() { + protected function getConnector() + { return Webhook::create(); } /** * Return JSON encoded response body */ - protected function getResponseBody($success = true) { + protected function getResponseBody($success = true) + { $data = [ 'success' => $success ]; @@ -47,9 +43,10 @@ protected function getResponseBody($success = true) { /** * We have done something wrong */ - protected function serverError($status_code = 503, $message = "") { + protected function serverError($status_code = 503, $message = "") + { Log::log($message, \Psr\Log\LogLevel::NOTICE); - $response = HTTPResponse::create( $this->getResponseBody(false), $status_code); + $response = HTTPResponse::create($this->getResponseBody(false), $status_code); $response->addHeader('Content-Type', 'application/json'); return $response; } @@ -57,7 +54,8 @@ protected function serverError($status_code = 503, $message = "") { /** * Client (being Mailgun user agent) has done something wrong */ - protected function clientError($status_code = 400, $message = "") { + protected function clientError($status_code = 400, $message = "") + { Log::log($message, \Psr\Log\LogLevel::NOTICE); $response = HTTPResponse::create($this->getResponseBody(false), $status_code); $response->addHeader('Content-Type', 'application/json'); @@ -67,7 +65,8 @@ protected function clientError($status_code = 400, $message = "") { /** * All is good */ - protected function returnOK($status_code = 200, $message = "OK") { + protected function returnOK($status_code = 200, $message = "OK") + { $response = HTTPResponse::create($this->getResponseBody(true), $status_code); $response->addHeader('Content-Type', 'application/json'); return $response; @@ -76,7 +75,8 @@ protected function returnOK($status_code = 200, $message = "OK") { /** * Ignore / requests */ - public function index($request) { + public function index($request) + { return $this->clientError(404, "Not Found"); } @@ -85,76 +85,73 @@ public function index($request) { * @throws \Exception|WebhookServerException|WebhookClientException|WebhookNotAcceptableException * The exception thrown depends on the error found. A 406 error will stop Mailgun from retrying a particular request */ - public function submit(HTTPRequest $request = null) { - + public function submit(HTTPRequest $request = null) + { try { - $connector = $this->getConnector(); // turned off in configuration - but allow retry if config error - if(!$connector->getWebhooksEnabled()) { + if (!$connector->getWebhooksEnabled()) { throw new WebhookServerException("Not enabled", 503); } // requests are always posts - Mailgun should only POST - if(!$request->isPOST()) { + if (!$request->isPOST()) { throw new WebhookClientException("Method not allowed", 405); } // requests are application/json $content_type = $request->getHeader('Content-Type'); - if($content_type != "application/json") { + if ($content_type != "application/json") { throw new WebhookClientException("Unexpected content-type: {$content_type}"); } // POST body - $payload = json_decode($request->getBody(), true); - if(!$payload) { + $payload = json_decode((string) $request->getBody(), true); + if (!$payload) { throw new WebhookClientException("No payload found"); } // No sig found - if(!isset($payload['signature'])) { + if (!isset($payload['signature'])) { throw new WebhookClientException("Missing payload data - signature"); } // No event data found - if(!isset($payload['event-data'])) { + if (!isset($payload['event-data'])) { // TODO - this is probably a client error throw new WebhookClientException("Missing payload data - event-data"); } // verify the variable, if set, is in the payload, ignore submission $variable = $connector->getWebhookFilterVariable();//from config - if($variable) { + if ($variable) { $webhook_filter_ok = false; $previous_variable = $connector->getWebhookPreviousFilterVariable();//from config - if(!empty($payload['event-data']['user-variables']['wfv'])) { - if($payload['event-data']['user-variables']['wfv'] == $variable - || $payload['event-data']['user-variables']['wfv'] == $previous_variable) { - // the webhook submission equals the current or previous variable - $webhook_filter_ok = true; - } + if (!empty($payload['event-data']['user-variables']['wfv']) && ($payload['event-data']['user-variables']['wfv'] == $variable + || $payload['event-data']['user-variables']['wfv'] == $previous_variable)) { + // the webhook submission equals the current or previous variable + $webhook_filter_ok = true; } - if(!$webhook_filter_ok) { + + if (!$webhook_filter_ok) { // respond with a 400 not a 406 (possible configuration error: allow time to fix) throw new WebhookClientException("Webhook filter variable mismatch", 400); } } // Not a valid signature - this could happen if the signing key is recycled - if(!$connector->verify_signature($payload['signature'])) { + if (!$connector->verify_signature($payload['signature'])) { throw new WebhookNotAcceptableException("Signature verification failed"); } $event = \Mailgun\Model\Event\Event::create($payload['event-data']); $me = MailgunEvent::create(); - if(($mailgun_event = $me->storeEvent($event)) && $mailgun_event->exists()) { + if (($mailgun_event = $me->storeEvent($event)) && $mailgun_event->exists()) { return $this->returnOk(); } throw new WebhookServerException("Failed to save local record", 503); - } catch (WebhookServerException $e) { // we did something wrong, Mailgun will try again return $this->serverError($e->getCode(), $e->getMessage()); @@ -168,6 +165,5 @@ public function submit(HTTPRequest $request = null) { //general server error return $this->serverError(500, $e->getMessage()); } - } } diff --git a/src/Email/MailgunEmail.php b/src/Email/MailgunEmail.php index 1270081..d5e2424 100644 --- a/src/Email/MailgunEmail.php +++ b/src/Email/MailgunEmail.php @@ -1,4 +1,5 @@ connector = Injector::inst()->get( Message::class ); + public function getConnector(): Message + { + $this->connector = Injector::inst()->get(Message::class); return $this->connector; } @@ -50,7 +48,8 @@ public function getConnector() : Message { * Custom parameters are retrievable once to avoid replaying them across * multiple messages */ - public function getCustomParameters() : array { + public function getCustomParameters(): array + { $customParameters = $this->customParameters; $this->clearCustomParameters(); return $customParameters; @@ -58,20 +57,19 @@ public function getCustomParameters() : array { /** * Clear custom parameters - * @return self */ - public function clearCustomParameters() { + public function clearCustomParameters(): static + { $this->customParameters = []; return $this; } /** * Set custom parameters on the message connector - * @return self */ - public function setCustomParameters(array $args) { + public function setCustomParameters(array $args): static + { $this->customParameters = $args; return $this; } - } diff --git a/src/Email/MailgunMailer.php b/src/Email/MailgunMailer.php index 1b6820a..25def64 100644 --- a/src/Email/MailgunMailer.php +++ b/src/Email/MailgunMailer.php @@ -1,4 +1,5 @@ config()->get('always_from'); - if(!$always_from && $this->alwaysFrom) { + if (!$always_from && $this->alwaysFrom) { $always_from = $this->alwaysFrom; } + return $always_from; } /** * Retrieve and set custom parameters on the API connector - * @param MailgunEmail $email * @param MessageConnector $connector instance for this send attempt - * @return MessageConnector */ - protected function assignCustomParameters(MailgunEmail &$email, MessageConnector &$connector) : MessageConnector { + protected function assignCustomParameters(MailgunEmail &$email, MessageConnector &$connector): MessageConnector + { $customParameters = $email->getCustomParameters(); $email->clearCustomParameters(); - $connector->setVariables( $customParameters['variables'] ?? [] ) - ->setOptions( $customParameters['options'] ?? [] ) - ->setCustomHeaders( $customParameters['headers'] ?? [] ) - ->setRecipientVariables( $customParameters['recipient-variables'] ?? [] ) + $connector->setVariables($customParameters['variables'] ?? []) + ->setOptions($customParameters['options'] ?? []) + ->setCustomHeaders($customParameters['headers'] ?? []) + ->setRecipientVariables($customParameters['recipient-variables'] ?? []) ->setSendIn($customParameters['send-in'] ?? 0) ->setAmpHtml($customParameters['amp-html'] ?? '') ->setTemplate($customParameters['template'] ?? []); @@ -92,37 +94,35 @@ public function send($email) // Send the payload $response = $connector->send($parameters); - if($response instanceof SendResponse) { + if ($response instanceof SendResponse) { // get a message.id from the response $message_id = $this->saveResponse($response); // return the message_id return $message_id; - } else if($response instanceof QueuedJobDescriptor) { + } elseif ($response instanceof QueuedJobDescriptor) { // return job return $response; } else { throw new \Exception("Tried to send, expected a SendResponse or a QueuedJobDescriptor but got type=" . gettype($response)); } - } catch (\Exception $e) { - Log::log('Mailgun-Sync / Mailgun error: ' . $e->getMessage(), \Psr\Log\LogLevel::NOTICE); + } catch (\Exception $exception) { + Log::log('Mailgun-Sync / Mailgun error: ' . $exception->getMessage(), \Psr\Log\LogLevel::NOTICE); } + return false; } /** * Process to, from, cc, bcc recipient headers that are in a email => displayName format * Returns a flattened array of values being recipients understandable to the Mailgun API - * @return array */ - public function processEmailDisplayName(array $data) { + public function processEmailDisplayName(array $data): array + { $list = []; foreach ($data as $email => $displayName) { - if (!empty($displayName)) { - $list[] = $displayName . " <" . $email . ">"; - } else { - $list[] = $email; - } + $list[] = empty($displayName) ? $email : $displayName . " <" . $email . ">"; } + return $list; } @@ -132,7 +132,8 @@ public function processEmailDisplayName(array $data) { * @param MessageConnector $connector the connector to the Mailgun PHP SDK client * @return array of parameters for the Mailgun API */ - public function prepareParameters(Email $email, MessageConnector $connector) : array { + public function prepareParameters(Email $email, MessageConnector $connector): array + { /** * @var Swift_Message @@ -143,7 +144,8 @@ public function prepareParameters(Email $email, MessageConnector $connector) : a throw new InvalidRequestException("There is no message associated with this request"); } - $recipients = $senders = []; + $recipients = []; + $senders = []; // Handle 'From' headers from Swift_Message $message_from = $message->getFrom(); @@ -164,7 +166,7 @@ public function prepareParameters(Email $email, MessageConnector $connector) : a $from = implode(",", $senders); // Assign custom parameters to the connector - if($email instanceof MailgunEmail) { + if ($email instanceof MailgunEmail) { $this->assignCustomParameters($email, $connector); } @@ -173,12 +175,7 @@ public function prepareParameters(Email $email, MessageConnector $connector) : a // process headers $headers = $message->getHeaders(); - if ($headers instanceof Swift_Mime_SimpleHeaderSet) { - $headers = $this->prepareHeaders( $headers ); - } else { - // ensure empty array - $headers = []; - } + $headers = $headers instanceof Swift_Mime_SimpleHeaderSet ? $this->prepareHeaders($headers) : []; // parameters for the API $parameters = []; @@ -195,16 +192,16 @@ public function prepareParameters(Email $email, MessageConnector $connector) : a */ $plain = $email->findPlainPart(); $plain_body = ''; - if($plain) { + if ($plain) { $plain_body = $plain->getBody(); } $parameters = array_merge($parameters, [ - 'from' => $from, - 'to' => $to, - 'subject' => $subject, - 'text' => $plain_body, - 'html' => $email->getBody() + 'from' => $from, + 'to' => $to, + 'subject' => $subject, + 'text' => $plain_body, + 'html' => $email->getBody() ]); // HEADERS: these generic headers override anything passed in or added as a custom parameter @@ -213,6 +210,7 @@ public function prepareParameters(Email $email, MessageConnector $connector) : a if (isset($headers['Cc'])) { $parameters['cc'] = $headers['Cc']; } + if (isset($headers['Bcc'])) { $parameters['bcc'] = $headers['Bcc']; } @@ -220,7 +218,7 @@ public function prepareParameters(Email $email, MessageConnector $connector) : a // Provide Mailgun the Attachments. Keys are 'fileContent' (the bytes) and filename (the file name) // If the key filename is not provided, Mailgun will use the name of the file, which may not be what you want displayed // TODO inline attchment disposition - if (!empty($attachments) && is_array($attachments)) { + if ($attachments !== [] && is_array($attachments)) { $parameters['attachment'] = $attachments; } @@ -241,14 +239,15 @@ public function prepareParameters(Email $email, MessageConnector $connector) : a * Given {@link \SilverStripe\Control\Email\Email} configuration, apply relevant values * @param array $parameters */ - public function assignDefaultParameters(&$parameters) { + public function assignDefaultParameters(&$parameters) + { // Override send all emails to $sendAllEmailsTo = Email::getSendAllEmailsTo(); - if($sendAllEmailsTo) { - if(is_string($sendAllEmailsTo)) { + if ($sendAllEmailsTo) { + if (is_string($sendAllEmailsTo)) { $parameters['to'] = $sendAllEmailsTo; - } else if(is_array($sendAllEmailsTo)) { + } elseif (is_array($sendAllEmailsTo)) { $sendAllEmailsTo = $this->processEmailDisplayName($sendAllEmailsTo); $parameters['to'] = implode(",", $sendAllEmailsTo); } else { @@ -258,12 +257,12 @@ public function assignDefaultParameters(&$parameters) { // Override from address, note always_from overrides this $sendAllEmailsFrom = Email::getSendAllEmailsFrom(); - if($sendAllEmailsFrom) { - if(is_string($sendAllEmailsFrom)) { + if ($sendAllEmailsFrom) { + if (is_string($sendAllEmailsFrom)) { $parameters['from'] = $sendAllEmailsFrom; - } else if(is_array($sendAllEmailsFrom)) { + } elseif (is_array($sendAllEmailsFrom)) { $sendAllEmailsFrom = $this->processEmailDisplayName($sendAllEmailsFrom); - $parameters['from'] = implode(",",$sendAllEmailsFrom); + $parameters['from'] = implode(",", $sendAllEmailsFrom); } else { throw new \Exception("Email::getSendAllEmailsFrom should be a string or array"); } @@ -271,19 +270,19 @@ public function assignDefaultParameters(&$parameters) { // Add or set CC defaults $ccAllEmailsTo = Email::getCCAllEmailsTo(); - if($ccAllEmailsTo) { + if ($ccAllEmailsTo) { $cc = ''; - if(is_string($ccAllEmailsTo)) { + if (is_string($ccAllEmailsTo)) { $cc = $ccAllEmailsTo; - } else if(is_array($ccAllEmailsTo)) { + } elseif (is_array($ccAllEmailsTo)) { $ccAllEmailsTo = $this->processEmailDisplayName($ccAllEmailsTo); $cc = implode(",", $ccAllEmailsTo); } else { throw new \Exception("Email::getCCAllEmailsTo should be a string or array"); } - if($cc) { - if(isset($parameters['cc'])) { + if ($cc !== '') { + if (isset($parameters['cc'])) { $parameters['cc'] .= "," . $cc; } else { $parameters['cc'] = $cc; @@ -293,33 +292,32 @@ public function assignDefaultParameters(&$parameters) { // Add or set BCC defaults $bccAllEmailsTo = Email::getBCCAllEmailsTo(); - if($bccAllEmailsTo) { + if ($bccAllEmailsTo) { $bcc = ''; - if(is_string($bccAllEmailsTo)) { + if (is_string($bccAllEmailsTo)) { $bcc = $bccAllEmailsTo; - } else if(is_array($bccAllEmailsTo)) { + } elseif (is_array($bccAllEmailsTo)) { $bccAllEmailsTo = $this->processEmailDisplayName($bccAllEmailsTo); $bcc = implode(",", $bccAllEmailsTo); } else { throw new \Exception("Email::getBCCAllEmailsTo should be a string or array"); } - if($bcc) { - if(isset($parameters['bcc'])) { + if ($bcc !== '') { + if (isset($parameters['bcc'])) { $parameters['bcc'] .= "," . $bcc; } else { $parameters['bcc'] = $bcc; } } } - } /** * @return array * Prepare headers for use in Mailgun */ - protected function prepareHeaders(Swift_Mime_SimpleHeaderSet $header_set) + protected function prepareHeaders(Swift_Mime_SimpleHeaderSet $header_set): array { $list = $header_set->getAll(); $headers = []; @@ -327,6 +325,7 @@ protected function prepareHeaders(Swift_Mime_SimpleHeaderSet $header_set) // Swift_Mime_Headers_ParameterizedHeader $headers[ $header->getFieldName() ] = $header->getFieldBody(); } + $denylist = $this->config()->get('denylist_headers'); if (is_array($denylist)) { $denylist = array_merge( @@ -337,6 +336,7 @@ protected function prepareHeaders(Swift_Mime_SimpleHeaderSet $header_set) unset($headers[ $header_name ]); } } + return $headers; } @@ -348,19 +348,21 @@ protected function prepareHeaders(Swift_Mime_SimpleHeaderSet $header_set) * 'mimetype' => $mimetype, * @param array $attachments Each value is a {@link Swift_Attachment} */ - protected function prepareAttachments(array $attachments) + protected function prepareAttachments(array $attachments): array { $mailgun_attachments = []; foreach ($attachments as $attachment) { if (!$attachment instanceof Swift_Attachment) { continue; } + $mailgun_attachments[] = [ 'fileContent' => $attachment->getBody(), 'filename' => $attachment->getFilename(), 'mimetype' => $attachment->getContentType() ]; } + return $mailgun_attachments; } @@ -369,11 +371,9 @@ protected function prepareAttachments(array $attachments) private 'id' => string '' (length=92) private 'message' => string 'Queued. Thank you.' (length=18) */ - final protected function saveResponse($message) + final protected function saveResponse($message): string { $message_id = $message->getId(); - $message_id = MessageConnector::cleanMessageId($message_id); - return $message_id; + return MessageConnector::cleanMessageId($message_id); } - } diff --git a/src/Exceptions/InvalidRequestException.php b/src/Exceptions/InvalidRequestException.php index 29feb28..a126f3a 100644 --- a/src/Exceptions/InvalidRequestException.php +++ b/src/Exceptions/InvalidRequestException.php @@ -1,4 +1,5 @@ filter([ 'JobStatus' => QueuedJob::STATUS_BROKEN, 'Implementation' => SendJob::class ]); $count = $descriptors->count(); - $kick = $skip = 0; - if($count > 0) { + $kick = 0; + $skip = 0; + if ($count > 0) { $this->totalSteps = $count; - foreach($descriptors as $descriptor) { - + foreach ($descriptors as $descriptor) { $data = @unserialize($descriptor->SavedJobData); - if(empty($data->parameters)) { + if (empty($data->parameters)) { // parameters cleared so pointless re-queuing $skip++; continue; @@ -55,7 +54,7 @@ public function process() $descriptor->JobStatus = QueuedJob::STATUS_NEW; $descriptor->StepsProcessed = 0; $descriptor->LastProcessedCount = -1; - $descriptor->Worker = null;// clear otherwise job is considered locked + $descriptor->Worker = '';// clear otherwise job is considered locked $descriptor->write(); $kick++; @@ -64,7 +63,7 @@ public function process() $this->addMessage( _t( - __CLASS__ . '.JOB_STATUS', + self::class . '.JOB_STATUS', "Marked {kick}, ignored {skip} broken SendJob descriptors as new", [ 'kick' => $kick, @@ -73,11 +72,10 @@ public function process() ), "info" ); - } else { $this->addMessage( _t( - __CLASS__ . '.JOB_STATUS_NO_JOBS', + self::class . '.JOB_STATUS_NO_JOBS', "No jobs can be re-queued" ), "info" @@ -85,6 +83,5 @@ public function process() } $this->isComplete = true; - } } diff --git a/src/Jobs/SendJob.php b/src/Jobs/SendJob.php index 60db017..f27fb42 100644 --- a/src/Jobs/SendJob.php +++ b/src/Jobs/SendJob.php @@ -1,4 +1,5 @@ $to, @@ -61,9 +61,10 @@ public function getSignature() $params = []; // these simple message params $parts = ['to','from','cc','bcc','subject']; - foreach($parts as $part) { - $params[ $part ] = isset($this->parameters[ $part ]) ? $this->parameters[ $part ] : ''; + foreach ($parts as $part) { + $params[ $part ] = $this->parameters[ $part ] ?? ''; } + // at this time $params['sendtime'] = microtime(true); return md5($this->domain . ":" . serialize($params)); @@ -73,12 +74,13 @@ public function getSignature() * Create the job * @param string $domain DEPRECATED * @param array $parameters for Mailgun API + * @phpstan-ignore constructor.unusedParameter */ public function __construct($domain = "", $parameters = []) { $this->connector = MessageConnector::create(); $this->domain = $this->connector->getApiDomain(); - if(!empty($parameters)) { + if (!empty($parameters)) { $this->parameters = $parameters; } } @@ -88,9 +90,7 @@ public function __construct($domain = "", $parameters = []) */ public function process() { - try { - if ($this->isComplete) { // the job has already been marked complete return; @@ -103,16 +103,16 @@ public function process() if (!$domain) { $msg = _t( - __CLASS__ . ".MISSING_API_DOMAIN", + self::class . ".MISSING_API_DOMAIN", "Mailgun configuration is missing the Mailgun API domain value" ); throw new JobProcessingException($msg); } $parameters = $this->parameters; - if(empty($parameters)) { + if (empty($parameters)) { $msg = _t( - __CLASS__ . ".EMPTY_PARAMS", + self::class . ".EMPTY_PARAMS", "Mailgun SendJob was called with empty parameters" ); throw new JobProcessingException($msg); @@ -138,16 +138,15 @@ public function process() throw new JobProcessingException( $this->addMessage( _t( - __CLASS__ . ".SEND_INVALID_RESPONSE_FROM_MAILGUN", + self::class . ".SEND_INVALID_RESPONSE_FROM_MAILGUN", "SendJob invalid response or no message.id returned" ) ) ); - } catch (JobProcessingException $e) { $this->addMessage( _t( - __CLASS__ . ".SEND_EXCEPTON", + self::class . ".SEND_EXCEPTON", "Mailgun send processing exception: {error}", [ "error" => $e->getMessage() @@ -158,7 +157,7 @@ public function process() } catch (\Exception $e) { $this->addMessage( _t( - __CLASS__ . ".GENERAL_EXCEPTON", + self::class . ".GENERAL_EXCEPTON", "Mailgun send general exception: {error}", [ "error" => $e->getMessage() @@ -175,10 +174,9 @@ public function process() */ throw new \Exception( _t( - __CLASS__ . ".MAILGUN_SEND_FAILED", + self::class . ".MAILGUN_SEND_FAILED", "Mailgun send failed. Check status.mailgun.com or connectivity?" ) ); - } } diff --git a/src/Jobs/TruncateJob.php b/src/Jobs/TruncateJob.php index a0dc382..9319cef 100644 --- a/src/Jobs/TruncateJob.php +++ b/src/Jobs/TruncateJob.php @@ -1,4 +1,5 @@ days > 0) { + if ($this->days > 0) { // allow for parts of days to the nearest hour $hours = round($this->days * 24); $dt = new DateTime("now -{$hours}hour"); } else { $dt = new DateTime(); } + $dt_formatted = $dt->format('Y-m-d H:i:s'); $this->addMessage("Removing events created before {$dt_formatted}", "info"); $events = MailgunEvent::get()->filter('Created:LessThan', $dt_formatted); $count = $events->count(); - if($count > 0) { + if ($count > 0) { $events->removeAll(); $this->addMessage("Removed {$count} events", "info"); } else { $this->addMessage("No events to remove", "info"); } + $this->currentStep = 1; $this->isComplete = true; } @@ -77,6 +80,7 @@ public function afterComplete() { $next = new DateTime(); $next->modify('+' . $this->recreate_in . ' seconds'); + $job = new TruncateJob($this->days, $this->recreate_in); $service = singleton(QueuedJobService::class); $descriptor_id = $service->queueJob($job, $next->format('Y-m-d H:i:s')); diff --git a/src/Models/Log.php b/src/Models/Log.php index 94d81a7..122067d 100644 --- a/src/Models/Log.php +++ b/src/Models/Log.php @@ -1,4 +1,5 @@ 'Text', // JSON encoded storage key ]; - /** - * @var array - */ - private static $summary_fields = [ + private static array $summary_fields = [ 'ID' => '#', 'EventType' => 'Event', 'Severity' => 'Severity', @@ -114,9 +107,8 @@ class MailgunEvent extends DataObject implements PermissionProvider /** * Defines a default list of filters for the search context - * @var array */ - private static $searchable_fields = [ + private static array $searchable_fields = [ 'Reason', 'Severity', 'EventType', @@ -125,10 +117,7 @@ class MailgunEvent extends DataObject implements PermissionProvider 'MessageId', ]; - /** - * @var array - */ - private static $indexes = [ + private static array $indexes = [ 'Created' => true, 'LastEdited' => true, 'EventType' => true, @@ -176,15 +165,17 @@ private function createGroupsAndPermissions() $manager_group = Group::create(); $manager_group->Code = $manager_code; } + $manager_group->Title = "Mailgun Managers"; $manager_group_id = $manager_group->write(); if ($manager_group_id) { $permissions = $manager_group->Permissions()->filter('Code', [ self::PERMISSIONS_DELETE, self::PERMISSIONS_VIEW ]); $codes = $permissions->column('Code'); - if(!in_array( self::PERMISSIONS_DELETE, $codes)) { + if (!in_array(self::PERMISSIONS_DELETE, $codes)) { Permission::grant($manager_group_id, self::PERMISSIONS_DELETE); } - if(!in_array( self::PERMISSIONS_VIEW, $codes)) { + + if (!in_array(self::PERMISSIONS_VIEW, $codes)) { Permission::grant($manager_group_id, self::PERMISSIONS_VIEW); } } @@ -201,13 +192,13 @@ public function getTitle() /** * Returns the age of the event, in seconds */ - public function Age() + public function Age(): ?float { if ($this->Timestamp == 0) { - return false; + return null; } - $age = time() - $this->Timestamp; - return $age; + + return time() - $this->Timestamp; } /** @@ -226,6 +217,7 @@ public function canDelete($member = null) if (!$member) { $member = Member::currentUser(); } + return Permission::check(self::PERMISSIONS_DELETE, 'any', $member); } @@ -237,6 +229,7 @@ public function canView($member = null) if (!$member) { $member = Member::currentUser(); } + return Permission::check(self::PERMISSIONS_VIEW, 'any', $member); } @@ -288,37 +281,34 @@ public function getCmsFields() */ public function getSiblingEvents() { - $events = MailgunEvent::get()->filter('MessageId', $this->MessageId)->sort('Timestamp ASC'); - return $events; + return MailgunEvent::get()->filter('MessageId', $this->MessageId)->sort('Timestamp ASC'); } /** * UTC date/time based on Timestamp of this event - * @return string */ - public function UTCDateTime() + public function UTCDateTime(): string { return $this->RecordDateTime("UTC"); } /** * Local date/time based on Timestamp of this event - * @return string */ - public function LocalDateTime() + public function LocalDateTime(): string { return $this->RecordDateTime("Australia/Sydney"); } /** * Return RFC2822 formatted string of event timestamp - * @return string */ - private function RecordDateTime($timezone = "UTC") + private function RecordDateTime(string $timezone = "UTC"): string { if (!$this->Timestamp) { return ""; } + $dt = new DateTime(); $dt->setTimestamp($this->Timestamp); $dt->setTimezone(new DateTimeZone($timezone)); @@ -328,12 +318,12 @@ private function RecordDateTime($timezone = "UTC") /** * Combining all event types that are related to a user action */ - public static function UserActionStatus() + public static function UserActionStatus(): array { return [ self::OPENED, self::CLICKED, self::UNSUBSCRIBED, self::COMPLAINED ]; } - public function IsFailed() + public function IsFailed(): bool { return $this->EventType == self::FAILED; } @@ -341,130 +331,118 @@ public function IsFailed() /** * @deprecated use IsFailed() in order to match API event naming */ - public function IsFailure() + public function IsFailure(): bool { return $this->IsFailed(); } // Mailgun has not even attempted to deliver these - public function IsRejected() + public function IsRejected(): bool { return $this->EventType == self::REJECTED; } /** * Helper method to determin if event is failed || rejected - * @return boolean */ - public function IsFailedOrRejected() + public function IsFailedOrRejected(): bool { return $this->IsFailed() || $this->IsRejected(); } - /** - * @return boolean - */ - public function IsDelivered() + public function IsDelivered(): bool { return $this->EventType == self::DELIVERED; } - /** - * @return boolean - */ - public function IsAccepted() + public function IsAccepted(): bool { return $this->EventType == self::ACCEPTED; } - /** - * @return boolean - */ - public function IsUserEvent() + public function IsUserEvent(): bool { return in_array($this->EventType, self::UserActionStatus()); } /** * Helper method to create a UTC Date from a timestamp - * @return string */ - private static function CreateUTCDate($timestamp) + private function CreateUTCDate($timestamp): string { - return self::CreateUTCDateTime($timestamp, "Y-m-d"); + return $this->CreateUTCDateTime($timestamp, "Y-m-d"); } /** * Helper method to create a UTC DateTime from a timestamp - * @return string */ - private static function CreateUTCDateTime($timestamp, $format = "Y-m-d H:i:s") + private function CreateUTCDateTime($timestamp, string $format = "Y-m-d H:i:s"): string { $dt = new DateTime(); $dt->setTimestamp($timestamp); $dt->setTimezone(new DateTimeZone('UTC')); - return $dt->format('Y-m-d H:i:s'); + return $dt->format($format); } /** * GetByMessageDetails - retrieve an event based on the message/timestamp/recipient/event type + * @deprecated + * @phpstan-ignore method.unused */ - private static function GetByMessageDetails($message_id, $timestamp, $recipient, $event_type) + private function GetByMessageDetails($message_id, $timestamp, $recipient, $event_type): false|object { if (!$message_id || !$timestamp || !$recipient || !$event_type) { return false; } + $event = MailgunEvent::get()->filter(['MessageId' => $message_id, 'Timestamp' => $timestamp, 'Recipient' => $recipient, 'EventType' => $event_type ])->first(); if (!empty($event->ID)) { return $event; } + return false; } /** * Return message header from the {@link Mailgun\Model\Event\Event} + * @deprecated * @return string * @param string $header the header to retrieve + * @phpstan-ignore method.unused */ private function getMessageHeader(MailgunEventModel $event, $header) { $message = $event->getMessage(); - $value = isset($message['headers'][$header]) ? $message['headers'][$header] : ''; - return $value; + return $message['headers'][$header] ?? ''; } /** * Based on a delivery status returned from Mailgun, grab relevant details for this record - * @param array $delivery_status */ - private function saveDeliveryStatus(array $delivery_status) + private function saveDeliveryStatus(array $delivery_status): bool { - $this->DeliveryStatusMessage = isset($delivery_status['message']) ? $delivery_status['message'] : ''; - $this->DeliveryStatusDescription = isset($delivery_status['description']) ? $delivery_status['description'] : ''; - $this->DeliveryStatusCode = isset($delivery_status['code']) ? $delivery_status['code'] : ''; - $this->DeliveryStatusAttempts = isset($delivery_status['attempt-no']) ? $delivery_status['attempt-no'] : ''; - $this->DeliveryStatusSession = isset($delivery_status['session-seconds']) ? $delivery_status['session-seconds'] : ''; - $this->DeliveryStatusMxHost = isset($delivery_status['mx-host']) ? $delivery_status['mx-host'] : ''; + $this->DeliveryStatusMessage = $delivery_status['message'] ?? ''; + $this->DeliveryStatusDescription = $delivery_status['description'] ?? ''; + $this->DeliveryStatusCode = $delivery_status['code'] ?? ''; + $this->DeliveryStatusAttempts = $delivery_status['attempt-no'] ?? ''; + $this->DeliveryStatusSession = $delivery_status['session-seconds'] ?? ''; + $this->DeliveryStatusMxHost = $delivery_status['mx-host'] ?? ''; return true; } /** * Given a Mailgun\Model\Event\Event, store if possible - * @param MailgunEventModel $event * @return MailgunEvent|boolean */ public function storeEvent(MailgunEventModel $event) { - $this->extend('onBeforeStoreMailgunEvent', $event); $mailgun_event_id = $event->getId(); $event_type = $event->getEvent(); - $variables = $event->getUserVariables(); $timestamp = $event->getTimestamp(); $status = $event->getDeliveryStatus(); $storage = $event->getStorage(); - $tags = $event->getTags(); $recipient = $event->getRecipient(); // get message id from headers @@ -478,19 +456,20 @@ public function storeEvent(MailgunEventModel $event) $mailgun_event->EventId = $mailgun_event_id;// webhooks do not provide a mailgun event id $mailgun_event->MessageId = $mailgun_message_id; $mailgun_event->Timestamp = $timestamp; - $mailgun_event->UTCEventDate = self::CreateUTCDate($timestamp); + $mailgun_event->UTCEventDate = $this->CreateUTCDate($timestamp); $mailgun_event->Severity = $event->getSeverity(); $mailgun_event->EventType = $event_type; $mailgun_event->Recipient = $recipient;// if the message is sent to Someone , the $recipient value will be someone@example.com $mailgun_event->Reason = $event->getReason();// doesn't appear to be set for 'rejected' events $mailgun_event->saveDeliveryStatus($status); - $mailgun_event->StorageURL = isset($storage['url']) ? $storage['url'] : ''; + $mailgun_event->StorageURL = $storage['url'] ?? ''; $mailgun_event->DecodedStorageKey = "";// no need to store this $mailgun_event_id = $mailgun_event->write(); if (!$mailgun_event_id) { // could not create record return false; } + $this->extend('onAfterStoreMailgunEvent', $event, $mailgun_event); return $mailgun_event; } @@ -501,12 +480,10 @@ public function storeEvent(MailgunEventModel $event) */ public function GetRecipientFailures() { - $events = MailgunEvent::get() + return MailgunEvent::get() ->filter('MessageId', $this->MessageId) // Failures for this specific message ->filter('Recipient', $this->Recipient) // Recipient is an email address ->filterAny('EventType', [ self::FAILED, self::REJECTED ]) ->count(); - return $events; } - } diff --git a/src/ORM/FieldType/DBLongText.php b/src/ORM/FieldType/DBLongText.php index d9be872..b2f8e31 100644 --- a/src/ORM/FieldType/DBLongText.php +++ b/src/ORM/FieldType/DBLongText.php @@ -1,4 +1,5 @@ ' formatting) + private static string $from_address = "from@example.com"; + + private static string $from_name = "From Tester";// option the from name (e.g for 'Joe ' formatting) // Test body HTML - private static $test_body = "

Header provider strategic

" + private static string $test_body = "

Header provider strategic

" . "

consulting support conversation advertisements policy promotional request.

" . "

Option purpose programming

"; - public function setUp() : void + public function setUp(): void { parent::setUp(); // Avoid using TestMailer for this test @@ -70,7 +74,8 @@ public function setUp() : void /** * Test that the API domain configured is maintained */ - public function testApiDomain() { + public function testApiDomain(): void + { $currentValue = Config::inst()->get(Base::class, 'api_domain'); $value = "testing.example.org"; Config::modify()->set(Base::class, 'api_domain', $value); @@ -83,12 +88,12 @@ public function testApiDomain() { /** * Test that the API endpoint configured is maintained */ - public function testApiEndpoint() { - + public function testApiEndpoint(): void + { $value = 'API_ENDPOINT_EU'; Config::modify()->set(Base::class, 'api_endpoint_region', $value); $connector = MessageConnector::create(); - $domains = $connector->getClient(); + $connector->getClient(); // assert that the expected URL value is what was set on the client $this->assertEquals(constant(Base::class . "::{$value}"), $connector->getApiEndpointRegion()); @@ -96,13 +101,14 @@ public function testApiEndpoint() { $value = ''; Config::modify()->set(Base::class, 'api_endpoint_region', $value); $connector = MessageConnector::create(); - $domains = $connector->getClient(); + $connector->getClient(); // when no value is set, the default region URL is used $this->assertEquals('', $connector->getApiEndpointRegion()); } - protected function getCustomParameters($to_address, $send_in) : array { + protected function getCustomParameters($to_address, $send_in): array + { $variables = [ 'test' => 'true', 'foo' => 'bar', @@ -129,18 +135,19 @@ protected function getCustomParameters($to_address, $send_in) : array { 'headers' => $headers, 'recipient-variables' => $recipient_variables ]; - if($send_in > 0) { + if ($send_in > 0) { $customParameters['send-in'] = $send_in; } + return $customParameters; } /** * test mailer delivery only, no sync or event checking, just that we get the expected response + * @return mixed[] */ - public function testMailerDelivery($subject = "test_mailer_delivery", $send_in = 0) + public function testMailerDelivery($subject = "test_mailer_delivery", $send_in = 0): array { - $to_address = self::config()->get('to_address'); $to_name = self::config()->get('to_name'); $this->assertNotEmpty($to_address); @@ -158,6 +165,8 @@ public function testMailerDelivery($subject = "test_mailer_delivery", $send_in = $email = Email::create(); + $this->assertInstanceOf(MailgunEmail::class, $email); + $email->setFrom($from); $email->setTo($to); $email->setCc(["cc@example.com" => "Cc Person"]); @@ -166,16 +175,18 @@ public function testMailerDelivery($subject = "test_mailer_delivery", $send_in = if ($cc = self::config()->get('cc_address')) { $email->setCc($cc); } + $htmlBody = self::config()->get('test_body'); - $email->setBody( $htmlBody ); + $email->setBody($htmlBody); $customParameters = $this->getCustomParameters($to_address, $send_in); + /** @var \NSWDPC\Messaging\Mailgun\MailgunEmail $email */ $email->setCustomParameters($customParameters); // send the email, returns a message_id if delivered $response = $email->send(); - if(Config::inst()->get(Base::class, 'send_via_job') == 'no') { + if (Config::inst()->get(Base::class, 'send_via_job') == 'no') { $this->assertEquals($response, TestMessage::MSG_ID); } else { // via job @@ -186,7 +197,7 @@ public function testMailerDelivery($subject = "test_mailer_delivery", $send_in = $this->assertEquals( "{$from_name} <{$from_address}>", - $sendData['parameters']['from'] , + $sendData['parameters']['from'], "From: mismatch" ); @@ -208,21 +219,21 @@ public function testMailerDelivery($subject = "test_mailer_delivery", $send_in = "Bcc: mismatch" ); - foreach($customParameters['options'] as $k=>$v) { - $this->assertEquals( $sendData['parameters']["o:{$k}"], $v, "Option $k failed"); + foreach ($customParameters['options'] as $k=>$v) { + $this->assertEquals($sendData['parameters']["o:{$k}"], $v, "Option {$k} failed"); } - foreach($customParameters['variables'] as $k=>$v) { - $this->assertEquals( $sendData['parameters']["v:{$k}"], $v , "Variable $k failed"); + foreach ($customParameters['variables'] as $k=>$v) { + $this->assertEquals($sendData['parameters']["v:{$k}"], $v, "Variable {$k} failed"); } - foreach($customParameters['headers'] as $k=>$v) { - $this->assertEquals( $sendData['parameters']["h:{$k}"], $v , "Header $k failed"); + foreach ($customParameters['headers'] as $k=>$v) { + $this->assertEquals($sendData['parameters']["h:{$k}"], $v, "Header {$k} failed"); } - $this->assertEquals( json_encode($customParameters['recipient-variables']), $sendData['parameters']['recipient-variables'] ); + $this->assertEquals(json_encode($customParameters['recipient-variables']), $sendData['parameters']['recipient-variables']); - $this->assertEquals($htmlBody, $sendData['parameters']['html'] ); + $this->assertEquals($htmlBody, $sendData['parameters']['html']); return $sendData; } @@ -230,7 +241,8 @@ public function testMailerDelivery($subject = "test_mailer_delivery", $send_in = /** * Test delivery via a Job */ - public function testJobMailerDelivery() { + public function testJobMailerDelivery(): void + { Config::modify()->set(Base::class, 'send_via_job', 'yes'); // send message $subject = "test_mailer_delivery_job"; @@ -244,7 +256,8 @@ public function testJobMailerDelivery() { /** * Test delivery via a Job */ - public function testJobMailerDeliveryInFuture() { + public function testJobMailerDeliveryInFuture(): void + { Config::modify()->set(Base::class, 'send_via_job', 'yes'); // send message $subject = "test_mailer_delivery_job_future"; @@ -258,9 +271,8 @@ public function testJobMailerDeliveryInFuture() { } - protected function checkJobData(QueuedJobDescriptor $job, $subject, $send_in) { - - + protected function checkJobData(QueuedJobDescriptor $job, $subject, $send_in) + { $this->assertEquals(SendJob::class, $job->Implementation); $data = @unserialize($job->SavedJobData ?? ''); @@ -290,27 +302,26 @@ protected function checkJobData(QueuedJobDescriptor $job, $subject, $send_in) { $customParameters = $this->getCustomParameters(self::config()->get('to_address'), $send_in); - foreach($customParameters['options'] as $k=>$v) { - $this->assertEquals( $data->parameters["o:{$k}"], $v, "Option $k failed"); + foreach ($customParameters['options'] as $k=>$v) { + $this->assertEquals($data->parameters["o:{$k}"], $v, "Option {$k} failed"); } - foreach($customParameters['variables'] as $k=>$v) { - $this->assertEquals( $data->parameters["v:{$k}"], $v , "Variable $k failed"); + foreach ($customParameters['variables'] as $k=>$v) { + $this->assertEquals($data->parameters["v:{$k}"], $v, "Variable {$k} failed"); } - foreach($customParameters['headers'] as $k=>$v) { - $this->assertEquals( $data->parameters["h:{$k}"], $v , "Header $k failed"); + foreach ($customParameters['headers'] as $k=>$v) { + $this->assertEquals($data->parameters["h:{$k}"], $v, "Header {$k} failed"); } - $this->assertEquals( json_encode($customParameters['recipient-variables']), $data->parameters['recipient-variables'] ); - + $this->assertEquals(json_encode($customParameters['recipient-variables']), $data->parameters['recipient-variables']); } /** * Test always from setting */ - public function testAlwaysFrom() { - + public function testAlwaysFrom(): void + { $alwaysFromEmail = 'alwaysfrom@example.com'; Config::modify()->set(MailgunMailer::class, 'always_from', $alwaysFromEmail); @@ -352,23 +363,26 @@ public function testAlwaysFrom() { /** * test API delivery only */ - public function testAPIDelivery() + public function testAPIDelivery(): void { - Config::modify()->set(Base::class, 'send_via_job', 'no'); $connector = MessageConnector::create(); - $to = $to_address = self::config()->get('to_address'); + $to = self::config()->get('to_address'); + $to_address = $to; $to_name = self::config()->get('to_name'); if ($to_name) { $to = $to_name . ' <' . $to_address . '>'; } + $this->assertNotEmpty($to_address); - $from = $from_address = self::config()->get('from_address'); + $from = self::config()->get('from_address'); + $from_address = $from; $from_name = self::config()->get('from_name'); if ($from_name) { $from = $from_name . ' <' . $from_address . '>'; } + $this->assertNotEmpty($from_address); $subject = "test_api_delivery"; @@ -399,7 +413,7 @@ public function testAPIDelivery() $this->assertArrayHasKey('parameters', $sendData); - foreach(['o:testmode','o:tag','from','to','subject','text','html'] as $key) { + foreach (['o:testmode','o:tag','from','to','subject','text','html'] as $key) { $this->assertEquals($parameters[ $key ], $sendData['parameters'][ $key ]); } } @@ -407,8 +421,8 @@ public function testAPIDelivery() /** * Test sending with default values set */ - public function testSendWithDefaultConfiguration() { - + public function testSendWithDefaultConfiguration(): void + { $overrideTo = 'allemails@example.com'; $overrideFrom = 'allemailsfrom@example.com'; $overrideCc = 'ccallemailsto@example.com'; @@ -448,7 +462,7 @@ public function testSendWithDefaultConfiguration() { $sendData = TestMessage::getSendData(); - foreach(['domain','parameters','sentVia','client','in'] as $key) { + foreach (['domain','parameters','sentVia','client','in'] as $key) { $this->assertArrayHasKey($key, $sendData); } @@ -459,15 +473,15 @@ public function testSendWithDefaultConfiguration() { $this->assertEquals($overrideTo, $sendData['parameters']['to']); $this->assertEquals($overrideFrom, $sendData['parameters']['from']); - $this->assertContains( $overrideCc, explode(",", $sendData['parameters']['cc']) ); - $this->assertContains( "{$overrideBccName} <{$overrideBcc}>", explode(",", $sendData['parameters']['bcc']) ); - + $this->assertContains($overrideCc, explode(",", (string) $sendData['parameters']['cc'])); + $this->assertContains("{$overrideBccName} <{$overrideBcc}>", explode(",", (string) $sendData['parameters']['bcc'])); } /** * test a message with attachments */ - public function testAttachmentDelivery() { + public function testAttachmentDelivery(): void + { $to_address = self::config()->get('to_address'); $to_name = self::config()->get('to_name'); $this->assertNotEmpty($to_address); @@ -490,24 +504,25 @@ public function testAttachmentDelivery() { $email->setFrom($from); $email->setTo($to); $email->setSubject($subject); + $htmlBody = self::config()->get('test_body'); - $email->setBody( $htmlBody ); + $email->setBody($htmlBody); $files = [ "test_attachment.pdf" => 'application/pdf', "test_attachment.txt" => 'text/plain' ]; $f = 1; - foreach($files as $file => $mimetype) { + foreach ($files as $file => $mimetype) { $email->addAttachment( - dirname(__FILE__) . "/attachments/{$file}", + __DIR__ . "/attachments/{$file}", $file, $mimetype ); $f++; } - $response = $email->send(); + $email->send(); $sendData = TestMessage::getSendData(); @@ -517,22 +532,22 @@ public function testAttachmentDelivery() { $f = 1; $this->assertEquals(count($files), count($attachments)); - foreach($attachments as $attachment) { - $this->assertArrayHasKey( 'filename', $attachment ); - $this->assertArrayHasKey( 'mimetype', $attachment ); - $this->assertArrayHasKey( 'fileContent', $attachment ); - foreach($files as $file => $mimetype) { - if($file == $attachment['filename']) { + foreach ($attachments as $attachment) { + $this->assertArrayHasKey('filename', $attachment); + $this->assertArrayHasKey('mimetype', $attachment); + $this->assertArrayHasKey('fileContent', $attachment); + foreach ($files as $file => $mimetype) { + if ($file == $attachment['filename']) { $this->assertEquals($mimetype, $attachment['mimetype']); $this->assertNotEmpty($attachment['fileContent']); $this->assertEquals( - file_get_contents( dirname(__FILE__) . "/attachments/{$file}" ), + file_get_contents(__DIR__ . "/attachments/{$file}"), $attachment['fileContent'] ); } } + $f++; } } - } diff --git a/tests/TestMessage.php b/tests/TestMessage.php index 7d57bf0..eb48c47 100644 --- a/tests/TestMessage.php +++ b/tests/TestMessage.php @@ -1,4 +1,5 @@ queueAndSend($domain, $parameters, $in); break; } + // no break case 'no': default: $this->sentVia = 'direct-to-api'; @@ -88,15 +88,16 @@ protected function sendMessage(array $parameters) { /** * Set data that would be used */ - public function setSendData(array $data) { + public function setSendData(array $data) + { self::$sendData = $data; } /** * Get data that would be used */ - public static function getSendData() : array { + public static function getSendData(): array + { return self::$sendData; } - } diff --git a/tests/WebhookTest.php b/tests/WebhookTest.php index 46a0e80..2a47695 100644 --- a/tests/WebhookTest.php +++ b/tests/WebhookTest.php @@ -15,13 +15,14 @@ */ class WebhookTest extends FunctionalTest { + private string $webhook_filter_variable = 'skjhgiehg943753-"'; - private $webhook_filter_variable = 'skjhgiehg943753-"'; - private $webhook_previous_filter_variable = 'snsd875bslw['; + private string $webhook_previous_filter_variable = 'snsd875bslw['; protected $usesDatabase = true; - public function setUp() : void { + public function setUp(): void + { parent::setUp(); Config::modify()->set(Base::class, 'webhook_filter_variable', $this->webhook_filter_variable); Config::modify()->set(Base::class, 'webhook_previous_filter_variable', $this->webhook_previous_filter_variable); @@ -31,21 +32,24 @@ public function setUp() : void { /** * Get test data from disk */ - protected function getWebhookRequestData($event_type) { - return file_get_contents( dirname(__FILE__) . "/webhooks/{$event_type}.json"); + protected function getWebhookRequestData($event_type): string|false + { + return file_get_contents(__DIR__ . "/webhooks/{$event_type}.json"); } /** * Our configured endpoint for submitting POST data */ - protected function getSubmissionUrl() { + protected function getSubmissionUrl(): string + { return '_wh/submit'; } /** * Webhook Mailgun API connector */ - protected function getConnector() { + protected function getConnector() + { return Webhook::create(); } @@ -53,7 +57,8 @@ protected function getConnector() { * Set a signing key in Configuration * @param string $signing_key */ - protected function setSigningKey($signing_key) { + protected function setSigningKey($signing_key) + { Config::modify()->set(Base::class, 'webhook_signing_key', $signing_key); } @@ -63,7 +68,8 @@ protected function setSigningKey($signing_key) { * @param string $request_data * @return array */ - protected function setSignatureOnRequest($signing_key, $request_data) { + protected function setSignatureOnRequest($signing_key, $request_data) + { $decoded = json_decode($request_data, true); $connector = $this->getConnector(); $signature = $connector->sign_token($decoded['signature']); @@ -71,7 +77,8 @@ protected function setSignatureOnRequest($signing_key, $request_data) { return $decoded; } - protected function setWebhookFilterVariable($data, $value) { + protected function setWebhookFilterVariable(array $data, $value): array + { $data['event-data']['user-variables']['wfv'] = $value; return $data; } @@ -81,8 +88,8 @@ protected function setWebhookFilterVariable($data, $value) { * and one that should fail * @param string $type */ - protected function sendWebhookRequest($type) { - + protected function sendWebhookRequest($type) + { $signing_key = "TEST_SHOULD_PASS"; $this->setSigningKey($signing_key); @@ -93,6 +100,7 @@ protected function sendWebhookRequest($type) { $session = null; $data = $this->setSignatureOnRequest($signing_key, $this->getWebhookRequestData($type)); $data = $this->setWebhookFilterVariable($data, $this->webhook_filter_variable); + $cookies = null; $body = json_encode($data, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT); @@ -108,7 +116,7 @@ protected function sendWebhookRequest($type) { // test if the event was saved $record = MailgunEvent::get()->filter('EventId', $event->getId())->first(); - $this->assertTrue( $record && $record->exists() , "DB Mailgun event does not exist for event {$event->getId()}"); + $this->assertTrue($record && $record->exists(), "DB Mailgun event does not exist for event {$event->getId()}"); // change the webhook config variable to the previous var $data = $this->setWebhookFilterVariable($data, $this->webhook_previous_filter_variable); @@ -130,7 +138,7 @@ protected function sendWebhookRequest($type) { ); // remove webhook variable and test - unset( $data['event-data']['user-variables']['wfv'] ); + unset($data['event-data']['user-variables']['wfv']); Config::modify()->set(Base::class, 'webhook_filter_variable', ''); Config::modify()->set(Base::class, 'webhook_previous_filter_variable', ''); @@ -143,35 +151,40 @@ protected function sendWebhookRequest($type) { $response->getStatusCode(), 'Expected failed response code 406 with incorrect signing_key but got ' . $response->getStatusCode() . "/" . $response->getStatusDescription() ); - } - public function testWebookDelivered() { + public function testWebookDelivered(): void + { $this->sendWebhookRequest("delivered"); } - public function testWebookClick() { + public function testWebookClick(): void + { $this->sendWebhookRequest("clicked"); } - public function testWebookOpened() { + public function testWebookOpened(): void + { $this->sendWebhookRequest("opened"); } - public function testWebookFailedPermanent() { + public function testWebookFailedPermanent(): void + { $this->sendWebhookRequest("failed_permanent"); } - public function testWebookFailedTemporary() { + public function testWebookFailedTemporary(): void + { $this->sendWebhookRequest("failed_temporary"); } - public function testWebookUnsubscribed() { + public function testWebookUnsubscribed(): void + { $this->sendWebhookRequest("unsubscribed"); } - public function testWebookComplained() { + public function testWebookComplained(): void + { $this->sendWebhookRequest("complained"); } - }