Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\CampaignBundle\Controller;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\CampaignBundle\Entity\LeadEventLog;
|
||||
use Mautic\CampaignBundle\Model\EventLogModel;
|
||||
use Mautic\CoreBundle\Controller\AjaxController as CommonAjaxController;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\InputHelper;
|
||||
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 Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class AjaxController extends CommonAjaxController
|
||||
{
|
||||
public function __construct(
|
||||
private DateHelper $dateHelper,
|
||||
ManagerRegistry $doctrine,
|
||||
ModelFactory $modelFactory,
|
||||
UserHelper $userHelper,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
Translator $translator,
|
||||
FlashBag $flashBag,
|
||||
RequestStack $requestStack,
|
||||
CorePermissions $security,
|
||||
) {
|
||||
parent::__construct($doctrine, $modelFactory, $userHelper, $coreParametersHelper, $dispatcher, $translator, $flashBag, $requestStack, $security);
|
||||
}
|
||||
|
||||
public function updateConnectionsAction(Request $request): \Symfony\Component\HttpFoundation\JsonResponse
|
||||
{
|
||||
$session = $request->getSession();
|
||||
$campaignId = InputHelper::clean($request->query->get('campaignId'));
|
||||
$canvasSettings = $request->request->all()['canvasSettings'] ?? [];
|
||||
if (empty($campaignId)) {
|
||||
$dataArray = ['success' => 0];
|
||||
} else {
|
||||
$session->set('mautic.campaign.'.$campaignId.'.events.canvassettings', $canvasSettings);
|
||||
|
||||
$dataArray = ['success' => 1];
|
||||
}
|
||||
|
||||
return $this->sendJsonResponse($dataArray);
|
||||
}
|
||||
|
||||
public function updateScheduledCampaignEventAction(Request $request): \Symfony\Component\HttpFoundation\JsonResponse
|
||||
{
|
||||
$eventId = (int) $request->request->get('eventId');
|
||||
$contactId = (int) $request->request->get('contactId');
|
||||
$newDate = InputHelper::clean($request->request->get('date'));
|
||||
$originalDate = InputHelper::clean($request->request->get('originalDate'));
|
||||
|
||||
$dataArray = ['success' => 0, 'date' => $originalDate];
|
||||
|
||||
if (!empty($eventId) && !empty($contactId) && !empty($newDate)) {
|
||||
if ($log = $this->getContactEventLog($eventId, $contactId)) {
|
||||
$newDate = new \DateTime($newDate);
|
||||
|
||||
if ($newDate >= new \DateTime()) {
|
||||
$log->setTriggerDate($newDate);
|
||||
|
||||
/** @var EventLogModel $logModel */
|
||||
$logModel = $this->getModel('campaign.event_log');
|
||||
$logModel->saveEntity($log);
|
||||
|
||||
$dataArray = [
|
||||
'success' => 1,
|
||||
'date' => $newDate->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format the date to match the view
|
||||
$dataArray['formattedDate'] = $this->dateHelper->toFull($dataArray['date']);
|
||||
|
||||
return $this->sendJsonResponse($dataArray);
|
||||
}
|
||||
|
||||
public function cancelScheduledCampaignEventAction(Request $request): \Symfony\Component\HttpFoundation\JsonResponse
|
||||
{
|
||||
$dataArray = ['success' => 0];
|
||||
|
||||
$eventId = (int) $request->request->get('eventId');
|
||||
$contactId = (int) $request->request->get('contactId');
|
||||
if (!empty($eventId) && !empty($contactId)) {
|
||||
if ($log = $this->getContactEventLog($eventId, $contactId)) {
|
||||
$log->setIsScheduled(false);
|
||||
|
||||
/** @var EventLogModel $logModel */
|
||||
$logModel = $this->getModel('campaign.event_log');
|
||||
$metadata = $log->getMetadata();
|
||||
$metadata['errors'] = $this->translator->trans(
|
||||
'mautic.campaign.event.cancelled.time',
|
||||
['%date%' => $log->getTriggerDate()->format('Y-m-d H:i:s')]
|
||||
);
|
||||
$log->setMetadata($metadata);
|
||||
$logModel->getRepository()->saveEntity($log);
|
||||
|
||||
$dataArray = ['success' => 1];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sendJsonResponse($dataArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LeadEventLog|null
|
||||
*/
|
||||
protected function getContactEventLog($eventId, $contactId)
|
||||
{
|
||||
$contact = $this->getModel('lead')->getEntity($contactId);
|
||||
if ($contact) {
|
||||
if ($this->security->hasEntityAccess('lead:leads:editown', 'lead:leads:editother', $contact->getPermissionUser())) {
|
||||
/** @var EventLogModel $logModel */
|
||||
$logModel = $this->getModel('campaign.event_log');
|
||||
|
||||
/** @var LeadEventLog $log */
|
||||
$log = $logModel->getRepository()
|
||||
->findOneBy(
|
||||
[
|
||||
'lead' => $contactId,
|
||||
'event' => $eventId,
|
||||
],
|
||||
['dateTriggered' => 'desc']
|
||||
);
|
||||
|
||||
if ($log && ($log->getTriggerDate() > new \DateTime())) {
|
||||
return $log;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\CampaignBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\CampaignBundle\Entity\Campaign;
|
||||
use Mautic\CampaignBundle\Entity\Event;
|
||||
use Mautic\CampaignBundle\Membership\MembershipManager;
|
||||
use Mautic\CampaignBundle\Model\CampaignModel;
|
||||
use Mautic\CampaignBundle\Model\EventModel;
|
||||
use Mautic\CoreBundle\Event\EntityExportEvent;
|
||||
use Mautic\CoreBundle\Event\EntityImportEvent;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\ImportHelper;
|
||||
use Mautic\CoreBundle\Helper\InputHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Controller\LeadAccessTrait;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<Campaign>
|
||||
*/
|
||||
class CampaignApiController extends CommonApiController
|
||||
{
|
||||
use LeadAccessTrait;
|
||||
|
||||
/**
|
||||
* @var CampaignModel|null
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
public function __construct(
|
||||
CorePermissions $security,
|
||||
Translator $translator,
|
||||
EntityResultHelper $entityResultHelper,
|
||||
RouterInterface $router,
|
||||
FormFactoryInterface $formFactory,
|
||||
AppVersion $appVersion,
|
||||
private RequestStack $requestStack,
|
||||
private MembershipManager $membershipManager,
|
||||
ManagerRegistry $doctrine,
|
||||
ModelFactory $modelFactory,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
private ValidatorInterface $validator,
|
||||
private EventModel $eventModel,
|
||||
) {
|
||||
$campaignModel = $modelFactory->getModel('campaign');
|
||||
\assert($campaignModel instanceof CampaignModel);
|
||||
|
||||
$this->model = $campaignModel;
|
||||
$this->entityClass = Campaign::class;
|
||||
$this->entityNameOne = 'campaign';
|
||||
$this->entityNameMulti = 'campaigns';
|
||||
$this->permissionBase = 'campaign:campaigns';
|
||||
$this->serializerGroups = [
|
||||
'campaignDetails',
|
||||
'campaignEventDetails',
|
||||
'categoryList',
|
||||
'publishDetails',
|
||||
'leadListList',
|
||||
'formList',
|
||||
];
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a lead to a campaign.
|
||||
*
|
||||
* @param int $id Campaign ID
|
||||
* @param int $leadId Lead ID
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function addLeadAction($id, $leadId)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
if (null !== $entity) {
|
||||
$leadModel = $this->getModel('lead');
|
||||
$lead = $leadModel->getEntity($leadId);
|
||||
|
||||
if (null == $lead) {
|
||||
return $this->notFound();
|
||||
} elseif (!$this->security->hasEntityAccess('lead:leads:editown', 'lead:leads:editother', $lead->getOwner())) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$this->membershipManager->addContact($lead, $entity);
|
||||
|
||||
$view = $this->view(['success' => 1], Response::HTTP_OK);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given lead from a campaign.
|
||||
*
|
||||
* @param int $id Campaign ID
|
||||
* @param int $leadId Lead ID
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function removeLeadAction($id, $leadId)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
if (null !== $entity) {
|
||||
$lead = $this->checkLeadAccess($leadId, 'edit');
|
||||
if ($lead instanceof Response) {
|
||||
return $lead;
|
||||
}
|
||||
|
||||
$this->membershipManager->removeContact($lead, $entity);
|
||||
|
||||
$view = $this->view(['success' => 1], Response::HTTP_OK);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Campaign &$entity
|
||||
* @param string $action
|
||||
*/
|
||||
protected function preSaveEntity(&$entity, $form, $parameters, $action = 'edit')
|
||||
{
|
||||
$method = $this->requestStack->getCurrentRequest()->getMethod();
|
||||
|
||||
if ('POST' === $method || 'PUT' === $method) {
|
||||
if (empty($parameters['events'])) {
|
||||
$msg = $this->translator->trans('mautic.campaign.form.events.notempty', [], 'validators');
|
||||
|
||||
return $this->returnError($msg, Response::HTTP_BAD_REQUEST);
|
||||
} elseif (empty($parameters['lists']) && empty($parameters['forms'])) {
|
||||
$msg = $this->translator->trans('mautic.campaign.form.sources.notempty', [], 'validators');
|
||||
|
||||
return $this->returnError($msg, Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
$deletedSources = ['lists' => [], 'forms' => []];
|
||||
$deletedEvents = [];
|
||||
$currentSources = [
|
||||
'lists' => isset($parameters['lists']) ? $this->modifyCampaignEventArray($parameters['lists']) : [],
|
||||
'forms' => isset($parameters['forms']) ? $this->modifyCampaignEventArray($parameters['forms']) : [],
|
||||
];
|
||||
|
||||
// delete events and sources which does not exist in the PUT request
|
||||
if ('PUT' === $method) {
|
||||
$requestEventIds = [];
|
||||
$requestSegmentIds = [];
|
||||
$requestFormIds = [];
|
||||
|
||||
foreach ($parameters['events'] as $key => $requestEvent) {
|
||||
if (!isset($requestEvent['id'])) {
|
||||
return $this->returnError('$campaign[events]['.$key.']["id"] is missing', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
$requestEventIds[] = $requestEvent['id'];
|
||||
}
|
||||
|
||||
foreach ($entity->getEvents() as $currentEvent) {
|
||||
if (!in_array($currentEvent->getId(), $requestEventIds)) {
|
||||
$deletedEvents[] = $currentEvent->getId();
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($parameters['lists'])) {
|
||||
foreach ($parameters['lists'] as $requestSegment) {
|
||||
if (!isset($requestSegment['id'])) {
|
||||
return $this->returnError('$campaign[lists]['.$key.']["id"] is missing', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
$requestSegmentIds[] = $requestSegment['id'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($entity->getLists() as $currentSegment) {
|
||||
if (!in_array($currentSegment->getId(), $requestSegmentIds)) {
|
||||
$deletedSources['lists'][$currentSegment->getId()] = 'ignore';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($parameters['forms'])) {
|
||||
foreach ($parameters['forms'] as $requestForm) {
|
||||
if (!isset($requestForm['id'])) {
|
||||
return $this->returnError('$campaign[forms]['.$key.']["id"] is missing', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
$requestFormIds[] = $requestForm['id'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($entity->getForms() as $currentForm) {
|
||||
if (!in_array($currentForm->getId(), $requestFormIds)) {
|
||||
$deletedSources['forms'][$currentForm->getId()] = 'ignore';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set lead sources
|
||||
$this->model->setLeadSources($entity, $currentSources, $deletedSources);
|
||||
|
||||
// Build and set Event entities
|
||||
if (isset($parameters['events']) && isset($parameters['canvasSettings'])) {
|
||||
$this->model->setEvents($entity, $parameters['events'], $parameters['canvasSettings'], $deletedEvents);
|
||||
}
|
||||
|
||||
/** @var array<ConstraintViolationListInterface<ConstraintViolationInterface>> $eventViolations */
|
||||
$eventViolations = array_filter(
|
||||
array_map(
|
||||
fn (Event $event) => $this->validator->validate($event),
|
||||
$entity->getEvents()->toArray()
|
||||
),
|
||||
fn ($error) => $error->count() > 0
|
||||
);
|
||||
|
||||
if (count($eventViolations) > 0) {
|
||||
$errors = [];
|
||||
foreach ($eventViolations as $violationList) {
|
||||
foreach ($violationList as $violation) {
|
||||
\assert($violation instanceof ConstraintViolationInterface);
|
||||
$errors[] = [
|
||||
'code' => $violation->getCode(),
|
||||
'message' => $violation->getMessage(),
|
||||
'details' => $violation->getPropertyPath(),
|
||||
'type' => 'validation',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$view = $this->view(['errors' => $errors], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
// Persist to the database before building connection so that IDs are available
|
||||
$this->model->saveEntity($entity);
|
||||
|
||||
// Update canvas settings with new event IDs then save
|
||||
if (isset($parameters['canvasSettings'])) {
|
||||
$this->model->setCanvasSettings($entity, $parameters['canvasSettings']);
|
||||
}
|
||||
|
||||
if (Request::METHOD_PUT === $method && !empty($deletedEvents)) {
|
||||
$this->eventModel->deleteEvents($entity->getEvents()->toArray(), $deletedEvents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the array structure.
|
||||
*
|
||||
* @param array $events
|
||||
*/
|
||||
public function modifyCampaignEventArray($events): array
|
||||
{
|
||||
$updatedEvents = [];
|
||||
|
||||
if ($events && is_array($events)) {
|
||||
foreach ($events as $event) {
|
||||
if (!empty($event['id'])) {
|
||||
$updatedEvents[$event['id']] = 'ignore';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $updatedEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of campaign contacts.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getContactsAction(Request $request, $id)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
if (!$this->checkEntityAccess($entity)) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$where = InputHelper::clean($request->query->get('where') ?? []);
|
||||
$order = InputHelper::clean($request->query->get('order') ?? []);
|
||||
$start = (int) $request->query->get('start', 0);
|
||||
$limit = (int) $request->query->get('limit', 100);
|
||||
|
||||
$where[] = [
|
||||
'col' => 'campaign_id',
|
||||
'expr' => 'eq',
|
||||
'val' => $id,
|
||||
];
|
||||
|
||||
$where[] = [
|
||||
'col' => 'manually_removed',
|
||||
'expr' => 'eq',
|
||||
'val' => 0,
|
||||
];
|
||||
|
||||
return $this->forward(
|
||||
'Mautic\CoreBundle\Controller\Api\StatsApiController::listAction',
|
||||
[
|
||||
'table' => 'campaign_leads',
|
||||
'itemsName' => 'contacts',
|
||||
'order' => $order,
|
||||
'where' => $where,
|
||||
'start' => $start,
|
||||
'limit' => $limit,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function cloneCampaignAction($campaignId)
|
||||
{
|
||||
if (empty($campaignId) || false == intval($campaignId)) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$original = $this->model->getEntity($campaignId);
|
||||
if (empty($original)) {
|
||||
return $this->notFound();
|
||||
}
|
||||
$entity = clone $original;
|
||||
|
||||
if (!$this->checkEntityAccess($entity, 'create')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$this->model->saveEntity($entity);
|
||||
|
||||
$headers = [];
|
||||
// return the newly created entities location if applicable
|
||||
|
||||
$route = 'mautic_api_campaigns_getone';
|
||||
$headers['Location'] = $this->generateUrl(
|
||||
$route,
|
||||
array_merge(['id' => $entity->getId()], $this->routeParams),
|
||||
true
|
||||
);
|
||||
|
||||
$view = $this->view([$this->entityNameOne => $entity], Response::HTTP_OK, $headers);
|
||||
|
||||
$this->setSerializationContext($view);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of events.
|
||||
*/
|
||||
public function exportCampaignAction(Request $request, int $campaignId): Response
|
||||
{
|
||||
// Check if the campaign exists
|
||||
$campaign = $this->model->getEntity($campaignId);
|
||||
if (!$campaign) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
// Check if user has permission to export campaigns
|
||||
if (!$this->security->isGranted('campaign:export:enable', 'MATCH_ONE')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
// Dispatch event to collect campaign data for export
|
||||
$event = new EntityExportEvent(Campaign::ENTITY_NAME, $campaignId);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$data = $event->getEntities();
|
||||
|
||||
// Prepare response
|
||||
$view = $this->view([$data], Response::HTTP_OK);
|
||||
$this->setSerializationContext($view);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
public function importCampaignAction(Request $request, UserHelper $userHelper, ImportHelper $importHelper): Response
|
||||
{
|
||||
// Check if user has permission to import campaigns
|
||||
if (!$this->security->isGranted('campaign:imports:create')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
// Decode request JSON
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
if (!$data || !isset($data[0][Campaign::ENTITY_NAME])) {
|
||||
$files = $request->files->all();
|
||||
|
||||
if (1 !== count($files)) {
|
||||
return $this->handleView(
|
||||
$this->view(['error' => $this->translator->trans('mautic.campaign.api.import.incorrect_zip_file', [], 'messages')], Response::HTTP_BAD_REQUEST)
|
||||
);
|
||||
}
|
||||
|
||||
$uploadedFile = array_values($files)[0];
|
||||
|
||||
if (!$uploadedFile->isValid()) {
|
||||
return $this->handleView(
|
||||
$this->view(['error' => $this->translator->trans('mautic.campaign.api.import.upload_failed', [], 'messages')], Response::HTTP_BAD_REQUEST)
|
||||
);
|
||||
}
|
||||
|
||||
if ('zip' !== strtolower($uploadedFile->getClientOriginalExtension())) {
|
||||
return $this->handleView(
|
||||
$this->view(['error' => $this->translator->trans('mautic.campaign.api.import.incorrect_upload_file_format', [], 'messages')], Response::HTTP_BAD_REQUEST)
|
||||
);
|
||||
}
|
||||
|
||||
$zipPath = $uploadedFile->getPathname();
|
||||
|
||||
if (!file_exists($zipPath)) {
|
||||
return $this->handleView(
|
||||
$this->view(['error' => $this->translator->trans('mautic.campaign.api.import.uploaded_file_no_exist', [], 'messages')], Response::HTTP_INTERNAL_SERVER_ERROR)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $importHelper->readZipFile($zipPath);
|
||||
} catch (\RuntimeException $e) {
|
||||
unlink($zipPath);
|
||||
|
||||
return $this->handleView(
|
||||
$this->view(['error' => $e->getMessage()], Response::HTTP_BAD_REQUEST)
|
||||
);
|
||||
}
|
||||
}
|
||||
$importHelper->recursiveRemoveEmailaddress($data);
|
||||
$userId = $userHelper->getUser()->getId();
|
||||
|
||||
foreach ($data as $entity) {
|
||||
$event = new EntityImportEvent(Campaign::ENTITY_NAME, $entity, $userId);
|
||||
$this->dispatcher->dispatch($event);
|
||||
}
|
||||
$view = $this->view([$this->translator->trans('mautic.campaign.campaign.import.finished', [], 'messages')], Response::HTTP_CREATED);
|
||||
$this->setSerializationContext($view);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\CampaignBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\ApiBundle\Serializer\Exclusion\FieldExclusionStrategy;
|
||||
use Mautic\CampaignBundle\Entity\Event;
|
||||
use Mautic\CampaignBundle\Model\EventModel;
|
||||
use Mautic\CoreBundle\Entity\FormEntity;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Controller\LeadAccessTrait;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<Event>
|
||||
*/
|
||||
class EventApiController extends CommonApiController
|
||||
{
|
||||
use LeadAccessTrait;
|
||||
|
||||
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper)
|
||||
{
|
||||
$campaignEventModel = $modelFactory->getModel('campaign.event');
|
||||
\assert($campaignEventModel instanceof EventModel);
|
||||
|
||||
$this->model = $campaignEventModel;
|
||||
$this->entityClass = Event::class;
|
||||
$this->entityNameOne = 'event';
|
||||
$this->entityNameMulti = 'events';
|
||||
$this->serializerGroups = ['campaignEventStandaloneDetails', 'campaignList'];
|
||||
$this->parentChildrenLevelDepth = 1;
|
||||
|
||||
// Don't include campaign in children/parent arrays
|
||||
$this->addExclusionStrategy(new FieldExclusionStrategy(['campaign'], 1));
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Event|FormEntity $entity
|
||||
*/
|
||||
protected function checkEntityAccess($entity, $action = 'view')
|
||||
{
|
||||
// Use the campaign for permission checks
|
||||
return parent::checkEntityAccess($entity->getCampaign(), $action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\CampaignBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use FOS\RestBundle\View\View;
|
||||
use Mautic\ApiBundle\Controller\FetchCommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\ApiBundle\Serializer\Exclusion\FieldInclusionStrategy;
|
||||
use Mautic\CampaignBundle\Entity\Campaign;
|
||||
use Mautic\CampaignBundle\Entity\Event;
|
||||
use Mautic\CampaignBundle\Entity\LeadEventLog;
|
||||
use Mautic\CampaignBundle\Model\EventLogModel;
|
||||
use Mautic\CampaignBundle\Model\EventModel;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Controller\LeadAccessTrait;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* @extends FetchCommonApiController<LeadEventLog>
|
||||
*/
|
||||
class EventLogApiController extends FetchCommonApiController
|
||||
{
|
||||
use LeadAccessTrait;
|
||||
|
||||
private const LOG_SERIALIZATION = 30;
|
||||
|
||||
/**
|
||||
* @var Campaign
|
||||
*/
|
||||
protected $campaign;
|
||||
|
||||
/**
|
||||
* @var Lead
|
||||
*/
|
||||
protected $contact;
|
||||
|
||||
/**
|
||||
* @var EventLogModel|null
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
public function __construct(
|
||||
CorePermissions $security,
|
||||
Translator $translator,
|
||||
EntityResultHelper $entityResultHelper,
|
||||
AppVersion $appVersion,
|
||||
RequestStack $requestStack,
|
||||
ManagerRegistry $doctrine,
|
||||
ModelFactory $modelFactory,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
) {
|
||||
$campaignEventLogModel = $modelFactory->getModel('campaign.event_log');
|
||||
\assert($campaignEventLogModel instanceof EventLogModel);
|
||||
$this->model = $campaignEventLogModel;
|
||||
$this->entityClass = LeadEventLog::class;
|
||||
$this->entityNameOne = 'event';
|
||||
$this->entityNameMulti = 'events';
|
||||
$this->parentChildrenLevelDepth = 1;
|
||||
$this->serializerGroups = [
|
||||
'campaignList',
|
||||
'ipAddressList',
|
||||
self::LOG_SERIALIZATION => 'campaignEventLogDetails',
|
||||
];
|
||||
|
||||
// Only include the id of the parent
|
||||
$this->addExclusionStrategy(new FieldInclusionStrategy(['id'], 1, 'parent'));
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public function getEntitiesAction(Request $request, UserHelper $userHelper)
|
||||
{
|
||||
$this->serializerGroups[self::LOG_SERIALIZATION] = 'campaignEventStandaloneLogDetails';
|
||||
$this->serializerGroups[] = 'campaignEventStandaloneList';
|
||||
$this->serializerGroups[] = 'leadBasicList';
|
||||
|
||||
return parent::getEntitiesAction($request, $userHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of events.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getContactEventsAction(Request $request, UserHelper $userHelper, $contactId, $campaignId = null)
|
||||
{
|
||||
// Ensure contact exists and user has access
|
||||
$contact = $this->checkLeadAccess($contactId, 'view');
|
||||
if ($contact instanceof Response) {
|
||||
return $contact;
|
||||
}
|
||||
|
||||
// Ensure campaign exists and user has access
|
||||
if (!empty($campaignId)) {
|
||||
$campaign = $this->getModel('campaign')->getEntity($campaignId);
|
||||
if (null == $campaign || !$campaign->getId()) {
|
||||
return $this->notFound();
|
||||
}
|
||||
if (!$this->checkEntityAccess($campaign)) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
// Check that contact is part of the campaign
|
||||
$membership = $campaign->getContactMembership($contact);
|
||||
if (0 === count($membership)) {
|
||||
return $this->returnError(
|
||||
$this->translator->trans(
|
||||
'mautic.campaign.error.contact_not_in_campaign',
|
||||
['%campaign%' => $campaignId, '%contact%' => $contactId]
|
||||
),
|
||||
Response::HTTP_CONFLICT
|
||||
);
|
||||
}
|
||||
|
||||
$this->campaign = $campaign;
|
||||
$this->serializerGroups[] = 'campaignEventWithLogsList';
|
||||
$this->serializerGroups[] = 'campaignLeadList';
|
||||
} else {
|
||||
unset($this->serializerGroups[self::LOG_SERIALIZATION]);
|
||||
$this->serializerGroups[] = 'campaignEventStandaloneList';
|
||||
$this->serializerGroups[] = 'campaignEventStandaloneLogDetails';
|
||||
}
|
||||
|
||||
$this->contact = $contact;
|
||||
$this->extraGetEntitiesArguments = [
|
||||
'contact_id' => $contactId,
|
||||
'campaign_id' => $campaignId,
|
||||
];
|
||||
|
||||
return $this->getEntitiesAction($request, $userHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public function editContactEventAction(Request $request, $eventId, $contactId)
|
||||
{
|
||||
$parameters = $request->request->all();
|
||||
|
||||
// Ensure contact exists and user has access
|
||||
$contact = $this->checkLeadAccess($contactId, 'edit');
|
||||
if ($contact instanceof Response) {
|
||||
return $contact;
|
||||
}
|
||||
|
||||
/** @var EventModel $eventModel */
|
||||
$eventModel = $this->getModel('campaign.event');
|
||||
/** @var Event $event */
|
||||
$event = $eventModel->getEntity($eventId);
|
||||
if (null === $event || !$event->getId()) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
// Ensure campaign edit access
|
||||
$campaign = $event->getCampaign();
|
||||
if (!$this->checkEntityAccess($campaign, 'edit')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$result = $this->model->updateContactEvent($event, $contact, $parameters);
|
||||
|
||||
if (is_string($result)) {
|
||||
return $this->returnError($result, Response::HTTP_CONFLICT);
|
||||
} else {
|
||||
[$log, $created] = $result;
|
||||
}
|
||||
|
||||
$event->addContactLog($log);
|
||||
$view = $this->view(
|
||||
[
|
||||
$this->entityNameOne => $event,
|
||||
],
|
||||
($created) ? Response::HTTP_CREATED : Response::HTTP_OK
|
||||
);
|
||||
$this->serializerGroups[] = 'campaignEventWithLogsDetails';
|
||||
$this->serializerGroups[] = 'campaignBasicList';
|
||||
$this->setSerializationContext($view);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|Response
|
||||
*/
|
||||
public function editEventsAction(Request $request)
|
||||
{
|
||||
$parameters = $request->request->all();
|
||||
|
||||
$valid = $this->validateBatchPayload($parameters);
|
||||
if ($valid instanceof Response) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
$events = $this->getBatchEntities($parameters, $errors, false, 'eventId', $this->getModel('campaign.event'), false);
|
||||
$contacts = $this->getBatchEntities($parameters, $errors, false, 'contactId', $this->getModel('lead'), false);
|
||||
|
||||
$this->inBatchMode = true;
|
||||
$errors = [];
|
||||
foreach ($parameters as $key => $params) {
|
||||
if (!isset($params['eventId']) || !isset($params['contactId']) || !isset($events[$params['eventId']])
|
||||
|| !isset($contacts[$params['contactId']])
|
||||
) {
|
||||
$errors[$key] = $this->notFound('mautic.campaign.error.edit_events.request_invalid');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$event = $events[$params['eventId']];
|
||||
|
||||
// Ensure contact exists and user has access
|
||||
$contact = $this->checkLeadAccess($contacts[$params['contactId']], 'edit');
|
||||
if ($contact instanceof Response) {
|
||||
$errors[$key] = $contact->getContent();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure campaign edit access
|
||||
$campaign = $event->getCampaign();
|
||||
if (!$this->checkEntityAccess($campaign, 'edit')) {
|
||||
$errors[$key] = $this->accessDenied();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $this->model->updateContactEvent($event, $contact, $params);
|
||||
|
||||
if (is_string($result)) {
|
||||
$errors[$key] = $this->returnError($result, Response::HTTP_CONFLICT);
|
||||
} else {
|
||||
[$log, $created] = $result;
|
||||
$event->addContactLog($log);
|
||||
}
|
||||
}
|
||||
|
||||
$payload = [
|
||||
$this->entityNameMulti => $events,
|
||||
];
|
||||
|
||||
if (!empty($errors)) {
|
||||
$payload['errors'] = $errors;
|
||||
}
|
||||
|
||||
$view = $this->view($payload, Response::HTTP_OK);
|
||||
$this->serializerGroups[] = 'campaignEventWithLogsList';
|
||||
$this->setSerializationContext($view);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
protected function view($data = null, ?int $statusCode = null, array $headers = []): View
|
||||
{
|
||||
if ($this->campaign) {
|
||||
$data['campaign'] = $this->campaign;
|
||||
|
||||
if ($this->contact) {
|
||||
[$data['membership'], $ignore] = $this->prepareEntitiesForView($this->campaign->getContactMembership($this->contact));
|
||||
}
|
||||
}
|
||||
|
||||
return parent::view($data, $statusCode, $headers);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\CampaignBundle\Controller;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Mautic\CampaignBundle\Entity\Campaign;
|
||||
use Mautic\CampaignBundle\Model\CampaignModel;
|
||||
use Mautic\CoreBundle\Helper\MapHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
class CampaignMapStatsController extends AbstractController
|
||||
{
|
||||
public const MAP_OPTIONS = [
|
||||
'contacts' => [
|
||||
'label' => 'mautic.lead.leads',
|
||||
'unit' => 'Contact',
|
||||
],
|
||||
'read_count' => [
|
||||
'label' => 'mautic.email.read',
|
||||
'unit' => 'Read',
|
||||
],
|
||||
'clicked_through_count'=> [
|
||||
'label' => 'mautic.email.click',
|
||||
'unit' => 'Click',
|
||||
],
|
||||
];
|
||||
|
||||
public const LEGEND_TEXT = 'Total: %total (%withCountry with country)';
|
||||
|
||||
public function __construct(private CampaignModel $model)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<int, array<string, int|string>>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getData(Campaign $entity, \DateTimeImmutable $dateFromObject, \DateTimeImmutable $dateToObject): array
|
||||
{
|
||||
return $this->model->getCountryStats($entity, $dateFromObject, $dateToObject);
|
||||
}
|
||||
|
||||
public function hasAccess(CorePermissions $security, Campaign $entity): bool
|
||||
{
|
||||
return $security->hasEntityAccess(
|
||||
'email:emails:viewown',
|
||||
'email:emails:viewother',
|
||||
$entity->getCreatedBy()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,array<string, string>>
|
||||
*/
|
||||
public function getMapOptions(Campaign $entity): array
|
||||
{
|
||||
if ($entity->isEmailCampaign()) {
|
||||
return self::MAP_OPTIONS;
|
||||
}
|
||||
|
||||
$key = array_key_first(self::MAP_OPTIONS);
|
||||
|
||||
return [$key => self::MAP_OPTIONS[$key]];
|
||||
}
|
||||
|
||||
public function getMapOptionsTitle(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function viewAction(
|
||||
CorePermissions $security,
|
||||
int $objectId,
|
||||
string $dateFrom = '',
|
||||
string $dateTo = '',
|
||||
): Response {
|
||||
$entity = $this->model->getEntity($objectId);
|
||||
|
||||
if (empty($entity) || !$this->hasAccess($security, $entity)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$statsCountries = $this->getData($entity, new \DateTimeImmutable($dateFrom), new \DateTimeImmutable($dateTo));
|
||||
$mapData = MapHelper::buildMapData($statsCountries, $this->getMapOptions($entity), self::LEGEND_TEXT);
|
||||
|
||||
return $this->render(
|
||||
'@MauticCore/Helper/map.html.twig',
|
||||
[
|
||||
'data' => $mapData[0]['data'],
|
||||
'height' => 315,
|
||||
'optionsEnabled' => true,
|
||||
'optionsTitle' => $this->getMapOptionsTitle(),
|
||||
'options' => $mapData,
|
||||
'legendEnabled' => true,
|
||||
'statUnit' => $mapData[0]['unit'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\CampaignBundle\Controller;
|
||||
|
||||
use Mautic\CampaignBundle\Model\CampaignModel;
|
||||
use Mautic\CoreBundle\Helper\Chart\BarChart;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\DateTimeHelper;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\EmailBundle\Stats\EmailPeriodMetrics;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CampaignMetricsController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private Translator $translator,
|
||||
private CoreParametersHelper $coreParametersHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
public function emailWeekdaysAction(
|
||||
EmailPeriodMetrics $emailPeriodMetrics,
|
||||
CampaignModel $model,
|
||||
int $objectId,
|
||||
string $dateFrom = '',
|
||||
string $dateTo = '',
|
||||
): Response {
|
||||
$entity = $model->getEntity($objectId);
|
||||
$eventsIds = $entity->getEmailSendEvents()->getKeys();
|
||||
$dateFromObject = new \DateTimeImmutable($dateFrom);
|
||||
$dateToObject = new \DateTimeImmutable($dateTo);
|
||||
|
||||
$dateTimeHelper = new DateTimeHelper();
|
||||
$defaultTimezoneOffset = $dateTimeHelper->getLocalDateTime()->format('Z');
|
||||
$stats = $emailPeriodMetrics->emailMetricsPerWeekdayByCampaignEvents($eventsIds, $dateFromObject, $dateToObject, $defaultTimezoneOffset);
|
||||
|
||||
$chart = new BarChart([
|
||||
$this->translator->trans('mautic.core.date.monday'),
|
||||
$this->translator->trans('mautic.core.date.tuesday'),
|
||||
$this->translator->trans('mautic.core.date.wednesday'),
|
||||
$this->translator->trans('mautic.core.date.thursday'),
|
||||
$this->translator->trans('mautic.core.date.friday'),
|
||||
$this->translator->trans('mautic.core.date.saturday'),
|
||||
$this->translator->trans('mautic.core.date.sunday'),
|
||||
]);
|
||||
|
||||
$chart->setDataset($this->translator->trans('mautic.email.sent'), array_column($stats, 'sent_count'));
|
||||
$chart->setDataset($this->translator->trans('mautic.email.read'), array_column($stats, 'read_count'));
|
||||
$chart->setDataset($this->translator->trans('mautic.email.click'), array_column($stats, 'hit_count'));
|
||||
|
||||
return $this->render(
|
||||
'@MauticCore/Helper/chart.html.twig',
|
||||
[
|
||||
'chartData' => $chart->render(),
|
||||
'chartType' => 'bar',
|
||||
'chartHeight' => 300,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function emailHoursAction(
|
||||
EmailPeriodMetrics $emailPeriodMetrics,
|
||||
CampaignModel $model,
|
||||
int $objectId,
|
||||
string $dateFrom = '',
|
||||
string $dateTo = '',
|
||||
): Response {
|
||||
$entity = $model->getEntity($objectId);
|
||||
$eventsIds = $entity->getEmailSendEvents()->getKeys();
|
||||
$dateFromObject = new \DateTimeImmutable($dateFrom);
|
||||
$dateToObject = new \DateTimeImmutable($dateTo);
|
||||
|
||||
$dateTimeHelper = new DateTimeHelper();
|
||||
$defaultTimezoneOffset = $dateTimeHelper->getLocalDateTime()->format('Z');
|
||||
|
||||
$stats = $emailPeriodMetrics->emailMetricsPerHourByCampaignEvents($eventsIds, $dateFromObject, $dateToObject, $defaultTimezoneOffset);
|
||||
|
||||
$hoursRange = range(0, 23);
|
||||
$labels = [];
|
||||
|
||||
$timeFormat = $this->coreParametersHelper->get('date_format_timeonly');
|
||||
|
||||
foreach ($hoursRange as $hour) {
|
||||
$startTime = (new \DateTime())->setTime($hour, 0);
|
||||
$endTime = (new \DateTime())->setTime(($hour + 1) % 24, 0);
|
||||
|
||||
$labels[] = $startTime->format($timeFormat).' - '.$endTime->format($timeFormat);
|
||||
}
|
||||
|
||||
$chart = new BarChart($labels);
|
||||
$chart->setDataset($this->translator->trans('mautic.email.sent'), array_column($stats, 'sent_count'));
|
||||
$chart->setDataset($this->translator->trans('mautic.email.read'), array_column($stats, 'read_count'));
|
||||
$chart->setDataset($this->translator->trans('mautic.email.click'), array_column($stats, 'hit_count'));
|
||||
|
||||
return $this->render(
|
||||
'@MauticCore/Helper/chart.html.twig',
|
||||
[
|
||||
'chartData' => $chart->render(),
|
||||
'chartType' => 'hour',
|
||||
'chartHeight' => 300,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,656 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\CampaignBundle\Controller;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\CampaignBundle\Entity\Event;
|
||||
use Mautic\CampaignBundle\EventCollector\EventCollector;
|
||||
use Mautic\CampaignBundle\Form\Type\EventType;
|
||||
use Mautic\CampaignBundle\Model\CampaignModel;
|
||||
use Mautic\CoreBundle\Controller\FormController as CommonFormController;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\DateTimeHelper;
|
||||
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 Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class EventController extends CommonFormController
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $supportedEventTypes = [
|
||||
Event::TYPE_DECISION,
|
||||
Event::TYPE_ACTION,
|
||||
Event::TYPE_CONDITION,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
FormFactoryInterface $formFactory,
|
||||
FormFieldHelper $fieldHelper,
|
||||
private EventCollector $eventCollector,
|
||||
private DateHelper $dateHelper,
|
||||
ManagerRegistry $doctrine,
|
||||
ModelFactory $modelFactory,
|
||||
UserHelper $userHelper,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
Translator $translator,
|
||||
FlashBag $flashBag,
|
||||
RequestStack $requestStack,
|
||||
CorePermissions $security,
|
||||
private CampaignModel $campaignModel,
|
||||
) {
|
||||
parent::__construct($formFactory, $fieldHelper, $doctrine, $modelFactory, $userHelper, $coreParametersHelper, $dispatcher, $translator, $flashBag, $requestStack, $security);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
private array $modifiedEvents = [];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private array $deletedEvents = [];
|
||||
|
||||
/**
|
||||
* Generates new form and processes post data.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function newAction(Request $request)
|
||||
{
|
||||
$success = 0;
|
||||
$valid = $cancelled = false;
|
||||
$this->setCampaignElements($request->request);
|
||||
if ('1' === $request->request->get('submit')) {
|
||||
$event = $request->request->all()['campaignevent'] ?? [];
|
||||
$type = $event['type'];
|
||||
$eventType = $event['eventType'];
|
||||
$campaignId = $event['campaignId'];
|
||||
$event['triggerDate'] = (!empty($event['triggerDate'])) ? (new DateTimeHelper($event['triggerDate']))->getDateTime() : null;
|
||||
} else {
|
||||
$type = $request->query->get('type');
|
||||
$eventType = $request->query->get('eventType');
|
||||
$campaignId = $request->query->get('campaignId');
|
||||
$anchorName = $request->query->get('anchor', '');
|
||||
$event = [
|
||||
'type' => $type,
|
||||
'eventType' => $eventType,
|
||||
'campaignId' => $campaignId,
|
||||
'anchor' => $anchorName,
|
||||
'anchorEventType' => $request->query->get('anchorEventType', ''),
|
||||
];
|
||||
}
|
||||
|
||||
// set the eventType key for events
|
||||
if (!in_array($eventType, $this->supportedEventTypes)) {
|
||||
return $this->modalAccessDenied();
|
||||
}
|
||||
|
||||
// ajax only for form fields
|
||||
if (!$type
|
||||
|| !$request->isXmlHttpRequest()
|
||||
|| !$this->security->isGranted(
|
||||
[
|
||||
'campaign:campaigns:edit',
|
||||
'campaign:campaigns:create',
|
||||
],
|
||||
'MATCH_ONE'
|
||||
)
|
||||
) {
|
||||
return $this->modalAccessDenied();
|
||||
}
|
||||
|
||||
// fire the builder event
|
||||
$events = $this->eventCollector->getEventsArray();
|
||||
$form = $this->formFactory->create(
|
||||
EventType::class,
|
||||
$event,
|
||||
[
|
||||
'action' => $this->generateUrl('mautic_campaignevent_action', ['objectAction' => 'new']),
|
||||
'settings' => $events[$eventType][$type],
|
||||
]
|
||||
);
|
||||
$event['settings'] = $events[$eventType][$type];
|
||||
|
||||
$form->get('campaignId')->setData($campaignId);
|
||||
|
||||
// Check for a submitted form and process it
|
||||
if ('1' === $request->request->get('submit')) {
|
||||
if (!$cancelled = $this->isFormCancelled($form)) {
|
||||
if ($valid = $this->isFormValid($form)) {
|
||||
$success = 1;
|
||||
|
||||
// form is valid so process the data
|
||||
$keyId = 'new'.bin2hex(random_bytes(32));
|
||||
|
||||
// save the properties to return with request
|
||||
$modifiedEvents = $this->getModifiedEvents();
|
||||
$formData = $form->getData();
|
||||
$event = array_merge($event, $formData);
|
||||
$event['id'] = $event['tempId'] = $keyId;
|
||||
if (empty($event['name'])) {
|
||||
// set it to the event default
|
||||
$event['name'] = $this->translator->trans($event['settings']['label']);
|
||||
}
|
||||
$modifiedEvents[$keyId] = $event;
|
||||
$this->modifiedEvents = $modifiedEvents;
|
||||
} else {
|
||||
$success = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$viewParams = ['type' => $type];
|
||||
if ($cancelled || $valid) {
|
||||
$closeModal = true;
|
||||
} else {
|
||||
$closeModal = false;
|
||||
if (isset($event['settings']['formTheme'])) {
|
||||
$viewParams['formTheme'] = $event['settings']['formTheme'];
|
||||
}
|
||||
|
||||
$viewParams['form'] = $form->createView();
|
||||
$viewParams['eventHeader'] = $this->translator->trans($event['settings']['label']);
|
||||
$viewParams['eventDescription'] = (!empty($event['settings']['description'])) ? $this->translator->trans(
|
||||
$event['settings']['description']
|
||||
) : '';
|
||||
}
|
||||
|
||||
$viewParams['hideTriggerMode'] = isset($event['settings']['hideTriggerMode']) && $event['settings']['hideTriggerMode'];
|
||||
|
||||
$passthroughVars = [
|
||||
'mauticContent' => 'campaignEvent',
|
||||
'success' => $success,
|
||||
'formSubmitted' => $form->isSubmitted(),
|
||||
'route' => false,
|
||||
];
|
||||
|
||||
if (1 === $success && !empty($modifiedEvents)) {
|
||||
$passthroughVars['modifiedEvents'] = $modifiedEvents;
|
||||
}
|
||||
|
||||
if (!empty($keyId)) {
|
||||
$passthroughVars = array_merge($passthroughVars, $this->eventViewVars($event, $campaignId, 'new'));
|
||||
}
|
||||
|
||||
if ($closeModal) {
|
||||
// just close the modal
|
||||
$passthroughVars['closeModal'] = 1;
|
||||
|
||||
return new JsonResponse($passthroughVars);
|
||||
} else {
|
||||
return $this->ajaxAction(
|
||||
$request,
|
||||
[
|
||||
'contentTemplate' => '@MauticCampaign/Event/form.html.twig',
|
||||
'viewParameters' => $viewParams,
|
||||
'passthroughVars' => $passthroughVars,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates edit form and processes post data.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function editAction(Request $request, $objectId)
|
||||
{
|
||||
$valid = $cancelled = false;
|
||||
$method = $request->getMethod();
|
||||
$campaignEvent = $request->request->all()['campaignevent'] ?? [];
|
||||
$campaignId = 'POST' === $method && !empty($campaignEvent['campaignId'])
|
||||
? $campaignEvent['campaignId']
|
||||
: $request->query->get('campaignId');
|
||||
|
||||
$this->setCampaignElements($request->request);
|
||||
$event = $this->modifiedEvents[$objectId] ?? [];
|
||||
if (empty($event)) {
|
||||
$eventEntity = $this->getModel('campaign.event')->getEntity($objectId);
|
||||
if (null === $eventEntity) {
|
||||
return $this->modalAccessDenied();
|
||||
}
|
||||
$event = $eventEntity->convertToArray();
|
||||
}
|
||||
|
||||
if ('1' === $request->request->get('submit')) {
|
||||
$event = array_merge($event, [
|
||||
'anchor' => $campaignEvent['anchor'] ?? '',
|
||||
'anchorEventType' => $campaignEvent['anchorEventType'] ?? '',
|
||||
]);
|
||||
} else {
|
||||
if (!isset($event['anchor']) && !empty($event['decisionPath'])) {
|
||||
// Used to generate label
|
||||
$event['anchor'] = $event['decisionPath'];
|
||||
}
|
||||
|
||||
if ($request->query->has('anchor')) {
|
||||
// Override the anchor
|
||||
$event['anchor'] = $request->get('anchor');
|
||||
}
|
||||
|
||||
if ($request->query->has('anchorEventType')) {
|
||||
// Override the anchorEventType
|
||||
$event['anchorEventType'] = $request->get('anchorEventType');
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we don't have an event, don't support the event type, this is not an
|
||||
* AJAX request, or we are not granted campaign edit/create, deny access.
|
||||
*/
|
||||
if (empty($event)
|
||||
|| empty($event['eventType'])
|
||||
|| !in_array($event['eventType'], $this->supportedEventTypes)
|
||||
|| !isset($event['type'])
|
||||
|| !$request->isXmlHttpRequest()
|
||||
|| !$this->security->isGranted(
|
||||
[
|
||||
'campaign:campaigns:edit',
|
||||
'campaign:campaigns:create',
|
||||
],
|
||||
'MATCH_ONE'
|
||||
)
|
||||
) {
|
||||
return $this->modalAccessDenied();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the CampaignBuilderEvent event to get all events.
|
||||
*
|
||||
* We can directly dereference the return value here to get
|
||||
* the supported events for this type because we already made
|
||||
* sure that we're accessing a supported event type above.
|
||||
*
|
||||
* Method getEventsArray() returns translated labels & descriptions
|
||||
*/
|
||||
$supportedEvents = $this->eventCollector->getEventsArray()[$event['eventType']];
|
||||
$form = $this->formFactory->create(
|
||||
EventType::class,
|
||||
(array) $event,
|
||||
[
|
||||
'action' => $this->generateUrl('mautic_campaignevent_action', ['objectAction' => 'edit', 'objectId' => $objectId]),
|
||||
'settings' => $supportedEvents[$event['type']],
|
||||
]
|
||||
);
|
||||
$event['settings'] = $supportedEvents[$event['type']];
|
||||
|
||||
$form->get('campaignId')->setData($campaignId);
|
||||
$modifiedEvents = $this->getModifiedEvents();
|
||||
|
||||
// Check for a submitted form and process it
|
||||
if ('1' === $request->request->get('submit')) {
|
||||
if (!$cancelled = $this->isFormCancelled($form)) {
|
||||
if ($valid = $this->isFormValid($form)) {
|
||||
$formData = $form->getData();
|
||||
$event = array_merge($event, $formData);
|
||||
|
||||
// Set the name to the event default if not known.
|
||||
if (empty($event['name'])) {
|
||||
$event['name'] = $event['settings']['label'];
|
||||
}
|
||||
$modifiedEvents[$objectId] = $event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$viewParams = [
|
||||
'type' => $event['type'],
|
||||
'hideTriggerMode' => isset($event['settings']['hideTriggerMode']) && $event['settings']['hideTriggerMode'],
|
||||
];
|
||||
|
||||
$passthroughVars = [
|
||||
'mauticContent' => 'campaignEvent',
|
||||
'success' => !$cancelled && $valid,
|
||||
'formSubmitted' => $form->isSubmitted(),
|
||||
'route' => false,
|
||||
'modifiedEvents'=> $modifiedEvents,
|
||||
'eventId' => $event['id'] ?? '',
|
||||
'event' => $event,
|
||||
];
|
||||
|
||||
if (!$cancelled && !$valid) {
|
||||
if (isset($event['settings']['formTheme'])) {
|
||||
$viewParams['formTheme'] = $event['settings']['formTheme'];
|
||||
}
|
||||
|
||||
$viewParams = array_merge($viewParams, [
|
||||
'form' => $form->createView(),
|
||||
'eventHeader' => $event['settings']['label'],
|
||||
'eventDescription' => $event['settings']['description'],
|
||||
]);
|
||||
|
||||
return $this->ajaxAction(
|
||||
$request,
|
||||
[
|
||||
'contentTemplate' => '@MauticCampaign/Event/form.html.twig',
|
||||
'viewParameters' => $viewParams,
|
||||
'passthroughVars' => $passthroughVars,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (!$cancelled && $valid) {
|
||||
$passthroughVars = array_merge($passthroughVars, $this->eventViewVars($event, $campaignId, 'edit'));
|
||||
}
|
||||
|
||||
// Just close the modal
|
||||
$passthroughVars['closeModal'] = 1;
|
||||
|
||||
return new JsonResponse($passthroughVars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the entity.
|
||||
*
|
||||
* @return JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function deleteAction(Request $request, $objectId)
|
||||
{
|
||||
$this->setCampaignElements($request->request);
|
||||
$modifiedEvents = $this->getModifiedEvents();
|
||||
$deletedEvents = $this->deletedEvents;
|
||||
|
||||
// ajax only for form fields
|
||||
if (!$request->isXmlHttpRequest()
|
||||
|| !$this->security->isGranted(
|
||||
[
|
||||
'campaign:campaigns:edit',
|
||||
'campaign:campaigns:create',
|
||||
],
|
||||
'MATCH_ONE'
|
||||
)
|
||||
) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$event = (array_key_exists($objectId, $modifiedEvents)) ? $modifiedEvents[$objectId] : null;
|
||||
|
||||
if ('POST' == $request->getMethod() && null !== $event) {
|
||||
$events = $this->eventCollector->getEventsArray();
|
||||
$event['settings'] = $events[$event['eventType']][$event['type']];
|
||||
|
||||
// Add the field to the delete list
|
||||
if (!in_array($objectId, $deletedEvents)) {
|
||||
// If event is new don't add to deleted list
|
||||
if (!str_contains($objectId, 'new')) {
|
||||
$deletedEvents[] = $objectId;
|
||||
}
|
||||
|
||||
// Always remove from modified list if deleted
|
||||
if (isset($modifiedEvents[$objectId])) {
|
||||
unset($modifiedEvents[$objectId]);
|
||||
}
|
||||
}
|
||||
|
||||
$dataArray = [
|
||||
'mauticContent' => 'campaignEvent',
|
||||
'success' => 1,
|
||||
'route' => false,
|
||||
'eventId' => $objectId,
|
||||
'deleted' => 1,
|
||||
'event' => $event,
|
||||
'deletedEvents' => $deletedEvents,
|
||||
];
|
||||
} else {
|
||||
$dataArray = ['success' => 0];
|
||||
}
|
||||
|
||||
return new JsonResponse($dataArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undeletes the entity.
|
||||
*
|
||||
* @return JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function undeleteAction(Request $request, $objectId)
|
||||
{
|
||||
$campaignId = $request->query->get('campaignId');
|
||||
$this->setCampaignElements($request->request);
|
||||
$modifiedEvents = $this->getModifiedEvents();
|
||||
$deletedEvents = $this->deletedEvents;
|
||||
|
||||
// ajax only for form fields
|
||||
if (!$request->isXmlHttpRequest()
|
||||
|| !$this->security->isGranted(
|
||||
[
|
||||
'campaign:campaigns:edit',
|
||||
'campaign:campaigns:create',
|
||||
],
|
||||
'MATCH_ONE'
|
||||
)
|
||||
) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$event = (array_key_exists($objectId, $modifiedEvents)) ? $modifiedEvents[$objectId] : null;
|
||||
|
||||
if ('POST' == $request->getMethod() && null !== $event) {
|
||||
$events = $this->eventCollector->getEventsArray();
|
||||
$event['settings'] = $events[$event['eventType']][$event['type']];
|
||||
|
||||
// add the field to the delete list
|
||||
if (in_array($objectId, $deletedEvents)) {
|
||||
$key = array_search($objectId, $deletedEvents);
|
||||
unset($deletedEvents[$key]);
|
||||
}
|
||||
|
||||
$template = (empty($event['settings']['template'])) ? '@MauticCampaign/Event/_generic.html.twig'
|
||||
: $event['settings']['template'];
|
||||
|
||||
// prevent undefined errors
|
||||
$entity = new Event();
|
||||
$blank = $entity->convertToArray();
|
||||
$event = array_merge($blank, $event);
|
||||
|
||||
$dataArray = [
|
||||
'mauticContent' => 'campaignEvent',
|
||||
'success' => 1,
|
||||
'route' => false,
|
||||
'eventId' => $objectId,
|
||||
'eventHtml' => $this->renderView(
|
||||
$template,
|
||||
[
|
||||
'event' => $event,
|
||||
'id' => $objectId,
|
||||
'campaignId' => $campaignId,
|
||||
]
|
||||
),
|
||||
'deletedEvents' => $deletedEvents,
|
||||
];
|
||||
} else {
|
||||
$dataArray = ['success' => 0];
|
||||
}
|
||||
|
||||
return new JsonResponse($dataArray);
|
||||
}
|
||||
|
||||
public function cloneAction(Request $request, string $objectId): JsonResponse
|
||||
{
|
||||
$campaignId = $request->query->get('campaignId');
|
||||
$session = $request->getSession();
|
||||
$this->setCampaignElements($request->request);
|
||||
$modifiedEvents = $this->getModifiedEvents();
|
||||
$campaign = $this->campaignModel->getEntity($campaignId);
|
||||
|
||||
// ajax only for form fields
|
||||
if (!$request->isXmlHttpRequest()
|
||||
|| !$this->security->isGranted(
|
||||
[
|
||||
'campaign:campaigns:edit',
|
||||
'campaign:campaigns:create',
|
||||
],
|
||||
'MATCH_ONE'
|
||||
)
|
||||
) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$event = (array_key_exists($objectId, $modifiedEvents)) ? $modifiedEvents[$objectId] : null;
|
||||
|
||||
if ('POST' == $request->getMethod() && null !== $event) {
|
||||
$keyId = 'new'.hash('sha1', uniqid((string) mt_rand()));
|
||||
$event['id'] = $event['tempId'] = $keyId;
|
||||
$session->set('mautic.campaign.events.clone.storage', $event);
|
||||
|
||||
$dataArray = [
|
||||
'success' => 1,
|
||||
'mauticContent' => 'campaignEventClone',
|
||||
'route' => false,
|
||||
'eventId' => $objectId,
|
||||
'eventName' => $event['name'],
|
||||
'eventType' => $event['eventType'],
|
||||
'type' => $event['type'],
|
||||
'campaignId' => $campaign ? $campaign->getId() : $campaignId,
|
||||
'campaignName' => $campaign ? $campaign->getName() : $this->translator->trans('mautic.campaign.event.clone.new.campaign'),
|
||||
];
|
||||
} else {
|
||||
$dataArray = ['success' => 0];
|
||||
}
|
||||
|
||||
return new JsonResponse($dataArray);
|
||||
}
|
||||
|
||||
public function insertAction(Request $request): JsonResponse
|
||||
{
|
||||
$campaignId = $request->query->get('campaignId');
|
||||
$session = $request->getSession();
|
||||
$this->setCampaignElements($request->request);
|
||||
$event = $session->get('mautic.campaign.events.clone.storage');
|
||||
|
||||
if (empty($event)) {
|
||||
return new JsonResponse([
|
||||
'error' => $this->translator->trans('mautic.campaign.event.clone.request.missing'),
|
||||
], 400);
|
||||
}
|
||||
$session->remove('mautic.campaign.events.clone.storage');
|
||||
|
||||
$keyId = 'new'.hash('sha1', uniqid((string) mt_rand()));
|
||||
$event['id'] = $event['tempId'] = $keyId;
|
||||
|
||||
$modifiedEvents[$keyId] = $event;
|
||||
$this->modifiedEvents = $modifiedEvents;
|
||||
|
||||
$passThroughVars = [
|
||||
'mauticContent' => 'campaignEvent',
|
||||
'clearCloneStorage' => true,
|
||||
'success' => 1,
|
||||
'route' => false,
|
||||
];
|
||||
|
||||
$passThroughVars = array_merge($passThroughVars, $this->eventViewVars($event, $campaignId, 'insert'));
|
||||
|
||||
return new JsonResponse($passThroughVars);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $event
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function eventViewVars(
|
||||
array $event,
|
||||
string $campaignId,
|
||||
string $action,
|
||||
): array {
|
||||
// Merge default event properties with provided event data
|
||||
$event = array_merge((new Event())->convertToArray(), $event);
|
||||
|
||||
// Determine the template
|
||||
$template = $event['settings']['template'] ?? '@MauticCampaign/Event/_generic.html.twig';
|
||||
|
||||
// Prepare common template variables
|
||||
$templateVars = [
|
||||
'event' => $event,
|
||||
'id' => $event['id'],
|
||||
'campaignId' => $campaignId,
|
||||
];
|
||||
if ('edit' === $action) {
|
||||
$templateVars['update'] = true;
|
||||
}
|
||||
|
||||
// Render the template and store it in the appropriate variable
|
||||
$passThroughKey = ('edit' === $action) ? 'updateHtml' : 'eventHtml';
|
||||
$passThroughVars[$passThroughKey] = $this->renderView($template, $templateVars);
|
||||
|
||||
// Pass through event-related variables
|
||||
$passThroughVars += [
|
||||
'event' => $event,
|
||||
'eventId' => $event['id'],
|
||||
'eventType' => $event['eventType'],
|
||||
];
|
||||
|
||||
// Handle trigger mode interval
|
||||
if (Event::TRIGGER_MODE_INTERVAL === $event['triggerMode']) {
|
||||
$label = 'mautic.campaign.connection.trigger.interval.label';
|
||||
|
||||
if (Event::PATH_INACTION === $event['anchor']) {
|
||||
$label .= '_inaction';
|
||||
}
|
||||
|
||||
$passThroughVars['label'] = $this->translator->trans(
|
||||
$label,
|
||||
[
|
||||
'%number%' => $event['triggerInterval'],
|
||||
'%unit%' => $this->translator->trans(
|
||||
'mautic.campaign.event.intervalunit.'.$event['triggerIntervalUnit'],
|
||||
['%count%' => $event['triggerInterval']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Handle trigger mode date
|
||||
if (Event::TRIGGER_MODE_DATE === $event['triggerMode']) {
|
||||
$label = 'mautic.campaign.connection.trigger.date.label';
|
||||
|
||||
if (Event::PATH_INACTION === $event['anchor']) {
|
||||
$label .= '_inaction';
|
||||
}
|
||||
|
||||
$passThroughVars['label'] = $this->translator->trans(
|
||||
$label,
|
||||
[
|
||||
'%full%' => $this->dateHelper->toFull($event['triggerDate']),
|
||||
'%time%' => $this->dateHelper->toTime($event['triggerDate']),
|
||||
'%date%' => $this->dateHelper->toShort($event['triggerDate']),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $passThroughVars;
|
||||
}
|
||||
|
||||
private function setCampaignElements(ParameterBag $request): void
|
||||
{
|
||||
if ($request->get('modifiedEvents')) {
|
||||
$this->modifiedEvents = json_decode($request->get('modifiedEvents'), true);
|
||||
}
|
||||
if ($request->get('deletedEvents')) {
|
||||
$this->deletedEvents = json_decode($request->get('deletedEvents'), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
private function getModifiedEvents(): array
|
||||
{
|
||||
return $this->modifiedEvents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\CampaignBundle\Controller;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\CampaignBundle\Entity\Campaign;
|
||||
use Mautic\CampaignBundle\Entity\Event;
|
||||
use Mautic\CampaignBundle\Form\Type\CampaignImportType;
|
||||
use Mautic\CoreBundle\Controller\AbstractFormController;
|
||||
use Mautic\CoreBundle\Event\EntityImportAnalyzeEvent;
|
||||
use Mautic\CoreBundle\Event\EntityImportEvent;
|
||||
use Mautic\CoreBundle\Event\EntityImportUndoEvent;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\DateTimeHelper;
|
||||
use Mautic\CoreBundle\Helper\ImportHelper;
|
||||
use Mautic\CoreBundle\Helper\PathsHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Service\FlashBag;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\FormBundle\Entity\Action;
|
||||
use Mautic\FormBundle\Entity\Field;
|
||||
use Mautic\FormBundle\Entity\Form;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class ImportController extends AbstractFormController
|
||||
{
|
||||
// Steps of the import
|
||||
public const STEP_UPLOAD_ZIP = 1;
|
||||
|
||||
public const STEP_PROGRESS_BAR = 2;
|
||||
|
||||
public const STEP_IMPORT_FROM_ZIP = 3;
|
||||
|
||||
public function __construct(
|
||||
ManagerRegistry $doctrine,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
ModelFactory $modelFactory,
|
||||
private UserHelper $userHelper,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
Translator $translator,
|
||||
FlashBag $flashBag,
|
||||
private RequestStack $requestStack,
|
||||
CorePermissions $security,
|
||||
private LoggerInterface $logger,
|
||||
private PathsHelper $pathsHelper,
|
||||
private FormFactoryInterface $formFactory,
|
||||
) {
|
||||
parent::__construct($doctrine, $modelFactory, $userHelper, $coreParametersHelper, $dispatcher, $translator, $flashBag, $requestStack, $security);
|
||||
}
|
||||
|
||||
public function newAction(): Response
|
||||
{
|
||||
if (!$this->security->isGranted('campaign:imports:create')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$session = $this->requestStack->getSession();
|
||||
$filePath = $session->get('mautic.campaign.import.file');
|
||||
|
||||
if ($filePath && file_exists($filePath)) {
|
||||
@unlink($filePath);
|
||||
$this->logger->info("Removed leftover import file on refresh: {$filePath}");
|
||||
}
|
||||
|
||||
$this->resetImport();
|
||||
|
||||
$form = $this->formFactory->create(CampaignImportType::class, [], [
|
||||
'action' => $this->generateUrl('mautic_campaign_import_action', ['objectAction' => 'upload']),
|
||||
]);
|
||||
|
||||
return $this->delegateView([
|
||||
'viewParameters' => [
|
||||
'form' => $form->createView(),
|
||||
'mauticContent' => 'campaignImport',
|
||||
],
|
||||
'contentTemplate' => '@MauticCampaign/Import/import.html.twig',
|
||||
]);
|
||||
}
|
||||
|
||||
public function uploadAction(Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('campaign:imports:create')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$fullPath = $this->pathsHelper->getImportCampaignsPath().'/'.$this->getImportFileName();
|
||||
$fileName = $this->getImportFileName();
|
||||
|
||||
$importDir = $this->pathsHelper->getImportCampaignsPath();
|
||||
$form = $this->formFactory->create(CampaignImportType::class, [], [
|
||||
'action' => $this->generateUrl('mautic_campaign_import_action', ['objectAction' => 'upload']),
|
||||
]);
|
||||
|
||||
// Handle cancel action
|
||||
if ($this->isFormCancelled($form)) {
|
||||
$this->resetImport();
|
||||
$this->removeImportFile($fullPath);
|
||||
$this->logger->log(LogLevel::WARNING, "Import for file {$fullPath} was canceled.");
|
||||
|
||||
return $this->newAction();
|
||||
}
|
||||
|
||||
// Validate form before processing
|
||||
if (!$this->isFormValid($form)) {
|
||||
$this->logger->error('No file uploaded.');
|
||||
$form->addError(new FormError($this->translator->trans('mautic.campaign.import.incorrectfile', [], 'validators')));
|
||||
} else {
|
||||
// Retrieve uploaded file
|
||||
$fileData = $request->files->get('campaign_import')['campaignFile'] ?? null;
|
||||
|
||||
if (!$fileData) {
|
||||
$this->logger->error('No file uploaded.');
|
||||
$form->addError(new FormError($this->translator->trans('mautic.campaign.import.nofile', [], 'validators')));
|
||||
} else {
|
||||
// Set progress to 0 before import starts
|
||||
$this->requestStack->getSession()->set('mautic.campaign.import.step', self::STEP_PROGRESS_BAR);
|
||||
$this->requestStack->getSession()->set('mautic.campaign.import.progress', 0);
|
||||
$this->requestStack->getSession()->remove('mautic.campaign.import.summary');
|
||||
try {
|
||||
// Ensure the import directory exists
|
||||
(new Filesystem())->mkdir($importDir, 0755);
|
||||
|
||||
// Remove existing file if it exists
|
||||
if (file_exists($fullPath)) {
|
||||
if (!unlink($fullPath)) {
|
||||
$this->logger->error("Failed to delete existing file before new upload: {$fullPath}");
|
||||
}
|
||||
}
|
||||
|
||||
// Move uploaded file
|
||||
$fileData->move($importDir, $fileName);
|
||||
|
||||
// Update session with the new file and progress reset
|
||||
$this->requestStack->getSession()->set('mautic.campaign.import.file', $fullPath);
|
||||
$this->logger->info("File successfully uploaded: {$fullPath}");
|
||||
|
||||
return $this->redirectToRoute('mautic_campaign_import_action', ['objectAction' => 'progress']);
|
||||
} catch (FileException $e) {
|
||||
$this->logger->error('File upload failed: '.$e->getMessage());
|
||||
|
||||
$form->addError(new FormError(
|
||||
$this->translator->trans(
|
||||
str_contains($e->getMessage(), 'upload_max_filesize')
|
||||
? 'mautic.lead.import.filetoolarge'
|
||||
: 'mautic.lead.import.filenotreadable',
|
||||
[],
|
||||
'validators'
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->delegateView([
|
||||
'viewParameters' => [
|
||||
'mauticContent' => 'campaignImport',
|
||||
'form' => $form->createView(),
|
||||
],
|
||||
'contentTemplate' => '@MauticCampaign/Import/import.html.twig',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels import by removing the uploaded file.
|
||||
*/
|
||||
public function cancelAction(): Response
|
||||
{
|
||||
if (!$this->security->isGranted('campaign:imports:create')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$filePath = $this->requestStack->getSession()->get('mautic.campaign.import.file');
|
||||
|
||||
if (is_string($filePath)) {
|
||||
$this->removeImportFile($filePath);
|
||||
}
|
||||
|
||||
$this->resetImport();
|
||||
$this->addFlashMessage('mautic.campaign.notice.import.canceled', [], FlashBag::LEVEL_NOTICE);
|
||||
|
||||
return $this->redirectToRoute('mautic_campaign_import_action', ['objectAction' => 'new']);
|
||||
}
|
||||
|
||||
private function resetImport(): void
|
||||
{
|
||||
$this->requestStack->getSession()->set('mautic.campaign.import.file', null);
|
||||
$this->requestStack->getSession()->set('mautic.campaign.import.step', self::STEP_UPLOAD_ZIP);
|
||||
$this->requestStack->getSession()->set('mautic.campaign.import.progress', 0);
|
||||
$this->requestStack->getSession()->remove('mautic.campaign.import.analyzeSummary');
|
||||
}
|
||||
|
||||
private function removeImportFile(string $filepath): void
|
||||
{
|
||||
if (file_exists($filepath) && is_readable($filepath)) {
|
||||
unlink($filepath);
|
||||
|
||||
$this->logger->log(LogLevel::WARNING, "File {$filepath} was removed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates unique import directory name inside the cache dir if not stored in the session.
|
||||
* If it exists in the session, returns that one.
|
||||
*/
|
||||
private function getImportFileName(): string
|
||||
{
|
||||
$session = $this->requestStack->getSession();
|
||||
$fileName = $session->get('mautic.campaign.import.file');
|
||||
|
||||
if ($fileName && !str_contains($fileName, '/')) {
|
||||
return $fileName;
|
||||
}
|
||||
|
||||
$uniqueId = bin2hex(random_bytes(8));
|
||||
$fileName = sprintf('%s_%s.zip', (new DateTimeHelper())->toUtcString('YmdHis'), $uniqueId);
|
||||
|
||||
$session->set('mautic.campaign.import.file', $fileName);
|
||||
|
||||
return $fileName;
|
||||
}
|
||||
|
||||
public function progressAction(ImportHelper $importHelper): Response
|
||||
{
|
||||
$session = $this->requestStack->getSession();
|
||||
$session->get('mautic.campaign.import.progress', 0);
|
||||
$step = $session->get('mautic.campaign.import.step', self::STEP_PROGRESS_BAR);
|
||||
$fullPath = $session->get('mautic.campaign.import.file');
|
||||
|
||||
// If there's no valid file, show an error
|
||||
if (!$fullPath || !file_exists($fullPath)) {
|
||||
if (self::STEP_UPLOAD_ZIP !== $step) {
|
||||
$this->addFlashMessage('mautic.campaign.import.nofile', [], FlashBag::LEVEL_ERROR, 'validators');
|
||||
}
|
||||
$this->resetImport();
|
||||
|
||||
return $this->redirectToRoute('mautic_campaign_import_action', ['objectAction' => 'new']);
|
||||
}
|
||||
|
||||
if (self::STEP_PROGRESS_BAR === $step) {
|
||||
$analyzeSummary = $this->analyzeData($importHelper, $fullPath);
|
||||
|
||||
if (empty($analyzeSummary)) {
|
||||
$this->addFlashMessage('mautic.campaign.import.nofile', [], FlashBag::LEVEL_ERROR, 'validators');
|
||||
$this->removeImportFile($fullPath);
|
||||
$this->resetImport();
|
||||
|
||||
return $this->redirectToRoute('mautic_campaign_import_action', ['objectAction' => 'new']);
|
||||
}
|
||||
$session->set('mautic.campaign.import.step', self::STEP_IMPORT_FROM_ZIP);
|
||||
$session->set('mautic.campaign.import.analyzeSummary', $analyzeSummary);
|
||||
|
||||
return $this->delegateView([
|
||||
'viewParameters' => [
|
||||
'importProgress' => 50,
|
||||
'analyzeSummary' => $analyzeSummary,
|
||||
'mauticContent' => 'campaignImport',
|
||||
],
|
||||
'contentTemplate' => '@MauticCampaign/Import/progress.html.twig',
|
||||
]);
|
||||
} else {
|
||||
try {
|
||||
$fileData = $importHelper->readZipFile($fullPath);
|
||||
$userId = $this->userHelper->getUser()->getId();
|
||||
$importSummary = [];
|
||||
|
||||
$importActions = $this->requestStack->getCurrentRequest()->get('importAction', []);
|
||||
|
||||
$importHelper->recursiveRemoveEmailaddress($fileData);
|
||||
|
||||
// Loop through importActions and clean UUIDs for 'create' actions
|
||||
foreach ($fileData as &$group) {
|
||||
foreach ($importActions as $entityType => $entities) {
|
||||
if (in_array($entityType, [Event::ENTITY_NAME, Field::ENTITY_NAME, Action::ENTITY_NAME], true)) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($group[$entityType])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($entities as $entityUuid => $action) {
|
||||
if ('create' !== $action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($group[$entityType] as &$item) {
|
||||
if (isset($item['uuid']) && (int) $item['uuid'] === (int) $entityUuid) {
|
||||
if (Campaign::ENTITY_NAME == $entityType) {
|
||||
foreach ($group[Event::ENTITY_NAME] as &$eventItem) {
|
||||
$eventItem['uuid'] = '';
|
||||
}
|
||||
}
|
||||
if (Form::ENTITY_NAME == $entityType) {
|
||||
if (isset($group[Field::ENTITY_NAME])) {
|
||||
foreach ($group[Field::ENTITY_NAME] as &$fieldItem) {
|
||||
$fieldItem['uuid'] = '';
|
||||
}
|
||||
}
|
||||
if (isset($group[Action::ENTITY_NAME])) {
|
||||
foreach ($group[Action::ENTITY_NAME] as &$actionItem) {
|
||||
$actionItem['uuid'] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
$item['uuid'] = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fileData as $entity) {
|
||||
$event = new EntityImportEvent(Campaign::ENTITY_NAME, $entity, $userId);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$summary = $event->getStatus();
|
||||
if (!empty($summary)) {
|
||||
$importSummary[] = $summary;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($importSummary as $summary) {
|
||||
foreach ([EntityImportEvent::NEW, EntityImportEvent::UPDATE] as $status) {
|
||||
if (!isset($summary[$status][Campaign::ENTITY_NAME])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$campaignData = $summary[$status][Campaign::ENTITY_NAME];
|
||||
$campaignName = $campaignData['names'][0] ?? 'Unknown';
|
||||
$campaignId = $campaignData['ids'][0] ?? 0;
|
||||
|
||||
$this->addFlashMessage(
|
||||
'mautic.campaign.notice.import.finished',
|
||||
[
|
||||
'%id%' => $campaignId,
|
||||
'%name%' => htmlspecialchars($campaignName, ENT_QUOTES, 'UTF-8'),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->removeImportFile($fullPath);
|
||||
$session->set('mautic.campaign.import.summary', $importSummary);
|
||||
$this->resetImport();
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
$this->addFlashMessage('mautic.campaign.import.nofile', [], FlashBag::LEVEL_ERROR, 'validators');
|
||||
|
||||
$this->removeImportFile($fullPath);
|
||||
$importSummary = [
|
||||
EntityImportEvent::ERRORS => [$e->getMessage()],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->delegateView([
|
||||
'viewParameters' => [
|
||||
'importProgress' => 100,
|
||||
'importSummary' => $importSummary,
|
||||
'mauticContent' => 'campaignImport',
|
||||
],
|
||||
'contentTemplate' => '@MauticCampaign/Import/progress.html.twig',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string, array<string, mixed>>
|
||||
*/
|
||||
private function analyzeData(ImportHelper $importHelper, string $fullPath): array
|
||||
{
|
||||
try {
|
||||
$fileData = $importHelper->readZipFile($fullPath);
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
$this->removeImportFile($fullPath);
|
||||
|
||||
return [
|
||||
[
|
||||
'errors' => [
|
||||
'messages' => [$e->getMessage()],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$allData = [];
|
||||
foreach ($fileData as $entityData) {
|
||||
$mergedSummary = [];
|
||||
foreach ($entityData as $key => $data) {
|
||||
if (empty($data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$event = new EntityImportAnalyzeEvent($key, $data);
|
||||
$this->dispatcher->dispatch($event);
|
||||
$summary = $event->getSummary();
|
||||
|
||||
foreach ($summary as $status => $entities) {
|
||||
if ('errors' === $status) {
|
||||
// Accumulate errors into a flat array
|
||||
$mergedSummary['errors'] = array_merge(
|
||||
$mergedSummary['errors'] ?? [],
|
||||
is_array($entities) ? $entities : [$entities]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
foreach ($entities as $entityName => $info) {
|
||||
if (!isset($mergedSummary[$status][$entityName])) {
|
||||
$mergedSummary[$status][$entityName] = [
|
||||
'names' => [],
|
||||
'uuids' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$mergedSummary[$status][$entityName]['names'] = array_merge(
|
||||
$mergedSummary[$status][$entityName]['names'],
|
||||
$info['names'] ?? []
|
||||
);
|
||||
$mergedSummary[$status][$entityName]['uuids'] = array_merge(
|
||||
$mergedSummary[$status][$entityName]['uuids'],
|
||||
$info['uuids'] ?? []
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($mergedSummary)) {
|
||||
$allData[] = $mergedSummary;
|
||||
}
|
||||
}
|
||||
|
||||
return $allData;
|
||||
}
|
||||
|
||||
public function undoAction(): JsonResponse
|
||||
{
|
||||
if (!$this->security->isGranted('campaign:imports:delete')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$session = $this->requestStack->getSession();
|
||||
$importSummaries = $session->get('mautic.campaign.import.summary', []);
|
||||
|
||||
$hasUndoData = false;
|
||||
|
||||
foreach ($importSummaries as $summary) {
|
||||
$updates = $summary[EntityImportEvent::UPDATE] ?? [];
|
||||
$newItems = $summary[EntityImportEvent::NEW] ?? [];
|
||||
|
||||
// Only trigger undo if there are no updates and we have new items
|
||||
if (empty($updates) && !empty($newItems)) {
|
||||
foreach ($newItems as $entityType => $data) {
|
||||
if (!empty($data['ids'])) {
|
||||
$undoEvent = new EntityImportUndoEvent($entityType, $data);
|
||||
$this->dispatcher->dispatch($undoEvent);
|
||||
$hasUndoData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasUndoData) {
|
||||
$this->logger->info('Undo import triggered for Campaign.');
|
||||
$this->addFlashMessage('mautic.campaign.notice.import.undo');
|
||||
} else {
|
||||
$this->addFlashMessage('mautic.campaign.notice.import.undo_no_data');
|
||||
}
|
||||
|
||||
return new JsonResponse(['flashes' => $this->getFlashContent()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\CampaignBundle\Controller;
|
||||
|
||||
use Mautic\CampaignBundle\Form\Type\CampaignLeadSourceType;
|
||||
use Mautic\CampaignBundle\Model\CampaignModel;
|
||||
use Mautic\CoreBundle\Controller\FormController as CommonFormController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SourceController extends CommonFormController
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $supportedSourceTypes = ['lists', 'forms'];
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $modifiedSources = [];
|
||||
|
||||
/**
|
||||
* @param int $objectId
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function newAction(Request $request, $objectId = 0)
|
||||
{
|
||||
$success = 0;
|
||||
$valid = $cancelled = false;
|
||||
$this->setCampaignElements($request->request);
|
||||
if ('1' === $request->request->get('submit')) {
|
||||
$source = $request->request->all()['campaign_leadsource'] ?? [];
|
||||
$sourceType = $source['sourceType'];
|
||||
} else {
|
||||
$sourceType = $request->query->get('sourceType');
|
||||
$source = [
|
||||
'sourceType' => $sourceType,
|
||||
];
|
||||
}
|
||||
|
||||
// set the sourceType key for sources
|
||||
if (!in_array($sourceType, $this->supportedSourceTypes)) {
|
||||
return $this->modalAccessDenied();
|
||||
}
|
||||
|
||||
// ajax only for form fields
|
||||
if (!$request->isXmlHttpRequest()
|
||||
|| !$this->security->isGranted(
|
||||
[
|
||||
'campaign:campaigns:edit',
|
||||
'campaign:campaigns:create',
|
||||
],
|
||||
'MATCH_ONE'
|
||||
)
|
||||
) {
|
||||
return $this->modalAccessDenied();
|
||||
}
|
||||
|
||||
$campaignModel = $this->getModel('campaign');
|
||||
\assert($campaignModel instanceof CampaignModel);
|
||||
$sourceList = $campaignModel->getSourceLists($sourceType);
|
||||
$form = $this->formFactory->create(
|
||||
CampaignLeadSourceType::class,
|
||||
$source,
|
||||
[
|
||||
'action' => $this->generateUrl('mautic_campaignsource_action', ['objectAction' => 'new', 'objectId' => $objectId]),
|
||||
'source_choices' => $sourceList,
|
||||
]
|
||||
);
|
||||
|
||||
$modifiedSources = $this->modifiedSources;
|
||||
// Check for a submitted form and process it
|
||||
if ('1' === $request->request->get('submit')) {
|
||||
if (!$cancelled = $this->isFormCancelled($form)) {
|
||||
if ($valid = $this->isFormValid($form)) {
|
||||
$success = 1;
|
||||
$modifiedSources[$sourceType] = array_flip($form[$sourceType]->getData());
|
||||
} else {
|
||||
$success = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$passthroughVars = [
|
||||
'mauticContent' => 'campaignSource',
|
||||
'success' => $success,
|
||||
'route' => false,
|
||||
];
|
||||
|
||||
if (1 === $success && !empty($modifiedSources)) {
|
||||
$passthroughVars['modifiedSources'] = $modifiedSources;
|
||||
}
|
||||
|
||||
if ($cancelled || $valid) {
|
||||
if ($valid) {
|
||||
$passthroughVars['sourceHtml'] = $this->renderView(
|
||||
'@MauticCampaign/Source/_index.html.twig',
|
||||
[
|
||||
'sourceType' => $sourceType,
|
||||
'campaignId' => $objectId,
|
||||
'names' => implode(', ', array_intersect_key($sourceList, $modifiedSources[$sourceType])),
|
||||
]
|
||||
);
|
||||
$passthroughVars['sourceType'] = $sourceType;
|
||||
}
|
||||
|
||||
// just close the modal
|
||||
$passthroughVars['closeModal'] = 1;
|
||||
|
||||
return new JsonResponse($passthroughVars);
|
||||
} else {
|
||||
$viewParams = [
|
||||
'sourceType' => $sourceType,
|
||||
'form' => $form->createView(),
|
||||
];
|
||||
|
||||
return $this->ajaxAction(
|
||||
$request,
|
||||
[
|
||||
'contentTemplate' => '@MauticCampaign/Source/form.html.twig',
|
||||
'viewParameters' => $viewParams,
|
||||
'passthroughVars' => $passthroughVars,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public function editAction(Request $request, $objectId)
|
||||
{
|
||||
$this->setCampaignElements($request->request);
|
||||
$modifiedSources = $this->modifiedSources;
|
||||
|
||||
if ('1' === $request->request->get('submit')) {
|
||||
$source = $request->request->all()['campaign_leadsource'] ?? [];
|
||||
$sourceType = $source['sourceType'];
|
||||
} else {
|
||||
$sourceType = $request->query->get('sourceType');
|
||||
$source = [
|
||||
'sourceType' => $sourceType,
|
||||
$sourceType => array_flip($modifiedSources[$sourceType]),
|
||||
];
|
||||
}
|
||||
|
||||
$success = 0;
|
||||
$valid = $cancelled = false;
|
||||
|
||||
if (!in_array($sourceType, $this->supportedSourceTypes)) {
|
||||
return $this->modalAccessDenied();
|
||||
}
|
||||
|
||||
// ajax only for form fields
|
||||
if (!$request->isXmlHttpRequest()
|
||||
|| !$this->security->isGranted(
|
||||
[
|
||||
'campaign:campaigns:edit',
|
||||
'campaign:campaigns:create',
|
||||
],
|
||||
'MATCH_ONE'
|
||||
)
|
||||
) {
|
||||
return $this->modalAccessDenied();
|
||||
}
|
||||
|
||||
$campaignModel = $this->getModel('campaign');
|
||||
\assert($campaignModel instanceof CampaignModel);
|
||||
$sourceList = $campaignModel->getSourceLists($sourceType);
|
||||
$form = $this->formFactory->create(
|
||||
CampaignLeadSourceType::class,
|
||||
$source,
|
||||
[
|
||||
'action' => $this->generateUrl('mautic_campaignsource_action', ['objectAction' => 'edit', 'objectId' => $objectId]),
|
||||
'source_choices' => $sourceList,
|
||||
]
|
||||
);
|
||||
|
||||
// Check for a submitted form and process it
|
||||
if ('1' === $request->request->get('submit')) {
|
||||
if (!$cancelled = $this->isFormCancelled($form)) {
|
||||
if ($valid = $this->isFormValid($form)) {
|
||||
$success = 1;
|
||||
|
||||
// save the properties to session
|
||||
$modifiedSources[$sourceType] = array_flip($form[$sourceType]->getData());
|
||||
} else {
|
||||
$success = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$passthroughVars = [
|
||||
'mauticContent' => 'campaignSource',
|
||||
'success' => $success,
|
||||
'route' => false,
|
||||
];
|
||||
|
||||
if (1 === $success && !empty($modifiedSources)) {
|
||||
$passthroughVars['modifiedSources'] = $modifiedSources;
|
||||
}
|
||||
|
||||
if ($cancelled || $valid) {
|
||||
if ($valid) {
|
||||
$passthroughVars['updateHtml'] = $this->renderView(
|
||||
'@MauticCampaign/Source/_index.html.twig',
|
||||
[
|
||||
'sourceType' => $sourceType,
|
||||
'campaignId' => $objectId,
|
||||
'names' => implode(', ', array_intersect_key($sourceList, $modifiedSources[$sourceType])),
|
||||
'update' => true,
|
||||
]
|
||||
);
|
||||
$passthroughVars['sourceType'] = $sourceType;
|
||||
}
|
||||
|
||||
// just close the modal
|
||||
$passthroughVars['closeModal'] = 1;
|
||||
|
||||
return new JsonResponse($passthroughVars);
|
||||
} else {
|
||||
$viewParams = [
|
||||
'sourceType' => $sourceType,
|
||||
'form' => $form->createView(),
|
||||
];
|
||||
|
||||
return $this->ajaxAction(
|
||||
$request,
|
||||
[
|
||||
'contentTemplate' => '@MauticCampaign/Source/form.html.twig',
|
||||
'viewParameters' => $viewParams,
|
||||
'passthroughVars' => $passthroughVars,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the entity.
|
||||
*
|
||||
* @return JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function deleteAction(Request $request, $objectId)
|
||||
{
|
||||
$this->setCampaignElements($request->request);
|
||||
$modifiedSources = $this->modifiedSources;
|
||||
$sourceType = $request->get('sourceType');
|
||||
|
||||
// ajax only for form fields
|
||||
if (!$request->isXmlHttpRequest()
|
||||
|| !$this->security->isGranted(
|
||||
[
|
||||
'campaign:campaigns:edit',
|
||||
'campaign:campaigns:create',
|
||||
],
|
||||
'MATCH_ONE'
|
||||
)
|
||||
) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
if ('POST' == $request->getMethod()) {
|
||||
// Add the field to the delete list
|
||||
if (isset($modifiedSources[$sourceType])) {
|
||||
unset($modifiedSources[$sourceType]);
|
||||
}
|
||||
|
||||
$dataArray = [
|
||||
'mauticContent' => 'campaignSource',
|
||||
'success' => 1,
|
||||
'route' => false,
|
||||
'sourceType' => $sourceType,
|
||||
'deleted' => 1,
|
||||
'modifiedSources' => $modifiedSources,
|
||||
];
|
||||
} else {
|
||||
$dataArray = ['success' => 0];
|
||||
}
|
||||
|
||||
return new JsonResponse($dataArray);
|
||||
}
|
||||
|
||||
private function setCampaignElements(ParameterBag $request): void
|
||||
{
|
||||
if ($request->get('modifiedSources')) {
|
||||
$this->modifiedSources = json_decode($request->get('modifiedSources'), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user