oont-contents/plugins/mailpoet-premium/lib/Newsletter/Stats/Bounces.php
2025-02-08 15:10:23 +01:00

170 lines
5.3 KiB
PHP

<?php declare(strict_types = 1);
namespace MailPoet\Premium\Newsletter\Stats;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\StatisticsBounceEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Listing;
use MailPoetVendor\Doctrine\DBAL\ParameterType;
use MailPoetVendor\Doctrine\DBAL\Result;
use MailPoetVendor\Doctrine\ORM\EntityManager;
class Bounces {
const STATUS_BOUNCED = 'bounced';
/** @var Listing\Handler */
private $listingHandler;
/** @var EntityManager */
private $entityManager;
public function __construct(
Listing\Handler $listingHandler,
EntityManager $entityManager
) {
$this->listingHandler = $listingHandler;
$this->entityManager = $entityManager;
}
/**
* @param array<string, mixed> $data
* @return Listing\ListingDefinition
*/
private function parseData($data): Listing\ListingDefinition {
// check if sort order was specified or default to "desc"
$data['sort_order'] = ($data['sort_order'] ?? null) === 'asc' ? 'asc' : 'desc';
// sanitize sort by
$sortableColumns = ['email', 'status', 'created_at'];
$sortBy = (!empty($data['sort_by']) && in_array($data['sort_by'], $sortableColumns, true))
? $data['sort_by']
: '';
if (empty($sortBy)) {
$sortBy = 'created_at';
}
$data['sort_by'] = $sortBy;
return $this->listingHandler->getListingDefinition($data);
}
/**
* @param array<string, mixed> $data
*
* @return array{
* count: int,
* filters: array{},
* groups: array{},
* items: array<int, array<string, mixed>>
* }
*/
public function get($data = []): array {
$definition = $this->parseData($data);
$countQuery = $this->getBouncesQuery($definition, true);
if ($countQuery) {
$query = 'SELECT COUNT(*) as cnt FROM ( ' . $countQuery . ' ) t ';
/** @var int $result */
$result = $this->entityManager->getConnection()->executeQuery($query)->fetchOne();
$count = intval($result);
$query = $this->getBouncesQuery($definition);
$query .= " ORDER BY {$definition->getSortBy()} {$definition->getSortOrder()} LIMIT :limit OFFSET :offset ";
$items = $this
->entityManager
->getConnection()
->executeQuery($query, [
'limit' => $definition->getLimit(),
'offset' => $definition->getOffset(),
], [
'limit' => ParameterType::INTEGER,
'offset' => ParameterType::INTEGER,
])
->fetchAllAssociative();
} else {
$count = 0;
$items = [];
}
return [
'count' => $count,
'filters' => [],
'groups' => [],
'items' => $items,
];
}
private function getBouncesQuery(Listing\ListingDefinition $definition, bool $count = false): ?string {
$searchConstraint = '';
$newsletterId = intval($definition->getParameters()['id']);
$subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
$bouncesTable = $this->entityManager->getClassMetadata(StatisticsBounceEntity::class)->getTableName();
$searchString = $definition->getSearch();
if (is_string($searchString) && !empty($searchString)) {
$searchConstraint = $this->getSearchConstraint($searchString);
if ($searchConstraint === null) {
// Nothing was found by search
return null;
}
}
$fields = [
'bounces.id',
'bounces.created_at',
'bounces.subscriber_id',
"'" . self::STATUS_BOUNCED . "' as status",
'subscribers.email',
'subscribers.first_name',
'subscribers.last_name',
];
return 'SELECT '
. self::getColumnList($fields, $count) . ' '
. 'FROM ' . $bouncesTable . ' bounces '
. 'LEFT JOIN ' . $subscriberTable . ' subscribers ON subscribers.id = bounces.subscriber_id '
. "WHERE bounces.newsletter_id = '" . $newsletterId . "' " . $searchConstraint;
}
private function getSearchConstraint(string $searchString): ?string {
// Search recipients
$search = trim($searchString);
$search = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $search); // escape for 'LIKE'
$qb = $this->entityManager->getConnection()->createQueryBuilder();
$qb->addSelect('id')
->from($this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName())
->orWhere($qb->expr()->like('email', ':search'))
->orWhere($qb->expr()->like('first_name', ':search'))
->orWhere($qb->expr()->like('last_name', ':search'))
->setParameter('search', '%' . $search . '%');
$result = $qb->execute();
if (!$result instanceof Result) { // for PHPStan, it doesn't know execute always returns Statement for SELECT queries
throw new \Exception('$statement must be an instance of Statement');
}
$subscriberIds = $result->fetchAll();
$subscriberIds = array_column($subscriberIds, 'id');
if (empty($subscriberIds)) {
return null;
}
return sprintf(
' AND subscribers.id IN (%s) ',
join(',', array_map('intval', $subscriberIds))
);
}
/**
* @param array<int, string> $fields
* @param bool $count
*
* @return string
*/
private static function getColumnList(array $fields, bool $count = false): string {
// Select ID field only for counting
return $count ? (string)reset($fields) : join(', ', $fields);
}
}