diff --git a/composer.json b/composer.json
index bc501f3..4d45f37 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,7 @@
"type": "library",
"description": "LDC CRM Client V2",
"license": "MIT",
- "version": "2.0.0",
+ "version": "2.0.1",
"authors": [
{
"name": "Jérémy Munsch",
@@ -32,7 +32,8 @@
"require-dev": {
"phpunit/phpunit": "~9.0",
"squizlabs/php_codesniffer": "^3.5",
- "orchestra/testbench": "^6.13"
+ "orchestra/testbench": "^6.13",
+ "slevomat/coding-standard": "^7.0"
},
"autoload": {
"psr-4": {
diff --git a/composer.lock b/composer.lock
index 488d3d2..27affc1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "8ae44f3768b7ee6045ae61fb2655ea9c",
+ "content-hash": "89b14a69db5103b9d01833ed279ab009",
"packages": [
{
"name": "brick/math",
@@ -4562,6 +4562,76 @@
}
],
"packages-dev": [
+ {
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "v0.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
+ "reference": "fe390591e0241955f22eb9ba327d137e501c771c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c",
+ "reference": "fe390591e0241955f22eb9ba327d137e501c771c",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0",
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "sensiolabs/security-checker": "^4.1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Franck Nijhof",
+ "email": "franck.nijhof@dealerdirect.com",
+ "homepage": "http://www.frenck.nl",
+ "role": "Developer / IT Manager"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "homepage": "http://www.dealerdirect.com",
+ "keywords": [
+ "PHPCodeSniffer",
+ "PHP_CodeSniffer",
+ "code quality",
+ "codesniffer",
+ "composer",
+ "installer",
+ "phpcs",
+ "plugin",
+ "qa",
+ "quality",
+ "standard",
+ "standards",
+ "style guide",
+ "stylecheck",
+ "tests"
+ ],
+ "support": {
+ "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
+ "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
+ },
+ "time": "2020-12-07T18:04:37+00:00"
+ },
{
"name": "doctrine/instantiator",
"version": "1.4.0",
@@ -5421,6 +5491,55 @@
},
"time": "2021-03-17T13:42:18+00:00"
},
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "0.5.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c",
+ "reference": "ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.12.87",
+ "phpstan/phpstan-strict-rules": "^0.12.5",
+ "phpunit/phpunit": "^9.5",
+ "symfony/process": "^5.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "support": {
+ "issues": "https://github.com/phpstan/phpdoc-parser/issues",
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.5"
+ },
+ "time": "2021-06-11T13:24:46+00:00"
+ },
{
"name": "phpunit/php-code-coverage",
"version": "9.2.6",
@@ -6807,6 +6926,67 @@
],
"time": "2020-09-28T06:39:44+00:00"
},
+ {
+ "name": "slevomat/coding-standard",
+ "version": "7.0.13",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/slevomat/coding-standard.git",
+ "reference": "122a9bf9a4a2195f74100f47dfb8375982f43cc9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/122a9bf9a4a2195f74100f47dfb8375982f43cc9",
+ "reference": "122a9bf9a4a2195f74100f47dfb8375982f43cc9",
+ "shasum": ""
+ },
+ "require": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7",
+ "php": "^7.1 || ^8.0",
+ "phpstan/phpdoc-parser": "0.5.1 - 0.5.5",
+ "squizlabs/php_codesniffer": "^3.6.0"
+ },
+ "require-dev": {
+ "phing/phing": "2.16.4",
+ "php-parallel-lint/php-parallel-lint": "1.3.0",
+ "phpstan/phpstan": "0.12.93",
+ "phpstan/phpstan-deprecation-rules": "0.12.6",
+ "phpstan/phpstan-phpunit": "0.12.21",
+ "phpstan/phpstan-strict-rules": "0.12.10",
+ "phpunit/phpunit": "7.5.20|8.5.5|9.5.7"
+ },
+ "type": "phpcodesniffer-standard",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "7.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "SlevomatCodingStandard\\": "SlevomatCodingStandard"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.",
+ "support": {
+ "issues": "https://github.com/slevomat/coding-standard/issues",
+ "source": "https://github.com/slevomat/coding-standard/tree/7.0.13"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/kukulich",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-07-29T14:30:22+00:00"
+ },
{
"name": "spatie/backtrace",
"version": "1.2.0",
diff --git a/phpcs.xml b/phpcs.xml
index f1840de..5604a41 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -77,6 +77,7 @@
+
diff --git a/src/CrmApiV2.php b/src/CrmApiV2.php
index b4de051..c23c7e7 100644
--- a/src/CrmApiV2.php
+++ b/src/CrmApiV2.php
@@ -68,6 +68,17 @@ class CrmApiV2
return Customer::find($identification);
}
+ /**
+ * Get all segmentation questions
+ *
+ * @param string $identification The customer hashcode or email
+ * @return Customer
+ */
+ public function getQuestions(string $identification): ?Customer
+ {
+ return Customer::find($identification);
+ }
+
/**
* Set the single optin status for an email
*
diff --git a/src/Enums/QuestionType.php b/src/Enums/QuestionType.php
new file mode 100644
index 0000000..354e321
--- /dev/null
+++ b/src/Enums/QuestionType.php
@@ -0,0 +1,25 @@
+ 1,
+ 'radio' => 2,
+ 'checkbox' => 3
+ ];
+ }
+}
diff --git a/src/Models/Answer.php b/src/Models/Answer.php
new file mode 100644
index 0000000..95a11fb
--- /dev/null
+++ b/src/Models/Answer.php
@@ -0,0 +1,114 @@
+ 'required|int',
+ 'answer' => 'required|string|max:255'
+ ];
+
+ public function __construct(array $properties = [])
+ {
+ parent::__construct();
+ $this->fill($properties);
+ return $this;
+ }
+
+ // PUBLIC METHODS
+
+ /**
+ * Get unanswered question for a specific customer
+ *
+ * @param \LDC\LDCCRM\Models\Customer $customer
+ * @return bool
+ */
+ public static function setQuestionAnswers(string $hashcode, int $questionId, array $answersId): bool
+ {
+ self::assertValidHascode($hashcode);
+ self::assertValidPrimaryKey($questionId);
+ if (!count($answersId)) {
+ return true;
+ }
+ foreach ($answersId as $answerId) {
+ self::assertValidPrimaryKey($answerId);
+ }
+ static::setUpApiInterface(); // set apiInterface
+ $request = Utils::getResquest(
+ 'apiV2.customers.answers.store',
+ [
+ 'customer' => $hashcode
+ ],
+ [
+ 'question' => $questionId,
+ 'answers' => $answersId
+ ]
+ );
+
+ $response = static::$apiInterface->call($request);
+ return $response->status->equals(ApiStatus::OK());
+ }
+}
diff --git a/src/Models/CrmClientModel.php b/src/Models/CrmClientModel.php
index 0d050be..be997f4 100644
--- a/src/Models/CrmClientModel.php
+++ b/src/Models/CrmClientModel.php
@@ -272,4 +272,40 @@ abstract class CrmClientModel
$refProp->setAccessible(true);
return $refProp->getValue($object);
}
+
+ /**
+ * Assert primaryKey is valid
+ *
+ * @param int $primaryKey
+ * @return void
+ * @throws LDC\LDCCRM\Exceptions\ValidationException
+ */
+ protected static function assertValidPrimaryKey(int $primaryKey): void
+ {
+ $validator = Utils::makeValidator(
+ ['id' => $primaryKey],
+ ['id' => 'required|int']
+ );
+ if ($validator->fails()) {
+ throw new ValidationException(implode(' ', $validator->errors()->get('id')));
+ }
+ }
+
+ /**
+ * Assert hashcode is valid
+ *
+ * @param string|null $hashcode
+ * @return void
+ * @throws LDC\LDCCRM\Exceptions\ValidationException
+ */
+ protected static function assertValidHascode(?string $hashcode): void
+ {
+ $validator = Utils::makeValidator(
+ ['hashcode' => $hashcode],
+ ['hashcode' => 'required|uuid']
+ );
+ if ($validator->fails()) {
+ throw new ValidationException(implode(' ', $validator->errors()->get('hashcode')));
+ }
+ }
}
diff --git a/src/Models/Customer.php b/src/Models/Customer.php
index 01b2988..660ba64 100644
--- a/src/Models/Customer.php
+++ b/src/Models/Customer.php
@@ -401,7 +401,7 @@ class Customer extends CrmClientModel
public static function find(string $hashcode): ?self
{
static::setUpApiInterface(); // set apiInterface
- $request = Utils::getResquest('apiV2.customer.show', [
+ $request = Utils::getResquest('apiV2.customers.show', [
'customer' => $hashcode
]);
$customer = null;
@@ -460,7 +460,7 @@ class Customer extends CrmClientModel
$data['password'] = $this->password;
static::setUpApiInterface(); // set apiInterface
- $request = Utils::getResquest('apiV2.customer.store', [], $data);
+ $request = Utils::getResquest('apiV2.customers.store', [], $data);
$response = static::$apiInterface->call($request);
if ($status = $response->status->equals(ApiStatus::OK())) {
@@ -473,7 +473,7 @@ class Customer extends CrmClientModel
{
$this->validateValue('hashcode', $this->hashcode);
static::setUpApiInterface(); // set apiInterface
- $request = Utils::getResquest('apiV2.customer.update', [
+ $request = Utils::getResquest('apiV2.customers.update', [
'customer' => $this->hashcode
], $this->updatedValues);
@@ -493,7 +493,7 @@ class Customer extends CrmClientModel
{
$this->validateValue('hashcode', $this->hashcode);
static::setUpApiInterface(); // set apiInterface
- $request = Utils::getResquest('apiV2.customer.destroy', [
+ $request = Utils::getResquest('apiV2.customers.destroy', [
'customer' => $this->hashcode
]);
@@ -510,7 +510,7 @@ class Customer extends CrmClientModel
{
$this->validateValue('hashcode', $this->hashcode);
static::setUpApiInterface(); // set apiInterface
- $request = Utils::getResquest('apiV2.customer.activate', [
+ $request = Utils::getResquest('apiV2.customers.activate', [
'customer' => $this->hashcode
]);
diff --git a/src/Models/Question.php b/src/Models/Question.php
new file mode 100644
index 0000000..d98d8d6
--- /dev/null
+++ b/src/Models/Question.php
@@ -0,0 +1,282 @@
+ 'required|int',
+ 'question' => 'required|string|max:255',
+ 'type' => ['required'],
+ 'answers' => 'required|array'
+ ];
+
+ public function __construct(array $properties = [])
+ {
+ parent::__construct();
+ if (count(self::$validation['type']) < 2) {
+ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
+ self::$validation['type'][] = function ($attribute, $value, $fail) {
+ if (!is_object($value) or !\in_array($value, QuestionType::toArray())) {
+ $type = \gettype($value);
+ $fail("Question type ($type)$value is not in QuestionType enum list");
+ }
+ };
+ }
+ $this->fill($properties);
+ return $this;
+ }
+
+ public function __get(string $field)
+ {
+ $props = \array_diff(
+ $this->modelFields,
+ $this->invisibleFields
+ );
+ if (in_array($field, $props)) {
+ if ($field === 'type' and !\is_object($this->{$field})) {
+ return QuestionType::make($this->{$field});
+ }
+ }
+ return parent::__get($field);
+ }
+
+ /**
+ * Set prop
+ *
+ * @param string $field
+ * @param mixed $value
+ * @throws TypeError if the field has an incorrect value
+ * @return mixed|null The input value or null is this prop does not exists
+ */
+ public function __set(string $field, $value)
+ {
+ $out = parent::__set($field, $value);
+ if (in_array($field, $this->modelFields)) {
+ $this->updatedValues[$field] = (\is_object($value) and
+ get_class($value) === QuestionType::class) ? $value->value : $value;
+ $this->setRawProperty($this, $field, $this->updatedValues[$field]);
+ }
+ return $out;
+ }
+
+ /**
+ * Fill data into this model
+ * using validation rules
+ *
+ * @param array $properties
+ * @return void
+ * @throws TypeError if any valid is not validated
+ */
+ public function fill(array $properties)
+ {
+ if (isset($properties['type'])) {
+ try {
+ $properties['type'] = (\is_int($properties['type']) or \ctype_digit($properties['type'])) ?
+ QuestionType::make((int)$properties['type']) : $properties['type'];
+ } catch (TypeError $e) {
+ // cast anyway
+ $properties['type'] = (int)$properties['type'];
+ }
+ }
+ parent::fill($properties);
+ }
+
+ /**
+ * Validate all model properties
+ *
+ * @return array the validated values
+ */
+ protected function validateAllValues(array $ignore = []): array
+ {
+ $out = [];
+ if (!\in_array('type', $ignore)) {
+ try {
+ $this->validateValue('type', QuestionType::make(self::getRawProperty($this, 'type')));
+ } catch (TypeError $e) {
+ // this to throw validation error if failure way
+ $this->validateValue('type', self::getRawProperty($this, 'type'));
+ }
+ $out['type'] = self::getRawProperty($this, 'type');
+ }
+ return array_merge($out, parent::validateAllValues(array_merge(['type'], $ignore)));
+ }
+
+ // PUBLIC METHODS
+
+ /**
+ * Get all questions only
+ *
+ * @param string $hashcode
+ * @return array
+ *
+ * with \LDC\LDCCRM\Models\Question[]
+ */
+ public static function all(): array
+ {
+ static::setUpApiInterface(); // set apiInterface
+ $request = Utils::getResquest('apiV2.questions.index');
+
+ $response = static::$apiInterface->call($request);
+ $out = [];
+ foreach ($response->data as $question) {
+ $answers = \array_map(function ($answer) {
+ return new Answer($answer);
+ }, $question['answers']);
+ $question['answers'] = $answers;
+ $out[] = new self($question);
+ }
+ return $out;
+ }
+
+ /**
+ * Get all the questions with customer answers
+ *
+ * @param \LDC\LDCCRM\Models\Customer $customer
+ * @return array
+ *
+ * [
+ * 0 => [
+ * 'question' => \LDC\LDCCRM\Models\Question,
+ * 'customerAnswers' => \LDC\LDCCRM\Models\Answer[]
+ * ]
+ * ]
+ *
+ * with \LDC\LDCCRM\Models\Question[]
+ * and the customer \LDC\LDCCRM\Models\Answer[]
+ */
+ public static function allWithCustomerAnswers(Customer $customer): array
+ {
+ static::setUpApiInterface(); // set apiInterface
+ $request = Utils::getResquest('apiV2.questions.indexWithResponse', [
+ 'customer' => $customer->hashcode
+ ]);
+
+ $response = static::$apiInterface->call($request);
+ $out = [];
+ foreach ($response->data as $question) {
+ $answers = \array_map(function ($answer) {
+ return new Answer($answer);
+ }, $question['answers']);
+ $customerAnswers = \array_map(function ($answer) {
+ return new Answer($answer);
+ }, $question['customerAnswers']);
+ unset($question['customerAnswers']);
+ $question['answers'] = $answers;
+ $out[] = [
+ 'question' => new self($question),
+ 'customerAnswers' => $customerAnswers,
+ ];
+ }
+ return $out;
+ }
+
+ /**
+ * Get unanswered question for a specific customer
+ *
+ * @param \LDC\LDCCRM\Models\Customer $customer
+ * @return \LDC\LDCCRM\Models\Question[]
+ */
+ public static function unansweredBy(Customer $customer): array
+ {
+ self::assertValidHascode($customer->hashcode);
+ static::setUpApiInterface(); // set apiInterface
+ $request = Utils::getResquest(
+ 'apiV2.questions.unAnsweredBy',
+ ['customer' => $customer->hashcode]
+ );
+
+ $response = static::$apiInterface->call($request);
+ $out = [];
+ foreach ($response->data as $question) {
+ $answers = \array_map(function ($answer) {
+ return new Answer($answer);
+ }, $question['answers']);
+ $question['answers'] = $answers;
+ $out[] = new self($question);
+ }
+ return $out;
+ }
+}
diff --git a/src/Transport/Response.php b/src/Transport/Response.php
index ccc2c39..7934cca 100644
--- a/src/Transport/Response.php
+++ b/src/Transport/Response.php
@@ -17,6 +17,7 @@ use RecursiveIteratorIterator;
* @property string $message The api message
* @property array $headers The http reponse headers
* @property array $body The http response body, this is parsed json
+ * @property ?array $data The reponse data
*
* @package LDCCrmClient
* @subpackage transport
diff --git a/src/routes.json b/src/routes.json
index 802cffe..6483290 100644
--- a/src/routes.json
+++ b/src/routes.json
@@ -18,40 +18,46 @@
"POST"
]
},
- "apiV2.customer.store": {
- "uri": "api/v2/customer",
+ "apiV2.customers.store": {
+ "uri": "api/v2/customers",
"methods": [
"POST"
]
},
- "apiV2.customer.show": {
- "uri": "api/v2/customer/{customer}",
+ "apiV2.customers.show": {
+ "uri": "api/v2/customers/{customer}",
"methods": [
"GET",
"HEAD"
]
},
- "apiV2.customer.update": {
- "uri": "api/v2/customer/{customer}",
+ "apiV2.customers.update": {
+ "uri": "api/v2/customers/{customer}",
"methods": [
"PUT",
"PATCH"
]
},
- "apiV2.customer.destroy": {
- "uri": "api/v2/customer/{customer}",
+ "apiV2.customers.destroy": {
+ "uri": "api/v2/customers/{customer}",
"methods": [
"DELETE"
]
},
- "apiV2.customer.authenticate": {
- "uri": "api/v2/customer/authenticate",
+ "apiV2.customers.authenticate": {
+ "uri": "api/v2/customers/authenticate",
"methods": [
"POST"
]
},
- "apiV2.customer.activate": {
- "uri": "api/v2/customer/{customer}/activate",
+ "apiV2.customers.activate": {
+ "uri": "api/v2/customers/{customer}/activate",
+ "methods": [
+ "POST"
+ ]
+ },
+ "apiV2.customers.answers.store": {
+ "uri": "api/v2/customers/{customer}/answers",
"methods": [
"POST"
]
@@ -67,5 +73,26 @@
"methods": [
"POST"
]
+ },
+ "apiV2.questions.index": {
+ "uri": "api/v2/questions",
+ "methods": [
+ "GET",
+ "HEAD"
+ ]
+ },
+ "apiV2.questions.unAnsweredBy": {
+ "uri": "api/v2/questions/un-answered-by/{customer}",
+ "methods": [
+ "GET",
+ "HEAD"
+ ]
+ },
+ "apiV2.questions.indexWithResponse": {
+ "uri": "api/v2/questions/with-answers/{customer}",
+ "methods": [
+ "GET",
+ "HEAD"
+ ]
}
}
\ No newline at end of file