![]() 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/Model/ |
<?php namespace Mautic\CampaignBundle\Model; use Doctrine\DBAL\Exception; use Doctrine\ORM\EntityManager; use Doctrine\ORM\PersistentCollection; use Mautic\CampaignBundle\CampaignEvents; use Mautic\CampaignBundle\Entity\Campaign; use Mautic\CampaignBundle\Entity\Event; use Mautic\CampaignBundle\Entity\Lead as CampaignLead; use Mautic\CampaignBundle\Entity\LeadEventLogRepository; use Mautic\CampaignBundle\Event as Events; use Mautic\CampaignBundle\EventCollector\EventCollector; use Mautic\CampaignBundle\Executioner\ContactFinder\Limiter\ContactLimiter; use Mautic\CampaignBundle\Form\Type\CampaignType; use Mautic\CampaignBundle\Helper\ChannelExtractor; use Mautic\CampaignBundle\Membership\MembershipBuilder; use Mautic\CoreBundle\Helper\Chart\ChartQuery; use Mautic\CoreBundle\Helper\Chart\LineChart; use Mautic\CoreBundle\Helper\CoreParametersHelper; use Mautic\CoreBundle\Helper\UserHelper; use Mautic\CoreBundle\Model\FormModel as CommonFormModel; use Mautic\CoreBundle\Security\Permissions\CorePermissions; use Mautic\CoreBundle\Translation\Translator; use Mautic\EmailBundle\Entity\Stat; use Mautic\EmailBundle\Entity\StatRepository; use Mautic\FormBundle\Entity\Form; use Mautic\FormBundle\Model\FormModel; use Mautic\LeadBundle\Entity\Lead; use Mautic\LeadBundle\Model\ListModel; use Mautic\LeadBundle\Tracker\ContactTracker; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; /** * @extends CommonFormModel<Campaign> */ class CampaignModel extends CommonFormModel { public function __construct( protected ListModel $leadListModel, protected FormModel $formModel, private EventCollector $eventCollector, private MembershipBuilder $membershipBuilder, private ContactTracker $contactTracker, EntityManager $em, CorePermissions $security, EventDispatcherInterface $dispatcher, UrlGeneratorInterface $router, Translator $translator, UserHelper $userHelper, LoggerInterface $mauticLogger, CoreParametersHelper $coreParametersHelper ) { parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper); } /** * @return \Mautic\CampaignBundle\Entity\CampaignRepository */ public function getRepository() { $repo = $this->em->getRepository(Campaign::class); $repo->setCurrentUser($this->userHelper->getUser()); return $repo; } /** * @return \Mautic\CampaignBundle\Entity\EventRepository */ public function getEventRepository() { return $this->em->getRepository(Event::class); } /** * @return \Mautic\CampaignBundle\Entity\LeadRepository */ public function getCampaignLeadRepository() { return $this->em->getRepository(CampaignLead::class); } /** * @return LeadEventLogRepository */ public function getCampaignLeadEventLogRepository() { return $this->em->getRepository(\Mautic\CampaignBundle\Entity\LeadEventLog::class); } public function getPermissionBase(): string { return 'campaign:campaigns'; } /** * @param object $entity * @param string|null $action * @param array $options * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ public function createForm($entity, FormFactoryInterface $formFactory, $action = null, $options = []): \Symfony\Component\Form\FormInterface { if (!$entity instanceof Campaign) { throw new MethodNotAllowedHttpException(['Campaign']); } if (!empty($action)) { $options['action'] = $action; } return $formFactory->create(CampaignType::class, $entity, $options); } /** * Get a specific entity or generate a new one if id is empty. */ public function getEntity($id = null): ?Campaign { if (null === $id) { return new Campaign(); } return parent::getEntity($id); } /** * Delete an array of campaigns. * * @param int[] $campaignIds * * @return array<int,Campaign> */ public function deleteEntities($campaignIds): array { $entities = []; foreach ($campaignIds as $campaignId) { $campaign = $this->getEntity($campaignId); if ($campaign) { $entities[$campaignId] = $campaign; $this->deleteEntity($campaign); } } return $entities; } public function deleteEntity($entity): void { // Null all the event parents for this campaign to avoid database constraints $this->getEventRepository()->nullEventParents($entity->getId()); $this->dispatchEvent('pre_delete', $entity); $this->getRepository()->setCampaignAsDeleted($entity->getId()); $this->dispatcher->dispatch(new Events\DeleteCampaign($entity), CampaignEvents::ON_CAMPAIGN_DELETE); } public function deleteCampaign(Campaign $campaign): void { $campaign->deletedId = $campaign->getId(); $this->getRepository()->deleteEntity($campaign); $this->dispatchEvent('post_delete', $campaign); } /** * @throws MethodNotAllowedHttpException */ protected function dispatchEvent($action, &$entity, $isNew = false, \Symfony\Contracts\EventDispatcher\Event $event = null): ?\Symfony\Contracts\EventDispatcher\Event { if ($entity instanceof CampaignLead) { return null; } if (!$entity instanceof Campaign) { throw new MethodNotAllowedHttpException(['Campaign']); } switch ($action) { case 'pre_save': $name = CampaignEvents::CAMPAIGN_PRE_SAVE; break; case 'post_save': $name = CampaignEvents::CAMPAIGN_POST_SAVE; break; case 'pre_delete': $name = CampaignEvents::CAMPAIGN_PRE_DELETE; break; case 'post_delete': $name = CampaignEvents::CAMPAIGN_POST_DELETE; break; default: return null; } if ($this->dispatcher->hasListeners($name)) { if (empty($event)) { $event = new Events\CampaignEvent($entity, $isNew); } $this->dispatcher->dispatch($event, $name); return $event; } else { return null; } } /** * @return array */ public function setEvents(Campaign $entity, $sessionEvents, $sessionConnections, $deletedEvents) { $existingEvents = $entity->getEvents()->toArray(); $events = []; $hierarchy = []; foreach ($sessionEvents as $properties) { $isNew = (!empty($properties['id']) && isset($existingEvents[$properties['id']])) ? false : true; $event = !$isNew ? $existingEvents[$properties['id']] : new Event(); foreach ($properties as $f => $v) { if ('id' == $f && str_starts_with($v, 'new')) { // set the temp ID used to be able to match up connections $event->setTempId($v); } if (in_array($f, ['id', 'parent'])) { continue; } $func = 'set'.ucfirst($f); if (method_exists($event, $func)) { $event->$func($v); } } ChannelExtractor::setChannel($event, $event, $this->eventCollector->getEventConfig($event)); $event->setCampaign($entity); $events[$properties['id']] = $event; } foreach ($deletedEvents as $deleteMe) { if (isset($existingEvents[$deleteMe])) { // Remove child from parent $parent = $existingEvents[$deleteMe]->getParent(); if ($parent) { $parent->removeChild($existingEvents[$deleteMe]); $existingEvents[$deleteMe]->removeParent(); } $entity->removeEvent($existingEvents[$deleteMe]); unset($events[$deleteMe]); } } $relationships = []; if (isset($sessionConnections['connections'])) { foreach ($sessionConnections['connections'] as $connection) { $source = $connection['sourceId']; $target = $connection['targetId']; if (in_array($source, ['lists', 'forms'])) { // Only concerned with events and not sources continue; } if (isset($connection['anchors']['source'])) { $sourceDecision = $connection['anchors']['source']; } else { $sourceDecision = (!empty($connection['anchors'][0])) ? $connection['anchors'][0]['endpoint'] : null; } if ('leadsource' == $sourceDecision) { // Lead source connection that does not matter continue; } $relationships[$target] = [ 'parent' => $source, 'decision' => $sourceDecision, ]; } } // Assign parent/child relationships foreach ($events as $id => $e) { if (isset($relationships[$id])) { // Has a parent $anchor = in_array($relationships[$id]['decision'], ['yes', 'no']) ? $relationships[$id]['decision'] : null; $events[$id]->setDecisionPath($anchor); $parentId = $relationships[$id]['parent']; $events[$id]->setParent($events[$parentId]); $hierarchy[$id] = $parentId; } elseif ($events[$id]->getParent()) { // No longer has a parent so null it out // Remove decision so that it doesn't affect execution $events[$id]->setDecisionPath(null); // Remove child from parent $parent = $events[$id]->getParent(); $parent->removeChild($events[$id]); // Remove parent from child $events[$id]->removeParent(); $hierarchy[$id] = 'null'; } else { // Is a parent $hierarchy[$id] = 'null'; // Remove decision so that it doesn't affect execution $events[$id]->setDecisionPath(null); } } $entity->addEvents($events); // set event order used when querying the events $this->buildOrder($hierarchy, $events, $entity); uasort( $events, function ($a, $b): int { $aOrder = $a->getOrder(); $bOrder = $b->getOrder(); return $aOrder <=> $bOrder; } ); // Persist events if campaign is being edited if ($entity->getId()) { $this->getEventRepository()->saveEntities($events); } return $events; } /** * @param bool $persist * * @return array */ public function setCanvasSettings($entity, $settings, $persist = true, $events = null) { if (null === $events) { $events = $entity->getEvents(); } $tempIds = []; foreach ($events as $e) { if ($e instanceof Event) { $tempIds[$e->getTempId()] = $e->getId(); } else { $tempIds[$e['tempId']] = $e['id']; } } if (!isset($settings['nodes'])) { $settings['nodes'] = []; } foreach ($settings['nodes'] as &$node) { if (str_contains($node['id'], 'new')) { // Find the real one and update the node $node['id'] = str_replace($node['id'], $tempIds[$node['id']], $node['id']); } } if (!isset($settings['connections'])) { $settings['connections'] = []; } foreach ($settings['connections'] as &$connection) { // Check source if (str_contains($connection['sourceId'], 'new')) { // Find the real one and update the node $connection['sourceId'] = str_replace($connection['sourceId'], $tempIds[$connection['sourceId']], $connection['sourceId']); } // Check target if (str_contains($connection['targetId'], 'new')) { // Find the real one and update the node $connection['targetId'] = str_replace($connection['targetId'], $tempIds[$connection['targetId']], $connection['targetId']); } // Rebuild anchors if (!isset($connection['anchors']['source'])) { $anchors = []; foreach ($connection['anchors'] as $k => $anchor) { $type = (0 === $k) ? 'source' : 'target'; $anchors[$type] = $anchor['endpoint']; } $connection['anchors'] = $anchors; } } $entity->setCanvasSettings($settings); if ($persist) { $this->getRepository()->saveEntity($entity); } return $settings; } /** * Get list of sources for a campaign. */ public function getLeadSources($campaign): array { $campaignId = ($campaign instanceof Campaign) ? $campaign->getId() : $campaign; $sources = []; // Lead lists $sources['lists'] = $this->getRepository()->getCampaignListSources($campaignId); // Forms $sources['forms'] = $this->getRepository()->getCampaignFormSources($campaignId); return $sources; } /** * Add and/or delete lead sources from a campaign. */ public function setLeadSources(Campaign $entity, $addedSources, $deletedSources): void { foreach ($addedSources as $type => $sources) { foreach ($sources as $id => $label) { switch ($type) { case 'lists': $entity->addList($this->em->getReference(\Mautic\LeadBundle\Entity\LeadList::class, $id)); break; case 'forms': $entity->addForm($this->em->getReference(Form::class, $id)); break; default: break; } } } foreach ($deletedSources as $type => $sources) { foreach ($sources as $id => $label) { switch ($type) { case 'lists': $entity->removeList($this->em->getReference(\Mautic\LeadBundle\Entity\LeadList::class, $id)); break; case 'forms': $entity->removeForm($this->em->getReference(Form::class, $id)); break; default: break; } } } } /** * Get a list of source choices. * * @param string $sourceType * @param bool $globalOnly */ public function getSourceLists($sourceType = null, $globalOnly = false): array { $choices = []; switch ($sourceType) { case 'lists': case null: $choices['lists'] = []; $lists = $globalOnly ? $this->leadListModel->getGlobalLists() : $this->leadListModel->getUserLists(); if ($lists) { foreach ($lists as $list) { $choices['lists'][$list['id']] = $list['name']; } } // no break case 'forms': case null: $choices['forms'] = []; $viewOther = $this->security->isGranted('form:forms:viewother'); $repo = $this->formModel->getRepository(); $repo->setCurrentUser($this->userHelper->getUser()); $forms = $repo->getFormList('', 0, 0, $viewOther, 'campaign'); foreach ($forms as $form) { $choices['forms'][$form['id']] = $form['name']; } } foreach ($choices as &$typeChoices) { asort($typeChoices); } return (null == $sourceType) ? $choices : $choices[$sourceType]; } /** * @param mixed $form * * @return array */ public function getCampaignsByForm($form) { $formId = ($form instanceof Form) ? $form->getId() : $form; return $this->getRepository()->findByFormId($formId); } /** * Gets the campaigns a specific lead is part of. * * @param bool $forList * * @return mixed */ public function getLeadCampaigns(Lead $lead = null, $forList = false) { static $campaigns = []; if (null === $lead) { $lead = $this->contactTracker->getContact(); } if (!isset($campaigns[$lead->getId()])) { $repo = $this->getRepository(); $leadId = $lead->getId(); // get the campaigns the lead is currently part of $campaigns[$leadId] = $repo->getPublishedCampaigns( null, $lead->getId(), $forList, $this->security->isGranted($this->getPermissionBase().':viewother') ); } return $campaigns[$lead->getId()]; } /** * Gets a list of published campaigns. * * @return array */ public function getPublishedCampaigns(bool $forList = false) { static $campaigns = []; if (empty($campaigns)) { $campaigns = $this->getRepository()->getPublishedCampaigns( null, null, $forList, $this->security->isGranted($this->getPermissionBase().':viewother') ); } return $campaigns; } /** * Saves a campaign lead, logs the error if saving fails. * * @return bool */ public function saveCampaignLead(CampaignLead $campaignLead) { try { $this->getCampaignLeadRepository()->saveEntity($campaignLead); return true; } catch (\Exception $exception) { $this->logger->log('error', $exception->getMessage(), ['exception' => $exception]); return false; } } /** * Get details of leads in a campaign. * * @return mixed */ public function getLeadDetails($campaign, $leads = null) { $campaignId = ($campaign instanceof Campaign) ? $campaign->getId() : $campaign; if ($leads instanceof PersistentCollection) { $leads = array_keys($leads->toArray()); } return $this->em->getRepository(CampaignLead::class)->getLeadDetails($campaignId, $leads); } /** * Get leads for a campaign. If $event is passed in, only leads who have not triggered the event are returned. * * @param Campaign $campaign * @param array $event * * @return mixed */ public function getCampaignLeads($campaign, $event = null) { $campaignId = ($campaign instanceof Campaign) ? $campaign->getId() : $campaign; $eventId = (is_array($event) && isset($event['id'])) ? $event['id'] : $event; return $this->em->getRepository(CampaignLead::class)->getLeads($campaignId, $eventId); } public function getCampaignListIds($id): array { return $this->getRepository()->getCampaignListIds((int) $id); } /** * Get line chart data of leads added to campaigns. * * @param string $unit {@link php.net/manual/en/function.date.php#refsect1-function.date-parameters} * @param string $dateFormat * @param array $filter * @param bool $canViewOthers */ public function getLeadsAddedLineChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = [], $canViewOthers = true): array { $chart = new LineChart($unit, $dateFrom, $dateTo, $dateFormat); $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); $q = $query->prepareTimeDataQuery('campaign_leads', 'date_added', $filter); if (!$canViewOthers) { $q->join('t', MAUTIC_TABLE_PREFIX.'campaigns', 'c', 'c.id = c.campaign_id') ->andWhere('c.created_by = :userId') ->setParameter('userId', $this->userHelper->getUser()->getId()); } $data = $query->loadAndBuildTimeData($q); $chart->setDataset($this->translator->trans('mautic.campaign.campaign.leads'), $data); return $chart->render(); } /** * Get line chart data of hits. * * @param string|null $unit {@link php.net/manual/en/function.date.php#refsect1-function.date-parameters} * @param string $dateFormat * @param array $filter */ public function getCampaignMetricsLineChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = []): array { $events = []; $chart = new LineChart($unit, $dateFrom, $dateTo, $dateFormat); $query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); $contacts = $query->fetchTimeData('campaign_leads', 'date_added', $filter); $chart->setDataset($this->translator->trans('mautic.campaign.campaign.leads'), $contacts); if (isset($filter['campaign_id'])) { $rawEvents = $this->getEventRepository()->getCampaignEvents($filter['campaign_id']); // Group events by type foreach ($rawEvents as $event) { if (isset($events[$event['type']])) { $events[$event['type']][] = $event['id']; } else { $events[$event['type']] = [$event['id']]; } } if ($events) { foreach ($events as $type => $eventIds) { $filter['event_id'] = $eventIds; if ($this->coreParametersHelper->get('campaign_use_summary')) { $q = $query->prepareTimeDataQuery('campaign_summary', 'date_triggered', $filter, 'triggered_count + non_action_path_taken_count', 'sum'); $rawData = $q->executeQuery()->fetchAllAssociative(); } else { // Exclude failed events $failedSq = $this->em->getConnection()->createQueryBuilder(); $failedSq->select('null') ->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_failed_log', 'fe') ->where( $failedSq->expr()->eq('fe.log_id', 't.id') ); $filter['failed_events'] = [ 'subquery' => sprintf('NOT EXISTS (%s)', $failedSq->getSQL()), ]; $q = $query->prepareTimeDataQuery('campaign_lead_event_log', 'date_triggered', $filter); $rawData = $q->executeQuery()->fetchAllAssociative(); } if (!empty($rawData)) { $triggers = $query->completeTimeData($rawData); $chart->setDataset($this->translator->trans('mautic.campaign.'.$type), $triggers); } } unset($filter['event_id']); } } return $chart->render(); } /** * @param Campaign $entity * @param string $root * @param int $order */ protected function buildOrder($hierarchy, &$events, $entity, $root = 'null', $order = 1) { $count = count($hierarchy); if (1 === $count && 'null' === array_unique(array_values($hierarchy))[0]) { // no parents so leave order as is return; } else { foreach ($hierarchy as $eventId => $parent) { if ($parent == $root || 1 === $count) { $events[$eventId]->setOrder($order); unset($hierarchy[$eventId]); if (count($hierarchy)) { $this->buildOrder($hierarchy, $events, $entity, $eventId, $order + 1); } } } } } /** * @param int $limit * @param bool $maxLeads */ public function rebuildCampaignLeads(Campaign $campaign, $limit = 1000, $maxLeads = false, OutputInterface $output = null): int { $contactLimiter = new ContactLimiter($limit); return $this->membershipBuilder->build($campaign, $contactLimiter, $maxLeads, $output); } public function getCampaignIdsWithDependenciesOnSegment($segmentId): array { $entities = $this->getRepository()->getEntities( [ 'filter' => [ 'force' => [ [ 'column' => 'l.id', 'expr' => 'eq', 'value' => $segmentId, ], ], ], 'joinLists' => true, ] ); $ids = []; foreach ($entities as $entity) { $ids[] = $entity->getId(); } return $ids; } /** * @return array<int, int> */ public function getCampaignIdsWithDependenciesOnEmail(int $emailId): array { return $this->getRepository()->getCampaignIdsWithDependenciesOnEmail($emailId); } /** * @return array<string, array<int, array<string, int|string>>> * * @throws Exception */ public function getCountryStats(Campaign $entity, \DateTimeImmutable $dateFrom, \DateTimeImmutable $dateTo): array { /** @var StatRepository $statRepo */ $statRepo = $this->em->getRepository(Stat::class); $results['contacts'] = $this->getCampaignMembersGroupByCountry($entity, $dateFrom, $dateTo); if ($entity->isEmailCampaign()) { $eventsEmailsSend = $entity->getEmailSendEvents(); $eventsIds = $eventsEmailsSend->getKeys(); $emailIds = []; foreach ($eventsEmailsSend as $event) { $emailIds[] = $event->getChannelId(); } $emailStats = $statRepo->getStatsSummaryByCountry($dateFrom, $dateTo, $emailIds, 'campaign', $eventsIds); $results['read_count'] = $results['clicked_through_count'] = []; foreach ($emailStats as $e) { $results['read_count'][] = array_intersect_key($e, array_flip(['country', 'read_count'])); $results['clicked_through_count'][] = array_intersect_key($e, array_flip(['country', 'clicked_through_count'])); } } return $results; } /** * Get leads in a campaign grouped by country. * * @return array{}|array<int, array<string, string|null>> */ public function getCampaignMembersGroupByCountry(Campaign $campaign, \DateTimeImmutable $dateFromObject, \DateTimeImmutable $dateToObject): array { return $this->em->getRepository(CampaignLead::class)->getCampaignMembersGroupByCountry($campaign, $dateFromObject, $dateToObject); } }