<?php declare(strict_types = 1); namespace MailPoet\Newsletter\Listing; if (!defined('ABSPATH')) exit; use MailPoet\Entities\NewsletterEntity; use MailPoet\Listing\ListingDefinition; use MailPoet\Listing\ListingRepository; use MailPoet\Util\Helpers; use MailPoetVendor\Doctrine\ORM\QueryBuilder; class NewsletterListingRepository extends ListingRepository { private static $supportedStatuses = [ NewsletterEntity::STATUS_DRAFT, NewsletterEntity::STATUS_SCHEDULED, NewsletterEntity::STATUS_SENDING, NewsletterEntity::STATUS_SENT, NewsletterEntity::STATUS_ACTIVE, ]; private static $supportedTypes = [ NewsletterEntity::TYPE_STANDARD, NewsletterEntity::TYPE_RE_ENGAGEMENT, NewsletterEntity::TYPE_WELCOME, NewsletterEntity::TYPE_AUTOMATIC, NewsletterEntity::TYPE_NOTIFICATION, NewsletterEntity::TYPE_NOTIFICATION_HISTORY, ]; public function getFilters(ListingDefinition $definition): array { $group = $definition->getGroup(); $typeParam = $definition->getParameters()['type'] ?? null; $groupParam = $definition->getParameters()['group'] ?? null; // newsletter types without filters if (in_array($typeParam, [NewsletterEntity::TYPE_NOTIFICATION_HISTORY])) { return []; } $queryBuilder = clone $this->queryBuilder; $this->applyFromClause($queryBuilder); if ($group) { $this->applyGroup($queryBuilder, $group); } if ($typeParam) { $this->applyType($queryBuilder, $typeParam, $groupParam); } $queryBuilder ->select('s.id, s.name, COUNT(n) AS newsletterCount') ->join('n.newsletterSegments', 'ns') ->join('ns.segment', 's') ->groupBy('s.id') ->addGroupBy('s.name') ->orderBy('s.name') ->having('COUNT(n) > 0'); // format segment list $segmentList = [ [ 'label' => __('All Lists', 'mailpoet'), 'value' => '', ], ]; foreach ($queryBuilder->getQuery()->getResult() as $item) { $segmentList[] = [ 'label' => sprintf('%s (%d)', $item['name'], $item['newsletterCount']), 'value' => $item['id'], ]; } return ['segment' => $segmentList]; } public function getGroups(ListingDefinition $definition): array { $queryBuilder = clone $this->queryBuilder; $this->applyFromClause($queryBuilder); $this->applyParameters($queryBuilder, $definition->getParameters()); // total count $countQueryBuilder = clone $queryBuilder; $countQueryBuilder->select('COUNT(n) AS newsletterCount'); $countQueryBuilder->andWhere('n.deletedAt IS NULL'); $totalCount = (int)$countQueryBuilder->getQuery()->getSingleScalarResult(); // trashed count $trashedCountQueryBuilder = clone $queryBuilder; $trashedCountQueryBuilder->select('COUNT(n) AS newsletterCount'); $trashedCountQueryBuilder->andWhere('n.deletedAt IS NOT NULL'); $trashedCount = (int)$trashedCountQueryBuilder->getQuery()->getSingleScalarResult(); // count-by-status query $queryBuilder->select('n.status, COUNT(n) AS newsletterCount'); $queryBuilder->andWhere('n.deletedAt IS NULL'); $queryBuilder->groupBy('n.status'); $map = []; foreach ($queryBuilder->getQuery()->getResult() as $item) { $map[$item['status']] = (int)$item['newsletterCount']; } $groups = [ [ 'name' => 'all', 'label' => __('All', 'mailpoet'), 'count' => $totalCount, ], ]; $type = $definition->getParameters()['type'] ?? null; switch ($type) { case NewsletterEntity::TYPE_STANDARD: $groups = array_merge($groups, [ [ 'name' => NewsletterEntity::STATUS_DRAFT, 'label' => __('Draft', 'mailpoet'), 'count' => $map[NewsletterEntity::STATUS_DRAFT] ?? 0, ], [ 'name' => NewsletterEntity::STATUS_SCHEDULED, 'label' => __('Scheduled', 'mailpoet'), 'count' => $map[NewsletterEntity::STATUS_SCHEDULED] ?? 0, ], [ 'name' => NewsletterEntity::STATUS_SENDING, 'label' => __('Sending', 'mailpoet'), 'count' => $map[NewsletterEntity::STATUS_SENDING] ?? 0, ], [ 'name' => NewsletterEntity::STATUS_SENT, 'label' => __('Sent', 'mailpoet'), 'count' => $map[NewsletterEntity::STATUS_SENT] ?? 0, ], ]); break; case NewsletterEntity::TYPE_NOTIFICATION_HISTORY: $groups = array_merge($groups, [ [ 'name' => NewsletterEntity::STATUS_SENDING, 'label' => __('Sending', 'mailpoet'), 'count' => $map[NewsletterEntity::STATUS_SENDING] ?? 0, ], [ 'name' => NewsletterEntity::STATUS_SENT, 'label' => __('Sent', 'mailpoet'), 'count' => $map[NewsletterEntity::STATUS_SENT] ?? 0, ], ]); break; case NewsletterEntity::TYPE_WELCOME: case NewsletterEntity::TYPE_RE_ENGAGEMENT: case NewsletterEntity::TYPE_NOTIFICATION: case NewsletterEntity::TYPE_AUTOMATIC: $groups = array_merge($groups, [ [ 'name' => NewsletterEntity::STATUS_ACTIVE, 'label' => __('Active', 'mailpoet'), 'count' => $map[NewsletterEntity::STATUS_ACTIVE] ?? 0, ], [ 'name' => NewsletterEntity::STATUS_DRAFT, 'label' => __('Not active', 'mailpoet'), 'count' => $map[NewsletterEntity::STATUS_DRAFT] ?? 0, ], ]); break; } $groups[] = [ 'name' => 'trash', 'label' => __('Trash', 'mailpoet'), 'count' => $trashedCount, ]; return $groups; } protected function applySelectClause(QueryBuilder $queryBuilder) { $queryBuilder->select("PARTIAL n.{id,subject,hash,type,status,sentAt,updatedAt,deletedAt}"); } protected function applyFromClause(QueryBuilder $queryBuilder) { $queryBuilder->from(NewsletterEntity::class, 'n'); } protected function applyGroup(QueryBuilder $queryBuilder, string $group) { // include/exclude deleted if ($group === 'trash') { $queryBuilder->andWhere('n.deletedAt IS NOT NULL'); } else { $queryBuilder->andWhere('n.deletedAt IS NULL'); } if (!in_array($group, self::$supportedStatuses)) { return; } $queryBuilder ->andWhere('n.status = :status') ->setParameter('status', $group); } protected function applySearch(QueryBuilder $queryBuilder, string $search) { $search = Helpers::escapeSearch($search); $queryBuilder ->andWhere('n.subject LIKE :search') ->setParameter('search', "%$search%"); } protected function applyFilters(QueryBuilder $queryBuilder, array $filters) { $segmentId = $filters['segment'] ?? null; if ($segmentId) { $queryBuilder ->join('n.newsletterSegments', 'ns') ->andWhere('ns.segment = :segmentId') ->setParameter('segmentId', $segmentId); } } protected function applyParameters(QueryBuilder $queryBuilder, array $parameters) { $type = $parameters['type'] ?? null; $group = $parameters['group'] ?? null; $parentId = $parameters['parentId'] ?? null; if ($type) { $this->applyType($queryBuilder, $type, $group); } if ($parentId) { $queryBuilder ->andWhere('n.parent = :parentId') ->setParameter('parentId', $parentId); } } protected function applySorting(QueryBuilder $queryBuilder, string $sortBy, string $sortOrder) { if ($sortBy === 'sentAt') { $queryBuilder->addSelect('CASE WHEN n.sentAt IS NULL THEN 1 ELSE 0 END AS HIDDEN sentAtIsNull'); $queryBuilder->addOrderBy('sentAtIsNull', 'DESC'); } $queryBuilder->addOrderBy("n.$sortBy", $sortOrder); } private function applyType(QueryBuilder $queryBuilder, string $type, string $group = null) { if (!in_array($type, self::$supportedTypes)) { return; } if ($type === NewsletterEntity::TYPE_AUTOMATIC && $group) { $queryBuilder ->join('n.options', 'o') ->join('o.optionField', 'opf') ->andWhere('o.value = :group') ->setParameter('group', $group) ->andWhere('opf.newsletterType = n.type'); } else { $queryBuilder ->andWhere('n.type = :type') ->setParameter('type', $type); } } }