diff --git a/lib/Newsletter/Scheduler/AutomaticEmailScheduler.php b/lib/Newsletter/Scheduler/AutomaticEmailScheduler.php new file mode 100644 index 0000000000..654aff70db --- /dev/null +++ b/lib/Newsletter/Scheduler/AutomaticEmailScheduler.php @@ -0,0 +1,106 @@ +event !== $event) continue; + if (is_callable($scheduling_condition) && !$scheduling_condition($newsletter)) continue; + $this->createAutomaticEmailSendingTask($newsletter, $subscriber_id, $meta); + } + } + + function scheduleOrRescheduleAutomaticEmail($group, $event, $subscriber_id, $meta = false) { + $newsletters = Scheduler::getNewsletters(Newsletter::TYPE_AUTOMATIC, $group); + if (empty($newsletters)) { + return false; + } + + foreach ($newsletters as $newsletter) { + if ($newsletter->event !== $event) { + continue; + } + + // try to find existing scheduled task for given subscriber + $task = ScheduledTask::findOneScheduledByNewsletterIdAndSubscriberId($newsletter->id, $subscriber_id); + if ($task) { + $this->rescheduleAutomaticEmailSendingTask($newsletter, $task); + } else { + $this->createAutomaticEmailSendingTask($newsletter, $subscriber_id, $meta); + } + } + } + + function rescheduleAutomaticEmail($group, $event, $subscriber_id) { + $newsletters = Scheduler::getNewsletters(Newsletter::TYPE_AUTOMATIC, $group); + if (empty($newsletters)) { + return false; + } + + foreach ($newsletters as $newsletter) { + if ($newsletter->event !== $event) { + continue; + } + + // try to find existing scheduled task for given subscriber + $task = ScheduledTask::findOneScheduledByNewsletterIdAndSubscriberId($newsletter->id, $subscriber_id); + if ($task) { + $this->rescheduleAutomaticEmailSendingTask($newsletter, $task); + } + } + } + + function cancelAutomaticEmail($group, $event, $subscriber_id) { + $newsletters = Scheduler::getNewsletters(Newsletter::TYPE_AUTOMATIC, $group); + if (empty($newsletters)) { + return false; + } + + foreach ($newsletters as $newsletter) { + if ($newsletter->event !== $event) { + continue; + } + + // try to find existing scheduled task for given subscriber + $task = ScheduledTask::findOneScheduledByNewsletterIdAndSubscriberId($newsletter->id, $subscriber_id); + if ($task) { + SendingQueue::where('task_id', $task->id)->deleteMany(); + ScheduledTaskSubscriber::where('task_id', $task->id)->deleteMany(); + $task->delete(); + } + } + } + + function createAutomaticEmailSendingTask($newsletter, $subscriber_id, $meta) { + $sending_task = SendingTask::create(); + $sending_task->newsletter_id = $newsletter->id; + if ($newsletter->sendTo === 'user' && $subscriber_id) { + $sending_task->setSubscribers([$subscriber_id]); + } + if ($meta) { + $sending_task->__set('meta', $meta); + } + $sending_task->status = SendingQueue::STATUS_SCHEDULED; + $sending_task->priority = SendingQueue::PRIORITY_MEDIUM; + + $sending_task->scheduled_at = Scheduler::getScheduledTimeWithDelay($newsletter->afterTimeType, $newsletter->afterTimeNumber); + return $sending_task->save(); + } + + private function rescheduleAutomaticEmailSendingTask($newsletter, $task) { + // compute new 'scheduled_at' from now + $task->scheduled_at = Scheduler::getScheduledTimeWithDelay($newsletter->afterTimeType, $newsletter->afterTimeNumber); + $task->save(); + } + +} diff --git a/lib/Newsletter/Scheduler/Scheduler.php b/lib/Newsletter/Scheduler/Scheduler.php index 5ba0369f32..7f2e55e267 100644 --- a/lib/Newsletter/Scheduler/Scheduler.php +++ b/lib/Newsletter/Scheduler/Scheduler.php @@ -4,107 +4,10 @@ namespace MailPoet\Newsletter\Scheduler; use Carbon\Carbon; use MailPoet\Models\Newsletter; -use MailPoet\Models\ScheduledTask; -use MailPoet\Models\ScheduledTaskSubscriber; -use MailPoet\Models\SendingQueue; -use MailPoet\Tasks\Sending as SendingTask; use MailPoet\WP\Functions as WPFunctions; class Scheduler { - static function scheduleAutomaticEmail($group, $event, $scheduling_condition = false, $subscriber_id = false, $meta = false) { - $newsletters = self::getNewsletters(Newsletter::TYPE_AUTOMATIC, $group); - if (empty($newsletters)) return false; - foreach ($newsletters as $newsletter) { - if ($newsletter->event !== $event) continue; - if (is_callable($scheduling_condition) && !$scheduling_condition($newsletter)) continue; - self::createAutomaticEmailSendingTask($newsletter, $subscriber_id, $meta); - } - } - - static function scheduleOrRescheduleAutomaticEmail($group, $event, $subscriber_id, $meta = false) { - $newsletters = self::getNewsletters(Newsletter::TYPE_AUTOMATIC, $group); - if (empty($newsletters)) { - return false; - } - - foreach ($newsletters as $newsletter) { - if ($newsletter->event !== $event) { - continue; - } - - // try to find existing scheduled task for given subscriber - $task = ScheduledTask::findOneScheduledByNewsletterIdAndSubscriberId($newsletter->id, $subscriber_id); - if ($task) { - self::rescheduleAutomaticEmailSendingTask($newsletter, $task); - } else { - self::createAutomaticEmailSendingTask($newsletter, $subscriber_id, $meta); - } - } - } - - static function rescheduleAutomaticEmail($group, $event, $subscriber_id) { - $newsletters = self::getNewsletters(Newsletter::TYPE_AUTOMATIC, $group); - if (empty($newsletters)) { - return false; - } - - foreach ($newsletters as $newsletter) { - if ($newsletter->event !== $event) { - continue; - } - - // try to find existing scheduled task for given subscriber - $task = ScheduledTask::findOneScheduledByNewsletterIdAndSubscriberId($newsletter->id, $subscriber_id); - if ($task) { - self::rescheduleAutomaticEmailSendingTask($newsletter, $task); - } - } - } - - static function cancelAutomaticEmail($group, $event, $subscriber_id) { - $newsletters = self::getNewsletters(Newsletter::TYPE_AUTOMATIC, $group); - if (empty($newsletters)) { - return false; - } - - foreach ($newsletters as $newsletter) { - if ($newsletter->event !== $event) { - continue; - } - - // try to find existing scheduled task for given subscriber - $task = ScheduledTask::findOneScheduledByNewsletterIdAndSubscriberId($newsletter->id, $subscriber_id); - if ($task) { - SendingQueue::where('task_id', $task->id)->deleteMany(); - ScheduledTaskSubscriber::where('task_id', $task->id)->deleteMany(); - $task->delete(); - } - } - } - - static function createAutomaticEmailSendingTask($newsletter, $subscriber_id, $meta) { - $sending_task = SendingTask::create(); - $sending_task->newsletter_id = $newsletter->id; - if ($newsletter->sendTo === 'user' && $subscriber_id) { - $sending_task->setSubscribers([$subscriber_id]); - } - if ($meta) { - $sending_task->__set('meta', $meta); - } - $sending_task->status = SendingQueue::STATUS_SCHEDULED; - $sending_task->priority = SendingQueue::PRIORITY_MEDIUM; - - $sending_task->scheduled_at = self::getScheduledTimeWithDelay($newsletter->afterTimeType, $newsletter->afterTimeNumber); - return $sending_task->save(); - } - - static function rescheduleAutomaticEmailSendingTask($newsletter, $task) { - // compute new 'scheduled_at' from now - $task->scheduled_at = self::getScheduledTimeWithDelay($newsletter->afterTimeType, $newsletter->afterTimeNumber); - $task->save(); - } - static function getNextRunDate($schedule, $from_timestamp = false) { $wp = new WPFunctions(); $from_timestamp = ($from_timestamp) ? $from_timestamp : $wp->currentTime('timestamp'); diff --git a/tests/integration/Newsletter/Scheduler/AutomaticEmailTest.php b/tests/integration/Newsletter/Scheduler/AutomaticEmailTest.php new file mode 100644 index 0000000000..3564111e58 --- /dev/null +++ b/tests/integration/Newsletter/Scheduler/AutomaticEmailTest.php @@ -0,0 +1,226 @@ +automatic_email_scheduler = new AutomaticEmailScheduler; + } + function testItCreatesScheduledAutomaticEmailSendingTaskForUser() { + $newsletter = $this->_createNewsletter(); + $this->_createNewsletterOptions( + $newsletter->id, + [ + 'sendTo' => 'user', + 'afterTimeType' => 'hours', + 'afterTimeNumber' => 2, + ] + ); + $newsletter = Newsletter::filter('filterWithOptions', Newsletter::TYPE_AUTOMATIC)->findOne($newsletter->id); + $subscriber = Subscriber::create(); + $subscriber->hydrate(Fixtures::get('subscriber_template')); + $subscriber->save(); + + $this->automatic_email_scheduler->createAutomaticEmailSendingTask($newsletter, $subscriber->id, $meta = null); + // new scheduled task should be created + $task = SendingTask::getByNewsletterId($newsletter->id); + $current_time = Carbon::createFromTimestamp(current_time('timestamp')); + Carbon::setTestNow($current_time); // mock carbon to return current time + expect($task->id)->greaterOrEquals(1); + expect($task->priority)->equals(SendingQueue::PRIORITY_MEDIUM); + expect($task->status)->equals(SendingQueue::STATUS_SCHEDULED); + expect(Carbon::parse($task->scheduled_at)->format('Y-m-d H:i')) + ->equals($current_time->addHours(2)->format('Y-m-d H:i')); + // task should have 1 associated user + $subscribers = $task->subscribers()->findMany(); + expect($subscribers)->count(1); + expect($subscribers[0]->id)->equals($subscriber->id); + } + + function testItAddsMetaToSendingQueueWhenCreatingAutomaticEmailSendingTask() { + $newsletter = $this->_createNewsletter(); + $this->_createNewsletterOptions( + $newsletter->id, + [ + 'sendTo' => 'user', + 'afterTimeType' => 'hours', + 'afterTimeNumber' => 2, + ] + ); + $newsletter = Newsletter::filter('filterWithOptions', Newsletter::TYPE_AUTOMATIC)->findOne($newsletter->id); + $subscriber = Subscriber::create(); + $subscriber->hydrate(Fixtures::get('subscriber_template')); + $subscriber->save(); + $meta = ['some' => 'value']; + + $this->automatic_email_scheduler->createAutomaticEmailSendingTask($newsletter, $subscriber->id, $meta); + // new queue record should be created with meta data + $queue = SendingQueue::where('newsletter_id', $newsletter->id)->findOne(); + expect($queue->getMeta())->equals($meta); + } + + function testItCreatesAutomaticEmailSendingTaskForSegment() { + $newsletter = $this->_createNewsletter(); + $this->_createNewsletterOptions( + $newsletter->id, + [ + 'sendTo' => 'segment', + 'afterTimeType' => 'hours', + 'afterTimeNumber' => 2, + ] + ); + $newsletter = Newsletter::filter('filterWithOptions', Newsletter::TYPE_AUTOMATIC)->findOne($newsletter->id); + + $this->automatic_email_scheduler->createAutomaticEmailSendingTask($newsletter, $subscriber = null, $meta = null); + // new scheduled task should be created + $task = SendingTask::getByNewsletterId($newsletter->id); + $current_time = Carbon::createFromTimestamp(current_time('timestamp')); + Carbon::setTestNow($current_time); // mock carbon to return current time + expect($task->id)->greaterOrEquals(1); + expect($task->priority)->equals(SendingQueue::PRIORITY_MEDIUM); + expect($task->status)->equals(SendingQueue::STATUS_SCHEDULED); + expect(Carbon::parse($task->scheduled_at)->format('Y-m-d H:i')) + ->equals($current_time->addHours(2)->format('Y-m-d H:i')); + // task should not have any subscribers + $subscribers = $task->subscribers()->findMany(); + expect($subscribers)->count(0); + } + + function testItDoesNotScheduleAutomaticEmailWhenGroupDoesNotMatch() { + $newsletter = $this->_createNewsletter(); + $this->_createNewsletterOptions( + $newsletter->id, + [ + 'group' => 'some_group', + 'event' => 'some_event', + 'sendTo' => 'user', + 'afterTimeType' => 'hours', + 'afterTimeNumber' => 2, + ] + ); + + // email should not be scheduled when group is not matched + $this->automatic_email_scheduler->scheduleAutomaticEmail('group_does_not_exist', 'some_event'); + expect(SendingQueue::findMany())->count(0); + } + + function testItDoesNotScheduleAutomaticEmailWhenEventDoesNotMatch() { + $newsletter = $this->_createNewsletter(); + $this->_createNewsletterOptions( + $newsletter->id, + [ + 'group' => 'some_group', + 'event' => 'some_event', + 'sendTo' => 'user', + 'afterTimeType' => 'hours', + 'afterTimeNumber' => 2, + ] + ); + + // email should not be scheduled when event is not matched + $this->automatic_email_scheduler->scheduleAutomaticEmail('some_group', 'event_does_not_exist'); + expect(SendingQueue::findMany())->count(0); + } + + function testItSchedulesAutomaticEmailWhenConditionMatches() { + $current_time = Carbon::createFromTimestamp(current_time('timestamp')); + Carbon::setTestNow($current_time); // mock carbon to return current time + $newsletter_1 = $this->_createNewsletter(); + $this->_createNewsletterOptions( + $newsletter_1->id, + [ + 'group' => 'some_group', + 'event' => 'some_event', + 'sendTo' => 'user', + 'afterTimeType' => 'hours', + 'afterTimeNumber' => 2, + ] + ); + $newsletter_2 = $this->_createNewsletter(); + $this->_createNewsletterOptions( + $newsletter_2->id, + [ + 'group' => 'some_group', + 'event' => 'some_event', + 'sendTo' => 'segment', + 'afterTimeType' => 'hours', + 'afterTimeNumber' => 2, + ] + ); + $condition = function($email) { + return $email->sendTo === 'segment'; + }; + + // email should only be scheduled if it matches condition ("send to segment") + $this->automatic_email_scheduler->scheduleAutomaticEmail('some_group', 'some_event', $condition); + $result = SendingQueue::findMany(); + expect($result)->count(1); + expect($result[0]->newsletter_id)->equals($newsletter_2->id); + // scheduled task should be created + $task = $result[0]->getTasks()->findOne(); + expect($task->id)->greaterOrEquals(1); + expect($task->priority)->equals(SendingQueue::PRIORITY_MEDIUM); + expect($task->status)->equals(SendingQueue::STATUS_SCHEDULED); + expect(Carbon::parse($task->scheduled_at)->format('Y-m-d H:i')) + ->equals($current_time->addHours(2)->format('Y-m-d H:i')); + } + + private function _createNewsletter() { + $newsletter = Newsletter::create(); + $newsletter->type = Newsletter::TYPE_AUTOMATIC; + $newsletter->status = Newsletter::STATUS_ACTIVE; + $newsletter->save(); + expect($newsletter->getErrors())->false(); + return $newsletter; + } + + private function _createNewsletterOptions($newsletter_id, $options) { + foreach ($options as $option => $value) { + $newsletter_option_field = NewsletterOptionField::where('name', $option)->findOne(); + if (!$newsletter_option_field) { + $newsletter_option_field = NewsletterOptionField::create(); + $newsletter_option_field->name = $option; + $newsletter_option_field->newsletter_type = Newsletter::TYPE_AUTOMATIC; + $newsletter_option_field->save(); + expect($newsletter_option_field->getErrors())->false(); + } + + $newsletter_option = NewsletterOption::create(); + $newsletter_option->option_field_id = $newsletter_option_field->id; + $newsletter_option->newsletter_id = $newsletter_id; + $newsletter_option->value = $value; + $newsletter_option->save(); + expect($newsletter_option->getErrors())->false(); + } + } + + function _after() { + Carbon::setTestNow(); + \ORM::raw_execute('TRUNCATE ' . Newsletter::$_table); + \ORM::raw_execute('TRUNCATE ' . NewsletterOption::$_table); + \ORM::raw_execute('TRUNCATE ' . NewsletterOptionField::$_table); + \ORM::raw_execute('TRUNCATE ' . NewsletterPost::$_table); + \ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table); + \ORM::raw_execute('TRUNCATE ' . ScheduledTaskSubscriber::$_table); + \ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table); + \ORM::raw_execute('TRUNCATE ' . Subscriber::$_table); + } +} diff --git a/tests/integration/Newsletter/Scheduler/SchedulerTest.php b/tests/integration/Newsletter/Scheduler/SchedulerTest.php index 95c21987ce..c55c6ed038 100644 --- a/tests/integration/Newsletter/Scheduler/SchedulerTest.php +++ b/tests/integration/Newsletter/Scheduler/SchedulerTest.php @@ -67,172 +67,6 @@ class SchedulerTest extends \MailPoetTest { ->equals('2016-04-20 16:00:00'); } - function testItCreatesScheduledAutomaticEmailSendingTaskForUser() { - $newsletter = $this->_createNewsletter(Newsletter::TYPE_AUTOMATIC); - $this->_createNewsletterOptions( - $newsletter->id, - Newsletter::TYPE_AUTOMATIC, - [ - 'sendTo' => 'user', - 'afterTimeType' => 'hours', - 'afterTimeNumber' => 2, - ] - ); - $newsletter = Newsletter::filter('filterWithOptions', Newsletter::TYPE_AUTOMATIC)->findOne($newsletter->id); - $subscriber = Subscriber::create(); - $subscriber->hydrate(Fixtures::get('subscriber_template')); - $subscriber->save(); - - Scheduler::createAutomaticEmailSendingTask($newsletter, $subscriber->id, $meta = null); - // new scheduled task should be created - $task = SendingTask::getByNewsletterId($newsletter->id); - $current_time = Carbon::createFromTimestamp(current_time('timestamp')); - Carbon::setTestNow($current_time); // mock carbon to return current time - expect($task->id)->greaterOrEquals(1); - expect($task->priority)->equals(SendingQueue::PRIORITY_MEDIUM); - expect($task->status)->equals(SendingQueue::STATUS_SCHEDULED); - expect(Carbon::parse($task->scheduled_at)->format('Y-m-d H:i')) - ->equals($current_time->addHours(2)->format('Y-m-d H:i')); - // task should have 1 associated user - $subscribers = $task->subscribers()->findMany(); - expect($subscribers)->count(1); - expect($subscribers[0]->id)->equals($subscriber->id); - } - - function testItAddsMetaToSendingQueueWhenCreatingAutomaticEmailSendingTask() { - $newsletter = $this->_createNewsletter(Newsletter::TYPE_AUTOMATIC); - $this->_createNewsletterOptions( - $newsletter->id, - Newsletter::TYPE_AUTOMATIC, - [ - 'sendTo' => 'user', - 'afterTimeType' => 'hours', - 'afterTimeNumber' => 2, - ] - ); - $newsletter = Newsletter::filter('filterWithOptions', Newsletter::TYPE_AUTOMATIC)->findOne($newsletter->id); - $subscriber = Subscriber::create(); - $subscriber->hydrate(Fixtures::get('subscriber_template')); - $subscriber->save(); - $meta = ['some' => 'value']; - - Scheduler::createAutomaticEmailSendingTask($newsletter, $subscriber->id, $meta); - // new queue record should be created with meta data - $queue = SendingQueue::where('newsletter_id', $newsletter->id)->findOne(); - expect($queue->getMeta())->equals($meta); - } - - function testItCreatesAutomaticEmailSendingTaskForSegment() { - $newsletter = $this->_createNewsletter(Newsletter::TYPE_AUTOMATIC); - $this->_createNewsletterOptions( - $newsletter->id, - Newsletter::TYPE_AUTOMATIC, - [ - 'sendTo' => 'segment', - 'afterTimeType' => 'hours', - 'afterTimeNumber' => 2, - ] - ); - $newsletter = Newsletter::filter('filterWithOptions', Newsletter::TYPE_AUTOMATIC)->findOne($newsletter->id); - - Scheduler::createAutomaticEmailSendingTask($newsletter, $subscriber = null, $meta = null); - // new scheduled task should be created - $task = SendingTask::getByNewsletterId($newsletter->id); - $current_time = Carbon::createFromTimestamp(current_time('timestamp')); - Carbon::setTestNow($current_time); // mock carbon to return current time - expect($task->id)->greaterOrEquals(1); - expect($task->priority)->equals(SendingQueue::PRIORITY_MEDIUM); - expect($task->status)->equals(SendingQueue::STATUS_SCHEDULED); - expect(Carbon::parse($task->scheduled_at)->format('Y-m-d H:i')) - ->equals($current_time->addHours(2)->format('Y-m-d H:i')); - // task should not have any subscribers - $subscribers = $task->subscribers()->findMany(); - expect($subscribers)->count(0); - } - - function testItDoesNotScheduleAutomaticEmailWhenGroupDoesNotMatch() { - $newsletter = $this->_createNewsletter(Newsletter::TYPE_AUTOMATIC); - $this->_createNewsletterOptions( - $newsletter->id, - Newsletter::TYPE_AUTOMATIC, - [ - 'group' => 'some_group', - 'event' => 'some_event', - 'sendTo' => 'user', - 'afterTimeType' => 'hours', - 'afterTimeNumber' => 2, - ] - ); - - // email should not be scheduled when group is not matched - Scheduler::scheduleAutomaticEmail('group_does_not_exist', 'some_event'); - expect(SendingQueue::findMany())->count(0); - } - - function testItDoesNotScheduleAutomaticEmailWhenEventDoesNotMatch() { - $newsletter = $this->_createNewsletter(Newsletter::TYPE_AUTOMATIC); - $this->_createNewsletterOptions( - $newsletter->id, - Newsletter::TYPE_AUTOMATIC, - [ - 'group' => 'some_group', - 'event' => 'some_event', - 'sendTo' => 'user', - 'afterTimeType' => 'hours', - 'afterTimeNumber' => 2, - ] - ); - - // email should not be scheduled when event is not matched - Scheduler::scheduleAutomaticEmail('some_group', 'event_does_not_exist'); - expect(SendingQueue::findMany())->count(0); - } - - function testItSchedulesAutomaticEmailWhenConditionMatches() { - $current_time = Carbon::createFromTimestamp(current_time('timestamp')); - Carbon::setTestNow($current_time); // mock carbon to return current time - $newsletter_1 = $this->_createNewsletter(Newsletter::TYPE_AUTOMATIC); - $this->_createNewsletterOptions( - $newsletter_1->id, - Newsletter::TYPE_AUTOMATIC, - [ - 'group' => 'some_group', - 'event' => 'some_event', - 'sendTo' => 'user', - 'afterTimeType' => 'hours', - 'afterTimeNumber' => 2, - ] - ); - $newsletter_2 = $this->_createNewsletter(Newsletter::TYPE_AUTOMATIC); - $this->_createNewsletterOptions( - $newsletter_2->id, - Newsletter::TYPE_AUTOMATIC, - [ - 'group' => 'some_group', - 'event' => 'some_event', - 'sendTo' => 'segment', - 'afterTimeType' => 'hours', - 'afterTimeNumber' => 2, - ] - ); - $condition = function($email) { - return $email->sendTo === 'segment'; - }; - - // email should only be scheduled if it matches condition ("send to segment") - Scheduler::scheduleAutomaticEmail('some_group', 'some_event', $condition); - $result = SendingQueue::findMany(); - expect($result)->count(1); - expect($result[0]->newsletter_id)->equals($newsletter_2->id); - // scheduled task should be created - $task = $result[0]->getTasks()->findOne(); - expect($task->id)->greaterOrEquals(1); - expect($task->priority)->equals(SendingQueue::PRIORITY_MEDIUM); - expect($task->status)->equals(SendingQueue::STATUS_SCHEDULED); - expect(Carbon::parse($task->scheduled_at)->format('Y-m-d H:i')) - ->equals($current_time->addHours(2)->format('Y-m-d H:i')); - } - private function _createNewsletter( $type = Newsletter::TYPE_NOTIFICATION, $status = Newsletter::STATUS_ACTIVE