From 1795964c69e43494abf0a085a417e1dadcdbf3d3 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 15 Mar 2016 12:10:09 -0400 Subject: [PATCH 1/4] - Updates composer dependencies --- composer.json | 2 +- composer.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 602b8112e0..3be91d72b0 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "j4mie/paris": "1.5.4", "swiftmailer/swiftmailer": "^5.4", "phpseclib/phpseclib": "*", - "mtdowling/cron-expression": "^1.0", + "mtdowling/cron-expression": "^1.1", "nesbot/carbon": "^1.21", "soundasleep/html2text": "^0.3.0" }, diff --git a/composer.lock b/composer.lock index 61b632474f..da2bdd5d99 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "db2edea5fb720fcb8013ac470e924c19", - "content-hash": "85119d1ccd5193b6b08fda305a2427e7", + "hash": "2bed8395d84740d7c0ae644a6c6216fd", + "content-hash": "7b66e221814f3d5839ed4faabd2f50ad", "packages": [ { "name": "cerdic/css-tidy", From 6e289b6a8f08e5d9a259c2fefcd0ce81413c37ec Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 16 Mar 2016 21:06:09 -0400 Subject: [PATCH 2/4] - Implements welcome e-mail scheduling --- lib/Config/Migrator.php | 1 + lib/Models/Subscriber.php | 2 + lib/Newsletter/Scheduler/Scheduler.php | 71 ++++++++++++++++++++++++++ lib/Segments/WP.php | 36 +++++++------ 4 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 lib/Newsletter/Scheduler/Scheduler.php diff --git a/lib/Config/Migrator.php b/lib/Config/Migrator.php index 1398bf72f4..9f7c751143 100644 --- a/lib/Config/Migrator.php +++ b/lib/Config/Migrator.php @@ -221,6 +221,7 @@ class Migrator { 'count_processed mediumint(9) NOT NULL DEFAULT 0,', 'count_to_process mediumint(9) NOT NULL DEFAULT 0,', 'count_failed mediumint(9) NOT NULL DEFAULT 0,', + 'scheduled_at TIMESTAMP NOT NULL DEFAULT 0,', 'processed_at TIMESTAMP NOT NULL DEFAULT 0,', 'created_at TIMESTAMP NOT NULL DEFAULT 0,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', diff --git a/lib/Models/Subscriber.php b/lib/Models/Subscriber.php index 19fe992de1..b1244d6ac8 100644 --- a/lib/Models/Subscriber.php +++ b/lib/Models/Subscriber.php @@ -1,6 +1,7 @@ save()) { $subscriber->addToSegments($segment_ids); + Scheduler::newSegmentSubscriptionNewsletter($subscriber, $segment_ids); } } diff --git a/lib/Newsletter/Scheduler/Scheduler.php b/lib/Newsletter/Scheduler/Scheduler.php new file mode 100644 index 0000000000..d8b67021c1 --- /dev/null +++ b/lib/Newsletter/Scheduler/Scheduler.php @@ -0,0 +1,71 @@ +filter('filterWithOptions') + ->findArray(); + } + + private static function scheduleWelcomeNewsletter($newsletter, $subscriber) { + $queue = SendingQueue::create(); + $queue->newsletter_id = $newsletter['id']; + $queue->subscribers = serialize( + array( + 'to_process' => array($subscriber['id']) + ) + ); + $queue->count_total = $queue->count_to_process = 1; + $after_time_type = $newsletter['afterTimeType']; + $after_time_number = $newsletter['afterTimeNumber']; + $scheduled_at = null; + switch($after_time_type) { + case 'hours': + $scheduled_at = Carbon::now() + ->addHours($after_time_number); + break; + case 'days': + $scheduled_at = Carbon::now() + ->addDays($after_time_number); + break; + case 'weeks': + $scheduled_at = Carbon::now() + ->addWeeks($after_time_number); + break; + } + if($scheduled_at) { + $queue->status = 'scheduled'; + $queue->scheduled_at = $scheduled_at; + } + $queue->save(); + } +} \ No newline at end of file diff --git a/lib/Segments/WP.php b/lib/Segments/WP.php index 63d409fccb..35cd8c1532 100644 --- a/lib/Segments/WP.php +++ b/lib/Segments/WP.php @@ -2,17 +2,15 @@ namespace MailPoet\Segments; use \MailPoet\Models\Subscriber; use \MailPoet\Models\Segment; +use MailPoet\Newsletter\Scheduler\Scheduler; class WP { static function synchronizeUser($wp_user_id) { - $wpUser = \get_userdata($wp_user_id); + $wp_user = \get_userdata($wp_user_id); $segment = Segment::getWPUsers(); - - if($wpUser === false or $segment === false) return; - - $subscriber = Subscriber::where('wp_user_id', $wpUser->ID) + if($wp_user === false or $segment === false) return; + $subscriber = Subscriber::where('wp_user_id', $wp_user->ID) ->findOne(); - switch(current_filter()) { case 'delete_user': case 'deleted_user': @@ -20,23 +18,22 @@ class WP { if($subscriber !== false && $subscriber->id()) { $subscriber->delete(); } - break; - + break; case 'user_register': + $new_user = (!$subscriber) ? true : false; case 'added_existing_user': case 'profile_update': default: // get first name & last name - $first_name = $wpUser->first_name; - $last_name = $wpUser->last_name; - if(empty($wpUser->first_name) && empty($wpUser->last_name)) { - $first_name = $wpUser->display_name; + $first_name = $wp_user->first_name; + $last_name = $wp_user->last_name; + if(empty($wp_user->first_name) && empty($wp_user->last_name)) { + $first_name = $wp_user->display_name; } - // subscriber data $data = array( - 'wp_user_id'=> $wpUser->ID, - 'email' => $wpUser->user_email, + 'wp_user_id' => $wp_user->ID, + 'email' => $wp_user->user_email, 'first_name' => $first_name, 'last_name' => $last_name, 'status' => 'subscribed' @@ -46,13 +43,18 @@ class WP { $data['id'] = $subscriber->id(); } $subscriber = Subscriber::createOrUpdate($data); - if($subscriber->getErrors() === false && $subscriber->id > 0) { if($segment !== false) { $segment->addSubscriber($subscriber->id); } + if(isset($new_user) && $new_user === true) { + Scheduler::newUserRegistrationNewsletter( + $subscriber->asArray(), + (array) $wp_user + ); + } } - break; + break; } } From bb9fce7f82b8180df3d4b797bf9713bc32d70b07 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 18 Mar 2016 09:58:40 -0400 Subject: [PATCH 3/4] - Implements post notification scheduling --- .../js/src/newsletters/types/notification.jsx | 26 +++++----- composer.lock | 6 +-- lib/Config/Populator.php | 12 +++++ lib/Models/Subscriber.php | 2 +- lib/Newsletter/Scheduler/Scheduler.php | 52 +++++++++++++++++-- lib/Router/Newsletters.php | 7 +++ lib/Segments/WP.php | 2 +- 7 files changed, 84 insertions(+), 23 deletions(-) diff --git a/assets/js/src/newsletters/types/notification.jsx b/assets/js/src/newsletters/types/notification.jsx index 92f1606cd1..9952590994 100644 --- a/assets/js/src/newsletters/types/notification.jsx +++ b/assets/js/src/newsletters/types/notification.jsx @@ -53,13 +53,13 @@ define( var weekDayField = { name: 'weekDay', values: { - 0: 'Monday', - 1: 'Tuesday', - 2: 'Wednesday', - 3: 'Thursday', - 4: 'Friday', - 5: 'Saturday', - 6: 'Sunday', + 0: 'Sunday', + 1: 'Monday', + 2: 'Tuesday', + 3: 'Wednesday', + 4: 'Thursday', + 5: 'Friday', + 6: 'Saturday' }, }; @@ -84,10 +84,10 @@ define( var nthWeekDayField = { name: 'nthWeekDay', values: { - '0': '1st', - '1': '2nd', - '2': '3rd', - '3': 'last', + '1': '1st', + '2': '2nd', + '3': '3rd', + 'L': 'last', }, }; @@ -99,9 +99,9 @@ define( return { intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly' timeOfDay: 0, - weekDay: 0, + weekDay: 1, monthDay: 0, - nthWeekDay: 0, + nthWeekDay: 1, }; }, handleIntervalChange: function(event) { diff --git a/composer.lock b/composer.lock index da2bdd5d99..84c3cf549c 100644 --- a/composer.lock +++ b/composer.lock @@ -2294,7 +2294,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/41ee6c70758f40fa1dbf90d019ae0a66c4a09e74", + "url": "https://api.github.com/repos/symfony/config/zipball/ee4cdda66aff834c8125e8f2c15932461667c521", "reference": "41ee6c70758f40fa1dbf90d019ae0a66c4a09e74", "shasum": "" }, @@ -2671,7 +2671,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/7fd5e4034cb8e215887136f5e176430bbf5ef085", + "url": "https://api.github.com/repos/symfony/form/zipball/b629051c77a4f37c625651d03002760385df4575", "reference": "7fd5e4034cb8e215887136f5e176430bbf5ef085", "shasum": "" }, @@ -2929,7 +2929,7 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac", + "url": "https://api.github.com/repos/symfony/process/zipball/d9d21cfcc3e202ee34777d6da38897695d4d208d", "reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac", "shasum": "" }, diff --git a/lib/Config/Populator.php b/lib/Config/Populator.php index d1518e7287..b24f763aa1 100644 --- a/lib/Config/Populator.php +++ b/lib/Config/Populator.php @@ -175,6 +175,18 @@ class Populator { 'name' => 'nthWeekDay', 'newsletter_type' => 'notification', ), + array( + 'name' => 'schedule', + 'newsletter_type' => 'notification', + ), + array( + 'name' => 'lastRunTime', + 'newsletter_type' => 'notification', + ), + array( + 'name' => 'lastRunResults', + 'newsletter_type' => 'notification', + ), ); } diff --git a/lib/Models/Subscriber.php b/lib/Models/Subscriber.php index b1244d6ac8..52f263c16b 100644 --- a/lib/Models/Subscriber.php +++ b/lib/Models/Subscriber.php @@ -209,7 +209,7 @@ class Subscriber extends Model { if($subscriber->save()) { $subscriber->addToSegments($segment_ids); - Scheduler::newSegmentSubscriptionNewsletter($subscriber, $segment_ids); + Scheduler::welcomeForSegmentSubscription($subscriber, $segment_ids); } } diff --git a/lib/Newsletter/Scheduler/Scheduler.php b/lib/Newsletter/Scheduler/Scheduler.php index d8b67021c1..ac9655ac28 100644 --- a/lib/Newsletter/Scheduler/Scheduler.php +++ b/lib/Newsletter/Scheduler/Scheduler.php @@ -3,29 +3,71 @@ namespace MailPoet\Newsletter\Scheduler; use Carbon\Carbon; use MailPoet\Models\Newsletter; +use MailPoet\Models\NewsletterOption; +use MailPoet\Models\NewsletterOptionField; use MailPoet\Models\SendingQueue; class Scheduler { - static function newSegmentSubscriptionNewsletter(array $subscriber, array $segments) { + static function postNotification($newsletter_id) { + $newsletter = Newsletter::filter('filterWithOptions') + ->findOne($newsletter_id); + $interval_type = $newsletter->intervalType; + $hour = (int) $newsletter->timeOfDay / 3600; + $week_day = $newsletter->weekDay; + $month_day = $newsletter->monthDay; + $nth_week_day = ($newsletter->nthWeekDay === 'L') ? + $newsletter->nthWeekDay : + '#' . $newsletter->nthWeekDay; + switch($interval_type) { + case 'immediately': + $cron = '* * * * *'; + break; + case 'immediate': //daily + $cron = sprintf('0 %s * * *', $hour); + break; + case 'weekly': + $cron = sprintf('0 %s * * %s', $hour, $week_day); + break; + case 'monthly': + $cron = sprintf('0 %s %s * *', $hour, $month_day); + break; + case 'nthWeekDay': + $cron = sprintf('0 %s ? * %s%s', $hour, $week_day, $nth_week_day); + break; + } + $option_field = NewsletterOptionField::where('name', 'schedule') + ->findOne(); + $relation = NewsletterOption::create(); + $relation->newsletter_id = $newsletter->id; + $relation->option_field_id = $option_field->id; + $relation->value = $cron; + $relation->save(); + } + + static function welcomeForSegmentSubscription( + array $subscriber, array $segments + ) { $newsletters = self::getWelcomeNewsletters(); if(!count($newsletters)) return; foreach($newsletters as $newsletter) { if($newsletter['event'] === 'segment' && in_array($newsletter['segment'], $segments) ) { - self::scheduleWelcomeNewsletter($newsletter, $subscriber); + self::createSendingQueueEntry($newsletter, $subscriber); } } } - static function newUserRegistrationNewsletter(array $subscriber, array $wp_user) { + static function welcomeForNewWordpressUserRegistration( + array $subscriber, array $wp_user + ) { $newsletters = self::getWelcomeNewsletters(); if(!count($newsletters)) return; foreach($newsletters as $newsletter) { if($newsletter['event'] === 'user' && in_array($newsletter['role'], $wp_user['roles']) ) { - self::scheduleWelcomeNewsletter($newsletter, $subscriber); + self::createSendingQueueEntry($newsletter, $subscriber); } } } @@ -36,7 +78,7 @@ class Scheduler { ->findArray(); } - private static function scheduleWelcomeNewsletter($newsletter, $subscriber) { + private static function createSendingQueueEntry($newsletter, $subscriber) { $queue = SendingQueue::create(); $queue->newsletter_id = $newsletter['id']; $queue->subscribers = serialize( diff --git a/lib/Router/Newsletters.php b/lib/Router/Newsletters.php index 2f40669a52..ce8e1cfebd 100644 --- a/lib/Router/Newsletters.php +++ b/lib/Router/Newsletters.php @@ -14,6 +14,7 @@ use MailPoet\Models\NewsletterOptionField; use MailPoet\Models\NewsletterOption; use MailPoet\Newsletter\Renderer\Renderer; use MailPoet\Models\SendingQueue; +use MailPoet\Newsletter\Scheduler\Scheduler; if(!defined('ABSPATH')) exit; @@ -265,6 +266,12 @@ class Newsletters { } } } + if(!isset($data['id']) && + isset($data['type']) && + $data['type'] === 'notification' + ) { + Scheduler::postNotification($newsletter->id); + } return array( 'result' => true, 'newsletter' => $newsletter->asArray() diff --git a/lib/Segments/WP.php b/lib/Segments/WP.php index 35cd8c1532..d482958239 100644 --- a/lib/Segments/WP.php +++ b/lib/Segments/WP.php @@ -48,7 +48,7 @@ class WP { $segment->addSubscriber($subscriber->id); } if(isset($new_user) && $new_user === true) { - Scheduler::newUserRegistrationNewsletter( + Scheduler::welcomeForNewWordpressUserRegistration( $subscriber->asArray(), (array) $wp_user ); From 17b56f016017fb0031f87fddd2ba3a7f486affc8 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 18 Mar 2016 12:11:38 -0400 Subject: [PATCH 4/4] - Fixes scheduling issues - Fixes unit test - Updates as per code review comments --- lib/Config/Populator.php | 6 +--- lib/Models/Subscriber.php | 2 +- lib/Newsletter/Scheduler/Scheduler.php | 43 +++++++++++++------------- lib/Segments/WP.php | 4 +-- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/lib/Config/Populator.php b/lib/Config/Populator.php index b24f763aa1..7f41855f99 100644 --- a/lib/Config/Populator.php +++ b/lib/Config/Populator.php @@ -180,11 +180,7 @@ class Populator { 'newsletter_type' => 'notification', ), array( - 'name' => 'lastRunTime', - 'newsletter_type' => 'notification', - ), - array( - 'name' => 'lastRunResults', + 'name' => 'lastSentData', 'newsletter_type' => 'notification', ), ); diff --git a/lib/Models/Subscriber.php b/lib/Models/Subscriber.php index 52f263c16b..eb8e452635 100644 --- a/lib/Models/Subscriber.php +++ b/lib/Models/Subscriber.php @@ -209,7 +209,7 @@ class Subscriber extends Model { if($subscriber->save()) { $subscriber->addToSegments($segment_ids); - Scheduler::welcomeForSegmentSubscription($subscriber, $segment_ids); + Scheduler::welcomeForSegmentSubscription($subscriber->id, $segment_ids); } } diff --git a/lib/Newsletter/Scheduler/Scheduler.php b/lib/Newsletter/Scheduler/Scheduler.php index ac9655ac28..7b46753057 100644 --- a/lib/Newsletter/Scheduler/Scheduler.php +++ b/lib/Newsletter/Scheduler/Scheduler.php @@ -8,16 +8,20 @@ use MailPoet\Models\NewsletterOptionField; use MailPoet\Models\SendingQueue; class Scheduler { + const seconds_in_hour = 3600; + const last_weekday_format = 'L'; + static function postNotification($newsletter_id) { $newsletter = Newsletter::filter('filterWithOptions') - ->findOne($newsletter_id); - $interval_type = $newsletter->intervalType; - $hour = (int) $newsletter->timeOfDay / 3600; - $week_day = $newsletter->weekDay; - $month_day = $newsletter->monthDay; - $nth_week_day = ($newsletter->nthWeekDay === 'L') ? - $newsletter->nthWeekDay : - '#' . $newsletter->nthWeekDay; + ->findOne($newsletter_id) + ->asArray(); + $interval_type = $newsletter['intervalType']; + $hour = (int) $newsletter['timeOfDay'] / self::seconds_in_hour; + $week_day = $newsletter['weekDay']; + $month_day = $newsletter['monthDay']; + $nth_week_day = ($newsletter['nthWeekDay'] === self::last_weekday_format) ? + $newsletter['nthWeekDay'] : + '#' . $newsletter['nthWeekDay']; switch($interval_type) { case 'immediately': $cron = '* * * * *'; @@ -36,38 +40,35 @@ class Scheduler { break; } $option_field = NewsletterOptionField::where('name', 'schedule') - ->findOne(); + ->findOne() + ->asArray(); $relation = NewsletterOption::create(); - $relation->newsletter_id = $newsletter->id; - $relation->option_field_id = $option_field->id; + $relation->newsletter_id = $newsletter['id']; + $relation->option_field_id = $option_field['id']; $relation->value = $cron; $relation->save(); } - static function welcomeForSegmentSubscription( - array $subscriber, array $segments - ) { + static function welcomeForSegmentSubscription($subscriber_id, array $segments) { $newsletters = self::getWelcomeNewsletters(); if(!count($newsletters)) return; foreach($newsletters as $newsletter) { if($newsletter['event'] === 'segment' && in_array($newsletter['segment'], $segments) ) { - self::createSendingQueueEntry($newsletter, $subscriber); + self::createSendingQueueEntry($newsletter, $subscriber_id); } } } - static function welcomeForNewWordpressUserRegistration( - array $subscriber, array $wp_user - ) { + static function welcomeForNewWPUser($subscriber_id, array $wp_user) { $newsletters = self::getWelcomeNewsletters(); if(!count($newsletters)) return; foreach($newsletters as $newsletter) { if($newsletter['event'] === 'user' && in_array($newsletter['role'], $wp_user['roles']) ) { - self::createSendingQueueEntry($newsletter, $subscriber); + self::createSendingQueueEntry($newsletter, $subscriber_id); } } } @@ -78,12 +79,12 @@ class Scheduler { ->findArray(); } - private static function createSendingQueueEntry($newsletter, $subscriber) { + private static function createSendingQueueEntry($newsletter, $subscriber_id) { $queue = SendingQueue::create(); $queue->newsletter_id = $newsletter['id']; $queue->subscribers = serialize( array( - 'to_process' => array($subscriber['id']) + 'to_process' => array($subscriber_id) ) ); $queue->count_total = $queue->count_to_process = 1; diff --git a/lib/Segments/WP.php b/lib/Segments/WP.php index d482958239..dab1a9da6e 100644 --- a/lib/Segments/WP.php +++ b/lib/Segments/WP.php @@ -48,8 +48,8 @@ class WP { $segment->addSubscriber($subscriber->id); } if(isset($new_user) && $new_user === true) { - Scheduler::welcomeForNewWordpressUserRegistration( - $subscriber->asArray(), + Scheduler::welcomeForNewWPUser( + $subscriber->id, (array) $wp_user ); }