Editor
2 months ago
Embed
1 month ago
Links
2 months ago
Listing
9 months ago
Options
2 years ago
Preview
1 month ago
Renderer
4 days ago
RestApi
1 week ago
Scheduler
4 days ago
Segment
1 year ago
Sending
1 week ago
Sharing
1 month ago
Shortcodes
2 days ago
Statistics
1 week ago
ViewInBrowser
1 month ago
ApiDataSanitizer.php
3 years ago
AutomatedLatestContent.php
2 months ago
AutomaticEmailsRepository.php
3 years ago
BlockPostQuery.php
2 months ago
BulkActionController.php
1 month ago
BulkActionException.php
1 month ago
DynamicProducts.php
11 months ago
NewsletterCoupon.php
2 months ago
NewsletterDeleteController.php
2 years ago
NewsletterHtmlSanitizer.php
2 years ago
NewsletterPostsRepository.php
2 years ago
NewsletterResendController.php
4 days ago
NewsletterSaveController.php
1 month ago
NewsletterValidator.php
1 year ago
NewslettersRepository.php
2 weeks ago
StatusController.php
1 month ago
Url.php
2 months ago
index.php
3 years ago
NewsletterValidator.php
118 lines
| 1 | <?php declare(strict_types = 1); |
| 2 | |
| 3 | namespace MailPoet\Newsletter; |
| 4 | |
| 5 | if (!defined('ABSPATH')) exit; |
| 6 | |
| 7 | |
| 8 | use MailPoet\Entities\NewsletterEntity; |
| 9 | use MailPoet\Services\Bridge; |
| 10 | use MailPoet\Settings\TrackingConfig; |
| 11 | use MailPoet\Validator\ValidationException; |
| 12 | |
| 13 | class NewsletterValidator { |
| 14 | |
| 15 | /** @var Bridge */ |
| 16 | private $bridge; |
| 17 | |
| 18 | /** @var TrackingConfig */ |
| 19 | private $trackingConfig; |
| 20 | |
| 21 | public function __construct( |
| 22 | Bridge $bridge, |
| 23 | TrackingConfig $trackingConfig |
| 24 | ) { |
| 25 | $this->bridge = $bridge; |
| 26 | $this->trackingConfig = $trackingConfig; |
| 27 | } |
| 28 | |
| 29 | public function validate(NewsletterEntity $newsletterEntity): ?string { |
| 30 | if ( |
| 31 | $newsletterEntity->getWpPostId() !== null |
| 32 | ) { |
| 33 | // Temporarily skip validation for emails created via Gutenberg editor |
| 34 | return null; |
| 35 | } |
| 36 | try { |
| 37 | $this->validateSegments($newsletterEntity); |
| 38 | $this->validateBody($newsletterEntity); |
| 39 | $this->validateUnsubscribeRequirements($newsletterEntity); |
| 40 | $this->validateReEngagementRequirements($newsletterEntity); |
| 41 | $this->validateAutomaticLatestContentRequirements($newsletterEntity); |
| 42 | } catch (ValidationException $exception) { |
| 43 | return $exception->getMessage(); |
| 44 | } |
| 45 | return null; |
| 46 | } |
| 47 | |
| 48 | private function validateUnsubscribeRequirements(NewsletterEntity $newsletterEntity): void { |
| 49 | if (!$this->bridge->isMailpoetSendingServiceEnabled()) { |
| 50 | return; |
| 51 | } |
| 52 | $content = $newsletterEntity->getContent(); |
| 53 | $hasUnsubscribeUrl = strpos($content, '[link:subscription_unsubscribe_url]') !== false; |
| 54 | $hasUnsubscribeLink = strpos($content, '[link:subscription_unsubscribe]') !== false; |
| 55 | |
| 56 | if (!$hasUnsubscribeLink && !$hasUnsubscribeUrl) { |
| 57 | throw new ValidationException(__('All emails must include an "Unsubscribe" link. Add a footer widget to your email to continue.', 'mailpoet')); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | private function validateSegments(NewsletterEntity $newsletterEntity): void { |
| 62 | if ( |
| 63 | $newsletterEntity->getType() !== NewsletterEntity::TYPE_NOTIFICATION |
| 64 | && $newsletterEntity->getType() !== NewsletterEntity::TYPE_RE_ENGAGEMENT |
| 65 | ) { |
| 66 | return; |
| 67 | } |
| 68 | |
| 69 | $emptySegmentsErrorMessage = __('You need to select a list to send to.', 'mailpoet'); |
| 70 | $segmentIds = $newsletterEntity->getSegmentIds(); |
| 71 | |
| 72 | if (empty($segmentIds)) { |
| 73 | throw new ValidationException($emptySegmentsErrorMessage); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | private function validateBody(NewsletterEntity $newsletterEntity): void { |
| 78 | $emptyBodyErrorMessage = __('Poet, please add prose to your masterpiece before you send it to your followers.', 'mailpoet'); |
| 79 | $content = $newsletterEntity->getContent(); |
| 80 | |
| 81 | if ($content === '') { |
| 82 | throw new ValidationException($emptyBodyErrorMessage); |
| 83 | } |
| 84 | |
| 85 | $contentBlocks = $newsletterEntity->getBody()['content']['blocks'] ?? []; |
| 86 | if (count($contentBlocks) < 1) { |
| 87 | throw new ValidationException($emptyBodyErrorMessage); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | private function validateReEngagementRequirements(NewsletterEntity $newsletterEntity): void { |
| 92 | if ($newsletterEntity->getType() !== NewsletterEntity::TYPE_RE_ENGAGEMENT) { |
| 93 | return; |
| 94 | } |
| 95 | |
| 96 | if (strpos($newsletterEntity->getContent(), '[link:subscription_re_engage_url]') === false) { |
| 97 | throw new ValidationException(__('A re-engagement email must include a link with [link:subscription_re_engage_url] shortcode.', 'mailpoet')); |
| 98 | } |
| 99 | |
| 100 | if (!$this->trackingConfig->isEmailTrackingEnabled()) { |
| 101 | throw new ValidationException(__('Re-engagement emails are disabled because open and click tracking is disabled in MailPoet → Settings → Advanced.', 'mailpoet')); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | private function validateAutomaticLatestContentRequirements(NewsletterEntity $newsletterEntity) { |
| 106 | if ($newsletterEntity->getType() !== NewsletterEntity::TYPE_NOTIFICATION) { |
| 107 | return; |
| 108 | } |
| 109 | $content = $newsletterEntity->getContent(); |
| 110 | if ( |
| 111 | strpos($content, '"type":"automatedLatestContent"') === false && |
| 112 | strpos($content, '"type":"automatedLatestContentLayout"') === false |
| 113 | ) { |
| 114 | throw new ValidationException(_x('Please add an “Automatic Latest Content” widget to the email from the right sidebar.', '(Please reuse the current translation used for the string “Automatic Latest Content”) This Error message is displayed when a user tries to send a “Post Notification” email without any “Automatic Latest Content” widget inside', 'mailpoet')); |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 |