Spaces:
No application file
No application file
namespace Mautic\CampaignBundle\Controller; | |
use Doctrine\DBAL\Cache\CacheException; | |
use Doctrine\Persistence\ManagerRegistry; | |
use Mautic\CampaignBundle\Entity\Campaign; | |
use Mautic\CampaignBundle\Entity\Event; | |
use Mautic\CampaignBundle\Entity\LeadEventLog; | |
use Mautic\CampaignBundle\Entity\LeadEventLogRepository; | |
use Mautic\CampaignBundle\Entity\Summary; | |
use Mautic\CampaignBundle\Entity\SummaryRepository; | |
use Mautic\CampaignBundle\EventCollector\EventCollector; | |
use Mautic\CampaignBundle\EventListener\CampaignActionJumpToEventSubscriber; | |
use Mautic\CampaignBundle\Model\CampaignModel; | |
use Mautic\CampaignBundle\Model\EventModel; | |
use Mautic\CoreBundle\Controller\AbstractStandardFormController; | |
use Mautic\CoreBundle\Factory\MauticFactory; | |
use Mautic\CoreBundle\Factory\ModelFactory; | |
use Mautic\CoreBundle\Factory\PageHelperFactoryInterface; | |
use Mautic\CoreBundle\Form\Type\DateRangeType; | |
use Mautic\CoreBundle\Helper\CoreParametersHelper; | |
use Mautic\CoreBundle\Helper\UserHelper; | |
use Mautic\CoreBundle\Security\Permissions\CorePermissions; | |
use Mautic\CoreBundle\Service\FlashBag; | |
use Mautic\CoreBundle\Translation\Translator; | |
use Mautic\CoreBundle\Twig\Helper\DateHelper; | |
use Mautic\FormBundle\Helper\FormFieldHelper; | |
use Mautic\LeadBundle\Controller\EntityContactsTrait; | |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
use Symfony\Component\Form\Form; | |
use Symfony\Component\Form\FormError; | |
use Symfony\Component\Form\FormFactoryInterface; | |
use Symfony\Component\Form\FormInterface; | |
use Symfony\Component\HttpFoundation\JsonResponse; | |
use Symfony\Component\HttpFoundation\RedirectResponse; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
use Symfony\Component\HttpFoundation\Response; | |
use Symfony\Component\HttpFoundation\Session\Session; | |
class CampaignController extends AbstractStandardFormController | |
{ | |
use EntityContactsTrait; | |
/** | |
* @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, | |
MauticFactory $factory, | |
ModelFactory $modelFactory, | |
UserHelper $userHelper, | |
CoreParametersHelper $coreParametersHelper, | |
EventDispatcherInterface $dispatcher, | |
Translator $translator, | |
FlashBag $flashBag, | |
private RequestStack $requestStack, | |
CorePermissions $security | |
) { | |
parent::__construct($formFactory, $fieldHelper, $managerRegistry, $factory, $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' | |
); | |
} | |
/** | |
* Deletes a group of entities. | |
* | |
* @return JsonResponse|RedirectResponse | |
*/ | |
public function batchDeleteAction(Request $request) | |
{ | |
return $this->batchDeleteStandard($request); | |
} | |
/** | |
* Clone an entity. | |
* | |
* @return JsonResponse|RedirectResponse|Response | |
*/ | |
public function cloneAction(Request $request, $objectId) | |
{ | |
return $this->cloneStandard($request, $objectId); | |
} | |
/** | |
* @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); | |
return $this->generateContactsGrid( | |
$request, | |
$pageHelperFactory, | |
$objectId, | |
$page, | |
'campaign:campaigns:view', | |
'campaign', | |
'campaign_leads', | |
null, | |
'campaign_id', | |
['manually_removed' => 0], | |
null, | |
null, | |
[], | |
null, | |
'entity.lead_id', | |
'DESC', | |
$count, | |
$dateFrom, | |
$dateTo | |
); | |
} | |
/** | |
* Deletes the entity. | |
* | |
* @return JsonResponse|RedirectResponse | |
*/ | |
public function deleteAction(Request $request, $objectId) | |
{ | |
return $this->deleteStandard($request, $objectId); | |
} | |
/** | |
* @param bool $ignorePost | |
* | |
* @return JsonResponse|RedirectResponse|Response | |
*/ | |
public function editAction(Request $request, $objectId, $ignorePost = false) | |
{ | |
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', | |
], | |
'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'), | |
]; | |
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') | |
); | |
} | |
/** | |
* View a specific campaign. | |
* | |
* @return JsonResponse|Response | |
*/ | |
public function viewAction(Request $request, $objectId) | |
{ | |
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); | |
$this->setSessionCanvasSettings($tempId, $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->clearSessionComponents($this->sessionId); | |
$this->sessionId = $entity->getId(); | |
} | |
} | |
/** | |
* @param bool $isClone | |
*/ | |
protected function beforeFormProcessed($entity, FormInterface $form, $action, $isPost, $objectId = null, $isClone = false) | |
{ | |
$sessionId = $this->getCampaignSessionId($entity, $action, $objectId); | |
// set added/updated events | |
[$this->modifiedEvents, $this->deletedEvents, $this->campaignEvents] = $this->getSessionEvents($sessionId); | |
// set added/updated sources | |
[$this->addedSources, $this->deletedSources, $campaignSources] = $this->getSessionSources($sessionId, $isClone); | |
$this->connections = $this->getSessionCanvasSettings($sessionId); | |
if ($isPost) { | |
$this->getCampaignModel()->setCanvasSettings($entity, $this->connections, false, $this->modifiedEvents); | |
$this->prepareCampaignSourcesForEdit($sessionId, $campaignSources, true); | |
} else { | |
if (!$isClone) { | |
// clear out existing fields in case the form was refreshed, browser closed, etc | |
$this->clearSessionComponents($sessionId); | |
$this->modifiedEvents = $this->campaignSources = []; | |
if ($entity->getId()) { | |
$campaignSources = $this->getCampaignModel()->getLeadSources($entity->getId()); | |
$this->prepareCampaignSourcesForEdit($sessionId, $campaignSources); | |
$this->setSessionCanvasSettings($sessionId, $entity->getCanvasSettings()); | |
} | |
} | |
$this->deletedEvents = []; | |
$form->get('sessionId')->setData($sessionId); | |
$this->prepareCampaignEventsForEdit($entity, $sessionId, $isClone); | |
} | |
} | |
/** | |
* @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->addedSources, $this->deletedSources, $campaignSources] = $this->getSessionSources($objectId, $isClone); | |
$this->getCampaignModel()->setLeadSources($entity, $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; | |
} | |
/** | |
* Clear field and events from the session. | |
*/ | |
protected function clearSessionComponents($id) | |
{ | |
$session = $this->getCurrentRequest()->getSession(); | |
$session->remove('mautic.campaign.'.$id.'.events.modified'); | |
$session->remove('mautic.campaign.'.$id.'.events.deleted'); | |
$session->remove('mautic.campaign.'.$id.'.events.canvassettings'); | |
$session->remove('mautic.campaign.'.$id.'.leadsources.current'); | |
$session->remove('mautic.campaign.'.$id.'.leadsources.modified'); | |
$session->remove('mautic.campaign.'.$id.'.leadsources.deleted'); | |
} | |
/** | |
* @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->get('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'; | |
} | |
/** | |
* @return mixed[] | |
*/ | |
protected function getPostActionRedirectArguments(array $args, $action): array | |
{ | |
switch ($action) { | |
case 'new': | |
case 'edit': | |
if (!empty($args['entity'])) { | |
$sessionId = $this->getCampaignSessionId($args['entity'], $action); | |
$this->clearSessionComponents($sessionId); | |
} | |
break; | |
} | |
return $args; | |
} | |
/** | |
* Get events from session. | |
*/ | |
protected function getSessionEvents($id): array | |
{ | |
$session = $this->getCurrentRequest()->getSession(); | |
$modifiedEvents = $session->get('mautic.campaign.'.$id.'.events.modified', []); | |
$deletedEvents = $session->get('mautic.campaign.'.$id.'.events.deleted', []); | |
$events = array_diff_key($modifiedEvents, array_flip($deletedEvents)); | |
return [$modifiedEvents, $deletedEvents, $events]; | |
} | |
/** | |
* Get events from session. | |
*/ | |
protected function getSessionSources($id, $isClone = false): array | |
{ | |
$session = $this->getCurrentRequest()->getSession(); | |
$campaignSources = $session->get('mautic.campaign.'.$id.'.leadsources.current', []); | |
$modifiedSources = $session->get('mautic.campaign.'.$id.'.leadsources.modified', []); | |
if ($campaignSources === $modifiedSources) { | |
if ($isClone) { | |
// Clone hasn't saved the sources yet so return the current list as added | |
return [$campaignSources, [], $campaignSources]; | |
} else { | |
return [[], [], $campaignSources]; | |
} | |
} | |
// Deleted sources | |
$deletedSources = []; | |
foreach ($campaignSources as $type => $sources) { | |
if (isset($modifiedSources[$type])) { | |
$deletedSources[$type] = array_diff_key($sources, $modifiedSources[$type]); | |
} else { | |
$deletedSources[$type] = $sources; | |
} | |
} | |
// Added sources | |
$addedSources = []; | |
foreach ($modifiedSources as $type => $sources) { | |
if (isset($campaignSources[$type])) { | |
$addedSources[$type] = array_diff_key($sources, $campaignSources[$type]); | |
} else { | |
$addedSources[$type] = $sources; | |
} | |
} | |
return [$addedSources, $deletedSources, $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]); | |
$events = $this->getCampaignModel()->getEventRepository()->getCampaignEvents($entity->getId()); | |
$dateFrom = null; | |
$dateTo = null; | |
$dateToPlusOne = null; | |
if ($this->coreParametersHelper->get('campaign_by_range')) { | |
$dateFrom = new \DateTimeImmutable($dateRangeForm->get('date_from')->getData()); | |
$dateTo = new \DateTimeImmutable($dateRangeForm->get('date_to')->getData()); | |
$dateToPlusOne = $dateTo->modify('+1 day'); | |
} | |
$leadCount = $this->getCampaignModel()->getRepository()->getCampaignLeadCount($entity->getId()); | |
$logCounts = $this->processCampaignLogCounts($entity->getId(), $dateFrom, $dateToPlusOne); | |
$campaignLogCounts = $logCounts['campaignLogCounts'] ?? []; | |
$campaignLogCountsProcessed = $logCounts['campaignLogCountsProcessed'] ?? []; | |
$this->processCampaignEvents($events, $leadCount, $campaignLogCounts, $campaignLogCountsProcessed); | |
$sortedEvents = $this->processCampaignEventsFromParentCondition($events); | |
$stats = $this->getCampaignModel()->getCampaignMetricsLineChartData( | |
null, | |
new \DateTime($dateRangeForm->get('date_from')->getData()), | |
new \DateTime($dateRangeForm->get('date_to')->getData()), | |
null, | |
['campaign_id' => $objectId] | |
); | |
$sourcesList = $this->getCampaignModel()->getSourceLists(); | |
$this->prepareCampaignSourcesForEdit($objectId, $sourcesList, true); | |
$this->prepareCampaignEventsForEdit($entity, $objectId, true); | |
$args['viewParameters'] = array_merge( | |
$args['viewParameters'], | |
[ | |
'campaign' => $entity, | |
'stats' => $stats, | |
'events' => $sortedEvents, | |
'eventSettings' => $this->eventCollector->getEventsArray(), | |
'sources' => $this->getCampaignModel()->getLeadSources($entity), | |
'dateRangeForm' => $dateRangeForm->createView(), | |
'campaignSources' => $this->campaignSources, | |
'campaignEvents' => $events, | |
] | |
); | |
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'), | |
] | |
); | |
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) { | |
$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->getCurrentRequest()->getSession()->set('mautic.campaign.'.$objectId.'.events.modified', $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) { | |
$session = $this->getCurrentRequest()->getSession(); | |
$session->set('mautic.campaign.'.$objectId.'.leadsources.current', $campaignSources); | |
$session->set('mautic.campaign.'.$objectId.'.leadsources.modified', $campaignSources); | |
} | |
} | |
protected function setSessionCanvasSettings($sessionId, $canvasSettings) | |
{ | |
$this->getCurrentRequest()->getSession()->set('mautic.campaign.'.$sessionId.'.events.canvassettings', $canvasSettings); | |
} | |
/** | |
* @return mixed | |
*/ | |
protected function getSessionCanvasSettings($sessionId) | |
{ | |
return $this->getCurrentRequest()->getSession()->get('mautic.campaign.'.$sessionId.'.events.canvassettings'); | |
} | |
/** | |
* @return array<string, array<int|string, array<int|string, int|string>>> | |
* | |
* @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, true, $dateFrom, $dateToPlusOne); | |
$campaignLogCountsProcessed = $eventLogRepo->getCampaignLogCounts($id, false, false, false, $dateFrom, $dateToPlusOne); | |
} | |
return [ | |
'campaignLogCounts' => $campaignLogCounts, | |
'campaignLogCountsProcessed' => $campaignLogCountsProcessed, | |
]; | |
} | |
/** | |
* @param array<int, array<int|string, int|string>> $events | |
* @param array<int|string, array<int|string, int|string>> $campaignLogCounts | |
* @param array<int|string, array<int|string, int|string>> $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<int, array<int|string, int|string>> $events | |
* | |
* @return array<string, array<int, array<int|string, int|string>>> | |
*/ | |
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<int, array<int, string>> $campaignLogCounts | |
* | |
* @return array<int, array<int, string>> | |
*/ | |
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'; | |
} | |
} | |