Files
piratepoet/mailpoet/lib/Subscribers/InactiveSubscribersController.php
Rodrigo Primo efab3be9ae Remove logic to handle MP2 subscribers when deactivating subscribers
This commit removes the logic that was added in #2045 to handle
subscribers migrated from MP2 when deactivating subscribers. Without it,
MP3 would deactive all subscribers imported from MP2 as the subscribe
date is migrated but the stats are not (see
https://mailpoet.atlassian.net/browse/MAILPOET-2040) for more details.

This code is not necessary anymore as we are removing all the MP2 migration
related code.

[MAILPOET-4376]
2022-08-15 12:46:22 +02:00

183 lines
7.1 KiB
PHP

<?php declare(strict_types = 1);
namespace MailPoet\Subscribers;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoetVendor\Carbon\Carbon;
use MailPoetVendor\Doctrine\DBAL\Connection;
use MailPoetVendor\Doctrine\ORM\EntityManager;
class InactiveSubscribersController {
const UNOPENED_EMAILS_THRESHOLD = 3;
const LIFETIME_EMAILS_THRESHOLD = 10;
private $processedTaskIdsTableCreated = false;
/** @var EntityManager */
private $entityManager;
public function __construct(
EntityManager $entityManager
) {
$this->entityManager = $entityManager;
}
public function markInactiveSubscribers(int $daysToInactive, int $batchSize, ?int $startId = null, ?int $unopenedEmails = self::UNOPENED_EMAILS_THRESHOLD) {
$thresholdDate = $this->getThresholdDate($daysToInactive);
return $this->deactivateSubscribers($thresholdDate, $batchSize, $startId, $unopenedEmails);
}
public function markActiveSubscribers(int $daysToInactive, int $batchSize): int {
$thresholdDate = $this->getThresholdDate($daysToInactive);
return $this->activateSubscribers($thresholdDate, $batchSize);
}
public function reactivateInactiveSubscribers(): void {
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
$reactivateAllInactiveQuery = "
UPDATE {$subscribersTable} SET status = :statusSubscribed WHERE status = :statusInactive
";
$this->entityManager->getConnection()->executeQuery($reactivateAllInactiveQuery, [
'statusSubscribed' => SubscriberEntity::STATUS_SUBSCRIBED,
'statusInactive' => SubscriberEntity::STATUS_INACTIVE,
]);
}
private function getThresholdDate(int $daysToInactive): Carbon {
$now = new Carbon();
return $now->subDays($daysToInactive);
}
/**
* @return int
*/
private function deactivateSubscribers(Carbon $thresholdDate, int $batchSize, ?int $startId = null, ?int $unopenedEmails = self::UNOPENED_EMAILS_THRESHOLD) {
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
$scheduledTasksTable = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
$scheduledTaskSubscribersTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
$sendingQueuesTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
$connection = $this->entityManager->getConnection();
$thresholdDateIso = $thresholdDate->toDateTimeString();
$dayAgo = new Carbon();
$dayAgoIso = $dayAgo->subDay()->toDateTimeString();
// Temporary table with processed tasks from threshold date up to yesterday
$processedTaskIdsTable = 'inactive_task_ids';
if (!$this->processedTaskIdsTableCreated) {
$processedTaskIdsTableSql = "
CREATE TEMPORARY TABLE IF NOT EXISTS {$processedTaskIdsTable}
(INDEX task_id_ids (id))
SELECT DISTINCT task_id as id FROM {$sendingQueuesTable} as sq
JOIN {$scheduledTasksTable} as st ON sq.task_id = st.id
WHERE st.processed_at > :thresholdDate
AND st.processed_at < :dayAgo
";
$connection->executeQuery($processedTaskIdsTableSql, [
'thresholdDate' => $thresholdDateIso,
'dayAgo' => $dayAgoIso,
]);
$this->processedTaskIdsTableCreated = true;
}
// Select subscribers who received at least a number of emails after threshold date and subscribed before that
$startId = (int)$startId;
$endId = $startId + $batchSize;
$lifetimeEmailsThreshold = self::LIFETIME_EMAILS_THRESHOLD;
$inactiveSubscriberIdsTmpTable = 'inactive_subscriber_ids';
$connection->executeQuery("
CREATE TEMPORARY TABLE IF NOT EXISTS {$inactiveSubscriberIdsTmpTable}
(UNIQUE subscriber_id (id))
SELECT s.id FROM {$subscribersTable} as s
JOIN {$scheduledTaskSubscribersTable} as sts USE INDEX (subscriber_id) ON s.id = sts.subscriber_id
JOIN {$processedTaskIdsTable} task_ids ON task_ids.id = sts.task_id
WHERE s.last_subscribed_at < :thresholdDate
AND s.status = :status
AND s.id >= :startId
AND s.id < :endId
AND s.email_count >= {$lifetimeEmailsThreshold}
GROUP BY s.id
HAVING count(s.id) >= :unopenedEmailsThreshold
",
[
'thresholdDate' => $thresholdDateIso,
'status' => SubscriberEntity::STATUS_SUBSCRIBED,
'startId' => $startId,
'endId' => $endId,
'unopenedEmailsThreshold' => $unopenedEmails,
]);
$result = $connection->executeQuery("
SELECT isi.id FROM {$inactiveSubscriberIdsTmpTable} isi
LEFT OUTER JOIN {$subscribersTable} as s ON isi.id = s.id AND GREATEST(
COALESCE(s.last_engagement_at, '0'),
COALESCE(s.last_subscribed_at, '0'),
COALESCE(s.created_at, '0')
) > :thresholdDate
WHERE s.id IS NULL
", [
'thresholdDate' => $thresholdDateIso,
]);
$idsToDeactivate = $result->fetchAllAssociative();
$connection->executeQuery("DROP TABLE {$inactiveSubscriberIdsTmpTable}");
$idsToDeactivate = array_map(
function ($id) {
return (int)$id['id'];
},
$idsToDeactivate
);
if (!count($idsToDeactivate)) {
return 0;
}
$connection->executeQuery("UPDATE {$subscribersTable} SET status = :statusInactive WHERE id IN (:idsToDeactivate)", [
'statusInactive' => SubscriberEntity::STATUS_INACTIVE,
'idsToDeactivate' => $idsToDeactivate,
], ['idsToDeactivate' => Connection::PARAM_INT_ARRAY]);
return count($idsToDeactivate);
}
private function activateSubscribers(Carbon $thresholdDate, int $batchSize): int {
$subscribersTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
$connection = $this->entityManager->getConnection();
$idsToActivate = $connection->executeQuery("
SELECT s.id
FROM {$subscribersTable} s
LEFT OUTER JOIN {$subscribersTable} s2 ON s.id = s2.id AND GREATEST(
COALESCE(s2.last_engagement_at, '0'),
COALESCE(s2.last_subscribed_at, '0'),
COALESCE(s2.created_at, '0')
) > :thresholdDate
WHERE s.last_subscribed_at < :thresholdDate
AND s.status = :statusInactive
AND s2.id IS NOT NULL
GROUP BY s.id
LIMIT :batchSize
", [
'thresholdDate' => $thresholdDate,
'statusInactive' => SubscriberEntity::STATUS_INACTIVE,
'batchSize' => $batchSize,
], ['batchSize' => \PDO::PARAM_INT])->fetchAllAssociative();
$idsToActivate = array_map(
function($id) {
return (int)$id['id'];
}, $idsToActivate
);
if (!count($idsToActivate)) {
return 0;
}
$connection->executeQuery("UPDATE {$subscribersTable} SET status = :statusSubscribed WHERE id IN (:idsToActivate)", [
'statusSubscribed' => SubscriberEntity::STATUS_SUBSCRIBED,
'idsToActivate' => $idsToActivate,
], ['idsToActivate' => Connection::PARAM_INT_ARRAY]);
return count($idsToActivate);
}
}