PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
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 / Newsletter / Scheduler / PostNotificationScheduler.php
mailpoet / lib / Newsletter / Scheduler Last commit date
AutomaticEmailScheduler.php 2 years ago AutomationEmailScheduler.php 1 year ago LatestNewsletterScheduler.php 2 months ago PostNotificationScheduler.php 2 months ago ReEngagementScheduler.php 5 days ago Scheduler.php 1 year ago WelcomeScheduler.php 2 years ago index.php 3 years ago
PostNotificationScheduler.php
240 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Newsletter\Scheduler;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Cron\Workers\SendingQueue\SendingQueue;
9 use MailPoet\Entities\NewsletterEntity;
10 use MailPoet\Entities\NewsletterOptionEntity;
11 use MailPoet\Entities\NewsletterOptionFieldEntity;
12 use MailPoet\Entities\ScheduledTaskEntity;
13 use MailPoet\Entities\SendingQueueEntity;
14 use MailPoet\Logging\LoggerFactory;
15 use MailPoet\Newsletter\NewsletterPostsRepository;
16 use MailPoet\Newsletter\NewslettersRepository;
17 use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository;
18 use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
19 use MailPoet\Newsletter\Sending\ScheduledTasksRepository;
20 use MailPoet\Newsletter\Sending\SendingQueuesRepository;
21 use MailPoet\WP\DateTime;
22 use MailPoet\WP\Posts;
23
24 class PostNotificationScheduler {
25
26 const SECONDS_IN_MINUTE = 60;
27 const SECONDS_IN_HOUR = 3600;
28 const LAST_WEEKDAY_FORMAT = 'L';
29 const INTERVAL_DAILY = 'daily';
30 const INTERVAL_IMMEDIATELY = 'immediately';
31 const INTERVAL_NTHWEEKDAY = 'nthWeekDay';
32 const INTERVAL_WEEKLY = 'weekly';
33 const INTERVAL_IMMEDIATE = 'immediate';
34 const INTERVAL_MONTHLY = 'monthly';
35
36 /** @var LoggerFactory */
37 private $loggerFactory;
38
39 /** @var NewslettersRepository */
40 private $newslettersRepository;
41
42 /** @var NewsletterOptionsRepository */
43 private $newsletterOptionsRepository;
44
45 /** @var NewsletterOptionFieldsRepository */
46 private $newsletterOptionFieldsRepository;
47
48 /** @var NewsletterPostsRepository */
49 private $newsletterPostsRepository;
50
51 /** @var Scheduler */
52 private $scheduler;
53
54 /*** @var ScheduledTasksRepository */
55 private $scheduledTasksRepository;
56
57 /*** @var SendingQueuesRepository */
58 private $sendingQueuesRepository;
59
60 public function __construct(
61 NewslettersRepository $newslettersRepository,
62 NewsletterOptionsRepository $newsletterOptionsRepository,
63 NewsletterOptionFieldsRepository $newsletterOptionFieldsRepository,
64 NewsletterPostsRepository $newsletterPostsRepository,
65 Scheduler $scheduler,
66 ScheduledTasksRepository $scheduledTasksRepository,
67 SendingQueuesRepository $sendingQueuesRepository
68 ) {
69 $this->loggerFactory = LoggerFactory::getInstance();
70 $this->newslettersRepository = $newslettersRepository;
71 $this->newsletterOptionsRepository = $newsletterOptionsRepository;
72 $this->newsletterOptionFieldsRepository = $newsletterOptionFieldsRepository;
73 $this->newsletterPostsRepository = $newsletterPostsRepository;
74 $this->scheduler = $scheduler;
75 $this->scheduledTasksRepository = $scheduledTasksRepository;
76 $this->sendingQueuesRepository = $sendingQueuesRepository;
77 }
78
79 public function transitionHook($newStatus, $oldStatus, $post) {
80 $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
81 'transition post notification hook initiated',
82 [
83 'post_id' => $post->ID,
84 'new_status' => $newStatus,
85 'old_status' => $oldStatus,
86 ]
87 );
88 $types = Posts::getTypes();
89 if (($newStatus !== 'publish') || $oldStatus === 'publish' || !isset($types[$post->post_type])) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
90 return;
91 }
92 $this->schedulePostNotification($post->ID);
93 }
94
95 public function schedulePostNotification($postId) {
96 $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
97 'schedule post notification hook',
98 ['post_id' => $postId]
99 );
100 $newsletters = $this->newslettersRepository->findActiveByTypes([NewsletterEntity::TYPE_NOTIFICATION]);
101 $this->newslettersRepository->prefetchOptions($newsletters);
102 if (!count($newsletters)) {
103 return false;
104 }
105 foreach ($newsletters as $newsletter) {
106 $post = $this->newsletterPostsRepository->findOneBy([
107 'newsletter' => $newsletter,
108 'postId' => $postId,
109 ]);
110 if ($post === null) {
111 $this->createPostNotificationSendingTask($newsletter);
112 }
113 }
114 }
115
116 public function createPostNotificationSendingTask(NewsletterEntity $newsletter): ?ScheduledTaskEntity {
117 $notificationHistory = $this->newslettersRepository->findSendingNotificationHistoryWithoutPausedOrInvalidTask($newsletter);
118 if (count($notificationHistory) > 0) {
119 return null;
120 }
121
122 $scheduleOption = $newsletter->getOption(NewsletterOptionFieldEntity::NAME_SCHEDULE);
123 if (!$scheduleOption) {
124 return null;
125 }
126 $nextRunDate = $this->scheduler->getNextRunDateTime($scheduleOption->getValue());
127 if (!$nextRunDate) {
128 return null;
129 }
130
131 // do not schedule duplicate queues for the same time
132 $lastQueue = $newsletter->getLatestQueue();
133 $task = $lastQueue !== null ? $lastQueue->getTask() : null;
134 $scheduledAt = $task !== null ? $task->getScheduledAt() : null;
135 if ($scheduledAt && $scheduledAt->format('Y-m-d H:i:s') === $nextRunDate->format('Y-m-d H:i:s')) {
136 return null;
137 }
138
139 $scheduledTask = new ScheduledTaskEntity();
140 $scheduledTask->setType(SendingQueue::TASK_TYPE);
141 $scheduledTask->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
142 $scheduledTask->setScheduledAt($nextRunDate);
143 $scheduledTask->setPriority(ScheduledTaskEntity::PRIORITY_MEDIUM);
144 $this->scheduledTasksRepository->persist($scheduledTask);
145 $this->scheduledTasksRepository->flush();
146
147 $sendingQueue = new SendingQueueEntity();
148 $sendingQueue->setNewsletter($newsletter);
149 $sendingQueue->setTask($scheduledTask);
150 $this->sendingQueuesRepository->persist($sendingQueue);
151 $this->sendingQueuesRepository->flush();
152 $scheduledTask->setSendingQueue($sendingQueue);
153
154 $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->info(
155 'schedule post notification',
156 [
157 'sending_task' => $scheduledTask->getId(),
158 'scheduled_at' => $nextRunDate->format(DateTime::DEFAULT_DATE_TIME_FORMAT),
159 ]
160 );
161 return $scheduledTask;
162 }
163
164 public function processPostNotificationSchedule(NewsletterEntity $newsletter) {
165 $intervalTypeOption = $newsletter->getOption(NewsletterOptionFieldEntity::NAME_INTERVAL_TYPE);
166 $intervalType = $intervalTypeOption ? $intervalTypeOption->getValue() : null;
167
168 $timeOfDayOption = $newsletter->getOption(NewsletterOptionFieldEntity::NAME_TIME_OF_DAY);
169 $hour = $timeOfDayOption ? (int)floor((int)$timeOfDayOption->getValue() / self::SECONDS_IN_HOUR) : null;
170 $minute = $timeOfDayOption ? ((int)$timeOfDayOption->getValue() - (int)($hour * self::SECONDS_IN_HOUR)) / self::SECONDS_IN_MINUTE : null;
171
172 $weekDayOption = $newsletter->getOption(NewsletterOptionFieldEntity::NAME_WEEK_DAY);
173 $weekDay = $weekDayOption ? $weekDayOption->getValue() : null;
174 $weekDay = $this->normalizeCronList($weekDay, range(0, 6), '1');
175
176 $monthDayOption = $newsletter->getOption(NewsletterOptionFieldEntity::NAME_MONTH_DAY);
177 $monthDay = $monthDayOption ? $monthDayOption->getValue() : null;
178 $monthDay = $this->normalizeCronList($monthDay, range(1, 28), '1');
179
180 $nthWeekDayOption = $newsletter->getOption(NewsletterOptionFieldEntity::NAME_NTH_WEEK_DAY);
181 $nthWeekDay = $nthWeekDayOption ? $nthWeekDayOption->getValue() : null;
182 $nthWeekDay = ($nthWeekDay === self::LAST_WEEKDAY_FORMAT) ? $nthWeekDay : '#' . $nthWeekDay;
183 switch ($intervalType) {
184 case self::INTERVAL_IMMEDIATE:
185 case self::INTERVAL_DAILY:
186 $schedule = sprintf('%s %s * * *', $minute, $hour);
187 break;
188 case self::INTERVAL_WEEKLY:
189 $schedule = sprintf('%s %s * * %s', $minute, $hour, $weekDay);
190 break;
191 case self::INTERVAL_NTHWEEKDAY:
192 // nthWeekDay schedules a single weekday (e.g. "every 1st Monday"),
193 // so reduce the normalized list to its first entry.
194 $firstWeekDay = explode(',', $weekDay)[0];
195 $schedule = sprintf('%s %s ? * %s%s', $minute, $hour, $firstWeekDay, $nthWeekDay);
196 break;
197 case self::INTERVAL_MONTHLY:
198 $schedule = sprintf('%s %s %s * *', $minute, $hour, $monthDay);
199 break;
200 case self::INTERVAL_IMMEDIATELY:
201 default:
202 $schedule = '* * * * *';
203 break;
204 }
205 $optionField = $this->newsletterOptionFieldsRepository->findOneBy([
206 'name' => NewsletterOptionFieldEntity::NAME_SCHEDULE,
207 ]);
208 if (!$optionField instanceof NewsletterOptionFieldEntity) {
209 throw new \Exception('NewsletterOptionField for schedule doesn’t exist.');
210 }
211 $scheduleOption = $newsletter->getOption(NewsletterOptionFieldEntity::NAME_SCHEDULE);
212 if ($scheduleOption === null) {
213 $scheduleOption = new NewsletterOptionEntity($newsletter, $optionField);
214 $newsletter->getOptions()->add($scheduleOption);
215 }
216 $scheduleOption->setValue($schedule);
217 $this->newsletterOptionsRepository->persist($scheduleOption);
218 $this->newsletterOptionsRepository->flush();
219 return $scheduleOption->getValue();
220 }
221
222 private function normalizeCronList($value, array $allowedValues, string $defaultValue): string {
223 $allowedValues = array_map('strval', $allowedValues);
224 $selectedValues = array_filter(
225 array_map('trim', explode(',', (string)$value)),
226 function ($selectedValue) use ($allowedValues) {
227 return in_array($selectedValue, $allowedValues, true);
228 }
229 );
230 $selectedValues = array_unique($selectedValues);
231 sort($selectedValues, SORT_NUMERIC);
232
233 if (empty($selectedValues)) {
234 return $defaultValue;
235 }
236
237 return implode(',', $selectedValues);
238 }
239 }
240