Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d148279
Add status field to contact
rcnsheppardp Oct 12, 2018
5207659
Function to return status
rcnsheppardp Oct 12, 2018
cae1610
Add missing required description tag to XML
rcnsheppardp Oct 16, 2018
86f993a
Remove unsupported mixed return type
rcnsheppardp Oct 16, 2018
7c220c8
Coverage for Get suppressed contacts since date
rcnsheppardp Oct 16, 2018
7a09f91
Fix errant comma typo on constructor call
rcnsheppardp Oct 16, 2018
662ede3
Remove status; it's duplicated by reason anyway
rcnsheppardp Oct 16, 2018
ce3e0bf
Return suppressions as a strongly-typed object
rcnsheppardp Oct 16, 2018
d7ea7e0
Fix bad copying/pasting
rcnsheppardp Oct 16, 2018
4863645
Add getter functions for suppressions
rcnsheppardp Oct 17, 2018
5ff98a2
Correct typing error
rcnsheppardp Oct 18, 2018
9ae67bc
Try datetime format
rcnsheppardp Oct 31, 2018
e2e7c93
Merge pull request #1 from wellcometrust/master
rcnsheppardp May 9, 2019
504eef5
Merge pull request #2 from RCNdev/develop
rcnsheppardp May 9, 2019
7f24fe0
Whitespace fixed
rcnsheppardp May 9, 2019
44203f7
Merge pull request #3 from RCNdev/develop
rcnsheppardp May 9, 2019
4044749
Update readme as test coverage passing
rcnsheppardp May 9, 2019
7f8b4b9
Merge pull request #4 from RCNdev/develop
rcnsheppardp May 9, 2019
6dc3326
1st attempt at pre-reqs for Dotdigital bulk contact/address book upload
rcnsheppardp May 9, 2019
40ade42
Add coverage for getting contact import status
rcnsheppardp May 10, 2019
d68ae2f
Implement getContactImportReport
rcnsheppardp May 10, 2019
383898e
Return the correct class
rcnsheppardp May 10, 2019
e6fb34e
Add GUID validation checks
rcnsheppardp May 10, 2019
6c5cb34
Test coverage for 3 APIs relating to bulk uploads
rcnsheppardp May 14, 2019
dab2cf6
Fix return type documentation
rcnsheppardp Jun 4, 2019
4b0fe75
Merge pull request #5 from RCNdev/feature/bulk-create-address
rcnsheppardp Jun 13, 2019
25635c2
Merge pull request #6 from RCNdev/develop
rcnsheppardp Jun 13, 2019
4c414b0
Add coverage: Resubscribe contact with no challenge
rcnsheppardp Jul 30, 2019
5673ca8
Merge pull request #7 from RCNdev/develop
rcnsheppardp Jul 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ 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
- [x] Get contact import report
- [ ] **Contact data fields**
- [x] Create contact data field
- [x] Delete contact data field
Expand Down
2 changes: 2 additions & 0 deletions phpmd.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
xsi:noNamespaceSchemaLocation="
http://pmd.sf.net/ruleset_xml_schema.xsd">

<description></description>

<!-- Import the entire default rule sets -->
<rule ref="rulesets/unusedcode.xml" />
<rule ref="rulesets/codesize.xml" />
Expand Down
8 changes: 8 additions & 0 deletions src/Adapter/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
19 changes: 19 additions & 0 deletions src/Adapter/GuzzleAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
]
]
]);
}
}
124 changes: 123 additions & 1 deletion src/Dotmailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
use Dotmailer\Factory\CampaignFactory;
use Psr\Http\Message\ResponseInterface;
use function GuzzleHttp\json_decode;
use Dotmailer\Entity\Suppression;
use Dotmailer\Entity\ContactImportStatus;
use Dotmailer\Entity\ContactImportReport;

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
Expand Down Expand Up @@ -161,7 +165,8 @@ public function getContactByEmail(string $email): Contact
$contact->email,
$contact->optInType,
$contact->emailType,
$contact->dataFields
$contact->dataFields,
$contact->status
);
}

Expand Down Expand Up @@ -251,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
Expand Down Expand Up @@ -375,4 +396,105 @@ function (string $name, string $value) {
]
);
}

/**
* @param \DateTimeInterface $dateTime
* @param int|null $select
* @param int|null $skip
*
* @return Suppression[]
*/
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[] = new Suppression(
new Contact(
$suppression->suppressedContact->id,
$suppression->suppressedContact->email,
$suppression->suppressedContact->optInType,
$suppression->suppressedContact->emailType
),
new \DateTime($suppression->dateRemoved),
$suppression->reason
);
}

return $suppressions;
}

/**
* 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',
$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
{
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);

$response = json_decode($this->response->getBody()->getContents());

$importStatus = new ContactImportStatus($response->id, $response->status);

return $importStatus;
}

/**
* @param string $id GUID import ID
*
* @return ContactImportReport
*/
public function getContactImportReport(string $id): ContactImportReport
{
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');

$report = ContactImportReport::fromJson($this->response->getBody()->getContents());

return $report;
}
}
18 changes: 17 additions & 1 deletion src/Entity/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ final class Contact implements Arrayable
*/
private $dataFields;

/**
* @var string
*/
private $status;

/**
* @param int|null $id
* @param string $email
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -98,6 +105,14 @@ public function getEmailType(): string
return $this->emailType;
}

/**
* @return string
*/
public function getStatus(): string
{
return $this->status;
}

/**
* @return array
*/
Expand Down Expand Up @@ -161,6 +176,7 @@ public function asArray(): array
'optInType' => $this->optInType,
'emailType' => $this->emailType,
'dataFields' => $this->dataFields,
'status' => $this->status,
];
}
}
119 changes: 119 additions & 0 deletions src/Entity/ContactImportReport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php
namespace Dotmailer\Entity;

final class ContactImportReport implements Arrayable
{
private $newContacts;
private $updatedContacts;
private $globallySuppressed;
private $invalidEntries;
private $duplicateEmails;
private $blocked;
private $unsubscribed;
private $hardBounced;
private $softBounced;
private $ispComplaints;
private $mailBlocked;
private $domainSuppressed;
private $pendingDoubleOptin;
private $failures;

/**
* @param int $newContacts
* @param int $updatedContacts
* @param int $globallySuppressed
* @param int $invalidEntries
* @param int $duplicateEmails
* @param int $blocked
* @param int $unsubscribed
* @param int $hardBounced
* @param int $softBounced
* @param int $ispComplaints
* @param int $mailBlocked
* @param int $domainSuppressed
* @param int $pendingDoubleOptin
* @param int $failures
*/
public function __construct(
int $newContacts,
int $updatedContacts,
int $globallySuppressed,
int $invalidEntries,
int $duplicateEmails,
int $blocked,
int $unsubscribed,
int $hardBounced,
int $softBounced,
int $ispComplaints,
int $mailBlocked,
int $domainSuppressed,
int $pendingDoubleOptin,
int $failures
) {
$this->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,
];
}
}
Loading