From d148279127d3a88955a4fbe9ca31379017afea69 Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Fri, 12 Oct 2018 17:38:51 +0100 Subject: [PATCH 01/22] Add status field to contact --- src/Dotmailer.php | 3 ++- src/Entity/Contact.php | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Dotmailer.php b/src/Dotmailer.php index 036d4f2..1def645 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -161,7 +161,8 @@ public function getContactByEmail(string $email): Contact $contact->email, $contact->optInType, $contact->emailType, - $contact->dataFields + $contact->dataFields, + $contact->status ); } diff --git a/src/Entity/Contact.php b/src/Entity/Contact.php index 71f3e03..01129ef 100644 --- a/src/Entity/Contact.php +++ b/src/Entity/Contact.php @@ -37,6 +37,11 @@ final class Contact implements Arrayable */ private $dataFields; + /** + * @var string + */ + private $status; + /** * @param int|null $id * @param string $email @@ -49,13 +54,15 @@ public function __construct( string $email, string $optInType = self::OPT_IN_TYPE_UNKNOWN, string $emailType = self::EMAIL_TYPE_PLAIN_TEXT, - array $dataFields = [] + array $dataFields = [], + string $status = null ) { $this->id = $id; $this->email = $email; $this->optInType = $optInType; $this->emailType = $emailType; $this->dataFields = $dataFields; + $this->status = $status; } /** @@ -161,6 +168,7 @@ public function asArray(): array 'optInType' => $this->optInType, 'emailType' => $this->emailType, 'dataFields' => $this->dataFields, + 'status' => $this->status, ]; } } From 5207659cabfe03b48d6e291c74a685b77eac33dc Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Fri, 12 Oct 2018 17:48:33 +0100 Subject: [PATCH 02/22] Function to return status --- src/Entity/Contact.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Entity/Contact.php b/src/Entity/Contact.php index 01129ef..2a18e07 100644 --- a/src/Entity/Contact.php +++ b/src/Entity/Contact.php @@ -105,6 +105,14 @@ public function getEmailType(): string return $this->emailType; } + /** + * @return string + */ + public function getStatus(): string + { + return $this->status; + } + /** * @return array */ From cae1610cc834ac53b34d9bcfe5c662a8720270da Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Tue, 16 Oct 2018 17:28:00 +0100 Subject: [PATCH 03/22] Add missing required description tag to XML --- phpmd.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpmd.xml b/phpmd.xml index a3d0c9c..b2245bf 100644 --- a/phpmd.xml +++ b/phpmd.xml @@ -7,6 +7,8 @@ xsi:noNamespaceSchemaLocation=" http://pmd.sf.net/ruleset_xml_schema.xsd"> + + From 86f993a3bcb2b933736654c91b48f79a681e7409 Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Tue, 16 Oct 2018 17:28:38 +0100 Subject: [PATCH 04/22] Remove unsupported mixed return type --- src/Entity/DataField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/DataField.php b/src/Entity/DataField.php index 6d10f95..3d52b8f 100644 --- a/src/Entity/DataField.php +++ b/src/Entity/DataField.php @@ -77,7 +77,7 @@ public function getVisibility(): string /** * @return mixed|null */ - public function getDefaultValue(): ?mixed + public function getDefaultValue() { return $this->defaultValue; } From 7c220c83771091cc6a7b4d16eca33449d9ff8740 Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Tue, 16 Oct 2018 17:32:21 +0100 Subject: [PATCH 05/22] Coverage for Get suppressed contacts since date --- README.md | 1 + src/Dotmailer.php | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/README.md b/README.md index 021fa1a..34a43da 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Currently the following endpoints are covered: - [x] Get unsubscribed contacts since date - [x] Unsubscribe contact - [x] Resubscribe contact + - [ ] Get suppressed contacts since date - [ ] **Contact data fields** - [x] Create contact data field - [x] Delete contact data field diff --git a/src/Dotmailer.php b/src/Dotmailer.php index 1def645..6507ad8 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -376,4 +376,43 @@ function (string $name, string $value) { ] ); } + + /** + * @param \DateTimeInterface $dateTime + * @param int|null $select + * @param int|null $skip + * + * @return Contact[] + */ + public function getSuppressedContactsSince( + \DateTimeInterface $dateTime, + int $select = null, + int $skip = null + ): array { + $this->response = $this->adapter->get( + '/v2/contacts/suppressed-since/' . $dateTime->format('Y-m-d'), + array_filter([ + 'select' => $select, + 'skip' => $skip, + ]) + ); + + $suppressions = []; + + foreach (json_decode($this->response->getBody()->getContents()) as $suppression) { + $suppressions[] = [ + 'suppressedContact' => new Contact( + $suppression->suppressedContact->id, + $suppression->suppressedContact->email, + $suppression->suppressedContact->optInType, + $suppression->suppressedContact->emailType, + $suppression->suppressedContact->status, + ), + 'dateRemoved' => new \DateTime($suppression->dateRemoved), + 'reason' => $suppression->reason, + ]; + } + + return $suppressions; + } } From 7a09f91503da99b5e311a2a33f4cdcca4b92d086 Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Tue, 16 Oct 2018 17:34:32 +0100 Subject: [PATCH 06/22] Fix errant comma typo on constructor call --- src/Dotmailer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotmailer.php b/src/Dotmailer.php index 6507ad8..b211702 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -406,7 +406,7 @@ public function getSuppressedContactsSince( $suppression->suppressedContact->email, $suppression->suppressedContact->optInType, $suppression->suppressedContact->emailType, - $suppression->suppressedContact->status, + $suppression->suppressedContact->status ), 'dateRemoved' => new \DateTime($suppression->dateRemoved), 'reason' => $suppression->reason, From 662ede329f25fc46b78b9820ea71d25eb1785d53 Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Tue, 16 Oct 2018 17:36:06 +0100 Subject: [PATCH 07/22] Remove status; it's duplicated by reason anyway --- src/Dotmailer.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Dotmailer.php b/src/Dotmailer.php index b211702..ef841e8 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -405,8 +405,7 @@ public function getSuppressedContactsSince( $suppression->suppressedContact->id, $suppression->suppressedContact->email, $suppression->suppressedContact->optInType, - $suppression->suppressedContact->emailType, - $suppression->suppressedContact->status + $suppression->suppressedContact->emailType ), 'dateRemoved' => new \DateTime($suppression->dateRemoved), 'reason' => $suppression->reason, From ce3e0bf312b0baa1d3f8f5ef26c854268c5734ca Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Tue, 16 Oct 2018 17:56:25 +0100 Subject: [PATCH 08/22] Return suppressions as a strongly-typed object --- src/Dotmailer.php | 13 +++++---- src/Entity/Suppression.php | 58 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 src/Entity/Suppression.php diff --git a/src/Dotmailer.php b/src/Dotmailer.php index ef841e8..04adc35 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -11,6 +11,7 @@ use Dotmailer\Factory\CampaignFactory; use Psr\Http\Message\ResponseInterface; use function GuzzleHttp\json_decode; +use Dotmailer\Entity\Suppression; class Dotmailer { @@ -382,7 +383,7 @@ function (string $name, string $value) { * @param int|null $select * @param int|null $skip * - * @return Contact[] + * @return Suppression[] */ public function getSuppressedContactsSince( \DateTimeInterface $dateTime, @@ -400,16 +401,16 @@ public function getSuppressedContactsSince( $suppressions = []; foreach (json_decode($this->response->getBody()->getContents()) as $suppression) { - $suppressions[] = [ - 'suppressedContact' => new Contact( + $suppressions[] = new Suppression( + new Contact( $suppression->suppressedContact->id, $suppression->suppressedContact->email, $suppression->suppressedContact->optInType, $suppression->suppressedContact->emailType ), - 'dateRemoved' => new \DateTime($suppression->dateRemoved), - 'reason' => $suppression->reason, - ]; + new \DateTime($suppression->dateRemoved), + $suppression->reason + ); } return $suppressions; diff --git a/src/Entity/Suppression.php b/src/Entity/Suppression.php new file mode 100644 index 0000000..90dab94 --- /dev/null +++ b/src/Entity/Suppression.php @@ -0,0 +1,58 @@ +id = $suppressedContact; + $this->email = $dateRemoved; + $this->optInType = $reason; + } + + /** + * @inheritdoc + */ + public function asArray(): array + { + return [ + 'suppressedContact' => $this->suppressedContact, + 'dateRemoved' => $this->dateRemoved, + 'reason' => $this->reason, + ]; + } +} \ No newline at end of file From d7ea7e0b3905f0725a10b8594dc2c320dc30354b Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Tue, 16 Oct 2018 17:58:13 +0100 Subject: [PATCH 09/22] Fix bad copying/pasting --- src/Entity/Suppression.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Entity/Suppression.php b/src/Entity/Suppression.php index 90dab94..db05125 100644 --- a/src/Entity/Suppression.php +++ b/src/Entity/Suppression.php @@ -39,9 +39,9 @@ public function __construct( \DateTime $dateRemoved, string $reason ) { - $this->id = $suppressedContact; - $this->email = $dateRemoved; - $this->optInType = $reason; + $this->suppressedContact = $suppressedContact; + $this->dateRemoved = $dateRemoved; + $this->reason = $reason; } /** From 4863645b54ddd1321bc50268d1aceb38c8e17501 Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Wed, 17 Oct 2018 11:37:46 +0100 Subject: [PATCH 10/22] Add getter functions for suppressions --- src/Entity/Suppression.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Entity/Suppression.php b/src/Entity/Suppression.php index db05125..3ddc639 100644 --- a/src/Entity/Suppression.php +++ b/src/Entity/Suppression.php @@ -44,6 +44,30 @@ public function __construct( $this->reason = $reason; } + /** + * @return Contact + */ + public function getSuppressedContact(): Contact + { + return $this->suppressedContact; + } + + /** + * @return \DateTime + */ + public function getDateRemoved(): \DateTime + { + return $this->dateRemoved; + } + + /** + * @return string + */ + public function getReason(): string + { + return $this->reason; + } + /** * @inheritdoc */ From 5ff98a2a1e090dc36a0547a1fb4dc2d3ac70c78b Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Thu, 18 Oct 2018 18:14:48 +0100 Subject: [PATCH 11/22] Correct typing error --- src/Entity/Suppression.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entity/Suppression.php b/src/Entity/Suppression.php index 3ddc639..fafa982 100644 --- a/src/Entity/Suppression.php +++ b/src/Entity/Suppression.php @@ -12,7 +12,7 @@ final class Suppression implements Arrayable const REASON_SUPPRESSED = 'Suppressed'; // The contact that has been actively suppressed by you in your account const REASON_NOTALLOWED = 'NotAllowed'; // The contact's email address is fully blocked from our system const REASON_DOMAINSUPPRESSION = 'DomainSuppression'; // The contact’s email domain is on your domain suppression list - const REASON_NOMXREECORD = 'NoMxRecord'; // The contact’s email domain does not have an MX DNS record. A mail exchange record provides the address of the mail server for that domain. + const REASON_NOMXRECORD = 'NoMxRecord'; // The contact’s email domain does not have an MX DNS record. A mail exchange record provides the address of the mail server for that domain. /** * @var Contact From 9ae67bcb5b70efac8d492c01a5c5b7ec80ca2255 Mon Sep 17 00:00:00 2001 From: rcnsheppardp Date: Wed, 31 Oct 2018 17:16:53 +0000 Subject: [PATCH 12/22] Try datetime format --- src/Dotmailer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotmailer.php b/src/Dotmailer.php index 04adc35..a5c65a0 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -391,7 +391,7 @@ public function getSuppressedContactsSince( int $skip = null ): array { $this->response = $this->adapter->get( - '/v2/contacts/suppressed-since/' . $dateTime->format('Y-m-d'), + '/v2/contacts/suppressed-since/' . $dateTime->format('c'), array_filter([ 'select' => $select, 'skip' => $skip, From 7f24fe09a103a54bca9d47ef43a241809cea45ec Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Thu, 9 May 2019 16:50:59 +0100 Subject: [PATCH 13/22] Whitespace fixed --- src/Dotmailer.php | 56 ++++----- src/Entity/Contact.php | 6 +- src/Entity/Suppression.php | 227 +++++++++++++++++++++++-------------- tests/DotmailerTest.php | 40 +++++++ 4 files changed, 216 insertions(+), 113 deletions(-) diff --git a/src/Dotmailer.php b/src/Dotmailer.php index a5c65a0..744b521 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -163,7 +163,7 @@ public function getContactByEmail(string $email): Contact $contact->optInType, $contact->emailType, $contact->dataFields, - $contact->status + $contact->status ); } @@ -386,33 +386,33 @@ function (string $name, string $value) { * @return Suppression[] */ public function getSuppressedContactsSince( - \DateTimeInterface $dateTime, - int $select = null, - int $skip = null + \DateTimeInterface $dateTime, + int $select = null, + int $skip = null ): array { - $this->response = $this->adapter->get( - '/v2/contacts/suppressed-since/' . $dateTime->format('c'), - array_filter([ - 'select' => $select, - 'skip' => $skip, - ]) - ); - - $suppressions = []; - - foreach (json_decode($this->response->getBody()->getContents()) as $suppression) { - $suppressions[] = new Suppression( - new Contact( - $suppression->suppressedContact->id, - $suppression->suppressedContact->email, - $suppression->suppressedContact->optInType, - $suppression->suppressedContact->emailType - ), - new \DateTime($suppression->dateRemoved), - $suppression->reason - ); - } - - return $suppressions; + $this->response = $this->adapter->get( + '/v2/contacts/suppressed-since/' . $dateTime->format('Y-m-d'), + array_filter([ + 'select' => $select, + 'skip' => $skip, + ]) + ); + + $suppressions = []; + + foreach (json_decode($this->response->getBody()->getContents()) as $suppression) { + $suppressions[] = new Suppression( + new Contact( + $suppression->suppressedContact->id, + $suppression->suppressedContact->email, + $suppression->suppressedContact->optInType, + $suppression->suppressedContact->emailType + ), + new \DateTime($suppression->dateRemoved), + $suppression->reason + ); + } + + return $suppressions; } } diff --git a/src/Entity/Contact.php b/src/Entity/Contact.php index 2a18e07..cd7b742 100644 --- a/src/Entity/Contact.php +++ b/src/Entity/Contact.php @@ -55,7 +55,7 @@ public function __construct( string $optInType = self::OPT_IN_TYPE_UNKNOWN, string $emailType = self::EMAIL_TYPE_PLAIN_TEXT, array $dataFields = [], - string $status = null + string $status = null ) { $this->id = $id; $this->email = $email; @@ -110,7 +110,7 @@ public function getEmailType(): string */ public function getStatus(): string { - return $this->status; + return $this->status; } /** @@ -176,7 +176,7 @@ public function asArray(): array 'optInType' => $this->optInType, 'emailType' => $this->emailType, 'dataFields' => $this->dataFields, - 'status' => $this->status, + 'status' => $this->status, ]; } } diff --git a/src/Entity/Suppression.php b/src/Entity/Suppression.php index fafa982..7634ba9 100644 --- a/src/Entity/Suppression.php +++ b/src/Entity/Suppression.php @@ -1,82 +1,145 @@ -suppressedContact = $suppressedContact; - $this->dateRemoved = $dateRemoved; - $this->reason = $reason; - } - - /** - * @return Contact - */ - public function getSuppressedContact(): Contact - { - return $this->suppressedContact; - } - - /** - * @return \DateTime - */ - public function getDateRemoved(): \DateTime - { - return $this->dateRemoved; - } - - /** - * @return string - */ - public function getReason(): string - { - return $this->reason; - } - - /** - * @inheritdoc - */ - public function asArray(): array - { - return [ - 'suppressedContact' => $this->suppressedContact, - 'dateRemoved' => $this->dateRemoved, - 'reason' => $this->reason, - ]; - } -} \ No newline at end of file +suppressedContact = $suppressedContact; + $this->dateRemoved = $dateRemoved; + $this->reason = $reason; + } + + /** + * @return Contact + */ + public function getSuppressedContact(): Contact + { + return $this->suppressedContact; + } + + /** + * @return \DateTime + */ + public function getDateRemoved(): \DateTime + { + return $this->dateRemoved; + } + + /** + * @return string + */ + public function getReason(): string + { + return $this->reason; + } + + /** + * @inheritdoc + */ + public function asArray(): array + { + return [ + 'suppressedContact' => $this->suppressedContact, + 'dateRemoved' => $this->dateRemoved, + 'reason' => $this->reason, + ]; + } +} diff --git a/tests/DotmailerTest.php b/tests/DotmailerTest.php index d068fb8..f500668 100644 --- a/tests/DotmailerTest.php +++ b/tests/DotmailerTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use function GuzzleHttp\json_encode; +use Dotmailer\Entity\Suppression; class DotmailerTest extends TestCase { @@ -422,6 +423,45 @@ public function testSendTransactionalEmailUsingATriggeredCampaign() $this->assertEquals($response, $this->dotmailer->getResponse()); } + + public function testGetSuppressedContactsSince() + { + $contact = $this->getContact(); + $dateRemoved = new \DateTime('2018-01-10'); + + $this->adapter + ->expects($this->once()) + ->method('get') + ->with( + '/v2/contacts/suppressed-since/' . self::DATE_FROM, + [ + 'select' => 1, + 'skip' => 2, + ] + ) + ->willReturn( + $this->getResponse( + [ + [ + 'suppressedContact' => $contact->asArray(), + 'dateRemoved' => $dateRemoved->format('Y-m-d'), + 'reason' => 'unsubscribed', + ] + ] + ) + ); + + $this->assertEquals( + [ + new Suppression( + $contact, + $dateRemoved, + 'unsubscribed' + ) + ], + $this->dotmailer->getSuppressedContactsSince(new \DateTime(self::DATE_FROM), 1, 2) + ); + } /** * @param array $contents From 4044749beabd981ddbdcc893a13e7b1c2778e9de Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Thu, 9 May 2019 16:54:56 +0100 Subject: [PATCH 14/22] Update readme as test coverage passing --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34a43da..4cdda72 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Currently the following endpoints are covered: - [x] Get unsubscribed contacts since date - [x] Unsubscribe contact - [x] Resubscribe contact - - [ ] Get suppressed contacts since date + - [x] Get suppressed contacts since date - [ ] **Contact data fields** - [x] Create contact data field - [x] Delete contact data field From 6dc33263f33fc487a157449c5927fa67d31957ed Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Thu, 9 May 2019 18:49:25 +0100 Subject: [PATCH 15/22] 1st attempt at pre-reqs for Dotdigital bulk contact/address book upload --- src/Adapter/Adapter.php | 8 ++++++++ src/Adapter/GuzzleAdapter.php | 19 +++++++++++++++++++ src/Dotmailer.php | 10 ++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/Adapter/Adapter.php b/src/Adapter/Adapter.php index c242e7e..3529bf2 100644 --- a/src/Adapter/Adapter.php +++ b/src/Adapter/Adapter.php @@ -36,4 +36,12 @@ public function put(string $url, array $content = []): ResponseInterface; * @return ResponseInterface */ public function delete(string $url): ResponseInterface; + + /** + * @param string $url + * @param string $filename + * + * @return ResponseInterface + */ + public function postfile(string $url, string $filepath, string $filename, string $mimetype): ResponseInterface; } diff --git a/src/Adapter/GuzzleAdapter.php b/src/Adapter/GuzzleAdapter.php index 221c259..505893d 100644 --- a/src/Adapter/GuzzleAdapter.php +++ b/src/Adapter/GuzzleAdapter.php @@ -82,4 +82,23 @@ public function delete(string $url): ResponseInterface { return $this->client->request('DELETE', $url); } + + /** + * {@inheritDoc} + */ + public function postfile(string $url, string $filepath, string $filename, string $mimetype): ResponseInterface + { + return $this->client->request('POST', $url, [ + 'multipart' => [ + [ + 'name' => 'file', + 'contents' => fopen($filepath, 'r'), // Just the resource; Guzzle handles the contents internally + 'filename' => $filename, + 'headers' => [ + 'Content-Type' => $mimetype + ] + ] + ] + ]); + } } diff --git a/src/Dotmailer.php b/src/Dotmailer.php index 744b521..4c3cc22 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -415,4 +415,14 @@ public function getSuppressedContactsSince( return $suppressions; } + + public function bulkCreateContactsInAddressBook(AddressBook $addressBook, string $file, string $filename) + { + $this->response = $this->adapter->postfile( + '/v2/address-books/' . $addressBook->getId() . '/contacts/import', + $file, + $filename, + 'text/csv' + ); + } } From 40ade4299b3746cc8c2c3fb7eb793cba297bbb53 Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Fri, 10 May 2019 15:35:00 +0100 Subject: [PATCH 16/22] Add coverage for getting contact import status --- README.md | 3 + src/Adapter/Adapter.php | 4 +- src/Adapter/GuzzleAdapter.php | 10 ++-- src/Dotmailer.php | 40 ++++++++++++- src/Entity/ContactImportStatus.php | 96 ++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 10 deletions(-) create mode 100644 src/Entity/ContactImportStatus.php diff --git a/README.md b/README.md index 4cdda72..f59f395 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,9 @@ Currently the following endpoints are covered: - [x] Unsubscribe contact - [x] Resubscribe contact - [x] Get suppressed contacts since date + - [ ] Bulk create contacts in address book + - [ ] Get contact import status + - [ ] Get contact import report - [ ] **Contact data fields** - [x] Create contact data field - [x] Delete contact data field diff --git a/src/Adapter/Adapter.php b/src/Adapter/Adapter.php index 3529bf2..f7f0d29 100644 --- a/src/Adapter/Adapter.php +++ b/src/Adapter/Adapter.php @@ -40,8 +40,8 @@ public function delete(string $url): ResponseInterface; /** * @param string $url * @param string $filename - * + * * @return ResponseInterface */ - public function postfile(string $url, string $filepath, string $filename, string $mimetype): ResponseInterface; + public function postfile(string $url, string $filePath, string $fileName, string $mimeType): ResponseInterface; } diff --git a/src/Adapter/GuzzleAdapter.php b/src/Adapter/GuzzleAdapter.php index 505893d..c6f6616 100644 --- a/src/Adapter/GuzzleAdapter.php +++ b/src/Adapter/GuzzleAdapter.php @@ -86,18 +86,18 @@ public function delete(string $url): ResponseInterface /** * {@inheritDoc} */ - public function postfile(string $url, string $filepath, string $filename, string $mimetype): ResponseInterface + public function postfile(string $url, string $filePath, string $fileName, string $mimeType): ResponseInterface { return $this->client->request('POST', $url, [ 'multipart' => [ [ 'name' => 'file', - 'contents' => fopen($filepath, 'r'), // Just the resource; Guzzle handles the contents internally - 'filename' => $filename, + 'contents' => fopen($filePath, 'r'), // Just the resource; Guzzle handles the contents internally + 'filename' => $fileName, 'headers' => [ - 'Content-Type' => $mimetype + 'Content-Type' => $mimeType ] - ] + ] ] ]); } diff --git a/src/Dotmailer.php b/src/Dotmailer.php index 4c3cc22..0e0ed9e 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -12,6 +12,7 @@ use Psr\Http\Message\ResponseInterface; use function GuzzleHttp\json_decode; use Dotmailer\Entity\Suppression; +use Dotmailer\Entity\ContactImportStatus; class Dotmailer { @@ -416,13 +417,46 @@ public function getSuppressedContactsSince( return $suppressions; } - public function bulkCreateContactsInAddressBook(AddressBook $addressBook, string $file, string $filename) + /** + * Bulk creates, or bulk updates, contacts in an address book + * + * @param AddressBook $addressBook Object containing the ID of the address book + * @param string $filePath Local filesystem path of the file to be imported + * @param string $fileName Discrete file name to pass to API + * + * @return \Dotmailer\Entity\ContactImportStatus + */ + public function bulkCreateContactsInAddressBook(AddressBook $addressBook, string $filePath, string $fileName) { $this->response = $this->adapter->postfile( '/v2/address-books/' . $addressBook->getId() . '/contacts/import', - $file, - $filename, + $filePath, + $fileName, 'text/csv' ); + + $response = json_decode($this->response->getBody()->getContents()); + + $importStatus = new ContactImportStatus($response->id, $response->status); + + return $importStatus; + } + + /** + * @param string $id GUID import ID + * + * @return ContactImportStatus + */ + public function getContactImportStatus(string $id): ContactImportStatus + { + //TODO: Validate GUID? + + $this->response = $this->adapter->get('/v2/contacts/import/' . $id); + + $response = json_decode($this->response->getBody()->getContents()); + + $importStatus = new ContactImportStatus($response->id, $response->status); + + return $importStatus; } } diff --git a/src/Entity/ContactImportStatus.php b/src/Entity/ContactImportStatus.php new file mode 100644 index 0000000..1429570 --- /dev/null +++ b/src/Entity/ContactImportStatus.php @@ -0,0 +1,96 @@ +id = $id; + $this->status = $status; + } + + /** + * @return Contact + */ + public function getId(): string + { + return $this->id; + } + + /** + * @return \DateTime + */ + public function getStatus(): string + { + return $this->status; + } + + /** + * @inheritdoc + */ + public function asArray(): array + { + return [ + 'id' => $this->id, + 'status' => $this->status, + ]; + } +} From d68ae2f48bf53a6be70f5133aa81e5241a784508 Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Fri, 10 May 2019 16:42:00 +0100 Subject: [PATCH 17/22] Implement getContactImportReport --- src/Dotmailer.php | 17 +++++ src/Entity/ContactImportReport.php | 119 +++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 src/Entity/ContactImportReport.php diff --git a/src/Dotmailer.php b/src/Dotmailer.php index 0e0ed9e..4050826 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -13,6 +13,7 @@ use function GuzzleHttp\json_decode; use Dotmailer\Entity\Suppression; use Dotmailer\Entity\ContactImportStatus; +use Dotmailer\Entity\ContactImportReport; class Dotmailer { @@ -459,4 +460,20 @@ public function getContactImportStatus(string $id): ContactImportStatus return $importStatus; } + + /** + * @param string $id GUID import ID + * + * @return ContactImportReport + */ + public function getContactImportReport(string $id): ContactImportStatus + { + //TODO: Validate GUID? + + $this->response = $this->adapter->get('/v2/contacts/import/' . $id . '/report'); + + $report = ContactImportReport::fromJson($this->response->getBody()->getContents()); + + return $report; + } } diff --git a/src/Entity/ContactImportReport.php b/src/Entity/ContactImportReport.php new file mode 100644 index 0000000..0c7d62b --- /dev/null +++ b/src/Entity/ContactImportReport.php @@ -0,0 +1,119 @@ +newContacts = $newContacts; + $this->updatedContacts = $updatedContacts; + $this->globallySuppressed = $globallySuppressed; + $this->invalidEntries = $invalidEntries; + $this->duplicateEmails = $duplicateEmails; + $this->blocked = $blocked; + $this->unsubscribed = $unsubscribed; + $this->hardBounced = $hardBounced; + $this->softBounced = $softBounced; + $this->ispComplaints = $ispComplaints; + $this->mailBlocked = $mailBlocked; + $this->domainSuppressed = $domainSuppressed; + $this->pendingDoubleOptin = $pendingDoubleOptin; + $this->failures = $failures; + } + + /** + * Initialise object from JSON + * + * @param string $json + * @return ContactImportReport + */ + public static function fromJson(string $json): self + { + $result = json_decode($json); + + return new self( + $result->newContacts, + $result->updatedContacts, + $result->globallySuppressed, + $result->invalidEntries, + $result->duplicateEmails, + $result->blocked, + $result->unsubscribed, + $result->hardBounced, + $result->softBounced, + $result->ispComplaints, + $result->mailBlocked, + $result->domainSuppressed, + $result->pendingDoubleOptin, + $result->failures + ); + } + + /** + * @inheritdoc + */ + public function asArray(): array + { + return [ + 'newContacts' => $this->newContacts, + 'updatedContacts' => $this->updatedContacts, + 'globallySuppressed' => $this->globallySuppressed, + 'invalidEntries' => $this->invalidEntries, + 'duplicateEmails' => $this->duplicateEmails, + 'blocked' => $this->blocked, + 'unsubscribed' => $this->unsubscribed, + 'hardBounced' => $this->hardBounced, + 'softBounced' => $this->softBounced, + 'ispComplaints' => $this->ispComplaints, + 'mailBlocked' => $this->mailBlocked, + 'domainSuppressed' => $this->domainSuppressed, + 'pendingDoubleOptin' => $this->pendingDoubleOptin, + 'failures' => $this->failures, + ]; + } +} From 383898ea6358b2a28f4705ed7aea7b6e98c74b69 Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Fri, 10 May 2019 16:46:25 +0100 Subject: [PATCH 18/22] Return the correct class --- src/Dotmailer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotmailer.php b/src/Dotmailer.php index 4050826..fad4b41 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -466,7 +466,7 @@ public function getContactImportStatus(string $id): ContactImportStatus * * @return ContactImportReport */ - public function getContactImportReport(string $id): ContactImportStatus + public function getContactImportReport(string $id): ContactImportReport { //TODO: Validate GUID? From e6fb34e83309d69b0320384d7e31a939d8546439 Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Fri, 10 May 2019 17:49:11 +0100 Subject: [PATCH 19/22] Add GUID validation checks --- src/Dotmailer.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Dotmailer.php b/src/Dotmailer.php index fad4b41..62c649f 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -18,6 +18,7 @@ class Dotmailer { const DEFAULT_URI = 'https://r1-api.dotmailer.com'; + const GUID_REGEX = '/^[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}?$/i'; /** * @var Adapter @@ -450,7 +451,9 @@ public function bulkCreateContactsInAddressBook(AddressBook $addressBook, string */ public function getContactImportStatus(string $id): ContactImportStatus { - //TODO: Validate GUID? + if (!preg_match(self::GUID_REGEX, $id)) { + throw new \Exception('ID did not contain a valid GUID'); + } $this->response = $this->adapter->get('/v2/contacts/import/' . $id); @@ -468,7 +471,9 @@ public function getContactImportStatus(string $id): ContactImportStatus */ public function getContactImportReport(string $id): ContactImportReport { - //TODO: Validate GUID? + if (!preg_match(self::GUID_REGEX, $id)) { + throw new \Exception('ID did not contain a valid GUID'); + } $this->response = $this->adapter->get('/v2/contacts/import/' . $id . '/report'); From 6c5cb34ce0f5053af3a4211973704ae2ce06effa Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Tue, 14 May 2019 17:23:27 +0100 Subject: [PATCH 20/22] Test coverage for 3 APIs relating to bulk uploads --- README.md | 6 ++-- tests/DotmailerTest.php | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f59f395..bb92897 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ Currently the following endpoints are covered: - [x] Unsubscribe contact - [x] Resubscribe contact - [x] Get suppressed contacts since date - - [ ] Bulk create contacts in address book - - [ ] Get contact import status - - [ ] Get contact import report + - [x] Bulk create contacts in address book + - [x] Get contact import status + - [x] Get contact import report - [ ] **Contact data fields** - [x] Create contact data field - [x] Delete contact data field diff --git a/tests/DotmailerTest.php b/tests/DotmailerTest.php index f500668..182681b 100644 --- a/tests/DotmailerTest.php +++ b/tests/DotmailerTest.php @@ -15,6 +15,8 @@ use PHPUnit\Framework\TestCase; use function GuzzleHttp\json_encode; use Dotmailer\Entity\Suppression; +use Dotmailer\Entity\ContactImportStatus; +use Dotmailer\Entity\ContactImportReport; class DotmailerTest extends TestCase { @@ -29,6 +31,10 @@ class DotmailerTest extends TestCase const WEBSITE = 'http://foo.bar/baz'; const DATA_FIELD = 'DATAFIELD'; const DATE_FROM = '2018-01-01'; + const NULL_GUID = '00000000-0000-0000-0000-000000000000'; + const FILE_PATH = '/test.csv'; + const FILE_NAME = 'test.csv'; + const FILE_MIME_TYPE = 'text/csv'; /** * @var Adapter|MockObject @@ -462,6 +468,54 @@ public function testGetSuppressedContactsSince() $this->dotmailer->getSuppressedContactsSince(new \DateTime(self::DATE_FROM), 1, 2) ); } + + public function testBulkCreateContactsInAddressBook() + { + $this->adapter + ->expects($this->once()) + ->method('postfile') + ->with( + '/v2/address-books/' . self::ID . '/contacts/import', + self::FILE_PATH, + self::FILE_NAME, + self::FILE_MIME_TYPE + ) + ->willReturn($this->getResponse($this->getContactImportStatus()->asArray())); + + $actual = $this->dotmailer->bulkCreateContactsInAddressBook( + new AddressBook(self::ID, self::NAME), + self::FILE_PATH, + self::FILE_NAME + ); + + $this->assertEquals($this->getContactImportStatus(), $actual); + } + + public function testGetContactImportStatus() + { + $this->adapter + ->expects($this->once()) + ->method('get') + ->with('/v2/contacts/import/' . self::NULL_GUID) + ->willReturn( + $this->getResponse($this->getContactImportStatus()->asArray()) + ); + + $this->assertEquals($this->getContactImportStatus(), $this->dotmailer->getContactImportStatus(self::NULL_GUID)); + } + + public function testGetContactImportReport() + { + $this->adapter + ->expects($this->once()) + ->method('get') + ->with('/v2/contacts/import/' . self::NULL_GUID . '/report') + ->willReturn( + $this->getResponse($this->getContactImportReport()->asArray()) + ); + + $this->assertEquals($this->getContactImportReport(), $this->dotmailer->getContactImportReport(self::NULL_GUID)); + } /** * @param array $contents @@ -518,4 +572,20 @@ private function getProgram(): Program { return new Program(self::ID, 'test program', Program::STATUS_ACTIVE, new \DateTime()); } + + /** + * @return ContactImportStatus + */ + private function getContactImportStatus(): ContactImportStatus + { + return new ContactImportStatus(self::NULL_GUID, ContactImportStatus::STATUS_NOT_FINISHED); + } + + /** + * @return ContactImportReport + */ + private function getContactImportReport(): ContactImportReport + { + return new ContactImportReport(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); + } } From dab2cf671a414db35b584a9ffd78234a49736ab2 Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Tue, 4 Jun 2019 16:43:27 +0100 Subject: [PATCH 21/22] Fix return type documentation --- src/Entity/ContactImportStatus.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Entity/ContactImportStatus.php b/src/Entity/ContactImportStatus.php index 1429570..1f56ed1 100644 --- a/src/Entity/ContactImportStatus.php +++ b/src/Entity/ContactImportStatus.php @@ -56,8 +56,7 @@ final class ContactImportStatus implements Arrayable /** * @param int|null $id - * @param \DateTime $dateRemoved - * @param string $reason + * @param string $status */ public function __construct( string $id, @@ -68,7 +67,7 @@ public function __construct( } /** - * @return Contact + * @return string */ public function getId(): string { @@ -76,7 +75,7 @@ public function getId(): string } /** - * @return \DateTime + * @return string */ public function getStatus(): string { From 4c414b03e277c3ec153fd10ad4a5a241c6f5f16a Mon Sep 17 00:00:00 2001 From: Peter Sheppard Date: Tue, 30 Jul 2019 11:06:53 +0100 Subject: [PATCH 22/22] Add coverage: Resubscribe contact with no challenge --- README.md | 1 + src/Dotmailer.php | 16 ++++++++++++++++ tests/DotmailerTest.php | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/README.md b/README.md index bb92897..b41d78f 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Currently the following endpoints are covered: - [x] Get unsubscribed contacts since date - [x] Unsubscribe contact - [x] Resubscribe contact + - [x] Resubscribe contact with no challenge - [x] Get suppressed contacts since date - [x] Bulk create contacts in address book - [x] Get contact import status diff --git a/src/Dotmailer.php b/src/Dotmailer.php index 62c649f..ec0f214 100644 --- a/src/Dotmailer.php +++ b/src/Dotmailer.php @@ -256,6 +256,22 @@ public function resubscribeContact(Contact $contact, string $preferredLocale = n $this->response = $this->adapter->post('/v2/contacts/resubscribe', array_filter($content)); } + + /** + * @param Contact $contact + * @param string|null $preferredLocale + * @param string|null $challengeUrl + */ + public function resubscribeContactWithNoChallenge(Contact $contact) + { + $content = [ + 'unsubscribedContact' => [ + 'email' => $contact->getEmail() + ], + ]; + + $this->response = $this->adapter->post('/v2/contacts/resubscribe-with-no-challenge', array_filter($content)); + } /** * @param DataField $dataField diff --git a/tests/DotmailerTest.php b/tests/DotmailerTest.php index 182681b..a3e5d32 100644 --- a/tests/DotmailerTest.php +++ b/tests/DotmailerTest.php @@ -588,4 +588,25 @@ private function getContactImportReport(): ContactImportReport { return new ContactImportReport(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); } + + public function testResubscribeContactWithNoChallenge() + { + $response = $this->getResponse(); + + $this->adapter + ->expects($this->once()) + ->method('post') + ->with( + '/v2/contacts/resubscribe-with-no-challenge', + [ + 'unsubscribedContact' => [ + 'email' => self::EMAIL + ], + ] + )->willReturn($response); + + $this->dotmailer->resubscribeContactWithNoChallenge($this->getContact()); + + $this->assertEquals($response, $this->dotmailer->getResponse()); + } }