PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
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 / API / JSON / v1 / SendingQueue.php
mailpoet / lib / API / JSON / v1 Last commit date
Analytics.php 3 years ago AutomatedLatestContent.php 2 months ago AutomaticEmails.php 3 years ago Captcha.php 1 year ago Coupons.php 2 years ago CustomFields.php 2 months ago DynamicProducts.php 1 year ago DynamicSegments.php 2 months ago FeatureFlags.php 3 years ago Forms.php 2 months ago Help.php 1 year ago ImportExport.php 2 months ago Mailer.php 1 year ago NewsletterLinks.php 2 months ago NewsletterTemplates.php 2 months ago Newsletters.php 2 months ago Premium.php 10 months ago RedirectResponse.php 1 year ago Segments.php 2 months ago SendingQueue.php 2 months ago Services.php 6 months ago Settings.php 2 months ago Setup.php 1 year ago StatisticsExport.php 3 months ago SubscriberStats.php 1 month ago Subscribers.php 2 months ago Tags.php 3 years ago UserFlags.php 2 years ago WoocommerceProductVariations.php 2 months ago WoocommerceSettings.php 3 years ago index.php 3 years ago
SendingQueue.php
329 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\API\JSON\v1;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\API\JSON\Endpoint as APIEndpoint;
9 use MailPoet\API\JSON\Error as APIError;
10 use MailPoet\API\JSON\Response;
11 use MailPoet\API\JSON\ResponseBuilders\SendingQueuesResponseBuilder;
12 use MailPoet\Config\AccessControl;
13 use MailPoet\Cron\ActionScheduler\Actions\DaemonTrigger;
14 use MailPoet\Cron\CronHelper;
15 use MailPoet\Cron\CronTrigger;
16 use MailPoet\Cron\Triggers\WordPress;
17 use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
18 use MailPoet\Entities\NewsletterEntity;
19 use MailPoet\Entities\ScheduledTaskEntity;
20 use MailPoet\Entities\SendingQueueEntity;
21 use MailPoet\Mailer\MailerFactory;
22 use MailPoet\Newsletter\NewslettersRepository;
23 use MailPoet\Newsletter\NewsletterValidator;
24 use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
25 use MailPoet\Newsletter\Sending\SendingQueuesRepository;
26 use MailPoet\Newsletter\Sending\TimeZoneCampaignScheduler;
27 use MailPoet\Segments\SubscribersFinder;
28 use MailPoet\Settings\SettingsController;
29 use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
30 use MailPoetVendor\Carbon\Carbon;
31
32 class SendingQueue extends APIEndpoint {
33 public $permissions = [
34 'global' => AccessControl::PERMISSION_MANAGE_EMAILS,
35 ];
36
37 /** @var SubscribersFeature */
38 private $subscribersFeature;
39
40 /** @var SubscribersFinder */
41 private $subscribersFinder;
42
43 /** @var NewslettersRepository */
44 private $newsletterRepository;
45
46 /** @var SendingQueuesRepository */
47 private $sendingQueuesRepository;
48
49 /** @var ScheduledTasksRepository */
50 private $scheduledTasksRepository;
51
52 /** @var MailerFactory */
53 private $mailerFactory;
54
55 /** @var NewsletterValidator */
56 private $newsletterValidator;
57
58 /** @var SettingsController */
59 private $settings;
60
61 /** @var DaemonTrigger */
62 private $actionSchedulerDaemonTriggerAction;
63
64 /** @var SendingQueuesResponseBuilder */
65 private $sendingQueuesResponseBuilder;
66
67 /** @var CronHelper */
68 private $cronHelper;
69
70 /** @var TimeZoneCampaignScheduler */
71 private $timeZoneCampaignScheduler;
72
73 public function __construct(
74 SubscribersFeature $subscribersFeature,
75 NewslettersRepository $newsletterRepository,
76 SendingQueuesRepository $sendingQueuesRepository,
77 SubscribersFinder $subscribersFinder,
78 ScheduledTasksRepository $scheduledTasksRepository,
79 MailerFactory $mailerFactory,
80 SettingsController $settings,
81 DaemonTrigger $actionSchedulerDaemonTriggerAction,
82 NewsletterValidator $newsletterValidator,
83 SendingQueuesResponseBuilder $sendingQueuesResponseBuilder,
84 CronHelper $cronHelper,
85 TimeZoneCampaignScheduler $timeZoneCampaignScheduler
86 ) {
87 $this->subscribersFeature = $subscribersFeature;
88 $this->subscribersFinder = $subscribersFinder;
89 $this->newsletterRepository = $newsletterRepository;
90 $this->sendingQueuesRepository = $sendingQueuesRepository;
91 $this->scheduledTasksRepository = $scheduledTasksRepository;
92 $this->mailerFactory = $mailerFactory;
93 $this->settings = $settings;
94 $this->actionSchedulerDaemonTriggerAction = $actionSchedulerDaemonTriggerAction;
95 $this->newsletterValidator = $newsletterValidator;
96 $this->sendingQueuesResponseBuilder = $sendingQueuesResponseBuilder;
97 $this->cronHelper = $cronHelper;
98 $this->timeZoneCampaignScheduler = $timeZoneCampaignScheduler;
99 }
100
101 public function add($data = []) {
102 if ($this->subscribersFeature->check()) {
103 return $this->errorResponse([
104 APIError::FORBIDDEN => __('Subscribers limit reached.', 'mailpoet'),
105 ], [], Response::STATUS_FORBIDDEN);
106 }
107 $newsletterId = (isset($data['newsletter_id'])
108 ? (int)$data['newsletter_id']
109 : false
110 );
111
112 // check that the newsletter exists
113 $newsletter = $this->newsletterRepository->findOneById($newsletterId);
114 $this->newsletterRepository->prefetchOptions([$newsletter]);
115
116 if (!$newsletter instanceof NewsletterEntity) {
117 return $this->errorResponse([
118 APIError::NOT_FOUND => __('This newsletter does not exist.', 'mailpoet'),
119 ]);
120 }
121
122 $validationError = $this->newsletterValidator->validate($newsletter);
123 if ($validationError) {
124 return $this->errorResponse([
125 APIError::BAD_REQUEST => $validationError,
126 ]);
127 }
128
129 try {
130 // check that the sending method has been configured properly by verifying that default mailer can be build
131 $this->mailerFactory->getDefaultMailer();
132
133 $isScheduled = (bool)$newsletter->getOptionValue('isScheduled');
134 if ($isScheduled && $this->timeZoneCampaignScheduler->isSubscriberTimeZoneMode($newsletter)) {
135 $sendingQueue = $this->timeZoneCampaignScheduler->schedule($newsletter);
136 WordPress::resetRunInterval();
137 $this->triggerSending($newsletter);
138 return $this->successResponse($this->sendingQueuesResponseBuilder->build($sendingQueue));
139 }
140 // Existing time zone batches must be reconciled regardless of the new send mode (scheduled or
141 // immediate). Otherwise orphaned time zone queues survive in the DB and may later be picked up
142 // by the scheduler cron, causing duplicate sends. Both calls are no-ops when the newsletter
143 // has no time zone queues.
144 if (!$this->timeZoneCampaignScheduler->canReplaceScheduledCampaign($newsletter)) {
145 throw new \Exception(
146 __('This email can no longer be edited because one or more time zone batches have already started.', 'mailpoet'),
147 Response::STATUS_BAD_REQUEST
148 );
149 }
150 $this->timeZoneCampaignScheduler->deleteScheduledCampaignQueues($newsletter);
151
152 $sendingQueue = $this->sendingQueuesRepository->findOneByNewsletterAndTaskStatus($newsletter, null);
153
154 if ($sendingQueue instanceof SendingQueueEntity) {
155 return $this->errorResponse([
156 APIError::NOT_FOUND => __('This newsletter is already being sent.', 'mailpoet'),
157 ]);
158 }
159
160 $sendingQueue = $this->sendingQueuesRepository->findOneByNewsletterAndTaskStatus($newsletter, ScheduledTaskEntity::STATUS_SCHEDULED);
161
162 if (is_null($sendingQueue)) {
163 $scheduledTask = new ScheduledTaskEntity();
164 $scheduledTask->setType(SendingQueueWorker::TASK_TYPE);
165 $sendingQueue = new SendingQueueEntity();
166 $sendingQueue->setNewsletter($newsletter);
167 $sendingQueue->setTask($scheduledTask);
168
169 $this->sendingQueuesRepository->persist($sendingQueue);
170 $this->newsletterRepository->refresh($newsletter);
171 } else {
172 $scheduledTask = $sendingQueue->getTask();
173 }
174
175 if (!$scheduledTask instanceof ScheduledTaskEntity) {
176 return $this->errorResponse([
177 APIError::NOT_FOUND => __('Unable to find scheduled task associated with this newsletter.', 'mailpoet'),
178 ]);
179 }
180
181 $scheduledTask->setPriority(ScheduledTaskEntity::PRIORITY_MEDIUM);
182 $this->scheduledTasksRepository->persist($scheduledTask);
183 $this->scheduledTasksRepository->flush();
184
185 WordPress::resetRunInterval();
186 if ($isScheduled) {
187 // set newsletter status
188 $newsletter->setStatus(NewsletterEntity::STATUS_SCHEDULED);
189
190 // set scheduled task status
191 $scheduledTask->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
192 $scheduledTask->setScheduledAt(new Carbon($newsletter->getOptionValue('scheduledAt')));
193 } else {
194 $segments = $newsletter->getSegmentIds();
195
196 $this->scheduledTasksRepository->refresh($scheduledTask);
197 $this->subscribersFinder->addSubscribersToTaskFromSegments($scheduledTask, $segments, $newsletter->getFilterSegmentId());
198 $subscribersCount = $scheduledTask->getSubscribers()->count();
199
200 if (!$subscribersCount) {
201 return $this->errorResponse([
202 APIError::UNKNOWN => __('There are no subscribers in that list!', 'mailpoet'),
203 ]);
204 }
205
206 $this->sendingQueuesRepository->updateCounts($sendingQueue);
207 $scheduledTask->setStatus(null);
208 $scheduledTask->setScheduledAt(null);
209
210 // set newsletter status
211 $newsletter->setStatus(NewsletterEntity::STATUS_SENDING);
212 }
213 $this->scheduledTasksRepository->persist($scheduledTask);
214 $this->newsletterRepository->flush();
215
216 $this->triggerSending($newsletter);
217 return $this->successResponse(
218 ($newsletter->getLatestQueue() instanceof SendingQueueEntity) ? $this->sendingQueuesResponseBuilder->build($newsletter->getLatestQueue()) : null
219 );
220 } catch (\Exception $e) {
221 $errorCode = APIError::UNKNOWN;
222 $statusCode = Response::STATUS_NOT_FOUND;
223 if ($e->getCode() === Response::STATUS_FORBIDDEN) {
224 $errorCode = APIError::FORBIDDEN;
225 $statusCode = Response::STATUS_FORBIDDEN;
226 } elseif ($e->getCode() === Response::STATUS_BAD_REQUEST) {
227 $errorCode = APIError::BAD_REQUEST;
228 $statusCode = Response::STATUS_BAD_REQUEST;
229 }
230 return $this->errorResponse([
231 $errorCode => $e->getMessage(),
232 ], [], $statusCode);
233 }
234 }
235
236 public function pause($data = []) {
237 $newsletterId = (isset($data['newsletter_id'])
238 ? (int)$data['newsletter_id']
239 : false
240 );
241 $newsletter = $this->newsletterRepository->findOneById($newsletterId);
242
243 if ($newsletter instanceof NewsletterEntity) {
244 $queue = $newsletter->getLastUpdatedQueue();
245
246 if (!$queue instanceof SendingQueueEntity) {
247 return $this->errorResponse([
248 APIError::UNKNOWN => __('This newsletter has not been sent yet.', 'mailpoet'),
249 ]);
250 } else {
251 if ($this->timeZoneCampaignScheduler->isTimeZoneQueue($queue)) {
252 $this->timeZoneCampaignScheduler->pauseCampaign($queue);
253 } else {
254 $this->sendingQueuesRepository->pause($queue);
255 }
256 return $this->successResponse();
257 }
258 } else {
259 return $this->errorResponse([
260 APIError::NOT_FOUND => __('This newsletter does not exist.', 'mailpoet'),
261 ]);
262 }
263 }
264
265 public function resume($data = []) {
266 if ($this->subscribersFeature->check()) {
267 return $this->errorResponse([
268 APIError::FORBIDDEN => __('Subscribers limit reached.', 'mailpoet'),
269 ], [], Response::STATUS_FORBIDDEN);
270 }
271 $newsletterId = (isset($data['newsletter_id'])
272 ? (int)$data['newsletter_id']
273 : false
274 );
275 $newsletter = $this->newsletterRepository->findOneById($newsletterId);
276
277 if ($newsletter instanceof NewsletterEntity) {
278 $queue = $newsletter->getLastUpdatedQueue();
279
280 if (!$queue instanceof SendingQueueEntity) {
281 return $this->errorResponse([
282 APIError::UNKNOWN => __('This newsletter has not been sent yet.', 'mailpoet'),
283 ]);
284 } else {
285 if ($this->timeZoneCampaignScheduler->isTimeZoneQueue($queue)) {
286 $this->timeZoneCampaignScheduler->resumeCampaign($queue);
287 } else {
288 $this->sendingQueuesRepository->resume($queue);
289 }
290 $this->triggerSending($newsletter);
291 return $this->successResponse();
292 }
293 } else {
294 return $this->errorResponse([
295 APIError::NOT_FOUND => __('This newsletter does not exist.', 'mailpoet'),
296 ]);
297 }
298 }
299
300 public function pingCron() {
301 try {
302 $cronPingResponse = $this->cronHelper->pingDaemon();
303 } catch (\Exception $e) {
304 return $this->errorResponse([
305 APIError::UNKNOWN => $e->getMessage(),
306 ]);
307 }
308 if (!$this->cronHelper->validatePingResponse($cronPingResponse)) {
309 return $this->errorResponse([
310 APIError::UNKNOWN => $cronPingResponse,
311 ]);
312 }
313 return $this->successResponse();
314 }
315
316 /**
317 * In case the newsletter was switched to sending trigger the background job immediately.
318 * This is done so that user immediately sees that email is sending and doesn't have to wait on WP Cron to start it.
319 */
320 private function triggerSending(NewsletterEntity $newsletter): void {
321 if (
322 $newsletter->getStatus() === NewsletterEntity::STATUS_SENDING
323 && $this->settings->get('cron_trigger.method') === CronTrigger::METHOD_ACTION_SCHEDULER
324 ) {
325 $this->actionSchedulerDaemonTriggerAction->process();
326 }
327 }
328 }
329