| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- <?php
- namespace MailPoet\Segments;
- if (!defined('ABSPATH')) exit;
- use MailPoet\Config\Env;
- use MailPoet\Models\ModelValidator;
- use MailPoet\Models\Segment;
- use MailPoet\Models\Subscriber;
- use MailPoet\Models\SubscriberSegment;
- use MailPoet\Settings\SettingsController;
- use MailPoet\Subscribers\Source;
- use MailPoet\Subscribers\SubscribersRepository;
- use MailPoet\WooCommerce\Helper as WCHelper;
- use MailPoet\WP\Functions as WPFunctions;
- use MailPoetVendor\Idiorm\ORM;
- class WooCommerce {
- /** @var SettingsController */
- private $settings;
- /** @var WPFunctions */
- private $wp;
- /** @var WP */
- private $wpSegment;
- /** @var string|null */
- private $mailpoetEmailCollation;
- /** @var string|null */
- private $wpPostmetaValueCollation;
- /** @var SubscribersRepository */
- private $subscribersRepository;
- /** @var WCHelper */
- private $woocommerceHelper;
- public function __construct(
- SettingsController $settings,
- WPFunctions $wp,
- WCHelper $woocommerceHelper,
- SubscribersRepository $subscribersRepository,
- WP $wpSegment
- ) {
- $this->settings = $settings;
- $this->wp = $wp;
- $this->wpSegment = $wpSegment;
- $this->subscribersRepository = $subscribersRepository;
- $this->woocommerceHelper = $woocommerceHelper;
- }
- public function shouldShowWooCommerceSegment() {
- $isWoocommerceActive = $this->woocommerceHelper->isWooCommerceActive();
- $woocommerceUserExists = $this->subscribersRepository->woocommerceUserExists();
- if (!$isWoocommerceActive && !$woocommerceUserExists) {
- return false;
- }
- return true;
- }
- public function synchronizeRegisteredCustomer($wpUserId, $currentFilter = null) {
- $wcSegment = Segment::getWooCommerceSegment();
- if ($wcSegment === false) return;
- $currentFilter = $currentFilter ?: $this->wp->currentFilter();
- switch ($currentFilter) {
- case 'woocommerce_delete_customer':
- // subscriber should be already deleted in WP users sync
- $this->unsubscribeUsersFromSegment(); // remove leftover association
- break;
- case 'woocommerce_new_customer':
- case 'woocommerce_created_customer':
- $newCustomer = true;
- case 'woocommerce_update_customer':
- default:
- $wpUser = $this->wp->getUserdata($wpUserId);
- $subscriber = Subscriber::where('wp_user_id', $wpUserId)
- ->findOne();
- if ($wpUser === false || $subscriber === false) {
- // registered customers should exist as WP users and WP segment subscribers
- return false;
- }
- $data = [
- 'is_woocommerce_user' => 1,
- ];
- if (!empty($newCustomer)) {
- $data['source'] = Source::WOOCOMMERCE_USER;
- }
- $data['id'] = $subscriber->id();
- if ($wpUser->first_name) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
- $data['first_name'] = $wpUser->first_name; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
- }
- if ($wpUser->last_name) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
- $data['last_name'] = $wpUser->last_name; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
- }
- $subscriber = Subscriber::createOrUpdate($data);
- if ($subscriber->getErrors() === false && $subscriber->id > 0) {
- // add subscriber to the WooCommerce Customers segment
- SubscriberSegment::subscribeToSegments(
- $subscriber,
- [$wcSegment->id]
- );
- }
- break;
- }
- return true;
- }
- public function synchronizeGuestCustomer($orderId) {
- $wcOrder = $this->woocommerceHelper->wcGetOrder($orderId);
- $wcSegment = Segment::getWooCommerceSegment();
- if ((!$wcOrder instanceof \WC_Order) || $wcSegment === false) return;
- $signupConfirmation = $this->settings->get('signup_confirmation');
- $status = Subscriber::STATUS_UNCONFIRMED;
- if ((bool)$signupConfirmation['enabled'] === false) {
- $status = Subscriber::STATUS_SUBSCRIBED;
- }
- $insertedEmails = $this->insertSubscribersFromOrders($orderId, $status);
- if (empty($insertedEmails[0]['email'])) {
- return false;
- }
- $subscriber = Subscriber::where('email', $insertedEmails[0]['email'])
- ->findOne();
- if ($subscriber !== false) {
- $firstName = $wcOrder->get_billing_first_name(); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
- $lastName = $wcOrder->get_billing_last_name(); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
- if ($firstName) {
- $subscriber->firstName = $firstName;
- }
- if ($lastName) {
- $subscriber->lastName = $lastName;
- }
- if ($firstName || $lastName) {
- $subscriber->save();
- }
- // add subscriber to the WooCommerce Customers segment
- SubscriberSegment::subscribeToSegments(
- $subscriber,
- [$wcSegment->id]
- );
- }
- }
- public function synchronizeCustomers() {
- $this->wpSegment->synchronizeUsers(); // synchronize registered users
- $this->markRegisteredCustomers();
- $insertedUsersEmails = $this->insertSubscribersFromOrders();
- $this->removeUpdatedSubscribersWithInvalidEmail($insertedUsersEmails);
- unset($insertedUsersEmails);
- $this->updateFirstNames();
- $this->updateLastNames();
- $this->insertUsersToSegment();
- $this->unsubscribeUsersFromSegment();
- $this->removeOrphanedSubscribers();
- $this->updateStatus();
- $this->updateGlobalStatus();
- return true;
- }
- private function ensureColumnCollation(): void {
- if ($this->mailpoetEmailCollation && $this->wpPostmetaValueCollation) {
- return;
- }
- global $wpdb;
- $mailpoetEmailColumn = $wpdb->get_row(
- 'SHOW FULL COLUMNS FROM ' . MP_SUBSCRIBERS_TABLE . ' WHERE Field = "email"'
- );
- $this->mailpoetEmailCollation = $mailpoetEmailColumn->Collation; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
- $wpPostmetaValueColumn = $wpdb->get_row(
- 'SHOW FULL COLUMNS FROM ' . $wpdb->postmeta . ' WHERE Field = "meta_value"'
- );
- $this->wpPostmetaValueCollation = $wpPostmetaValueColumn->Collation; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
- }
- private function needsCollationChange(): bool {
- $this->ensureColumnCollation();
- $collation1 = (string)$this->mailpoetEmailCollation;
- $collation2 = (string)$this->wpPostmetaValueCollation;
- if ($collation1 === $collation2) {
- return false;
- }
- $collation1UnderscorePos = strpos($collation1, '_');
- $collation2UnderscorePos = strpos($collation2, '_');
- $charset1 = substr($collation1, 0, $collation1UnderscorePos === false ? strlen($collation1) : $collation1UnderscorePos);
- $charset2 = substr($collation2, 0, $collation2UnderscorePos === false ? strlen($collation2) : $collation2UnderscorePos);
- return $charset1 === $charset2;
- }
- private function markRegisteredCustomers() {
- // Mark WP users having a customer role as WooCommerce subscribers
- global $wpdb;
- $subscribersTable = Subscriber::$_table;
- Subscriber::rawExecute(sprintf('
- UPDATE LOW_PRIORITY %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 = "' . $wpdb->prefix . 'capabilities"
- SET is_woocommerce_user = 1, source = "%4$s"
- WHERE wpum.meta_value LIKE "%%\"customer\"%%"
- ', $subscribersTable, $wpdb->users, $wpdb->usermeta, Source::WOOCOMMERCE_USER));
- }
- private function insertSubscribersFromOrders($orderId = null, $status = Subscriber::STATUS_SUBSCRIBED) {
- global $wpdb;
- $subscribersTable = Subscriber::$_table;
- $orderId = !is_null($orderId) ? (int)$orderId : null;
- $insertedUsersEmails = ORM::for_table($wpdb->users)->raw_query(
- 'SELECT DISTINCT wppm.meta_value as email FROM `' . $wpdb->prefix . 'postmeta` wppm
- JOIN `' . $wpdb->prefix . 'posts` p ON wppm.post_id = p.ID AND p.post_type = "shop_order"
- WHERE wppm.meta_key = "_billing_email" AND wppm.meta_value != ""
- ' . ($orderId ? ' AND p.ID = "' . $orderId . '"' : '') . '
- ')->findArray();
- Subscriber::rawExecute(sprintf('
- INSERT IGNORE INTO %1$s (is_woocommerce_user, email, status, created_at, last_subscribed_at, source)
- SELECT 1, wppm.meta_value, "%2$s", CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), "%3$s" FROM `' . $wpdb->prefix . 'postmeta` wppm
- JOIN `' . $wpdb->prefix . 'posts` p ON wppm.post_id = p.ID AND p.post_type = "shop_order"
- WHERE wppm.meta_key = "_billing_email" AND wppm.meta_value != ""
- ' . ($orderId ? ' AND p.ID = "' . $orderId . '"' : '') . '
- ON DUPLICATE KEY UPDATE is_woocommerce_user = 1
- ', $subscribersTable, $status, Source::WOOCOMMERCE_USER));
- return $insertedUsersEmails;
- }
- private function removeUpdatedSubscribersWithInvalidEmail($updatedEmails) {
- $validator = new ModelValidator();
- $invalidIsWoocommerceUsers = array_map(function($item) {
- return $item['email'];
- },
- array_filter($updatedEmails, function($updatedEmail) use($validator) {
- return !$validator->validateEmail($updatedEmail['email']);
- }));
- if (!$invalidIsWoocommerceUsers) {
- return;
- }
- ORM::for_table(Subscriber::$_table)
- ->whereNull('wp_user_id')
- ->where('is_woocommerce_user', 1)
- ->whereIn('email', $invalidIsWoocommerceUsers)
- ->delete_many();
- }
- private function updateFirstNames() {
- global $wpdb;
- $collate = '';
- if ($this->needsCollationChange()) {
- $collate = ' COLLATE ' . $this->mailpoetEmailCollation;
- }
- $subscribersTable = Subscriber::$_table;
- Subscriber::rawExecute(sprintf('
- UPDATE LOW_PRIORITY %1$s mps
- JOIN %2$s wppm ON mps.email = wppm.meta_value %3$s 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
- ', $subscribersTable, $wpdb->postmeta, $collate));
- }
- private function updateLastNames() {
- global $wpdb;
- $collate = '';
- if ($this->needsCollationChange()) {
- $collate = ' COLLATE ' . $this->mailpoetEmailCollation;
- }
- $subscribersTable = Subscriber::$_table;
- Subscriber::rawExecute(sprintf('
- UPDATE LOW_PRIORITY %1$s mps
- JOIN %2$s wppm ON mps.email = wppm.meta_value %3$s 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
- ', $subscribersTable, $wpdb->postmeta, $collate));
- }
- private function insertUsersToSegment() {
- $wcSegment = Segment::getWooCommerceSegment();
- $subscribersTable = Subscriber::$_table;
- $wpMailpoetSubscriberSegmentTable = SubscriberSegment::$_table;
- // Subscribe WC users to segment
- Subscriber::rawExecute(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
- ', $wpMailpoetSubscriberSegmentTable, $wcSegment->id, $subscribersTable));
- }
- private function unsubscribeUsersFromSegment() {
- $wcSegment = Segment::getWooCommerceSegment();
- $subscribersTable = Subscriber::$_table;
- $wpMailpoetSubscriberSegmentTable = SubscriberSegment::$_table;
- // Unsubscribe non-WC or invalid users from segment
- Subscriber::rawExecute(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)
- ', $wpMailpoetSubscriberSegmentTable, $subscribersTable, $wcSegment->id));
- }
- private function updateGlobalStatus() {
- $subscribersTable = Subscriber::$_table;
- $subscriberSegmentTable = SubscriberSegment::$_table;
- $wcSegment = Segment::getWooCommerceSegment();
- // Set global status unsubscribed to all woocommerce users without any segment
- $sql = sprintf('
- UPDATE %1$s mps
- LEFT JOIN %2$s mpss ON mpss.subscriber_id = mps.id
- SET mps.status = "unsubscribed"
- WHERE
- mpss.id IS NULL
- AND mps.is_woocommerce_user = 1
- ', $subscribersTable, $subscriberSegmentTable);
- Subscriber::rawExecute($sql);
- // SET global status unsubscribed to all woocommerce users who have only 1 segment and it is woocommerce segment and they are not subscribed
- // You can't specify target table 'mps' for update in FROM clause
- $sql = sprintf('
- UPDATE %1$s as mps
- JOIN %2$s as mpss on mps.id = mpss.subscriber_id AND mpss.segment_id = "%3$s" AND mpss.status = "unsubscribed"
- SET mps.status = "unsubscribed"
- WHERE mps.id IN (
- SELECT s.id -- get all subscribers with exactly 1 list
- FROM ( SELECT id FROM %1$s WHERE is_woocommerce_user = 1) as s
- JOIN %2$s as l on s.id=l.subscriber_id
- GROUP BY s.id
- HAVING COUNT(l.id) = 1
- )
- ', $subscribersTable, $subscriberSegmentTable, $wcSegment->id);
- Subscriber::rawExecute($sql);
- }
- 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;
- $wcSegment = Segment::getWooCommerceSegment();
- // Unmark registered customers
- // Insert WC customer IDs to a temporary table for left join to use an index
- $tmpTableName = Env::$dbPrefix . 'tmp_wc_ids';
- // Registered users with orders
- Subscriber::rawExecute(sprintf('
- CREATE TEMPORARY TABLE %1$s
- (`id` int(11) unsigned NOT NULL, UNIQUE(`id`)) AS
- SELECT DISTINCT wppm.meta_value AS id FROM %2$s wppm
- JOIN %3$s wpp ON wppm.post_id = wpp.ID
- AND wpp.post_type = "shop_order"
- WHERE wppm.meta_key = "_customer_user"
- ', $tmpTableName, $wpdb->postmeta, $wpdb->posts));
- // Registered users with a customer role
- Subscriber::rawExecute(sprintf('
- INSERT IGNORE INTO %1$s
- SELECT DISTINCT wpum.user_id AS id FROM %2$s wpum
- WHERE wpum.meta_key = "%3$s" AND wpum.meta_value LIKE "%%\"customer\"%%"
- ', $tmpTableName, $wpdb->usermeta, $wpdb->prefix . 'capabilities'));
- // Unmark WC list registered users which aren't WC customers anymore
- Subscriber::tableAlias('mps')
- ->select('mps.*')
- ->join(
- MP_SUBSCRIBER_SEGMENT_TABLE,
- 'mps.`id` = mpss.`subscriber_id` AND mpss.`segment_id` = "' . $wcSegment->id . '"',
- 'mpss'
- )
- ->leftOuterJoin(
- $tmpTableName,
- 'mps.`wp_user_id` = wctmp.`id`',
- 'wctmp'
- )
- ->where('is_woocommerce_user', 1)
- ->whereNull('wctmp.id')
- ->whereNotNull('wp_user_id')
- ->findResultSet()
- ->set('is_woocommerce_user', 0)
- ->save();
- Subscriber::rawExecute('DROP TABLE ' . $tmpTableName);
- // Remove guest customers
- // Insert WC customer emails to a temporary table and ensure matching collations
- // between MailPoet and WooCommerce emails for left join to use an index
- $tmpTableName = Env::$dbPrefix . 'tmp_wc_emails';
- Subscriber::rawExecute(sprintf('
- CREATE TEMPORARY TABLE %1$s
- (`email` varchar(150) NOT NULL, UNIQUE(`email`)) COLLATE %2$s AS
- SELECT DISTINCT wppm.meta_value AS email FROM %3$s wppm
- JOIN %4$s wpp ON wppm.post_id = wpp.ID
- AND wpp.post_type = "shop_order"
- WHERE wppm.meta_key = "_billing_email"
- ', $tmpTableName, $this->mailpoetEmailCollation, $wpdb->postmeta, $wpdb->posts));
- // Remove WC list guest users which aren't WC customers anymore
- Subscriber::tableAlias('mps')
- ->select('mps.*')
- ->join(
- MP_SUBSCRIBER_SEGMENT_TABLE,
- 'mps.`id` = mpss.`subscriber_id` AND mpss.`segment_id` = "' . $wcSegment->id . '"',
- 'mpss'
- )
- ->leftOuterJoin(
- $tmpTableName,
- 'mps.`email` = wctmp.`email`',
- 'wctmp'
- )
- ->where('is_woocommerce_user', 1)
- ->whereNull('wctmp.email')
- ->whereNull('wp_user_id')
- ->findResultSet()
- ->set('is_woocommerce_user', 0)
- ->delete();
- Subscriber::rawExecute('DROP TABLE ' . $tmpTableName);
- }
- private function updateStatus() {
- $subscribeOldCustomers = $this->settings->get('mailpoet_subscribe_old_woocommerce_customers.enabled', false);
- if ($subscribeOldCustomers !== "1") {
- $status = Subscriber::STATUS_UNSUBSCRIBED;
- } else {
- $status = Subscriber::STATUS_SUBSCRIBED;
- }
- $subscribersTable = Subscriber::$_table;
- $subscriberSegmentTable = SubscriberSegment::$_table;
- $wcSegment = Segment::getWooCommerceSegment();
- $sql = sprintf('
- UPDATE LOW_PRIORITY %1$s mpss
- JOIN %2$s mps ON mpss.subscriber_id = mps.id
- SET mpss.status = "%3$s"
- WHERE
- mpss.segment_id = %4$s
- AND mps.confirmed_at IS NULL
- AND mps.confirmed_ip IS NULL
- AND mps.is_woocommerce_user = 1
- ', $subscriberSegmentTable, $subscribersTable, $status, $wcSegment->id);
- Subscriber::rawExecute($sql);
- }
- }
|