Add WooCommerce synchronization [MAILPOET-1723]
This commit is contained in:
@ -4,6 +4,7 @@ namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscription\Form;
|
||||
use MailPoet\Segments\WooCommerce as WooCommerceSegment;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Hooks {
|
||||
@ -17,18 +18,24 @@ class Hooks {
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var WooCommerceSegment */
|
||||
private $woocommerce_segment;
|
||||
|
||||
function __construct(
|
||||
Form $subscription_form,
|
||||
SettingsController $settings,
|
||||
WPFunctions $wp
|
||||
WPFunctions $wp,
|
||||
WooCommerceSegment $woocommerce_segment
|
||||
) {
|
||||
$this->subscription_form = $subscription_form;
|
||||
$this->settings = $settings;
|
||||
$this->wp = $wp;
|
||||
$this->woocommerce_segment = $woocommerce_segment;
|
||||
}
|
||||
|
||||
function init() {
|
||||
$this->setupWPUsers();
|
||||
$this->setupWooCommerceUsers();
|
||||
$this->setupImageSize();
|
||||
$this->setupListing();
|
||||
$this->setupSubscriptionEvents();
|
||||
@ -158,6 +165,35 @@ class Hooks {
|
||||
);
|
||||
}
|
||||
|
||||
function setupWooCommerceUsers() {
|
||||
// WooCommerce Customers synchronization
|
||||
add_action(
|
||||
'woocommerce_new_customer',
|
||||
[$this->woocommerce_segment, 'synchronizeRegisteredCustomer'],
|
||||
7
|
||||
);
|
||||
add_action(
|
||||
'woocommerce_update_customer',
|
||||
[$this->woocommerce_segment, 'synchronizeRegisteredCustomer'],
|
||||
7
|
||||
);
|
||||
add_action(
|
||||
'woocommerce_delete_customer',
|
||||
[$this->woocommerce_segment, 'synchronizeRegisteredCustomer'],
|
||||
7
|
||||
);
|
||||
add_action(
|
||||
'woocommerce_checkout_update_order_meta',
|
||||
[$this->woocommerce_segment, 'synchronizeGuestCustomer'],
|
||||
7
|
||||
);
|
||||
add_action(
|
||||
'woocommerce_process_shop_order_meta',
|
||||
[$this->woocommerce_segment, 'synchronizeGuestCustomer'],
|
||||
7
|
||||
);
|
||||
}
|
||||
|
||||
function setupImageSize() {
|
||||
add_filter(
|
||||
'image_size_names_choose',
|
||||
|
@ -197,7 +197,7 @@ class Migrator {
|
||||
'updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'deleted_at timestamp NULL,',
|
||||
'unconfirmed_data longtext,',
|
||||
"source enum('form','imported','administrator','api','wordpress_user','unknown') DEFAULT 'unknown',",
|
||||
"source enum('form','imported','administrator','api','wordpress_user','woocommerce_user','unknown') DEFAULT 'unknown',",
|
||||
'count_confirmations int(11) unsigned NOT NULL DEFAULT 0,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY email (email),',
|
||||
|
@ -80,6 +80,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
$container->autowire(\MailPoet\Subscribers\RequiredCustomFieldValidator::class)->setPublic(true);
|
||||
// Segments
|
||||
$container->autowire(\MailPoet\Segments\SubscribersListings::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Segments\WooCommerce::class)->setPublic(true);
|
||||
// Settings
|
||||
$container->autowire(\MailPoet\Settings\SettingsController::class)->setPublic(true);
|
||||
// Subscription
|
||||
|
263
lib/Segments/WooCommerce.php
Normal file
263
lib/Segments/WooCommerce.php
Normal file
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
namespace MailPoet\Segments;
|
||||
|
||||
use MailPoet\Models\ModelValidator;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Models\Segment;
|
||||
use MailPoet\Models\SubscriberSegment;
|
||||
use MailPoet\Newsletter\Scheduler\Scheduler;
|
||||
use MailPoet\Subscribers\Source;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class WooCommerce {
|
||||
function synchronizeRegisteredCustomer($wp_user_id, $current_filter = null) {
|
||||
$wc_segment = Segment::getWooCommerceSegment();
|
||||
|
||||
if($wc_segment === false) return;
|
||||
|
||||
$current_filter = $current_filter ?: current_filter();
|
||||
switch($current_filter) {
|
||||
case 'woocommerce_delete_customer':
|
||||
// subscriber should be already deleted in WP users sync
|
||||
$this->unsubscribeUsersFromSegment(); // remove leftover association
|
||||
break;
|
||||
case 'woocommerce_new_customer':
|
||||
$new_customer = true;
|
||||
case 'woocommerce_update_customer':
|
||||
default:
|
||||
$wp_user = \get_userdata($wp_user_id);
|
||||
$subscriber = Subscriber::where('wp_user_id', $wp_user_id)
|
||||
->findOne();
|
||||
|
||||
if ($wp_user === false || $subscriber === false) {
|
||||
// registered customers should exist as WP users and WP segment subscribers
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'is_woocommerce_user' => 1,
|
||||
);
|
||||
if (!empty($new_customer)) {
|
||||
$data['status'] = Subscriber::STATUS_SUBSCRIBED;
|
||||
$data['source'] = Source::WOOCOMMERCE_USER;
|
||||
}
|
||||
$data['id'] = $subscriber->id();
|
||||
$data['deleted_at'] = null; // remove the user from the trash
|
||||
|
||||
$subscriber = Subscriber::createOrUpdate($data);
|
||||
if($subscriber->getErrors() === false && $subscriber->id > 0) {
|
||||
// add subscriber to the WooCommerce Customers segment
|
||||
SubscriberSegment::subscribeToSegments(
|
||||
$subscriber,
|
||||
array($wc_segment->id)
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function synchronizeGuestCustomer($order_id, $current_filter = null) {
|
||||
$wc_order = \get_post($order_id);
|
||||
$wc_segment = Segment::getWooCommerceSegment();
|
||||
|
||||
if($wc_order === false or $wc_segment === false) return;
|
||||
|
||||
$current_filter = $current_filter ?: current_filter();
|
||||
switch($current_filter) {
|
||||
case 'woocommerce_checkout_update_order_meta':
|
||||
case 'woocommerce_process_shop_order_meta':
|
||||
default:
|
||||
$inserted_emails = $this->insertSubscribersFromOrders($order_id);
|
||||
if (empty($inserted_emails[0]['email'])) {
|
||||
return false;
|
||||
}
|
||||
$subscriber = Subscriber::where('email', $inserted_emails[0]['email'])
|
||||
->findOne();
|
||||
|
||||
if($subscriber !== false) {
|
||||
// add subscriber to the WooCommerce Customers segment
|
||||
SubscriberSegment::subscribeToSegments(
|
||||
$subscriber,
|
||||
array($wc_segment->id)
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function synchronizeCustomers() {
|
||||
|
||||
WP::synchronizeUsers(); // synchronize registered users
|
||||
|
||||
$this->markRegisteredCustomers();
|
||||
$inserted_users_emails = $this->insertSubscribersFromOrders();
|
||||
$this->removeUpdatedSubscribersWithInvalidEmail($inserted_users_emails);
|
||||
$this->removeFromTrash();
|
||||
$this->updateFirstNames();
|
||||
$this->updateLastNames();
|
||||
$this->insertUsersToSegment();
|
||||
$this->unsubscribeUsersFromSegment();
|
||||
$this->removeOrphanedSubscribers();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function markRegisteredCustomers() {
|
||||
// Mark WP users having a customer role as WooCommerce subscribers
|
||||
global $wpdb;
|
||||
$subscribers_table = Subscriber::$_table;
|
||||
Subscriber::raw_execute(sprintf('
|
||||
UPDATE %1$s mps
|
||||
JOIN %2$s wu ON mps.wp_user_id = wu.id
|
||||
JOIN %3$s wpum ON wu.id = wpum.user_id AND wpum.meta_key = "wpdev_capabilities"
|
||||
SET is_woocommerce_user = 1
|
||||
WHERE wpum.meta_value LIKE "%%\"customer\"%%"
|
||||
', $subscribers_table, $wpdb->users, $wpdb->usermeta));
|
||||
}
|
||||
|
||||
private function insertSubscribersFromOrders($order_id = null) {
|
||||
global $wpdb;
|
||||
$subscribers_table = Subscriber::$_table;
|
||||
$order_id = !is_null($order_id) ? (int)$order_id : null;
|
||||
|
||||
$inserted_users_emails = \ORM::for_table($wpdb->users)->raw_query(
|
||||
'SELECT DISTINCT wppm.meta_value as email FROM `wpdev_postmeta` wppm
|
||||
JOIN `wpdev_posts` p ON wppm.post_id = p.ID AND p.post_type = "shop_order"
|
||||
WHERE wppm.meta_key = "_billing_email" AND wppm.meta_value != ""
|
||||
' . ($order_id ? ' AND p.ID = "' . $order_id . '"' : '') . '
|
||||
')->findArray();
|
||||
|
||||
Subscriber::raw_execute(sprintf('
|
||||
INSERT IGNORE INTO %1$s (is_woocommerce_user, email, status, created_at, source)
|
||||
SELECT 1, wppm.meta_value, "%2$s", CURRENT_TIMESTAMP(), "%3$s" FROM `wpdev_postmeta` wppm
|
||||
JOIN `wpdev_posts` p ON wppm.post_id = p.ID AND p.post_type = "shop_order"
|
||||
WHERE wppm.meta_key = "_billing_email" AND wppm.meta_value != ""
|
||||
' . ($order_id ? ' AND p.ID = "' . $order_id . '"' : '') . '
|
||||
ON DUPLICATE KEY UPDATE is_woocommerce_user = 1
|
||||
', $subscribers_table, Subscriber::STATUS_SUBSCRIBED, Source::WOOCOMMERCE_USER));
|
||||
|
||||
return $inserted_users_emails;
|
||||
}
|
||||
|
||||
private function removeUpdatedSubscribersWithInvalidEmail($updated_emails) {
|
||||
$validator = new ModelValidator();
|
||||
$invalid_is_woocommerce_users = array_map(function($item) {
|
||||
return $item['email'];
|
||||
},
|
||||
array_filter($updated_emails, function($updated_email) use($validator) {
|
||||
return !$validator->validateEmail($updated_email['email']);
|
||||
}));
|
||||
if(!$invalid_is_woocommerce_users) {
|
||||
return;
|
||||
}
|
||||
\ORM::for_table(Subscriber::$_table)
|
||||
->whereNull('wp_user_id')
|
||||
->where('is_woocommerce_user', 1)
|
||||
->whereIn('email', $invalid_is_woocommerce_users)
|
||||
->delete_many();
|
||||
}
|
||||
|
||||
private function updateFirstNames() {
|
||||
global $wpdb;
|
||||
$subscribers_table = Subscriber::$_table;
|
||||
Subscriber::raw_execute(sprintf('
|
||||
UPDATE %1$s mps
|
||||
JOIN %2$s wppm ON mps.email = wppm.meta_value AND wppm.meta_key = "_billing_email"
|
||||
JOIN %2$s wppm2 ON wppm2.post_id = wppm.post_id AND wppm2.meta_key = "_billing_first_name"
|
||||
JOIN (SELECT MAX(post_id) AS max_id FROM %2$s WHERE meta_key = "_billing_email" GROUP BY meta_value) AS tmaxid ON tmaxid.max_id = wppm.post_id
|
||||
SET mps.first_name = wppm2.meta_value
|
||||
WHERE mps.first_name = ""
|
||||
AND mps.is_woocommerce_user = 1
|
||||
AND wppm2.meta_value IS NOT NULL
|
||||
', $subscribers_table, $wpdb->postmeta));
|
||||
}
|
||||
|
||||
private function updateLastNames() {
|
||||
global $wpdb;
|
||||
$subscribers_table = Subscriber::$_table;
|
||||
Subscriber::raw_execute(sprintf('
|
||||
UPDATE %1$s mps
|
||||
JOIN %2$s wppm ON mps.email = wppm.meta_value AND wppm.meta_key = "_billing_email"
|
||||
JOIN %2$s wppm2 ON wppm2.post_id = wppm.post_id AND wppm2.meta_key = "_billing_last_name"
|
||||
JOIN (SELECT MAX(post_id) AS max_id FROM %2$s WHERE meta_key = "_billing_email" GROUP BY meta_value) AS tmaxid ON tmaxid.max_id = wppm.post_id
|
||||
SET mps.last_name = wppm2.meta_value
|
||||
WHERE mps.last_name = ""
|
||||
AND mps.is_woocommerce_user = 1
|
||||
AND wppm2.meta_value IS NOT NULL
|
||||
', $subscribers_table, $wpdb->postmeta));
|
||||
}
|
||||
|
||||
private function insertUsersToSegment() {
|
||||
$wc_segment = Segment::getWooCommerceSegment();
|
||||
$subscribers_table = Subscriber::$_table;
|
||||
$wp_mailpoet_subscriber_segment_table = SubscriberSegment::$_table;
|
||||
// Subscribe WC users to segment
|
||||
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.is_woocommerce_user = 1
|
||||
', $wp_mailpoet_subscriber_segment_table, $wc_segment->id, $subscribers_table));
|
||||
}
|
||||
|
||||
private function unsubscribeUsersFromSegment() {
|
||||
$wc_segment = Segment::getWooCommerceSegment();
|
||||
$subscribers_table = Subscriber::$_table;
|
||||
$wp_mailpoet_subscriber_segment_table = SubscriberSegment::$_table;
|
||||
// Unsubscribe non-WC or invalid users from segment
|
||||
Subscriber::raw_execute(sprintf('
|
||||
DELETE mpss FROM %s mpss
|
||||
LEFT JOIN %s mps ON mpss.subscriber_id = mps.id
|
||||
WHERE mpss.segment_id = %s AND (mps.is_woocommerce_user = 0 OR mps.email = "" OR mps.email IS NULL)
|
||||
', $wp_mailpoet_subscriber_segment_table, $subscribers_table, $wc_segment->id));
|
||||
}
|
||||
|
||||
private function removeFromTrash() {
|
||||
$subscribers_table = Subscriber::$_table;
|
||||
Subscriber::raw_execute(sprintf('
|
||||
UPDATE %1$s
|
||||
SET %1$s.deleted_at = NULL
|
||||
WHERE %1$s.is_woocommerce_user = 1
|
||||
', $subscribers_table));
|
||||
}
|
||||
|
||||
private function removeOrphanedSubscribers() {
|
||||
// Remove orphaned WooCommerce segment subscribers (not having a matching WC customer email),
|
||||
// e.g. if WC orders were deleted directly from the database
|
||||
// or a customer role was revoked and a user has no orders
|
||||
global $wpdb;
|
||||
|
||||
$wc_segment = Segment::getWooCommerceSegment();
|
||||
|
||||
// Unmark registered customers
|
||||
$set = $wc_segment->subscribers()
|
||||
->leftOuterJoin(
|
||||
$wpdb->postmeta,
|
||||
'wppm.meta_key = "_billing_email" AND ' . MP_SUBSCRIBERS_TABLE . '.email = wppm.meta_value',
|
||||
'wppm'
|
||||
)
|
||||
->leftOuterJoin($wpdb->posts, 'wppm.post_id = wpp.ID AND wpp.post_type = "shop_order"', 'wpp')
|
||||
->join($wpdb->usermeta, 'wp_user_id = wpum.user_id AND wpum.meta_key = "wpdev_capabilities"', 'wpum')
|
||||
->whereRaw('(wppm.meta_value IS NULL AND wpum.meta_value NOT LIKE "%\"customer\"%")')
|
||||
->whereNotNull('wp_user_id')
|
||||
->findResultSet()
|
||||
->set('is_woocommerce_user', 0)
|
||||
->save();
|
||||
|
||||
// Remove guest customers
|
||||
$wc_segment->subscribers()
|
||||
->leftOuterJoin(
|
||||
$wpdb->postmeta,
|
||||
'wppm.meta_key = "_billing_email" AND ' . MP_SUBSCRIBERS_TABLE . '.email = wppm.meta_value',
|
||||
'wppm'
|
||||
)
|
||||
->leftOuterJoin($wpdb->posts, 'wppm.post_id = wpp.ID AND wpp.post_type = "shop_order"', 'wpp')
|
||||
->whereRaw('(wppm.meta_value IS NULL OR wpp.ID IS NULL OR ' . MP_SUBSCRIBERS_TABLE . '.email = "")')
|
||||
->whereNull('wp_user_id')
|
||||
->findResultSet()
|
||||
->set('is_woocommerce_user', 0)
|
||||
->delete();
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ use MailPoet\Config\Menu;
|
||||
use MailPoet\Config\Renderer;
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\WooCommerce\Helper as WooCommerceHelper;
|
||||
use MailPoet\WP\Functions;
|
||||
|
||||
class MenuTest extends \MailPoetTest {
|
||||
@ -42,7 +43,7 @@ class MenuTest extends \MailPoetTest {
|
||||
|
||||
function testItChecksMailpoetAPIKey() {
|
||||
$renderer = Stub::make(new Renderer());
|
||||
$menu = new Menu($renderer, new AccessControl(), new SettingsController(), new Functions());
|
||||
$menu = new Menu($renderer, new AccessControl(), new SettingsController(), new Functions(), new WooCommerceHelper);
|
||||
|
||||
$_REQUEST['page'] = 'mailpoet-newsletters';
|
||||
$checker = Stub::make(
|
||||
@ -64,7 +65,7 @@ class MenuTest extends \MailPoetTest {
|
||||
|
||||
function testItChecksPremiumKey() {
|
||||
$renderer = Stub::make(new Renderer());
|
||||
$menu = new Menu($renderer, new AccessControl(), new SettingsController(), new Functions());
|
||||
$menu = new Menu($renderer, new AccessControl(), new SettingsController(), new Functions(), new WooCommerceHelper);
|
||||
|
||||
$_REQUEST['page'] = 'mailpoet-newsletters';
|
||||
$checker = Stub::make(
|
||||
|
Reference in New Issue
Block a user