Spaces:
No application file
No application file
| namespace Mautic\CampaignBundle\Entity; | |
| use Doctrine\DBAL\Cache\QueryCacheProfile; | |
| use Doctrine\DBAL\Types\Types; | |
| use Doctrine\ORM\Query\Expr; | |
| use Mautic\CampaignBundle\Entity\Result\CountResult; | |
| use Mautic\CampaignBundle\Executioner\ContactFinder\Limiter\ContactLimiter; | |
| use Mautic\CoreBundle\Entity\CommonRepository; | |
| /** | |
| * @extends CommonRepository<Campaign> | |
| */ | |
| class CampaignRepository extends CommonRepository | |
| { | |
| use ContactLimiterTrait; | |
| use ReplicaConnectionTrait; | |
| public function getEntities(array $args = []) | |
| { | |
| $q = $this->getEntityManager()->createQueryBuilder(); | |
| $q->select($this->getTableAlias().', cat') | |
| ->from(Campaign::class, $this->getTableAlias(), $this->getTableAlias().'.id') | |
| ->leftJoin($this->getTableAlias().'.category', 'cat'); | |
| if (!empty($args['joinLists'])) { | |
| $q->leftJoin($this->getTableAlias().'.lists', 'l'); | |
| } | |
| if (!empty($args['joinForms'])) { | |
| $q->leftJoin($this->getTableAlias().'.forms', 'f'); | |
| } | |
| $q->where($q->expr()->isNull($this->getTableAlias().'.deleted')); | |
| $args['qb'] = $q; | |
| return parent::getEntities($args); | |
| } | |
| public function setCampaignAsDeleted(int $campaignId): void | |
| { | |
| $dateTime = (new \DateTime())->format('Y-m-d H:i:s'); | |
| $this->getEntityManager()->getConnection()->update( | |
| MAUTIC_TABLE_PREFIX.Event::TABLE_NAME, | |
| ['deleted' => $dateTime], | |
| ['campaign_id' => $campaignId] | |
| ); | |
| $this->getEntityManager()->getConnection()->update( | |
| MAUTIC_TABLE_PREFIX.Campaign::TABLE_NAME, | |
| ['deleted' => $dateTime, 'is_published' => 0], | |
| ['id' => $campaignId] | |
| ); | |
| } | |
| /** | |
| * Returns a list of all published (and active) campaigns (optionally for a specific lead). | |
| * | |
| * @param bool $forList If true, returns ID and name only | |
| * @param bool $viewOther If true, returns all the campaigns | |
| * | |
| * @return array | |
| */ | |
| public function getPublishedCampaigns($specificId = null, ?int $leadId = null, $forList = false, $viewOther = false) | |
| { | |
| $q = $this->getEntityManager()->createQueryBuilder() | |
| ->from(Campaign::class, 'c', 'c.id'); | |
| if ($forList && $leadId) { | |
| $q->select('partial c.{id, name}, partial l.{campaign, lead, dateAdded, manuallyAdded, manuallyRemoved}, partial ll.{id}'); | |
| } elseif ($forList) { | |
| $q->select('partial c.{id, name}, partial ll.{id}'); | |
| } else { | |
| $q->select('c, l, partial ll.{id}') | |
| ->leftJoin('c.events', 'e') | |
| ->leftJoin('e.log', 'o'); | |
| } | |
| if ($leadId || !$forList) { | |
| $q->leftJoin('c.leads', 'l'); | |
| } | |
| $q->leftJoin('c.lists', 'll') | |
| ->where($this->getPublishedByDateExpression($q)); | |
| if (!$viewOther) { | |
| $q->andWhere($q->expr()->eq('c.createdBy', ':id')) | |
| ->setParameter('id', $this->currentUser->getId()); | |
| } | |
| if (!empty($specificId)) { | |
| $q->andWhere( | |
| $q->expr()->eq('c.id', (int) $specificId) | |
| ); | |
| } | |
| if (!empty($leadId)) { | |
| $q->andWhere( | |
| $q->expr()->eq('IDENTITY(l.lead)', (int) $leadId) | |
| ); | |
| $q->andWhere( | |
| $q->expr()->eq('l.manuallyRemoved', ':manuallyRemoved') | |
| )->setParameter('manuallyRemoved', false); | |
| } | |
| return $q->getQuery()->getArrayResult(); | |
| } | |
| /** | |
| * Returns a list of all published (and active) campaigns that specific lead lists are part of. | |
| * | |
| * @param int|array $leadLists | |
| */ | |
| public function getPublishedCampaignsByLeadLists($leadLists): array | |
| { | |
| if (!is_array($leadLists)) { | |
| $leadLists = [(int) $leadLists]; | |
| } else { | |
| foreach ($leadLists as &$id) { | |
| $id = (int) $id; | |
| } | |
| } | |
| $q = $this->getEntityManager()->getConnection()->createQueryBuilder() | |
| ->select('c.id, c.name, ll.leadlist_id as list_id') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaigns', 'c'); | |
| $q->join('c', MAUTIC_TABLE_PREFIX.'campaign_leadlist_xref', 'll', 'c.id = ll.campaign_id') | |
| ->where($this->getPublishedByDateExpression($q)); | |
| $q->andWhere( | |
| $q->expr()->in('ll.leadlist_id', $leadLists) | |
| ); | |
| $results = $q->executeQuery()->fetchAllAssociative(); | |
| $campaigns = []; | |
| foreach ($results as $result) { | |
| if (!isset($campaigns[$result['id']])) { | |
| $campaigns[$result['id']] = [ | |
| 'id' => $result['id'], | |
| 'name' => $result['name'], | |
| 'lists' => [], | |
| ]; | |
| } | |
| $campaigns[$result['id']]['lists'][$result['list_id']] = [ | |
| 'id' => $result['list_id'], | |
| ]; | |
| } | |
| return $campaigns; | |
| } | |
| /** | |
| * Get array of list IDs assigned to this campaign. | |
| * | |
| * @param int|null $id | |
| */ | |
| public function getCampaignListIds($id = null): array | |
| { | |
| $q = $this->getEntityManager()->getConnection()->createQueryBuilder() | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_leadlist_xref', 'cl'); | |
| if ($id) { | |
| $q->select('cl.leadlist_id') | |
| ->where( | |
| $q->expr()->eq('cl.campaign_id', $id) | |
| ); | |
| } else { | |
| // Retrieve a list of unique IDs that are assigned to a campaign | |
| $q->select('DISTINCT cl.leadlist_id'); | |
| } | |
| $lists = []; | |
| $results = $q->executeQuery()->fetchAllAssociative(); | |
| foreach ($results as $r) { | |
| $lists[] = $r['leadlist_id']; | |
| } | |
| return $lists; | |
| } | |
| /** | |
| * Get array of list IDs => name assigned to this campaign. | |
| */ | |
| public function getCampaignListSources($id): array | |
| { | |
| $q = $this->getEntityManager()->getConnection()->createQueryBuilder() | |
| ->select('cl.leadlist_id, l.name') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_leadlist_xref', 'cl') | |
| ->join('cl', MAUTIC_TABLE_PREFIX.'lead_lists', 'l', 'l.id = cl.leadlist_id'); | |
| $q->where( | |
| $q->expr()->eq('cl.campaign_id', $id) | |
| ); | |
| $lists = []; | |
| $results = $q->executeQuery()->fetchAllAssociative(); | |
| foreach ($results as $r) { | |
| $lists[$r['leadlist_id']] = $r['name']; | |
| } | |
| return $lists; | |
| } | |
| /** | |
| * Get array of form IDs => name assigned to this campaign. | |
| */ | |
| public function getCampaignFormSources($id): array | |
| { | |
| $q = $this->getEntityManager()->getConnection()->createQueryBuilder() | |
| ->select('cf.form_id, f.name') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_form_xref', 'cf') | |
| ->join('cf', MAUTIC_TABLE_PREFIX.'forms', 'f', 'f.id = cf.form_id'); | |
| $q->where( | |
| $q->expr()->eq('cf.campaign_id', $id) | |
| ); | |
| $forms = []; | |
| $results = $q->executeQuery()->fetchAllAssociative(); | |
| foreach ($results as $r) { | |
| $forms[$r['form_id']] = $r['name']; | |
| } | |
| return $forms; | |
| } | |
| /** | |
| * @return array | |
| */ | |
| public function findByFormId($formId) | |
| { | |
| $q = $this->createQueryBuilder('c') | |
| ->join('c.forms', 'f'); | |
| $q->where( | |
| $q->expr()->eq('f.id', $formId) | |
| ); | |
| return $q->getQuery()->getResult(); | |
| } | |
| public function getTableAlias(): string | |
| { | |
| return 'c'; | |
| } | |
| /** | |
| * @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q | |
| */ | |
| protected function addCatchAllWhereClause($q, $filter): array | |
| { | |
| return $this->addStandardCatchAllWhereClause($q, $filter, [ | |
| 'c.name', | |
| 'c.description', | |
| ]); | |
| } | |
| /** | |
| * @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q | |
| */ | |
| protected function addSearchCommandWhereClause($q, $filter): array | |
| { | |
| return $this->addStandardSearchCommandWhereClause($q, $filter); | |
| } | |
| /** | |
| * @return string[] | |
| */ | |
| public function getSearchCommands(): array | |
| { | |
| return $this->getStandardSearchCommands(); | |
| } | |
| /** | |
| * Get a list of popular (by logs) campaigns. | |
| * | |
| * @param int $limit | |
| */ | |
| public function getPopularCampaigns($limit = 10): array | |
| { | |
| $q = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $q->select('count(cl.ip_id) as hits, c.id AS campaign_id, c.name') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'cl') | |
| ->leftJoin('cl', MAUTIC_TABLE_PREFIX.'campaigns', 'c', 'cl.campaign_id = c.id') | |
| ->orderBy('hits', 'DESC') | |
| ->groupBy('c.id, c.name') | |
| ->setMaxResults($limit); | |
| $expr = $this->getPublishedByDateExpression($q, 'c'); | |
| $q->where($expr); | |
| return $q->executeQuery()->fetchAllAssociative(); | |
| } | |
| public function getCountsForPendingContacts($campaignId, array $pendingEvents, ContactLimiter $limiter): CountResult | |
| { | |
| $q = $this->getReplicaConnection($limiter)->createQueryBuilder(); | |
| $q->select('min(cl.lead_id) as min_id, max(cl.lead_id) as max_id, count(cl.lead_id) as the_count') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_leads', 'cl') | |
| ->where( | |
| $q->expr()->and( | |
| $q->expr()->eq('cl.campaign_id', (int) $campaignId), | |
| $q->expr()->eq('cl.manually_removed', ':false') | |
| ) | |
| ) | |
| ->setParameter('false', false, 'boolean'); | |
| $this->updateQueryFromContactLimiter('cl', $q, $limiter, true); | |
| if (count($pendingEvents) > 0) { | |
| $sq = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $sq->select('null') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'e') | |
| ->where( | |
| $sq->expr()->and( | |
| $sq->expr()->eq('cl.lead_id', 'e.lead_id'), | |
| $sq->expr()->eq('e.rotation', 'cl.rotation'), | |
| $sq->expr()->in('e.event_id', $pendingEvents) | |
| ) | |
| ); | |
| $q->andWhere( | |
| sprintf('NOT EXISTS (%s)', $sq->getSQL()) | |
| ); | |
| } | |
| $result = $q->executeQuery()->fetchAssociative(); | |
| return new CountResult($result['the_count'], $result['min_id'], $result['max_id']); | |
| } | |
| /** | |
| * Get pending contact IDs for a campaign. | |
| */ | |
| public function getPendingContactIds($campaignId, ContactLimiter $limiter): array | |
| { | |
| if ($limiter->hasCampaignLimit() && 0 === $limiter->getCampaignLimitRemaining()) { | |
| return []; | |
| } | |
| $q = $this->getReplicaConnection($limiter)->createQueryBuilder(); | |
| $q->select('cl.lead_id') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_leads', 'cl') | |
| ->where( | |
| $q->expr()->and( | |
| $q->expr()->eq('cl.campaign_id', (int) $campaignId), | |
| $q->expr()->eq('cl.manually_removed', ':false') | |
| ) | |
| ) | |
| ->setParameter('false', false, 'boolean') | |
| ->orderBy('cl.lead_id', 'ASC'); | |
| $this->updateQueryFromContactLimiter('cl', $q, $limiter); | |
| // Only leads that have not started the campaign | |
| $sq = $this->getReplicaConnection($limiter)->createQueryBuilder(); | |
| $sq->select('null') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'e') | |
| ->where( | |
| $sq->expr()->and( | |
| $sq->expr()->eq('e.lead_id', 'cl.lead_id'), | |
| $sq->expr()->eq('e.campaign_id', (int) $campaignId), | |
| $sq->expr()->eq('e.rotation', 'cl.rotation') | |
| ) | |
| ); | |
| $q->andWhere( | |
| sprintf('NOT EXISTS (%s)', $sq->getSQL()) | |
| ); | |
| if ($limiter->hasCampaignLimit() && $limiter->getCampaignLimitRemaining() < $limiter->getBatchLimit()) { | |
| $q->setMaxResults($limiter->getCampaignLimitRemaining()); | |
| } | |
| $results = $q->executeQuery()->fetchAllAssociative(); | |
| $leads = []; | |
| foreach ($results as $r) { | |
| $leads[] = $r['lead_id']; | |
| } | |
| unset($results); | |
| if ($limiter->hasCampaignLimit()) { | |
| $limiter->reduceCampaignLimitRemaining(count($leads)); | |
| } | |
| return $leads; | |
| } | |
| /** | |
| * Get a count of leads that belong to the campaign. | |
| * | |
| * @param int $campaignId | |
| * @param int $leadId Optional lead ID to check if lead is part of campaign | |
| * @param array $pendingEvents List of specific events to rule out | |
| * | |
| * @throws \Doctrine\DBAL\Cache\CacheException | |
| */ | |
| public function getCampaignLeadCount($campaignId, $leadId = null, $pendingEvents = [], \DateTimeInterface $dateFrom = null, \DateTimeInterface $dateTo = null): int | |
| { | |
| $q = $this->getReplicaConnection()->createQueryBuilder(); | |
| $q->select('count(cl.lead_id) as lead_count') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_leads', 'cl') | |
| ->where( | |
| $q->expr()->and( | |
| $q->expr()->eq('cl.campaign_id', (int) $campaignId), | |
| $q->expr()->eq('cl.manually_removed', ':false') | |
| ) | |
| ) | |
| ->setParameter('false', false, Types::BOOLEAN); | |
| if ($leadId) { | |
| $q->andWhere( | |
| $q->expr()->eq('cl.lead_id', (int) $leadId) | |
| ); | |
| } | |
| if ($dateFrom && $dateTo) { | |
| $q->andWhere('cl.date_added BETWEEN FROM_UNIXTIME(:dateFrom) AND FROM_UNIXTIME(:dateTo)') | |
| ->setParameter('dateFrom', $dateFrom->getTimestamp(), \PDO::PARAM_INT) | |
| ->setParameter('dateTo', $dateTo->getTimestamp(), \PDO::PARAM_INT); | |
| } | |
| if (count($pendingEvents) > 0) { | |
| $sq = $this->getReplicaConnection()->createQueryBuilder(); | |
| $sq->select('null') | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'e') | |
| ->where( | |
| $sq->expr()->and( | |
| $sq->expr()->eq('cl.lead_id', 'e.lead_id'), | |
| $sq->expr()->in('e.event_id', $pendingEvents) | |
| ) | |
| ); | |
| if ($dateFrom && $dateTo) { | |
| $sq->andWhere('cl.date_triggered BETWEEN FROM_UNIXTIME(:dateFrom) AND FROM_UNIXTIME(:dateTo)') | |
| ->setParameter('dateFrom', $dateFrom->getTimestamp(), \PDO::PARAM_INT) | |
| ->setParameter('dateTo', $dateTo->getTimestamp(), \PDO::PARAM_INT); | |
| } | |
| $q->andWhere( | |
| sprintf('NOT EXISTS (%s)', $sq->getSQL()) | |
| ); | |
| } | |
| if ($this->getReplicaConnection()->getConfiguration()->getResultCache()) { | |
| $results = $this->getReplicaConnection()->executeCacheQuery( | |
| $q->getSQL(), | |
| $q->getParameters(), | |
| $q->getParameterTypes(), | |
| new QueryCacheProfile(600) | |
| )->fetchAllAssociative(); | |
| } else { | |
| $results = $q->executeQuery()->fetchAllAssociative(); | |
| } | |
| return (int) $results[0]['lead_count']; | |
| } | |
| /** | |
| * Get lead data of a campaign. | |
| * | |
| * @param int $start | |
| * @param bool|false $limit | |
| * @param array $select | |
| * | |
| * @return mixed[] | |
| */ | |
| public function getCampaignLeads($campaignId, $start = 0, $limit = false, $select = ['cl.lead_id']): array | |
| { | |
| $q = $this->getReplicaConnection()->createQueryBuilder(); | |
| $q->select($select) | |
| ->from(MAUTIC_TABLE_PREFIX.'campaign_leads', 'cl') | |
| ->where( | |
| $q->expr()->and( | |
| $q->expr()->eq('cl.campaign_id', (int) $campaignId), | |
| $q->expr()->eq('cl.manually_removed', ':false') | |
| ) | |
| ) | |
| ->setParameter('false', false, 'boolean') | |
| ->orderBy('cl.lead_id', 'ASC'); | |
| if (!empty($limit)) { | |
| $q->setFirstResult($start) | |
| ->setMaxResults($limit); | |
| } | |
| return $q->executeQuery()->fetchAllAssociative(); | |
| } | |
| /** | |
| * @return mixed | |
| */ | |
| public function getContactSingleSegmentByCampaign($contactId, $campaignId) | |
| { | |
| $q = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| return $q->select('ll.id, ll.name') | |
| ->from(MAUTIC_TABLE_PREFIX.'lead_lists', 'll') | |
| ->join('ll', MAUTIC_TABLE_PREFIX.'lead_lists_leads', 'lll', 'lll.leadlist_id = ll.id and lll.lead_id = :contactId and lll.manually_removed = 0') | |
| ->join('ll', MAUTIC_TABLE_PREFIX.'campaign_leadlist_xref', 'clx', 'clx.leadlist_id = ll.id and clx.campaign_id = :campaignId') | |
| ->setParameter('contactId', (int) $contactId) | |
| ->setParameter('campaignId', (int) $campaignId) | |
| ->setMaxResults(1) | |
| ->executeQuery() | |
| ->fetchAssociative(); | |
| } | |
| /** | |
| * @param int $segmentId | |
| * @param array $campaignIds | |
| */ | |
| public function getCampaignsSegmentShare($segmentId, $campaignIds = []): array | |
| { | |
| $q = $this->getEntityManager()->getConnection()->createQueryBuilder(); | |
| $q->select('c.id, c.name, ROUND(IFNULL(COUNT(DISTINCT t.lead_id)/COUNT(DISTINCT cl.lead_id)*100, 0),1) segmentCampaignShare'); | |
| $q->from(MAUTIC_TABLE_PREFIX.'campaigns', 'c') | |
| ->leftJoin('c', MAUTIC_TABLE_PREFIX.'campaign_leads', 'cl', 'cl.campaign_id = c.id AND cl.manually_removed = 0') | |
| ->leftJoin('cl', | |
| '(SELECT lll.lead_id AS ll, lll.lead_id FROM '.MAUTIC_TABLE_PREFIX.'lead_lists_leads lll WHERE lll.leadlist_id = '.$segmentId | |
| .' AND lll.manually_removed = 0)', | |
| 't', | |
| 't.lead_id = cl.lead_id' | |
| ); | |
| $q->groupBy('c.id'); | |
| if (!empty($campaignIds)) { | |
| $q->where($q->expr()->in('c.id', $campaignIds)); | |
| } | |
| return $q->executeQuery()->fetchAllAssociative(); | |
| } | |
| /** | |
| * Searches for emails assigned to campaign and returns associative array of email ids in format:. | |
| * | |
| * array (size=1) | |
| * 0 => | |
| * array (size=2) | |
| * 'channelId' => int 18 | |
| * | |
| * or empty array if nothing found. | |
| * | |
| * @param int $id | |
| */ | |
| public function fetchEmailIdsById($id): array | |
| { | |
| $emails = $this->getEntityManager() | |
| ->createQueryBuilder() | |
| ->select('e.channelId') | |
| ->from(Campaign::class, $this->getTableAlias(), $this->getTableAlias().'.id') | |
| ->leftJoin( | |
| $this->getTableAlias().'.events', | |
| 'e', | |
| Expr\Join::WITH, | |
| "e.channel = '".Event::CHANNEL_EMAIL."'" | |
| ) | |
| ->where($this->getTableAlias().'.id = :id') | |
| ->setParameter('id', $id) | |
| ->andWhere('e.channelId IS NOT NULL') | |
| ->getQuery() | |
| ->setHydrationMode(\Doctrine\ORM\Query::HYDRATE_ARRAY) | |
| ->getResult(); | |
| $return = []; | |
| foreach ($emails as $email) { | |
| // Every channelId represents e-mail ID | |
| $return[] = $email['channelId']; // mautic_campaign_events.channel_id | |
| } | |
| return $return; | |
| } | |
| /** | |
| * @return array<int, int> | |
| */ | |
| public function getCampaignIdsWithDependenciesOnEmail(int $emailId): array | |
| { | |
| $query = $this->getEntityManager() | |
| ->createQueryBuilder() | |
| ->select($this->getTableAlias().'.id') | |
| ->distinct() | |
| ->from(Campaign::class, $this->getTableAlias(), $this->getTableAlias().'.id') | |
| ->leftJoin( | |
| $this->getTableAlias().'.events', | |
| 'e', | |
| Expr\Join::WITH, | |
| "e.channel = '".Event::CHANNEL_EMAIL."'" | |
| ) | |
| ->where('e.channelId = :emailId') | |
| ->setParameter('emailId', $emailId) | |
| ->getQuery(); | |
| return array_unique(array_map(fn ($val): int => (int) $val, $query->getSingleColumnResult())); | |
| } | |
| } | |