diff --git a/lib/API/JSON/v1/Newsletters.php b/lib/API/JSON/v1/Newsletters.php index 282b8e5bb4..34d52060ab 100644 --- a/lib/API/JSON/v1/Newsletters.php +++ b/lib/API/JSON/v1/Newsletters.php @@ -1,6 +1,7 @@ findOne($id); if($newsletter === false) { return $this->errorResponse(array( @@ -147,11 +148,22 @@ class Newsletters extends APIEndpoint { if(!empty($errors)) { return $this->errorResponse($errors); - } else { - return $this->successResponse( - Newsletter::findOne($newsletter->id)->asArray() - ); } + + // if there are past due notifications, reschedule them for the next send date + if($newsletter->type === Newsletter::TYPE_NOTIFICATION && $status === Newsletter::STATUS_ACTIVE) { + $next_run_date = Scheduler::getNextRunDate($newsletter->schedule); + $newsletter->queue() + ->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp'))) + ->where('status', SendingQueue::STATUS_SCHEDULED) + ->findResultSet() + ->set('scheduled_at', $next_run_date) + ->save(); + } + + return $this->successResponse( + Newsletter::findOne($newsletter->id)->asArray() + ); } function restore($data = array()) { diff --git a/lib/Newsletter/Scheduler/Scheduler.php b/lib/Newsletter/Scheduler/Scheduler.php index 2a825b1ae7..2905530894 100644 --- a/lib/Newsletter/Scheduler/Scheduler.php +++ b/lib/Newsletter/Scheduler/Scheduler.php @@ -176,6 +176,18 @@ class Scheduler { return $next_run_date; } + static function getPreviousRunDate($schedule, $from_timestamp = false) { + $from_timestamp = ($from_timestamp) ? $from_timestamp : current_time('timestamp'); + try { + $schedule = \Cron\CronExpression::factory($schedule); + $previous_run_date = $schedule->getPreviousRunDate(Carbon::createFromTimestamp($from_timestamp)) + ->format('Y-m-d H:i:s'); + } catch(\Exception $e) { + $previous_run_date = false; + } + return $previous_run_date; + } + static function getNewsletters($type) { return Newsletter::getPublished() ->filter('filterType', $type) diff --git a/tests/unit/API/JSON/v1/NewslettersTest.php b/tests/unit/API/JSON/v1/NewslettersTest.php index 7997da2d69..98b4a1eab2 100644 --- a/tests/unit/API/JSON/v1/NewslettersTest.php +++ b/tests/unit/API/JSON/v1/NewslettersTest.php @@ -8,6 +8,7 @@ use Helper\WordPressHooks as WPHooksHelper; use MailPoet\API\JSON\v1\Newsletters; use MailPoet\API\JSON\Response as APIResponse; use MailPoet\Models\Newsletter; +use MailPoet\Models\NewsletterOption; use MailPoet\Models\NewsletterOptionField; use MailPoet\Models\NewsletterSegment; use MailPoet\Models\Segment; @@ -140,7 +141,6 @@ class NewslettersTest extends \MailPoetTest { expect(WPHooksHelper::isActionDone($hook_name))->true(); expect(WPHooksHelper::getActionDone($hook_name)[0] instanceof Newsletter)->true(); - $invalid_data = array( 'subject' => 'Missing newsletter type' ); @@ -357,6 +357,51 @@ class NewslettersTest extends \MailPoetTest { ->equals('This newsletter does not exist.'); } + function testItReschedulesPastDuePostNotificationsWhenStatusIsSetBackToActive() { + $newsletter_option_field = NewsletterOptionField::create(); + $newsletter_option_field->name = 'schedule'; + $newsletter_option_field->newsletter_type = Newsletter::TYPE_NOTIFICATION; + $newsletter_option_field->save(); + $schedule = sprintf('0 %d * * *', Carbon::createFromTimestamp(current_time('timestamp'))->hour); // every day at current hour + $random_future_date = Carbon::createFromTimestamp(current_time('timestamp'))->addDays(10)->format('Y-m-d H:i:s'); // 10 days from now + $newsletter_option = NewsletterOption::createOrUpdate( + array( + 'newsletter_id' => $this->post_notification->id, + 'option_field_id' => $newsletter_option_field->id, + 'value' => $schedule + ) + ); + $sending_queue_1 = SendingQueue::create(); + $sending_queue_1->newsletter_id = $this->post_notification->id; + $sending_queue_1->scheduled_at = Scheduler::getPreviousRunDate($schedule); + $sending_queue_1->status = SendingQueue::STATUS_SCHEDULED; + $sending_queue_1->save(); + $sending_queue_2 = SendingQueue::create(); + $sending_queue_2->newsletter_id = $this->post_notification->id; + $sending_queue_2->scheduled_at = $random_future_date; + $sending_queue_2->status = SendingQueue::STATUS_SCHEDULED; + $sending_queue_2->save(); + $sending_queue_3 = SendingQueue::create(); + $sending_queue_3->newsletter_id = $this->post_notification->id; + $sending_queue_3->scheduled_at = Scheduler::getPreviousRunDate($schedule); + $sending_queue_3->save(); + + $router = new Newsletters(); + $response = $router->setStatus( + array( + 'id' => $this->post_notification->id, + 'status' => Newsletter::STATUS_ACTIVE + ) + ); + $sending_queues = SendingQueue::findMany(); + // previously scheduled notification is rescheduled for future date + expect($sending_queues[0]->scheduled_at)->equals(Scheduler::getNextRunDate($schedule)); + // future scheduled notifications are left intact + expect($sending_queues[1]->scheduled_at)->equals($random_future_date); + // previously unscheduled (e.g., sent/sending) notifications are left intact + expect($sending_queues[2]->scheduled_at)->equals(Scheduler::getPreviousRunDate($schedule)); + } + function testItCanRestoreANewsletter() { $this->newsletter->trash(); diff --git a/tests/unit/Newsletter/Scheduler/SchedulerTest.php b/tests/unit/Newsletter/Scheduler/SchedulerTest.php index 79f4c8d3d1..4d04ecc8de 100644 --- a/tests/unit/Newsletter/Scheduler/SchedulerTest.php +++ b/tests/unit/Newsletter/Scheduler/SchedulerTest.php @@ -25,7 +25,7 @@ class SchedulerTest extends \MailPoetTest { function testItGetsActiveNewslettersFilteredByType() { $newsletter = $this->_createNewsletter($type = Newsletter::TYPE_WELCOME); - // no newsletters wtih type "notification" should be found + // no newsletters with type "notification" should be found expect(Scheduler::getNewsletters(Newsletter::TYPE_NOTIFICATION))->isEmpty(); // one newsletter with type "welcome" should be found @@ -41,6 +41,15 @@ class SchedulerTest extends \MailPoetTest { expect(Scheduler::getNextRunDate('invalid CRON expression'))->false(); } + function testItCanGetPreviousRunDate() { + // it accepts cron syntax and returns previous run date + $current_time = Carbon::createFromTimestamp(current_time('timestamp')); + expect(Scheduler::getPreviousRunDate('* * * * *')) + ->equals($current_time->subMinute()->format('Y-m-d H:i:00')); + // when invalid CRON expression is used, false response is returned + expect(Scheduler::getPreviousRunDate('invalid CRON expression'))->false(); + } + function testItFormatsDatetimeString() { expect(Scheduler::formatDatetimeString('April 20, 2016 4pm')) ->equals('2016-04-20 16:00:00');