diff --git a/lib/Config/Migrator.php b/lib/Config/Migrator.php index b8342b4cd3..42bcbc1dc1 100644 --- a/lib/Config/Migrator.php +++ b/lib/Config/Migrator.php @@ -172,7 +172,8 @@ class Migrator { 'deleted_at TIMESTAMP NULL,', 'unconfirmed_data longtext,', 'PRIMARY KEY (id),', - 'UNIQUE KEY email (email)' + 'UNIQUE KEY email (email),', + 'KEY wp_user_id (wp_user_id)', ); return $this->sqlify(__FUNCTION__, $attributes); } diff --git a/lib/Segments/WP.php b/lib/Segments/WP.php index eceaecccca..44152419ef 100644 --- a/lib/Segments/WP.php +++ b/lib/Segments/WP.php @@ -79,28 +79,109 @@ class WP { } static function synchronizeUsers() { - // get wordpress users list + + self::updateSubscribersEmails(); + self::insertSubscribers(); + self::removeFromTrash(); + self::updateFirstNames(); + self::updateLastNames(); + self::updateFristNameIfMissing(); + self::insertUsersToSegment(); + self::removeOrphanedSubscribers(); + + return true; + } + + private static function updateSubscribersEmails() { + global $wpdb; + $subscribers_table = Subscriber::$_table; + Subscriber::raw_execute(sprintf(' + UPDATE IGNORE %s + JOIN %susers as wu ON %s.wp_user_id = wu.id + SET email = user_email + WHERE %s.wp_user_id IS NOT NULL + ', $subscribers_table, $wpdb->prefix, $subscribers_table, $subscribers_table)); + } + + private static function insertSubscribers() { + global $wpdb; + $subscribers_table = Subscriber::$_table; + Subscriber::raw_execute(sprintf(' + INSERT IGNORE INTO %s(wp_user_id, email, status, created_at) + SELECT wu.id, wu.user_email, "subscribed", CURRENT_TIMESTAMP() FROM %susers wu + LEFT JOIN %s mps ON wu.id = mps.wp_user_id + WHERE mps.wp_user_id IS NULL + ', $subscribers_table, $wpdb->prefix, $subscribers_table)); + } + + private static function updateFirstNames() { + global $wpdb; + $subscribers_table = Subscriber::$_table; + Subscriber::raw_execute(sprintf(' + UPDATE %s + JOIN %susermeta as wpum ON %s.wp_user_id = wpum.user_id AND meta_key = "first_name" + SET first_name = meta_value + WHERE %s.first_name = "" + AND %s.wp_user_id IS NOT NULL + ', $subscribers_table, $wpdb->prefix, $subscribers_table, $subscribers_table, $subscribers_table)); + } + + private static function updateLastNames() { + global $wpdb; + $subscribers_table = Subscriber::$_table; + Subscriber::raw_execute(sprintf(' + UPDATE %s + JOIN %susermeta as wpum ON %s.wp_user_id = wpum.user_id AND meta_key = "last_name" + SET last_name = meta_value + WHERE %s.last_name = "" + AND %s.wp_user_id IS NOT NULL + ', $subscribers_table, $wpdb->prefix, $subscribers_table, $subscribers_table, $subscribers_table)); + } + + private static function updateFristNameIfMissing() { + global $wpdb; + $subscribers_table = Subscriber::$_table; + Subscriber::raw_execute(sprintf(' + UPDATE %s + JOIN %susers wu ON %s.wp_user_id = wu.id + SET first_name = display_name + WHERE %s.first_name = "" + AND %s.wp_user_id IS NOT NULL + ', $subscribers_table, $wpdb->prefix, $subscribers_table, $subscribers_table, $subscribers_table)); + } + + private static function insertUsersToSegment() { $wp_segment = Segment::getWPSegment(); + $subscribers_table = Subscriber::$_table; + $wp_mailpoet_subscriber_segment_table = SubscriberSegment::$_table; + Subscriber::raw_execute(sprintf(' + INSERT IGNORE INTO %s(subscriber_id, segment_id, created_at) + SELECT mps.id, "%s", CURRENT_TIMESTAMP() FROM %s mps + WHERE mps.wp_user_id IS NOT NULL + ', $wp_mailpoet_subscriber_segment_table, $wp_segment->id, $subscribers_table)); + } - // fetch all wp users id - $wp_users = \get_users(array( - 'count_total' => false, - 'fields' => 'ID' - )); - - // update data for each wp user - foreach($wp_users as $wp_user_id) { - static::synchronizeUser($wp_user_id); - } + private static function removeFromTrash() { + $subscribers_table = Subscriber::$_table; + Subscriber::raw_execute(sprintf(' + UPDATE %s + SET deleted_at = NULL + WHERE %s.wp_user_id IS NOT NULL + ', $subscribers_table, $subscribers_table)); + } + private static function removeOrphanedSubscribers() { // remove orphaned wp segment subscribers (not having a matching wp user id), // e.g. if wp users were deleted directly from the database - $wp_segment->subscribers() - ->whereNotIn('wp_user_id', $wp_users) + global $wpdb; + + Subscriber::table_alias('wpms') + ->leftOuterJoin($wpdb->prefix . 'users', array('wpms.wp_user_id', '=', 'wu.id'), 'wu') + ->whereNull('wu.id') ->findResultSet() ->set('wp_user_id', null) ->delete(); - return true; } } + diff --git a/tests/unit/Segments/WPTest.php b/tests/unit/Segments/WPTest.php index 8a45c6c2f8..da1d2d9b7f 100644 --- a/tests/unit/Segments/WPTest.php +++ b/tests/unit/Segments/WPTest.php @@ -1,55 +1,194 @@ wp_user_1 = $this->createWPUser('phoenix_test_user'); - $this->wp_user_2 = $this->createWPUser('phoenix_test_user2'); - $this->wp_segment = Segment::getWPSegment(); +use Carbon\Carbon; +use MailPoet\Models\Segment; +use MailPoet\Models\Subscriber; +use MailPoet\Segments\WP; + +class WPTest extends \MailPoetTest { + + private $userIds = array(); + + function testItSynchronizeUsers() { + $this->insertUser(); + $this->insertUser(); + WP::synchronizeUsers(); + $subscribersCount = $this->getSubscribersCount(); + expect($subscribersCount)->equals(2); + } + + function testItSynchronizeNewUsers() { + $this->insertUser(); + $this->insertUser(); + WP::synchronizeUsers(); + $this->insertUser(); + WP::synchronizeUsers(); + $subscribersCount = $this->getSubscribersCount(); + expect($subscribersCount)->equals(3); + } + + function testItSynchronizeEmails() { + $id = $this->insertUser(); + WP::synchronizeUsers(); + $this->updateWPUserEmail($id, 'user-sync-test-xx@email.com'); + WP::synchronizeUsers(); + $subscriber = Subscriber::where('wp_user_id', $id)->findOne(); + expect($subscriber->email)->equals('user-sync-test-xx@email.com'); + } + + function testItSynchronizeFirstNames() { + $id = $this->insertUser(); + WP::synchronizeUsers(); + update_user_meta($id, 'first_name', 'First name'); + WP::synchronizeUsers(); + $subscriber = Subscriber::where('wp_user_id', $id)->findOne(); + expect($subscriber->first_name)->equals('First name'); + } + + function testItSynchronizeLastNames() { + $id = $this->insertUser(); + WP::synchronizeUsers(); + update_user_meta($id, 'last_name', 'Last name'); + WP::synchronizeUsers(); + $subscriber = Subscriber::where('wp_user_id', $id)->findOne(); + expect($subscriber->last_name)->equals('Last name'); + } + + function testItSynchronizeFirstNamesUsingDisplayName() { + $id = $this->insertUser(); + WP::synchronizeUsers(); + $this->updateWPUserDisplayName($id, 'First name'); + WP::synchronizeUsers(); + $subscriber = Subscriber::where('wp_user_id', $id)->findOne(); + expect($subscriber->first_name)->equals('First name'); + } + + function testItSynchronizeSegment() { + $this->insertUser(); + $this->insertUser(); + $this->insertUser(); + WP::synchronizeUsers(); + $subscribers = Segment::getWPSegment()->subscribers()->whereIn('wp_user_id', $this->userIds); + expect($subscribers->count())->equals(3); + } + + function testItRemovesUsersFromTrash() { + $id = $this->insertUser(); + WP::synchronizeUsers(); + $subscriber = Subscriber::where("wp_user_id", $id)->findOne(); + $subscriber->deleted_at = Carbon::now(); + $subscriber->save(); + WP::synchronizeUsers(); + $subscriber = Subscriber::where("wp_user_id", $id)->findOne(); + expect($subscriber->deleted_at)->null(); } function testItSynchronizesDeletedWPUsersUsingHooks() { - expect($this->getWPSegmentSubscribers()->count())->equals(2); - wp_delete_user($this->wp_user_1->ID); - expect($this->getWPSegmentSubscribers()->count())->equals(1); - } - - function testItForciblySynchronizesDeletedWPUsers() { - global $wpdb; - expect($this->getWPSegmentSubscribers()->count())->equals(2); - // Remove a WP user directly from the database - \ORM::for_table($wpdb->prefix . 'users') - ->where('id', $this->wp_user_2->ID) - ->deleteMany(); + $id = $this->insertUser(); + $this->insertUser(); WP::synchronizeUsers(); - expect($this->getWPSegmentSubscribers()->count())->equals(1); + $subscribersCount = $this->getSubscribersCount(); + expect($subscribersCount)->equals(2); + wp_delete_user($id); + $subscribersCount = $this->getSubscribersCount(); + expect($subscribersCount)->equals(1); } - private function getWPSegmentSubscribers() { - return $this->wp_segment->subscribers() - ->whereIn( - 'wp_user_id', - array( - $this->wp_user_1->ID, - $this->wp_user_2->ID - ) - ); + function testItRemovesOrphanedSubscribers() { + $this->insertUser(); + $id = $this->insertUser(); + WP::synchronizeUsers(); + $this->deleteWPUser($id); + WP::synchronizeUsers(); + $subscribers = Segment::getWPSegment()->subscribers()->whereIn('wp_user_id', $this->userIds); + expect($subscribers->count())->equals(1); } - private function createWPUser($login) { - $WP_user = wp_create_user($login, 'pass', $login . '@example.com'); - $WP_user = get_user_by('login', $login); - return $WP_user; + function _before() { + $this->cleanData(); } function _after() { - \ORM::raw_execute('TRUNCATE ' . Segment::$_table); - wp_delete_user($this->wp_user_1->ID); - wp_delete_user($this->wp_user_2->ID); + $this->cleanData(); } -} + + private function cleanData() { + \ORM::raw_execute('TRUNCATE ' . Segment::$_table); + global $wpdb; + $db = \ORM::getDb(); + $db->exec(sprintf(' + DELETE FROM + %susers + WHERE + user_email LIKE "user-sync-test%%" + ', $wpdb->prefix)); + } + + private function getSubscribersCount() { + return Subscriber::whereIn("wp_user_id", $this->userIds)->count(); + } + + /** + * Insert a user without invoking wp hooks. + * Those tests are testing user synchronisation, so we need data in wp_users table which has not been synchronised to + * mailpoet database yet. We cannot use wp_insert_user functions because they would do the sync on insert. + * + * @return string + */ + private function insertUser() { + global $wpdb; + $db = \ORM::getDb(); + $db->exec(sprintf(' + INSERT INTO + %susers(user_login, user_email, user_registered) + VALUES + ( + CONCAT("user-sync-test", rand()), + CONCAT("user", rand(), "@example.com"), + "2017-01-02 12:31:12" + )', $wpdb->prefix)); + $id = $db->lastInsertId(); + $this->userIds[] = $id; + return $id; + } + + private function updateWPUserEmail($id, $email) { + global $wpdb; + $db = \ORM::getDb(); + $db->exec(sprintf(' + UPDATE + %susers + SET user_email = "%s" + WHERE + id = %s + ', $wpdb->prefix, $email, $id)); + } + + private function updateWPUserDisplayName($id, $name) { + global $wpdb; + $db = \ORM::getDb(); + $db->exec(sprintf(' + UPDATE + %susers + SET display_name = "%s" + WHERE + id = %s + ', $wpdb->prefix, $name, $id)); + } + + private function deleteWPUser($id) { + global $wpdb; + $db = \ORM::getDb(); + $db->exec(sprintf(' + DELETE FROM + %susers + WHERE + id = %s + ', $wpdb->prefix, $id)); + } + +} \ No newline at end of file