This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,33 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\API\JSON\ResponseBuilders;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\CustomFieldEntity;
class CustomFieldsResponseBuilder {
/**
* @param CustomFieldEntity[] $customFields
* @return array
*/
public function buildBatch(array $customFields) {
return array_map([$this, 'build'], $customFields);
}
/**
* @param CustomFieldEntity $customField
* @return array
*/
public function build(CustomFieldEntity $customField) {
return [
'id' => $customField->getId(),
'name' => $customField->getName(),
'type' => $customField->getType(),
'params' => $customField->getParams(),
'created_at' => ($createdAt = $customField->getCreatedAt()) ? $createdAt->format('Y-m-d H:i:s') : null,
'updated_at' => $customField->getUpdatedAt()->format('Y-m-d H:i:s'),
];
}
}
@@ -0,0 +1,120 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\API\JSON\ResponseBuilders;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\DynamicSegmentFilterData;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Segments\SegmentDependencyValidator;
use MailPoet\Segments\SegmentSubscribersRepository;
use MailPoet\Subscribers\SubscribersCountsController;
use MailPoet\WP\Functions;
class DynamicSegmentsResponseBuilder {
const DATE_FORMAT = 'Y-m-d H:i:s';
/** @var SegmentsResponseBuilder */
private $segmentsResponseBuilder;
/** @var Functions */
private $wp;
/** @var SegmentSubscribersRepository */
private $segmentSubscriberRepository;
/** @var SegmentDependencyValidator */
private $segmentDependencyValidator;
/** @var SubscribersCountsController */
private $subscribersCountsController;
public function __construct(
Functions $wp,
SegmentSubscribersRepository $segmentSubscriberRepository,
SegmentsResponseBuilder $segmentsResponseBuilder,
SegmentDependencyValidator $segmentDependencyValidator,
SubscribersCountsController $subscribersCountsController
) {
$this->segmentsResponseBuilder = $segmentsResponseBuilder;
$this->segmentSubscriberRepository = $segmentSubscriberRepository;
$this->wp = $wp;
$this->segmentDependencyValidator = $segmentDependencyValidator;
$this->subscribersCountsController = $subscribersCountsController;
}
public function build(SegmentEntity $segmentEntity) {
$data = $this->segmentsResponseBuilder->build($segmentEntity);
$data = $this->addMissingPluginProperties($segmentEntity, $data);
$dynamicFilters = $segmentEntity->getDynamicFilters();
$filters = [];
foreach ($dynamicFilters as $dynamicFilter) {
$filter = $dynamicFilter->getFilterData()->getData();
$filter['id'] = $dynamicFilter->getId();
$filter['segmentType'] = $dynamicFilter->getFilterData()->getFilterType(); // We need to add filterType with key segmentType due to BC
$filter['action'] = $dynamicFilter->getFilterData()->getAction();
if (isset($filter['country_code']) && !is_array($filter['country_code'])) {
// Convert to multiple values filter
$filter['country_code'] = [$filter['country_code']];
}
if (isset($filter['wordpressRole']) && !is_array($filter['wordpressRole'])) {
// new filters are always array, they support multiple values, the old didn't convert old filters to new format
$filter['wordpressRole'] = [$filter['wordpressRole']];
}
if (($filter['segmentType'] === DynamicSegmentFilterData::TYPE_EMAIL) && isset($filter['newsletter_id'])) {
$filter['newsletter_id'] = intval($filter['newsletter_id']);
}
$filters[] = $filter;
}
$data['filters'] = $filters;
return $data;
}
public function buildForListing(array $segments): array {
$data = [];
foreach ($segments as $segment) {
$data[] = $this->buildListingItem($segment);
}
return $data;
}
private function addMissingPluginProperties(SegmentEntity $segment, array $data): array {
$missingPlugins = $this->segmentDependencyValidator->getMissingPluginsBySegment($segment);
if ($missingPlugins) {
$missingPlugin = reset($missingPlugins);
$data['is_plugin_missing'] = true;
$missingPluginMessage = $this->segmentDependencyValidator->getCustomErrorMessage($missingPlugin);
if ($missingPluginMessage) {
$data['missing_plugin_message'] = $missingPluginMessage;
} else {
$data['missing_plugin_message']['message'] =
sprintf(
// translators: %s is the name of the missing plugin.
__('Activate the %s plugin to see the number of subscribers and enable the editing of this segment.', 'mailpoet'),
$missingPlugin
);
}
} else {
$data['is_plugin_missing'] = false;
$data['missing_plugin_message'] = null;
}
return $data;
}
private function buildListingItem(SegmentEntity $segment): array {
$data = $this->segmentsResponseBuilder->build($segment);
$data = $this->addMissingPluginProperties($segment, $data);
$data['subscribers_url'] = $this->wp->adminUrl(
'admin.php?page=mailpoet-subscribers#/filter[segment=' . $segment->getId() . ']'
);
$segmentStatisticsCount = $this->subscribersCountsController->getSegmentStatisticsCount($segment);
$data['count_all'] = $segmentStatisticsCount['all'];
$data['count_subscribed'] = $segmentStatisticsCount[SubscriberEntity::STATUS_SUBSCRIBED];
return $data;
}
}
@@ -0,0 +1,54 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\API\JSON\ResponseBuilders;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\FormEntity;
use MailPoet\Statistics\StatisticsFormsRepository;
class FormsResponseBuilder {
const DATE_FORMAT = 'Y-m-d H:i:s';
/** @var StatisticsFormsRepository */
private $statisticsFormsRepository;
public function __construct(
StatisticsFormsRepository $statisticsFormsRepository
) {
$this->statisticsFormsRepository = $statisticsFormsRepository;
}
public function build(FormEntity $form) {
return [
'id' => (string)$form->getId(), // (string) for BC
'name' => $form->getName(),
'status' => $form->getStatus(),
'body' => $form->getBody(),
'settings' => $form->getSettings(),
'styles' => $form->getStyles(),
'created_at' => ($createdAt = $form->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
'updated_at' => $form->getUpdatedAt()->format(self::DATE_FORMAT),
'deleted_at' => ($deletedAt = $form->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
];
}
public function buildForListing(array $forms) {
$data = [];
foreach ($forms as $form) {
$form = $this->build($form);
$form['signups'] = $this->statisticsFormsRepository->getTotalSignups($form['id']);
$form['segments'] = (
!empty($form['settings']['segments'])
? $form['settings']['segments']
: []
);
$data[] = $form;
}
return $data;
}
}
@@ -0,0 +1,44 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\API\JSON\ResponseBuilders;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\NewsletterTemplateEntity;
class NewsletterTemplatesResponseBuilder {
const DATE_FORMAT = 'Y-m-d H:i:s';
public function build(NewsletterTemplateEntity $template): array {
return [
'id' => $template->getId(),
'categories' => $template->getCategories(),
'thumbnail' => $template->getThumbnail(),
'name' => $template->getName(),
'readonly' => $template->getReadonly(),
'body' => $template->getBody(),
'created_at' => ($createdAt = $template->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
'updated_at' => $template->getUpdatedAt()->format(self::DATE_FORMAT),
'newsletter_id' => ($newsletter = $template->getNewsletter()) ? $newsletter->getId() : null,
];
}
/**
* @param NewsletterTemplateEntity[] $newsletterTemplates
* @return mixed[]
*/
public function buildForListing(array $newsletterTemplates): array {
$data = [];
foreach ($newsletterTemplates as $template) {
$data[] = [
'id' => $template->getId(),
'categories' => $template->getCategories(),
'thumbnail' => $template->getThumbnail(),
'name' => $template->getName(),
'readonly' => $template->getReadonly(),
];
}
return $data;
}
}
@@ -0,0 +1,328 @@
<?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(),
'campaign_name' => $newsletter->getCampaignName(),
];
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->countAllToProcessByNewsletter(
$newsletter
);
}
if ($relation === self::RELATION_STATISTICS) {
$data['statistics'] = $this->newslettersStatsRepository->getStatistics($newsletter)->asArray();
}
}
return $data;
}
private function processPersonalizationTags(?string $content): ?string {
if (is_null($content) || strlen($content) === 0) {
return $content;
}
if (strpos($content, '<!--') === false) {
// we don't need to parse anything if there are no personalization tags
return $content;
}
if (!class_exists('\MailPoet\EmailEditor\Engine\PersonalizationTags\HTML_Tag_Processor')) {
// editor is not active, we cannot process personalization tags
return $content;
}
$content_processor = new \MailPoet\EmailEditor\Engine\PersonalizationTags\HTML_Tag_Processor($content);
while ($content_processor->next_token()) {
$type = $content_processor->get_token_type();
if ($type === '#comment') {
$token = $content_processor->get_modifiable_text();
$content_processor->replace_token($token);
}
}
$content_processor->flush_updates();
return $content_processor->get_updated_html();
}
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;
}
/**
* @param NewsletterEntity $newsletter
* @param NewsletterStatistics|null $statistics
* @param SendingQueueEntity|null $latestQueue
* @return array<string, mixed>
*/
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' => $this->processPersonalizationTags($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,
'campaign_name' => $newsletter->getCampaignName(),
];
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->countAllToProcessByNewsletter(
$newsletter
);
} 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;
}
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' => $this->processPersonalizationTags($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;
}
}
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);
namespace MailPoet\API\JSON\ResponseBuilders;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
class ScheduledTaskSubscriberResponseBuilder {
public function build(ScheduledTaskSubscriberEntity $scheduledSubscriber) {
$subscriber = $scheduledSubscriber->getSubscriber();
$task = $scheduledSubscriber->getTask();
return [
'processed' => $scheduledSubscriber->getProcessed(),
'failed' => $scheduledSubscriber->getFailed(),
'error' => $scheduledSubscriber->getError(),
'taskId' => $task ? $task->getId() : null,
'email' => $subscriber ? $subscriber->getEmail() : null,
'subscriberId' => $subscriber ? $subscriber->getId() : null,
'firstName' => $subscriber ? $subscriber->getFirstName() : null,
'lastName' => $subscriber ? $subscriber->getLastName() : null,
];
}
public function buildForListing(array $scheduledSubscribers) {
$data = [];
foreach ($scheduledSubscribers as $scheduledSubscriber) {
$data[] = $this->build($scheduledSubscriber);
}
return $data;
}
}
@@ -0,0 +1,61 @@
<?php declare(strict_types = 1);
namespace MailPoet\API\JSON\ResponseBuilders;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Subscribers\SubscribersCountsController;
use MailPoet\WP\Functions;
class SegmentsResponseBuilder {
const DATE_FORMAT = 'Y-m-d H:i:s';
/** @var Functions */
private $wp;
/** @var SubscribersCountsController */
private $subscribersCountsController;
public function __construct(
Functions $wp,
SubscribersCountsController $subscribersCountsController
) {
$this->wp = $wp;
$this->subscribersCountsController = $subscribersCountsController;
}
public function build(SegmentEntity $segment): array {
return [
'id' => (string)$segment->getId(), // (string) for BC
'name' => $segment->getName(),
'type' => $segment->getType(),
'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,
'average_engagement_score' => $segment->getAverageEngagementScore(),
'filters_connect' => $segment->getFiltersConnectOperator(),
'showInManageSubscriptionPage' => (int)$segment->getDisplayInManageSubscriptionPage(),
];
}
public function buildForListing(array $segments): array {
$data = [];
foreach ($segments as $segment) {
$data[] = $this->buildListingItem($segment);
}
return $data;
}
private function buildListingItem(SegmentEntity $segment): array {
$data = $this->build($segment);
$data['subscribers_count'] = $this->subscribersCountsController->getSegmentStatisticsCount($segment);
$data['subscribers_url'] = $this->wp->adminUrl(
'admin.php?page=mailpoet-subscribers#/filter[segment=' . $segment->getId() . ']'
);
return $data;
}
}
@@ -0,0 +1,44 @@
<?php declare(strict_types = 1);
namespace MailPoet\API\JSON\ResponseBuilders;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\SendingQueueEntity;
class SendingQueuesResponseBuilder {
public function build(SendingQueueEntity $sendingQueue): array {
if (!$sendingQueue->getTask() instanceof ScheduledTaskEntity) {
throw new \RuntimeException('Invalid state. SendingQueue has no ScheduledTask associated.');
}
return [
'id' => $sendingQueue->getId(),
'type' => $sendingQueue->getTask()->getType(),
'status' => $sendingQueue->getTask()->getStatus(),
'priority' => $sendingQueue->getTask()->getPriority(),
'scheduled_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getScheduledAt()),
'processed_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getProcessedAt()),
'created_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getCreatedAt()),
'updated_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getUpdatedAt()),
'deleted_at' => $this->getFormattedDateOrNull($sendingQueue->getTask()->getDeletedAt()),
'in_progress' => $sendingQueue->getTask()->getInProgress(),
'reschedule_count' => $sendingQueue->getTask()->getRescheduleCount(),
'meta' => $sendingQueue->getMeta(),
'task_id' => $sendingQueue->getTask()->getId(),
'newsletter_id' => ($sendingQueue->getNewsletter() instanceof NewsletterEntity) ? $sendingQueue->getNewsletter()->getId() : null,
'newsletter_rendered_body' => $sendingQueue->getNewsletterRenderedBody(),
'newsletter_rendered_subject' => $sendingQueue->getNewsletterRenderedSubject(),
'count_total' => $sendingQueue->getCountTotal(),
'count_processed' => $sendingQueue->getCountProcessed(),
'count_to_process' => $sendingQueue->getCountToProcess(),
];
}
private function getFormattedDateOrNull(?\DateTimeInterface $date): ?string {
return $date ? $date->format('Y-m-d H:i:s') : null;
}
}
@@ -0,0 +1,199 @@
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\API\JSON\ResponseBuilders;
if (!defined('ABSPATH')) exit;
use MailPoet\CustomFields\CustomFieldsRepository;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\SegmentEntity;
use MailPoet\Entities\SubscriberCustomFieldEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Statistics\StatisticsUnsubscribesRepository;
use MailPoet\Subscribers\SubscriberCustomFieldRepository;
use MailPoetVendor\Doctrine\ORM\EntityManager;
class SubscribersResponseBuilder {
const DATE_FORMAT = 'Y-m-d H:i:s';
/** @var StatisticsUnsubscribesRepository */
private $statisticsUnsubscribesRepository;
/** @var CustomFieldsRepository */
private $customFieldsRepository;
/** @var SubscriberCustomFieldRepository */
private $subscriberCustomFieldRepository;
/** @var EntityManager */
private $entityManager;
public function __construct(
EntityManager $entityManager,
CustomFieldsRepository $customFieldsRepository,
SubscriberCustomFieldRepository $subscriberCustomFieldRepository,
StatisticsUnsubscribesRepository $statisticsUnsubscribesRepository
) {
$this->statisticsUnsubscribesRepository = $statisticsUnsubscribesRepository;
$this->customFieldsRepository = $customFieldsRepository;
$this->subscriberCustomFieldRepository = $subscriberCustomFieldRepository;
$this->entityManager = $entityManager;
}
public function buildForListing(array $subscribers): array {
$this->prefetchRelations($subscribers);
$data = [];
foreach ($subscribers as $subscriber) {
$data[] = $this->buildListingItem($subscriber);
}
return $data;
}
private function buildListingItem(SubscriberEntity $subscriber): array {
return [
'id' => (string)$subscriber->getId(), // (string) for BC
'email' => $subscriber->getEmail(),
'first_name' => $subscriber->getFirstName(),
'last_name' => $subscriber->getLastName(),
'subscriptions' => $this->buildSubscriptions($subscriber),
'status' => $subscriber->getStatus(),
'count_confirmations' => $subscriber->getConfirmationsCount(),
'wp_user_id' => $subscriber->getWpUserId(),
'is_woocommerce_user' => $subscriber->getIsWoocommerceUser(),
'created_at' => ($createdAt = $subscriber->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
'last_subscribed_at' => ($lastSubscribedAt = $subscriber->getLastSubscribedAt()) ? $lastSubscribedAt->format(self::DATE_FORMAT) : null,
'engagement_score' => $subscriber->getEngagementScore(),
'tags' => $this->buildTags($subscriber),
];
}
public function build(SubscriberEntity $subscriberEntity): array {
$data = [
'id' => (string)$subscriberEntity->getId(),
'wp_user_id' => $subscriberEntity->getWpUserId(),
'is_woocommerce_user' => $subscriberEntity->getIsWoocommerceUser(),
'subscriptions' => $this->buildSubscriptions($subscriberEntity),
'unsubscribes' => $this->buildUnsubscribes($subscriberEntity),
'status' => $subscriberEntity->getStatus(),
'last_name' => $subscriberEntity->getLastName(),
'first_name' => $subscriberEntity->getFirstName(),
'email' => $subscriberEntity->getEmail(),
'created_at' => ($createdAt = $subscriberEntity->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
'updated_at' => ($updatedAt = $subscriberEntity->getUpdatedAt()) ? $updatedAt->format(self::DATE_FORMAT) : null,
'deleted_at' => ($deletedAt = $subscriberEntity->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
'subscribed_ip' => $subscriberEntity->getSubscribedIp(),
'confirmed_ip' => $subscriberEntity->getConfirmedIp(),
'confirmed_at' => ($confirmedAt = $subscriberEntity->getConfirmedAt()) ? $confirmedAt->format(self::DATE_FORMAT) : null,
'last_subscribed_at' => ($lastSubscribedAt = $subscriberEntity->getLastSubscribedAt()) ? $lastSubscribedAt->format(self::DATE_FORMAT) : null,
'unconfirmed_data' => $subscriberEntity->getUnconfirmedData(),
'source' => $subscriberEntity->getSource(),
'count_confirmations' => $subscriberEntity->getConfirmationsCount(),
'unsubscribe_token' => $subscriberEntity->getUnsubscribeToken(),
'link_token' => $subscriberEntity->getLinkToken(),
'tags' => $this->buildTags($subscriberEntity),
];
return $this->buildCustomFields($subscriberEntity, $data);
}
private function buildSubscriptions(SubscriberEntity $subscriberEntity): array {
$result = [];
foreach ($subscriberEntity->getSubscriberSegments() as $subscriberSegment) {
$segment = $subscriberSegment->getSegment();
if ($segment instanceof SegmentEntity) {
$result[] = [
'id' => $subscriberSegment->getId(),
'subscriber_id' => (string)$subscriberEntity->getId(),
'created_at' => ($createdAt = $subscriberSegment->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
'segment_id' => (string)$segment->getId(),
'status' => $subscriberSegment->getStatus(),
'updated_at' => $subscriberSegment->getUpdatedAt()->format(self::DATE_FORMAT),
];
}
}
return $result;
}
private function buildUnsubscribes(SubscriberEntity $subscriberEntity): array {
$unsubscribes = $this->statisticsUnsubscribesRepository->findBy([
'subscriber' => $subscriberEntity,
], [
'createdAt' => 'desc',
]);
$result = [];
foreach ($unsubscribes as $unsubscribe) {
$mapped = [
'source' => $unsubscribe->getSource(),
'meta' => $unsubscribe->getMeta(),
'createdAt' => $unsubscribe->getCreatedAt(),
];
$newsletter = $unsubscribe->getNewsletter();
if ($newsletter instanceof NewsletterEntity) {
$mapped['newsletterId'] = $newsletter->getId();
$mapped['newsletterSubject'] = $newsletter->getSubject();
}
$result[] = $mapped;
}
return $result;
}
private function buildCustomFields(SubscriberEntity $subscriberEntity, array $data): array {
$customFields = $this->customFieldsRepository->findAll();
foreach ($customFields as $customField) {
$subscriberCustomField = $this->subscriberCustomFieldRepository->findOneBy(
['subscriber' => $subscriberEntity, 'customField' => $customField]
);
if ($subscriberCustomField instanceof SubscriberCustomFieldEntity) {
$data['cf_' . $customField->getId()] = $subscriberCustomField->getValue();
}
}
return $data;
}
private function buildTags(SubscriberEntity $subscriber): array {
$result = [];
foreach ($subscriber->getSubscriberTags() as $subscriberTag) {
$tag = $subscriberTag->getTag();
if (!$tag) {
continue;
}
$result[] = [
'id' => $subscriberTag->getId(),
'subscriber_id' => (string)$subscriber->getId(),
'tag_id' => (string)$tag->getId(),
'created_at' => ($createdAt = $subscriberTag->getCreatedAt()) ? $createdAt->format(self::DATE_FORMAT) : null,
'updated_at' => $subscriberTag->getUpdatedAt()->format(self::DATE_FORMAT),
'name' => $tag->getName(),
];
}
return $result;
}
/**
* @param SubscriberEntity[] $subscribers
*/
private function prefetchRelations(array $subscribers): void {
// Prefetch subscriptions
$this->entityManager->createQueryBuilder()
->select('PARTIAL s.{id}, ssg, sg')
->from(SubscriberEntity::class, 's')
->leftJoin('s.subscriberSegments', 'ssg')
->leftJoin('ssg.segment', 'sg')
->where('s.id IN (:subscribers)')
->setParameter('subscribers', $subscribers)
->getQuery()
->getResult();
// Prefetch tags
$this->entityManager->createQueryBuilder()
->select('PARTIAL s.{id}, st, t')
->from(SubscriberEntity::class, 's')
->leftJoin('s.subscriberTags', 'st')
->leftJoin('st.tag', 't')
->where('s.id IN (:subscribers)')
->setParameter('subscribers', $subscribers)
->getQuery()
->getResult();
}
}
@@ -0,0 +1 @@
<?php