PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.33.1
MailPoet – Newsletters, Email Marketing, and Automation v5.33.1
5.37.0 5.36.1 5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / EmailEditor / Integrations / MailPoet / EmailApiController.php
mailpoet / lib / EmailEditor / Integrations / MailPoet Last commit date
Blocks 11 months ago Coupons 2 months ago Endpoints 4 months ago Patterns 2 months ago PersonalizationTags 1 month ago ProductCollection 2 months ago Templates 3 months ago AutomationEmailContextProvider.php 3 months ago AutomationEmailPreviewOrderProvider.php 3 months ago BlockEmailContentDetector.php 3 months ago Cli.php 1 month ago DependencyNotice.php 1 year ago EditorPageRenderer.php 2 months ago EmailApiController.php 2 months ago EmailEditor.php 4 months ago EmailEditorPreviewEmail.php 10 months ago Logger.php 11 months ago MailPoetCssInliner.php 9 months ago MailpoetCssInlinerFactory.php 1 year ago PersonalizationTagManager.php 1 month ago index.php 2 years ago
EmailApiController.php
304 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\EmailEditor\Integrations\MailPoet;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Entities\NewsletterEntity;
9 use MailPoet\Entities\NewsletterOptionEntity;
10 use MailPoet\Entities\NewsletterOptionFieldEntity;
11 use MailPoet\Entities\NewsletterSegmentEntity;
12 use MailPoet\Entities\SegmentEntity;
13 use MailPoet\Newsletter\NewslettersRepository;
14 use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository;
15 use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
16 use MailPoet\Newsletter\Segment\NewsletterSegmentRepository;
17 use MailPoet\Newsletter\Sharing\ShareVisibility;
18 use MailPoet\Newsletter\Url as NewsletterUrl;
19 use MailPoet\NotFoundException;
20 use MailPoet\UnexpectedValueException;
21 use MailPoet\Validator\Builder;
22 use MailPoetVendor\Doctrine\ORM\EntityManager;
23
24 class EmailApiController {
25 /** @var NewslettersRepository */
26 private $newsletterRepository;
27
28 /** @var NewsletterUrl */
29 private $newsletterUrl;
30
31 /** @var NewsletterOptionFieldsRepository */
32 private $newsletterOptionFieldsRepository;
33
34 /** @var NewsletterOptionsRepository */
35 private $newsletterOptionsRepository;
36
37 /** @var NewsletterSegmentRepository */
38 private $newsletterSegmentRepository;
39
40 /** @var EntityManager */
41 private $entityManager;
42
43 /** @var ShareVisibility */
44 private $shareVisibility;
45
46 public function __construct(
47 NewslettersRepository $newsletterRepository,
48 NewsletterUrl $newsletterUrl,
49 NewsletterOptionFieldsRepository $newsletterOptionFieldsRepository,
50 NewsletterOptionsRepository $newsletterOptionsRepository,
51 NewsletterSegmentRepository $newsletterSegmentRepository,
52 EntityManager $entityManager,
53 ShareVisibility $shareVisibility
54 ) {
55 $this->newsletterRepository = $newsletterRepository;
56 $this->newsletterUrl = $newsletterUrl;
57 $this->newsletterOptionFieldsRepository = $newsletterOptionFieldsRepository;
58 $this->newsletterOptionsRepository = $newsletterOptionsRepository;
59 $this->newsletterSegmentRepository = $newsletterSegmentRepository;
60 $this->entityManager = $entityManager;
61 $this->shareVisibility = $shareVisibility;
62 }
63
64 /**
65 * @param array $postEmailData - WP_Post data
66 * @return array - MailPoet specific email data that will be attached to the post API response
67 */
68 public function getEmailData($postEmailData): array {
69 $newsletter = $this->newsletterRepository->findOneBy(['wpPost' => $postEmailData['id']]);
70 $isAutomationNewsletter = $newsletter && ($newsletter->isAutomation() || $newsletter->isAutomationTransactional());
71 $showInArchive = $newsletter
72 ? $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_EXCLUDE_FROM_ARCHIVE) !== '1'
73 : true;
74 return [
75 'id' => $newsletter ? $newsletter->getId() : null,
76 'type' => $newsletter ? $newsletter->getType() : '',
77 'subject' => $newsletter ? $newsletter->getSubject() : '',
78 'preheader' => $newsletter ? $newsletter->getPreheader() : '',
79 'sender_name' => $newsletter ? $newsletter->getSenderName() : '',
80 'sender_address' => $newsletter ? $newsletter->getSenderAddress() : '',
81 'reply_to_name' => $newsletter ? $newsletter->getReplyToName() : '',
82 'reply_to_address' => $newsletter ? $newsletter->getReplyToAddress() : '',
83 'preview_url' => $this->newsletterUrl->getViewInBrowserUrl($newsletter),
84 'deleted_at' => $newsletter && $newsletter->getDeletedAt() !== null ? $newsletter->getDeletedAt()->format('c') : null,
85 'scheduled_at' => $newsletter ? $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_SCHEDULED_AT) : null,
86 'utm_campaign' => $newsletter ? $newsletter->getGaCampaign() : '',
87 'segment_ids' => $newsletter ? $newsletter->getSegmentIds() : [],
88 'is_automation_newsletter' => $isAutomationNewsletter,
89 'share_url' => $newsletter && $this->shareVisibility->isSupported($newsletter)
90 ? $this->newsletterUrl->getPublicShareUrl($newsletter)
91 : '',
92 'share_visibility' => $newsletter
93 ? $this->shareVisibility->getConfiguredVisibility($newsletter)
94 : ShareVisibility::VISIBILITY_DEFAULT,
95 'effective_share_visibility' => $newsletter
96 ? $this->shareVisibility->getEffectiveVisibility($newsletter)
97 : $this->shareVisibility->getDefaultVisibility(),
98 'can_share' => $newsletter ? $this->shareVisibility->canShare($newsletter) : false,
99 'show_in_archive' => $showInArchive,
100 ];
101 }
102
103 /**
104 * Update MailPoet specific data we store with Emails.
105 */
106 public function saveEmailData(array $data, \WP_Post $emailPost): void {
107 $newsletter = $this->newsletterRepository->findOneById($data['id']);
108 if (!$newsletter) {
109 throw new NotFoundException('Newsletter was not found');
110 }
111 if ($newsletter->getWpPostId() !== $emailPost->ID) {
112 throw new UnexpectedValueException('Newsletter ID does not match the post ID');
113 }
114
115 $newsletter->setSubject($data['subject']);
116 $newsletter->setPreheader($data['preheader']);
117
118 if (array_key_exists('sender_name', $data)) {
119 $newsletter->setSenderName($data['sender_name']);
120 }
121
122 if (array_key_exists('sender_address', $data)) {
123 $newsletter->setSenderAddress($data['sender_address']);
124 }
125
126 if (array_key_exists('reply_to_name', $data)) {
127 $newsletter->setReplyToName($data['reply_to_name']);
128 }
129
130 if (array_key_exists('reply_to_address', $data)) {
131 $newsletter->setReplyToAddress($data['reply_to_address']);
132 }
133
134 if (isset($data['utm_campaign'])) {
135 $newsletter->setGaCampaign($data['utm_campaign']);
136 }
137
138 if (isset($data['deleted_at'])) {
139 if (empty($data['deleted_at'])) {
140 $data['deleted_at'] = null;
141 } else {
142 $data['deleted_at'] = new \DateTime($data['deleted_at']);
143 }
144 $newsletter->setDeletedAt($data['deleted_at']);
145 }
146
147 if (array_key_exists('scheduled_at', $data)) {
148 $this->updateScheduledAtOption($newsletter, $data['scheduled_at']);
149 }
150
151 if (array_key_exists('share_visibility', $data)) {
152 $this->updateOption(
153 $newsletter,
154 NewsletterOptionFieldEntity::NAME_SHARE_VISIBILITY,
155 $this->shareVisibility->sanitize((string)$data['share_visibility'])
156 );
157 }
158
159 if (array_key_exists('show_in_archive', $data)) {
160 $this->updateShowInArchiveOption($newsletter, $data['show_in_archive']);
161 }
162
163 if (isset($data['segment_ids']) && is_array($data['segment_ids'])) {
164 $this->updateSegments($newsletter, $data['segment_ids']);
165 $this->entityManager->refresh($newsletter);
166 }
167
168 $this->newsletterRepository->flush();
169 }
170
171 private function updateShowInArchiveOption(NewsletterEntity $newsletter, $showInArchiveValue): void {
172 if (!is_bool($showInArchiveValue)) {
173 throw new UnexpectedValueException('Invalid show_in_archive value. Expected a boolean.');
174 }
175
176 if ($newsletter->getType() !== NewsletterEntity::TYPE_STANDARD) {
177 return;
178 }
179
180 $this->updateOption(
181 $newsletter,
182 NewsletterOptionFieldEntity::NAME_EXCLUDE_FROM_ARCHIVE,
183 $showInArchiveValue ? '0' : '1'
184 );
185 }
186
187 private function updateOption($newsletter, string $optionName, $optionValue): void {
188 $optionField = $this->newsletterOptionFieldsRepository->findOneBy([
189 'name' => $optionName,
190 'newsletterType' => $newsletter->getType(),
191 ]);
192
193 if (!$optionField) {
194 return;
195 }
196
197 $option = $this->newsletterOptionsRepository->findOneBy([
198 'newsletter' => $newsletter,
199 'optionField' => $optionField,
200 ]);
201
202 if (!$option) {
203 $option = new NewsletterOptionEntity($newsletter, $optionField);
204 $this->newsletterOptionsRepository->persist($option);
205 $newsletter->getOptions()->add($option);
206 }
207
208 $option->setValue($optionValue);
209 }
210
211 private function updateScheduledAtOption($newsletter, $scheduledAtValue): void {
212 // Validate the scheduled_at value
213 if ($scheduledAtValue !== null && $scheduledAtValue !== '') {
214 try {
215 new \DateTime($scheduledAtValue);
216 } catch (\Exception $e) {
217 throw new UnexpectedValueException('Invalid scheduled_at format. Expected a valid datetime string.');
218 }
219 }
220
221 $this->updateOption($newsletter, NewsletterOptionFieldEntity::NAME_SCHEDULED_AT, $scheduledAtValue);
222
223 // Also update the isScheduled option
224 $this->updateOption(
225 $newsletter,
226 NewsletterOptionFieldEntity::NAME_IS_SCHEDULED,
227 $scheduledAtValue !== null && $scheduledAtValue !== '' ? '1' : '0'
228 );
229 }
230
231 /**
232 * @param array $segmentIds Array of segment IDs
233 */
234 private function updateSegments($newsletter, array $segmentIds): void {
235 // Normalize segment IDs to integers for consistent strict comparison
236 $segmentIds = array_map('intval', $segmentIds);
237
238 // Remove existing segments that are not in the new list
239 $existingSegments = $newsletter->getNewsletterSegments();
240 foreach ($existingSegments as $newsletterSegment) {
241 $segment = $newsletterSegment->getSegment();
242 if (!$segment || !in_array($segment->getId(), $segmentIds, true)) {
243 $this->entityManager->remove($newsletterSegment);
244 }
245 }
246
247 // Add new segments
248 foreach ($segmentIds as $segmentId) {
249 $segmentIdInt = (int)$segmentId;
250 $segment = $this->entityManager->getReference(SegmentEntity::class, $segmentIdInt);
251 if (!$segment) {
252 continue;
253 }
254
255 // Check if the newsletter-segment relationship already exists
256 $existingRelation = $this->newsletterSegmentRepository->findOneBy([
257 'newsletter' => $newsletter,
258 'segment' => $segment,
259 ]);
260
261 if (!$existingRelation) {
262 $newsletterSegment = new NewsletterSegmentEntity($newsletter, $segment);
263 $this->entityManager->persist($newsletterSegment);
264 }
265 }
266 $this->entityManager->flush();
267 }
268
269 public function trashEmail(\WP_Post $wpPost) {
270 $newsletter = $this->newsletterRepository->findOneBy(['wpPost' => $wpPost->ID]);
271 if (!$newsletter) {
272 throw new NotFoundException('Newsletter was not found');
273 }
274 if ($newsletter->getWpPostId() !== $wpPost->ID) {
275 throw new UnexpectedValueException('Newsletter ID does not match the post ID');
276 }
277 $this->newsletterRepository->bulkTrash([$newsletter->getId()]);
278 }
279
280 public function getEmailDataSchema(): array {
281 return Builder::object([
282 'id' => Builder::integer()->nullable(),
283 'type' => Builder::string(),
284 'subject' => Builder::string(),
285 'preheader' => Builder::string(),
286 'sender_name' => Builder::string(),
287 'sender_address' => Builder::string(),
288 'reply_to_name' => Builder::string(),
289 'reply_to_address' => Builder::string(),
290 'preview_url' => Builder::string(),
291 'deleted_at' => Builder::string()->nullable(),
292 'scheduled_at' => Builder::string()->nullable(),
293 'utm_campaign' => Builder::string(),
294 'segment_ids' => Builder::array(),
295 'is_automation_newsletter' => Builder::boolean(),
296 'share_url' => Builder::string(),
297 'share_visibility' => Builder::string(),
298 'effective_share_visibility' => Builder::string(),
299 'can_share' => Builder::boolean(),
300 'show_in_archive' => Builder::boolean(),
301 ])->toArray();
302 }
303 }
304