Bez popisu

NewslettersResponseBuilder.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <?php
  2. namespace MailPoet\API\JSON\ResponseBuilders;
  3. if (!defined('ABSPATH')) exit;
  4. use MailPoet\Entities\NewsletterEntity;
  5. use MailPoet\Entities\SegmentEntity;
  6. use MailPoet\Entities\SendingQueueEntity;
  7. use MailPoet\Models\SendingQueue;
  8. use MailPoet\Newsletter\NewslettersRepository;
  9. use MailPoet\Newsletter\Statistics\NewsletterStatistics;
  10. use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository;
  11. use MailPoet\Newsletter\Url as NewsletterUrl;
  12. use MailPoetVendor\Doctrine\ORM\EntityManager;
  13. class NewslettersResponseBuilder {
  14. const DATE_FORMAT = 'Y-m-d H:i:s';
  15. const RELATION_QUEUE = 'queue';
  16. const RELATION_SEGMENTS = 'segments';
  17. const RELATION_OPTIONS = 'options';
  18. const RELATION_TOTAL_SENT = 'total_sent';
  19. const RELATION_CHILDREN_COUNT = 'children_count';
  20. const RELATION_SCHEDULED = 'scheduled';
  21. const RELATION_STATISTICS = 'statistics';
  22. /** @var NewsletterStatisticsRepository */
  23. private $newslettersStatsRepository;
  24. /** @var NewslettersRepository */
  25. private $newslettersRepository;
  26. /** @var EntityManager */
  27. private $entityManager;
  28. /** @var NewsletterUrl */
  29. private $newsletterUrl;
  30. public function __construct(
  31. EntityManager $entityManager,
  32. NewslettersRepository $newslettersRepository,
  33. NewsletterStatisticsRepository $newslettersStatsRepository,
  34. NewsletterUrl $newsletterUrl
  35. ) {
  36. $this->newslettersStatsRepository = $newslettersStatsRepository;
  37. $this->newslettersRepository = $newslettersRepository;
  38. $this->entityManager = $entityManager;
  39. $this->newsletterUrl = $newsletterUrl;
  40. }
  41. public function build(NewsletterEntity $newsletter, $relations = []) {
  42. $data = [
  43. 'id' => (string)$newsletter->getId(), // (string) for BC
  44. 'hash' => $newsletter->getHash(),
  45. 'subject' => $newsletter->getSubject(),
  46. 'type' => $newsletter->getType(),
  47. 'sender_address' => $newsletter->getSenderAddress(),
  48. 'sender_name' => $newsletter->getSenderName(),
  49. 'status' => $newsletter->getStatus(),
  50. 'reply_to_address' => $newsletter->getReplyToAddress(),
  51. 'reply_to_name' => $newsletter->getReplyToName(),
  52. 'preheader' => $newsletter->getPreheader(),
  53. 'body' => $newsletter->getBody(),
  54. 'sent_at' => ($sentAt = $newsletter->getSentAt()) ? $sentAt->format(self::DATE_FORMAT) : null,
  55. 'created_at' => $newsletter->getCreatedAt()->format(self::DATE_FORMAT),
  56. 'updated_at' => $newsletter->getUpdatedAt()->format(self::DATE_FORMAT),
  57. 'deleted_at' => ($deletedAt = $newsletter->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
  58. 'parent_id' => ($parent = $newsletter->getParent()) ? $parent->getId() : null,
  59. 'unsubscribe_token' => $newsletter->getUnsubscribeToken(),
  60. 'ga_campaign' => $newsletter->getGaCampaign(),
  61. ];
  62. foreach ($relations as $relation) {
  63. if ($relation === self::RELATION_QUEUE) {
  64. $data['queue'] = ($queue = $newsletter->getLatestQueue()) ? $this->buildQueue($queue) : false; // false for BC
  65. }
  66. if ($relation === self::RELATION_SEGMENTS) {
  67. $data['segments'] = $this->buildSegments($newsletter);
  68. }
  69. if ($relation === self::RELATION_OPTIONS) {
  70. $data['options'] = $this->buildOptions($newsletter);
  71. }
  72. if ($relation === self::RELATION_TOTAL_SENT) {
  73. $data['total_sent'] = $this->newslettersStatsRepository->getTotalSentCount($newsletter);
  74. }
  75. if ($relation === self::RELATION_CHILDREN_COUNT) {
  76. $data['children_count'] = $this->newslettersStatsRepository->getChildrenCount($newsletter);
  77. }
  78. if ($relation === self::RELATION_SCHEDULED) {
  79. $data['total_scheduled'] = (int)SendingQueue::findTaskByNewsletterId($newsletter->getId())
  80. ->where('tasks.status', SendingQueue::STATUS_SCHEDULED)
  81. ->count();
  82. }
  83. if ($relation === self::RELATION_STATISTICS) {
  84. $data['statistics'] = $this->newslettersStatsRepository->getStatistics($newsletter)->asArray();
  85. }
  86. }
  87. return $data;
  88. }
  89. /**
  90. * @param NewsletterEntity[] $newsletters
  91. * @return mixed[]
  92. */
  93. public function buildForListing(array $newsletters): array {
  94. $statistics = $this->newslettersStatsRepository->getBatchStatistics($newsletters);
  95. $latestQueues = $this->getBatchLatestQueuesWithTasks($newsletters);
  96. $this->newslettersRepository->prefetchOptions($newsletters);
  97. $this->newslettersRepository->prefetchSegments($newsletters);
  98. $data = [];
  99. foreach ($newsletters as $newsletter) {
  100. $id = $newsletter->getId();
  101. $data[] = $this->buildListingItem($newsletter, $statistics[$id] ?? null, $latestQueues[$id] ?? null);
  102. }
  103. return $data;
  104. }
  105. private function buildListingItem(NewsletterEntity $newsletter, NewsletterStatistics $statistics = null, SendingQueueEntity $latestQueue = null): array {
  106. $data = [
  107. 'id' => (string)$newsletter->getId(), // (string) for BC
  108. 'hash' => $newsletter->getHash(),
  109. 'subject' => $newsletter->getSubject(),
  110. 'type' => $newsletter->getType(),
  111. 'status' => $newsletter->getStatus(),
  112. 'sent_at' => ($sentAt = $newsletter->getSentAt()) ? $sentAt->format(self::DATE_FORMAT) : null,
  113. 'updated_at' => $newsletter->getUpdatedAt()->format(self::DATE_FORMAT),
  114. 'deleted_at' => ($deletedAt = $newsletter->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
  115. 'segments' => [],
  116. 'queue' => false,
  117. 'statistics' => ($statistics && $newsletter->getType() !== NewsletterEntity::TYPE_NOTIFICATION)
  118. ? $statistics->asArray()
  119. : false,
  120. 'preview_url' => $this->newsletterUrl->getViewInBrowserUrl(
  121. (object)[
  122. 'id' => $newsletter->getId(),
  123. 'hash' => $newsletter->getHash(),
  124. ],
  125. null,
  126. in_array($newsletter->getStatus(), [NewsletterEntity::STATUS_SENT, NewsletterEntity::STATUS_SENDING], true)
  127. ? $latestQueue
  128. : false
  129. ),
  130. ];
  131. if ($newsletter->getType() === NewsletterEntity::TYPE_STANDARD) {
  132. $data['segments'] = $this->buildSegments($newsletter);
  133. $data['queue'] = $latestQueue ? $this->buildQueue($latestQueue) : false; // false for BC
  134. } elseif (in_array($newsletter->getType(), [NewsletterEntity::TYPE_WELCOME, NewsletterEntity::TYPE_AUTOMATIC], true)) {
  135. $data['segments'] = [];
  136. $data['options'] = $this->buildOptions($newsletter);
  137. $data['total_sent'] = $statistics ? $statistics->getTotalSentCount() : 0;
  138. $data['total_scheduled'] = (int)SendingQueue::findTaskByNewsletterId($newsletter->getId())
  139. ->where('tasks.status', SendingQueue::STATUS_SCHEDULED)
  140. ->count();
  141. } elseif ($newsletter->getType() === NewsletterEntity::TYPE_NOTIFICATION) {
  142. $data['segments'] = $this->buildSegments($newsletter);
  143. $data['children_count'] = $this->newslettersStatsRepository->getChildrenCount($newsletter);
  144. $data['options'] = $this->buildOptions($newsletter);
  145. } elseif ($newsletter->getType() === NewsletterEntity::TYPE_NOTIFICATION_HISTORY) {
  146. $data['segments'] = $this->buildSegments($newsletter);
  147. $data['queue'] = $latestQueue ? $this->buildQueue($latestQueue) : false; // false for BC
  148. } elseif ($newsletter->getType() === NewsletterEntity::TYPE_RE_ENGAGEMENT) {
  149. $data['segments'] = $this->buildSegments($newsletter);
  150. $data['options'] = $this->buildOptions($newsletter);
  151. $data['total_sent'] = $statistics ? $statistics->getTotalSentCount() : 0;
  152. $data['total_scheduled'] = SendingQueue::findTaskByNewsletterId($newsletter->getId())
  153. ->where('tasks.status', SendingQueue::STATUS_SCHEDULED)
  154. ->count();
  155. }
  156. return $data;
  157. }
  158. private function buildSegments(NewsletterEntity $newsletter) {
  159. $output = [];
  160. foreach ($newsletter->getNewsletterSegments() as $newsletterSegment) {
  161. $segment = $newsletterSegment->getSegment();
  162. if (!$segment || $segment->getDeletedAt()) {
  163. continue;
  164. }
  165. $output[] = $this->buildSegment($segment);
  166. }
  167. return $output;
  168. }
  169. private function buildOptions(NewsletterEntity $newsletter) {
  170. $output = [];
  171. foreach ($newsletter->getOptions() as $option) {
  172. $optionField = $option->getOptionField();
  173. if (!$optionField) {
  174. continue;
  175. }
  176. $output[$optionField->getName()] = $option->getValue();
  177. }
  178. // convert 'afterTimeNumber' string to integer
  179. if (isset($output['afterTimeNumber']) && is_numeric($output['afterTimeNumber'])) {
  180. $output['afterTimeNumber'] = (int)$output['afterTimeNumber'];
  181. }
  182. return $output;
  183. }
  184. private function buildSegment(SegmentEntity $segment) {
  185. return [
  186. 'id' => (string)$segment->getId(), // (string) for BC
  187. 'name' => $segment->getName(),
  188. 'type' => $segment->getType(),
  189. 'description' => $segment->getDescription(),
  190. 'created_at' => $segment->getCreatedAt()->format(self::DATE_FORMAT),
  191. 'updated_at' => $segment->getUpdatedAt()->format(self::DATE_FORMAT),
  192. 'deleted_at' => ($deletedAt = $segment->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
  193. ];
  194. }
  195. private function buildQueue(SendingQueueEntity $queue) {
  196. $task = $queue->getTask();
  197. if ($task === null) {
  198. return null;
  199. }
  200. // the following crazy mix of '$queue' and '$task' comes from 'array_merge($task, $queue)'
  201. // (MailPoet\Tasks\Sending) which means all equal-named fields will be taken from '$queue'
  202. return [
  203. 'id' => (string)$queue->getId(), // (string) for BC
  204. 'type' => $task->getType(),
  205. 'status' => $task->getStatus(),
  206. 'priority' => (string)$task->getPriority(), // (string) for BC
  207. 'scheduled_at' => ($scheduledAt = $task->getScheduledAt()) ? $scheduledAt->format(self::DATE_FORMAT) : null,
  208. 'processed_at' => ($processedAt = $task->getProcessedAt()) ? $processedAt->format(self::DATE_FORMAT) : null,
  209. 'created_at' => $queue->getCreatedAt()->format(self::DATE_FORMAT),
  210. 'updated_at' => $queue->getUpdatedAt()->format(self::DATE_FORMAT),
  211. 'deleted_at' => ($deletedAt = $queue->getDeletedAt()) ? $deletedAt->format(self::DATE_FORMAT) : null,
  212. 'meta' => $queue->getMeta(),
  213. 'task_id' => (string)$task->getId(), // (string) for BC
  214. 'newsletter_id' => ($newsletter = $queue->getNewsletter()) ? (string)$newsletter->getId() : null, // (string) for BC
  215. 'newsletter_rendered_subject' => $queue->getNewsletterRenderedSubject(),
  216. 'count_total' => (string)$queue->getCountTotal(), // (string) for BC
  217. 'count_processed' => (string)$queue->getCountProcessed(), // (string) for BC
  218. 'count_to_process' => (string)$queue->getCountToProcess(), // (string) for BC
  219. ];
  220. }
  221. private function getBatchLatestQueuesWithTasks(array $newsletters): array {
  222. // this implements the same logic as NewsletterEntity::getLatestQueue() but for a batch of $newsletters
  223. $subqueryQueryBuilder = $this->entityManager->createQueryBuilder();
  224. $subquery = $subqueryQueryBuilder
  225. ->select('MAX(subSq.id) AS maxId')
  226. ->from(SendingQueueEntity::class, 'subSq')
  227. ->where('subSq.newsletter IN (:newsletters)')
  228. ->setParameter('newsletters', $newsletters)
  229. ->groupBy('subSq.newsletter')
  230. ->getQuery();
  231. $latestQueueIds = array_column($subquery->getResult(), 'maxId');
  232. if (empty($latestQueueIds)) {
  233. return [];
  234. }
  235. $queryBuilder = $this->entityManager->createQueryBuilder();
  236. $results = $queryBuilder
  237. ->select('PARTIAL sq.{id, createdAt, updatedAt, deletedAt, meta, newsletterRenderedSubject, countTotal, countProcessed, countToProcess}')
  238. ->addSelect('PARTIAL t.{id, type, status, priority, scheduledAt, processedAt}')
  239. ->addSelect('IDENTITY(sq.newsletter)')
  240. ->from(SendingQueueEntity::class, 'sq')
  241. ->join('sq.task', 't')
  242. ->where('sq.id IN (:sub)')
  243. ->setParameter('sub', $latestQueueIds)
  244. ->getQuery()
  245. ->getResult();
  246. $latestQueues = [];
  247. foreach ($results as $result) {
  248. $latestQueues[(int)$result[1]] = $result[0];
  249. }
  250. return $latestQueues;
  251. }
  252. }