*/ protected array $campaignElements = []; /** * @var array */ protected $addedSources = []; /** * @var array */ protected $campaignEvents = []; /** * @var array */ protected $campaignSources = []; /** * @var array */ protected $connections = []; /** * @var array */ protected $deletedEvents = []; /** * @var array */ protected $deletedSources = []; /** * @var array */ protected $listFilters = []; /** * @var array */ protected $modifiedEvents = []; protected $sessionId; public function __construct( FormFactoryInterface $formFactory, FormFieldHelper $fieldHelper, private EventCollector $eventCollector, private DateHelper $dateHelper, ManagerRegistry $managerRegistry, ModelFactory $modelFactory, UserHelper $userHelper, CoreParametersHelper $coreParametersHelper, EventDispatcherInterface $dispatcher, Translator $translator, FlashBag $flashBag, private LoggerInterface $logger, private RequestStack $requestStack, CorePermissions $security, private EntityManager $em, ) { parent::__construct($formFactory, $fieldHelper, $managerRegistry, $modelFactory, $userHelper, $coreParametersHelper, $dispatcher, $translator, $flashBag, $requestStack, $security); } protected function getPermissions(): array { // set some permissions return (array) $this->security->isGranted( [ 'campaign:campaigns:viewown', 'campaign:campaigns:viewother', 'campaign:campaigns:create', 'campaign:campaigns:editown', 'campaign:campaigns:editother', 'campaign:campaigns:cloneown', 'campaign:campaigns:cloneother', 'campaign:campaigns:deleteown', 'campaign:campaigns:deleteother', 'campaign:campaigns:publishown', 'campaign:campaigns:publishother', ], 'RETURN_ARRAY' ); } public function batchDeleteAction(Request $request): JsonResponse|RedirectResponse { return $this->batchDeleteStandard($request); } public function cloneAction(Request $request, $objectId): JsonResponse|RedirectResponse|Response { return $this->cloneStandard($request, $objectId); } /** * @param array $assetList */ private function handleExportDownload( ExportHelper $exportHelper, string $jsonOutput, array $assetList, string $exportFileName, ): JsonResponse|BinaryFileResponse { $filePath = $exportHelper->writeToZipFile($jsonOutput, $assetList, ''); if (!file_exists($filePath)) { $this->logger->error('Export file could not be created', ['filePath' => $filePath]); $this->addFlashMessage('mautic.campaign.error.export.file_not_found', ['%path%' => $filePath], FlashBag::LEVEL_ERROR); return new JsonResponse([ 'error' => $this->translator->trans('mautic.campaign.error.export.file_not_found', ['%path%' => $filePath], 'flashes'), 'flashes' => $this->getFlashContent(), ], 400); } return $exportHelper->downloadAsZip($filePath, $exportFileName); } public function exportAction(ExportHelper $exportHelper, CampaignModel $campaignModel, int $objectId): JsonResponse|BinaryFileResponse|Response { if (!$this->security->isGranted('campaign:export:enable', 'MATCH_ONE')) { $this->logger->error('Access denied for campaign export', ['user' => $this->user->getId()]); return $this->accessDenied(); } $campaign = $campaignModel->getEntity($objectId); if (empty($campaign)) { $this->logger->error('Campaign not found for export', ['objectId' => $objectId]); return $this->notFound(); } $date = (new \DateTimeImmutable())->format(DateTimeHelper::FORMAT_DB); $exportFileName = $this->translator->trans('mautic.campaign.campaign_export_file.name', ['%date%' => $date]); $event = new EntityExportEvent(Campaign::ENTITY_NAME, $objectId); $event = $this->dispatcher->dispatch($event); $data = $event->getEntities(); $jsonOutput = json_encode([$data], JSON_PRETTY_PRINT); $assetListEvent = new AssetExportListEvent([$data]); $assetListEvent = $this->dispatcher->dispatch($assetListEvent); $assetList = $assetListEvent->getList(); return $this->handleExportDownload($exportHelper, $jsonOutput, $assetList, $exportFileName); } public function batchExportAction(Request $request, ExportHelper $exportHelper): JsonResponse|BinaryFileResponse|Response { // set some permissions $permissions = $this->security->isGranted( [ 'campaign:campaigns:viewown', 'campaign:campaigns:viewother', 'campaign:campaigns:create', 'campaign:campaigns:editown', 'campaign:campaigns:editother', 'campaign:campaigns:deleteown', 'campaign:campaigns:deleteother', ], 'RETURN_ARRAY' ); if (!$permissions['campaign:campaigns:viewown'] && !$permissions['campaign:campaigns:viewother']) { return $this->accessDenied(); } elseif (!$this->security->isGranted('campaign:export:enable', 'MATCH_ONE')) { return $this->accessDenied(); } $session = $request->getSession(); $filter = $session->get('mautic.campaign.filter', ''); $orderByDir = $session->get('mautic.campaign.orderbydir', 'ASC'); $ids = $request->get('ids'); $date = (new \DateTimeImmutable())->format(DateTimeHelper::FORMAT_DB); $exportFileName = $this->translator->trans('mautic.campaign.campaign_export_file.name', ['%date%' => $date]); $objectIds = json_decode($ids, true); if (empty($ids)) { $repo = $this->em->getRepository(Campaign::class); $repo->setTranslator($this->translator); $args = [ 'filter' => $filter, 'orderBy' => 'c.id', 'orderByDir' => $orderByDir, 'ignore_paginator' => true, // to get full result, not paginated ]; // Query campaigns $campaigns = $repo->getEntities($args); // Get campaign IDs $objectIds = array_map(fn ($c) => $c->getId(), $campaigns); } $allData = []; if (empty($objectIds)) { $this->addFlashMessage('mautic.campaign.error.export.no_campaigns_selected', [], FlashBag::LEVEL_WARNING); return new JsonResponse([ 'error' => $this->translator->trans('mautic.campaign.error.export.no_campaigns_selected', [], 'flashes'), 'flashes' => $this->getFlashContent(), ], 400); } foreach ($objectIds as $objectId) { $event = new EntityExportEvent(Campaign::ENTITY_NAME, (int) $objectId); $event = $this->dispatcher->dispatch($event); $data = $event->getEntities(); if (!empty($data)) { $allData[] = $data; } } $assetListEvent = new AssetExportListEvent($allData); $assetListEvent = $this->dispatcher->dispatch($assetListEvent); $assetList = $assetListEvent->getList(); $jsonOutput = json_encode($allData, JSON_PRETTY_PRINT); return $this->handleExportDownload($exportHelper, $jsonOutput, $assetList, $exportFileName); } /** * @param string|int $objectId * @param int $page * @param int|null $count * * @return JsonResponse|RedirectResponse|Response */ public function contactsAction( Request $request, PageHelperFactoryInterface $pageHelperFactory, $objectId, $page = 1, $count = null, ?\DateTimeInterface $dateFrom = null, ?\DateTimeInterface $dateTo = null, ) { $session = $request->getSession(); $session->set('mautic.campaign.contact.page', $page); $permissions = [ 'campaign:campaigns:view', 'lead:leads:viewown', 'lead:leads:viewother', ]; return $this->generateContactsGrid( $request, $pageHelperFactory, $objectId, $page, $permissions, 'campaign', 'campaign_leads', null, 'campaign_id', ['manually_removed' => 0], null, null, [], null, 'entity.lead_id', 'DESC', $count, $dateFrom, $dateTo ); } public function EventStatsAction(int $objectId, string $dateFromValue, string $dateToValue): JsonResponse { $response = []; $events = $this->getCampaignModel()->getEventRepository()->getCampaignEvents($objectId); $dateFrom = null; $dateTo = null; $dateToPlusOne = null; if ($this->coreParametersHelper->get('campaign_by_range')) { $dateFrom = new \DateTimeImmutable($dateFromValue); $dateTo = new \DateTimeImmutable($dateToValue); $dateToPlusOne = $dateTo->modify('+1 day'); } $leadCount = $this->getCampaignModel()->getRepository()->getCampaignLeadCount($objectId); $logCounts = $this->processCampaignLogCounts($objectId, $dateFrom, $dateToPlusOne); $campaignLogCounts = $logCounts['campaignLogCounts'] ?? []; $campaignLogCountsProcessed = $logCounts['campaignLogCountsProcessed'] ?? []; $this->processCampaignEvents($events, $leadCount, $campaignLogCounts, $campaignLogCountsProcessed); $sortedEvents = $this->processCampaignEventsFromParentCondition($events); $sourcesList = $this->getCampaignModel()->getSourceLists(); $campaign = $this->getCampaignModel()->getEntity($objectId); $this->prepareCampaignSourcesForEdit($objectId, $sourcesList, true); $response['preview'] = trim( $this->renderView( '@MauticCampaign/Campaign/_preview.html.twig', [ 'campaignId' => $objectId, 'campaign' => $campaign, 'campaignEvents' => $events, 'campaignSources' => $this->campaignSources, 'eventSettings' => $this->eventCollector->getEventsArray(), 'canvasSettings' => $campaign->getCanvasSettings(), ] ) ); $response['decisions'] = trim($this->renderView('@MauticCampaign/Campaign/_events.html.twig', ['events' => $sortedEvents['decision']])); $response['actions'] = trim($this->renderView('@MauticCampaign/Campaign/_events.html.twig', ['events' => $sortedEvents['action']])); $response['conditions'] = trim($this->renderView('@MauticCampaign/Campaign/_events.html.twig', ['events' => $sortedEvents['condition']])); return new JsonResponse(array_filter($response)); } public function GraphAction(Request $request, int $objectId, string $dateFrom, string $dateTo): Response { $dateRangeValues = ['date_from' => $dateFrom, 'date_to' => $dateTo]; $action = $this->generateUrl('mautic_campaign_action', ['objectAction' => 'view', 'objectId' => $objectId]); $dateRangeForm = $this->formFactory->create(DateRangeType::class, $dateRangeValues, ['action' => $action]); $stats = $this->getCampaignModel()->getCampaignMetricsLineChartData( null, new \DateTime($dateRangeForm->get('date_from')->getData()), new \DateTime($dateRangeForm->get('date_to')->getData()), null, ['campaign_id' => $objectId] ); return $this->ajaxAction( $request, [ 'contentTemplate' => '@MauticCampaign/Campaign/graph.html.twig', 'viewParameters' => [ 'campiagnId' => $objectId, 'stats' => $stats, 'dateRangeForm' => $dateRangeForm->createView(), ], ] ); } public function deleteAction(Request $request, $objectId): JsonResponse|RedirectResponse { return $this->deleteStandard($request, $objectId); } /** * @param bool $ignorePost */ public function editAction(Request $request, $objectId, $ignorePost = false): JsonResponse|RedirectResponse|Response { return $this->editStandard($request, $objectId, $ignorePost); } /** * @param int $page * * @return JsonResponse|Response */ public function indexAction(Request $request, $page = null) { // set some permissions $permissions = $this->security->isGranted( [ 'campaign:campaigns:view', 'campaign:campaigns:viewown', 'campaign:campaigns:viewother', 'campaign:campaigns:create', 'campaign:campaigns:edit', 'campaign:campaigns:editown', 'campaign:campaigns:editother', 'campaign:campaigns:delete', 'campaign:campaigns:deleteown', 'campaign:campaigns:deleteother', 'campaign:campaigns:publish', 'campaign:campaigns:publishown', 'campaign:campaigns:publishother', 'campaign:imports:view', 'campaign:imports:create', ], 'RETURN_ARRAY', null, true ); if (!$permissions['campaign:campaigns:view']) { return $this->accessDenied(); } $this->setListFilters(); $session = $request->getSession(); if (empty($page)) { $page = $session->get('mautic.campaign.page', 1); } $limit = $session->get('mautic.campaign.limit', $this->coreParametersHelper->get('default_pagelimit')); $start = (1 === $page) ? 0 : (($page - 1) * $limit); if ($start < 0) { $start = 0; } $search = $request->get('search', $session->get('mautic.campaign.filter', '')); $session->set('mautic.campaign.filter', $search); $filter = ['string' => $search, 'force' => []]; $model = $this->getModel('campaign'); if (!$permissions[$this->getPermissionBase().':viewother']) { $filter['force'][] = ['column' => 'c.createdBy', 'expr' => 'eq', 'value' => $this->user->getId()]; } $orderBy = $session->get('mautic.campaign.orderby', 'c.dateModified'); $orderByDir = $session->get('mautic.campaign.orderbydir', $this->getDefaultOrderDirection()); [$count, $items] = $this->getIndexItems($start, $limit, $filter, $orderBy, $orderByDir); if ($count && $count < ($start + 1)) { // the number of entities are now less then the current page so redirect to the last page $lastPage = (1 === $count) ? 1 : (((ceil($count / $limit)) ?: 1) ?: 1); $session->set('mautic.campaign.page', $lastPage); $returnUrl = $this->generateUrl('mautic_campaign_index', ['page' => $lastPage]); return $this->postActionRedirect( $this->getPostActionRedirectArguments( [ 'returnUrl' => $returnUrl, 'viewParameters' => ['page' => $lastPage], 'contentTemplate' => 'Mautic\CampaignBundle\Controller\CampaignController::indexAction', 'passthroughVars' => [ 'mauticContent' => 'campaign', ], ], 'index' ) ); } // set what page currently on so that we can return here after form submission/cancellation $session->set('mautic.campaign.page', $page); $viewParameters = [ 'permissionBase' => $this->getPermissionBase(), 'mauticContent' => $this->getJsLoadMethodPrefix(), 'sessionVar' => $this->getSessionBase(), 'actionRoute' => $this->getActionRoute(), 'indexRoute' => $this->getIndexRoute(), 'tablePrefix' => $model->getRepository()->getTableAlias(), 'modelName' => $this->getModelName(), 'translationBase' => $this->getTranslationBase(), 'searchValue' => $search, 'items' => $items, 'totalItems' => $count, 'page' => $page, 'limit' => $limit, 'permissions' => $permissions, 'tmpl' => $request->get('tmpl', 'index'), 'enableExportPermission'=> $this->security->isAdmin() || $this->security->isGranted('campaign:export:enable', 'MATCH_ONE'), ]; return $this->delegateView( $this->getViewArguments( [ 'viewParameters' => $viewParameters, 'contentTemplate' => '@MauticCampaign/Campaign/list.html.twig', 'passthroughVars' => [ 'mauticContent' => $this->getJsLoadMethodPrefix(), 'route' => $this->generateUrl($this->getIndexRoute(), ['page' => $page]), ], ], 'index' ) ); } /** * Generates new form and processes post data. * * @return RedirectResponse|Response */ public function newAction(Request $request) { /** @var CampaignModel $model */ $model = $this->getModel('campaign'); $campaign = $model->getEntity(); if (!$this->security->isGranted('campaign:campaigns:create')) { return $this->accessDenied(); } // set the page we came from $page = $request->getSession()->get('mautic.campaign.page', 1); $options = $this->getEntityFormOptions(); $action = $this->generateUrl('mautic_campaign_action', ['objectAction' => 'new']); $form = $model->createForm($campaign, $this->formFactory, $action, $options); // /Check for a submitted form and process it $isPost = 'POST' === $request->getMethod(); $this->beforeFormProcessed($campaign, $form, 'new', $isPost); if ($isPost) { $valid = false; if (!$cancelled = $this->isFormCancelled($form)) { if ($valid = $this->isFormValid($form)) { if ($valid = $this->beforeEntitySave($campaign, $form, 'new')) { $campaign->setDateModified(new \DateTime()); $model->saveEntity($campaign); $this->afterEntitySave($campaign, $form, 'new', $valid); if (method_exists($this, 'viewAction')) { $viewParameters = ['objectId' => $campaign->getId(), 'objectAction' => 'view']; $returnUrl = $this->generateUrl('mautic_campaign_action', $viewParameters); $template = 'Mautic\CampaignBundle\Controller\CampaignController::viewAction'; } else { $viewParameters = ['page' => $page]; $returnUrl = $this->generateUrl('mautic_campaign_index', $viewParameters); $template = 'Mautic\CampaignBundle\Controller\CampaignController::indexAction'; } } } $this->afterFormProcessed($valid, $campaign, $form, 'new'); } else { $viewParameters = ['page' => $page]; $returnUrl = $this->generateUrl($this->getIndexRoute(), $viewParameters); $template = 'Mautic\CampaignBundle\Controller\CampaignController::indexAction'; } $passthrough = [ 'mauticContent' => 'cammpaign', ]; if ($isInPopup = isset($form['updateSelect'])) { $template = false; $passthrough = array_merge( $passthrough, $this->getUpdateSelectParams($form['updateSelect']->getData(), $campaign) ); } if ($cancelled || ($valid && !$this->isFormApplied($form))) { if ($isInPopup) { $passthrough['closeModal'] = true; } return $this->postActionRedirect( $this->getPostActionRedirectArguments( [ 'returnUrl' => $returnUrl, 'viewParameters' => $viewParameters, 'contentTemplate' => $template, 'passthroughVars' => $passthrough, 'entity' => $campaign, ], 'new' ) ); } elseif ($valid && $this->isFormApplied($form)) { return $this->editAction($request, $campaign->getId(), true); } } $delegateArgs = [ 'viewParameters' => [ 'permissionBase' => $model->getPermissionBase(), 'mauticContent' => 'campaign', 'actionRoute' => 'mautic_campaign_action', 'indexRoute' => 'mautic_campaign_index', 'tablePrefix' => 'c', 'modelName' => 'campaign', 'translationBase' => $this->getTranslationBase(), 'tmpl' => $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index', 'entity' => $campaign, 'form' => $this->getFormView($form, 'new'), ], 'contentTemplate' => '@MauticCampaign/Campaign/form.html.twig', 'passthroughVars' => [ 'mauticContent' => 'campaign', 'route' => $this->generateUrl( 'mautic_campaign_action', [ 'objectAction' => (!empty($valid) ? 'edit' : 'new'), // valid means a new form was applied 'objectId' => ($campaign) ? $campaign->getId() : 0, ] ), 'validationError' => $this->getFormErrorForBuilder($form), ], 'entity' => $campaign, 'form' => $form, ]; return $this->delegateView( $this->getViewArguments($delegateArgs, 'new') ); } public function viewAction(Request $request, $objectId): JsonResponse|Response { return $this->viewStandard($request, $objectId, $this->getModelName(), null, null, 'campaign'); } /** * @param Campaign $campaign * @param Campaign $oldCampaign */ protected function afterEntityClone($campaign, $oldCampaign) { $tempId = 'mautic_'.sha1(uniqid(mt_rand(), true)); $objectId = $oldCampaign->getId(); // Get the events that need to be duplicated as well $events = $oldCampaign->getEvents()->toArray(); $campaign->setIsPublished(false); // Clone the campaign's events /** @var Event $event */ foreach ($events as $event) { $tempEventId = 'new'.$event->getId(); $clone = clone $event; $clone->nullId(); $clone->setCampaign($campaign); $clone->setTempId($tempEventId); // Just wipe out the parent as it'll be generated when the cloned entity is saved $clone->setParent(null); if (CampaignActionJumpToEventSubscriber::EVENT_NAME === $clone->getType()) { // Update properties to point to the new temp ID $properties = $clone->getProperties(); $properties['jumpToEvent'] = 'new'.$properties['jumpToEvent']; $clone->setProperties($properties); } $campaign->addEvent($tempEventId, $clone); } // Update canvas settings with new event ids $canvasSettings = $campaign->getCanvasSettings(); if (isset($canvasSettings['nodes'])) { foreach ($canvasSettings['nodes'] as &$node) { // Only events and not lead sources if (is_numeric($node['id'])) { $node['id'] = 'new'.$node['id']; } } } if (isset($canvasSettings['connections'])) { foreach ($canvasSettings['connections'] as &$c) { // Only events and not lead sources if (is_numeric($c['sourceId'])) { $c['sourceId'] = 'new'.$c['sourceId']; } // Only events and not lead sources if (is_numeric($c['targetId'])) { $c['targetId'] = 'new'.$c['targetId']; } } } // Simulate edit $campaign->setCanvasSettings($canvasSettings); $tempId = $this->getCampaignSessionId($campaign, 'clone', $tempId); $campaignSources = $this->getCampaignModel()->getLeadSources($objectId); $this->prepareCampaignSourcesForEdit($tempId, $campaignSources); } /** * @param object $entity * @param string $action * @param bool|null $persistConnections */ protected function afterEntitySave($entity, FormInterface $form, $action, $persistConnections = null) { if ($persistConnections) { // Update canvas settings with new event IDs then save $this->connections = $this->getCampaignModel()->setCanvasSettings($entity, $this->connections); } else { // Just update and add to entity $this->connections = $this->getCampaignModel()->setCanvasSettings($entity, $this->connections, false, $this->modifiedEvents); } } /** * @param bool $isClone */ protected function afterFormProcessed($isValid, $entity, FormInterface $form, $action, $isClone = false) { if (!$isValid) { // Add the canvas settings to the entity to be able to rebuild it $this->afterEntitySave($entity, $form, $action, false); } else { $this->sessionId = $entity->getId(); } } /** * This method is called before and after form is submitted. * * @param bool $isClone */ protected function beforeFormProcessed($entity, FormInterface $form, $action, $isPost, $objectId = null, $isClone = false) { $sessionId = $this->getCampaignSessionId($entity, $action, $objectId); if ($isPost) { // fetch data from form - use all() to get array data $requestData = $this->requestStack->getCurrentRequest()->request->all(); $campaign = $requestData['campaign'] ?? []; $campaignElements = $campaign['campaignElements'] ?? []; // First load existing events to ensure we have complete data if (!$isClone && $entity->getId()) { $this->prepareCampaignEventsForEdit($entity, $sessionId, $isClone); } // set global elements (this may override some events with form data) $this->setCampaignElements($campaignElements, $isClone); $this->getCampaignModel()->setCanvasSettings($entity, $this->connections, false, $this->modifiedEvents); $this->prepareCampaignSourcesForEdit($sessionId, $this->campaignSources, true); } else { if (!$isClone) { // clear out existing fields in case the form was refreshed, browser closed, etc $this->modifiedEvents = $this->campaignSources = []; if ($entity->getId()) { $campaignSources = $this->getCampaignModel()->getLeadSources($entity->getId()); $this->prepareCampaignSourcesForEdit($sessionId, $campaignSources); } else { $this->campaignElements['modifiedSources'] = []; $this->campaignElements['campaignSources'] = []; } } $this->deletedEvents = []; $this->prepareCampaignEventsForEdit($entity, $sessionId, $isClone); $form->get('sessionId')->setData($sessionId); $this->campaignElements['canvasSettings'] = $entity->getCanvasSettings(); $form->get('campaignElements')->setData(json_encode($this->campaignElements)); } } /** * Method to take JSON string or array of campaignElements and set all global variables. * * @param string|array $campaignElements */ private function setCampaignElements(string|array $campaignElements, bool $isClone = false): void { // sets the global campaignElements if (is_string($campaignElements)) { $this->campaignElements = json_decode($campaignElements, true); } else { $this->campaignElements = $campaignElements; } // set added/updated events - comes from global campaignElements which was set above $this->setCampaignEvents(); // set added/updated sources - comes from global campaignElements which was set above $this->setCampaignSources($isClone); $this->connections = $this->campaignElements['canvasSettings'] ?? []; } /** * @param Campaign $entity * @param bool $isClone */ protected function beforeEntitySave($entity, FormInterface $form, $action, $objectId = null, $isClone = false): bool { if (empty($this->campaignEvents)) { // set the error $form->addError( new FormError( $this->translator->trans('mautic.campaign.form.events.notempty', [], 'validators') ) ); return false; } if (empty($this->campaignSources['lists']) && empty($this->campaignSources['forms'])) { // set the error $form->addError( new FormError( $this->translator->trans('mautic.campaign.form.sources.notempty', [], 'validators') ) ); return false; } if ($isClone) { $this->setCampaignSources($isClone); $this->getCampaignModel()->setLeadSources($entity, $this->campaignElements['campaignSources'], []); // If this is a clone, we need to save the entity first to properly build the events, sources and canvas settings $this->getCampaignModel()->getRepository()->saveEntity($entity); // Set as new so that timestamps are still hydrated $entity->setNew(); $this->sessionId = $entity->getId(); } // Set lead sources $this->getCampaignModel()->setLeadSources($entity, $this->addedSources, $this->deletedSources); // Build and set Event entities $this->getCampaignModel()->setEvents($entity, $this->campaignEvents, $this->connections, $this->deletedEvents); if ('edit' === $action && null !== $this->connections) { if (!empty($this->deletedEvents)) { /** @var EventModel $eventModel */ $eventModel = $this->getModel('campaign.event'); $eventModel->deleteEvents($entity->getEvents()->toArray(), $this->deletedEvents); } } return true; } /** * @return CampaignModel */ protected function getCampaignModel() { /** @var CampaignModel $model */ $model = $this->getModel($this->getModelName()); return $model; } /** * @return int|string|null */ protected function getCampaignSessionId(Campaign $campaign, $action, $objectId = null) { if (isset($this->sessionId)) { return $this->sessionId; } if ($objectId) { $sessionId = $objectId; } elseif ('new' === $action && empty($sessionId)) { $sessionId = 'mautic_'.sha1(uniqid(mt_rand(), true)); if ($this->requestStack->getCurrentRequest()->request->has('campaign')) { $campaign = $this->requestStack->getCurrentRequest()->request->all()['campaign'] ?? []; $sessionId = $campaign['sessionId'] ?? $sessionId; } } elseif ('edit' === $action) { $sessionId = $campaign->getId(); } $this->sessionId = $sessionId; return $sessionId; } protected function getTemplateBase(): string { return '@MauticCampaign/Campaign'; } protected function getIndexItems($start, $limit, $filter, $orderBy, $orderByDir, array $args = []) { $session = $this->getCurrentRequest()->getSession(); $currentFilters = $session->get('mautic.campaign.list_filters', []); $updatedFilters = $this->requestStack->getCurrentRequest()->get('filters', false); $sourceLists = $this->getCampaignModel()->getSourceLists(); $listFilters = [ 'filters' => [ 'placeholder' => $this->translator->trans('mautic.campaign.filter.placeholder'), 'multiple' => true, 'groups' => [ 'mautic.campaign.leadsource.form' => [ 'options' => $sourceLists['forms'], 'prefix' => 'form', ], 'mautic.campaign.leadsource.list' => [ 'options' => $sourceLists['lists'], 'prefix' => 'list', ], ], ], ]; if ($updatedFilters) { // Filters have been updated // Parse the selected values $newFilters = []; $updatedFilters = json_decode($updatedFilters, true); if ($updatedFilters) { foreach ($updatedFilters as $updatedFilter) { [$clmn, $fltr] = explode(':', $updatedFilter); $newFilters[$clmn][] = $fltr; } $currentFilters = $newFilters; } else { $currentFilters = []; } } $session->set('mautic.campaign.list_filters', $currentFilters); $joinLists = $joinForms = false; if (!empty($currentFilters)) { $listIds = $catIds = []; foreach ($currentFilters as $type => $typeFilters) { $listFilters['filters']['groups']['mautic.campaign.leadsource.'.$type]['values'] = $typeFilters; foreach ($typeFilters as $fltr) { if ('list' == $type) { $listIds[] = (int) $fltr; } else { $formIds[] = (int) $fltr; } } } if (!empty($listIds)) { $joinLists = true; $filter['force'][] = ['column' => 'l.id', 'expr' => 'in', 'value' => $listIds]; } if (!empty($formIds)) { $joinForms = true; $filter['force'][] = ['column' => 'f.id', 'expr' => 'in', 'value' => $formIds]; } } // Store for customizeViewArguments $this->listFilters = $listFilters; return parent::getIndexItems( $start, $limit, $filter, $orderBy, $orderByDir, [ 'joinLists' => $joinLists, 'joinForms' => $joinForms, ] ); } protected function getModelName(): string { return 'campaign'; } /** * Set events from form data. */ private function setCampaignEvents(): void { $this->modifiedEvents = (array) ($this->campaignElements['modifiedEvents'] ?? []); $this->deletedEvents = (array) ($this->campaignElements['deletedEvents'] ?? []); $this->campaignEvents = array_diff_key($this->modifiedEvents, array_flip($this->deletedEvents)); } /** * Set sources from form data. */ private function setCampaignSources(bool $isClone = false): void { $campaignSources = (array) ($this->campaignElements['campaignSources'] ?? []); $modifiedSources = (array) ($this->campaignElements['modifiedSources'] ?? []); if ($campaignSources === $modifiedSources) { if ($isClone) { // Clone hasn't saved the sources yet so return the current list as added $this->addedSources = $this->campaignSources = $campaignSources; } else { $this->campaignSources = $campaignSources; } } else { // Deleted sources foreach ($campaignSources as $type => $sources) { if (isset($modifiedSources[$type])) { $this->deletedSources[$type] = array_diff_key($sources, $modifiedSources[$type]); } else { $this->deletedSources[$type] = $sources; } } // Added sources foreach ($modifiedSources as $type => $sources) { if (isset($campaignSources[$type])) { $this->addedSources[$type] = array_diff_key($sources, $campaignSources[$type]); } else { $this->addedSources[$type] = $sources; } } $this->campaignSources = $modifiedSources; } } /** * @param string $action * * @throws CacheException */ protected function getViewArguments(array $args, $action): array { switch ($action) { case 'index': $args['viewParameters']['filters'] = $this->listFilters; break; case 'view': /** @var Campaign $entity */ $entity = $args['entity']; $objectId = $args['objectId']; // Init the date range filter form $dateRangeValues = $this->requestStack->getCurrentRequest()->get('daterange', []); $action = $this->generateUrl('mautic_campaign_action', ['objectAction' => 'view', 'objectId' => $objectId]); $dateRangeForm = $this->formFactory->create(DateRangeType::class, $dateRangeValues, ['action' => $action]); $isEmailStatsEnabled = (bool) $this->coreParametersHelper->get('campaign_email_stats_enabled', true); $showEmailStats = $isEmailStatsEnabled && $entity->isEmailCampaign(); $args['viewParameters'] = array_merge( $args['viewParameters'], [ 'campaign' => $entity, 'sources' => $this->getCampaignModel()->getLeadSources($entity), 'showEmailStats' => $showEmailStats, 'dateRangeForm' => $dateRangeForm->createView(), 'campaignElements' => $this->campaignElements, ] ); break; case 'new': case 'edit': $session = $this->getCurrentRequest()->getSession(); $args['viewParameters'] = array_merge( $args['viewParameters'], [ 'eventSettings' => $this->eventCollector->getEventsArray(), 'campaignEvents' => $this->campaignEvents, 'campaignSources' => $this->campaignSources, 'deletedEvents' => $this->deletedEvents, 'hasEventClone' => $session->has('mautic.campaign.events.clone.storage'), 'campaignElements' => $this->campaignElements, ] ); break; } return $args; } /** * @param bool $isClone * * @return array */ protected function prepareCampaignEventsForEdit($entity, $objectId, $isClone = false) { // load existing events into session $campaignEvents = []; $existingEvents = $entity->getEvents()->toArray(); $translator = $this->translator; foreach ($existingEvents as $e) { // remove deleted events from existing events if (!empty($e->getDeleted())) { continue; } $event = $e->convertToArray(); if ($isClone) { $id = $e->getTempId(); $event['id'] = $id; } else { $id = $e->getId(); } unset($event['campaign']); unset($event['children']); unset($event['parent']); unset($event['log']); $label = false; switch ($event['triggerMode']) { case 'interval': $label = $translator->trans( 'mautic.campaign.connection.trigger.interval.label'.('no' == $event['decisionPath'] ? '_inaction' : ''), [ '%number%' => $event['triggerInterval'], '%unit%' => $translator->trans( 'mautic.campaign.event.intervalunit.'.$event['triggerIntervalUnit'], ['%count%' => $event['triggerInterval']] ), ] ); break; case 'date': $label = $translator->trans( 'mautic.campaign.connection.trigger.date.label'.('no' == $event['decisionPath'] ? '_inaction' : ''), [ '%full%' => $this->dateHelper->toFull($event['triggerDate']), '%time%' => $this->dateHelper->toTime($event['triggerDate']), '%date%' => $this->dateHelper->toShort($event['triggerDate']), ] ); break; } if ($label) { $event['label'] = $label; } $campaignEvents[$id] = $event; } $this->modifiedEvents = $this->campaignEvents = $campaignEvents; $this->campaignElements['modifiedEvents'] = $campaignEvents; $this->campaignElements['campaignEvents'] = $campaignEvents; } protected function prepareCampaignSourcesForEdit($objectId, $campaignSources, $isPost = false) { $this->campaignSources = []; if (is_array($campaignSources)) { foreach ($campaignSources as $type => $sources) { if (!empty($sources)) { $campaignModel = $this->getModel('campaign'); \assert($campaignModel instanceof CampaignModel); $sourceList = $campaignModel->getSourceLists($type); $this->campaignSources[$type] = [ 'sourceType' => $type, 'campaignId' => $objectId, 'names' => implode(', ', array_intersect_key($sourceList, $sources)), ]; } } } if (!$isPost) { $this->campaignElements['campaignSources'] = $campaignSources; $this->campaignElements['modifiedSources'] = $campaignSources; } } /** * @return array>> * * @throws CacheException */ private function processCampaignLogCounts(int $id, ?\DateTimeImmutable $dateFrom, ?\DateTimeImmutable $dateToPlusOne): array { if ($this->coreParametersHelper->get('campaign_use_summary')) { /** @var SummaryRepository $summaryRepo */ $summaryRepo = $this->doctrine->getManager()->getRepository(Summary::class); $campaignLogCounts = $summaryRepo->getCampaignLogCounts($id, $dateFrom, $dateToPlusOne); $campaignLogCountsProcessed = $this->getCampaignLogCountsProcessed($campaignLogCounts); } else { /** @var LeadEventLogRepository $eventLogRepo */ $eventLogRepo = $this->doctrine->getManager()->getRepository(LeadEventLog::class); $campaignLogCounts = $eventLogRepo->getCampaignLogCounts($id, false, false, false, $dateFrom, $dateToPlusOne); $campaignLogCountsProcessed = $eventLogRepo->getCampaignLogCounts($id, true, false, false, $dateFrom, $dateToPlusOne); } return [ 'campaignLogCounts' => $campaignLogCounts, 'campaignLogCountsProcessed' => $campaignLogCountsProcessed, ]; } /** * @param array> $events * @param array> $campaignLogCounts * @param array> $campaignLogCountsProcessed */ private function processCampaignEvents( array &$events, int $leadCount, array $campaignLogCounts, array $campaignLogCountsProcessed, ): void { foreach ($events as &$event) { $event['logCountForPending'] = $event['logCountProcessed'] = $event['percent'] = $event['yesPercent'] = $event['noPercent'] = 0; if (isset($campaignLogCounts[$event['id']])) { $loggedCount = array_sum($campaignLogCounts[$event['id']]); $logCountsProcessed = isset($campaignLogCountsProcessed[$event['id']]) ? array_sum($campaignLogCountsProcessed[$event['id']]) : 0; $pending = $loggedCount - $logCountsProcessed; $event['logCountForPending'] = $pending; $event['logCountProcessed'] = $logCountsProcessed; [$totalNo, $totalYes] = $campaignLogCounts[$event['id']]; $total = $totalYes + $totalNo; if ($leadCount) { $event['percent'] = min(100, max(0, round(($loggedCount / $total) * 100, 1))); $event['yesPercent'] = min(100, max(0, round(($totalYes / $total) * 100, 1))); $event['noPercent'] = min(100, max(0, round(($totalNo / $total) * 100, 1))); } } } } /** * @param array> $events * * @return array>> */ private function processCampaignEventsFromParentCondition(array &$events): array { $sortedEvents = [ 'decision' => [], 'action' => [], 'condition' => [], ]; // rewrite stats data from parent condition if exist foreach ($events as &$event) { if (!empty($event['decisionPath']) && !empty($event['parent_id']) && isset($events[$event['parent_id']]) && 'condition' !== $event['eventType']) { $parentEvent = $events[$event['parent_id']]; $event['percent'] = $parentEvent['percent']; $event['yesPercent'] = $parentEvent['yesPercent']; $event['noPercent'] = $parentEvent['noPercent']; if ('yes' === $event['decisionPath']) { $event['noPercent'] = 0; } else { $event['yesPercent'] = 0; } } $sortedEvents[$event['eventType']][] = $event; } return $sortedEvents; } /** * @param array> $campaignLogCounts * * @return array> */ private function getCampaignLogCountsProcessed(array &$campaignLogCounts): array { $campaignLogCountsProcessed = []; foreach ($campaignLogCounts as $eventId => $campaignLogCount) { $campaignLogCountsProcessed[$eventId][] = $campaignLogCount[2]; unset($campaignLogCounts[$eventId][2]); } return $campaignLogCountsProcessed; } protected function getDefaultOrderDirection(): string { return 'DESC'; } }