Geen omschrijving

Scheduler.php 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <?php
  2. namespace MailPoet\Cron\Workers;
  3. if (!defined('ABSPATH')) exit;
  4. use MailPoet\Cron\CronHelper;
  5. use MailPoet\Logging\LoggerFactory;
  6. use MailPoet\Models\Newsletter;
  7. use MailPoet\Models\ScheduledTask;
  8. use MailPoet\Models\Segment;
  9. use MailPoet\Models\Subscriber;
  10. use MailPoet\Models\SubscriberSegment;
  11. use MailPoet\Newsletter\Scheduler\PostNotificationScheduler;
  12. use MailPoet\Newsletter\Scheduler\Scheduler as NewsletterScheduler;
  13. use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
  14. use MailPoet\Segments\SubscribersFinder;
  15. use MailPoet\Tasks\Sending as SendingTask;
  16. class Scheduler {
  17. const TASK_BATCH_SIZE = 5;
  18. /** @var SubscribersFinder */
  19. private $subscribersFinder;
  20. /** @var LoggerFactory */
  21. private $loggerFactory;
  22. /** @var CronHelper */
  23. private $cronHelper;
  24. public function __construct(
  25. SubscribersFinder $subscribersFinder,
  26. LoggerFactory $loggerFactory,
  27. CronHelper $cronHelper
  28. ) {
  29. $this->cronHelper = $cronHelper;
  30. $this->subscribersFinder = $subscribersFinder;
  31. $this->loggerFactory = $loggerFactory;
  32. }
  33. public function process($timer = false) {
  34. $timer = $timer ?: microtime(true);
  35. // abort if execution limit is reached
  36. $this->cronHelper->enforceExecutionLimit($timer);
  37. $scheduledQueues = self::getScheduledQueues();
  38. if (!count($scheduledQueues)) return false;
  39. $this->updateTasks($scheduledQueues);
  40. foreach ($scheduledQueues as $i => $queue) {
  41. $newsletter = Newsletter::findOneWithOptions($queue->newsletterId);
  42. if (!$newsletter || $newsletter->deletedAt !== null) {
  43. $queue->delete();
  44. } elseif ($newsletter->status !== Newsletter::STATUS_ACTIVE && $newsletter->status !== Newsletter::STATUS_SCHEDULED) {
  45. continue;
  46. } elseif ($newsletter->type === Newsletter::TYPE_WELCOME) {
  47. $this->processWelcomeNewsletter($newsletter, $queue);
  48. } elseif ($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
  49. $this->processPostNotificationNewsletter($newsletter, $queue);
  50. } elseif ($newsletter->type === Newsletter::TYPE_STANDARD) {
  51. $this->processScheduledStandardNewsletter($newsletter, $queue);
  52. } elseif ($newsletter->type === Newsletter::TYPE_AUTOMATIC) {
  53. $this->processScheduledAutomaticEmail($newsletter, $queue);
  54. }
  55. $this->cronHelper->enforceExecutionLimit($timer);
  56. }
  57. }
  58. public function processWelcomeNewsletter($newsletter, $queue) {
  59. $subscribers = $queue->getSubscribers();
  60. if (empty($subscribers[0])) {
  61. $queue->delete();
  62. return false;
  63. }
  64. $subscriberId = (int)$subscribers[0];
  65. if ($newsletter->event === 'segment') {
  66. if ($this->verifyMailpoetSubscriber($subscriberId, $newsletter, $queue) === false) {
  67. return false;
  68. }
  69. } else {
  70. if ($newsletter->event === 'user') {
  71. if ($this->verifyWPSubscriber($subscriberId, $newsletter, $queue) === false) {
  72. return false;
  73. }
  74. }
  75. }
  76. $queue->status = null;
  77. $queue->save();
  78. return true;
  79. }
  80. public function processPostNotificationNewsletter($newsletter, $queue) {
  81. $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->addInfo(
  82. 'process post notification in scheduler',
  83. ['newsletter_id' => $newsletter->id, 'task_id' => $queue->taskId]
  84. );
  85. // ensure that segments exist
  86. $segments = $newsletter->segments()->findMany();
  87. if (empty($segments)) {
  88. $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->addInfo(
  89. 'post notification no segments',
  90. ['newsletter_id' => $newsletter->id, 'task_id' => $queue->taskId]
  91. );
  92. return $this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
  93. }
  94. // ensure that subscribers are in segments
  95. $subscribersCount = $this->subscribersFinder->addSubscribersToTaskFromSegments($queue->task(), $segments);
  96. if (empty($subscribersCount)) {
  97. $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->addInfo(
  98. 'post notification no subscribers',
  99. ['newsletter_id' => $newsletter->id, 'task_id' => $queue->taskId]
  100. );
  101. return $this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
  102. }
  103. // create a duplicate newsletter that acts as a history record
  104. $notificationHistory = $this->createNotificationHistory($newsletter->id);
  105. if (!$notificationHistory) return false;
  106. // queue newsletter for delivery
  107. $queue->newsletterId = $notificationHistory->id;
  108. $queue->status = null;
  109. $queue->save();
  110. // update notification status
  111. $notificationHistory->setStatus(Newsletter::STATUS_SENDING);
  112. $this->loggerFactory->getLogger(LoggerFactory::TOPIC_POST_NOTIFICATIONS)->addInfo(
  113. 'post notification set status to sending',
  114. ['newsletter_id' => $newsletter->id, 'task_id' => $queue->taskId]
  115. );
  116. return true;
  117. }
  118. public function processScheduledAutomaticEmail($newsletter, $queue) {
  119. if ($newsletter->sendTo === 'segment') {
  120. $segment = Segment::findOne($newsletter->segment);
  121. $result = $this->subscribersFinder->addSubscribersToTaskFromSegments($queue->task(), [$segment]);
  122. if (empty($result)) {
  123. $queue->delete();
  124. return false;
  125. }
  126. } else {
  127. $subscribers = $queue->getSubscribers();
  128. $subscriber = (!empty($subscribers) && is_array($subscribers)) ?
  129. Subscriber::findOne($subscribers[0]) :
  130. false;
  131. if (!$subscriber) {
  132. $queue->delete();
  133. return false;
  134. }
  135. if ($this->verifySubscriber($subscriber, $queue) === false) {
  136. return false;
  137. }
  138. }
  139. $queue->status = null;
  140. $queue->save();
  141. return true;
  142. }
  143. public function processScheduledStandardNewsletter($newsletter, SendingTask $task) {
  144. $segments = $newsletter->segments()->findMany();
  145. $this->subscribersFinder->addSubscribersToTaskFromSegments($task->task(), $segments);
  146. // update current queue
  147. $task->updateCount();
  148. $task->status = null;
  149. $task->save();
  150. // update newsletter status
  151. $newsletter->setStatus(Newsletter::STATUS_SENDING);
  152. return true;
  153. }
  154. public function verifyMailpoetSubscriber($subscriberId, $newsletter, $queue) {
  155. $subscriber = Subscriber::findOne($subscriberId);
  156. // check if subscriber is in proper segment
  157. $subscriberInSegment =
  158. SubscriberSegment::where('subscriber_id', $subscriberId)
  159. ->where('segment_id', $newsletter->segment)
  160. ->where('status', 'subscribed')
  161. ->findOne();
  162. if (!$subscriber || !$subscriberInSegment) {
  163. $queue->delete();
  164. return false;
  165. }
  166. return $this->verifySubscriber($subscriber, $queue);
  167. }
  168. public function verifyWPSubscriber($subscriberId, $newsletter, $queue) {
  169. // check if user has the proper role
  170. $subscriber = Subscriber::findOne($subscriberId);
  171. if (!$subscriber || $subscriber->isWPUser() === false) {
  172. $queue->delete();
  173. return false;
  174. }
  175. $wpUser = get_userdata($subscriber->wpUserId);
  176. if ($wpUser === false) {
  177. $queue->delete();
  178. return false;
  179. }
  180. if ($newsletter->role !== WelcomeScheduler::WORDPRESS_ALL_ROLES
  181. && !in_array($newsletter->role, ((array)$wpUser)['roles'])
  182. ) {
  183. $queue->delete();
  184. return false;
  185. }
  186. return $this->verifySubscriber($subscriber, $queue);
  187. }
  188. public function verifySubscriber($subscriber, $queue) {
  189. if ($subscriber->status === Subscriber::STATUS_UNCONFIRMED) {
  190. // reschedule delivery
  191. $queue->rescheduleProgressively();
  192. return false;
  193. } else if ($subscriber->status === Subscriber::STATUS_UNSUBSCRIBED) {
  194. $queue->delete();
  195. return false;
  196. }
  197. return true;
  198. }
  199. public function deleteQueueOrUpdateNextRunDate($queue, $newsletter) {
  200. if ($newsletter->intervalType === PostNotificationScheduler::INTERVAL_IMMEDIATELY) {
  201. $queue->delete();
  202. return;
  203. } else {
  204. $nextRunDate = NewsletterScheduler::getNextRunDate($newsletter->schedule);
  205. if (!$nextRunDate) {
  206. $queue->delete();
  207. return;
  208. }
  209. $queue->scheduledAt = $nextRunDate;
  210. $queue->save();
  211. }
  212. }
  213. public function createNotificationHistory($newsletterId) {
  214. $newsletter = Newsletter::findOne($newsletterId);
  215. if (!$newsletter instanceof Newsletter) {
  216. return false;
  217. }
  218. $notificationHistory = $newsletter->createNotificationHistory();
  219. return ($notificationHistory->getErrors() === false) ?
  220. $notificationHistory :
  221. false;
  222. }
  223. private function updateTasks(array $scheduledQueues) {
  224. $ids = array_map(function ($queue) {
  225. return $queue->taskId;
  226. }, $scheduledQueues);
  227. ScheduledTask::touchAllByIds($ids);
  228. }
  229. public static function getScheduledQueues() {
  230. return SendingTask::getScheduledQueues(self::TASK_BATCH_SIZE);
  231. }
  232. }