Skip to content

Commit f41d07c

Browse files
author
Martin Kluska
committedFeb 14, 2017
initial commit
0 parents  commit f41d07c

34 files changed

+2696
-0
lines changed
 

‎.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
composer.phar
2+
/vendor/
3+
composer.lock
4+
.env
5+
6+
7+

‎CONTRIBUTING.md

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Contribution or overriding
2+
Are welcome. To add a new provider just add a new Handler (which extends AbstractHandler). Then implement the chunk
3+
upload and progress.
4+
5+
1. Fork the project.
6+
2. Create your bugfix/feature branch and write your (try well-commented) code.
7+
3. Commit your changes (and your tests) and push to your branch.
8+
4. Create a new pull request against this package's `master` branch.
9+
10+
## Pull Requests
11+
12+
- **Use the [PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md).**
13+
The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
14+
15+
- **Consider our release cycle.** We try to follow [SemVer v2.0.0](http://semver.org/).
16+
17+
- **Document any change in behaviour.** Make sure the `README.md` and any other relevant
18+
documentation are kept up-to-date.
19+
20+
- **Create feature branches.** Don't ask us to pull from your master branch.
21+
22+
- **One pull request per feature.** If you want to do more than one thing, send multiple pull requests.
23+
24+
**Thank you!**
25+
26+
## Running test
27+
28+
* For running live test, just create `.env` with your credentials set
29+
* To test your credentials run `PingLiveTest`
30+
* To test import, comment `return` statement in `ImportLiveTest`
31+
32+
## Quick guide
33+
34+
* Create Request in same namespace concept `SmartEmailing\v3\Request\Module\Request`.
35+
* For large module, create a wrapper that will create the requests
36+
* For a request logic, just extend `AbstractRequest`
37+
* To send data (json or body) use the options() method that you need to implement (see Import.php)
38+
39+
### Module example
40+
41+
```php
42+
43+
class Contacts {
44+
/**
45+
* @var Api
46+
*/
47+
private $api;
48+
49+
/**
50+
* @param Api $api
51+
*/
52+
public function __construct(Api $api)
53+
{
54+
$this->api = $api;
55+
}
56+
57+
public function lists() {
58+
return $this->listsRequest()->send();
59+
}
60+
61+
public function listsRequest() {
62+
return new ListRequest($this->api);
63+
}
64+
65+
public function get($id) {
66+
return $this->getRequest($id)->send();
67+
}
68+
69+
....
70+
}
71+
```
72+

‎LICENSE.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# The MIT License (MIT)
2+
3+
Copyright (c) 2016 Martin Kluska <martin@kluska.cz>
4+
5+
> Permission is hereby granted, free of charge, to any person obtaining a copy
6+
> of this software and associated documentation files (the "Software"), to deal
7+
> in the Software without restriction, including without limitation the rights
8+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
> copies of the Software, and to permit persons to whom the Software is
10+
> furnished to do so, subject to the following conditions:
11+
>
12+
> The above copyright notice and this permission notice shall be included in all
13+
> copies or substantial portions of the Software.
14+
>
15+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
> SOFTWARE.

‎composer.json

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "pion/smart-emailing-v3",
3+
"description": "Wrapper for SmartEmailing API",
4+
"type": "library",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Martin Kluska",
9+
"email": "martin.kluska@imakers.cz"
10+
}
11+
],
12+
"autoload": {
13+
"psr-4": {
14+
"SmartEmailing\\v3\\": "src"
15+
},
16+
"files": [
17+
"src/Helpers/global_helpers.php"
18+
]
19+
},
20+
"autoload-dev": {
21+
"psr-4": {
22+
"SmartEmailing\\v3\\Tests\\": "tests"
23+
}
24+
},
25+
"minimum-stability": "stable",
26+
"require": {
27+
"php": ">=5.5",
28+
"guzzlehttp/guzzle": "^6.2"
29+
},
30+
"require-dev": {
31+
"phpunit/phpunit": "^5.7",
32+
"vlucas/phpdotenv": "^2.4"
33+
}
34+
}

‎env.example

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
USERNAME=test
2+
API_KEY=api-key

‎phpunit.xml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit backupGlobals="false"
3+
backupStaticAttributes="false"
4+
colors="true"
5+
convertErrorsToExceptions="true"
6+
convertNoticesToExceptions="true"
7+
convertWarningsToExceptions="true"
8+
processIsolation="false"
9+
stopOnFailure="false"
10+
syntaxCheck="false">
11+
<testsuites>
12+
<testsuite name="Application Test Suite">
13+
<directory>./tests/</directory>
14+
</testsuite>
15+
</testsuites>
16+
<filter>
17+
<whitelist>
18+
<directory suffix=".php">src/</directory>
19+
</whitelist>
20+
<exclude>
21+
<directory>vendor/</directory>
22+
<directory>tests/</directory>
23+
</exclude>
24+
</filter>
25+
</phpunit>

