Spaces:
No application file
No application file
| namespace Mautic\EmailBundle\Entity; | |
| use Doctrine\DBAL\ArrayParameterType; | |
| use Doctrine\DBAL\Exception; | |
| use Mautic\CoreBundle\Entity\CommonRepository; | |
| use Mautic\CoreBundle\Helper\Chart\ChartQuery; | |
| use Mautic\CoreBundle\Helper\DateTimeHelper; | |
| use Mautic\LeadBundle\Entity\TimelineTrait; | |
| /** | |
| * @extends CommonRepository<Stat> | |
| */ | |
| class StatRepository extends CommonRepository | |
| { | |
| use TimelineTrait; | |
| /** | |
| * @return mixed | |
| * | |
| * @throws \Doctrine\ORM\NoResultException | |
| * @throws \Doctrine\ORM\NonUniqueResultException | |
| */ | |
| public function getEmailStatus($trackingHash) | |
| { | |
| $q = $this->createQueryBuilder('s'); | |
| $q->select('s') | |
| ->leftJoin('s.lead', 'l') | |
| ->leftJoin('s.email', 'e') | |
| ->where( | |
| $q->expr()->eq('s.trackingHash', ':hash') | |
| ) | |
| ->setParameter('hash', $trackingHash); | |
| $result = $q->getQuery()->getResult(); | |
| return (!empty($result)) ? $result[0] : null; | |
| } | |
| /** | |
| * @param int $contactId | |
| * @param int $emailId | |
| * | |
| * @return array | |
| */ | |
| public function getUniqueClickedLinksPerContactAndEmail($contactId, $emailId) | |
| { | |
| $q = $this->_em->getConnection()->createQueryBuilder(); | |
| $q->select('distinct ph.url, ph.date_hit') | |
| ->from(MAUTIC_TABLE_PREFIX.'page_hits', 'ph') | |
| ->where('ph.email_id = :emailId') | |
| ->andWhere('ph.lead_id = :leadId') | |
| ->setParameter('leadId', $contactId) | |
| ->setParameter('emailId', $emailId); | |
| $result = $q->executeQuery()->fetchAllAssociative(); | |
| foreach ($result as $row) { | |
| $data[$row['date_hit']] = $row['url']; | |
| } | |
| return $data; | |
| } | |
| /** | |
| * @param int $limit | |
| * @param int|null $createdByUserId | |
| * @param int|null $companyId | |
| * @param int|null $campaignId | |
| * @param int|null $segmentId | |
| */ | |
| public function getSentEmailToContactData( | |
| $limit, | |
| \DateTime $dateFrom, | |
| \DateTime $dateTo, | |
| $createdByUserId = null, | |
| $companyId = null, | |
| $campaignId = null, | |
| $segmentId = null | |
| ): array { | |
| $q = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $q->select('s.id, s.lead_id, s.email_address, s.is_read, s.email_id, s.date_sent, s.date_read') | |
| ->from(MAUTIC_TABLE_PREFIX.'email_stats', 's') | |
| ->leftJoin('s', MAUTIC_TABLE_PREFIX.'emails', 'e', 's.email_id = e.id') | |
| ->addSelect('e.name AS email_name') | |
| ->leftJoin('s', MAUTIC_TABLE_PREFIX.'page_hits', 'ph', 'ph.source = "email" and ph.source_id = s.email_id and ph.lead_id = s.lead_id') | |
| ->addSelect('COUNT(ph.id) AS link_hits'); | |
| if (null !== $createdByUserId) { | |
| $q->andWhere('e.created_by = :userId') | |
| ->setParameter('userId', $createdByUserId); | |
| } | |
| $q->andWhere('s.date_sent BETWEEN :dateFrom AND :dateTo') | |
| ->setParameter('dateFrom', $dateFrom->format('Y-m-d H:i:s')) | |
| ->setParameter('dateTo', $dateTo->format('Y-m-d H:i:s')); | |
| $companyJoinOnExpr = $q->expr()->and( | |
| $q->expr()->eq('s.lead_id', 'cl.lead_id') | |
| ); | |
| if (!empty($companyId)) { | |
| // Must force a one to one relationship | |
| $companyJoinOnExpr->with( | |
| $q->expr()->eq('cl.is_primary', 1) | |
| ); | |
| } | |
| $q->leftJoin('s', MAUTIC_TABLE_PREFIX.'companies_leads', 'cl', $companyJoinOnExpr) | |
| ->leftJoin('s', MAUTIC_TABLE_PREFIX.'companies', 'c', 'cl.company_id = c.id') | |
| ->addSelect('c.id AS company_id') | |
| ->addSelect('c.companyname AS company_name'); | |
| if (!empty($companyId)) { | |
| $q->andWhere('cl.company_id = :companyId') | |
| ->setParameter('companyId', $companyId); | |
| } | |
| $q->leftJoin('s', MAUTIC_TABLE_PREFIX.'campaign_events', 'ce', 's.source = "campaign.event" and s.source_id = ce.id') | |
| ->leftJoin('ce', MAUTIC_TABLE_PREFIX.'campaigns', 'campaign', 'ce.campaign_id = campaign.id') | |
| ->addSelect('campaign.id AS campaign_id') | |
| ->addSelect('campaign.name AS campaign_name'); | |
| if (null !== $campaignId) { | |
| $q->andWhere('ce.campaign_id = :campaignId') | |
| ->setParameter('campaignId', $campaignId); | |
| } | |
| $q->leftJoin('s', MAUTIC_TABLE_PREFIX.'lead_lists', 'll', 's.list_id = ll.id') | |
| ->addSelect('ll.id AS segment_id') | |
| ->addSelect('ll.name AS segment_name'); | |
| if (null !== $segmentId) { | |
| $sb = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $sb->select('null') | |
| ->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 'lll') | |
| ->where( | |
| $sb->expr()->and( | |
| $sb->expr()->eq('lll.leadlist_id', ':segmentId'), | |
| $sb->expr()->eq('lll.lead_id', 'ph.lead_id'), | |
| $sb->expr()->eq('lll.manually_removed', 0) | |
| ) | |
| ); | |
| // Filter for both broadcasts and campaign related segments | |
| $q->andWhere( | |
| $q->expr()->or( | |
| $q->expr()->eq('s.list_id', ':segmentId'), | |
| $q->expr()->and( | |
| $q->expr()->isNull('s.list_id'), | |
| sprintf('EXISTS (%s)', $sb->getSQL()) | |
| ) | |
| ) | |
| ) | |
| ->setParameter('segmentId', $segmentId); | |
| } | |
| $q->setMaxResults($limit); | |
| $q->groupBy('s.id'); | |
| $q->orderBy('s.id', 'DESC'); | |
| return $q->executeQuery()->fetchAllAssociative(); | |
| } | |
| /** | |
| * @param array<int,int|string>|int|null $emailIds | |
| * @param array<int,int|string>|int|true|null $listId | |
| */ | |
| public function getSentStats($emailIds, $listId = null): array | |
| { | |
| if (!is_array($emailIds)) { | |
| $emailIds = [(int) $emailIds]; | |
| } | |
| $q = $this->_em->getConnection()->createQueryBuilder(); | |
| $q->select('s.lead_id') | |
| ->from(MAUTIC_TABLE_PREFIX.'email_stats', 's') | |
| ->where( | |
| $q->expr()->in('s.email_id', $emailIds) | |
| ); | |
| if ($listId) { | |
| $q->andWhere('s.list_id = :list') | |
| ->setParameter('list', $listId); | |
| } | |
| $result = $q->executeQuery()->fetchAllAssociative(); | |
| // index by lead | |
| $stats = []; | |
| foreach ($result as $r) { | |
| $stats[$r['lead_id']] = $r['lead_id']; | |
| } | |
| unset($result); | |
| return $stats; | |
| } | |
| /** | |
| * @param array<int,int|string>|int|null $emailIds | |
| * @param array<int,int|string>|int|true|null $listId | |
| * @param bool $combined | |
| * | |
| * @return array|int | |
| */ | |
| public function getSentCount($emailIds = null, $listId = null, ChartQuery $chartQuery = null, $combined = false) | |
| { | |
| return $this->getStatusCount('is_sent', $emailIds, $listId, $chartQuery, $combined); | |
| } | |
| /** | |
| * @param array<int,int|string>|int|null $emailIds | |
| * @param array<int,int|string>|int|null $listId | |
| * @param bool $combined | |
| * | |
| * @return array|int | |
| */ | |
| public function getReadCount($emailIds = null, $listId = null, ChartQuery $chartQuery = null, $combined = false) | |
| { | |
| return $this->getStatusCount('is_read', $emailIds, $listId, $chartQuery, $combined); | |
| } | |
| /** | |
| * @param array<int,int|string>|int|null $emailIds | |
| * @param array<int,int|string>|int|true|null $listId | |
| * @param bool $combined | |
| * | |
| * @return array|int | |
| */ | |
| public function getFailedCount($emailIds = null, $listId = null, ChartQuery $chartQuery = null, $combined = false) | |
| { | |
| return $this->getStatusCount('is_failed', $emailIds, $listId, $chartQuery, $combined); | |
| } | |
| /** | |
| * @param string $column | |
| * @param array<int,int|string>|int|null $emailIds | |
| * @param array<int,int|string>|int|true|null $listId | |
| * @param bool $combined | |
| * | |
| * @return array|int | |
| */ | |
| public function getStatusCount($column, $emailIds = null, $listId = null, ChartQuery $chartQuery = null, $combined = false) | |
| { | |
| $q = $this->_em->getConnection()->createQueryBuilder(); | |
| $q->select('count(s.id) as count') | |
| ->from(MAUTIC_TABLE_PREFIX.'email_stats', 's'); | |
| if ($emailIds) { | |
| if (!is_array($emailIds)) { | |
| $emailIds = [(int) $emailIds]; | |
| } | |
| $q->where( | |
| $q->expr()->in('s.email_id', $emailIds) | |
| ); | |
| } | |
| if ($listId) { | |
| if (!$combined) { | |
| if (true === $listId) { | |
| $q->addSelect('s.list_id') | |
| ->groupBy('s.list_id'); | |
| } elseif (is_array($listId)) { | |
| $q->andWhere( | |
| $q->expr()->in('s.list_id', ':segmentIds') | |
| ); | |
| $q->setParameter('segmentIds', $listId, ArrayParameterType::INTEGER); | |
| $q->addSelect('s.list_id') | |
| ->groupBy('s.list_id'); | |
| } else { | |
| $q->andWhere('s.list_id = :list_id') | |
| ->setParameter('list_id', $listId); | |
| } | |
| } else { | |
| $subQ = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $subQ->select('null') | |
| ->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 'list') | |
| ->andWhere( | |
| $q->expr()->and( | |
| $q->expr()->in('list.leadlist_id', array_map('intval', $listId)), | |
| $q->expr()->eq('list.lead_id', 's.lead_id') | |
| ) | |
| ); | |
| $q->andWhere(sprintf('EXISTS (%s)', $subQ->getSQL())); | |
| } | |
| } | |
| if ('is_sent' === $column) { | |
| $q->andWhere('s.is_failed = :false') | |
| ->setParameter('false', false, 'boolean'); | |
| } else { | |
| $q->andWhere($column.' = :true') | |
| ->setParameter('true', true, 'boolean'); | |
| } | |
| if ($chartQuery) { | |
| if ('is_read' === $column) { | |
| $chartQuery->applyDateFilters($q, 'date_read', 's'); | |
| } else { | |
| $chartQuery->applyDateFilters($q, 'date_sent', 's'); | |
| } | |
| } | |
| $results = $q->executeQuery()->fetchAllAssociative(); | |
| if ((true === $listId || is_array($listId)) && !$combined) { | |
| // Return list group of counts | |
| $byList = []; | |
| foreach ($results as $result) { | |
| $byList[$result['list_id']] = $result['count']; | |
| } | |
| return $byList; | |
| } | |
| return (isset($results[0])) ? $results[0]['count'] : 0; | |
| } | |
| /** | |
| * @param array<int,int|string>|int $emailIds | |
| */ | |
| public function getOpenedRates($emailIds, \DateTime $fromDate = null): array | |
| { | |
| $inIds = (!is_array($emailIds)) ? [$emailIds] : $emailIds; | |
| $sq = $this->_em->getConnection()->createQueryBuilder(); | |
| $sq->select('e.email_id, count(e.id) as the_count') | |
| ->from(MAUTIC_TABLE_PREFIX.'email_stats', 'e') | |
| ->where( | |
| $sq->expr()->and( | |
| $sq->expr()->eq('e.is_failed', ':false'), | |
| $sq->expr()->in('e.email_id', $inIds) | |
| ) | |
| )->setParameter('false', false, 'boolean'); | |
| if (null !== $fromDate) { | |
| // make sure the date is UTC | |
| $dt = new DateTimeHelper($fromDate); | |
| $sq->andWhere( | |
| $sq->expr()->gte('e.date_sent', $sq->expr()->literal($dt->toUtcString())) | |
| ); | |
| } | |
| $sq->groupBy('e.email_id'); | |
| // get a total number of sent emails first | |
| $totalCounts = $sq->executeQuery()->fetchAllAssociative(); | |
| $return = []; | |
| foreach ($inIds as $id) { | |
| $return[$id] = [ | |
| 'totalCount' => 0, | |
| 'readCount' => 0, | |
| 'readRate' => 0, | |
| ]; | |
| } | |
| foreach ($totalCounts as $t) { | |
| if (null != $t['email_id']) { | |
| $return[$t['email_id']]['totalCount'] = (int) $t['the_count']; | |
| } | |
| } | |
| // now get a read count | |
| $sq->andWhere('e.is_read = :true') | |
| ->setParameter('true', true, 'boolean'); | |
| $readCounts = $sq->executeQuery()->fetchAllAssociative(); | |
| foreach ($readCounts as $r) { | |
| $return[$r['email_id']]['readCount'] = (int) $r['the_count']; | |
| $return[$r['email_id']]['readRate'] = ($return[$r['email_id']]['totalCount']) ? | |
| round(($r['the_count'] / $return[$r['email_id']]['totalCount']) * 100, 2) : | |
| 0; | |
| } | |
| return (!is_array($emailIds)) ? $return[$emailIds] : $return; | |
| } | |
| /** | |
| * @param array<int,int|string>|int $emailIds | |
| * | |
| * @return array<int, array<string, mixed>> | |
| */ | |
| public function getOpenedStatIds($emailIds = null, $listId = null): array | |
| { | |
| $q = $this->_em->getConnection()->createQueryBuilder(); | |
| $q->select('s.id') | |
| ->from(MAUTIC_TABLE_PREFIX.'email_stats', 's'); | |
| if ($emailIds) { | |
| if (!is_array($emailIds)) { | |
| $emailIds = [(int) $emailIds]; | |
| } | |
| $q->where( | |
| $q->expr()->in('s.email_id', $emailIds) | |
| ); | |
| } | |
| $q->andWhere('open_count > 0'); | |
| if ($listId) { | |
| $q->andWhere('s.list_id = '.(int) $listId); | |
| } | |
| return $q->executeQuery()->fetchAllAssociative(); | |
| } | |
| /** | |
| * Get a lead's email stat. | |
| * | |
| * @param int $leadId | |
| * | |
| * @return array | |
| * | |
| * @throws \Doctrine\ORM\NoResultException | |
| * @throws \Doctrine\ORM\NonUniqueResultException | |
| */ | |
| public function getLeadStats($leadId, array $options = []) | |
| { | |
| $query = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $query->from(MAUTIC_TABLE_PREFIX.'email_stats', 's') | |
| ->leftJoin('s', MAUTIC_TABLE_PREFIX.'emails', 'e', 's.email_id = e.id') | |
| ->leftJoin('s', MAUTIC_TABLE_PREFIX.'email_copies', 'ec', 's.copy_id = ec.id'); | |
| if ($leadId) { | |
| $query->andWhere( | |
| $query->expr()->eq('s.lead_id', (int) $leadId) | |
| ); | |
| } | |
| if (!empty($options['basic_select'])) { | |
| $query->select( | |
| 's.email_id, s.id, s.date_read as dateRead, s.date_sent as dateSent, e.subject, e.name as email_name, s.is_read as isRead, s.is_failed as isFailed, ec.subject as storedSubject' | |
| ); | |
| } else { | |
| $query->select( | |
| 's.email_id, s.id, s.date_read as dateRead, s.date_sent as dateSent,e.subject, e.name as email_name, s.is_read as isRead, s.is_failed as isFailed, s.viewed_in_browser as viewedInBrowser, s.retry_count as retryCount, s.list_id, l.name as list_name, s.tracking_hash as idHash, s.open_details as openDetails, ec.subject as storedSubject, s.lead_id' | |
| ) | |
| ->leftJoin('s', MAUTIC_TABLE_PREFIX.'lead_lists', 'l', 's.list_id = l.id'); | |
| } | |
| $timestampColumn = 's.date_sent'; | |
| if (isset($options['state'])) { | |
| $state = $options['state']; | |
| if ('read' == $state) { | |
| $timestampColumn = 's.date_read'; | |
| $query->andWhere( | |
| $query->expr()->eq('s.is_read', 1) | |
| ); | |
| } elseif ('failed' == $state) { | |
| $query->andWhere( | |
| $query->expr()->eq('s.is_failed', 1) | |
| ); | |
| } | |
| } | |
| if (isset($options['search']) && $options['search']) { | |
| $query->andWhere( | |
| $query->expr()->or( | |
| $query->expr()->like('ec.subject', $query->expr()->literal('%'.$options['search'].'%')), | |
| $query->expr()->like('e.subject', $query->expr()->literal('%'.$options['search'].'%')), | |
| $query->expr()->like('e.name', $query->expr()->literal('%'.$options['search'].'%')) | |
| ) | |
| ); | |
| } | |
| if (isset($options['fromDate']) && $options['fromDate']) { | |
| $dt = new DateTimeHelper($options['fromDate']); | |
| $query->andWhere( | |
| $query->expr()->gte($timestampColumn, $query->expr()->literal($dt->toUtcString())) | |
| ); | |
| } | |
| $timeToReadParser = function (&$stat): void { | |
| $dateSent = new DateTimeHelper($stat['dateSent']); | |
| if (!empty($stat['dateSent']) && !empty($stat['dateRead'])) { | |
| $stat['timeToRead'] = $dateSent->getDiff($stat['dateRead']); | |
| } else { | |
| $stat['timeToRead'] = false; | |
| } | |
| }; | |
| return $this->getTimelineResults( | |
| $query, | |
| $options, | |
| 'storedSubject, e.subject', | |
| $timestampColumn, | |
| ['openDetails'], | |
| ['dateRead', 'dateSent'], | |
| $timeToReadParser | |
| ); | |
| } | |
| /** | |
| * Get counts for Sent, Read and Failed emails. | |
| * | |
| * @param QueryBuilder $query | |
| * | |
| * @return array | |
| * | |
| * @throws \Doctrine\ORM\NoResultException | |
| * @throws \Doctrine\ORM\NonUniqueResultException | |
| */ | |
| public function getIgnoredReadFailed($query = null) | |
| { | |
| $query->select('count(es.id) as sent, count(CASE WHEN es.is_read THEN 1 ELSE null END) as "read", count(CASE WHEN es.is_failed THEN 1 ELSE null END) as failed'); | |
| $results = $query->execute()->fetchAssociative(); | |
| if ($results) { | |
| $results['ignored'] = $results['sent'] - $results['read'] - $results['failed']; | |
| unset($results['sent']); | |
| } else { | |
| $results['ignored'] = $results['sent'] = $results['read'] = $results['failed'] = 0; | |
| } | |
| return $results; | |
| } | |
| /** | |
| * Get pie graph data for Sent, Read and Failed email count. | |
| * | |
| * @param QueryBuilder $query | |
| * | |
| * @return array | |
| * | |
| * @throws \Doctrine\ORM\NoResultException | |
| * @throws \Doctrine\ORM\NonUniqueResultException | |
| */ | |
| public function getMostEmails($query, $limit = 10, $offset = 0) | |
| { | |
| $query | |
| ->setMaxResults($limit) | |
| ->setFirstResult($offset); | |
| return $query->execute()->fetchAllAssociative(); | |
| } | |
| /** | |
| * Get sent counts based grouped by email Id. | |
| * | |
| * @param array $emailIds | |
| */ | |
| public function getSentCounts($emailIds = [], \DateTime $fromDate = null): array | |
| { | |
| $q = $this->_em->getConnection()->createQueryBuilder(); | |
| $q->select('e.email_id, count(e.id) as sentcount') | |
| ->from(MAUTIC_TABLE_PREFIX.'email_stats', 'e') | |
| ->where( | |
| $q->expr()->and( | |
| $q->expr()->in('e.email_id', $emailIds), | |
| $q->expr()->eq('e.is_failed', ':false') | |
| ) | |
| )->setParameter('false', false, 'boolean'); | |
| if (null !== $fromDate) { | |
| // make sure the date is UTC | |
| $dt = new DateTimeHelper($fromDate); | |
| $q->andWhere( | |
| $q->expr()->gte('e.date_read', $q->expr()->literal($dt->toUtcString())) | |
| ); | |
| } | |
| $q->groupBy('e.email_id'); | |
| // get a total number of sent emails first | |
| $results = $q->executeQuery()->fetchAllAssociative(); | |
| $counts = []; | |
| foreach ($results as $r) { | |
| $counts[$r['email_id']] = $r['sentcount']; | |
| } | |
| return $counts; | |
| } | |
| /** | |
| * Updates lead ID (e.g. after a lead merge). | |
| */ | |
| public function updateLead($fromLeadId, $toLeadId): void | |
| { | |
| $q = $this->_em->getConnection()->createQueryBuilder(); | |
| $q->update(MAUTIC_TABLE_PREFIX.'email_stats') | |
| ->set('lead_id', (int) $toLeadId) | |
| ->where('lead_id = '.(int) $fromLeadId) | |
| ->executeStatement(); | |
| } | |
| /** | |
| * Delete a stat. | |
| */ | |
| public function deleteStat($id): void | |
| { | |
| $this->getEntityManager()->getConnection()->delete(MAUTIC_TABLE_PREFIX.'email_stats', ['id' => (int) $id]); | |
| } | |
| public function deleteStats(array $ids): void | |
| { | |
| $qb = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $qb->delete(MAUTIC_TABLE_PREFIX.'email_stats') | |
| ->where( | |
| $qb->expr()->in('id', $ids) | |
| ) | |
| ->executeStatement(); | |
| } | |
| public function getTableAlias(): string | |
| { | |
| return 's'; | |
| } | |
| /** | |
| * @return array | |
| */ | |
| public function findContactEmailStats($leadId, $emailId) | |
| { | |
| return $this->createQueryBuilder('s') | |
| ->where('IDENTITY(s.lead) = :leadId AND IDENTITY(s.email) = :emailId') | |
| ->setParameter('leadId', (int) $leadId) | |
| ->setParameter('emailId', (int) $emailId) | |
| ->getQuery() | |
| ->getResult(); | |
| } | |
| /** | |
| * @return mixed | |
| */ | |
| public function checkContactsSentEmail($contacts, $emailId) | |
| { | |
| $query = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $query->from(MAUTIC_TABLE_PREFIX.'email_stats', 's'); | |
| $query->select('id, lead_id') | |
| ->where('s.email_id = :email') | |
| ->andWhere('s.lead_id in (:contacts)') | |
| ->andWhere('is_failed = 0') | |
| ->setParameter('email', $emailId) | |
| ->setParameter('contacts', $contacts); | |
| return $query->executeQuery()->fetchAssociative(); | |
| } | |
| /** | |
| * @return array Formatted as [contactId => sentCount] | |
| */ | |
| public function getSentCountForContacts(array $contacts, $emailId): array | |
| { | |
| $query = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $query->from(MAUTIC_TABLE_PREFIX.'email_stats', 's'); | |
| $query->select('count(s.id) as sent_count, s.lead_id') | |
| ->where('s.email_id = :email') | |
| ->andWhere('s.lead_id in (:contacts)') | |
| ->andWhere('s.is_failed = 0') | |
| ->setParameter('email', $emailId) | |
| ->setParameter('contacts', $contacts, ArrayParameterType::INTEGER) | |
| ->groupBy('s.lead_id'); | |
| $results = $query->executeQuery()->fetchAllAssociative(); | |
| $contacts = []; | |
| foreach ($results as $result) { | |
| $contacts[$result['lead_id']] = $result['sent_count']; | |
| } | |
| return $contacts; | |
| } | |
| /** | |
| * @param array<int> $contacts | |
| * | |
| * @return array<int, array<string, int|float>> | |
| * | |
| * @throws Exception | |
| */ | |
| public function getStatsSummaryForContacts(array $contacts): array | |
| { | |
| $queryBuilder = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $subQueryBuilder = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $leadAlias = 'l'; // leads | |
| $statsAlias = 'es'; // email_stats | |
| $subQueryAlias = 'sq'; // sub query | |
| $cutAlias = 'cut'; // channel_url_trackables | |
| $pageHitsAlias = 'ph'; // page_hits | |
| // use sub query to get page hits for and unique page hits selected contacts | |
| $subQueryBuilder->select( | |
| "COUNT({$pageHitsAlias}.id) AS hits", | |
| "COUNT(DISTINCT({$pageHitsAlias}.redirect_id)) AS unique_hits", | |
| "{$cutAlias}.channel_id", | |
| "{$pageHitsAlias}.lead_id" | |
| ) | |
| ->from(MAUTIC_TABLE_PREFIX.'channel_url_trackables', $cutAlias) | |
| ->join( | |
| $cutAlias, | |
| MAUTIC_TABLE_PREFIX.'page_hits', | |
| $pageHitsAlias, | |
| "{$cutAlias}.redirect_id = {$pageHitsAlias}.redirect_id AND {$cutAlias}.channel_id = {$pageHitsAlias}.source_id" | |
| ) | |
| ->where("{$cutAlias}.channel = 'email' AND {$pageHitsAlias}.source = 'email'") | |
| ->andWhere("{$pageHitsAlias}.lead_id in (:contacts)") | |
| ->setParameter('contacts', $contacts, ArrayParameterType::INTEGER) | |
| ->groupBy("{$cutAlias}.channel_id, {$pageHitsAlias}.lead_id"); | |
| // main query | |
| $queryBuilder->select( | |
| "{$leadAlias}.id AS `lead_id`", | |
| "COUNT({$statsAlias}.id) AS `sent_count`", | |
| "SUM(IF({$statsAlias}.is_read IS NULL, 0, {$statsAlias}.is_read)) AS `read_count`", | |
| "SUM(IF({$subQueryAlias}.hits is NULL, 0, 1)) AS `clicked_through_count`", | |
| )->from(MAUTIC_TABLE_PREFIX.'email_stats', $statsAlias) | |
| ->rightJoin( | |
| $statsAlias, | |
| MAUTIC_TABLE_PREFIX.'leads', | |
| $leadAlias, | |
| "{$statsAlias}.lead_id=l.id" | |
| )->leftJoin( | |
| $statsAlias, | |
| "({$subQueryBuilder->getSQL()})", | |
| $subQueryAlias, | |
| "{$statsAlias}.email_id = {$subQueryAlias}.channel_id AND {$statsAlias}.lead_id = {$subQueryAlias}.lead_id" | |
| )->andWhere("{$leadAlias}.id in (:contacts)") | |
| ->setParameter('contacts', $contacts, ArrayParameterType::INTEGER) | |
| ->groupBy("{$leadAlias}.id"); | |
| $results = $queryBuilder->executeQuery()->fetchAllAssociative(); | |
| $contacts = []; | |
| foreach ($results as $result) { | |
| $sentCount = (int) $result['sent_count']; | |
| $readCount = (int) $result['read_count']; | |
| $clickedCount = (int) $result['clicked_through_count']; | |
| $contacts[(int) $result['lead_id']] = [ | |
| 'sent_count' => $sentCount, | |
| 'read_count' => $readCount, | |
| 'clicked_count' => $clickedCount, | |
| 'open_rate' => round($sentCount > 0 ? ($readCount / $sentCount) : 0, 4), | |
| 'click_through_rate' => round($sentCount > 0 ? ($clickedCount / $sentCount) : 0, 4), | |
| 'click_through_open_rate' => round($readCount > 0 ? ($clickedCount / $readCount) : 0, 4), | |
| ]; | |
| } | |
| return $contacts; | |
| } | |
| /** | |
| * @param array<int> $emailsIds | |
| * @param array<int> $eventsIds | |
| * | |
| * @return array<int, array<string, int|string>> | |
| * | |
| * @throws Exception | |
| */ | |
| public function getStatsSummaryByCountry(\DateTimeImmutable $dateFrom, \DateTimeImmutable $dateTo, array $emailsIds, string $sourceType = 'email', array $eventsIds = []): array | |
| { | |
| $queryBuilder = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $subQueryBuilder = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $leadAlias = 'l'; // leads | |
| $statsAlias = 'es'; // email_stats | |
| $subQueryAlias = 'sq'; // sub query | |
| $cutAlias = 'cut'; // channel_url_trackables | |
| $pageHitsAlias = 'ph'; // page_hits | |
| // use sub query to get page hits for and unique page hits selected contacts | |
| $subQueryBuilder->select( | |
| "COUNT({$pageHitsAlias}.id) AS hits", | |
| "COUNT(DISTINCT({$pageHitsAlias}.redirect_id)) AS unique_hits", | |
| "{$cutAlias}.channel_id", | |
| "{$pageHitsAlias}.lead_id" | |
| ) | |
| ->from(MAUTIC_TABLE_PREFIX.'channel_url_trackables', $cutAlias) | |
| ->join( | |
| $cutAlias, | |
| MAUTIC_TABLE_PREFIX.'page_hits', | |
| $pageHitsAlias, | |
| "{$cutAlias}.redirect_id = {$pageHitsAlias}.redirect_id AND {$cutAlias}.channel_id = {$pageHitsAlias}.source_id" | |
| ) | |
| ->where("{$cutAlias}.channel = 'email' AND {$pageHitsAlias}.source = 'email'") | |
| ->andWhere("{$cutAlias}.channel_id in (:emails)") | |
| ->groupBy("{$cutAlias}.channel_id, {$pageHitsAlias}.lead_id"); | |
| // main query | |
| $queryBuilder->addSelect( | |
| "COUNT({$statsAlias}.id) AS `sent_count`", | |
| "SUM(IF({$statsAlias}.is_read IS NULL, 0, {$statsAlias}.is_read)) AS `read_count`", | |
| "SUM(IF({$subQueryAlias}.hits is NULL, 0, 1)) AS `clicked_through_count`", | |
| )->from(MAUTIC_TABLE_PREFIX.'email_stats', $statsAlias) | |
| ->rightJoin( | |
| $statsAlias, | |
| MAUTIC_TABLE_PREFIX.'leads', | |
| $leadAlias, | |
| "{$statsAlias}.lead_id=l.id" | |
| )->leftJoin( | |
| $statsAlias, | |
| "({$subQueryBuilder->getSQL()})", | |
| $subQueryAlias, | |
| "{$statsAlias}.email_id = {$subQueryAlias}.channel_id AND {$statsAlias}.lead_id = {$subQueryAlias}.lead_id" | |
| ); | |
| switch ($sourceType) { | |
| case 'campaign': | |
| $queryBuilder->addSelect("{$leadAlias}.country AS `country`") | |
| ->andWhere("{$statsAlias}.source_id in (:events)") | |
| ->andWhere("{$statsAlias}.source = :source") | |
| ->setParameter('emails', $emailsIds, ArrayParameterType::INTEGER) | |
| ->setParameter('events', $eventsIds, ArrayParameterType::INTEGER) | |
| ->setParameter('source', 'campaign.event'); | |
| break; | |
| case 'email': | |
| $queryBuilder->addSelect("{$leadAlias}.country AS `country`") | |
| ->andWhere("{$statsAlias}.email_id in (:emails)") | |
| ->setParameter('emails', $emailsIds, ArrayParameterType::INTEGER); | |
| } | |
| $queryBuilder->groupBy("{$leadAlias}.country"); | |
| $queryBuilder->andWhere("{$statsAlias}.date_sent BETWEEN :dateFrom AND :dateTo"); | |
| $queryBuilder->setParameter('dateFrom', $dateFrom->format(DateTimeHelper::FORMAT_DB)); | |
| $queryBuilder->setParameter('dateTo', $dateTo->setTime(23, 59, 59)->format('Y-m-d H:i:s')); | |
| return $queryBuilder->executeQuery()->fetchAllAssociative(); | |
| } | |
| } | |