Replace Segment model with Doctrine in \MailPoet\Segments\WP

[MAILPOET-5752]
This commit is contained in:
Rodrigo Primo
2023-11-29 10:38:13 -03:00
committed by Aschepikov
parent b63834b02b
commit 374fbe6867
8 changed files with 117 additions and 47 deletions

View File

@@ -6,7 +6,6 @@ use MailPoet\Config\SubscriberChangesNotifier;
use MailPoet\DI\ContainerWrapper; use MailPoet\DI\ContainerWrapper;
use MailPoet\Entities\SegmentEntity; use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\SubscriberEntity; use MailPoet\Entities\SubscriberEntity;
use MailPoet\Models\Segment;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment; use MailPoet\Models\SubscriberSegment;
use MailPoet\Newsletter\Scheduler\WelcomeScheduler; use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
@@ -43,6 +42,8 @@ class WP {
/** @var Validator */ /** @var Validator */
private $validator; private $validator;
private $segmentsRepository;
public function __construct( public function __construct(
WPFunctions $wp, WPFunctions $wp,
WelcomeScheduler $welcomeScheduler, WelcomeScheduler $welcomeScheduler,
@@ -50,7 +51,8 @@ class WP {
SubscribersRepository $subscribersRepository, SubscribersRepository $subscribersRepository,
SubscriberSegmentRepository $subscriberSegmentRepository, SubscriberSegmentRepository $subscriberSegmentRepository,
SubscriberChangesNotifier $subscriberChangesNotifier, SubscriberChangesNotifier $subscriberChangesNotifier,
Validator $validator Validator $validator,
SegmentsRepository $segmentsRepository
) { ) {
$this->wp = $wp; $this->wp = $wp;
$this->welcomeScheduler = $welcomeScheduler; $this->welcomeScheduler = $welcomeScheduler;
@@ -59,6 +61,7 @@ class WP {
$this->subscriberSegmentRepository = $subscriberSegmentRepository; $this->subscriberSegmentRepository = $subscriberSegmentRepository;
$this->subscriberChangesNotifier = $subscriberChangesNotifier; $this->subscriberChangesNotifier = $subscriberChangesNotifier;
$this->validator = $validator; $this->validator = $validator;
$this->segmentsRepository = $segmentsRepository;
} }
/** /**
@@ -102,8 +105,7 @@ class WP {
*/ */
private function createOrUpdateSubscriber(string $currentFilter, \WP_User $wpUser, $subscriber = false, $oldWpUserData = false): void { private function createOrUpdateSubscriber(string $currentFilter, \WP_User $wpUser, $subscriber = false, $oldWpUserData = false): void {
// Add or update // Add or update
$wpSegment = Segment::getWPSegment(); $wpSegment = $this->segmentsRepository->getWPUsersSegment();
if (!$wpSegment) return;
// find subscriber by email when is false // find subscriber by email when is false
if (!$subscriber) { if (!$subscriber) {
@@ -138,7 +140,7 @@ class WP {
unset($data['source']); // don't override status for existing users unset($data['source']); // don't override status for existing users
} }
$addingNewUserToDisabledWPSegment = $wpSegment->deletedAt !== null && $currentFilter === 'user_register'; $addingNewUserToDisabledWPSegment = $wpSegment->getDeletedAt() !== null && $currentFilter === 'user_register';
$otherActiveSegments = []; $otherActiveSegments = [];
if ($subscriber) { if ($subscriber) {
@@ -160,13 +162,13 @@ class WP {
// add subscriber to the WP Users segment // add subscriber to the WP Users segment
SubscriberSegment::subscribeToSegments( SubscriberSegment::subscribeToSegments(
$subscriber, $subscriber,
[$wpSegment->id] [$wpSegment->getId()]
); );
if (!$signupConfirmationEnabled && $subscriber->status === Subscriber::STATUS_SUBSCRIBED && $currentFilter === 'user_register') { if (!$signupConfirmationEnabled && $subscriber->status === Subscriber::STATUS_SUBSCRIBED && $currentFilter === 'user_register') {
$subscriberSegment = $this->subscriberSegmentRepository->findOneBy([ $subscriberSegment = $this->subscriberSegmentRepository->findOneBy([
'subscriber' => $subscriber->id(), 'subscriber' => $subscriber->id(),
'segment' => $wpSegment->id(), 'segment' => $wpSegment->getId(),
]); ]);
if (!is_null($subscriberSegment)) { if (!is_null($subscriberSegment)) {
@@ -264,9 +266,9 @@ class WP {
private function insertSubscribers(): array { private function insertSubscribers(): array {
global $wpdb; global $wpdb;
$wpSegment = Segment::getWPSegment(); $wpSegment = $this->segmentsRepository->getWPUsersSegment();
if (!$wpSegment) return [];
if ($wpSegment->deletedAt !== null) { if ($wpSegment->getDeletedAt() !== null) {
$subscriberStatus = SubscriberEntity::STATUS_UNCONFIRMED; $subscriberStatus = SubscriberEntity::STATUS_UNCONFIRMED;
$deletedAt = 'CURRENT_TIMESTAMP()'; $deletedAt = 'CURRENT_TIMESTAMP()';
} else { } else {
@@ -338,28 +340,17 @@ class WP {
} }
private function insertUsersToSegment(): void { private function insertUsersToSegment(): void {
$wpSegment = Segment::getWPSegment(); $wpSegment = $this->segmentsRepository->getWPUsersSegment();
$subscribersTable = Subscriber::$_table; $subscribersTable = Subscriber::$_table;
$wpMailpoetSubscriberSegmentTable = SubscriberSegment::$_table; $wpMailpoetSubscriberSegmentTable = SubscriberSegment::$_table;
Subscriber::rawExecute(sprintf(' Subscriber::rawExecute(sprintf('
INSERT IGNORE INTO %s(subscriber_id, segment_id, created_at) INSERT IGNORE INTO %s(subscriber_id, segment_id, created_at)
SELECT mps.id, "%s", CURRENT_TIMESTAMP() FROM %s mps SELECT mps.id, "%s", CURRENT_TIMESTAMP() FROM %s mps
WHERE mps.wp_user_id > 0 WHERE mps.wp_user_id > 0
', $wpMailpoetSubscriberSegmentTable, $wpSegment->id, $subscribersTable)); ', $wpMailpoetSubscriberSegmentTable, $wpSegment->getId(), $subscribersTable));
} }
private function removeOrphanedSubscribers(): void { private function removeOrphanedSubscribers(): void {
// remove orphaned wp segment subscribers (not having a matching wp user id), $this->subscribersRepository->removeOrphanedSubscribersFromWpSegment();
// e.g. if wp users were deleted directly from the database
global $wpdb;
$wpSegment = Segment::getWPSegment();
$wpSegment->subscribers()
->leftOuterJoin($wpdb->users, [MP_SUBSCRIBERS_TABLE . '.wp_user_id', '=', 'wu.id'], 'wu')
->whereRaw('(wu.id IS NULL OR ' . MP_SUBSCRIBERS_TABLE . '.email = "")')
->findResultSet()
->set('wp_user_id', null)
->delete();
} }
} }

View File

@@ -12,6 +12,7 @@ use MailPoet\Entities\SubscriberEntity;
use MailPoet\Entities\SubscriberSegmentEntity; use MailPoet\Entities\SubscriberSegmentEntity;
use MailPoet\Entities\SubscriberTagEntity; use MailPoet\Entities\SubscriberTagEntity;
use MailPoet\Entities\TagEntity; use MailPoet\Entities\TagEntity;
use MailPoet\Segments\SegmentsRepository;
use MailPoet\Util\License\Features\Subscribers; use MailPoet\Util\License\Features\Subscribers;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
use MailPoetVendor\Carbon\Carbon; use MailPoetVendor\Carbon\Carbon;
@@ -38,14 +39,19 @@ class SubscribersRepository extends Repository {
/** @var SubscriberChangesNotifier */ /** @var SubscriberChangesNotifier */
private $changesNotifier; private $changesNotifier;
/** @var SegmentsRepository */
private $segmentsRepository;
public function __construct( public function __construct(
EntityManager $entityManager, EntityManager $entityManager,
SubscriberChangesNotifier $changesNotifier, SubscriberChangesNotifier $changesNotifier,
WPFunctions $wp WPFunctions $wp,
SegmentsRepository $segmentsRepository
) { ) {
$this->wp = $wp; $this->wp = $wp;
parent::__construct($entityManager); parent::__construct($entityManager);
$this->changesNotifier = $changesNotifier; $this->changesNotifier = $changesNotifier;
$this->segmentsRepository = $segmentsRepository;
} }
protected function getEntityClassName() { protected function getEntityClassName() {
@@ -553,6 +559,24 @@ class SubscribersRepository extends Repository {
return $count; return $count;
} }
public function removeOrphanedSubscribersFromWpSegment(): void {
global $wpdb;
$segmentId = $this->segmentsRepository->getWpUsersSegment()->getId();
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
$subscriberSegmentsTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
$this->entityManager->getConnection()->executeStatement(
"DELETE s
FROM {$subscribersTable} s
INNER JOIN {$subscriberSegmentsTable} ss ON s.id = ss.subscriber_id
LEFT JOIN {$wpdb->users} u ON s.wp_user_id = u.id
WHERE ss.segment_id = :segmentId AND (u.id IS NULL OR s.email = '')",
['segmentId' => $segmentId], ['segmentId' => \PDO::PARAM_INT]
);
}
/** /**
* @return int - number of processed ids * @return int - number of processed ids
*/ */

View File

@@ -83,6 +83,19 @@ class IntegrationTester extends \Codeception\Actor {
return $userId; return $userId;
} }
/**
* Deletes a WP user directly from the database without triggering any hooks.
* Needed to be able to test deleting orphaned subscribers.
*/
public function deleteWPUserFromDatabase(int $id): void {
global $wpdb;
$this->entityManager->getConnection()->executeStatement(
"DELETE FROM {$wpdb->users} WHERE id = :id",
['id' => $id], ['id' => \PDO::PARAM_INT]
);
}
public function createWordPressTerm(string $term, string $taxonomy, array $args = []): int { public function createWordPressTerm(string $term, string $taxonomy, array $args = []): int {
$term = wp_insert_term($term, $taxonomy, $args); $term = wp_insert_term($term, $taxonomy, $args);
if ($term instanceof WP_Error) { if ($term instanceof WP_Error) {

View File

@@ -246,7 +246,7 @@ class WPTest extends \MailPoetTest {
$this->updateWPUserEmail($id, ''); $this->updateWPUserEmail($id, '');
$this->wpSegment->synchronizeUsers(); $this->wpSegment->synchronizeUsers();
verify($this->subscribersRepository->findOneBy(['wpUserId' => $id]))->null(); verify($this->subscribersRepository->findOneBy(['wpUserId' => $id]))->null();
$this->deleteWPUser($id); $this->tester->deleteWPUserFromDatabase($id);
} }
public function testRemovesUsersWithInvalidEmailsFromSunscribersDuringSynchronization(): void { public function testRemovesUsersWithInvalidEmailsFromSunscribersDuringSynchronization(): void {
@@ -255,7 +255,7 @@ class WPTest extends \MailPoetTest {
$this->updateWPUserEmail($id, 'ivalid.@email.com'); $this->updateWPUserEmail($id, 'ivalid.@email.com');
$this->wpSegment->synchronizeUsers(); $this->wpSegment->synchronizeUsers();
verify($this->subscribersRepository->findOneBy(['wpUserId' => $id]))->null(); verify($this->subscribersRepository->findOneBy(['wpUserId' => $id]))->null();
$this->deleteWPUser($id); $this->tester->deleteWPUserFromDatabase($id);
} }
public function testItDoesNotSynchronizeEmptyEmailsForNewUsers(): void { public function testItDoesNotSynchronizeEmptyEmailsForNewUsers(): void {
@@ -263,7 +263,7 @@ class WPTest extends \MailPoetTest {
$this->updateWPUserEmail($id, ''); $this->updateWPUserEmail($id, '');
$this->wpSegment->synchronizeUsers(); $this->wpSegment->synchronizeUsers();
verify($this->subscribersRepository->findOneBy(['wpUserId' => $id]))->null(); verify($this->subscribersRepository->findOneBy(['wpUserId' => $id]))->null();
$this->deleteWPUser($id); $this->tester->deleteWPUserFromDatabase($id);
} }
public function testItSynchronizeFirstNames(): void { public function testItSynchronizeFirstNames(): void {
@@ -372,7 +372,7 @@ class WPTest extends \MailPoetTest {
$this->insertUser(); $this->insertUser();
$id = $this->insertUser(); $id = $this->insertUser();
$this->wpSegment->synchronizeUsers(); $this->wpSegment->synchronizeUsers();
$this->deleteWPUser($id); $this->tester->deleteWPUserFromDatabase($id);
$this->wpSegment->synchronizeUsers(); $this->wpSegment->synchronizeUsers();
$subscribers = $this->subscribersRepository->findBy(['wpUserId' => $this->userIds]); $subscribers = $this->subscribersRepository->findBy(['wpUserId' => $this->userIds]);
verify(count($subscribers))->equals(1); verify(count($subscribers))->equals(1);
@@ -722,17 +722,6 @@ class WPTest extends \MailPoetTest {
', $wpdb->users, $name, $id)); ', $wpdb->users, $name, $id));
} }
private function deleteWPUser(int $id): void {
global $wpdb;
$db = ORM::getDb();
$db->exec(sprintf('
DELETE FROM
%s
WHERE
id = %s
', $wpdb->users, $id));
}
private function clearEmail(SubscriberEntity $subscriber): void { private function clearEmail(SubscriberEntity $subscriber): void {
$this->connection->executeStatement( $this->connection->executeStatement(
'UPDATE ' . MP_SUBSCRIBERS_TABLE . ' 'UPDATE ' . MP_SUBSCRIBERS_TABLE . '

View File

@@ -509,7 +509,12 @@ class ClicksTest extends \MailPoetTest {
$clicksRepository = $this->diContainer->get(StatisticsClicksRepository::class); $clicksRepository = $this->diContainer->get(StatisticsClicksRepository::class);
$data = $this->trackData; $data = $this->trackData;
$data->userAgent = 'User Agent'; $data->userAgent = 'User Agent';
$subscribersRepository = new SubscribersRepository($this->entityManager, new SubscriberChangesNotifier($wpMock), $wpMock); $subscribersRepository = $this->getServiceWithOverrides(SubscribersRepository::class,
[
'changesNotifier' => new SubscriberChangesNotifier($wpMock),
'wp' => $wpMock,
]
);
$statisticsOpensRepository = $this->diContainer->get(StatisticsOpensRepository::class); $statisticsOpensRepository = $this->diContainer->get(StatisticsOpensRepository::class);
$opens = new Opens( $opens = new Opens(
$statisticsOpensRepository, $statisticsOpensRepository,
@@ -547,7 +552,12 @@ class ClicksTest extends \MailPoetTest {
$clicksRepository = $this->diContainer->get(StatisticsClicksRepository::class); $clicksRepository = $this->diContainer->get(StatisticsClicksRepository::class);
$data = $this->trackData; $data = $this->trackData;
$data->userAgent = null; $data->userAgent = null;
$subscribersRepository = new SubscribersRepository($this->entityManager, new SubscriberChangesNotifier($wpMock), $wpMock); $subscribersRepository = $this->getServiceWithOverrides(SubscribersRepository::class,
[
'changesNotifier' => new SubscriberChangesNotifier($wpMock),
'wp' => $wpMock,
]
);
$statisticsOpensRepository = $this->diContainer->get(StatisticsOpensRepository::class); $statisticsOpensRepository = $this->diContainer->get(StatisticsOpensRepository::class);
$opens = new Opens( $opens = new Opens(
$statisticsOpensRepository, $statisticsOpensRepository,
@@ -585,7 +595,12 @@ class ClicksTest extends \MailPoetTest {
$clicksRepository = $this->diContainer->get(StatisticsClicksRepository::class); $clicksRepository = $this->diContainer->get(StatisticsClicksRepository::class);
$data = $this->trackData; $data = $this->trackData;
$data->userAgent = UserAgentEntity::MACHINE_USER_AGENTS[0]; $data->userAgent = UserAgentEntity::MACHINE_USER_AGENTS[0];
$subscribersRepository = new SubscribersRepository($this->entityManager, new SubscriberChangesNotifier($wpMock), $wpMock); $subscribersRepository = $this->getServiceWithOverrides(SubscribersRepository::class,
[
'changesNotifier' => new SubscriberChangesNotifier($wpMock),
'wp' => $wpMock,
]
);
$statisticsOpensRepository = $this->diContainer->get(StatisticsOpensRepository::class); $statisticsOpensRepository = $this->diContainer->get(StatisticsOpensRepository::class);
$opens = new Opens( $opens = new Opens(
$statisticsOpensRepository, $statisticsOpensRepository,

View File

@@ -366,7 +366,12 @@ class OpensTest extends \MailPoetTest {
$opens = Stub::construct($this->opens, [ $opens = Stub::construct($this->opens, [
$this->diContainer->get(StatisticsOpensRepository::class), $this->diContainer->get(StatisticsOpensRepository::class),
$this->diContainer->get(UserAgentsRepository::class), $this->diContainer->get(UserAgentsRepository::class),
new SubscribersRepository($this->entityManager, new SubscriberChangesNotifier($wpMock), $wpMock), $this->getServiceWithOverrides(SubscribersRepository::class,
[
'changesNotifier' => new SubscriberChangesNotifier($wpMock),
'wp' => $wpMock,
]
),
], [ ], [
'returnResponse' => null, 'returnResponse' => null,
], $this); ], $this);
@@ -390,7 +395,12 @@ class OpensTest extends \MailPoetTest {
$opens = Stub::construct($this->opens, [ $opens = Stub::construct($this->opens, [
$this->diContainer->get(StatisticsOpensRepository::class), $this->diContainer->get(StatisticsOpensRepository::class),
$this->diContainer->get(UserAgentsRepository::class), $this->diContainer->get(UserAgentsRepository::class),
new SubscribersRepository($this->entityManager, new SubscriberChangesNotifier($wpMock), $wpMock), $this->getServiceWithOverrides(SubscribersRepository::class,
[
'changesNotifier' => new SubscriberChangesNotifier($wpMock),
'wp' => $wpMock,
]
),
], [ ], [
'returnResponse' => null, 'returnResponse' => null,
], $this); ], $this);
@@ -414,7 +424,12 @@ class OpensTest extends \MailPoetTest {
$opens = Stub::construct($this->opens, [ $opens = Stub::construct($this->opens, [
$this->diContainer->get(StatisticsOpensRepository::class), $this->diContainer->get(StatisticsOpensRepository::class),
$this->diContainer->get(UserAgentsRepository::class), $this->diContainer->get(UserAgentsRepository::class),
new SubscribersRepository($this->entityManager, new SubscriberChangesNotifier($wpMock), $wpMock), $this->getServiceWithOverrides(SubscribersRepository::class,
[
'changesNotifier' => new SubscriberChangesNotifier($wpMock),
'wp' => $wpMock,
]
),
], [ ], [
'returnResponse' => null, 'returnResponse' => null,
], $this); ], $this);

View File

@@ -398,6 +398,23 @@ class SubscribersRepositoryTest extends \MailPoetTest {
verify($this->repository->getMaxSubscriberId())->equals($subscriberTwo->getId()); verify($this->repository->getMaxSubscriberId())->equals($subscriberTwo->getId());
} }
public function testRemoveOrphanedSubscribersFromWpSegment(): void {
$wpUserId1 = $this->tester->createWordPressUser('subscriber1@email.com', 'author');
$wpUserId2 = $this->tester->createWordPressUser('subscriber2@email.com', 'author');
$wpUserId3 = $this->tester->createWordPressUser('subscriber3@email.com', 'author');
$subscriber2 = $this->repository->findOneBy(['wpUserId' => $wpUserId2]);
$subscriber3 = $this->repository->findOneBy(['wpUserId' => $wpUserId3]);
$this->tester->deleteWPUserFromDatabase($wpUserId1);
$this->repository->removeOrphanedSubscribersFromWpSegment();
$subscribers = $this->repository->findAll();
$this->assertCount(2, $subscribers);
$this->assertSame($subscribers[0], $subscriber2);
$this->assertSame($subscribers[1], $subscriber3);
}
private function createSubscriber(string $email, ?DateTimeImmutable $deletedAt = null): SubscriberEntity { private function createSubscriber(string $email, ?DateTimeImmutable $deletedAt = null): SubscriberEntity {
$subscriber = new SubscriberEntity(); $subscriber = new SubscriberEntity();
$subscriber->setEmail($email); $subscriber->setEmail($email);

View File

@@ -28,9 +28,15 @@ class SubscriberEngagementTest extends \MailPoetTest {
public function _before() { public function _before() {
$this->wooCommerceHelperMock = $this->createMock(Helper::class); $this->wooCommerceHelperMock = $this->createMock(Helper::class);
$this->wpMock = $this->createMock(WPFunctions::class); $this->wpMock = $this->createMock(WPFunctions::class);
$subscribersRepository = $this->getServiceWithOverrides(SubscribersRepository::class,
[
'changesNotifier' => new SubscriberChangesNotifier($this->wpMock),
'wp' => $this->wpMock,
]
);
$this->subscriberEngagement = new SubscriberEngagement( $this->subscriberEngagement = new SubscriberEngagement(
$this->wooCommerceHelperMock, $this->wooCommerceHelperMock,
new SubscribersRepository($this->entityManager, new SubscriberChangesNotifier($this->wpMock), $this->wpMock) $subscribersRepository
); );
} }