Add method for creating custom fields in API
[MAILPOET-2041]
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
164
lib/CustomFields/ApiDataSanitizer.php
Normal file
164
lib/CustomFields/ApiDataSanitizer.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@@ -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')
|
||||
);
|
||||
}
|
||||
|
||||
|
244
tests/unit/CustomFields/ApiDataSanitizerTest.php
Normal file
244
tests/unit/CustomFields/ApiDataSanitizerTest.php
Normal 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' => '',
|
||||
]);
|
||||
}
|
||||
}
|
@@ -1,5 +1,11 @@
|
||||
<?php
|
||||
|
||||
function __($text) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
define('ABSPATH', '/');
|
||||
|
||||
$console = new \Codeception\Lib\Console\Output([]);
|
||||
|
||||
abstract class MailPoetUnitTest extends \Codeception\TestCase\Test {
|
||||
|
Reference in New Issue
Block a user