geno/wp-content/plugins/mailpoet/lib/API/JSON/ResponseBuilders/NewslettersResponseBuilder.php
2024-02-01 11:54:18 +00:00

304 lines
13 KiB
PHP

<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\API\JSON\ResponseBuilders;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\DynamicSegmentFilterEntity;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\SendingQueueEntity;
use MailPoet\Logging\LoggerFactory;
use MailPoet\Logging\LogRepository;
use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
use MailPoet\Newsletter\Statistics\NewsletterStatistics;
use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository;
use MailPoet\Newsletter\Url as NewsletterUrl;
use MailPoetVendor\Doctrine\ORM\EntityManager;
class NewslettersResponseBuilder {
const DATE_FORMAT = 'Y-m-d H:i:s';
const RELATION_QUEUE = 'queue';
const RELATION_SEGMENTS = 'segments';
const RELATION_OPTIONS = 'options';
const RELATION_TOTAL_SENT = 'total_sent';
const RELATION_CHILDREN_COUNT = 'children_count';
const RELATION_SCHEDULED = 'scheduled';
const RELATION_STATISTICS = 'statistics';
/** @var NewsletterStatisticsRepository */
private $newslettersStatsRepository;
/** @var NewslettersRepository */
private $newslettersRepository;
/** @var EntityManager */
private $entityManager;
/** @var NewsletterUrl */
private $newsletterUrl;
/** @var SendingQueuesRepository */
private $sendingQueuesRepository;
/*** @var LogRepository */
private $logRepository;
public function __construct(
EntityManager $entityManager,
NewslettersRepository $newslettersRepository,
NewsletterStatisticsRepository $newslettersStatsRepository,
NewsletterUrl $newsletterUrl,
SendingQueuesRepository $sendingQueuesRepository,
LogRepository $logRepository
) {
$this->newslettersStatsRepository = $newslettersStatsRepository;
$this->newslettersRepository = $newslettersRepository;
$this->entityManager = $entityManager;
$this->newsletterUrl = $newsletterUrl;
$this->sendingQueuesRepository = $sendingQueuesRepository;
$this->logRepository = $logRepository;
}
public function build(NewsletterEntity $newsletter, $relations = []) {
$data = [
'id' => (string)$newsletter->getId(), // (string) for BC
'hash' => $newsletter->getHash(),
'subject' => $newsletter->getSubject(),
'type' => $newsletter->getType(),
'sender_address' => $newsletter->getSenderAddress(),
'sender_name' => $newsletter->getSenderName(),
'status' => $newsletter->getStatus(),
'reply_to_address' => $newsletter->getReplyToAddress(),
'reply_to_name' => $newsletter->getReplyToName(),
'preheader' => $newsletter->getPreheader(),
'body' => $newsletter->getBody(),
'sent_at' => ($sentAt = $newsletter->getSentAt()) ? $sentAt->format(self::DATE_FORMAT) : null,
'created_at' => ($createdAt = $newsletter->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
'updated_at' => $newsletter->getUpdatedAt()->format(self::DATE_FORMAT),
'deleted_at' => ($deletedAt = $newsletter->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
'parent_id' => ($parent = $newsletter->getParent()) ? $parent->getId() : null,
'unsubscribe_token' => $newsletter->getUnsubscribeToken(),
'ga_campaign' => $newsletter->getGaCampaign(),
'wp_post_id' => $newsletter->getWpPostId(),
];
foreach ($relations as $relation) {
if ($relation === self::RELATION_QUEUE) {
$data['queue'] = ($queue = $newsletter->getLatestQueue()) ? $this->buildQueue($queue) : false; // false for BC
}
if ($relation === self::RELATION_SEGMENTS) {
$data['segments'] = $this->buildSegments($newsletter);
}
if ($relation === self::RELATION_OPTIONS) {
$data['options'] = $this->buildOptions($newsletter);
}
if ($relation === self::RELATION_TOTAL_SENT) {
$data['total_sent'] = $this->newslettersStatsRepository->getTotalSentCount($newsletter);
}
if ($relation === self::RELATION_CHILDREN_COUNT) {
$data['children_count'] = $this->newslettersStatsRepository->getChildrenCount($newsletter);
}
if ($relation === self::RELATION_SCHEDULED) {
$data['total_scheduled'] = $this->sendingQueuesRepository->countAllByNewsletterAndTaskStatus(
$newsletter,
SendingQueueEntity::STATUS_SCHEDULED
);
}
if ($relation === self::RELATION_STATISTICS) {
$data['statistics'] = $this->newslettersStatsRepository->getStatistics($newsletter)->asArray();
}
}
return $data;
}
/**
* @param NewsletterEntity[] $newsletters
* @return mixed[]
*/
public function buildForListing(array $newsletters): array {
$statistics = $this->newslettersStatsRepository->getBatchStatistics($newsletters);
$latestQueues = $this->getBatchLatestQueuesWithTasks($newsletters);
$this->newslettersRepository->prefetchOptions($newsletters);
$this->newslettersRepository->prefetchSegments($newsletters);
$data = [];
foreach ($newsletters as $newsletter) {
$id = $newsletter->getId();
$data[] = $this->buildListingItem($newsletter, $statistics[$id] ?? null, $latestQueues[$id] ?? null);
}
return $data;
}
private function buildListingItem(NewsletterEntity $newsletter, NewsletterStatistics $statistics = null, SendingQueueEntity $latestQueue = null): array {
$couponBlockLogs = array_map(function ($item) {
return "Coupon block: $item";
}, $this->logRepository->getRawMessagesForNewsletter($newsletter, LoggerFactory::TOPIC_COUPONS));
$data = [
'id' => (string)$newsletter->getId(), // (string) for BC
'hash' => $newsletter->getHash(),
'subject' => $newsletter->getSubject(),
'type' => $newsletter->getType(),
'status' => $newsletter->getStatus(),
'sent_at' => ($sentAt = $newsletter->getSentAt()) ? $sentAt->format(self::DATE_FORMAT) : null,
'updated_at' => $newsletter->getUpdatedAt()->format(self::DATE_FORMAT),
'deleted_at' => ($deletedAt = $newsletter->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
'segments' => [],
'queue' => false,
'wp_post_id' => $newsletter->getWpPostId(),
'statistics' => ($statistics && $newsletter->getType() !== NewsletterEntity::TYPE_NOTIFICATION)
? $statistics->asArray()
: false,
'preview_url' => $this->newsletterUrl->getViewInBrowserUrl(
$newsletter,
null,
in_array($newsletter->getStatus(), [NewsletterEntity::STATUS_SENT, NewsletterEntity::STATUS_SENDING], true)
? $latestQueue
: null
),
'logs' => $couponBlockLogs,
];
if ($newsletter->getType() === NewsletterEntity::TYPE_STANDARD) {
$data['segments'] = $this->buildSegments($newsletter);
$data['queue'] = $latestQueue ? $this->buildQueue($latestQueue) : false; // false for BC
$data['options'] = $this->buildOptions($newsletter);
} elseif (in_array($newsletter->getType(), [NewsletterEntity::TYPE_WELCOME, NewsletterEntity::TYPE_AUTOMATIC], true)) {
$data['segments'] = [];
$data['options'] = $this->buildOptions($newsletter);
$data['total_sent'] = $statistics ? $statistics->getTotalSentCount() : 0;
$data['total_scheduled'] = $this->sendingQueuesRepository->countAllByNewsletterAndTaskStatus(
$newsletter,
SendingQueueEntity::STATUS_SCHEDULED
);
} elseif ($newsletter->getType() === NewsletterEntity::TYPE_NOTIFICATION) {
$data['segments'] = $this->buildSegments($newsletter);
$data['children_count'] = $this->newslettersStatsRepository->getChildrenCount($newsletter);
$data['options'] = $this->buildOptions($newsletter);
} elseif ($newsletter->getType() === NewsletterEntity::TYPE_NOTIFICATION_HISTORY) {
$data['segments'] = $this->buildSegments($newsletter);
$data['queue'] = $latestQueue ? $this->buildQueue($latestQueue) : false; // false for BC
} elseif ($newsletter->getType() === NewsletterEntity::TYPE_RE_ENGAGEMENT) {
$data['segments'] = $this->buildSegments($newsletter);
$data['options'] = $this->buildOptions($newsletter);
$data['total_sent'] = $statistics ? $statistics->getTotalSentCount() : 0;
}
return $data;
}
private function buildSegments(NewsletterEntity $newsletter) {
$output = [];
foreach ($newsletter->getNewsletterSegments() as $newsletterSegment) {
$segment = $newsletterSegment->getSegment();
if (!$segment || $segment->getDeletedAt()) {
continue;
}
$output[] = $this->buildSegment($segment);
}
return $output;
}
private function buildOptions(NewsletterEntity $newsletter) {
$output = [];
foreach ($newsletter->getOptions() as $option) {
$optionField = $option->getOptionField();
if (!$optionField) {
continue;
}
$output[$optionField->getName()] = $option->getValue();
}
// convert 'afterTimeNumber' string to integer
if (isset($output['afterTimeNumber']) && is_numeric($output['afterTimeNumber'])) {
$output['afterTimeNumber'] = (int)$output['afterTimeNumber'];
}
return $output;
}
private function buildSegment(SegmentEntity $segment) {
$filters = $segment->getType() === SegmentEntity::TYPE_DYNAMIC ? $segment->getDynamicFilters()->toArray() : [];
return [
'id' => (string)$segment->getId(), // (string) for BC
'name' => $segment->getName(),
'type' => $segment->getType(),
'filters' => array_map(function(DynamicSegmentFilterEntity $filter) {
return [
'action' => $filter->getFilterData()->getAction(),
'type' => $filter->getFilterData()->getFilterType(),
];
}, $filters),
'description' => $segment->getDescription(),
'created_at' => ($createdAt = $segment->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
'updated_at' => $segment->getUpdatedAt()->format(self::DATE_FORMAT),
'deleted_at' => ($deletedAt = $segment->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
];
}
private function buildQueue(SendingQueueEntity $queue) {
$task = $queue->getTask();
if ($task === null) {
return null;
}
// the following crazy mix of '$queue' and '$task' comes from 'array_merge($task, $queue)'
// (MailPoet\Tasks\Sending) which means all equal-named fields will be taken from '$queue'
return [
'id' => (string)$queue->getId(), // (string) for BC
'type' => $task->getType(),
'status' => $task->getStatus(),
'priority' => (string)$task->getPriority(), // (string) for BC
'scheduled_at' => ($scheduledAt = $task->getScheduledAt()) ? $scheduledAt->format(self::DATE_FORMAT) : null,
'processed_at' => ($processedAt = $task->getProcessedAt()) ? $processedAt->format(self::DATE_FORMAT) : null,
'created_at' => ($createdAt = $queue->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
'updated_at' => $queue->getUpdatedAt()->format(self::DATE_FORMAT),
'deleted_at' => ($deletedAt = $queue->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
'meta' => $queue->getMeta(),
'task_id' => (string)$task->getId(), // (string) for BC
'newsletter_id' => ($newsletter = $queue->getNewsletter()) ? (string)$newsletter->getId() : null, // (string) for BC
'newsletter_rendered_subject' => $queue->getNewsletterRenderedSubject(),
'count_total' => (string)$queue->getCountTotal(), // (string) for BC
'count_processed' => (string)$queue->getCountProcessed(), // (string) for BC
'count_to_process' => (string)$queue->getCountToProcess(), // (string) for BC
];
}
private function getBatchLatestQueuesWithTasks(array $newsletters): array {
// this implements the same logic as NewsletterEntity::getLatestQueue() but for a batch of $newsletters
$subqueryQueryBuilder = $this->entityManager->createQueryBuilder();
$subquery = $subqueryQueryBuilder
->select('MAX(subSq.id) AS maxId')
->from(SendingQueueEntity::class, 'subSq')
->where('subSq.newsletter IN (:newsletters)')
->setParameter('newsletters', $newsletters)
->groupBy('subSq.newsletter')
->getQuery();
$latestQueueIds = array_column($subquery->getResult(), 'maxId');
if (empty($latestQueueIds)) {
return [];
}
$queryBuilder = $this->entityManager->createQueryBuilder();
$results = $queryBuilder
->select('PARTIAL sq.{id, createdAt, updatedAt, deletedAt, meta, newsletterRenderedSubject, countTotal, countProcessed, countToProcess}')
->addSelect('PARTIAL t.{id, type, status, priority, scheduledAt, processedAt}')
->addSelect('IDENTITY(sq.newsletter)')
->from(SendingQueueEntity::class, 'sq')
->join('sq.task', 't')
->where('sq.id IN (:sub)')
->setParameter('sub', $latestQueueIds)
->getQuery()
->getResult();
$latestQueues = [];
foreach ($results as $result) {
$latestQueues[(int)$result[1]] = $result[0];
}
return $latestQueues;
}
}