Per WP security best practices, sanitization should be handled as early as possible. So this commit move updates the calls to sanitize the segment name and description to the part of the code where the user input is first processed, instead of when the data is saved to the database. [MAILPOET-5232]
205 lines
6.6 KiB
PHP
205 lines
6.6 KiB
PHP
<?php declare(strict_types = 1);
|
||
|
||
namespace MailPoet\API\MP\v1;
|
||
|
||
use MailPoet\Entities\SegmentEntity;
|
||
use MailPoet\Form\FormsRepository;
|
||
use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
|
||
use MailPoet\Segments\SegmentsRepository;
|
||
|
||
class Segments {
|
||
private const DATE_FORMAT = 'Y-m-d H:i:s';
|
||
|
||
/** @var NewsletterSegmentRepository */
|
||
private $newsletterSegmentRepository;
|
||
|
||
/** @var FormsRepository */
|
||
private $formsRepository;
|
||
|
||
/** @var SegmentsRepository */
|
||
private $segmentsRepository;
|
||
|
||
public function __construct (
|
||
NewsletterSegmentRepository $newsletterSegmentRepository,
|
||
FormsRepository $formsRepository,
|
||
SegmentsRepository $segmentsRepository
|
||
) {
|
||
$this->newsletterSegmentRepository = $newsletterSegmentRepository;
|
||
$this->formsRepository = $formsRepository;
|
||
$this->segmentsRepository = $segmentsRepository;
|
||
}
|
||
|
||
public function getAll(): array {
|
||
$segments = $this->segmentsRepository->findBy(['type' => SegmentEntity::TYPE_DEFAULT], ['id' => 'asc']);
|
||
$result = [];
|
||
foreach ($segments as $segment) {
|
||
$result[] = $this->buildItem($segment);
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
public function addList(array $data): array {
|
||
$this->validateSegmentName($data);
|
||
|
||
try {
|
||
$name = isset($data['name']) ? sanitize_text_field($data['name']) : '';
|
||
$description = isset($data['description']) ? sanitize_textarea_field($data['description']) : '';
|
||
$segment = $this->segmentsRepository->createOrUpdate($name, $description);
|
||
} catch (\Exception $e) {
|
||
throw new APIException(
|
||
__('The list couldn’t be created in the database', 'mailpoet'),
|
||
APIException::FAILED_TO_SAVE_LIST
|
||
);
|
||
}
|
||
|
||
return $this->buildItem($segment);
|
||
}
|
||
|
||
public function updateList(array $data): array {
|
||
// firstly validation on list id
|
||
$this->validateSegmentId((string)($data['id'] ?? ''));
|
||
|
||
// secondly validation on list name
|
||
$this->validateSegmentName($data);
|
||
|
||
// update is supported only for default segment type
|
||
$this->validateSegmentType((string)$data['id']);
|
||
|
||
$name = isset($data['name']) ? sanitize_text_field($data['name']) : '';
|
||
$description = isset($data['description']) ? sanitize_textarea_field($data['description']) : '';
|
||
|
||
try {
|
||
$segment = $this->segmentsRepository->createOrUpdate(
|
||
$name,
|
||
$description,
|
||
SegmentEntity::TYPE_DEFAULT,
|
||
[],
|
||
(int)$data['id']
|
||
);
|
||
} catch (\Exception $e) {
|
||
throw new APIException(
|
||
__('The list couldn’t be updated in the database', 'mailpoet'),
|
||
APIException::FAILED_TO_UPDATE_LIST
|
||
);
|
||
}
|
||
|
||
return $this->buildItem($segment);
|
||
}
|
||
|
||
public function deleteList(string $listId): bool {
|
||
$this->validateSegmentId($listId);
|
||
|
||
// delete is supported only for default segment type
|
||
$this->validateSegmentType($listId);
|
||
|
||
$activelyUsedNewslettersSubjects = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments([$listId]);
|
||
if (isset($activelyUsedNewslettersSubjects[$listId])) {
|
||
throw new APIException(
|
||
str_replace(
|
||
'%1$s',
|
||
"'" . join("', '", $activelyUsedNewslettersSubjects[$listId] ) . "'",
|
||
// translators: %1$s is a comma-seperated list of emails for which the segment is used.
|
||
_x('List cannot be deleted because it’s used for %1$s email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.', 'mailpoet')
|
||
),
|
||
APIException::LIST_USED_IN_EMAIL
|
||
);
|
||
}
|
||
|
||
$activelyUsedFormNames = $this->formsRepository->getNamesOfFormsForSegments();
|
||
if (isset($activelyUsedFormNames[$listId])) {
|
||
throw new APIException(
|
||
str_replace(
|
||
'%1$s',
|
||
"'" . join("', '", $activelyUsedFormNames[$listId] ) . "'",
|
||
// translators: %1$s is a comma-seperated list of forms for which the segment is used.
|
||
_nx(
|
||
'List cannot be deleted because it’s used for %1$s form',
|
||
'List cannot be deleted because it’s used for %1$s forms',
|
||
count($activelyUsedFormNames[$listId]),
|
||
'Alert shown when trying to delete segment, when it is assigned to a form.',
|
||
'mailpoet'
|
||
)
|
||
),
|
||
APIException::LIST_USED_IN_FORM
|
||
);
|
||
}
|
||
|
||
try {
|
||
$this->segmentsRepository->bulkDelete([$listId]);
|
||
return true;
|
||
} catch (\Exception $e) {
|
||
throw new APIException(
|
||
__('The list couldn’t be deleted from the database', 'mailpoet'),
|
||
APIException::FAILED_TO_DELETE_LIST
|
||
);
|
||
}
|
||
}
|
||
|
||
private function validateSegmentId(string $segmentId): void {
|
||
if (empty($segmentId)) {
|
||
throw new APIException(
|
||
__('List id is required.', 'mailpoet'),
|
||
APIException::LIST_ID_REQUIRED
|
||
);
|
||
}
|
||
|
||
if (!$this->segmentsRepository->findOneById($segmentId)) {
|
||
throw new APIException(
|
||
__('The list does not exist.', 'mailpoet'),
|
||
APIException::LIST_NOT_EXISTS
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Throws an exception when the segment's name is invalid
|
||
* @return void
|
||
*/
|
||
private function validateSegmentName(array $data): void {
|
||
if (empty($data['name'])) {
|
||
throw new APIException(
|
||
__('List name is required.', 'mailpoet'),
|
||
APIException::LIST_NAME_REQUIRED
|
||
);
|
||
}
|
||
|
||
if (!$this->segmentsRepository->isNameUnique($data['name'], null)) {
|
||
throw new APIException(
|
||
__('This list already exists.', 'mailpoet'),
|
||
APIException::LIST_EXISTS
|
||
);
|
||
}
|
||
}
|
||
|
||
private function validateSegmentType(string $segmentId): void {
|
||
$segment = $this->segmentsRepository->findOneById($segmentId);
|
||
if ($segment && $segment->getType() !== SegmentEntity::TYPE_DEFAULT) {
|
||
throw new APIException(
|
||
str_replace(
|
||
'%1$s',
|
||
"'" . $segment->getType() . "'",
|
||
// translators: %1$s is an invalid segment type.
|
||
__('List of the type %1$s is not supported for this action.', 'mailpoet')
|
||
),
|
||
APIException::LIST_TYPE_IS_NOT_SUPPORTED
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param SegmentEntity $segment
|
||
* @return array
|
||
*/
|
||
private function buildItem(SegmentEntity $segment): array {
|
||
return [
|
||
'id' => (string)$segment->getId(), // (string) for BC
|
||
'name' => $segment->getName(),
|
||
'type' => $segment->getType(),
|
||
'description' => $segment->getDescription(),
|
||
'created_at' => ($createdAt = $segment->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
|
||
'updated_at' => $segment->getUpdatedAt()->format(self::DATE_FORMAT),
|
||
'deleted_at' => ($deletedAt = $segment->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
|
||
];
|
||
}
|
||
}
|