This is only tangentially related to this ticket. I noticed during testing that the filter segment tag was not showing up in listings for standard newsletters that were drafts or scheduled. This is because options weren't included in the response for standard newsletters. MAILPOET-5512
302 lines
13 KiB
PHP
302 lines
13 KiB
PHP
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
|
|
|
namespace MailPoet\API\JSON\ResponseBuilders;
|
|
|
|
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;
|
|
}
|
|
}
|