Spaces:
No application file
No application file
namespace Mautic\FormBundle\Controller; | |
use Doctrine\Persistence\ManagerRegistry; | |
use Mautic\CoreBundle\Controller\FormController as CommonFormController; | |
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\ThemeHelper; | |
use Mautic\CoreBundle\Helper\UserHelper; | |
use Mautic\CoreBundle\Model\AuditLogModel; | |
use Mautic\CoreBundle\Security\Permissions\CorePermissions; | |
use Mautic\CoreBundle\Service\FlashBag; | |
use Mautic\CoreBundle\Translation\Translator; | |
use Mautic\CoreBundle\Twig\Helper\AnalyticsHelper; | |
use Mautic\CoreBundle\Twig\Helper\AssetsHelper; | |
use Mautic\CoreBundle\Twig\Helper\SlotsHelper; | |
use Mautic\FormBundle\Collector\AlreadyMappedFieldCollectorInterface; | |
use Mautic\FormBundle\Collector\MappedObjectCollector; | |
use Mautic\FormBundle\Entity\Field; | |
use Mautic\FormBundle\Entity\Form; | |
use Mautic\FormBundle\Exception\ValidationException; | |
use Mautic\FormBundle\Helper\FormFieldHelper; | |
use Mautic\FormBundle\Model\FormModel; | |
use Mautic\FormBundle\Model\SubmissionModel; | |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
use Symfony\Component\Form\FormError; | |
use Symfony\Component\Form\FormFactoryInterface; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
use Symfony\Component\HttpFoundation\Response; | |
class FormController extends CommonFormController | |
{ | |
public function __construct( | |
FormFactoryInterface $formFactory, | |
FormFieldHelper $fieldHelper, | |
private AlreadyMappedFieldCollectorInterface $alreadyMappedFieldCollector, | |
private MappedObjectCollector $mappedObjectCollector, | |
ManagerRegistry $doctrine, | |
MauticFactory $factory, | |
ModelFactory $modelFactory, | |
UserHelper $userHelper, | |
CoreParametersHelper $coreParametersHelper, | |
EventDispatcherInterface $dispatcher, | |
Translator $translator, | |
FlashBag $flashBag, | |
RequestStack $requestStack, | |
CorePermissions $security | |
) { | |
parent::__construct($formFactory, $fieldHelper, $doctrine, $factory, $modelFactory, $userHelper, $coreParametersHelper, $dispatcher, $translator, $flashBag, $requestStack, $security); | |
} | |
/** | |
* @param int $page | |
* | |
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response | |
*/ | |
public function indexAction(Request $request, PageHelperFactoryInterface $pageHelperFactory, $page = 1) | |
{ | |
// set some permissions | |
$permissions = $this->security->isGranted( | |
[ | |
'form:forms:viewown', | |
'form:forms:viewother', | |
'form:forms:create', | |
'form:forms:editown', | |
'form:forms:editother', | |
'form:forms:deleteown', | |
'form:forms:deleteother', | |
'form:forms:publishown', | |
'form:forms:publishother', | |
], | |
'RETURN_ARRAY' | |
); | |
if (!$permissions['form:forms:viewown'] && !$permissions['form:forms:viewother']) { | |
return $this->accessDenied(); | |
} | |
$this->setListFilters(); | |
$session = $request->getSession(); | |
$pageHelper = $pageHelperFactory->make('mautic.form', $page); | |
$limit = $pageHelper->getLimit(); | |
$start = $pageHelper->getStart(); | |
$search = $request->get('search', $session->get('mautic.form.filter', '')); | |
$filter = ['string' => $search, 'force' => []]; | |
$session->set('mautic.form.filter', $search); | |
if (!$permissions['form:forms:viewother']) { | |
$filter['force'][] = ['column' => 'f.createdBy', 'expr' => 'eq', 'value' => $this->user->getId()]; | |
} | |
$orderBy = $session->get('mautic.form.orderby', 'f.dateModified'); | |
$orderByDir = $session->get('mautic.form.orderbydir', $this->getDefaultOrderDirection()); | |
$forms = $this->getModel('form.form')->getEntities( | |
[ | |
'start' => $start, | |
'limit' => $limit, | |
'filter' => $filter, | |
'orderBy' => $orderBy, | |
'orderByDir' => $orderByDir, | |
] | |
); | |
$count = count($forms); | |
if ($count && $count < ($start + 1)) { | |
// the number of entities are now less then the current page so redirect to the last page | |
$lastPage = $pageHelper->countPage($count); | |
$pageHelper->rememberPage($lastPage); | |
$returnUrl = $this->generateUrl('mautic_form_index', ['page' => $lastPage]); | |
return $this->postActionRedirect( | |
[ | |
'returnUrl' => $returnUrl, | |
'viewParameters' => ['page' => $lastPage], | |
'contentTemplate' => 'Mautic\FormBundle\Controller\FormController::indexAction', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
], | |
] | |
); | |
} | |
$pageHelper->rememberPage($page); | |
return $this->delegateView( | |
[ | |
'viewParameters' => [ | |
'searchValue' => $search, | |
'items' => $forms, | |
'totalItems' => $count, | |
'page' => $page, | |
'limit' => $limit, | |
'permissions' => $permissions, | |
'security' => $this->security, | |
'tmpl' => $request->get('tmpl', 'index'), | |
], | |
'contentTemplate' => '@MauticForm/Form/list.html.twig', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
'route' => $this->generateUrl('mautic_form_index', ['page' => $page]), | |
], | |
] | |
); | |
} | |
/** | |
* Loads a specific form into the detailed panel. | |
* | |
* @param int $objectId | |
* | |
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response | |
*/ | |
public function viewAction(Request $request, $objectId) | |
{ | |
/** @var FormModel $model */ | |
$model = $this->getModel('form'); | |
$activeForm = $model->getEntity($objectId); | |
// set the page we came from | |
$page = $request->getSession()->get('mautic.form.page', 1); | |
if (null === $activeForm) { | |
// set the return URL | |
$returnUrl = $this->generateUrl('mautic_form_index', ['page' => $page]); | |
return $this->postActionRedirect( | |
[ | |
'returnUrl' => $returnUrl, | |
'viewParameters' => ['page' => $page], | |
'contentTemplate' => 'Mautic\FormBundle\Controller\FormController::indexAction', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
], | |
'flashes' => [ | |
[ | |
'type' => 'error', | |
'msg' => 'mautic.form.error.notfound', | |
'msgVars' => ['%id%' => $objectId], | |
], | |
], | |
] | |
); | |
} elseif (!$this->security->hasEntityAccess( | |
'form:forms:viewown', | |
'form:forms:viewother', | |
$activeForm->getCreatedBy() | |
) | |
) { | |
return $this->accessDenied(); | |
} | |
$permissions = $this->security->isGranted( | |
[ | |
'form:forms:viewown', | |
'form:forms:viewother', | |
'form:forms:create', | |
'form:forms:editown', | |
'form:forms:editother', | |
'form:forms:deleteown', | |
'form:forms:deleteother', | |
'form:forms:publishown', | |
'form:forms:publishother', | |
], | |
'RETURN_ARRAY' | |
); | |
// Audit Log | |
$auditLogModel = $this->getModel('core.auditlog'); | |
\assert($auditLogModel instanceof AuditLogModel); | |
$logs = $auditLogModel->getLogForObject('form', $objectId, $activeForm->getDateAdded()); | |
// Init the date range filter form | |
$dateRangeValues = $request->get('daterange', []); | |
$action = $this->generateUrl('mautic_form_action', ['objectAction' => 'view', 'objectId' => $objectId]); | |
$dateRangeForm = $this->formFactory->create(DateRangeType::class, $dateRangeValues, ['action' => $action]); | |
$formSubmissionModel = $this->getModel('form.submission'); | |
\assert($formSubmissionModel instanceof SubmissionModel); | |
// Submission stats per time period | |
$timeStats = $formSubmissionModel->getSubmissionsLineChartData( | |
null, | |
new \DateTime($dateRangeForm->get('date_from')->getData()), | |
new \DateTime($dateRangeForm->get('date_to')->getData()), | |
null, | |
['form_id' => $objectId] | |
); | |
// Only show actions and fields that still exist | |
$customComponents = $model->getCustomComponents(); | |
$activeFormActions = []; | |
foreach ($activeForm->getActions() as $formAction) { | |
if (!isset($customComponents['actions'][$formAction->getType()])) { | |
continue; | |
} | |
$type = explode('.', $formAction->getType()); | |
$activeFormActions[$type[0]][] = $formAction; | |
} | |
$activeFormFields = []; | |
$availableFields = array_flip($this->fieldHelper->getChoiceList($customComponents['fields'])); | |
foreach ($activeForm->getFields() as $field) { | |
if (!isset($availableFields[$field->getType()])) { | |
continue; | |
} | |
$activeFormFields[] = $field; | |
} | |
$submissionCounts = $formSubmissionModel->getRepository()->getSubmissionCounts($activeForm); | |
return $this->delegateView( | |
[ | |
'viewParameters' => [ | |
'activeForm' => $activeForm, | |
'submissionCounts' => $submissionCounts, | |
'page' => $page, | |
'logs' => $logs, | |
'permissions' => $permissions, | |
'stats' => [ | |
'submissionsInTime' => $timeStats, | |
], | |
'dateRangeForm' => $dateRangeForm->createView(), | |
'activeFormActions' => $activeFormActions, | |
'activeFormFields' => $activeFormFields, | |
'formScript' => htmlspecialchars($model->getFormScript($activeForm), ENT_QUOTES, 'UTF-8'), | |
'formContent' => htmlspecialchars($model->getContent($activeForm, false), ENT_QUOTES, 'UTF-8'), | |
'availableActions' => $customComponents['actions'], | |
], | |
'contentTemplate' => '@MauticForm/Form/details.html.twig', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
'route' => $action, | |
], | |
] | |
); | |
} | |
/** | |
* Generates new form and processes post data. | |
* | |
* @return array|\Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response | |
* | |
* @throws \Exception | |
*/ | |
public function newAction(Request $request) | |
{ | |
/** @var FormModel $model */ | |
$model = $this->getModel('form'); | |
$entity = $model->getEntity(); | |
$session = $request->getSession(); | |
if (!$this->security->isGranted('form:forms:create')) { | |
return $this->accessDenied(); | |
} | |
// set the page we came from | |
$page = $request->getSession()->get('mautic.form.page', 1); | |
$mauticform = $request->request->get('mauticform') ?? []; | |
$sessionId = $mauticform['sessionId'] ?? 'mautic_'.sha1(uniqid(mt_rand(), true)); | |
// set added/updated fields | |
$modifiedFields = $session->get('mautic.form.'.$sessionId.'.fields.modified', []); | |
$deletedFields = $session->get('mautic.form.'.$sessionId.'.fields.deleted', []); | |
// set added/updated actions | |
$modifiedActions = $session->get('mautic.form.'.$sessionId.'.actions.modified', []); | |
$deletedActions = $session->get('mautic.form.'.$sessionId.'.actions.deleted', []); | |
$action = $this->generateUrl('mautic_form_action', ['objectAction' => 'new']); | |
$form = $model->createForm($entity, $this->formFactory, $action); | |
// /Check for a submitted form and process it | |
if ('POST' == $request->getMethod()) { | |
$valid = false; | |
if (!$cancelled = $this->isFormCancelled($form)) { | |
if ($valid = $this->isFormValid($form)) { | |
// only save fields that are not to be deleted | |
$fields = array_diff_key($modifiedFields, array_flip($deletedFields)); | |
// make sure that at least one field is selected | |
if (empty($fields)) { | |
// set the error | |
$form->addError( | |
new FormError( | |
$this->translator->trans('mautic.form.form.fields.notempty', [], 'validators') | |
) | |
); | |
$valid = false; | |
} else { | |
$model->setFields($entity, $fields); | |
try { | |
// Set alias to prevent SQL errors | |
$alias = $model->cleanAlias($entity->getName(), '', 10); | |
$entity->setAlias($alias); | |
// Set timestamps | |
$model->setTimestamps($entity, true, false); | |
// Save the form first and new actions so that new fields are available to actions. | |
// Using the repository function to not trigger the listeners twice. | |
$model->getRepository()->saveEntity($entity); | |
// Only save actions that are not to be deleted | |
$actions = array_diff_key($modifiedActions, array_flip($deletedActions)); | |
// Set and persist actions | |
$model->setActions($entity, $actions); | |
// Save and trigger listeners | |
$model->saveEntity($entity, $this->getFormButton($form, ['buttons', 'save'])->isClicked()); | |
$this->addFlashMessage( | |
'mautic.core.notice.created', | |
[ | |
'%name%' => $entity->getName(), | |
'%menu_link%' => 'mautic_form_index', | |
'%url%' => $this->generateUrl( | |
'mautic_form_action', | |
[ | |
'objectAction' => 'edit', | |
'objectId' => $entity->getId(), | |
] | |
), | |
] | |
); | |
if ($this->getFormButton($form, ['buttons', 'save'])->isClicked()) { | |
$viewParameters = [ | |
'objectAction' => 'view', | |
'objectId' => $entity->getId(), | |
]; | |
$returnUrl = $this->generateUrl('mautic_form_action', $viewParameters); | |
$template = 'Mautic\FormBundle\Controller\FormController::viewAction'; | |
} else { | |
// return edit view so that all the session stuff is loaded | |
return $this->editAction($request, $entity->getId(), true); | |
} | |
} catch (ValidationException $ex) { | |
$form->addError( | |
new FormError( | |
$ex->getMessage() | |
) | |
); | |
$valid = false; | |
} catch (\Exception $e) { | |
$form['name']->addError( | |
new FormError($this->translator->trans('mautic.form.schema.failed', [], 'validators')) | |
); | |
$valid = false; | |
if ('dev' == $this->getParameter('kernel.environment')) { | |
throw $e; | |
} | |
} | |
} | |
} | |
} else { | |
$viewParameters = ['page' => $page]; | |
$returnUrl = $this->generateUrl('mautic_form_index', $viewParameters); | |
$template = 'Mautic\FormBundle\Controller\FormController::indexAction'; | |
} | |
if ($cancelled || ($valid && $this->getFormButton($form, ['buttons', 'save'])->isClicked())) { | |
// clear temporary fields | |
$this->clearSessionComponents($request, $sessionId); | |
return $this->postActionRedirect( | |
[ | |
'returnUrl' => $returnUrl, | |
'viewParameters' => $viewParameters, | |
'contentTemplate' => $template, | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
], | |
] | |
); | |
} | |
} else { | |
// clear out existing fields in case the form was refreshed, browser closed, etc | |
$this->clearSessionComponents($request, $sessionId); | |
$modifiedFields = $modifiedActions = $deletedActions = $deletedFields = []; | |
$form->get('sessionId')->setData($sessionId); | |
// add a submit button | |
$keyId = 'new'.hash('sha1', uniqid(mt_rand())); | |
$field = new Field(); | |
$modifiedFields[$keyId] = $field->convertToArray(); | |
$modifiedFields[$keyId]['label'] = $this->translator->trans('mautic.core.form.submit'); | |
$modifiedFields[$keyId]['alias'] = 'submit'; | |
$modifiedFields[$keyId]['showLabel'] = 1; | |
$modifiedFields[$keyId]['type'] = 'button'; | |
$modifiedFields[$keyId]['id'] = $keyId; | |
$modifiedFields[$keyId]['inputAttributes'] = 'class="btn btn-default"'; | |
$modifiedFields[$keyId]['formId'] = $sessionId; | |
unset($modifiedFields[$keyId]['form']); | |
$session->set('mautic.form.'.$sessionId.'.fields.modified', $modifiedFields); | |
} | |
// fire the form builder event | |
$customComponents = $model->getCustomComponents(); | |
return $this->delegateView( | |
[ | |
'viewParameters' => [ | |
'fields' => $this->fieldHelper->getChoiceList($customComponents['fields']), | |
'formFields' => $modifiedFields, | |
'mappedFields' => $this->mappedObjectCollector->buildCollection(...$entity->getMappedFieldObjects()), | |
'deletedFields' => $deletedFields, | |
'viewOnlyFields' => $customComponents['viewOnlyFields'], | |
'actions' => $customComponents['choices'], | |
'actionSettings' => $customComponents['actions'], | |
'formActions' => $modifiedActions, | |
'deletedActions' => $deletedActions, | |
'tmpl' => $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index', | |
'activeForm' => $entity, | |
'form' => $form->createView(), | |
'inBuilder' => true, | |
], | |
'contentTemplate' => '@MauticForm/Builder/index.html.twig', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
'route' => $this->generateUrl( | |
'mautic_form_action', | |
[ | |
'objectAction' => (!empty($valid) ? 'edit' : 'new'), // valid means a new form was applied | |
'objectId' => $entity->getId(), | |
] | |
), | |
], | |
] | |
); | |
} | |
/** | |
* Generates edit form and processes post data. | |
* | |
* @param int|Form $objectId | |
* @param bool $ignorePost | |
* @param bool $forceTypeSelection | |
* | |
* @return \Symfony\Component\HttpFoundation\JsonResponse|Response | |
*/ | |
public function editAction(Request $request, $objectId, $ignorePost = false, $forceTypeSelection = false) | |
{ | |
/** @var FormModel $model */ | |
$model = $this->getModel('form'); | |
$formData = $request->request->get('mauticform'); | |
$sessionId = $formData['sessionId'] ?? null; | |
$customComponents = $model->getCustomComponents(); | |
$modifiedFields = []; | |
$deletedFields = []; | |
$modifiedActions = []; | |
$deletedActions = []; | |
if ($objectId instanceof Form) { | |
$entity = $objectId; | |
$objectId = 'mautic_'.sha1(uniqid(mt_rand(), true)); | |
} else { | |
$entity = $model->getEntity($objectId); | |
// Process submit of cloned form | |
if (null == $entity && $objectId == $sessionId) { | |
$entity = $model->getEntity(); | |
} | |
} | |
$session = $request->getSession(); | |
$cleanSlate = true; | |
// set the page we came from | |
$page = $request->getSession()->get('mautic.form.page', 1); | |
// set the return URL | |
$returnUrl = $this->generateUrl('mautic_form_index', ['page' => $page]); | |
$postActionVars = [ | |
'returnUrl' => $returnUrl, | |
'viewParameters' => ['page' => $page], | |
'contentTemplate' => 'Mautic\FormBundle\Controller\FormController::indexAction', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
], | |
]; | |
// form not found | |
if (null === $entity) { | |
return $this->postActionRedirect( | |
array_merge( | |
$postActionVars, | |
[ | |
'flashes' => [ | |
[ | |
'type' => 'error', | |
'msg' => 'mautic.form.error.notfound', | |
'msgVars' => ['%id%' => $objectId], | |
], | |
], | |
] | |
) | |
); | |
} elseif (!$this->security->hasEntityAccess( | |
'form:forms:editown', | |
'form:forms:editother', | |
$entity->getCreatedBy() | |
) | |
) { | |
return $this->accessDenied(); | |
} elseif ($model->isLocked($entity)) { | |
// deny access if the entity is locked | |
return $this->isLocked($postActionVars, $entity, 'form.form'); | |
} | |
$action = $this->generateUrl('mautic_form_action', ['objectAction' => 'edit', 'objectId' => $objectId]); | |
$form = $model->createForm($entity, $this->formFactory, $action); | |
// /Check for a submitted form and process it | |
if (!$ignorePost && 'POST' == $request->getMethod()) { | |
$valid = false; | |
if (!$cancelled = $this->isFormCancelled($form)) { | |
// set added/updated fields | |
$modifiedFields = $session->get('mautic.form.'.$objectId.'.fields.modified', []); | |
$deletedFields = $session->get('mautic.form.'.$objectId.'.fields.deleted', []); | |
$fields = array_diff_key($modifiedFields, array_flip($deletedFields)); | |
// set added/updated actions | |
$modifiedActions = $session->get('mautic.form.'.$objectId.'.actions.modified', []); | |
$deletedActions = $session->get('mautic.form.'.$objectId.'.actions.deleted', []); | |
$actions = array_diff_key($modifiedActions, array_flip($deletedActions)); | |
if ($valid = $this->isFormValid($form)) { | |
// make sure that at least one field is selected | |
if (empty($fields)) { | |
// set the error | |
$form->addError( | |
new FormError( | |
$this->translator->trans('mautic.form.form.fields.notempty', [], 'validators') | |
) | |
); | |
$valid = false; | |
} else { | |
$model->setFields($entity, $fields); | |
$model->deleteFields($entity, $deletedFields); | |
$alias = $entity->getAlias(); | |
if (empty($alias)) { | |
$alias = $model->cleanAlias($entity->getName(), '', 10); | |
$entity->setAlias($alias); | |
} | |
if (!$entity->getId()) { | |
// Set timestamps because this is a new clone | |
$model->setTimestamps($entity, true, false); | |
} | |
// save the form first so that new fields are available to actions | |
// use the repository method to not trigger listeners twice | |
try { | |
$model->getRepository()->saveEntity($entity); | |
// Ensure actions are compatible with form type | |
if (!$entity->isStandalone()) { | |
foreach ($actions as $actionId => $action) { | |
if (empty($customComponents['actions'][$action['type']]['allowCampaignForm'])) { | |
unset($actions[$actionId]); | |
$deletedActions[] = $actionId; | |
} | |
} | |
} | |
if (count($actions)) { | |
// Now set and persist the actions | |
$model->setActions($entity, $actions); | |
} | |
// Delete deleted actions | |
$model->deleteActions($entity, $deletedActions); | |
// Persist and execute listeners | |
$model->saveEntity($entity, $this->getFormButton($form, ['buttons', 'save'])->isClicked()); | |
// Reset objectId to entity ID (can be session ID in case of cloned entity) | |
$objectId = $entity->getId(); | |
$this->addFlashMessage( | |
'mautic.core.notice.updated', | |
[ | |
'%name%' => $entity->getName(), | |
'%menu_link%' => 'mautic_form_index', | |
'%url%' => $this->generateUrl( | |
'mautic_form_action', | |
[ | |
'objectAction' => 'edit', | |
'objectId' => $entity->getId(), | |
] | |
), | |
] | |
); | |
if ($this->getFormButton($form, ['buttons', 'save'])->isClicked()) { | |
$viewParameters = [ | |
'objectAction' => 'view', | |
'objectId' => $entity->getId(), | |
]; | |
$returnUrl = $this->generateUrl('mautic_form_action', $viewParameters); | |
$template = 'Mautic\FormBundle\Controller\FormController::viewAction'; | |
} | |
} catch (ValidationException $ex) { | |
$form->addError( | |
new FormError( | |
$ex->getMessage() | |
) | |
); | |
$valid = false; | |
} | |
} | |
} | |
} else { | |
// unlock the entity | |
$model->unlockEntity($entity); | |
$viewParameters = ['page' => $page]; | |
$returnUrl = $this->generateUrl('mautic_form_index', $viewParameters); | |
$template = 'Mautic\FormBundle\Controller\FormController::indexAction'; | |
} | |
if ($cancelled || ($valid && $this->getFormButton($form, ['buttons', 'save'])->isClicked())) { | |
// remove fields from session | |
$this->clearSessionComponents($request, $objectId); | |
// Clear session items in case columns changed | |
$session->remove('mautic.formresult.'.$entity->getId().'.orderby'); | |
$session->remove('mautic.formresult.'.$entity->getId().'.orderbydir'); | |
$session->remove('mautic.formresult.'.$entity->getId().'.filters'); | |
return $this->postActionRedirect( | |
array_merge( | |
$postActionVars, | |
[ | |
'returnUrl' => $returnUrl, | |
'viewParameters' => $viewParameters, | |
'contentTemplate' => $template, | |
] | |
) | |
); | |
} elseif ($valid && $form->get('buttons')->get('apply')->isClicked()) { | |
// Rebuild everything to include new ids | |
$cleanSlate = true; | |
$reorder = true; | |
if ($valid) { | |
// Rebuild the form with new action so that apply doesn't keep creating a clone | |
$action = $this->generateUrl('mautic_form_action', ['objectAction' => 'edit', 'objectId' => $entity->getId()]); | |
$form = $model->createForm($entity, $this->formFactory, $action); | |
} | |
} | |
} else { | |
$cleanSlate = true; | |
// lock the entity | |
$model->lockEntity($entity); | |
} | |
if (!$form->isSubmitted()) { | |
$form->get('sessionId')->setData($objectId); | |
} | |
// Get field and action settings | |
$availableFields = $this->fieldHelper->getChoiceList($customComponents['fields']); | |
if ($cleanSlate) { | |
// clean slate | |
$this->clearSessionComponents($request, $objectId); | |
$this->alreadyMappedFieldCollector->removeAllForForm($objectId); | |
// load existing fields into session | |
$modifiedFields = []; | |
$existingFields = $entity->getFields()->toArray(); | |
$submitButton = false; | |
foreach ($existingFields as $formField) { | |
// Check to see if the field still exists | |
if ('button' == $formField->getType()) { | |
// submit button found | |
$submitButton = true; | |
} | |
if ('button' !== $formField->getType() && !in_array($formField->getType(), $availableFields)) { | |
continue; | |
} | |
$id = $formField->getId(); | |
$field = $formField->convertToArray(); | |
if (!$id) { | |
// Cloned entity | |
$id = $field['id'] = $field['sessionId'] = 'new'.hash('sha1', uniqid(mt_rand())); | |
} | |
unset($field['form']); | |
if (isset($customComponents['fields'][$field['type']])) { | |
// Set the custom parameters | |
$field['customParameters'] = $customComponents['fields'][$field['type']]; | |
} | |
$field['formId'] = $objectId; | |
$modifiedFields[$id] = $field; | |
if (!empty($field['mappedObject']) && !empty($field['mappedField']) && empty($field['parent'])) { | |
$this->alreadyMappedFieldCollector->addField($objectId, $field['mappedObject'], $field['mappedField']); | |
} | |
} | |
if (!$submitButton) { // means something deleted the submit button from the form | |
// add a submit button | |
$keyId = 'new'.hash('sha1', uniqid(mt_rand())); | |
$field = new Field(); | |
$modifiedFields[$keyId] = $field->convertToArray(); | |
$modifiedFields[$keyId]['label'] = $this->translator->trans('mautic.core.form.submit'); | |
$modifiedFields[$keyId]['alias'] = 'submit'; | |
$modifiedFields[$keyId]['showLabel'] = 1; | |
$modifiedFields[$keyId]['type'] = 'button'; | |
$modifiedFields[$keyId]['id'] = $keyId; | |
$modifiedFields[$keyId]['inputAttributes'] = 'class="btn btn-default"'; | |
$modifiedFields[$keyId]['formId'] = $objectId; | |
unset($modifiedFields[$keyId]['form']); | |
} | |
if (!empty($reorder)) { | |
uasort( | |
$modifiedFields, | |
fn ($a, $b): int => $a['order'] <=> $b['order'] | |
); | |
} | |
$session->set('mautic.form.'.$objectId.'.fields.modified', $modifiedFields); | |
$deletedFields = []; | |
// Load existing actions into session | |
$modifiedActions = []; | |
$existingActions = $entity->getActions()->toArray(); | |
foreach ($existingActions as $formAction) { | |
// Check to see if the action still exists | |
if (!isset($customComponents['actions'][$formAction->getType()])) { | |
continue; | |
} | |
$id = $formAction->getId(); | |
$action = $formAction->convertToArray(); | |
if (!$id) { | |
// Cloned entity so use a random Id instead | |
$action['id'] = $id = 'new'.hash('sha1', uniqid(mt_rand())); | |
} | |
unset($action['form']); | |
$modifiedActions[$id] = $action; | |
} | |
if (!empty($reorder)) { | |
uasort( | |
$modifiedActions, | |
fn ($a, $b): int => $a['order'] <=> $b['order'] | |
); | |
} | |
$session->set('mautic.form.'.$objectId.'.actions.modified', $modifiedActions); | |
$deletedActions = []; | |
} | |
return $this->delegateView( | |
[ | |
'viewParameters' => [ | |
'fields' => $availableFields, | |
'formFields' => $modifiedFields, | |
'deletedFields' => $deletedFields, | |
'mappedFields' => $this->mappedObjectCollector->buildCollection(...$entity->getMappedFieldObjects()), | |
'formActions' => $modifiedActions, | |
'deletedActions' => $deletedActions, | |
'viewOnlyFields' => $customComponents['viewOnlyFields'], | |
'actions' => $customComponents['choices'], | |
'actionSettings' => $customComponents['actions'], | |
'fieldSettings' => $customComponents['fields'], | |
'tmpl' => $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index', | |
'activeForm' => $entity, | |
'form' => $form->createView(), | |
'forceTypeSelection' => $forceTypeSelection, | |
'inBuilder' => true, | |
], | |
'contentTemplate' => '@MauticForm/Builder/index.html.twig', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
'route' => $this->generateUrl( | |
'mautic_form_action', | |
[ | |
'objectAction' => 'edit', | |
'objectId' => $entity->getId(), | |
] | |
), | |
], | |
] | |
); | |
} | |
/** | |
* Clone an entity. | |
* | |
* @param int $objectId | |
* | |
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response | |
*/ | |
public function cloneAction(Request $request, $objectId) | |
{ | |
$model = $this->getModel('form.form'); | |
/** @var Form $entity */ | |
$entity = $model->getEntity($objectId); | |
if (null != $entity) { | |
if (!$this->security->isGranted('form:forms:create') | |
|| !$this->security->hasEntityAccess( | |
'form:forms:viewown', | |
'form:forms:viewother', | |
$entity->getCreatedBy() | |
) | |
) { | |
return $this->accessDenied(); | |
} | |
$entity = clone $entity; | |
$entity->setIsPublished(false); | |
// Clone the forms's fields | |
$fields = $entity->getFields()->toArray(); | |
/** @var Field $field */ | |
foreach ($fields as $field) { | |
$fieldClone = clone $field; | |
$fieldClone->setForm($entity); | |
$fieldClone->setSessionId(null); | |
$entity->addField($field->getId(), $fieldClone); | |
} | |
// Clone the forms's actions | |
$actions = $entity->getActions()->toArray(); | |
/** @var \Mautic\FormBundle\Entity\Action $action */ | |
foreach ($actions as $action) { | |
$actionClone = clone $action; | |
$actionClone->setForm($entity); | |
$entity->addAction($action->getId(), $actionClone); | |
} | |
} | |
return $this->editAction($request, $entity, true, true); | |
} | |
/** | |
* Gives a preview of the form. | |
* | |
* @param int $objectId | |
*/ | |
public function previewAction($objectId, ThemeHelper $themeHelper, AssetsHelper $assetsHelper, SlotsHelper $slotsHelper, AnalyticsHelper $analyticsHelper): Response | |
{ | |
/** @var FormModel $model */ | |
$model = $this->getModel('form.form'); | |
$form = $model->getEntity($objectId); | |
if (null === $form) { | |
$html = | |
'<h1>'. | |
$this->translator->trans('mautic.form.error.notfound', ['%id%' => $objectId], 'flashes'). | |
'</h1>'; | |
} elseif (!$this->security->hasEntityAccess( | |
'form:forms:editown', | |
'form:forms:editother', | |
$form->getCreatedBy() | |
) | |
) { | |
$html = '<h1>'.$this->translator->trans('mautic.core.error.accessdenied', [], 'flashes').'</h1>'; | |
} else { | |
$html = $model->getContent($form, true, false); | |
} | |
$model->populateValuesWithGetParameters($form, $html); | |
$viewParams = [ | |
'content' => $html, | |
'stylesheets' => [], | |
'name' => $form->getName(), | |
'metaRobots' => '<meta name="robots" content="index">', | |
]; | |
if ($form->getNoIndex()) { | |
$viewParams['metaRobots'] = '<meta name="robots" content="noindex">'; | |
} | |
// Use form specific template or system-wide default theme | |
$template = $form->getTemplate() ?? $this->coreParametersHelper->get('theme'); | |
if (!empty($template)) { | |
$theme = $themeHelper->getTheme($template); | |
if ($theme->getTheme() != $template) { | |
$config = $theme->getConfig(); | |
if (in_array('form', $config['features'])) { | |
$template = $theme->getTheme(); | |
} else { | |
$template = null; | |
} | |
} | |
} | |
$viewParams['template'] = $template; | |
if (!empty($template)) { | |
$logicalName = $themeHelper->checkForTwigTemplate('@themes/'.$template.'/html/form.html.twig'); | |
$slotsHelper->set('pageTitle', $form->getName()); | |
$analytics = $analyticsHelper->getCode(); | |
if (!empty($analytics)) { | |
$assetsHelper->addCustomDeclaration($analytics); | |
} | |
if ($form->getNoIndex()) { | |
$assetsHelper->addCustomDeclaration('<meta name="robots" content="noindex">'); | |
} | |
return $this->render($logicalName, $viewParams); | |
} | |
return $this->render('@MauticForm/form.html.twig', $viewParams); | |
} | |
/** | |
* Deletes the entity. | |
* | |
* @param int $objectId | |
* | |
* @return Response | |
*/ | |
public function deleteAction(Request $request, $objectId) | |
{ | |
$page = $request->getSession()->get('mautic.form.page', 1); | |
$returnUrl = $this->generateUrl('mautic_form_index', ['page' => $page]); | |
$flashes = []; | |
$postActionVars = [ | |
'returnUrl' => $returnUrl, | |
'viewParameters' => ['page' => $page], | |
'contentTemplate' => 'Mautic\FormBundle\Controller\FormController::indexAction', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
], | |
]; | |
if (Request::METHOD_POST === $request->getMethod()) { | |
$model = $this->getModel('form.form'); | |
\assert($model instanceof FormModel); | |
$entity = $model->getEntity($objectId); | |
if (null === $entity) { | |
$flashes[] = [ | |
'type' => 'error', | |
'msg' => 'mautic.form.error.notfound', | |
'msgVars' => ['%id%' => $objectId], | |
]; | |
} elseif (!$this->security->hasEntityAccess( | |
'form:forms:deleteown', | |
'form:forms:deleteother', | |
$entity->getCreatedBy() | |
) | |
) { | |
return $this->accessDenied(); | |
} elseif ($model->isLocked($entity)) { | |
return $this->isLocked($postActionVars, $entity, 'form.form'); | |
} | |
$model->deleteEntity($entity); | |
$identifier = $this->translator->trans($entity->getName()); | |
$flashes[] = [ | |
'type' => 'notice', | |
'msg' => 'mautic.core.notice.deleted', | |
'msgVars' => [ | |
'%name%' => $identifier, | |
'%id%' => $objectId, | |
], | |
]; | |
} // else don't do anything | |
return $this->postActionRedirect( | |
array_merge( | |
$postActionVars, | |
[ | |
'flashes' => $flashes, | |
] | |
) | |
); | |
} | |
/** | |
* Deletes a group of entities. | |
* | |
* @return Response | |
*/ | |
public function batchDeleteAction(Request $request) | |
{ | |
$page = $request->getSession()->get('mautic.form.page', 1); | |
$returnUrl = $this->generateUrl('mautic_form_index', ['page' => $page]); | |
$flashes = []; | |
$postActionVars = [ | |
'returnUrl' => $returnUrl, | |
'viewParameters' => ['page' => $page], | |
'contentTemplate' => 'Mautic\FormBundle\Controller\FormController::indexAction', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
], | |
]; | |
if (Request::METHOD_POST === $request->getMethod()) { | |
$model = $this->getModel('form'); | |
\assert($model instanceof FormModel); | |
$ids = json_decode($request->query->get('ids', '')); | |
$deleteIds = []; | |
// Loop over the IDs to perform access checks pre-delete | |
foreach ($ids as $objectId) { | |
$objectId = (int) $objectId; | |
$entity = $model->getEntity($objectId); | |
if (null === $entity) { | |
$flashes[] = [ | |
'type' => 'error', | |
'msg' => 'mautic.form.error.notfound', | |
'msgVars' => ['%id%' => $objectId], | |
]; | |
} elseif (!$this->security->hasEntityAccess( | |
'form:forms:deleteown', | |
'form:forms:deleteother', | |
$entity->getCreatedBy() | |
) | |
) { | |
$flashes[] = $this->accessDenied(true); | |
} elseif ($model->isLocked($entity)) { | |
$flashes[] = $this->isLocked($postActionVars, $entity, 'form.form', true); | |
} else { | |
$deleteIds[] = $objectId; | |
} | |
} | |
// Delete everything we are able to | |
if (!empty($deleteIds)) { | |
$entities = $model->deleteEntities($deleteIds); | |
$flashes[] = [ | |
'type' => 'notice', | |
'msg' => 'mautic.form.notice.batch_deleted', | |
'msgVars' => [ | |
'%count%' => count($entities), | |
], | |
]; | |
} | |
} // else don't do anything | |
return $this->postActionRedirect( | |
array_merge( | |
$postActionVars, | |
[ | |
'flashes' => $flashes, | |
] | |
) | |
); | |
} | |
/** | |
* Clear field and actions from the session. | |
*/ | |
public function clearSessionComponents(Request $request, $sessionId): void | |
{ | |
$session = $request->getSession(); | |
$session->remove('mautic.form.'.$sessionId.'.fields.modified'); | |
$session->remove('mautic.form.'.$sessionId.'.fields.deleted'); | |
$session->remove('mautic.form.'.$sessionId.'.actions.modified'); | |
$session->remove('mautic.form.'.$sessionId.'.actions.deleted'); | |
$this->alreadyMappedFieldCollector->removeAllForForm((string) $sessionId); | |
} | |
public function batchRebuildHtmlAction(Request $request) | |
{ | |
$page = $request->getSession()->get('mautic.form.page', 1); | |
$returnUrl = $this->generateUrl('mautic_form_index', ['page' => $page]); | |
$flashes = []; | |
$postActionVars = [ | |
'returnUrl' => $returnUrl, | |
'viewParameters' => ['page' => $page], | |
'contentTemplate' => 'Mautic\FormBundle\Controller\FormController::indexAction', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_form_index', | |
'mauticContent' => 'form', | |
], | |
]; | |
if ('POST' == $request->getMethod()) { | |
/** @var FormModel $model */ | |
$model = $this->getModel('form'); | |
$ids = json_decode($request->query->get('ids', '')); | |
$count = 0; | |
// Loop over the IDs to perform access checks pre-delete | |
foreach ($ids as $objectId) { | |
$entity = $model->getEntity($objectId); | |
if (null === $entity) { | |
$flashes[] = [ | |
'type' => 'error', | |
'msg' => 'mautic.form.error.notfound', | |
'msgVars' => ['%id%' => $objectId], | |
]; | |
} elseif (!$this->security->hasEntityAccess( | |
'form:forms:editown', | |
'form:forms:editother', | |
$entity->getCreatedBy() | |
) | |
) { | |
$flashes[] = $this->accessDenied(true); | |
} elseif ($model->isLocked($entity)) { | |
$flashes[] = $this->isLocked($postActionVars, $entity, 'form.form', true); | |
} else { | |
$model->generateHtml($entity); | |
++$count; | |
} | |
} | |
$flashes[] = [ | |
'type' => 'notice', | |
'msg' => 'mautic.form.notice.batch_html_generated', | |
'msgVars' => [ | |
'%count%' => $count, | |
], | |
]; | |
} // else don't do anything | |
return $this->postActionRedirect( | |
array_merge( | |
$postActionVars, | |
[ | |
'flashes' => $flashes, | |
] | |
) | |
); | |
} | |
public function getModelName(): string | |
{ | |
return 'form'; | |
} | |
protected function getDefaultOrderDirection(): string | |
{ | |
return 'DESC'; | |
} | |
} | |