Add method for creating custom fields in API

[MAILPOET-2041]
This commit is contained in:
Pavel Dohnal
2019-05-29 16:03:27 +02:00
committed by M. Shull
parent f678c22f5b
commit c15c693507
5 changed files with 441 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
<?php
namespace MailPoet\API\MP\v1;
use MailPoet\CustomFields\ApiDataSanitizer;
use MailPoet\Models\CustomField;
use MailPoet\Models\Segment;
use MailPoet\Models\Subscriber;
@@ -26,14 +27,19 @@ class API {
/** @var RequiredCustomFieldValidator */
private $required_custom_field_validator;
/** @var ApiDataSanitizer */
private $custom_fields_data_sanitizer;
public function __construct(
NewSubscriberNotificationMailer $new_subscriber_notification_mailer,
ConfirmationEmailMailer $confirmation_email_mailer,
RequiredCustomFieldValidator $required_custom_field_validator
RequiredCustomFieldValidator $required_custom_field_validator,
ApiDataSanitizer $custom_fields_data_sanitizer
) {
$this->new_subscriber_notification_mailer = $new_subscriber_notification_mailer;
$this->confirmation_email_mailer = $confirmation_email_mailer;
$this->required_custom_field_validator = $required_custom_field_validator;
$this->custom_fields_data_sanitizer = $custom_fields_data_sanitizer;
}
function getSubscriberFields() {
@@ -82,6 +88,23 @@ class API {
return $data;
}
function addSubscriberField(array $data = []) {
try {
$custom_field = CustomField::createOrUpdate($this->custom_fields_data_sanitizer->sanitize($data));
$errors = $custom_field->getErrors();
if (!empty($errors)) {
throw new \Exception('Failed to save a new subscriber field');
}
$custom_field = CustomField::findOne($custom_field->id);
if (!$custom_field instanceof CustomField) {
throw new \Exception('Failed to create a new subscriber field');
}
return $custom_field->asArray();
} catch (\InvalidArgumentException $e) {
throw new \Exception($e->getMessage(), $e->getCode(), $e);
}
}
function subscribeToList($subscriber_id, $list_id, $options = []) {
return $this->subscribeToLists($subscriber_id, [$list_id], $options);
}

View File

@@ -0,0 +1,164 @@
<?php
namespace MailPoet\CustomFields;
use InvalidArgumentException;
use MailPoet\Models\CustomField;
class ApiDataSanitizer {
function sanitize(array $data = []) {
$this->checkMandatoryStringParameter($data, 'name');
$this->checkMandatoryStringParameter($data, 'type');
$this->checkParamsType($data);
return [
'name' => $data['name'],
'type' => strtolower($data['type']),
'params' => $this->sanitizeParams($data),
];
}
private function checkMandatoryStringParameter(array $data, $parameter_name) {
if (!isset($data[$parameter_name]) || empty($data[$parameter_name])) {
throw new InvalidArgumentException(sprintf(__('Mandatory argument "%s" is missing', 'mailpoet'), $parameter_name));
}
if (!is_string($data[$parameter_name])) {
throw new InvalidArgumentException(sprintf(__('Mandatory argument "%s" has to be string', 'mailpoet'), $parameter_name));
}
}
private function checkParamsType($data) {
if (isset($data['params']) && !is_array($data['params'])) {
throw new InvalidArgumentException(sprintf(__('Params has to be array', 'mailpoet')));
}
}
private function sanitizeParams($data) {
$data['params'] = isset($data['params']) ? $data['params'] : [];
$result = [];
$result['required'] = $this->getRequired($data['params']);
$result['label'] = $this->getLabel($data);
return $result + $this->getExtraParams($data);
}
private function getLabel($data) {
if (empty($data['params']['label'])) {
return $data['name'];
} else {
return $data['params']['label'];
}
}
private function getRequired($params) {
if (isset($params['required']) && $params['required']) {
return '1';
}
return '';
}
private function getExtraParams($data) {
$type = strtolower($data['type']);
if (in_array($type, [CustomField::TYPE_TEXT, CustomField::TYPE_TEXTAREA], true)) {
return $this->getExtraParamsForText($data['params']);
}
if (in_array($type, [CustomField::TYPE_RADIO, CustomField::TYPE_SELECT], true)) {
return $this->getExtraParamsForSelect($data['params']);
}
if ($type === CustomField::TYPE_CHECKBOX) {
return $this->getExtraParamsForCheckbox($data['params']);
}
if ($type === CustomField::TYPE_DATE) {
return $this->getExtraParamsForDate($data['params']);
}
throw new InvalidArgumentException(sprintf(__('Invalid type "%s"', 'mailpoet'), $type));
}
private function getExtraParamsForText($params) {
if (isset($params['validate'])) {
$validate = trim(strtolower($params['validate']));
if (in_array($validate, ['number', 'alphanum', 'phone'], true)) {
return ['validate' => $validate];
}
throw new InvalidArgumentException(__('Validate parameter is not valid', 'mailpoet'));
}
return [];
}
private function getExtraParamsForCheckbox($params) {
if (!isset($params['values']) || empty($params['values']) || count($params['values']) > 1) {
throw new InvalidArgumentException(__('You need to pass exactly one value for checkbox', 'mailpoet'));
}
$value = reset($params['values']);
return ['values' => [$this->sanitizeValue($value)]];
}
private function getExtraParamsForDate($params) {
$date_type = (isset($params['date_type'])
? $params['date_type']
: 'year_month_day'
);
$input_date_format = (isset($params['date_format'])
? $params['date_format']
: ''
);
switch ($date_type) {
case 'year_month_day':
if (!in_array($input_date_format, ['MM/DD/YYYY', 'DD/MM/YYYY', 'YYYY/MM/DD'], true)) {
throw new InvalidArgumentException(__('Invalid date_format for year_month_day', 'mailpoet'));
}
$date_format = $input_date_format;
break;
case 'year_month':
if (!in_array($input_date_format, ['YYYY/MM', 'MM/YY'], true)) {
throw new InvalidArgumentException(__('Invalid date_format for year_month', 'mailpoet'));
}
$date_format = $input_date_format;
break;
case 'month':
$date_format = 'MM';
break;
case 'year':
$date_format = 'YYYY';
break;
case 'day':
$date_format = 'DD';
break;
default:
throw new InvalidArgumentException(__('Invalid value for date_type', 'mailpoet'));
}
return [
'date_type' => $date_type,
'date_format' => $date_format,
];
}
private function getExtraParamsForSelect($params) {
if (!isset($params['values']) || empty($params['values'])) {
throw new InvalidArgumentException(__('You need to pass some values for this type', 'mailpoet'));
}
$values = [];
foreach ($params['values'] as $value) {
$values[] = $this->sanitizeValue($value);
}
return ['values' => $values];
}
private function sanitizeValue($value) {
if (!isset($value['value']) || empty($value['value'])) {
throw new InvalidArgumentException(__('Value cannot be empty', 'mailpoet'));
}
$result = ['value' => $value['value']];
if (isset($value['is_checked']) && $value['is_checked']) {
$result['is_checked'] = '1';
} else {
$result['is_checked'] = '';
}
return $result;
}
}

View File

@@ -6,6 +6,7 @@ use AspectMock\Test as Mock;
use Codeception\Util\Fixtures;
use Codeception\Stub;
use Codeception\Stub\Expected;
use MailPoet\CustomFields\ApiDataSanitizer;
use MailPoet\Models\CustomField;
use MailPoet\Models\ScheduledTask;
use MailPoet\Models\Segment;
@@ -24,7 +25,8 @@ class APITest extends \MailPoetTest {
return new \MailPoet\API\MP\v1\API(
Stub::makeEmpty(NewSubscriberNotificationMailer::class, ['send']),
Stub::makeEmpty(ConfirmationEmailMailer::class, ['sendConfirmationEmail']),
Stub::makeEmptyExcept(RequiredCustomFieldValidator::class, 'validate')
Stub::makeEmptyExcept(RequiredCustomFieldValidator::class, 'validate'),
Stub::makeEmptyExcept(ApiDataSanitizer::class, 'validate')
);
}

View File

@@ -0,0 +1,244 @@
<?php
namespace MailPoet\CustomFields;
use InvalidArgumentException;
class ApiDataSanitizerTest extends \MailPoetUnitTest {
/** @var ApiDataSanitizer */
private $sanitizer;
function _before() {
$this->sanitizer = new ApiDataSanitizer();
}
function testItThrowsIfNameIsMissing() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize(['type' => 'text']);
}
function testItThrowsIfNameIsEmpty() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize(['name' => '', 'type' => 'text']);
}
function testItThrowsIfNameIsWrongType() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize(['name' => ['x'], 'type' => 'text']);
}
function testItThrowsIfTypeIsMissing() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize(['name' => 'Name']);
}
function testItThrowsIfTypeIsEmpty() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize(['name' => 'Name', 'type' => '']);
}
function testItThrowsIfTypeIsWrongType() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize(['name' => 'Name', 'type' => ['y']]);
}
function testItThrowsIfTypeIsInvalid() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize(['name' => 'Name', 'type' => 'Invalid Type']);
}
function testItThrowsIfParamsIsInvalidType() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text', 'params' => 'xyz']);
}
function testItReturnsArray() {
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text']);
expect($result)->internalType('array');
}
function testItReturnsName() {
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text']);
expect($result)->hasKey('name');
expect($result['name'])->same('Name');
}
function testItReturnsType() {
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'Text']);
expect($result)->hasKey('type');
expect($result['type'])->same('text');
}
function testItIgnoresUnknownProperties() {
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text', 'unknown' => 'Unknown property']);
expect($result)->hasntKey('unknown');
}
function testItReturnsParamsIfPassed() {
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text', 'params' => ['required' => '1']]);
expect($result)->hasKey('params');
}
function testItReturnsCorrectRequiredForm() {
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text', 'params' => ['required' => true]]);
expect($result['params']['required'])->same('1');
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text', 'params' => ['required' => false]]);
expect($result['params']['required'])->same('');
}
function testItIgnoresUnknownParams() {
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text', 'params' => ['unknown' => 'Unknown property']]);
expect($result)->hasKey('params');
expect($result['params'])->hasntKey('unknown');
}
function testItFillsLabel() {
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text']);
expect($result['params'])->hasKey('label');
expect($result['params']['label'])->same('Name');
}
function testItThrowsForInvalidValidate() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text', 'params' => ['validate' => 'unknown']]);
}
function testItReturnsSanitizedValidate() {
$result = $this->sanitizer->sanitize(['name' => 'Name', 'type' => 'text', 'params' => ['validate' => 'alphanuM']]);
expect($result['params']['validate'])->same('alphanum');
}
function testItThrowsIfNoValuesInRadio() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'radio',
]);
}
function testItReturnsSanitizedValuesForRadio() {
$result = $this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'radio',
'params' => [
'values' => [
[
'value' => 'value 1',
'unknown' => 'Unknown property',
],
[
'is_checked' => true,
'value' => 'value 2',
],
],
],
]);
$values = $result['params']['values'];
expect($values)->internalType('array');
expect($values)->count(2);
expect($values[0])->same(['value' => 'value 1', 'is_checked' => '']);
expect($values[1])->same(['value' => 'value 2', 'is_checked' => '1']);
}
function testItThrowsIfNoValuesInCheckbox() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'checkbox',
]);
}
function testItThrowsIfMoreValuesInCheckbox() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'checkbox',
'params' => [
'values' => [
[
'value' => 'value 1',
],
[
'value' => 'value 2',
],
],
],
]);
}
function testItThrowsIfNameValueMissingInCheckbox() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'checkbox',
'params' => [
'values' => [
[
'is_checked' => true,
],
],
],
]);
}
function testItSanitizeCheckbox() {
$result = $this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'checkbox',
'params' => [
'values' => [
[
'is_checked' => true,
'value' => 'value 1',
],
],
],
]);
$values = $result['params']['values'];
expect($values)->internalType('array');
expect($values)->count(1);
expect($values[0])->same(['value' => 'value 1', 'is_checked' => '1']);
}
function testDateThrowsIfNoDateFormat() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'date',
'params' => [],
]);
}
function testDateThrowsIfInvalidDateFormat() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'date',
'params' => ['date_format' => 'invalid'],
]);
}
function testDateThrowsIfInvalidDateType() {
$this->expectException(InvalidArgumentException::class);
$this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'date',
'params' => ['date_format' => 'MM/DD/YYYY', 'date_type' => 'invalid'],
]);
}
function testSanitizeDate() {
$result = $this->sanitizer->sanitize([
'name' => 'Name',
'type' => 'date',
'params' => ['date_format' => 'MM/DD/YYYY', 'date_type' => 'year_month_day'],
]);
expect($result['params'])->equals([
'date_format' => 'MM/DD/YYYY',
'date_type' => 'year_month_day',
'label' => 'Name',
'required' => '',
]);
}
}

View File

@@ -1,5 +1,11 @@
<?php
function __($text) {
return $text;
}
define('ABSPATH', '/');
$console = new \Codeception\Lib\Console\Output([]);
abstract class MailPoetUnitTest extends \Codeception\TestCase\Test {