diff --git a/mailpoet/lib/API/JSON/v1/Newsletters.php b/mailpoet/lib/API/JSON/v1/Newsletters.php index 52bf52f0d5..f150d1d654 100644 --- a/mailpoet/lib/API/JSON/v1/Newsletters.php +++ b/mailpoet/lib/API/JSON/v1/Newsletters.php @@ -22,6 +22,7 @@ use MailPoet\Newsletter\Preview\SendPreviewException; use MailPoet\Newsletter\Scheduler\PostNotificationScheduler; use MailPoet\Newsletter\Scheduler\Scheduler; use MailPoet\Newsletter\Url as NewsletterUrl; +use MailPoet\Newsletter\Validator; use MailPoet\Settings\SettingsController; use MailPoet\Settings\TrackingConfig; use MailPoet\UnexpectedValueException; @@ -79,6 +80,9 @@ class Newsletters extends APIEndpoint { /** @var TrackingConfig */ private $trackingConfig; + /** @var Validator */ + private $validator; + /** @var Scheduler */ private $scheduler; @@ -97,7 +101,8 @@ class Newsletters extends APIEndpoint { NewsletterSaveController $newsletterSaveController, NewsletterUrl $newsletterUrl, TrackingConfig $trackingConfig, - Scheduler $scheduler + Scheduler $scheduler, + Validator $validator ) { $this->listingHandler = $listingHandler; $this->wp = $wp; @@ -114,6 +119,7 @@ class Newsletters extends APIEndpoint { $this->newsletterUrl = $newsletterUrl; $this->trackingConfig = $trackingConfig; $this->scheduler = $scheduler; + $this->validator = $validator; } public function get($data = []) { @@ -184,6 +190,13 @@ class Newsletters extends APIEndpoint { ]); } + if ($status === NewsletterEntity::STATUS_ACTIVE) { + $validationError = $this->validator->validate($newsletter); + if ($validationError !== null) { + return $this->errorResponse([APIError::FORBIDDEN => $validationError], [], Response::STATUS_FORBIDDEN); + } + } + // if the re-engagement email doesn't contain the re-engage link, it can't be activated if ($newsletter->getType() === NewsletterEntity::TYPE_RE_ENGAGEMENT && $status === NewsletterEntity::STATUS_ACTIVE) { if (strpos($newsletter->getContent(), '[link:subscription_re_engage_url]') === false) { diff --git a/mailpoet/lib/Newsletter/Validator.php b/mailpoet/lib/Newsletter/Validator.php index daebde800b..041b04aecb 100644 --- a/mailpoet/lib/Newsletter/Validator.php +++ b/mailpoet/lib/Newsletter/Validator.php @@ -22,21 +22,31 @@ class Validator { && is_array($newsletterEntity->getBody()) && $newsletterEntity->getBody()['content'] ) { - $body = json_encode($newsletterEntity->getBody()['content']); - if ($body === false) { - return __('Poet, please add prose to your masterpiece before you send it to your followers.'); + $content = $newsletterEntity->getBody()['content']; + $encodedBody = json_encode($content); + if ($encodedBody === false) { + return $this->emptyContentErrorMessage(); + } else { + $blocks = $content['blocks'] ?? []; + if (empty($blocks)) { + return $this->emptyContentErrorMessage(); + } } if ( $this->bridge->isMailpoetSendingServiceEnabled() - && (strpos($body, '[link:subscription_unsubscribe_url]') === false) - && (strpos($body, '[link:subscription_unsubscribe]') === false) + && (strpos($encodedBody, '[link:subscription_unsubscribe_url]') === false) + && (strpos($encodedBody, '[link:subscription_unsubscribe]') === false) ) { return __('All emails must include an "Unsubscribe" link. Add a footer widget to your email to continue.'); } } else { - return __('Poet, please add prose to your masterpiece before you send it to your followers.'); + return $this->emptyContentErrorMessage(); } return null; } + + private function emptyContentErrorMessage(): string { + return __('Poet, please add prose to your masterpiece before you send it to your followers.'); + } } diff --git a/mailpoet/tests/integration/API/JSON/v1/NewslettersTest.php b/mailpoet/tests/integration/API/JSON/v1/NewslettersTest.php index f92f974594..e3c01f9fc5 100644 --- a/mailpoet/tests/integration/API/JSON/v1/NewslettersTest.php +++ b/mailpoet/tests/integration/API/JSON/v1/NewslettersTest.php @@ -36,6 +36,7 @@ use MailPoet\Newsletter\Segment\NewsletterSegmentRepository; use MailPoet\Newsletter\Sending\SendingQueuesRepository; use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository; use MailPoet\Newsletter\Url; +use MailPoet\Newsletter\Validator; use MailPoet\Router\Router; use MailPoet\Segments\SegmentsRepository; use MailPoet\Settings\SettingsController; @@ -695,14 +696,15 @@ class NewslettersTest extends \MailPoetTest { $this->diContainer->get(NewsletterSaveController::class), $this->diContainer->get(Url::class), $this->diContainer->get(TrackingConfig::class), - $this->scheduler + $this->scheduler, + $this->diContainer->get(Validator::class) ); } private function createNewsletter(string $subject, string $type): NewsletterEntity { $newsletter = new NewsletterEntity(); $newsletter->setSubject($subject); - $newsletter->setBody(Fixtures::get('newsletter_body_template')); + $newsletter->setBody((array)json_decode(Fixtures::get('newsletter_body_template'), true)); $newsletter->setType($type); $newsletter->setHash(Security::generateHash()); $this->newsletterRepository->persist($newsletter); diff --git a/mailpoet/tests/integration/API/JSON/v1/SendingQueueTest.php b/mailpoet/tests/integration/API/JSON/v1/SendingQueueTest.php index ed611f5be9..5bed592049 100644 --- a/mailpoet/tests/integration/API/JSON/v1/SendingQueueTest.php +++ b/mailpoet/tests/integration/API/JSON/v1/SendingQueueTest.php @@ -143,7 +143,21 @@ class SendingQueueTest extends \MailPoetTest { $newsletter = new NewsletterEntity(); $newsletter->setSubject('subject'); $newsletter->setType(NewsletterEntity::TYPE_STANDARD); - $newsletter->setBody(['content' => ['type' => 'container', 'columnLayout' => false, 'orientation' => 'vertical']]); + $newsletter->setBody([ + 'content' => + [ + 'type' => 'container', + 'columnLayout' => false, + 'orientation' => 'vertical', + 'blocks' => [ + [ + 'type' => 'header', + 'link' => '', + 'text' => 'Hello!' + ] + ] + ] + ]); $this->entityManager->persist($newsletter); $this->entityManager->flush(); $sendingQueue = new SendingQueueAPI( @@ -165,6 +179,20 @@ class SendingQueueTest extends \MailPoetTest { expect($response['errors'][0]['error'])->stringContainsString('bad_request'); } + public function testItRejectsNewslettersWithoutContentBlocks() { + $newsletter = new NewsletterEntity(); + $newsletter->setSubject('subject'); + $newsletter->setType(NewsletterEntity::TYPE_STANDARD); + $newsletter->setBody(['content' => ['type' => 'container', 'columnLayout' => false, 'orientation' => 'vertical']]); + $this->entityManager->persist($newsletter); + $this->entityManager->flush(); + $sendingQueue = $this->diContainer->get(SendingQueueAPI::class); + $response = $sendingQueue->add(['newsletter_id' => $newsletter->getId()]); + $result = $response->getData(); + expect($result['errors'][0])->array(); + expect($result['errors'][0]['message'])->stringContainsString('Poet, please add prose to your masterpiece before you send it to your followers'); + } + private function _createOrUpdateNewsletterOptions(NewsletterEntity $newsletter, $newsletterType, $options) { $newsletterOptionFieldRepository = $this->diContainer->get(NewsletterOptionFieldsRepository::class); $newsletterOptionRepository = $this->diContainer->get(NewsletterOptionsRepository::class);