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'; | |
| } | |
| } | |