203 lines
8.4 KiB
PHP
Executable File
203 lines
8.4 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Mautic\EmailBundle\Stats;
|
|
|
|
use Doctrine\DBAL\ArrayParameterType;
|
|
use Doctrine\DBAL\Connection;
|
|
use Doctrine\DBAL\Query\QueryBuilder;
|
|
use Mautic\CoreBundle\Helper\DateTimeHelper;
|
|
|
|
class EmailPeriodMetrics
|
|
{
|
|
private const CAMPAIGN_EVENT_SOURCE = 'campaign.event';
|
|
private const EMAIL_SOURCE = 'email';
|
|
|
|
public function __construct(
|
|
private Connection $connection,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @param array<int, int> $eventsIds
|
|
*
|
|
* @return array<int, array<string, int|string>>
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
public function emailMetricsPerWeekdayByCampaignEvents(array $eventsIds, \DateTimeImmutable $dateFrom, \DateTimeImmutable $dateTo, string $timezoneOffset): array
|
|
{
|
|
$queryBuilder = $this->connection->createQueryBuilder();
|
|
$daysSubQuery = $this->createDaysSubQuery();
|
|
|
|
$queryBuilder
|
|
->select(
|
|
'd.day',
|
|
'IFNULL(s.sent_count, 0) AS sent_count',
|
|
'IFNULL(r.read_count, 0) AS read_count',
|
|
'IFNULL(c.hit_count, 0) AS hit_count'
|
|
)
|
|
->from("({$daysSubQuery->getSQL()})", 'd')
|
|
->leftJoin('d', "({$this->createClicksSubQuery()->getSQL()})", 'c', 'c.hit_day = d.day')
|
|
->leftJoin('d', "({$this->createSentSubQuery()->getSQL()})", 's', 's.sent_day = d.day')
|
|
->leftJoin('d', "({$this->createReadSubQuery()->getSQL()})", 'r', 'r.read_day = d.day')
|
|
->setParameter('source_ids', $eventsIds, ArrayParameterType::INTEGER)
|
|
->setParameter('timezoneOffset', $timezoneOffset)
|
|
->setParameter('dateFrom', $dateFrom->format(DateTimeHelper::FORMAT_DB))
|
|
->setParameter('dateTo', $dateTo->setTime(23, 59, 59)->format(DateTimeHelper::FORMAT_DB))
|
|
->setParameter('email_source', self::EMAIL_SOURCE)
|
|
->setParameter('campaign_event_source', self::CAMPAIGN_EVENT_SOURCE)
|
|
->orderBy('d.day');
|
|
|
|
return $queryBuilder->executeQuery()->fetchAllAssociative();
|
|
}
|
|
|
|
/**
|
|
* @param array<int, int> $eventsIds
|
|
*
|
|
* @return array<int, array<string, int|string>>
|
|
*/
|
|
public function emailMetricsPerHourByCampaignEvents(array $eventsIds, \DateTimeImmutable $dateFrom, \DateTimeImmutable $dateTo, string $timezoneOffset): array
|
|
{
|
|
$queryBuilder = $this->connection->createQueryBuilder();
|
|
$hoursSubQuery = $this->createHoursSubQuery();
|
|
|
|
$queryBuilder
|
|
->select(
|
|
'h.hour',
|
|
'IFNULL(s.sent_count, 0) AS sent_count',
|
|
'IFNULL(r.read_count, 0) AS read_count',
|
|
'IFNULL(c.hit_count, 0) AS hit_count'
|
|
)
|
|
->from("({$hoursSubQuery->getSQL()})", 'h')
|
|
->leftJoin('h', "({$this->createClicksHourlySubQuery()->getSQL()})", 'c', 'c.hit_hour = h.hour')
|
|
->leftJoin('h', "({$this->createSentHourlySubQuery()->getSQL()})", 's', 's.sent_hour = h.hour')
|
|
->leftJoin('h', "({$this->createReadHourlySubQuery()->getSQL()})", 'r', 'r.read_hour = h.hour')
|
|
->setParameter('source_ids', $eventsIds, ArrayParameterType::INTEGER)
|
|
->setParameter('timezoneOffset', $timezoneOffset)
|
|
->setParameter('format', '%H')
|
|
->setParameter('dateFrom', $dateFrom->format(DateTimeHelper::FORMAT_DB))
|
|
->setParameter('dateTo', $dateTo->setTime(23, 59, 59)->format('Y-m-d H:i:s'))
|
|
->setParameter('email_source', self::EMAIL_SOURCE)
|
|
->setParameter('campaign_event_source', self::CAMPAIGN_EVENT_SOURCE)
|
|
->orderBy('h.hour');
|
|
|
|
return $queryBuilder->executeQuery()->fetchAllAssociative();
|
|
}
|
|
|
|
private function createClicksSubQuery(): QueryBuilder
|
|
{
|
|
return $this->connection->createQueryBuilder()
|
|
->select(
|
|
'WEEKDAY(TIMESTAMPADD(SECOND, :timezoneOffset, ph.date_hit)) AS hit_day',
|
|
'COUNT(DISTINCT ph.id) AS hit_count'
|
|
)
|
|
->from(MAUTIC_TABLE_PREFIX.'email_stats', 'es')
|
|
->join('es', MAUTIC_TABLE_PREFIX.'page_hits', 'ph', 'es.lead_id = ph.lead_id')
|
|
->join('es', MAUTIC_TABLE_PREFIX.'channel_url_trackables', 'cut', 'cut.channel_id = es.email_id AND cut.redirect_id = ph.redirect_id')
|
|
->where('ph.date_hit BETWEEN :dateFrom AND :dateTo')
|
|
->andWhere('ph.source = :email_source')
|
|
->andWhere('cut.channel = :email_source')
|
|
->andWhere('es.source = :campaign_event_source')
|
|
->andWhere('es.source_id IN (:source_ids)')
|
|
->groupBy('hit_day');
|
|
}
|
|
|
|
private function createSentSubQuery(): QueryBuilder
|
|
{
|
|
return $this->createBasicStatsSubQuery('date_sent', 'sent_day', 'sent_count');
|
|
}
|
|
|
|
private function createReadSubQuery(): QueryBuilder
|
|
{
|
|
return $this->createBasicStatsSubQuery('date_read', 'read_day', 'read_count')
|
|
->andWhere('es.is_read = 1');
|
|
}
|
|
|
|
private function createClicksHourlySubQuery(): QueryBuilder
|
|
{
|
|
return $this->connection->createQueryBuilder()
|
|
->select(
|
|
'TIME_FORMAT(TIMESTAMPADD(SECOND, :timezoneOffset, ph.date_hit), :format) AS hit_hour',
|
|
'COUNT(DISTINCT ph.id) AS hit_count'
|
|
)
|
|
->from(MAUTIC_TABLE_PREFIX.'email_stats', 'es')
|
|
->join('es', MAUTIC_TABLE_PREFIX.'page_hits', 'ph', 'es.lead_id = ph.lead_id')
|
|
->join('es', MAUTIC_TABLE_PREFIX.'channel_url_trackables', 'cut', 'cut.channel_id = es.email_id AND cut.redirect_id = ph.redirect_id')
|
|
->where('ph.date_hit BETWEEN :dateFrom AND :dateTo')
|
|
->andWhere('ph.source = :email_source')
|
|
->andWhere('cut.channel = :email_source')
|
|
->andWhere('es.source = :campaign_event_source')
|
|
->andWhere('es.source_id IN (:source_ids)')
|
|
->groupBy('hit_hour')
|
|
->orderBy('hit_hour', 'ASC');
|
|
}
|
|
|
|
private function createSentHourlySubQuery(): QueryBuilder
|
|
{
|
|
return $this->createBasicHourlyStatsSubQuery('date_sent', 'sent_hour', 'sent_count');
|
|
}
|
|
|
|
private function createReadHourlySubQuery(): QueryBuilder
|
|
{
|
|
return $this->createBasicHourlyStatsSubQuery('date_read', 'read_hour', 'read_count')
|
|
->andWhere('es.is_read = 1');
|
|
}
|
|
|
|
private function createBasicStatsSubQuery(string $dateColumn, string $groupByAlias, string $countAlias): QueryBuilder
|
|
{
|
|
return $this->connection->createQueryBuilder()
|
|
->select(
|
|
"WEEKDAY(TIMESTAMPADD(SECOND, :timezoneOffset, $dateColumn)) AS $groupByAlias",
|
|
"COUNT(id) AS $countAlias"
|
|
)
|
|
->from(MAUTIC_TABLE_PREFIX.'email_stats', 'es')
|
|
->where("es.$dateColumn IS NOT NULL")
|
|
->andWhere("es.$dateColumn BETWEEN :dateFrom AND :dateTo")
|
|
->andWhere('es.source = :campaign_event_source')
|
|
->andWhere('es.source_id IN (:source_ids)')
|
|
->groupBy($groupByAlias)
|
|
->orderBy($groupByAlias);
|
|
}
|
|
|
|
private function createBasicHourlyStatsSubQuery(string $dateColumn, string $groupByAlias, string $countAlias): QueryBuilder
|
|
{
|
|
return $this->connection->createQueryBuilder()
|
|
->select(
|
|
"TIME_FORMAT(TIMESTAMPADD(SECOND, :timezoneOffset, $dateColumn), :format) AS $groupByAlias",
|
|
"COUNT(id) AS $countAlias"
|
|
)
|
|
->from(MAUTIC_TABLE_PREFIX.'email_stats', 'es')
|
|
->where("es.$dateColumn IS NOT NULL")
|
|
->andWhere("es.$dateColumn BETWEEN :dateFrom AND :dateTo")
|
|
->andWhere('es.source = :campaign_event_source')
|
|
->andWhere('es.source_id IN (:source_ids)')
|
|
->groupBy($groupByAlias)
|
|
->orderBy($groupByAlias, 'ASC')
|
|
->setMaxResults(24);
|
|
}
|
|
|
|
private function createDaysSubQuery(): QueryBuilder
|
|
{
|
|
$subQuery = $this->connection->createQueryBuilder();
|
|
$daysQuery = '0 AS day';
|
|
for ($i = 1; $i < 7; ++$i) {
|
|
$daysQuery .= sprintf(' UNION ALL SELECT %d AS day', $i);
|
|
}
|
|
|
|
return $subQuery->select($daysQuery);
|
|
}
|
|
|
|
private function createHoursSubQuery(): QueryBuilder
|
|
{
|
|
$subQuery = $this->connection->createQueryBuilder();
|
|
$hoursString = '00 AS hour';
|
|
for ($i = 1; $i < 24; ++$i) {
|
|
$hoursString .= sprintf(' UNION ALL SELECT %02d AS hour', $i);
|
|
}
|
|
|
|
return $subQuery->select($hoursString);
|
|
}
|
|
}
|