![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/mautic.corals.io/app/bundles/CampaignBundle/Entity/ |
<?php namespace Mautic\CampaignBundle\Entity; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Types\Types; use Mautic\CampaignBundle\Executioner\ContactFinder\Limiter\ContactLimiter; use Mautic\CoreBundle\Entity\CommonRepository; use Mautic\CoreBundle\Helper\Chart\ChartQuery; use Mautic\LeadBundle\Entity\TimelineTrait; /** * @extends CommonRepository<LeadEventLog> */ class LeadEventLogRepository extends CommonRepository { use TimelineTrait; use ContactLimiterTrait; use ReplicaConnectionTrait; public const LOG_DELETE_BATCH_SIZE = 5000; public function getEntities(array $args = []) { $alias = $this->getTableAlias(); $q = $this ->createQueryBuilder($alias) ->join($alias.'.ipAddress', 'i'); if (empty($args['campaign_id'])) { $q->join($alias.'.event', 'e') ->join($alias.'.campaign', 'c'); } else { $q->andWhere( $q->expr()->eq('IDENTITY('.$this->getTableAlias().'.campaign)', (int) $args['campaign_id']) ); } if (!empty($args['contact_id'])) { $q->andWhere( $q->expr()->eq('IDENTITY('.$this->getTableAlias().'.lead)', (int) $args['contact_id']) ); } $args['qb'] = $q; return parent::getEntities($args); } public function getTableAlias(): string { return 'll'; } /** * Get a lead's page event log. * * @param int|null $leadId * * @return array */ public function getLeadLogs($leadId = null, array $options = []) { $query = $this->getEntityManager() ->getConnection() ->createQueryBuilder() ->select('ll.id as log_id, ll.event_id, ll.campaign_id, ll.date_triggered as dateTriggered, e.name AS event_name, e.description AS event_description, c.name AS campaign_name, c.description AS campaign_description, ll.metadata, e.type, ll.is_scheduled as isScheduled, ll.trigger_date as triggerDate, ll.channel, ll.channel_id as channel_id, ll.lead_id, fl.reason as fail_reason ' ) ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'll') ->join('ll', MAUTIC_TABLE_PREFIX.'campaign_events', 'e', 'll.event_id = e.id') ->join('ll', MAUTIC_TABLE_PREFIX.'campaigns', 'c', 'll.campaign_id = c.id') ->leftJoin('ll', MAUTIC_TABLE_PREFIX.'campaign_lead_event_failed_log', 'fl', 'fl.log_id = ll.id') ->andWhere('e.event_type != :eventType') ->setParameter('eventType', 'decision'); if ($leadId) { $query->where('ll.lead_id = '.(int) $leadId); } if (isset($options['scheduledState'])) { if ($options['scheduledState']) { // Include cancelled as well $query->andWhere( $query->expr()->or( $query->expr()->eq('ll.is_scheduled', ':scheduled'), $query->expr()->and( $query->expr()->eq('ll.is_scheduled', 0), $query->expr()->isNull('ll.date_triggered') ) ) ); } else { $query->andWhere( $query->expr()->eq('ll.is_scheduled', ':scheduled') ); } $query->setParameter('scheduled', $options['scheduledState'], 'boolean'); } if (isset($options['search']) && $options['search']) { $query->andWhere( $query->expr()->or( $query->expr()->like('e.name', $query->expr()->literal('%'.$options['search'].'%')), $query->expr()->like('e.description', $query->expr()->literal('%'.$options['search'].'%')), $query->expr()->like('c.name', $query->expr()->literal('%'.$options['search'].'%')), $query->expr()->like('c.description', $query->expr()->literal('%'.$options['search'].'%')) ) ); } return $this->getTimelineResults($query, $options, 'e.name', 'll.date_triggered', ['metadata'], ['dateTriggered', 'triggerDate']); } /** * Get a lead's upcoming events. */ public function getUpcomingEvents(array $options = null): array { $leadIps = []; $query = $this->_em->getConnection()->createQueryBuilder(); $query->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'll') ->select('ll.event_id, ll.campaign_id, ll.trigger_date, ll.lead_id, e.name AS event_name, e.description AS event_description, c.name AS campaign_name, c.description AS campaign_description, ll.metadata, CONCAT(CONCAT(l.firstname, \' \'), l.lastname) AS lead_name') ->leftJoin('ll', MAUTIC_TABLE_PREFIX.'campaign_events', 'e', 'e.id = ll.event_id') ->leftJoin('ll', MAUTIC_TABLE_PREFIX.'campaigns', 'c', 'c.id = e.campaign_id') ->leftJoin('ll', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = ll.lead_id') ->where($query->expr()->eq('ll.is_scheduled', 1)); if (isset($options['lead'])) { /** @var \Mautic\CoreBundle\Entity\IpAddress $ip */ foreach ($options['lead']->getIpAddresses() as $ip) { $leadIps[] = $ip->getId(); } $query->andWhere('ll.lead_id = :leadId') ->setParameter('leadId', $options['lead']->getId()); } if (isset($options['type'])) { $query->andwhere('e.type = :type') ->setParameter('type', $options['type']); } if (isset($options['eventType'])) { if (is_array($options['eventType'])) { $query->andWhere( $query->expr()->in('e.event_type', array_map([$query->expr(), 'literal'], $options['eventType'])) ); } else { $query->andwhere('e.event_type = :eventTypes') ->setParameter('eventTypes', $options['eventType']); } } if (isset($options['limit'])) { $query->setMaxResults($options['limit']); } else { $query->setMaxResults(10); } $query->orderBy('ll.trigger_date'); if (empty($options['canViewOthers']) && isset($this->currentUser)) { $query->andWhere('c.created_by = :userId') ->setParameter('userId', $this->currentUser->getId()); } return $query->executeQuery()->fetchAllAssociative(); } /** * @param int $campaignId * @param bool $excludeScheduled * @param bool $excludeNegative * @param bool $all * * @throws \Doctrine\DBAL\Cache\CacheException */ public function getCampaignLogCounts( $campaignId, $excludeScheduled = false, $excludeNegative = true, $all = false, \DateTimeInterface $dateFrom = null, \DateTimeInterface $dateTo = null, int $eventId = null ): array { $join = $all ? 'leftJoin' : 'innerJoin'; $q = $this->_em->getConnection()->createQueryBuilder(); $q->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'o'); $q->$join( 'o', MAUTIC_TABLE_PREFIX.'campaign_leads', 'l', 'l.campaign_id = '.(int) $campaignId.' and l.manually_removed = 0 and o.lead_id = l.lead_id and l.rotation = o.rotation' ); $expr = $q->expr()->and( $q->expr()->eq('o.campaign_id', (int) $campaignId) ); if ($eventId) { $expr = $expr->with( $q->expr()->eq('o.event_id', $eventId) ); } $groupBy = 'o.event_id'; if ($excludeNegative) { $q->select('o.event_id, count(o.lead_id) as lead_count'); $expr = $expr->with( $q->expr()->or( $q->expr()->isNull('o.non_action_path_taken'), $q->expr()->eq('o.non_action_path_taken', ':false') ) ); } else { $q->select('o.event_id, count(o.lead_id) as lead_count, o.non_action_path_taken'); $groupBy .= ', o.non_action_path_taken'; } if ($excludeScheduled) { $expr = $expr->with( $q->expr()->eq('o.is_scheduled', ':false') ); } // Exclude failed events $failedSq = $this->getReplicaConnection()->createQueryBuilder(); $failedSq->select('null') ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_failed_log', 'fe') ->where( $failedSq->expr()->eq('fe.log_id', 'o.id') ); if ($dateFrom && $dateTo) { $failedSq->andWhere('fe.date_added BETWEEN FROM_UNIXTIME(:dateFrom) AND FROM_UNIXTIME(:dateTo)') ->setParameter('dateFrom', $dateFrom->getTimestamp(), \PDO::PARAM_INT) ->setParameter('dateTo', $dateTo->getTimestamp(), \PDO::PARAM_INT); } $expr = $expr->with( sprintf('NOT EXISTS (%s)', $failedSq->getSQL()) ); $q->where($expr) ->setParameter('false', false, 'boolean') ->groupBy($groupBy); if ($dateFrom && $dateTo) { $q->andWhere('o.date_triggered BETWEEN FROM_UNIXTIME(:dateFrom) AND FROM_UNIXTIME(:dateTo)') ->setParameter('dateFrom', $dateFrom->getTimestamp(), \PDO::PARAM_INT) ->setParameter('dateTo', $dateTo->getTimestamp(), \PDO::PARAM_INT); } if ($this->_em->getConnection()->getConfiguration()->getResultCache()) { $results = $this->_em->getConnection()->executeCacheQuery( $q->getSQL(), $q->getParameters(), $q->getParameterTypes(), new QueryCacheProfile(600) )->fetchAllAssociative(); } else { $results = $q->executeQuery()->fetchAllAssociative(); } $return = []; // group by event id foreach ($results as $l) { if (!$excludeNegative) { if (!isset($return[$l['event_id']])) { $return[$l['event_id']] = [ 0 => 0, 1 => 0, ]; } $key = (int) $l['non_action_path_taken'] ? 0 : 1; $return[$l['event_id']][$key] = (int) $l['lead_count']; } else { $return[$l['event_id']] = (int) $l['lead_count']; } } return $return; } /** * Updates lead ID (e.g. after a lead merge). */ public function updateLead($fromLeadId, $toLeadId): void { // First check to ensure the $toLead doesn't already exist $results = $this->_em->getConnection()->createQueryBuilder() ->select('cl.event_id') ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'cl') ->where('cl.lead_id = '.$toLeadId) ->executeQuery() ->fetchAllAssociative(); $exists = []; foreach ($results as $r) { $exists[] = $r['event_id']; } $q = $this->_em->getConnection()->createQueryBuilder(); $q->update(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log') ->set('lead_id', (int) $toLeadId) ->where('lead_id = '.(int) $fromLeadId); if (!empty($exists)) { $q->andWhere( $q->expr()->notIn('event_id', $exists) )->executeStatement(); // Delete remaining leads as the new lead already belongs $this->_em->getConnection()->createQueryBuilder() ->delete(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log') ->where('lead_id = '.(int) $fromLeadId) ->executeStatement(); } else { $q->executeStatement(); } } public function getChartQuery($options): array { $chartQuery = new ChartQuery($this->getReplicaConnection(), $options['dateFrom'], $options['dateTo']); // Load points for selected period $query = $this->getReplicaConnection()->createQueryBuilder(); $query->select('ll.id, ll.date_triggered') ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'll') ->join('ll', MAUTIC_TABLE_PREFIX.'campaign_events', 'e', 'e.id = ll.event_id'); if (isset($options['channel'])) { $query->andwhere("e.channel = '".$options['channel']."'"); } if (isset($options['channelId'])) { $query->andwhere('e.channel_id = '.(int) $options['channelId']); } if (isset($options['type'])) { $query->andwhere("e.type = '".$options['type']."'"); } if (isset($options['logChannel'])) { $query->andwhere("ll.channel = '".$options['logChannel']."'"); } if (isset($options['logChannelId'])) { $query->andwhere('ll.channel_id = '.(int) $options['logChannelId']); } if (!isset($options['is_scheduled'])) { $query->andWhere($query->expr()->eq('ll.is_scheduled', 0)); } else { $query->andWhere($query->expr()->eq('ll.is_scheduled', 1)); } return $chartQuery->fetchTimeData('('.$query.')', 'date_triggered'); } /** * @param int $eventId * * @return ArrayCollection * * @throws \Doctrine\ORM\Query\QueryException */ public function getScheduled($eventId, \DateTime $now, ContactLimiter $limiter) { if ($limiter->hasCampaignLimit() && 0 === $limiter->getCampaignLimitRemaining()) { return new ArrayCollection(); } $this->getReplicaConnection($limiter); $q = $this->createQueryBuilder('o'); $q->select('o, e, c') ->indexBy('o', 'o.id') ->innerJoin('o.event', 'e') ->innerJoin('e.campaign', 'c') ->where( $q->expr()->andX( $q->expr()->eq('IDENTITY(o.event)', ':eventId'), $q->expr()->eq('o.isScheduled', ':true'), $q->expr()->lte('o.triggerDate', ':now'), $q->expr()->eq('c.isPublished', 1) ) ) ->setParameter('eventId', (int) $eventId) ->setParameter('now', $now) ->setParameter('true', true, Types::BOOLEAN); $this->updateOrmQueryFromContactLimiter('o', $q, $limiter); if ($limiter->hasCampaignLimit() && $limiter->getCampaignLimitRemaining() < $limiter->getBatchLimit()) { $q->setMaxResults($limiter->getCampaignLimitRemaining()); } $result = new ArrayCollection($q->getQuery()->getResult()); if ($limiter->hasCampaignLimit()) { $limiter->reduceCampaignLimitRemaining($result->count()); } return $result; } /** * @throws \Doctrine\ORM\Query\QueryException */ public function getScheduledByIds(array $ids): ArrayCollection { $this->getReplicaConnection(); $q = $this->createQueryBuilder('o'); $q->select('o, e, c') ->indexBy('o', 'o.id') ->innerJoin('o.event', 'e') ->innerJoin('e.campaign', 'c') ->where( $q->expr()->andX( $q->expr()->in('o.id', $ids), $q->expr()->eq('o.isScheduled', 1), $q->expr()->eq('c.isPublished', 1), $q->expr()->isNull('c.deleted'), $q->expr()->isNull('e.deleted') ) ); return new ArrayCollection($q->getQuery()->getResult()); } /** * @param int $campaignId */ public function getScheduledCounts($campaignId, \DateTime $date, ContactLimiter $limiter): array { $now = clone $date; $now->setTimezone(new \DateTimeZone('UTC')); $q = $this->getReplicaConnection($limiter)->createQueryBuilder(); $expr = $q->expr()->and( $q->expr()->eq('l.campaign_id', ':campaignId'), $q->expr()->eq('l.is_scheduled', ':true'), $q->expr()->lte('l.trigger_date', ':now'), $q->expr()->eq('c.is_published', 1) ); $this->updateQueryFromContactLimiter('l', $q, $limiter, true); $results = $q->select('COUNT(*) as event_count, l.event_id') ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'l') ->join('l', MAUTIC_TABLE_PREFIX.'campaigns', 'c', 'l.campaign_id = c.id') ->where($expr) ->setParameter('campaignId', (int) $campaignId) ->setParameter('now', $now->format('Y-m-d H:i:s')) ->setParameter('true', true, \PDO::PARAM_BOOL) ->groupBy('l.event_id') ->executeQuery() ->fetchAllAssociative(); $events = []; foreach ($results as $result) { $events[$result['event_id']] = (int) $result['event_count']; } return $events; } public function getDatesExecuted($eventId, array $contactIds): array { $qb = $this->getReplicaConnection()->createQueryBuilder(); $qb->select('log.lead_id, log.date_triggered, log.is_scheduled') ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'log') ->where( $qb->expr()->and( $qb->expr()->eq('log.event_id', $eventId), $qb->expr()->in('log.lead_id', $contactIds) ) ); $results = $qb->executeQuery()->fetchAllAssociative(); $dates = []; foreach ($results as $result) { $dates[$result['lead_id']] = new \DateTime($result['date_triggered'], new \DateTimeZone('UTC')); if (1 === (int) $result['is_scheduled']) { unset($dates[$result['lead_id']]); } } return $dates; } public function getOldestTriggeredDate(): ?\DateTime { $qb = $this->getReplicaConnection()->createQueryBuilder(); $qb->select('log.date_triggered') ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'log') ->orderBy('log.date_triggered', 'ASC') ->setMaxResults(1); $results = $qb->executeQuery()->fetchAllAssociative(); return isset($results[0]['date_triggered']) ? new \DateTime($results[0]['date_triggered']) : null; } /** * @param int $contactId * @param int $campaignId * @param int $rotation */ public function hasBeenInCampaignRotation($contactId, $campaignId, $rotation): bool { $qb = $this->getReplicaConnection()->createQueryBuilder(); $qb->select('log.rotation') ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'log') ->where( $qb->expr()->and( $qb->expr()->eq('log.lead_id', ':contactId'), $qb->expr()->eq('log.campaign_id', ':campaignId'), $qb->expr()->in('log.rotation', ':rotation') ) ) ->setParameter('contactId', (int) $contactId) ->setParameter('campaignId', (int) $campaignId) ->setParameter('rotation', (int) $rotation) ->setMaxResults(1); $results = $qb->executeQuery()->fetchAllAssociative(); return !empty($results); } /** * @param string $message * * @throws \Doctrine\DBAL\Exception */ public function unscheduleEvents(Lead $campaignMember, $message): void { $contactId = $campaignMember->getLead()->getId(); $campaignId = $campaignMember->getCampaign()->getId(); $rotation = $campaignMember->getRotation(); $dateAdded = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s'); // Insert entries into the failed log so it's known why they were never executed $prefix = MAUTIC_TABLE_PREFIX; $sql = <<<SQL REPLACE INTO {$prefix}campaign_lead_event_failed_log( `log_id`, `date_added`, `reason`) SELECT id, :dateAdded as date_added, :message as reason from {$prefix}campaign_lead_event_log WHERE is_scheduled = 1 AND lead_id = :contactId AND campaign_id = :campaignId AND rotation = :rotation SQL; $connection = $this->getEntityManager()->getConnection(); $stmt = $connection->prepare($sql); $stmt->bindValue('dateAdded', $dateAdded, \PDO::PARAM_STR); $stmt->bindValue('message', $message, \PDO::PARAM_STR); $stmt->bindValue('contactId', $contactId, \PDO::PARAM_INT); $stmt->bindValue('campaignId', $campaignId, \PDO::PARAM_INT); $stmt->bindValue('rotation', $rotation, \PDO::PARAM_INT); $stmt->executeStatement(); // Now unschedule them $qb = $connection->createQueryBuilder(); $qb->update(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log') ->set('is_scheduled', 0) ->where( $qb->expr()->and( $qb->expr()->eq('is_scheduled', 1), $qb->expr()->eq('lead_id', ':contactId'), $qb->expr()->eq('campaign_id', ':campaignId'), $qb->expr()->eq('rotation', ':rotation') ) ) ->setParameters( [ 'contactId' => (int) $contactId, 'campaignId' => (int) $campaignId, 'rotation' => (int) $rotation, ] ) ->executeStatement(); } public function removeEventLogsByCampaignId(int $campaignId): void { $table_name = $this->getTableName(); $sql = "DELETE FROM {$table_name} WHERE campaign_id = (?) LIMIT ".self::LOG_DELETE_BATCH_SIZE; $conn = $this->getEntityManager()->getConnection(); $deleteEntries = true; while ($deleteEntries) { $deleteEntries = $conn->executeQuery($sql, [$campaignId], [Types::INTEGER])->rowCount(); } } /** * @param string[] $eventIds */ public function removeEventLogs(array $eventIds): void { $table_name = $this->getTableName(); $sql = "DELETE FROM {$table_name} WHERE event_id IN (?) ORDER BY event_id ASC LIMIT ".self::LOG_DELETE_BATCH_SIZE; $conn = $this->getEntityManager()->getConnection(); $deleteEntries = true; while ($deleteEntries) { $deleteEntries = $conn->executeQuery($sql, [$eventIds], [ArrayParameterType::INTEGER])->rowCount(); } } }