‎readme.md

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Smart Emailing API v3
2+
API wrapper for [Smart emailing](http://smartemailing.cz) API. Currenlty in development.
3+
4+
[![Total Downloads](https://poser.pugx.org/pion/smart-emailing-v3/downloads?format=flat)](https://packagist.org/packages/pion/smart-emailing-v3)
5+
[![Latest Stable Version](https://poser.pugx.org/pion/smart-emailing-v3/v/stable?format=flat)](https://packagist.org/packages/pion/smart-emailing-v3)
6+
[![Latest Unstable Version](https://poser.pugx.org/pion/smart-emailing-v3/v/unstable?format=flat)](https://packagist.org/packages/pion/smart-emailing-v3)
7+
8+
* [Installation](#installation)
9+
* [Usage](#usage)
10+
* [Supports](#supports)
11+
* [Features](#features)
12+
* [Basic documentation](#basic-documentation)
13+
* [Example](#example)
14+
* [Javascript](#javascript)
15+
* [Laravel controller](#laravel.controller)
16+
* [Controller](#controller)
17+
* [Route](#route)
18+
* [Providers/Handlers](#providers-handlers)
19+
* [Changelog](#changelog)
20+
* [Contribution or overriding](#contribution-or-overriding)
21+
* [Suggested frontend libs](#suggested-frontend-libs)
22+
23+
## Installation
24+
25+
**Install via composer**
26+
27+
```
28+
composer require pion/smart-emailing-v3
29+
```
30+
31+
## Usage
32+
33+
Create an Api instance with your username and apiKey.
34+
35+
```php
36+
use SmartEmailing\v3\Api;
37+
38+
...
39+
$api = new Api('username', 'api-key');
40+
41+
```
42+
43+
then use the `$api` with desired method/component.
44+
45+
```php
46+
// Creates a new instance
47+
$api->import()->addContact(new Contact('test@test.cz'))->send();
48+
```
49+
50+
or
51+
52+
```php
53+
// Creates a new instance
54+
$import = $api->import();
55+
$contact = new Contact('test@test.cz');
56+
$contact->setName('Martin')->setNameDay('2017-12-11 11:11:11');
57+
$import->addContact($contact);
58+
59+
// Create new contact that will be inserted in the contact list
60+
$contact2 = $import->newContact('test2@test.cz');
61+
$contact2->setName('Test');
62+
63+
// Create new contact that will be inserted in the contact list
64+
$import->newContact('test3@test.cz')->setName('Test');
65+
$import->send();
66+
```
67+
68+
## Supports
69+
70+
* [x] [Import](https://app.smartemailing.cz/docs/api/v3/index.html#api-Import-Import_contacts)`$api->import()` or `new Import($api)`
71+
* [x] [Ping](https://app.smartemailing.cz/docs/api/v3/index.html#api-Tests-Aliveness_test) `$api->ping()` or `new Ping($api)`
72+
* [x] [Credentials](https://app.smartemailing.cz/docs/api/v3/index.html#api-Tests-Login_test_with_GET) `$api->credentials()` or `new Credentials($api)`
73+
* [ ] [Contactlist](https://app.smartemailing.cz/docs/api/v3/index.html#api-Contactlists-Get_Contactlists) Retrieve list `$api->contactlist()->lists()` or detail `$api->contactlist()->get($id)` - wrapper for 2 Request objects
74+
* [ ] [Customfields](https://app.smartemailing.cz/docs/api/v3/index.html#api-Customfields) Similar concept as contact-list
75+
* [ ] [Customfiels options](https://app.smartemailing.cz/docs/api/v3/index.html#api-Customfield_Options)
76+
* [ ] [Contacts](https://app.smartemailing.cz/docs/api/v3/index.html#api-Contacts) Similar concept as contact-list
77+
* [ ] [Contacts in list](https://app.smartemailing.cz/docs/api/v3/index.html#api-Contacts_in_lists) Similar concept as contact-list
78+
* [ ] [Custom emails](https://app.smartemailing.cz/docs/api/v3/index.html#api-Custom_emails)
79+
* [ ] [Emails](https://app.smartemailing.cz/docs/api/v3/index.html#api-Emails)
80+
* [ ] [Newsletter](https://app.smartemailing.cz/docs/api/v3/index.html#api-Newsletter)
81+
* [ ] [Webhooks](https://app.smartemailing.cz/docs/api/v3/index.html#api-Webhooks)
82+
83+
## Changelog
84+
85+
none
86+
87+
## Contribution or overriding
88+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute changes. All contributions are welcome.
89+
90+
## Copyright and License
91+
92+
[smart-emailing-v3](https://github.com/pionl/smart-emailing-v3)
93+
was written by [Martin Kluska](http://kluska.cz) and is released under the
94+
[MIT License](LICENSE.md).
95+
96+
Copyright (c) 2016 Martin Kluska

‎src/Api.php

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
namespace SmartEmailing\v3;
3+
4+
use GuzzleHttp\Client;
5+
use SmartEmailing\v3\Request\Credentials\Credentials;
6+
use SmartEmailing\v3\Request\Import\Import;
7+
use SmartEmailing\v3\Request\Ping\Ping;
8+
9+
/**
10+
* Class Api
11+
* @package SmartEmailing\v3
12+
* @link https://app.smartemailing.cz/docs/api/v3/index.html#api-Tests-Aliveness_test
13+
*/
14+
class Api
15+
{
16+
/**
17+
* The final API endpoint
18+
* @var string
19+
*/
20+
private $apiUrl;
21+
22+
/**
23+
* Api constructor.
24+
*
25+
* @param string $username
26+
* @param string $apiKey
27+
* @param string|null $apiUrl
28+
*/
29+
public function __construct($username, $apiKey, $apiUrl = null)
30+
{
31+
$this->apiUrl = $apiUrl;
32+
$this->client = new Client([
33+
'auth' => [$username, $apiKey],
34+
'base_uri' => ($apiUrl ? $apiUrl : 'https://app.smartemailing.cz/api/v3/')
35+
]);
36+
}
37+
38+
/**
39+
* Returns current API client with auth setup and base URL
40+
* @return Client
41+
*/
42+
public function client()
43+
{
44+
return $this->client;
45+
}
46+
47+
/**
48+
* Creates new import request
49+
* @return Import
50+
*/
51+
public function import()
52+
{
53+
return new Import($this);
54+
}
55+
56+
/**
57+
* @return Ping
58+
*/
59+
public function ping()
60+
{
61+
return new Ping($this);
62+
}
63+
64+
/**
65+
* @return Credentials
66+
*/
67+
public function credentials()
68+
{
69+
return new Credentials($this);
70+
}
71+
72+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
namespace SmartEmailing\v3\Exceptions;
3+
4+
class InvalidFormatException extends \LogicException
5+
{
6+
/**
7+
* Checks if the value is in the array. Throws exception if not
8+
* @param mixed $value
9+
* @param array $allowed
10+
*/
11+
public static function checkInArray($value, array $allowed)
12+
{
13+
if (!in_array($value, $allowed)) {
14+
throw new InvalidFormatException("Value '{$value}' not allowed: ".implode(', ', $allowed));
15+
}
16+
}
17+
}

‎src/Exceptions/RequestException.php

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
namespace SmartEmailing\v3\Exceptions;
3+
4+
use GuzzleHttp\Exception\RequestException as GuzzleRequestException;
5+
use Psr\Http\Message\RequestInterface;
6+
use SmartEmailing\v3\Request\Response;
7+
8+
class RequestException extends \RuntimeException
9+
{
10+
/** @var RequestInterface|null */
11+
private $request;
12+
13+
/** @var Response */
14+
private $response;
15+
16+
/**
17+
* RequestException constructor.
18+
*
19+
* @param Response $response
20+
* @param RequestInterface|null $request
21+
* @param string|null $message
22+
* @param int $code
23+
* @param \Exception|null $exception
24+
*/
25+
public function __construct(Response $response, RequestInterface $request = null, $message = null, $code = 0,
26+
$exception = null)
27+
{
28+
$this->response = $response;
29+
$this->request = $request;
30+
31+
parent::__construct($message, $code, $exception);
32+
}
33+
34+
/**
35+
* @return Response
36+
*/
37+
public function response()
38+
{
39+
return $this->response;
40+
}
41+
42+
/**
43+
* @return null|RequestInterface
44+
*/
45+
public function request()
46+
{
47+
return $this->request;
48+
}
49+
}

‎src/Helpers/global_helpers.php

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
namespace SmartEmailing\v3\Helpers;
3+
4+
/**
5+
* Converts the given date to allowed format
6+
*
7+
* @param string $date date string that can be converted by strtotime
8+
* @param bool $convert
9+
*
10+
* @return false|string
11+
*/
12+
function convertDate($date, $convert = true)
13+
{
14+
if (!$convert) {
15+
return $date;
16+
}
17+
18+
return date('Y-m-d H:i:s', strtotime($date));
19+
}

‎src/Request/AbstractRequest.php

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
namespace SmartEmailing\v3\Request;
3+
4+
use GuzzleHttp\Exception\RequestException as GuzzleRequestException;
5+
use Psr\Http\Message\ResponseInterface;
6+
use SmartEmailing\v3\Api;
7+
use SmartEmailing\v3\Exceptions\RequestException;
8+
9+
abstract class AbstractRequest
10+
{
11+
/**
12+
* @var Api
13+
*/
14+
private $api;
15+
16+
/**
17+
* Creates the abstract endpoint that will call the methods.
18+
*
19+
* @param Api $api
20+
*/
21+
public function __construct(Api $api)
22+
{
23+
$this->api = $api;
24+
}
25+
26+
/**
27+
* @return Api
28+
*/
29+
public function api()
30+
{
31+
return $this->api;
32+
}
33+
34+
/**
35+
* Returns the request method
36+
* @return string
37+
*/
38+
protected function method()
39+
{
40+
return 'GET';
41+
}
42+
43+
/**
44+
* Returns the request uri endpoint
45+
* @return string
46+
*/
47+
abstract protected function endpoint();
48+
49+
/**
50+
* Returns the request options that will be sent to the endpoint.
51+
* @return array
52+
*/
53+
abstract protected function options();
54+
55+
/**
56+
* Sends the request and builds the response
57+
*
58+
* @return Response
59+
* @throws RequestException
60+
*/
61+
public function send()
62+
{
63+
try {
64+
// Send the request and handle the Guzzle exception
65+
$response = $this->api()->client()->request(
66+
$this->method(), $this->endpoint(), $this->options()
67+
);
68+
69+
// Convert the response to internal response object
70+
$internalResponse = $this->createResponse($response);
71+
72+
// Check if the response has an error and throw exception
73+
$this->handleErrorResponseStatus($internalResponse);
74+
75+
// Pass the response
76+
return $internalResponse;
77+
} catch (GuzzleRequestException $exception) {
78+
throw $this->convertGuzzleException($exception, false);
79+
}
80+
}
81+
82+
/**
83+
* Converts the Guzzles RequestException into internal exception
84+
* @param GuzzleRequestException $exception
85+
*
86+
* @return RequestException
87+
*/
88+
protected function convertGuzzleException(GuzzleRequestException $exception)
89+
{
90+
// Convert to internal exception
91+
$response = $this->createResponse($exception->getResponse());
92+
$message = $exception->getMessage();
93+
94+
// Use the message from API or from Guzzle exception when not fully readable and we have json
95+
// message
96+
if ($message === 'Client error' && is_string($response->message())) {
97+
$message = "Client error: {$response->message()}";
98+
}
99+
100+
// Throw an exception
101+
return new RequestException(
102+
$response, $exception->getRequest(), $message, $exception->getCode(), $exception
103+
);
104+
}
105+
106+
/**
107+
* If response has error status then creates an RequestException with the response message
108+
*
109+
* @param Response $response
110+
*
111+
* @throws RequestException
112+
*/
113+
protected function handleErrorResponseStatus(Response $response)
114+
{
115+
// If there is error, lets throw an exception
116+
if ($response->status() === Response::ERROR) {
117+
$errorMessage = $response->message();
118+
throw new RequestException($response, null, "Client error: {$errorMessage}", $response->statusCode());
119+
}
120+
}
121+
122+
/**
123+
* Builds the internal response
124+
*
125+
* @param ResponseInterface|null $response
126+
*
127+
* @return Response
128+
*/
129+
protected function createResponse($response)
130+
{
131+
return new Response($response);
132+
}
133+
}
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
namespace SmartEmailing\v3\Request\Credentials;
3+
4+
use Psr\Http\Message\ResponseInterface;
5+
use SmartEmailing\v3\Request\AbstractRequest;
6+
7+
class Credentials extends AbstractRequest
8+
{
9+
protected function endpoint()
10+
{
11+
return 'check-credentials';
12+
}
13+
14+
protected function options()
15+
{
16+
return [];
17+
}
18+
19+
/**
20+
* @return Response
21+
*/
22+
public function send()
23+
{
24+
return parent::send();
25+
}
26+
27+
28+
/**
29+
* @param ResponseInterface|null $response
30+
*
31+
* @return Response
32+
*/
33+
protected function createResponse($response)
34+
{
35+
return new Response($response);
36+
}
37+
38+
39+
}

‎src/Request/Credentials/Response.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
namespace SmartEmailing\v3\Request\Credentials;
3+
4+
use Psr\Http\Message\ResponseInterface;
5+
use SmartEmailing\v3\Request\Response as BaseRequest;
6+
7+
class Response extends BaseRequest
8+
{
9+
/**
10+
* @var int
11+
*/
12+
protected $accountId;
13+
14+
/**
15+
* Setups the final data
16+
* @return $this
17+
*/
18+
protected function setupData()
19+
{
20+
parent::setupData();
21+
return $this->set('account_id', 'accountId');
22+
}
23+
24+
/**
25+
* Current account id
26+
* @return int
27+
*/
28+
public function accountId()
29+
{
30+
return $this->accountId;
31+
}
32+
33+
}

‎src/Request/Import/Contact.php

+438
Large diffs are not rendered by default.

‎src/Request/Import/ContactList.php

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
namespace SmartEmailing\v3\Request\Import;
3+
use SmartEmailing\v3\Exceptions\InvalidFormatException;
4+
5+
/**
6+
* Contact list wrapper with public properties (allows force set and easy getter). The fluent setter will help
7+
* to set values in correct format.
8+
* @package SmartEmailing\v3\Request\Import
9+
*/
10+
class ContactList implements \JsonSerializable
11+
{
12+
const CONFIRMED = 'confirmed';
13+
const REMOVED = 'removed';
14+
const UNSUBSCRIBED = 'unsubscribed';
15+
16+
/**
17+
* @var int|null
18+
*/
19+
public $id;
20+
/**
21+
* Contact's status in Contactlist. Allowed values: "confirmed", "unsubscribed", "removed"
22+
* @var string Default value: confirmed
23+
*/
24+
public $status = self::CONFIRMED;
25+
26+
/**
27+
* ContactList constructor.
28+
*
29+
* @param int $id
30+
* @param string $status Default value: confirmed
31+
*/
32+
public function __construct($id, $status = null)
33+
{
34+
$this->setId($id);
35+
36+
if (!is_null($status)) {
37+
$this->setStatus($status);
38+
}
39+
}
40+
41+
42+
/**
43+
* @param int $id
44+
*
45+
* @return ContactList
46+
*/
47+
public function setId($id)
48+
{
49+
$this->id = intval($id);
50+
return $this;
51+
}
52+
53+
/**
54+
* Contact's status in Contactlist. Allowed values: "confirmed", "unsubscribed", "removed"
55+
*
56+
* @param string $status
57+
*
58+
* @return $this
59+
*/
60+
public function setStatus($status)
61+
{
62+
InvalidFormatException::checkInArray($status, [self::CONFIRMED, self::UNSUBSCRIBED, self::REMOVED]);
63+
$this->status = $status;
64+
return $this;
65+
}
66+
67+
68+
/**
69+
* Converts data to array
70+
* @return array
71+
*/
72+
public function toArray()
73+
{
74+
return [
75+
'id' => $this->id,
76+
'status' => $this->status
77+
];
78+
}
79+
80+
/**
81+
* @return array
82+
*/
83+
public function jsonSerialize()
84+
{
85+
return $this->toArray();
86+
}
87+
}

‎src/Request/Import/CustomField.php

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
namespace SmartEmailing\v3\Request\Import;
3+
4+
/**
5+
* Contact field wrapper with public properties (allows force set and easy getter). The fluent setter will help
6+
* to set values in correct format.
7+
* @package SmartEmailing\v3\Request\Import
8+
*/
9+
class CustomField implements \JsonSerializable
10+
{
11+
/**
12+
* @var int|null
13+
*/
14+
public $id = null;
15+
16+
/**
17+
* Array of Customfields options IDs matching with selected Customfield. Required for composite customfields
18+
* @var array
19+
*/
20+
public $options = [];
21+
22+
/**
23+
* String value for simple customfields, and YYYY-MM-DD HH:MM:SS for date customfields. Value size is limited to
24+
* 64KB. Required for simple customfields
25+
* @var string|null
26+
*/
27+
public $value = null;
28+
29+
/**
30+
* CustomField constructor.
31+
*
32+
* @param int|null $id
33+
*/
34+
public function __construct($id)
35+
{
36+
$this->setId($id);
37+
}
38+
39+
40+
/**
41+
* @param int|null $id
42+
*
43+
* @return CustomField
44+
*/
45+
public function setId($id)
46+
{
47+
$this->id = $id;
48+
return $this;
49+
}
50+
51+
/**
52+
* Array of Customfields options IDs matching with selected Customfield. Required for composite customfields
53+
*
54+
* @param array $options
55+
*
56+
* @return CustomField
57+
*/
58+
public function setOptions(array $options)
59+
{
60+
$this->options = $options;
61+
return $this;
62+
}
63+
64+
/**
65+
* Adds a CustomField id for composite customfields
66+
* @param int $customFiledId
67+
*
68+
* @return $this
69+
*/
70+
public function addOption($customFiledId)
71+
{
72+
$this->options[] = intval($customFiledId);
73+
return $this;
74+
}
75+
76+
/**
77+
* String value for simple customfields, and YYYY-MM-DD HH:MM:SS for date customfields. Value size is limited to
78+
* 64KB. Required for simple customfields
79+
*
80+
* @param null|string $value
81+
*
82+
* @return CustomField
83+
*/
84+
public function setValue($value)
85+
{
86+
$this->value = $value;
87+
return $this;
88+
}
89+
90+
91+
/**
92+
* Converts data to array
93+
* @return array
94+
*/
95+
public function toArray()
96+
{
97+
$array = [
98+
'id' => $this->id
99+
];
100+
101+
if (!empty($this->options)) {
102+
$array['options'] = $this->options;
103+
}
104+
if (!is_null($this->value)) {
105+
$array['value'] = $this->value;
106+
}
107+
return $array;
108+
}
109+
110+
/**
111+
* @return array
112+
*/
113+
public function jsonSerialize()
114+
{
115+
return $this->toArray();
116+
}
117+
}

‎src/Request/Import/Import.php

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
namespace SmartEmailing\v3\Request\Import;
3+
4+
use SmartEmailing\v3\Api;
5+
use SmartEmailing\v3\Request\AbstractRequest;
6+
7+
class Import extends AbstractRequest implements \JsonSerializable
8+
{
9+
/**
10+
* @var Settings
11+
*/
12+
protected $settings;
13+
14+
/**
15+
* @var array
16+
*/
17+
protected $contacts = [];
18+
19+
public function __construct(Api $api)
20+
{
21+
parent::__construct($api);
22+
$this->settings = new Settings();
23+
}
24+
25+
/**
26+
* @param Contact $contact
27+
*
28+
* @return $this
29+
*/
30+
public function addContact(Contact $contact)
31+
{
32+
$this->contacts[] = $contact;
33+
return $this;
34+
}
35+
36+
/**
37+
* Creates new contact and adds to the contact list. Returns the newly created contact
38+
*
39+
* @param string $email
40+
*
41+
* @return Contact
42+
*/
43+
public function newContact($email)
44+
{
45+
$contact = new Contact($email);
46+
$this->addContact($contact);
47+
return $contact;
48+
}
49+
50+
/**
51+
* @return array
52+
*/
53+
public function contacts()
54+
{
55+
return $this->contacts;
56+
}
57+
58+
/**
59+
* @return Settings
60+
*/
61+
public function settings()
62+
{
63+
return $this->settings;
64+
}
65+
66+
67+
//region AbstractRequest implementation
68+
protected function endpoint()
69+
{
70+
return 'import';
71+
}
72+
73+
protected function options()
74+
{
75+
return [
76+
'json' => $this->jsonSerialize()
77+
];
78+
}
79+
80+
protected function method()
81+
{
82+
return 'POST';
83+
}
84+
85+
//endregion
86+
87+
//region Data convert
88+
/**
89+
* Converts data to array
90+
* @return array
91+
*/
92+
public function toArray()
93+
{
94+
return [
95+
'settings' => $this->settings,
96+
'data' => $this->contacts
97+
];
98+
}
99+
100+
/**
101+
* @return array
102+
*/
103+
public function jsonSerialize()
104+
{
105+
return $this->toArray();
106+
}
107+
//endregion
108+
109+
}

‎src/Request/Import/Settings.php

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
namespace SmartEmailing\v3\Request\Import;
3+
4+
/**
5+
* Class Settings
6+
*
7+
* Settings for import.
8+
*
9+
* @package SmartEmailing\v3\Request\Import
10+
*/
11+
class Settings implements \JsonSerializable
12+
{
13+
//region Properties
14+
/**
15+
* Existing contact's defaultfields and customfields (including nameday, gender and salution) will be updated ONLY
16+
* if this is set to true. If false is provided, only contactlist statuses will be updated. Nothing else.
17+
*
18+
* Default value: true
19+
* @var bool
20+
*/
21+
public $update = true;
22+
/**
23+
* If name is provided in Contact data section, automatically generate nameday defaultfield and overwrite existing
24+
* value if any.
25+
*
26+
* Default value: true
27+
* @var bool
28+
*/
29+
public $addNameDays = true;
30+
/**
31+
* If name is provided in Contact data section, automatically generate gender defaultfield and overwrite existing
32+
* value if any.
33+
*
34+
* Default value: true
35+
* @var bool
36+
*/
37+
public $addGenders = true;
38+
/**
39+
* If name is provided in Contact data section, automatically generate salution defaultfield and overwrite existing
40+
* value if any.
41+
*
42+
* Default value: true
43+
* @var bool
44+
*/
45+
public $addSalutations = true;
46+
/**
47+
* If this flag is set to true, all contacts that are unsubscribed in some lists will stay unsubscribed regardless
48+
* of imported statuses. This is very useful when Import should respect unsubscriptions from previous campaigns and
49+
* we strongly recommend to keep this turned on.
50+
*
51+
* Default value: true
52+
* @var bool
53+
*/
54+
public $preserveUnSubscribed = true;
55+
/**
56+
* If this flag is set to true, all contacts with invalid e-mail addresses will be silently skipped and your Import
57+
* will finish without them. Otherwise it will be terminated with 422 Error.
58+
*
59+
* Default value: false
60+
* @var bool
61+
*/
62+
public $skipInvalidEmails = false;
63+
//endregion
64+
65+
//region Setters
66+
/**
67+
* Existing contact's defaultfields and customfields (including nameday, gender and salution) will be updated ONLY
68+
* if this is set to true. If false is provided, only contactlist statuses will be updated. Nothing else.
69+
*
70+
* @param bool $update default value: true
71+
*
72+
* @return Settings
73+
*/
74+
public function setUpdate($update)
75+
{
76+
$this->update = $update;
77+
return $this;
78+
}
79+
80+
/**
81+
* If name is provided in Contact data section, automatically generate nameday defaultfield and overwrite existing
82+
* value if any.
83+
*
84+
* @param bool $addNameDays Default value: true
85+
*
86+
* @return Settings
87+
*/
88+
public function setAddNameDays($addNameDays)
89+
{
90+
$this->addNameDays = $addNameDays;
91+
return $this;
92+
}
93+
94+
/**
95+
* If name is provided in Contact data section, automatically generate gender defaultfield and overwrite existing
96+
* value if any.
97+
*
98+
* @param bool $addGenders Default value: true
99+
*
100+
* @return Settings
101+
*/
102+
public function setAddGenders($addGenders)
103+
{
104+
$this->addGenders = $addGenders;
105+
return $this;
106+
}
107+
108+
/**
109+
* If name is provided in Contact data section, automatically generate nameday defaultfield and overwrite existing
110+
* value if any.
111+
*
112+
* @param bool $addSalutations Default value: true
113+
*
114+
* @return Settings
115+
*/
116+
public function setAddSalutations($addSalutations)
117+
{
118+
$this->addSalutations = $addSalutations;
119+
return $this;
120+
}
121+
122+
/**
123+
* If this flag is set to true, all contacts that are unsubscribed in some lists will stay unsubscribed regardless
124+
* of imported statuses. This is very useful when Import should respect unsubscriptions from previous campaigns and
125+
* we strongly recommend to keep this turned on.
126+
*
127+
* @param bool $preserveUnSubscribed Default value: true
128+
*
129+
* @return Settings
130+
*/
131+
public function setPreserveUnSubscribed($preserveUnSubscribed)
132+
{
133+
$this->preserveUnSubscribed = $preserveUnSubscribed;
134+
return $this;
135+
}
136+
137+
/**
138+
* Existing contact's defaultfields and customfields (including nameday, gender and salution) will be updated ONLY
139+
* if this is set to true. If false is provided, only contactlist statuses will be updated. Nothing else.
140+
*
141+
* @param bool $skipInvalidEmails Default value: false
142+
*
143+
* @return Settings
144+
*/
145+
public function setSkipInvalidEmails($skipInvalidEmails)
146+
{
147+
$this->skipInvalidEmails = $skipInvalidEmails;
148+
return $this;
149+
}
150+
//endregion
151+
152+
/**
153+
* Converts the settings to array
154+
* @return array
155+
*/
156+
public function toArray()
157+
{
158+
return [
159+
'update' => $this->update,
160+
'add_namedays' => $this->addNameDays,
161+
'add_genders' => $this->addGenders,
162+
'add_salutions' => $this->addSalutations,
163+
'preserve_unsubscribed' => $this->preserveUnSubscribed,
164+
'skip_invalid_emails' => $this->skipInvalidEmails
165+
];
166+
}
167+
168+
/**
169+
* @return array
170+
*/
171+
public function jsonSerialize()
172+
{
173+
return $this->toArray();
174+
}
175+
176+
}

‎src/Request/Ping/Ping.php

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
namespace SmartEmailing\v3\Request\Ping;
3+
4+
use SmartEmailing\v3\Request\AbstractRequest;
5+
6+
class Ping extends AbstractRequest
7+
{
8+
protected function endpoint()
9+
{
10+
return 'ping';
11+
}
12+
13+
protected function options()
14+
{
15+
return [];
16+
}
17+
}

‎src/Request/Response.php

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<?php
2+
namespace SmartEmailing\v3\Request;
3+
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
7+
class Response
8+
{
9+
const ERROR = 'error';
10+
const SUCCESS = 'ok';
11+
const CREATED = 'created';
12+
13+
/**
14+
* @var ResponseInterface|null
15+
*/
16+
private $response;
17+
18+
/**
19+
* @var array|null
20+
*/
21+
protected $data = null;
22+
23+
/**
24+
* @var array
25+
*/
26+
protected $meta = [];
27+
28+
/**
29+
* @var string|null
30+
*/
31+
protected $message = null;
32+
33+
/**
34+
* @var string
35+
*/
36+
protected $status = self::ERROR;
37+
38+
/**
39+
* @var array|null
40+
*/
41+
protected $json = null;
42+
43+
/**
44+
* @param ResponseInterface|null $response
45+
*/
46+
public function __construct($response)
47+
{
48+
$this->response = $response;
49+
50+
if ($response == null) {
51+
return;
52+
}
53+
54+
$json = json_decode($response->getBody(), true);
55+
56+
// If we have valid json, lets get the data
57+
if (is_array($json) && isset($json['status'])) {
58+
$this->json = $json;
59+
60+
// Fill the data
61+
$this->setupData();
62+
}
63+
}
64+
65+
/**
66+
* When json is valid, setups the data
67+
* @return $this
68+
*/
69+
protected function setupData()
70+
{
71+
return $this->set('status')
72+
->set('meta')
73+
->set('message');
74+
}
75+
76+
//region Getters
77+
78+
/**
79+
* Response message
80+
*
81+
* @return string|null
82+
*/
83+
public function message()
84+
{
85+
return $this->message;
86+
}
87+
88+
/**
89+
* Response meta list
90+
*
91+
* @return array
92+
*/
93+
public function meta()
94+
{
95+
return $this->meta;
96+
}
97+
98+
/**
99+
* An error/success status
100+
*
101+
* @return string
102+
*/
103+
public function status()
104+
{
105+
return $this->status;
106+
}
107+
108+
/**
109+
* @return ResponseInterface
110+
*/
111+
public function response()
112+
{
113+
return $this->response;
114+
}
115+
116+
/**
117+
* Response data
118+
*
119+
* @return array|null
120+
*/
121+
public function data()
122+
{
123+
return $this->data;
124+
}
125+
126+
/**
127+
* Fully decoded json if avail.
128+
* @return array|mixed|null
129+
*/
130+
public function json()
131+
{
132+
return $this->json;
133+
}
134+
135+
/**
136+
* Checks if status is success
137+
* @return bool
138+
*/
139+
public function isSuccess()
140+
{
141+
return $this->status === self::SUCCESS || $this->status === self::CREATED;
142+
}
143+
144+
/**
145+
* Returns the status code from guzzle response
146+
* @return int
147+
*/
148+
public function statusCode()
149+
{
150+
if (is_null($this->response())) {
151+
return 500;
152+
}
153+
return $this->response()->getStatusCode();
154+
}
155+
156+
//endregion
157+
158+
//region Helpers
159+
/**
160+
* Sets the property by given key with a value from current json (using the same key). If not found
161+
* uses the current property value.
162+
*
163+
* @param string $key the key in array and a propertyName if not provided
164+
* @param string|null $propertyName
165+
*
166+
* @return $this
167+
*/
168+
protected function set($key, $propertyName = null)
169+
{
170+
if (is_null($propertyName)) {
171+
$propertyName = $key;
172+
}
173+
174+
$this->{$propertyName} = $this->value($this->json, $key, $this->{$propertyName});
175+
return $this;
176+
}
177+
178+
/**
179+
* Tries to get data from given array
180+
*
181+
* @param array $array
182+
* @param string $key
183+
* @param mixed|null $default
184+
*
185+
* @return mixed|null
186+
*/
187+
protected function value(array $array, $key, $default = null)
188+
{
189+
if (isset($array[$key])) {
190+
return $array[$key];
191+
}
192+
193+
return $default;
194+
}
195+
//endregion
196+
}

‎tests/ApiClientTest.php

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests;
3+
4+
use GuzzleHttp\Psr7\Uri;
5+
6+
class ApiClientTest extends BaseTestCase
7+
{
8+
/**
9+
* Tests the client creation
10+
*/
11+
public function testConstruct()
12+
{
13+
$this->assertNotNull($this->createApi()->client(), 'The api client must be created');
14+
}
15+
16+
/**
17+
* Tests if the client config has auth data
18+
*/
19+
public function testAuthConfig()
20+
{
21+
$auth = $this->createApi()->client()->getConfig('auth');
22+
23+
$this->assertEquals([$this->username, $this->apiKey], $auth,
24+
'The username and api-key are not same in http client');
25+
}
26+
27+
/**
28+
* Tests the default base URL
29+
*/
30+
public function testDefaultBaseUri()
31+
{
32+
/** @var Uri $baseUri */
33+
$baseUri = $this->createApi()->client()->getConfig('base_uri');
34+
35+
// Check if the URL is same
36+
$this->assertEquals('https://app.smartemailing.cz/api/v3/', $baseUri);
37+
}
38+
39+
/**
40+
* Tests the default base URL
41+
*/
42+
public function testCustomBaseUri()
43+
{
44+
/** @var Uri $baseUri */
45+
$baseUri = $this->createApi('test')->client()->getConfig('base_uri');
46+
47+
// Check if the URL is same
48+
$this->assertEquals('test', $baseUri);
49+
}
50+
}

‎tests/BaseTestCase.php

+236
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests;
3+
4+
use Dotenv\Dotenv;
5+
use GuzzleHttp\Client;
6+
use GuzzleHttp\Exception\BadResponseException;
7+
use GuzzleHttp\Handler\MockHandler;
8+
use GuzzleHttp\Psr7\Request;
9+
use GuzzleHttp\Psr7\Response;
10+
use PHPUnit\Framework\TestCase;
11+
use Psr\Http\Message\ResponseInterface;
12+
use SmartEmailing\v3\Api;
13+
use SmartEmailing\v3\Exceptions\RequestException;
14+
use SmartEmailing\v3\Request\AbstractRequest;
15+
use SmartEmailing\v3\Request\Response as InternalResponse;
16+
17+
class BaseTestCase extends TestCase
18+
{
19+
protected $username;
20+
protected $apiKey;
21+
22+
/**
23+
* Constructs a test case with the given name. Setups default api-key/username
24+
*
25+
* @param string $name
26+
* @param array $data
27+
* @param string $dataName
28+
*/
29+
public function __construct($name = null, array $data = [], $dataName = '')
30+
{
31+
parent::__construct($name, $data, $dataName);
32+
33+
// Load the Env variables
34+
$dotEnv = new Dotenv(__DIR__.'/../');
35+
$dotEnv->load();
36+
37+
// Setup the username/api-key
38+
$this->username = $this->env('USERNAME', 'username');
39+
$this->apiKey = $this->env('API_KEY', 'password');
40+
}
41+
42+
/**
43+
* Creates a tests for send request that will check if correct parameters are send to clients request method
44+
*
45+
* @param Api|\PHPUnit_Framework_MockObject_MockObject $apiStub
46+
* @param AbstractRequest $request
47+
* @param string $endpointName
48+
* @param string $httpMethod
49+
* @param array|null|object $options
50+
*/
51+
protected function createEndpointTest($apiStub, $request, $endpointName, $httpMethod = 'GET',
52+
$options = [])
53+
{
54+
// Build the client that will mock the client->request method
55+
$client = $this->createMock(Client::class);
56+
$response = $this->createMock(ResponseInterface::class);
57+
58+
// Make a response that is valid and ok - prevent exception
59+
$response->expects($this->once())->method('getBody')->willReturn('{
60+
"status": "ok",
61+
"meta": [
62+
],
63+
"message": "Hi there! API version 3 here!"
64+
}');
65+
66+
// Build customizable options to check
67+
$optionsCheck = null;
68+
69+
if (is_null($options)) {
70+
$optionsCheck = $this->anything();
71+
} else if (is_object($options)) {
72+
$optionsCheck = $options;
73+
} else {
74+
$optionsCheck = $this->equalTo($options);
75+
}
76+
77+
// The send method will trigger the request once with given properties (request methods)
78+
$client->expects($this->once())->method('request')->with(
79+
$this->equalTo($httpMethod), $this->equalTo($endpointName), $optionsCheck
80+
81+
)->willReturn($response);
82+
83+
$apiStub->method('client')->willReturn($client);
84+
$request->send();
85+
}
86+
87+
/**
88+
* Creates a response mock and runs the send method. Then checks for the response result.
89+
*
90+
* @param Api|\PHPUnit_Framework_MockObject_MockObject $apiStub
91+
* @param AbstractRequest $request
92+
* @param string $responseText
93+
* @param string $responseMessage
94+
* @param string $responseStatus
95+
* @param array $meta
96+
* @param string $responseClass
97+
* @param int $responseCode
98+
*
99+
* @return InternalResponse
100+
*/
101+
public function createSendResponse($apiStub, $request, $responseText, $responseMessage,
102+
$responseStatus = InternalResponse::SUCCESS, array $meta = [],
103+
$responseClass = InternalResponse::class, $responseCode = 200)
104+
{
105+
106+
$this->createMockHandlerToApi($apiStub, $responseText, $responseCode);
107+
108+
// Run the request
109+
$response = $request->send();
110+
111+
$this->assertResponse($response, $responseClass, $responseStatus, $responseMessage, $meta);
112+
return $response;
113+
}
114+
115+
/**
116+
* Creates a response mock and runs the send method. Then checks for the response result.
117+
*
118+
* @param Api|\PHPUnit_Framework_MockObject_MockObject $apiStub
119+
* @param AbstractRequest $request
120+
* @param string $responseText
121+
* @param string $responseMessage
122+
* @param string $responseStatus
123+
* @param array $meta
124+
* @param string $responseClass
125+
* @param int $responseCode
126+
*
127+
* @return RequestException
128+
*/
129+
public function createSendErrorResponse($apiStub, $request, $responseText, $responseMessage,
130+
$responseStatus = InternalResponse::SUCCESS, array $meta = [],
131+
$responseClass = InternalResponse::class, $responseCode = 200)
132+
{
133+
134+
$this->createMockHandlerToApi($apiStub, $responseText, $responseCode);
135+
136+
try {
137+
// Run the request
138+
$response = $request->send();
139+
140+
$this->fail('The send request should raise an exception when Guzzle raises RequestException
141+
(non 200 status code) or API returns 200 status code with error status in json');
142+
} catch (RequestException $exception) {
143+
$this->assertResponse($exception->response(), $responseClass, $responseStatus, $responseMessage, $meta);
144+
return $exception;
145+
}
146+
}
147+
148+
/**
149+
* Creates a MockHandler with a response and mocks the client in mocked api
150+
*
151+
* @param Api|\PHPUnit_Framework_MockObject_MockObject $apiStub
152+
* @param string $responseText
153+
* @param int $responseCode
154+
*/
155+
protected function createMockHandlerToApi($apiStub, $responseText, $responseCode)
156+
{
157+
$responseQueue = [];
158+
if ($responseCode > 300) {
159+
$responseQueue[] = new BadResponseException(
160+
'Client error', new Request('GET', 'test'), new Response($responseCode, [], $responseText)
161+
);
162+
} else {
163+
$responseQueue[] = new Response($responseCode, [], $responseText);
164+
}
165+
166+
// Return own responses
167+
$handler = new MockHandler($responseQueue);
168+
169+
// Build the client
170+
$client = new Client(['handler' => $handler]);
171+
172+
// Replace the client
173+
$apiStub->method('client')->willReturn($client);
174+
}
175+
176+
/**
177+
* @param InternalResponse $response
178+
* @param string $responseClass
179+
* @param string $responseStatus
180+
* @param string $responseMessage
181+
* @param array $meta
182+
*/
183+
protected function assertResponse($response, $responseClass, $responseStatus, $responseMessage, array $meta)
184+
{
185+
// Check the response
186+
$this->assertInstanceOf($responseClass, $response);
187+
$this->assertEquals($responseStatus, $response->status());
188+
$this->assertEquals($responseMessage, $response->message());
189+
$this->assertEquals($meta, $response->meta());
190+
}
191+
192+
/**
193+
* Creates new API
194+
*
195+
* @param string|null $apiUrl
196+
*
197+
* @return Api
198+
*/
199+
protected function createApi($apiUrl = null)
200+
{
201+
return new Api($this->username, $this->apiKey, $apiUrl);
202+
}
203+
204+
/**
205+
* Gets the value of an environment variable.
206+
*
207+
* @param string $key
208+
* @param mixed $default
209+
*
210+
* @return mixed
211+
*/
212+
function env($key, $default = null)
213+
{
214+
$value = getenv($key);
215+
216+
if ($value === false) {
217+
return $default;
218+
}
219+
220+
switch (strtolower($value)) {
221+
case 'true':
222+
case '(true)':
223+
return true;
224+
case 'false':
225+
case '(false)':
226+
return false;
227+
case 'empty':
228+
case '(empty)':
229+
return '';
230+
case 'null':
231+
case '(null)':
232+
return null;
233+
}
234+
return $value;
235+
}
236+
}

‎tests/Mock/RequestMock.php

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Mock;
3+
4+
use SmartEmailing\v3\Request\AbstractRequest;
5+
6+
class RequestMock extends AbstractRequest
7+
{
8+
protected function endpoint()
9+
{
10+
return 'endpoint';
11+
}
12+
13+
protected function options()
14+
{
15+
return ['test'];
16+
}
17+
18+
}

‎tests/Request/AbstractRequestTest.php

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request;
3+
4+
use SmartEmailing\v3\Api;
5+
use SmartEmailing\v3\Request\AbstractRequest;
6+
use SmartEmailing\v3\Request\Response;
7+
use SmartEmailing\v3\Tests\BaseTestCase;
8+
use SmartEmailing\v3\Tests\Mock\RequestMock;
9+
10+
class AbstractRequestTest extends BaseTestCase
11+
{
12+
/**
13+
* @var AbstractRequest|\PHPUnit_Framework_MockObject_MockObject
14+
*/
15+
protected $request;
16+
17+
/**
18+
* @var Api|\PHPUnit_Framework_MockObject_MockObject
19+
*/
20+
protected $apiStub;
21+
22+
/**
23+
* Builds the ping instance on every test
24+
*/
25+
public function setUp()
26+
{
27+
/** @var $apiStub */
28+
$this->apiStub = $this->createMock(Api::class);
29+
$this->request = new RequestMock($this->apiStub);
30+
}
31+
32+
/**
33+
* Test the arguments that are passed to request
34+
*/
35+
public function testEndpointAndOptions()
36+
{
37+
$this->createEndpointTest($this->apiStub, $this->request, 'endpoint', 'GET', ['test']);
38+
}
39+
40+
public function testResponse()
41+
{
42+
$this->createSendResponse($this->apiStub, $this->request, '{
43+
"status": "ok",
44+
"meta": [
45+
],
46+
"message": "Test"
47+
}', 'Test');
48+
}
49+
50+
/**
51+
* Tests the fatal error - status code 500 with custom message in json
52+
*/
53+
public function testResponseStatusCodeWithResponseError()
54+
{
55+
$exception = $this->createSendErrorResponse($this->apiStub, $this->request, '{
56+
"status": "error",
57+
"meta": [
58+
],
59+
"message": "An error"
60+
}', 'An error', Response::ERROR, [], Response::class, 500);
61+
62+
$this->assertEquals('Client error: An error', $exception->getMessage(),
63+
'The exception should use json message if Guzzle returns simple error');
64+
$this->assertEquals(500, $exception->getCode(), 'Exception must have same code as status code');
65+
$this->assertEquals(500, $exception->response()->statusCode());
66+
$this->assertNotNull($exception->request(), 'Non 200 status code should have request -
67+
passed from guzzle exception');
68+
}
69+
70+
/**
71+
* Tests the fatal error - status code 200 with custom message in json
72+
*/
73+
public function testResponse200StatusCodeWithResponseError()
74+
{
75+
$exception = $this->createSendErrorResponse($this->apiStub, $this->request, '{
76+
"status": "error",
77+
"meta": [
78+
],
79+
"message": "An error"
80+
}', 'An error', Response::ERROR, [], Response::class, 200);
81+
82+
$this->assertEquals('Client error: An error', $exception->getMessage(), 'The exception should use json message');
83+
$this->assertEquals(200, $exception->getCode(), 'Exception must have same code as status code');
84+
$this->assertEquals(200, $exception->response()->statusCode());
85+
$this->assertNull($exception->request(), 'Error with 200 status code has no request');
86+
}
87+
88+
/**
89+
* Tests the fatal error - status code 500 with custom message in json
90+
*/
91+
public function testResponseStatusCode()
92+
{
93+
$exception = $this->createSendErrorResponse(
94+
$this->apiStub, $this->request, null, null, Response::ERROR, [], Response::class, 500
95+
);
96+
97+
$this->assertEquals('Client error', $exception->getMessage(), 'The exception should use the Guzzles message');
98+
$this->assertEquals(500, $exception->getCode(), 'Exception must have same code as status code');
99+
$this->assertEquals(500, $exception->response()->statusCode());
100+
$this->assertNotNull($exception->request(), 'Non 200 status code should have request -
101+
passed from guzzle exception');
102+
}
103+
104+
/**
105+
* Tests the fatal error - status code 500 with custom message in json
106+
*/
107+
public function testResponseStatusCodeWithJson()
108+
{
109+
$exception = $this->createSendErrorResponse(
110+
$this->apiStub, $this->request, '{
111+
"status": "error",
112+
"meta": [
113+
],
114+
"message": "An error"
115+
}', 'An error', Response::ERROR, [], Response::class, 500
116+
);
117+
118+
$this->assertEquals('Client error: An error', $exception->getMessage(), 'The guzzle message is simple, append message');
119+
$this->assertEquals(500, $exception->getCode(), 'Exception must have same code as status code');
120+
$this->assertEquals(500, $exception->response()->statusCode());
121+
$this->assertNotNull($exception->request(), 'Non 200 status code should have request -
122+
passed from guzzle exception');
123+
}
124+
125+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request\Credentials;
3+
4+
use SmartEmailing\v3\Api;
5+
use SmartEmailing\v3\Exceptions\RequestException;
6+
use SmartEmailing\v3\Request\Credentials\Credentials;
7+
use SmartEmailing\v3\Request\Credentials\Response;
8+
use SmartEmailing\v3\Tests\BaseTestCase;
9+
10+
class CredentialsLiveTest extends BaseTestCase
11+
{
12+
13+
/**
14+
* Mocks the request and checks if request is returned via send method
15+
*/
16+
public function testSend()
17+
{
18+
$response = $this->createApi()->credentials()->send();
19+
$this->assertInstanceOf(Response::class, $response);
20+
$this->assertEquals(Response::SUCCESS, $response->status());
21+
$this->assertEquals('Hi there! Your credentials are valid!', $response->message());
22+
$this->assertNotNull($response->accountId(), 'The account is missing from response.');
23+
}
24+
25+
/**
26+
* Mocks the request and checks if request is returned via send method
27+
*/
28+
public function testSend401()
29+
{
30+
try {
31+
$response = (new Api('test', 'password'))->credentials()->send();
32+
$this->fail('The response should raise an RequestException due incorrect credentials - Guzzle raises an exception');
33+
} catch (RequestException $exception) {
34+
/** @var Response $response */
35+
$response = $exception->response();
36+
$this->assertInstanceOf(Response::class, $response);
37+
$this->assertEquals(Response::ERROR, $response->status());
38+
$this->assertEquals('Authentication Failed', $response->message());
39+
$this->assertNull($response->accountId(), 'Default value of accountId should be null');
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request\Credentials;
3+
4+
use SmartEmailing\v3\Api;
5+
use SmartEmailing\v3\Request\Credentials\Credentials;
6+
use SmartEmailing\v3\Request\Credentials\Response;
7+
use SmartEmailing\v3\Tests\BaseTestCase;
8+
9+
class CredentialsTest extends BaseTestCase
10+
{
11+
/**
12+
* @var Credentials
13+
*/
14+
protected $credentials;
15+
16+
/**
17+
* @var Api|\PHPUnit_Framework_MockObject_MockObject
18+
*/
19+
protected $apiStub;
20+
21+
/**
22+
* Builds the ping instance on every test
23+
*/
24+
public function setUp()
25+
{
26+
/** @var $apiStub */
27+
$this->apiStub = $this->createMock(Api::class);
28+
$this->credentials = new Credentials($this->apiStub);
29+
}
30+
31+
/**
32+
* Tests the endpoint and options in the Credentials class
33+
*/
34+
public function testEndpointAndOptions()
35+
{
36+
$this->createEndpointTest($this->apiStub, $this->credentials, 'check-credentials');
37+
}
38+
39+
40+
41+
/**
42+
* Mocks the request and checks if request is returned via send method
43+
*/
44+
public function testSend()
45+
{
46+
/** @var Response $response */
47+
$response = $this->createSendResponse($this->apiStub, $this->credentials, '{
48+
"status": "ok",
49+
"meta": [
50+
],
51+
"message": "Hi there! Your credentials are valid!",
52+
"account_id": 2
53+
}', 'Hi there! Your credentials are valid!');
54+
55+
$this->assertEquals(2, $response->accountId());
56+
}
57+
}
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request\Import;
3+
4+
use SmartEmailing\v3\Exceptions\InvalidFormatException;
5+
use SmartEmailing\v3\Request\Import\ContactList;
6+
use SmartEmailing\v3\Tests\BaseTestCase;
7+
8+
class ContactListTest extends BaseTestCase
9+
{
10+
/**
11+
* @var ContactList
12+
*/
13+
protected $list;
14+
15+
protected function setUp()
16+
{
17+
$this->list = new ContactList(1);
18+
}
19+
20+
public function testConstruct()
21+
{
22+
$this->assertEquals(1, $this->list->id);
23+
}
24+
25+
public function testSetStatus()
26+
{
27+
$this->assertEquals(ContactList::CONFIRMED, $this->list->status);
28+
$this->assertEquals(ContactList::REMOVED, $this->list->setStatus(ContactList::REMOVED)->status);
29+
}
30+
31+
public function testSetInvalidStatus()
32+
{
33+
try {
34+
$this->list->setStatus('test');
35+
} catch (InvalidFormatException $exception) {
36+
$this->assertStringStartsWith("Value 'test'", $exception->getMessage());
37+
}
38+
}
39+
40+
public function testToArray()
41+
{
42+
$this->assertEquals([
43+
'id' => 1,
44+
'status' => ContactList::CONFIRMED
45+
], $this->list->toArray());
46+
}
47+
48+
public function testJsonSerialize()
49+
{
50+
$this->assertEquals([
51+
'id' => 1,
52+
'status' => ContactList::CONFIRMED
53+
], $this->list->jsonSerialize());
54+
}
55+
}

‎tests/Request/Import/ContactTest.php

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request\Import;
3+
4+
use SmartEmailing\v3\Exceptions\InvalidFormatException;
5+
use SmartEmailing\v3\Request\Import\Contact;
6+
use SmartEmailing\v3\Tests\BaseTestCase;
7+
8+
class ContactTest extends BaseTestCase
9+
{
10+
/**
11+
* @var Contact
12+
*/
13+
protected $contact;
14+
15+
public function setUp()
16+
{
17+
$this->contact = new Contact('email@gmail.com');
18+
}
19+
20+
/**
21+
* Test that only email will be returned - array filter works
22+
*/
23+
public function testToArraySingleItem()
24+
{
25+
$haystack = $this->contact->toArray();
26+
$this->assertCount(1, $haystack, 'The array should be filtered from null or empty array values');
27+
}
28+
29+
/**
30+
* Tests if the notes are set and toArray returns 2 items
31+
*/
32+
public function testToArrayMultiple()
33+
{
34+
$this->contact->setNotes('test');
35+
$haystack = $this->contact->toArray();
36+
$this->assertCount(2, $haystack, 'There should be 2 values - email and note');
37+
}
38+
39+
public function testSetGender()
40+
{
41+
$this->contact->setGender('M');
42+
$this->assertEquals('M', $this->contact->gender);
43+
}
44+
45+
public function testSetGenderNull()
46+
{
47+
$this->contact->setGender(null);
48+
$this->assertNull($this->contact->gender);
49+
}
50+
51+
/**
52+
* Test that gender will raise exception if invalid format is passed
53+
*/
54+
public function testSetGenderInvalid()
55+
{
56+
try {
57+
$this->contact->setGender('G');
58+
$this->fail('Set gender should raise InvalidFormatException if invalid');
59+
} catch (InvalidFormatException $exception) {
60+
$this->assertStringStartsWith("Value 'G'", $exception->getMessage(), 'The exception should contain passed value');
61+
}
62+
}
63+
64+
public function testSetBlacklisted()
65+
{
66+
$this->assertEquals(1, $this->contact->setBlacklisted(true)->blacklisted, 'Bool value should be converted to int');
67+
$this->assertEquals(0, $this->contact->setBlacklisted(false)->blacklisted, 'Bool value should be converted to int');
68+
$this->assertEquals(1, $this->contact->setBlacklisted(1)->blacklisted);
69+
}
70+
71+
public function testSetNameDay()
72+
{
73+
$this->assertEquals(
74+
'2010-12-13 00:00:00', $this->contact->setNameDay('2010-12-13')->nameDay, 'The date should be converted'
75+
);
76+
$this->assertEquals(
77+
'2010-12-13', $this->contact->setNameDay('2010-12-13', false)->nameDay, 'We should be able to disable converting'
78+
);
79+
$this->assertEquals(
80+
'2010-12-13', $this->contact->setNameDay('2010-12-13', false)->nameDay
81+
);
82+
83+
$this->assertEquals(
84+
date('Y-m-d H:i:s', strtotime('+1 month')), $this->contact->setNameDay('+1 month')->nameDay
85+
);
86+
$this->assertCount(2, $this->contact->toArray(), 'There should be 2 values - email and date');
87+
}
88+
89+
public function testJsonSerialize()
90+
{
91+
$this->assertCount(1, $this->contact->jsonSerialize(), 'There should be 2 values - email and date');
92+
}
93+
}
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request\Import;
3+
4+
use SmartEmailing\v3\Request\Import\CustomField;
5+
use SmartEmailing\v3\Tests\BaseTestCase;
6+
7+
class CustomFieldTest extends BaseTestCase
8+
{
9+
/**
10+
* @var CustomField
11+
*/
12+
protected $field;
13+
14+
protected function setUp()
15+
{
16+
$this->field = new CustomField(12);
17+
}
18+
19+
public function testConstruct()
20+
{
21+
$this->assertEquals(12, $this->field->id);
22+
}
23+
24+
public function testConstructNumeric()
25+
{
26+
$this->assertEquals(13, (new CustomField('13'))->id);
27+
}
28+
29+
public function testSetIdNumeric()
30+
{
31+
$this->assertEquals(13, $this->field->setId('13')->id);
32+
}
33+
34+
public function testSetValue()
35+
{
36+
$this->assertEquals('test', $this->field->setValue('test')->value);
37+
}
38+
39+
public function testSetOptions()
40+
{
41+
$this->assertEquals([1,2], $this->field->setOptions([1,2])->options);
42+
}
43+
44+
public function testNonArray()
45+
{
46+
try {
47+
$this->field->setOptions('test');
48+
$this->fail('The options should require an array and raise warning');
49+
} catch (\Exception $exception) {
50+
$this->assertContains('the type array', $exception->getMessage());
51+
}
52+
}
53+
54+
public function testAddOption()
55+
{
56+
$this->field->addOption(1);
57+
$this->field->addOption(2);
58+
$this->assertEquals([1, 2], $this->field->options);
59+
}
60+
}
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request\Import;
3+
4+
use SmartEmailing\v3\Api;
5+
use SmartEmailing\v3\Request\Import\Contact;
6+
use SmartEmailing\v3\Request\Import\Import;
7+
use SmartEmailing\v3\Request\Import\Settings;
8+
use SmartEmailing\v3\Request\Response;
9+
use SmartEmailing\v3\Tests\BaseTestCase;
10+
11+
class ImportLiveTest extends BaseTestCase
12+
{
13+
/**
14+
* @var Import
15+
*/
16+
protected $import;
17+
18+
19+
protected function setUp()
20+
{
21+
parent::setUp();
22+
/** @var $apiStub */
23+
$this->import = $this->createApi()->import();
24+
}
25+
26+
/**
27+
* Tests if the endpoint/options is passed to request
28+
*/
29+
public function testBasic() {
30+
$this->assertInstanceOf(Import::class, $this->import);
31+
}
32+
33+
/**
34+
* Live test of sync
35+
*/
36+
public function testContactImport()
37+
{
38+
// Uncomment if you want to try
39+
return;
40+
41+
$contactFull = new Contact('test2@test.cz');
42+
43+
$contactFull->setGender('M')->setNotes('test')->setBirthday('today')->setBlacklisted(true);
44+
45+
$this->import->addContact(new Contact('test@test.cz'))->addContact($contactFull);
46+
47+
$this->import->settings()->setSkipInvalidEmails(true);
48+
49+
$response = $this->import->send();
50+
51+
$this->assertEquals(Response::CREATED, $response->status());
52+
$this->assertEquals(201, $response->statusCode());
53+
}
54+
55+
}

‎tests/Request/Import/ImportTest.php

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request\Import;
3+
4+
use SmartEmailing\v3\Api;
5+
use SmartEmailing\v3\Request\Import\Contact;
6+
use SmartEmailing\v3\Request\Import\Import;
7+
use SmartEmailing\v3\Request\Import\Settings;
8+
use SmartEmailing\v3\Tests\BaseTestCase;
9+
10+
class ImportTest extends BaseTestCase
11+
{
12+
/**
13+
* @var Import
14+
*/
15+
protected $import;
16+
17+
/**
18+
* @var Api|\PHPUnit_Framework_MockObject_MockObject
19+
*/
20+
protected $apiStub;
21+
22+
protected function setUp()
23+
{
24+
parent::setUp();
25+
/** @var $apiStub */
26+
$this->apiStub = $this->createMock(Api::class);
27+
$this->import = new Import($this->apiStub);
28+
}
29+
30+
/**
31+
* Tests if the endpoint/options is passed to request
32+
*/
33+
public function testEndpoint() {
34+
$this->createEndpointTest($this->apiStub, $this->import, 'import', 'POST', $this->arrayHasKey('json'));
35+
}
36+
37+
public function testConstruct()
38+
{
39+
$this->assertInstanceOf(Settings::class, $this->import->settings());
40+
$this->assertCount(0, $this->import->contacts());
41+
}
42+
43+
public function testAddContact()
44+
{
45+
$this->assertCount(1, $this->import->addContact(new Contact('test@test.cz'))->contacts());
46+
$this->assertCount(2, $this->import->addContact(new Contact('test2@test.cz'))->contacts());
47+
$this->assertCount(3, $this->import->addContact(new Contact('test@test.cz'))->contacts());
48+
}
49+
50+
public function testNewContact()
51+
{
52+
$this->import->newContact('test@test.cz');
53+
$this->assertCount(1, $this->import->contacts());
54+
}
55+
56+
}

‎tests/Request/Ping/PingLiveTest.php

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request\Ping;
3+
4+
use SmartEmailing\v3\Request\Ping\Ping;
5+
use SmartEmailing\v3\Request\Response;
6+
use SmartEmailing\v3\Tests\BaseTestCase;
7+
8+
class PingLiveTest extends BaseTestCase
9+
{
10+
/**
11+
* @var Ping
12+
*/
13+
protected $ping;
14+
15+
16+
/**
17+
* Builds the ping instance on every test
18+
*/
19+
public function setUp()
20+
{
21+
/** @var $apiStub */
22+
$this->ping = $this->createApi()->ping();
23+
}
24+
25+
/**
26+
* Mocks the request and checks if request is returned via send method
27+
*/
28+
public function testSend()
29+
{
30+
$response = $this->ping->send();
31+
$this->assertInstanceOf(Response::class, $response);
32+
$this->assertEquals(Response::SUCCESS, $response->status());
33+
$this->assertEquals('Hi there! API version 3 here!', $response->message());
34+
$this->assertEquals([], $response->meta());
35+
}
36+
}

‎tests/Request/Ping/PingTest.php

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
namespace SmartEmailing\v3\Tests\Request\Ping;
3+
4+
use GuzzleHttp\Client;
5+
use GuzzleHttp\Handler\MockHandler;
6+
use GuzzleHttp\Psr7\Response;
7+
use Psr\Http\Message\ResponseInterface;
8+
use SmartEmailing\v3\Request\Ping\Ping;
9+
use SmartEmailing\v3\Api;
10+
use SmartEmailing\v3\Request\Response as InternalResponse;
11+
use SmartEmailing\v3\Tests\BaseTestCase;
12+
13+
class PingTest extends BaseTestCase
14+
{
15+
/**
16+
* @var Ping
17+
*/
18+
protected $ping;
19+
20+
/**
21+
* @var Api|\PHPUnit_Framework_MockObject_MockObject
22+
*/
23+
protected $apiStub;
24+
25+
/**
26+
* Builds the ping instance on every test
27+
*/
28+
public function setUp()
29+
{
30+
/** @var $apiStub */
31+
$this->apiStub = $this->createMock(Api::class);
32+
$this->ping = new Ping($this->apiStub);
33+
}
34+
35+
/**
36+
* Tests if the endpoint/options is passed to request
37+
*/
38+
public function testEndpointAndOptions() {
39+
$this->createEndpointTest($this->apiStub, $this->ping, 'ping');
40+
}
41+
42+
/**
43+
* Mocks the request and checks if request is returned via send method
44+
*/
45+
public function testSend()
46+
{
47+
$this->createSendResponse($this->apiStub, $this->ping, '{
48+
"status": "ok",
49+
"meta": [
50+
],
51+
"message": "Hi there! API version 3 here!"
52+
}', 'Hi there! API version 3 here!');
53+
}
54+
}

0 commit comments

Comments
 (0)
Please sign in to comment.