Spaces:
No application file
No application file
namespace Mautic\FormBundle\Model; | |
use Doctrine\ORM\EntityManagerInterface; | |
use Mautic\CoreBundle\Doctrine\Helper\ColumnSchemaHelper; | |
use Mautic\CoreBundle\Doctrine\Helper\TableSchemaHelper; | |
use Mautic\CoreBundle\Helper\Chart\ChartQuery; | |
use Mautic\CoreBundle\Helper\CoreParametersHelper; | |
use Mautic\CoreBundle\Helper\ThemeHelperInterface; | |
use Mautic\CoreBundle\Helper\UserHelper; | |
use Mautic\CoreBundle\Model\FormModel as CommonFormModel; | |
use Mautic\CoreBundle\Security\Permissions\CorePermissions; | |
use Mautic\CoreBundle\Translation\Translator; | |
use Mautic\FormBundle\Collector\MappedObjectCollectorInterface; | |
use Mautic\FormBundle\Entity\Action; | |
use Mautic\FormBundle\Entity\Field; | |
use Mautic\FormBundle\Entity\Form; | |
use Mautic\FormBundle\Entity\FormRepository; | |
use Mautic\FormBundle\Event\FormBuilderEvent; | |
use Mautic\FormBundle\Event\FormEvent; | |
use Mautic\FormBundle\Form\Type\FormType; | |
use Mautic\FormBundle\FormEvents; | |
use Mautic\FormBundle\Helper\FormFieldHelper; | |
use Mautic\FormBundle\Helper\FormUploader; | |
use Mautic\FormBundle\ProgressiveProfiling\DisplayManager; | |
use Mautic\LeadBundle\Entity\Lead; | |
use Mautic\LeadBundle\Helper\FormFieldHelper as ContactFieldHelper; | |
use Mautic\LeadBundle\Helper\PrimaryCompanyHelper; | |
use Mautic\LeadBundle\Model\FieldModel as LeadFieldModel; | |
use Mautic\LeadBundle\Tracker\ContactTracker; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
use Symfony\Component\Form\FormFactoryInterface; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; | |
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | |
use Symfony\Contracts\EventDispatcher\Event; | |
use Twig\Environment; | |
/** | |
* @extends CommonFormModel<Form> | |
*/ | |
class FormModel extends CommonFormModel | |
{ | |
public function __construct( | |
protected RequestStack $requestStack, | |
protected Environment $twig, | |
protected ThemeHelperInterface $themeHelper, | |
protected ActionModel $formActionModel, | |
protected FieldModel $formFieldModel, | |
protected FormFieldHelper $fieldHelper, | |
private PrimaryCompanyHelper $primaryCompanyHelper, | |
protected LeadFieldModel $leadFieldModel, | |
private FormUploader $formUploader, | |
private ContactTracker $contactTracker, | |
private ColumnSchemaHelper $columnSchemaHelper, | |
private TableSchemaHelper $tableSchemaHelper, | |
private MappedObjectCollectorInterface $mappedObjectCollector, | |
EntityManagerInterface $em, | |
CorePermissions $security, | |
EventDispatcherInterface $dispatcher, | |
UrlGeneratorInterface $router, | |
Translator $translator, | |
UserHelper $userHelper, | |
LoggerInterface $mauticLogger, | |
CoreParametersHelper $coreParametersHelper | |
) { | |
parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper); | |
} | |
/** | |
* @return FormRepository | |
*/ | |
public function getRepository() | |
{ | |
return $this->em->getRepository(Form::class); | |
} | |
public function getPermissionBase(): string | |
{ | |
return 'form:forms'; | |
} | |
public function getNameGetter(): string | |
{ | |
return 'getName'; | |
} | |
public function createForm($entity, FormFactoryInterface $formFactory, $action = null, $options = []): \Symfony\Component\Form\FormInterface | |
{ | |
if (!$entity instanceof Form) { | |
throw new MethodNotAllowedHttpException(['Form']); | |
} | |
if (!empty($action)) { | |
$options['action'] = $action; | |
} | |
return $formFactory->create(FormType::class, $entity, $options); | |
} | |
/** | |
* @param string|int|null $id | |
*/ | |
public function getEntity($id = null): ?Form | |
{ | |
if (null === $id) { | |
return new Form(); | |
} | |
$entity = parent::getEntity($id); | |
if ($entity && $entity->getFields()) { | |
foreach ($entity->getFields() as $field) { | |
$this->addMappedFieldOptions($field); | |
} | |
} | |
return $entity; | |
} | |
/** | |
* @throws MethodNotAllowedHttpException | |
*/ | |
protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null): ?Event | |
{ | |
if (!$entity instanceof Form) { | |
throw new MethodNotAllowedHttpException(['Form']); | |
} | |
switch ($action) { | |
case 'pre_save': | |
$name = FormEvents::FORM_PRE_SAVE; | |
break; | |
case 'post_save': | |
$name = FormEvents::FORM_POST_SAVE; | |
break; | |
case 'pre_delete': | |
$name = FormEvents::FORM_PRE_DELETE; | |
break; | |
case 'post_delete': | |
$name = FormEvents::FORM_POST_DELETE; | |
break; | |
default: | |
return null; | |
} | |
if ($this->dispatcher->hasListeners($name)) { | |
if (empty($event)) { | |
$event = new FormEvent($entity, $isNew); | |
$event->setEntityManager($this->em); | |
} | |
$this->dispatcher->dispatch($event, $name); | |
return $event; | |
} else { | |
return null; | |
} | |
} | |
public function setFields(Form $entity, $sessionFields): void | |
{ | |
$order = 1; | |
$existingFields = $entity->getFields()->toArray(); | |
$formName = $entity->generateFormName(); | |
foreach ($sessionFields as $key => $properties) { | |
$isNew = (!empty($properties['id']) && isset($existingFields[$properties['id']])) ? false : true; | |
$field = !$isNew ? $existingFields[$properties['id']] : new Field(); | |
if (!$isNew) { | |
if (empty($properties['alias'])) { | |
$properties['alias'] = $field->getAlias(); | |
} | |
if (empty($properties['label'])) { | |
$properties['label'] = $field->getLabel(); | |
} | |
} | |
if ($formName === $properties['alias']) { | |
// Change the alias to prevent potential ID collisions in the rendered HTML | |
$properties['alias'] = 'f_'.$properties['alias']; | |
} | |
foreach ($properties as $f => $v) { | |
if (in_array($f, ['id', 'order'])) { | |
continue; | |
} | |
$func = 'set'.ucfirst($f); | |
if (method_exists($field, $func)) { | |
$field->$func($v); | |
} | |
} | |
$field->setForm($entity); | |
$field->setSessionId($key); | |
if (!$field->getParent()) { | |
$field->setOrder($order); | |
++$order; | |
} else { | |
if (isset($sessionFields[$field->getParent()]['order'])) { | |
$field->setOrder($sessionFields[$field->getParent()]['order']); | |
} else { | |
$field->setOrder($order); | |
} | |
} | |
$entity->addField($properties['id'], $field); | |
} | |
// Persist if the entity is known | |
if ($entity->getId()) { | |
$this->formFieldModel->saveEntities($existingFields); | |
} | |
} | |
public function deleteFields(Form $entity, $sessionFields): void | |
{ | |
if (empty($sessionFields)) { | |
return; | |
} | |
$existingFields = $entity->getFields()->toArray(); | |
$deleteFields = []; | |
foreach ($sessionFields as $fieldId) { | |
if (!isset($existingFields[$fieldId])) { | |
continue; | |
} | |
$this->handleFilesDelete($existingFields[$fieldId]); | |
$entity->removeField($fieldId, $existingFields[$fieldId]); | |
$deleteFields[] = $fieldId; | |
} | |
// Delete fields from db | |
if (count($deleteFields)) { | |
$this->formFieldModel->deleteEntities($deleteFields); | |
} | |
} | |
private function handleFilesDelete(Field $field): void | |
{ | |
if (!$field->isFileType()) { | |
return; | |
} | |
$this->formUploader->deleteAllFilesOfFormField($field); | |
} | |
public function setActions(Form $entity, $sessionActions): void | |
{ | |
$order = 1; | |
$existingActions = $entity->getActions()->toArray(); | |
$savedFields = $entity->getFields()->toArray(); | |
// match sessionId with field Id to update mapped fields | |
$fieldIds = []; | |
foreach ($savedFields as $field) { | |
$fieldIds[$field->getSessionId()] = $field->getId(); | |
} | |
foreach ($sessionActions as $properties) { | |
$isNew = (!empty($properties['id']) && isset($existingActions[$properties['id']])) ? false : true; | |
$action = !$isNew ? $existingActions[$properties['id']] : new Action(); | |
foreach ($properties as $f => $v) { | |
if (in_array($f, ['id', 'order'])) { | |
continue; | |
} | |
$func = 'set'.ucfirst($f); | |
if ('properties' == $f) { | |
if (isset($v['mappedFields'])) { | |
foreach ($v['mappedFields'] as $pk => $pv) { | |
if (str_contains($pv, 'new')) { | |
$v['mappedFields'][$pk] = $fieldIds[$pv]; | |
} | |
} | |
} | |
} | |
if (method_exists($action, $func)) { | |
$action->$func($v); | |
} | |
} | |
$action->setForm($entity); | |
$action->setOrder($order); | |
++$order; | |
$entity->addAction($properties['id'], $action); | |
} | |
// Persist if form is being edited | |
if ($entity->getId()) { | |
$this->formActionModel->saveEntities($existingActions); | |
} | |
} | |
/** | |
* @param array $actions | |
*/ | |
public function deleteActions(Form $entity, $actions): void | |
{ | |
if (empty($actions)) { | |
return; | |
} | |
$existingActions = $entity->getActions()->toArray(); | |
$deleteActions = []; | |
foreach ($actions as $actionId) { | |
if (isset($existingActions[$actionId])) { | |
$actionEntity = $this->em->getReference(Action::class, (int) $actionId); | |
$entity->removeAction($actionEntity); | |
$deleteActions[] = $actionId; | |
} | |
} | |
// Delete actions from db | |
if (count($deleteActions)) { | |
$this->formActionModel->deleteEntities($deleteActions); | |
} | |
} | |
public function saveEntity($entity, $unlock = true): void | |
{ | |
$isNew = ($entity->getId()) ? false : true; | |
if ($isNew && !$entity->getAlias()) { | |
$alias = $this->cleanAlias($entity->getName(), '', 10); | |
$entity->setAlias($alias); | |
} | |
$this->backfillReplacedPropertiesForBc($entity); | |
// save the form so that the ID is available for the form html | |
parent::saveEntity($entity, $unlock); | |
// now build the form table | |
if ($entity->getId()) { | |
$this->createTableSchema($entity, $isNew); | |
} | |
$this->generateHtml($entity); | |
} | |
/** | |
* Obtains the content. | |
* | |
* @param bool|true $withScript | |
* @param bool|true $useCache | |
*/ | |
public function getContent(Form $form, $withScript = true, $useCache = true): string | |
{ | |
$html = $this->getFormHtml($form, $useCache); | |
if ($withScript) { | |
$html = $this->getFormScript($form)."\n\n".$this->removeScriptTag($html); | |
} else { | |
$html = $this->removeScriptTag($html); | |
} | |
return $html; | |
} | |
/** | |
* Obtains the cached HTML of a form and generates it if missing. | |
* | |
* @param bool|true $useCache | |
* | |
* @return string | |
*/ | |
public function getFormHtml(Form $form, $useCache = true) | |
{ | |
if ($useCache && !$form->usesProgressiveProfiling()) { | |
$cachedHtml = $form->getCachedHtml(); | |
} | |
if (empty($cachedHtml)) { | |
$cachedHtml = $this->generateHtml($form, $useCache); | |
} | |
if (!$form->getInKioskMode()) { | |
$this->populateValuesWithLead($form, $cachedHtml); | |
} | |
return $cachedHtml; | |
} | |
/** | |
* Get results for a form and lead. | |
* | |
* @param int $leadId | |
* @param int $limit | |
*/ | |
public function getLeadSubmissions(Form $form, $leadId, $limit = 200): array | |
{ | |
return $this->getRepository()->getFormResults( | |
$form, | |
[ | |
'leadId' => $leadId, | |
'limit' => $limit, | |
] | |
); | |
} | |
/** | |
* Generate the form's html. | |
* | |
* @param bool $persist | |
*/ | |
public function generateHtml(Form $entity, $persist = true): string | |
{ | |
// Use specific template or system-wide default theme | |
$theme = $entity->getTemplate() ?? $this->coreParametersHelper->get('theme'); | |
$submissions = null; | |
$lead = ($this->requestStack->getCurrentRequest()) ? $this->contactTracker->getContact() : null; | |
$style = ''; | |
$styleToRender = '@MauticForm/Builder/_style.html.twig'; | |
$formToRender = '@MauticForm/Builder/form.html.twig'; | |
if ($this->twig->getLoader()->exists('@themes/'.$theme.'/html/MauticFormBundle/Builder/_style.html.twig')) { | |
$styleToRender = '@themes/'.$theme.'/html/MauticFormBundle/Builder/_style.html.twig'; | |
} | |
if ($this->twig->getLoader()->exists('@themes/'.$theme.'/html/MauticFormBundle/Builder/form.html.twig')) { | |
$formToRender = '@themes/'.$theme.'/html/MauticFormBundle/Builder/form.html.twig'; | |
} | |
if ($lead instanceof Lead && $lead->getId() && $entity->usesProgressiveProfiling()) { | |
$submissions = $this->getLeadSubmissions($entity, $lead->getId()); | |
} | |
if ($entity->getRenderStyle()) { | |
$styleTheme = $styleToRender; | |
$style = $this->twig->render($this->themeHelper->checkForTwigTemplate($styleTheme)); | |
} | |
// Determine pages | |
$fields = $entity->getFields()->toArray(); | |
// Ensure the correct order in case this is generated right after a form save with new fields | |
uasort($fields, fn ($a, $b): int => $a->getOrder() <=> $b->getOrder()); | |
$viewOnlyFields = $this->getCustomComponents()['viewOnlyFields']; | |
$displayManager = new DisplayManager($entity, !empty($viewOnlyFields) ? $viewOnlyFields : []); | |
[$pages, $lastPage] = $this->getPages($fields); | |
$html = $this->twig->render( | |
$formToRender, | |
[ | |
'fieldSettings' => $this->getCustomComponents()['fields'], | |
'viewOnlyFields' => $viewOnlyFields, | |
'fields' => $fields, | |
'mappedFields' => $this->mappedObjectCollector->buildCollection(...$entity->getMappedFieldObjects()), | |
'form' => $entity, | |
'theme' => '@themes/'.$entity->getTemplate().'/Field/', | |
'submissions' => $submissions, | |
'lead' => $lead, | |
'formPages' => $pages, | |
'lastFormPage' => $lastPage, | |
'style' => $style, | |
'inBuilder' => false, | |
'displayManager' => $displayManager, | |
'successfulSubmitAction' => $this->coreParametersHelper->get('successful_submit_action'), | |
] | |
); | |
if (!$entity->usesProgressiveProfiling()) { | |
$entity->setCachedHtml($html); | |
if ($persist) { | |
// bypass model function as events aren't needed for this | |
$this->getRepository()->saveEntity($entity); | |
} | |
} | |
return $html; | |
} | |
public function getPages(array $fields): array | |
{ | |
$pages = ['open' => [], 'close' => []]; | |
$openFieldId = | |
$previousId = | |
$lastPage = false; | |
$pageCount = 1; | |
foreach ($fields as $fieldId => $field) { | |
if ('pagebreak' == $field->getType() && $openFieldId) { | |
// Open the page | |
$pages['open'][$openFieldId] = $pageCount; | |
$openFieldId = false; | |
$lastPage = $fieldId; | |
// Close the page at the next page break | |
if ($previousId) { | |
$pages['close'][$previousId] = $pageCount; | |
++$pageCount; | |
} | |
} else { | |
if (!$openFieldId) { | |
$openFieldId = $fieldId; | |
} | |
} | |
$previousId = $fieldId; | |
} | |
if ($openFieldId) { | |
$pages['open'][$openFieldId] = $pageCount; | |
} | |
if ($previousId !== $lastPage) { | |
$pages['close'][$previousId] = $pageCount; | |
} | |
return [$pages, $lastPage]; | |
} | |
/** | |
* Creates the table structure for form results. | |
* | |
* @param bool $isNew | |
* @param bool $dropExisting | |
*/ | |
public function createTableSchema(Form $entity, $isNew = false, $dropExisting = false): void | |
{ | |
// create the field as its own column in the leads table | |
$name = 'form_results_'.$entity->getId().'_'.$entity->getAlias(); | |
$columns = $this->generateFieldColumns($entity); | |
if ($isNew || (!$isNew && !$this->tableSchemaHelper->checkTableExists($name))) { | |
$this->tableSchemaHelper->addTable([ | |
'name' => $name, | |
'columns' => $columns, | |
'options' => [ | |
'primaryKey' => ['submission_id'], | |
'uniqueIndex' => ['submission_id', 'form_id'], | |
], | |
], true, $dropExisting); | |
$this->tableSchemaHelper->executeChanges(); | |
} else { | |
// check to make sure columns exist | |
$columnSchemaHelper = $this->columnSchemaHelper->setName($name); | |
foreach ($columns as $c) { | |
if (!$columnSchemaHelper->checkColumnExists($c['name'])) { | |
$columnSchemaHelper->addColumn($c, false); | |
} | |
} | |
$columnSchemaHelper->executeChanges(); | |
} | |
} | |
public function deleteEntity($entity): void | |
{ | |
/* @var Form $entity */ | |
$this->deleteFormFiles($entity); | |
if (!$entity->getId()) { | |
// delete the associated results table | |
$this->tableSchemaHelper->deleteTable('form_results_'.$entity->deletedId.'_'.$entity->getAlias()); | |
$this->tableSchemaHelper->executeChanges(); | |
} | |
parent::deleteEntity($entity); | |
} | |
/** | |
* @param mixed[] $ids | |
* | |
* @return mixed[] | |
*/ | |
public function deleteEntities($ids): array | |
{ | |
$entities = parent::deleteEntities($ids); | |
foreach ($entities as $id => $entity) { | |
/* @var Form $entity */ | |
// delete the associated results table | |
$this->tableSchemaHelper->deleteTable('form_results_'.$id.'_'.$entity->getAlias()); | |
$this->deleteFormFiles($entity); | |
} | |
$this->tableSchemaHelper->executeChanges(); | |
return $entities; | |
} | |
private function deleteFormFiles(Form $form): void | |
{ | |
$this->formUploader->deleteFilesOfForm($form); | |
} | |
/** | |
* Generate an array of columns from fields. | |
*/ | |
public function generateFieldColumns(Form $form): array | |
{ | |
$fields = $form->getFields()->toArray(); | |
$columns = [ | |
[ | |
'name' => 'submission_id', | |
'type' => 'integer', | |
], | |
[ | |
'name' => 'form_id', | |
'type' => 'integer', | |
], | |
]; | |
$ignoreTypes = $this->getCustomComponents()['viewOnlyFields']; | |
foreach ($fields as $f) { | |
if (!in_array($f->getType(), $ignoreTypes)) { | |
$columns[] = [ | |
'name' => $f->getAlias(), | |
'type' => 'text', | |
'options' => [ | |
'notnull' => false, | |
], | |
]; | |
} | |
} | |
return $columns; | |
} | |
/** | |
* Gets array of custom fields and submit actions from bundles subscribed FormEvents::FORM_ON_BUILD. | |
* | |
* @return mixed | |
*/ | |
public function getCustomComponents() | |
{ | |
static $customComponents; | |
if (empty($customComponents)) { | |
// build them | |
$event = new FormBuilderEvent($this->translator); | |
$this->dispatcher->dispatch($event, FormEvents::FORM_ON_BUILD); | |
$customComponents['fields'] = $event->getFormFields(); | |
$customComponents['actions'] = $event->getSubmitActions(); | |
$customComponents['choices'] = $event->getSubmitActionGroups(); | |
$customComponents['validators'] = $event->getValidators(); | |
// Generate a list of fields that are not persisted to the database by default | |
$notPersist = ['button', 'captcha', 'freetext', 'freehtml', 'pagebreak']; | |
foreach ($customComponents['fields'] as $type => $field) { | |
if (isset($field['builderOptions']) && isset($field['builderOptions']['addSaveResult']) && false === $field['builderOptions']['addSaveResult']) { | |
$notPersist[] = $type; | |
} | |
} | |
$customComponents['viewOnlyFields'] = $notPersist; | |
} | |
return $customComponents; | |
} | |
/** | |
* Get the document write javascript for the form. | |
*/ | |
public function getAutomaticJavascript(Form $form): string | |
{ | |
$html = $this->getContent($form, false); | |
$formScript = $this->getFormScript($form); | |
// replace line breaks with literal symbol and escape quotations | |
$search = ["\r\n", "\n", '"']; | |
$replace = ['', '', '\"']; | |
$html = str_replace($search, $replace, $html); | |
$oldFormScript = str_replace($search, $replace, $formScript); | |
$newFormScript = $this->generateJsScript($formScript); | |
// Write html for all browser and fallback for IE | |
$script = ' | |
var scr = document.currentScript; | |
var html = "'.$html.'"; | |
if (scr !== undefined) { | |
scr.insertAdjacentHTML("afterend", html); | |
'.$newFormScript.' | |
} else { | |
document.write("'.$oldFormScript.'"+html); | |
} | |
'; | |
return $script; | |
} | |
public function getFormScript(Form $form): string | |
{ | |
$theme = $form->getTemplate(); | |
$scriptToRender = '@MauticForm/Builder/_script.html.twig'; | |
if (!empty($theme)) { | |
if ($this->twig->getLoader()->exists('@themes/'.$theme.'/MauticForm/Builder/_script.html.twig')) { | |
$scriptToRender = '@themes/'.$theme.'/MauticForm/Builder/_script.html.twig'; | |
} | |
} | |
$script = $this->twig->render( | |
$scriptToRender, | |
[ | |
'form' => $form, | |
'theme' => $theme, | |
] | |
); | |
$html = $this->getFormHtml($form); | |
$scripts = $this->extractScriptTag($html); | |
foreach ($scripts as $item) { | |
$script .= $item."\n"; | |
} | |
return $script; | |
} | |
/** | |
* Writes in form values from get parameters. | |
*/ | |
public function populateValuesWithGetParameters(Form $form, &$formHtml): void | |
{ | |
$formName = $form->generateFormName(); | |
$request = $this->requestStack->getCurrentRequest(); | |
$fields = $form->getFields()->toArray(); | |
/** @var Field $f */ | |
foreach ($fields as $f) { | |
$alias = $f->getAlias(); | |
if ($request->query->has($alias)) { | |
$value = urlencode($request->query->get($alias)); | |
$this->fieldHelper->populateField($f, $value, $formName, $formHtml); | |
} | |
} | |
} | |
/** | |
* @param string $formHtml | |
*/ | |
public function populateValuesWithLead(Form $form, &$formHtml): void | |
{ | |
$formName = $form->generateFormName(); | |
$fields = $form->getFields(); | |
$autoFillFields = []; | |
$objectsToAutoFill = ['contact', 'company']; | |
/** @var Field $field */ | |
foreach ($fields as $key => $field) { | |
// we want work just with matched autofill fields | |
if ( | |
$field->getMappedField() | |
&& $field->getIsAutoFill() | |
&& in_array($field->getMappedObject(), $objectsToAutoFill) | |
) { | |
$autoFillFields[$key] = $field; | |
} | |
} | |
// no fields for populate | |
if (!count($autoFillFields)) { | |
return; | |
} | |
$lead = $this->contactTracker->getContact(); | |
if (!$lead instanceof Lead) { | |
return; | |
} | |
// get the contact (lead) and primary company field values | |
$leadArray = $this->primaryCompanyHelper->getProfileFieldsWithPrimaryCompany($lead); | |
if (!is_array($leadArray) || count($leadArray) <= 0) { | |
return; | |
} | |
foreach ($autoFillFields as $field) { | |
$value = $leadArray[$field->getMappedField()] ?? ''; | |
// just skip string empty field | |
if ('' !== $value) { | |
$this->fieldHelper->populateField($field, $value, $formName, $formHtml); | |
} | |
} | |
} | |
public function getFilterExpressionFunctions($operator = null): array | |
{ | |
$operatorOptions = [ | |
'=' => [ | |
'label' => 'mautic.lead.list.form.operator.equals', | |
'expr' => 'eq', | |
'negate_expr' => 'neq', | |
], | |
'!=' => [ | |
'label' => 'mautic.lead.list.form.operator.notequals', | |
'expr' => 'neq', | |
'negate_expr' => 'eq', | |
], | |
'gt' => [ | |
'label' => 'mautic.lead.list.form.operator.greaterthan', | |
'expr' => 'gt', | |
'negate_expr' => 'lt', | |
], | |
'gte' => [ | |
'label' => 'mautic.lead.list.form.operator.greaterthanequals', | |
'expr' => 'gte', | |
'negate_expr' => 'lt', | |
], | |
'lt' => [ | |
'label' => 'mautic.lead.list.form.operator.lessthan', | |
'expr' => 'lt', | |
'negate_expr' => 'gt', | |
], | |
'lte' => [ | |
'label' => 'mautic.lead.list.form.operator.lessthanequals', | |
'expr' => 'lte', | |
'negate_expr' => 'gt', | |
], | |
'like' => [ | |
'label' => 'mautic.lead.list.form.operator.islike', | |
'expr' => 'like', | |
'negate_expr' => 'notLike', | |
], | |
'!like' => [ | |
'label' => 'mautic.lead.list.form.operator.isnotlike', | |
'expr' => 'notLike', | |
'negate_expr' => 'like', | |
], | |
'startsWith' => [ | |
'label' => 'mautic.core.operator.starts.with', | |
'expr' => 'startsWith', | |
'negate_expr' => 'startsWith', | |
], | |
'endsWith' => [ | |
'label' => 'mautic.core.operator.ends.with', | |
'expr' => 'endsWith', | |
'negate_expr' => 'endsWith', | |
], | |
'contains' => [ | |
'label' => 'mautic.core.operator.contains', | |
'expr' => 'contains', | |
'negate_expr' => 'contains', | |
], | |
]; | |
return (null === $operator) ? $operatorOptions : $operatorOptions[$operator]; | |
} | |
/** | |
* Get a list of assets in a date range. | |
* | |
* @param int $limit | |
* @param array $filters | |
* @param array $options | |
* | |
* @return array | |
*/ | |
public function getFormList($limit = 10, \DateTime $dateFrom = null, \DateTime $dateTo = null, $filters = [], $options = []) | |
{ | |
$q = $this->em->getConnection()->createQueryBuilder(); | |
$q->select('t.id, t.name, t.date_added, t.date_modified') | |
->from(MAUTIC_TABLE_PREFIX.'forms', 't') | |
->setMaxResults($limit); | |
if (!empty($options['canViewOthers'])) { | |
$q->andWhere('t.created_by = :userId') | |
->setParameter('userId', $this->userHelper->getUser()->getId()); | |
} | |
$chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo); | |
$chartQuery->applyFilters($q, $filters); | |
$chartQuery->applyDateFilters($q, 'date_added'); | |
return $q->execute()->fetchAllAssociative(); | |
} | |
/** | |
* Load HTML consider Libxml < 2.7.8. | |
*/ | |
private function loadHTML(&$dom, $html): void | |
{ | |
if (defined('LIBXML_HTML_NOIMPLIED') && defined('LIBXML_HTML_NODEFDTD')) { | |
$dom->loadHTML(mb_encode_numericentity($html, [0x80, 0x10FFFF, 0, 0xFFFFF], 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); | |
} else { | |
$dom->loadHTML(mb_encode_numericentity($html, [0x80, 0x10FFFF, 0, 0xFFFFF], 'UTF-8')); | |
} | |
} | |
/** | |
* Save HTML consider Libxml < 2.7.8. | |
* | |
* @return string | |
*/ | |
private function saveHTML($dom, $html) | |
{ | |
if (defined('LIBXML_HTML_NOIMPLIED') && defined('LIBXML_HTML_NODEFDTD')) { | |
return $dom->saveHTML($html); | |
} else { | |
// remove DOCTYPE, <html>, and <body> tags for old libxml | |
return preg_replace('/^<!DOCTYPE.+?>/', '', str_replace(['<html>', '</html>', '<body>', '</body>'], ['', '', '', ''], $dom->saveHTML($html))); | |
} | |
} | |
/** | |
* Extract script from html. | |
*/ | |
private function extractScriptTag($html): array | |
{ | |
libxml_use_internal_errors(true); | |
$dom = new \DOMDocument(); | |
$this->loadHTML($dom, $html); | |
$items = $dom->getElementsByTagName('script'); | |
$scripts = []; | |
foreach ($items as $script) { | |
$scripts[] = $this->saveHTML($dom, $script); | |
} | |
return $scripts; | |
} | |
/** | |
* Remove script from html. | |
*/ | |
private function removeScriptTag($html): string | |
{ | |
libxml_use_internal_errors(true); | |
$dom = new \DOMDocument(); | |
$this->loadHTML($dom, '<div>'.$html.'</div>'); | |
$items = $dom->getElementsByTagName('script'); | |
$remove = []; | |
foreach ($items as $item) { | |
$remove[] = $item; | |
} | |
foreach ($remove as $item) { | |
$item->parentNode->removeChild($item); | |
} | |
$root = $dom->documentElement; | |
$result = ''; | |
foreach ($root->childNodes as $childNode) { | |
$result .= $this->saveHTML($dom, $childNode); | |
} | |
return $result; | |
} | |
/** | |
* Generate dom manipulation javascript to include all script. | |
*/ | |
private function generateJsScript($html): string | |
{ | |
libxml_use_internal_errors(true); | |
$dom = new \DOMDocument(); | |
$this->loadHTML($dom, '<div>'.$html.'</div>'); | |
$items = $dom->getElementsByTagName('script'); | |
$javascript = ''; | |
foreach ($items as $key => $script) { | |
if ($script->hasAttribute('src')) { | |
$javascript .= " | |
var script$key = document.createElement('script'); | |
script$key.src = '".$script->getAttribute('src')."'; | |
document.getElementsByTagName('head')[0].appendChild(script$key);"; | |
} else { | |
$scriptContent = $script->nodeValue; | |
$scriptContent = str_replace(["\r\n", "\n", '"'], ['', '', '\"'], $scriptContent); | |
$javascript .= " | |
var inlineScript$key = document.createTextNode(\"$scriptContent\"); | |
var script$key = document.createElement('script'); | |
script$key.appendChild(inlineScript$key); | |
document.getElementsByTagName('head')[0].appendChild(script$key);"; | |
} | |
} | |
return $javascript; | |
} | |
/** | |
* Finds out whether the. | |
*/ | |
private function addMappedFieldOptions(Field $formField): void | |
{ | |
$formFieldProps = $formField->getProperties(); | |
$mappedFieldAlias = $formField->getMappedField(); | |
if (empty($formFieldProps['syncList']) || empty($mappedFieldAlias) || 'contact' !== $formField->getMappedObject()) { | |
return; | |
} | |
$list = $this->getContactFieldPropertiesList($mappedFieldAlias); | |
if (!empty($list)) { | |
$formFieldProps['list'] = ['list' => $list]; | |
if (array_key_exists('optionlist', $formFieldProps)) { | |
$formFieldProps['optionlist'] = ['list' => $list]; | |
} | |
$formField->setProperties($formFieldProps); | |
} | |
} | |
/** | |
* @return mixed[]|null | |
*/ | |
public function getContactFieldPropertiesList(string $contactFieldAlias): ?array | |
{ | |
$contactField = $this->leadFieldModel->getEntityByAlias($contactFieldAlias); // @todo this must use all objects as well. Not just contact. | |
if (empty($contactField) || !in_array($contactField->getType(), ContactFieldHelper::getListTypes())) { | |
return null; | |
} | |
$contactFieldProps = $contactField->getProperties(); | |
switch ($contactField->getType()) { | |
case 'select': | |
case 'multiselect': | |
case 'lookup': | |
$list = $contactFieldProps['list'] ?? []; | |
break; | |
case 'boolean': | |
$list = [$contactFieldProps['no'], $contactFieldProps['yes']]; | |
break; | |
case 'country': | |
$list = ContactFieldHelper::getCountryChoices(); | |
break; | |
case 'region': | |
$list = ContactFieldHelper::getRegionChoices(); | |
break; | |
case 'timezone': | |
$list = ContactFieldHelper::getTimezonesChoices(); | |
break; | |
case 'locale': | |
$list = ContactFieldHelper::getLocaleChoices(); | |
break; | |
default: | |
return null; | |
} | |
return $list; | |
} | |
/** | |
* @param string $fieldAlias | |
* | |
* @return Field|null | |
*/ | |
public function findFormFieldByAlias(Form $form, $fieldAlias) | |
{ | |
foreach ($form->getFields() as $field) { | |
if ($field->getAlias() === $fieldAlias) { | |
return $field; | |
} | |
} | |
return null; | |
} | |
private function backfillReplacedPropertiesForBc(Form $entity): void | |
{ | |
/** @var Field $field */ | |
foreach ($entity->getFields() as $field) { | |
if (!$field->getLeadField() && $field->getMappedField()) { | |
$field->setLeadField($field->getMappedField()); | |
} elseif ($field->getLeadField() && !$field->getMappedField()) { | |
$field->setMappedField($field->getLeadField()); | |
$field->setMappedObject( | |
str_starts_with($field->getLeadField(), 'company') && 'company' !== $field->getLeadField() ? 'company' : 'contact' | |
); | |
} | |
} | |
} | |
} | |