Issues (3627)

app/bundles/FormBundle/Model/FormModel.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2014 Mautic Contributors. All rights reserved
5
 * @author      Mautic
6
 *
7
 * @link        http://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\FormBundle\Model;
13
14
use DOMDocument;
15
use Mautic\CoreBundle\Doctrine\Helper\ColumnSchemaHelper;
16
use Mautic\CoreBundle\Doctrine\Helper\TableSchemaHelper;
17
use Mautic\CoreBundle\Helper\Chart\ChartQuery;
18
use Mautic\CoreBundle\Helper\TemplatingHelper;
19
use Mautic\CoreBundle\Helper\ThemeHelper;
20
use Mautic\CoreBundle\Model\FormModel as CommonFormModel;
21
use Mautic\FormBundle\Entity\Action;
22
use Mautic\FormBundle\Entity\Field;
23
use Mautic\FormBundle\Entity\Form;
24
use Mautic\FormBundle\Event\FormBuilderEvent;
25
use Mautic\FormBundle\Event\FormEvent;
26
use Mautic\FormBundle\Form\Type\FormType;
27
use Mautic\FormBundle\FormEvents;
28
use Mautic\FormBundle\Helper\FormFieldHelper;
29
use Mautic\FormBundle\Helper\FormUploader;
30
use Mautic\LeadBundle\Entity\Lead;
31
use Mautic\LeadBundle\Helper\FormFieldHelper as ContactFieldHelper;
32
use Mautic\LeadBundle\Model\FieldModel as LeadFieldModel;
33
use Mautic\LeadBundle\Tracker\ContactTracker;
34
use Symfony\Component\EventDispatcher\Event;
35
use Symfony\Component\HttpFoundation\RequestStack;
36
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
37
38
/**
39
 * Class FormModel.
40
 */
41
class FormModel extends CommonFormModel
42
{
43
    /**
44
     * @var \Symfony\Component\HttpFoundation\Request|null
45
     */
46
    protected $request;
47
48
    /**
49
     * @var TemplatingHelper
50
     */
51
    protected $templatingHelper;
52
53
    /**
54
     * @var ThemeHelper
55
     */
56
    protected $themeHelper;
57
58
    /**
59
     * @var ActionModel
60
     */
61
    protected $formActionModel;
62
63
    /**
64
     * @var FieldModel
65
     */
66
    protected $formFieldModel;
67
68
    /**
69
     * @var FormFieldHelper
70
     */
71
    protected $fieldHelper;
72
73
    /**
74
     * @var LeadFieldModel
75
     */
76
    protected $leadFieldModel;
77
78
    /**
79
     * @var FormUploader
80
     */
81
    private $formUploader;
82
83
    /**
84
     * @var ContactTracker
85
     */
86
    private $contactTracker;
87
88
    /**
89
     * @var ColumnSchemaHelper
90
     */
91
    private $columnSchemaHelper;
92
93
    /**
94
     * @var TableSchemaHelper
95
     */
96
    private $tableSchemaHelper;
97
98
    /**
99
     * FormModel constructor.
100
     */
101
    public function __construct(
102
        RequestStack $requestStack,
103
        TemplatingHelper $templatingHelper,
104
        ThemeHelper $themeHelper,
105
        ActionModel $formActionModel,
106
        FieldModel $formFieldModel,
107
        FormFieldHelper $fieldHelper,
108
        LeadFieldModel $leadFieldModel,
109
        FormUploader $formUploader,
110
        ContactTracker $contactTracker,
111
        ColumnSchemaHelper $columnSchemaHelper,
112
        TableSchemaHelper $tableSchemaHelper
113
    ) {
114
        $this->request                = $requestStack->getCurrentRequest();
115
        $this->templatingHelper       = $templatingHelper;
116
        $this->themeHelper            = $themeHelper;
117
        $this->formActionModel        = $formActionModel;
118
        $this->formFieldModel         = $formFieldModel;
119
        $this->fieldHelper            = $fieldHelper;
120
        $this->leadFieldModel         = $leadFieldModel;
121
        $this->formUploader           = $formUploader;
122
        $this->contactTracker         = $contactTracker;
123
        $this->columnSchemaHelper     = $columnSchemaHelper;
124
        $this->tableSchemaHelper      = $tableSchemaHelper;
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     *
130
     * @return \Mautic\FormBundle\Entity\FormRepository
131
     */
132
    public function getRepository()
133
    {
134
        return $this->em->getRepository('MauticFormBundle:Form');
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function getPermissionBase()
141
    {
142
        return 'form:forms';
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function getNameGetter()
149
    {
150
        return 'getName';
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function createForm($entity, $formFactory, $action = null, $options = [])
157
    {
158
        if (!$entity instanceof Form) {
159
            throw new MethodNotAllowedHttpException(['Form']);
160
        }
161
162
        if (!empty($action)) {
163
            $options['action'] = $action;
164
        }
165
166
        return $formFactory->create(FormType::class, $entity, $options);
167
    }
168
169
    /**
170
     * @param null $id
171
     *
172
     * @return Form
173
     */
174
    public function getEntity($id = null)
175
    {
176
        if (null === $id) {
177
            return new Form();
178
        }
179
180
        $entity = parent::getEntity($id);
181
182
        if ($entity && $entity->getFields()) {
183
            foreach ($entity->getFields() as $field) {
184
                $this->addLeadFieldOptions($field);
185
            }
186
        }
187
188
        return $entity;
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     *
194
     * @return bool|FormEvent|void
195
     *
196
     * @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
197
     */
198
    protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null)
199
    {
200
        if (!$entity instanceof Form) {
201
            throw new MethodNotAllowedHttpException(['Form']);
202
        }
203
204
        switch ($action) {
205
            case 'pre_save':
206
                $name = FormEvents::FORM_PRE_SAVE;
207
                break;
208
            case 'post_save':
209
                $name = FormEvents::FORM_POST_SAVE;
210
                break;
211
            case 'pre_delete':
212
                $name = FormEvents::FORM_PRE_DELETE;
213
                break;
214
            case 'post_delete':
215
                $name = FormEvents::FORM_POST_DELETE;
216
                break;
217
            default:
218
                return null;
219
        }
220
221
        if ($this->dispatcher->hasListeners($name)) {
222
            if (empty($event)) {
223
                $event = new FormEvent($entity, $isNew);
224
                $event->setEntityManager($this->em);
225
            }
226
227
            $this->dispatcher->dispatch($name, $event);
228
229
            return $event;
230
        } else {
231
            return null;
232
        }
233
    }
234
235
    /**
236
     * @param $sessionFields
237
     */
238
    public function setFields(Form $entity, $sessionFields)
239
    {
240
        $order          = 1;
241
        $existingFields = $entity->getFields()->toArray();
242
        $formName       = $entity->generateFormName();
243
        foreach ($sessionFields as $key => $properties) {
244
            $isNew = (!empty($properties['id']) && isset($existingFields[$properties['id']])) ? false : true;
245
            $field = !$isNew ? $existingFields[$properties['id']] : new Field();
246
247
            if (!$isNew) {
248
                if (empty($properties['alias'])) {
249
                    $properties['alias'] = $field->getAlias();
250
                }
251
                if (empty($properties['label'])) {
252
                    $properties['label'] = $field->getLabel();
253
                }
254
            }
255
256
            if ($formName === $properties['alias']) {
257
                // Change the alias to prevent potential ID collisions in the rendered HTML
258
                $properties['alias'] = 'f_'.$properties['alias'];
259
            }
260
261
            foreach ($properties as $f => $v) {
262
                if (in_array($f, ['id', 'order'])) {
263
                    continue;
264
                }
265
266
                $func = 'set'.ucfirst($f);
267
                if (method_exists($field, $func)) {
268
                    $field->$func($v);
269
                }
270
            }
271
            $field->setForm($entity);
272
            $field->setSessionId($key);
273
            $field->setOrder($order);
274
            ++$order;
275
            $entity->addField($properties['id'], $field);
276
        }
277
278
        // Persist if the entity is known
279
        if ($entity->getId()) {
280
            $this->formFieldModel->saveEntities($existingFields);
281
        }
282
    }
283
284
    /**
285
     * @param $sessionFields
286
     */
287
    public function deleteFields(Form $entity, $sessionFields)
288
    {
289
        if (empty($sessionFields)) {
290
            return;
291
        }
292
293
        $existingFields = $entity->getFields()->toArray();
294
        $deleteFields   = [];
295
        foreach ($sessionFields as $fieldId) {
296
            if (!isset($existingFields[$fieldId])) {
297
                continue;
298
            }
299
            $this->handleFilesDelete($existingFields[$fieldId]);
300
            $entity->removeField($fieldId, $existingFields[$fieldId]);
301
            $deleteFields[] = $fieldId;
302
        }
303
304
        // Delete fields from db
305
        if (count($deleteFields)) {
306
            $this->formFieldModel->deleteEntities($deleteFields);
307
        }
308
    }
309
310
    private function handleFilesDelete(Field $field)
311
    {
312
        if (!$field->isFileType()) {
313
            return;
314
        }
315
316
        $this->formUploader->deleteAllFilesOfFormField($field);
317
    }
318
319
    /**
320
     * @param $sessionActions
321
     */
322
    public function setActions(Form $entity, $sessionActions)
323
    {
324
        $order           = 1;
325
        $existingActions = $entity->getActions()->toArray();
326
        $savedFields     = $entity->getFields()->toArray();
327
328
        //match sessionId with field Id to update mapped fields
329
        $fieldIds = [];
330
        foreach ($savedFields as $field) {
331
            $fieldIds[$field->getSessionId()] = $field->getId();
332
        }
333
334
        foreach ($sessionActions as $properties) {
335
            $isNew  = (!empty($properties['id']) && isset($existingActions[$properties['id']])) ? false : true;
336
            $action = !$isNew ? $existingActions[$properties['id']] : new Action();
337
338
            foreach ($properties as $f => $v) {
339
                if (in_array($f, ['id', 'order'])) {
340
                    continue;
341
                }
342
343
                $func = 'set'.ucfirst($f);
344
345
                if ('properties' == $f) {
346
                    if (isset($v['mappedFields'])) {
347
                        foreach ($v['mappedFields'] as $pk => $pv) {
348
                            if (false !== strpos($pv, 'new')) {
349
                                $v['mappedFields'][$pk] = $fieldIds[$pv];
350
                            }
351
                        }
352
                    }
353
                }
354
355
                if (method_exists($action, $func)) {
356
                    $action->$func($v);
357
                }
358
            }
359
            $action->setForm($entity);
360
            $action->setOrder($order);
361
            ++$order;
362
            $entity->addAction($properties['id'], $action);
363
        }
364
365
        // Persist if form is being edited
366
        if ($entity->getId()) {
367
            $this->formActionModel->saveEntities($existingActions);
368
        }
369
    }
370
371
    /**
372
     * @param array $actions
373
     */
374
    public function deleteActions(Form $entity, $actions)
375
    {
376
        if (empty($actions)) {
377
            return;
378
        }
379
380
        $existingActions = $entity->getActions()->toArray();
381
        $deleteActions   = [];
382
        foreach ($actions as $actionId) {
383
            if (isset($existingActions[$actionId])) {
384
                $actionEntity = $this->em->getReference('MauticFormBundle:Action', (int) $actionId);
385
                $entity->removeAction($actionEntity);
386
                $deleteActions[] = $actionId;
387
            }
388
        }
389
390
        // Delete actions from db
391
        if (count($deleteActions)) {
392
            $this->formActionModel->deleteEntities($deleteActions);
393
        }
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399
    public function saveEntity($entity, $unlock = true)
400
    {
401
        $isNew = ($entity->getId()) ? false : true;
402
403
        if ($isNew && !$entity->getAlias()) {
404
            $alias = $this->cleanAlias($entity->getName(), '', 10);
405
            $entity->setAlias($alias);
406
        }
407
408
        //save the form so that the ID is available for the form html
409
        parent::saveEntity($entity, $unlock);
410
411
        //now build the form table
412
        if ($entity->getId()) {
413
            $this->createTableSchema($entity, $isNew);
414
        }
415
416
        $this->generateHtml($entity);
417
    }
418
419
    /**
420
     * Obtains the content.
421
     *
422
     * @param bool|true $withScript
423
     * @param bool|true $useCache
424
     *
425
     * @return string
426
     */
427
    public function getContent(Form $form, $withScript = true, $useCache = true)
428
    {
429
        $html = $this->getFormHtml($form, $useCache);
430
431
        if ($withScript) {
432
            $html = $this->getFormScript($form)."\n\n".$this->removeScriptTag($html);
433
        } else {
434
            $html = $this->removeScriptTag($html);
435
        }
436
437
        return $html;
438
    }
439
440
    /**
441
     * Obtains the cached HTML of a form and generates it if missing.
442
     *
443
     * @param bool|true $useCache
444
     *
445
     * @return string
446
     */
447
    public function getFormHtml(Form $form, $useCache = true)
448
    {
449
        if ($useCache && !$form->usesProgressiveProfiling()) {
450
            $cachedHtml = $form->getCachedHtml();
451
        }
452
453
        if (empty($cachedHtml)) {
454
            $cachedHtml = $this->generateHtml($form, $useCache);
455
        }
456
457
        if (!$form->getInKioskMode()) {
458
            $this->populateValuesWithLead($form, $cachedHtml);
459
        }
460
461
        return $cachedHtml;
462
    }
463
464
    /**
465
     * Get results for a form and lead.
466
     *
467
     * @param int $leadId
468
     * @param int $limit
469
     *
470
     * @return array
471
     */
472
    public function getLeadSubmissions(Form $form, $leadId, $limit = 200)
473
    {
474
        return $this->getRepository()->getFormResults(
475
            $form,
476
            [
477
                'leadId' => $leadId,
478
                'limit'  => $limit,
479
            ]
480
        );
481
    }
482
483
    /**
484
     * Generate the form's html.
485
     *
486
     * @param bool $persist
487
     *
488
     * @return string
489
     */
490
    public function generateHtml(Form $entity, $persist = true)
491
    {
492
        //generate cached HTML
493
        $theme       = $entity->getTemplate();
494
        $submissions = null;
495
        $lead        = ($this->request) ? $this->contactTracker->getContact() : null;
496
        $style       = '';
497
498
        if (!empty($theme)) {
499
            $theme .= '|';
500
        }
501
502
        if ($lead instanceof Lead && $lead->getId() && $entity->usesProgressiveProfiling()) {
503
            $submissions = $this->getLeadSubmissions($entity, $lead->getId());
504
        }
505
506
        if ($entity->getRenderStyle()) {
507
            $templating = $this->templatingHelper->getTemplating();
508
            $styleTheme = $theme.'MauticFormBundle:Builder:style.html.php';
509
            $style      = $templating->render($this->themeHelper->checkForTwigTemplate($styleTheme));
510
        }
511
512
        // Determine pages
513
        $fields = $entity->getFields()->toArray();
514
515
        // Ensure the correct order in case this is generated right after a form save with new fields
516
        uasort($fields, function ($a, $b) {
517
            if ($a->getOrder() === $b->getOrder()) {
518
                return 0;
519
            }
520
521
            return ($a->getOrder() < $b->getOrder()) ? -1 : 1;
522
        });
523
524
        $pages = ['open' => [], 'close' => []];
525
526
        $openFieldId  =
527
        $closeFieldId =
528
        $previousId   =
529
        $lastPage     = false;
530
        $pageCount    = 1;
531
532
        foreach ($fields as $fieldId => $field) {
533
            if ('pagebreak' == $field->getType() && $openFieldId) {
534
                // Open the page
535
                $pages['open'][$openFieldId] = $pageCount;
536
                $openFieldId                 = false;
537
                $lastPage                    = $fieldId;
538
539
                // Close the page at the next page break
540
                if ($previousId) {
541
                    $pages['close'][$previousId] = $pageCount;
542
543
                    ++$pageCount;
544
                }
545
            } else {
546
                if (!$openFieldId) {
547
                    $openFieldId = $fieldId;
548
                }
549
            }
550
551
            $previousId = $fieldId;
552
        }
553
554
        if (!empty($pages)) {
555
            if ($openFieldId) {
556
                $pages['open'][$openFieldId] = $pageCount;
557
            }
558
            if ($previousId !== $lastPage) {
559
                $pages['close'][$previousId] = $pageCount;
560
            }
561
        }
562
563
        $html = $this->templatingHelper->getTemplating()->render(
564
            $theme.'MauticFormBundle:Builder:form.html.php',
565
            [
566
                'fieldSettings' => $this->getCustomComponents()['fields'],
567
                'fields'        => $fields,
568
                'contactFields' => $this->leadFieldModel->getFieldListWithProperties(),
569
                'companyFields' => $this->leadFieldModel->getFieldListWithProperties('company'),
570
                'form'          => $entity,
571
                'theme'         => $theme,
572
                'submissions'   => $submissions,
573
                'lead'          => $lead,
574
                'formPages'     => $pages,
575
                'lastFormPage'  => $lastPage,
576
                'style'         => $style,
577
                'inBuilder'     => false,
578
            ]
579
        );
580
581
        if (!$entity->usesProgressiveProfiling()) {
582
            $entity->setCachedHtml($html);
583
584
            if ($persist) {
585
                //bypass model function as events aren't needed for this
586
                $this->getRepository()->saveEntity($entity);
587
            }
588
        }
589
590
        return $html;
591
    }
592
593
    /**
594
     * Creates the table structure for form results.
595
     *
596
     * @param bool $isNew
597
     * @param bool $dropExisting
598
     */
599
    public function createTableSchema(Form $entity, $isNew = false, $dropExisting = false)
600
    {
601
        //create the field as its own column in the leads table
602
        $name         = 'form_results_'.$entity->getId().'_'.$entity->getAlias();
603
        $columns      = $this->generateFieldColumns($entity);
604
        if ($isNew || (!$isNew && !$this->tableSchemaHelper->checkTableExists($name))) {
0 ignored issues
show
The condition $isNew is always false.
Loading history...
605
            $this->tableSchemaHelper->addTable([
606
                'name'    => $name,
607
                'columns' => $columns,
608
                'options' => [
609
                    'primaryKey'  => ['submission_id'],
610
                    'uniqueIndex' => ['submission_id', 'form_id'],
611
                ],
612
            ], true, $dropExisting);
613
            $this->tableSchemaHelper->executeChanges();
614
        } else {
615
            //check to make sure columns exist
616
            $columnSchemaHelper = $this->columnSchemaHelper->setName($name);
617
            foreach ($columns as $c) {
618
                if (!$columnSchemaHelper->checkColumnExists($c['name'])) {
619
                    $columnSchemaHelper->addColumn($c, false);
620
                }
621
            }
622
            $columnSchemaHelper->executeChanges();
623
        }
624
    }
625
626
    /**
627
     * {@inheritdoc}
628
     */
629
    public function deleteEntity($entity)
630
    {
631
        /* @var Form $entity */
632
        $this->deleteFormFiles($entity);
633
634
        if (!$entity->getId()) {
635
            //delete the associated results table
636
            $this->tableSchemaHelper->deleteTable('form_results_'.$entity->deletedId.'_'.$entity->getAlias());
637
            $this->tableSchemaHelper->executeChanges();
638
        }
639
        parent::deleteEntity($entity);
640
    }
641
642
    /**
643
     * {@inheritdoc}
644
     */
645
    public function deleteEntities($ids)
646
    {
647
        $entities     = parent::deleteEntities($ids);
648
        foreach ($entities as $id => $entity) {
649
            /* @var Form $entity */
650
            //delete the associated results table
651
            $this->tableSchemaHelper->deleteTable('form_results_'.$id.'_'.$entity->getAlias());
652
            $this->deleteFormFiles($entity);
653
        }
654
        $this->tableSchemaHelper->executeChanges();
655
656
        return $entities;
657
    }
658
659
    private function deleteFormFiles(Form $form)
660
    {
661
        $this->formUploader->deleteFilesOfForm($form);
662
    }
663
664
    /**
665
     * Generate an array of columns from fields.
666
     *
667
     * @return array
668
     */
669
    public function generateFieldColumns(Form $form)
670
    {
671
        $fields = $form->getFields()->toArray();
672
673
        $columns = [
674
            [
675
                'name' => 'submission_id',
676
                'type' => 'integer',
677
            ],
678
            [
679
                'name' => 'form_id',
680
                'type' => 'integer',
681
            ],
682
        ];
683
        $ignoreTypes = $this->getCustomComponents()['viewOnlyFields'];
684
        foreach ($fields as $f) {
685
            if (!in_array($f->getType(), $ignoreTypes) && false !== $f->getSaveResult()) {
686
                $columns[] = [
687
                    'name'    => $f->getAlias(),
688
                    'type'    => 'text',
689
                    'options' => [
690
                        'notnull' => false,
691
                    ],
692
                ];
693
            }
694
        }
695
696
        return $columns;
697
    }
698
699
    /**
700
     * Gets array of custom fields and submit actions from bundles subscribed FormEvents::FORM_ON_BUILD.
701
     *
702
     * @return mixed
703
     */
704
    public function getCustomComponents()
705
    {
706
        static $customComponents;
707
708
        if (empty($customComponents)) {
709
            //build them
710
            $event = new FormBuilderEvent($this->translator);
711
            $this->dispatcher->dispatch(FormEvents::FORM_ON_BUILD, $event);
712
            $customComponents['fields']     = $event->getFormFields();
713
            $customComponents['actions']    = $event->getSubmitActions();
714
            $customComponents['choices']    = $event->getSubmitActionGroups();
715
            $customComponents['validators'] = $event->getValidators();
716
717
            // Generate a list of fields that are not persisted to the database by default
718
            $notPersist = ['button', 'captcha', 'freetext', 'freehtml', 'pagebreak'];
719
            foreach ($customComponents['fields'] as $type => $field) {
720
                if (isset($field['builderOptions']) && isset($field['builderOptions']['addSaveResult']) && false === $field['builderOptions']['addSaveResult']) {
721
                    $notPersist[] = $type;
722
                }
723
            }
724
            $customComponents['viewOnlyFields'] = $notPersist;
725
        }
726
727
        return $customComponents;
728
    }
729
730
    /**
731
     * Get the document write javascript for the form.
732
     *
733
     * @return string
734
     */
735
    public function getAutomaticJavascript(Form $form)
736
    {
737
        $html       = $this->getContent($form, false);
738
        $formScript = $this->getFormScript($form);
739
740
        //replace line breaks with literal symbol and escape quotations
741
        $search        = ["\r\n", "\n", '"'];
742
        $replace       = ['', '', '\"'];
743
        $html          = str_replace($search, $replace, $html);
744
        $oldFormScript = str_replace($search, $replace, $formScript);
745
        $newFormScript = $this->generateJsScript($formScript);
746
747
        // Write html for all browser and fallback for IE
748
        $script = '
749
            var scr  = document.currentScript;
750
            var html = "'.$html.'";
751
            
752
            if (scr !== undefined) {
753
                scr.insertAdjacentHTML("afterend", html);
754
                '.$newFormScript.'
755
            } else {
756
                document.write("'.$oldFormScript.'"+html);
757
            }
758
        ';
759
760
        return $script;
761
    }
762
763
    /**
764
     * @return string
765
     */
766
    public function getFormScript(Form $form)
767
    {
768
        $theme = $form->getTemplate();
769
770
        if (!empty($theme)) {
771
            $theme .= '|';
772
        }
773
774
        $script = $this->templatingHelper->getTemplating()->render(
775
            $theme.'MauticFormBundle:Builder:script.html.php',
776
            [
777
                'form'  => $form,
778
                'theme' => $theme,
779
            ]
780
        );
781
782
        $html    = $this->getFormHtml($form);
783
        $scripts = $this->extractScriptTag($html);
784
785
        foreach ($scripts as $item) {
786
            $script .= $item."\n";
787
        }
788
789
        return $script;
790
    }
791
792
    /**
793
     * Writes in form values from get parameters.
794
     *
795
     * @param $form
796
     * @param $formHtml
797
     */
798
    public function populateValuesWithGetParameters(Form $form, &$formHtml)
799
    {
800
        $formName = $form->generateFormName();
801
802
        $fields = $form->getFields()->toArray();
803
        /** @var \Mautic\FormBundle\Entity\Field $f */
804
        foreach ($fields as $f) {
805
            $alias = $f->getAlias();
806
            if ($this->request->query->has($alias)) {
807
                $value = $this->request->query->get($alias);
808
809
                $this->fieldHelper->populateField($f, $value, $formName, $formHtml);
810
            }
811
        }
812
    }
813
814
    /**
815
     * @param $formHtml
816
     */
817
    public function populateValuesWithLead(Form $form, &$formHtml)
818
    {
819
        $formName       = $form->generateFormName();
820
        $fields         = $form->getFields();
821
        $autoFillFields = [];
822
823
        /** @var \Mautic\FormBundle\Entity\Field $field */
824
        foreach ($fields as $key => $field) {
825
            $leadField  = $field->getLeadField();
826
            $isAutoFill = $field->getIsAutoFill();
827
828
            // we want work just with matched autofill fields
829
            if (isset($leadField) && $isAutoFill) {
830
                $autoFillFields[$key] = $field;
831
            }
832
        }
833
834
        // no fields for populate
835
        if (!count($autoFillFields)) {
836
            return;
837
        }
838
839
        $lead = $this->contactTracker->getContact();
840
        if (!$lead instanceof Lead) {
841
            return;
842
        }
843
844
        foreach ($autoFillFields as $field) {
845
            $value = $lead->getFieldValue($field->getLeadField());
846
            // just skip string empty field
847
            if ('' !== $value) {
848
                $this->fieldHelper->populateField($field, $value, $formName, $formHtml);
849
            }
850
        }
851
    }
852
853
    /**
854
     * @param null $operator
855
     *
856
     * @return array
857
     */
858
    public function getFilterExpressionFunctions($operator = null)
859
    {
860
        $operatorOptions = [
861
            '=' => [
862
                'label'       => 'mautic.lead.list.form.operator.equals',
863
                'expr'        => 'eq',
864
                'negate_expr' => 'neq',
865
            ],
866
            '!=' => [
867
                'label'       => 'mautic.lead.list.form.operator.notequals',
868
                'expr'        => 'neq',
869
                'negate_expr' => 'eq',
870
            ],
871
            'gt' => [
872
                'label'       => 'mautic.lead.list.form.operator.greaterthan',
873
                'expr'        => 'gt',
874
                'negate_expr' => 'lt',
875
            ],
876
            'gte' => [
877
                'label'       => 'mautic.lead.list.form.operator.greaterthanequals',
878
                'expr'        => 'gte',
879
                'negate_expr' => 'lt',
880
            ],
881
            'lt' => [
882
                'label'       => 'mautic.lead.list.form.operator.lessthan',
883
                'expr'        => 'lt',
884
                'negate_expr' => 'gt',
885
            ],
886
            'lte' => [
887
                'label'       => 'mautic.lead.list.form.operator.lessthanequals',
888
                'expr'        => 'lte',
889
                'negate_expr' => 'gt',
890
            ],
891
            'like' => [
892
                'label'       => 'mautic.lead.list.form.operator.islike',
893
                'expr'        => 'like',
894
                'negate_expr' => 'notLike',
895
            ],
896
            '!like' => [
897
                'label'       => 'mautic.lead.list.form.operator.isnotlike',
898
                'expr'        => 'notLike',
899
                'negate_expr' => 'like',
900
            ],
901
            'startsWith' => [
902
                'label'       => 'mautic.core.operator.starts.with',
903
                'expr'        => 'startsWith',
904
                'negate_expr' => 'startsWith',
905
            ],
906
            'endsWith' => [
907
                'label'       => 'mautic.core.operator.ends.with',
908
                'expr'        => 'endsWith',
909
                'negate_expr' => 'endsWith',
910
            ],
911
            'contains' => [
912
                'label'       => 'mautic.core.operator.contains',
913
                'expr'        => 'contains',
914
                'negate_expr' => 'contains',
915
            ],
916
        ];
917
918
        return (null === $operator) ? $operatorOptions : $operatorOptions[$operator];
919
    }
920
921
    /**
922
     * Get a list of assets in a date range.
923
     *
924
     * @param int       $limit
925
     * @param \DateTime $dateFrom
926
     * @param \DateTime $dateTo
927
     * @param array     $filters
928
     * @param array     $options
929
     *
930
     * @return array
931
     */
932
    public function getFormList($limit = 10, \DateTime $dateFrom = null, \DateTime $dateTo = null, $filters = [], $options = [])
933
    {
934
        $q = $this->em->getConnection()->createQueryBuilder();
935
        $q->select('t.id, t.name, t.date_added, t.date_modified')
936
            ->from(MAUTIC_TABLE_PREFIX.'forms', 't')
937
            ->setMaxResults($limit);
938
939
        if (!empty($options['canViewOthers'])) {
940
            $q->andWhere('t.created_by = :userId')
941
                ->setParameter('userId', $this->userHelper->getUser()->getId());
942
        }
943
944
        $chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
945
        $chartQuery->applyFilters($q, $filters);
946
        $chartQuery->applyDateFilters($q, 'date_added');
947
948
        return $q->execute()->fetchAll();
949
    }
950
951
    /**
952
     * Load HTML consider Libxml < 2.7.8.
953
     *
954
     * @param $html
955
     */
956
    private function loadHTML(&$dom, $html)
957
    {
958
        if (defined('LIBXML_HTML_NOIMPLIED') && defined('LIBXML_HTML_NODEFDTD')) {
959
            $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
960
        } else {
961
            $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
962
        }
963
    }
964
965
    /**
966
     * Save HTML consider Libxml < 2.7.8.
967
     *
968
     * @param $html
969
     *
970
     * @return string
971
     */
972
    private function saveHTML($dom, $html)
973
    {
974
        if (defined('LIBXML_HTML_NOIMPLIED') && defined('LIBXML_HTML_NODEFDTD')) {
975
            return $dom->saveHTML($html);
976
        } else {
977
            // remove DOCTYPE, <html>, and <body> tags for old libxml
978
            return preg_replace('/^<!DOCTYPE.+?>/', '', str_replace(['<html>', '</html>', '<body>', '</body>'], ['', '', '', ''], $dom->saveHTML($html)));
979
        }
980
    }
981
982
    /**
983
     * Extract script from html.
984
     *
985
     * @param $html
986
     *
987
     * @return array
988
     */
989
    private function extractScriptTag($html)
990
    {
991
        libxml_use_internal_errors(true);
992
        $dom = new DOMDocument();
993
        $this->loadHTML($dom, $html);
994
        $items = $dom->getElementsByTagName('script');
995
996
        $scripts = [];
997
        foreach ($items as $script) {
998
            $scripts[] = $this->saveHTML($dom, $script);
999
        }
1000
1001
        return $scripts;
1002
    }
1003
1004
    /**
1005
     * Remove script from html.
1006
     *
1007
     * @param $html
1008
     *
1009
     * @return string
1010
     */
1011
    private function removeScriptTag($html)
1012
    {
1013
        libxml_use_internal_errors(true);
1014
        $dom = new DOMDocument();
1015
        $this->loadHTML($dom, '<div>'.$html.'</div>');
1016
        $items = $dom->getElementsByTagName('script');
1017
1018
        $remove = [];
1019
        foreach ($items as $item) {
1020
            $remove[] = $item;
1021
        }
1022
1023
        foreach ($remove as $item) {
1024
            $item->parentNode->removeChild($item);
1025
        }
1026
1027
        $root   = $dom->documentElement;
1028
        $result = '';
1029
        foreach ($root->childNodes as $childNode) {
1030
            $result .= $this->saveHTML($dom, $childNode);
1031
        }
1032
1033
        return $result;
1034
    }
1035
1036
    /**
1037
     * Generate dom manipulation javascript to include all script.
1038
     *
1039
     * @param $html
1040
     *
1041
     * @return string
1042
     */
1043
    private function generateJsScript($html)
1044
    {
1045
        libxml_use_internal_errors(true);
1046
        $dom = new DOMDocument();
1047
        $this->loadHTML($dom, '<div>'.$html.'</div>');
1048
        $items = $dom->getElementsByTagName('script');
1049
1050
        $javascript = '';
1051
        foreach ($items as $key => $script) {
1052
            if ($script->hasAttribute('src')) {
1053
                $javascript .= "
1054
                var script$key = document.createElement('script');
1055
                script$key.src = '".$script->getAttribute('src')."';
1056
                document.getElementsByTagName('head')[0].appendChild(script$key);";
1057
            } else {
1058
                $scriptContent = $script->nodeValue;
1059
                $scriptContent = str_replace(["\r\n", "\n", '"'], ['', '', '\"'], $scriptContent);
1060
1061
                $javascript .= "
1062
                var inlineScript$key = document.createTextNode(\"$scriptContent\");
1063
                var script$key       = document.createElement('script');
1064
                script$key.appendChild(inlineScript$key);
1065
                document.getElementsByTagName('head')[0].appendChild(script$key);";
1066
            }
1067
        }
1068
1069
        return $javascript;
1070
    }
1071
1072
    /**
1073
     * Finds out whether the.
1074
     */
1075
    private function addLeadFieldOptions(Field $formField)
1076
    {
1077
        $formFieldProps    = $formField->getProperties();
1078
        $contactFieldAlias = $formField->getLeadField();
1079
1080
        if (empty($formFieldProps['syncList']) || empty($contactFieldAlias)) {
1081
            return;
1082
        }
1083
1084
        $contactField = $this->leadFieldModel->getEntityByAlias($contactFieldAlias);
1085
1086
        if (empty($contactField) || !in_array($contactField->getType(), ContactFieldHelper::getListTypes())) {
1087
            return;
1088
        }
1089
1090
        $contactFieldProps = $contactField->getProperties();
1091
1092
        switch ($contactField->getType()) {
1093
            case 'select':
1094
            case 'multiselect':
1095
            case 'lookup':
1096
                $list = isset($contactFieldProps['list']) ? $contactFieldProps['list'] : [];
1097
                break;
1098
            case 'boolean':
1099
                $list = [$contactFieldProps['no'], $contactFieldProps['yes']];
1100
                break;
1101
            case 'country':
1102
                $list = ContactFieldHelper::getCountryChoices();
1103
                break;
1104
            case 'region':
1105
                $list = ContactFieldHelper::getRegionChoices();
1106
                break;
1107
            case 'timezone':
1108
                $list = ContactFieldHelper::getTimezonesChoices();
1109
                break;
1110
            case 'locale':
1111
                $list = ContactFieldHelper::getLocaleChoices();
1112
                break;
1113
            default:
1114
                return;
1115
        }
1116
1117
        if (!empty($list)) {
1118
            $formFieldProps['list'] = ['list' => $list];
1119
            if (array_key_exists('optionlist', $formFieldProps)) {
1120
                $formFieldProps['optionlist'] = ['list' => $list];
1121
            }
1122
            $formField->setProperties($formFieldProps);
1123
        }
1124
    }
1125
1126
    /**
1127
     * @param string $fieldAlias
1128
     *
1129
     * @return Field|null
1130
     */
1131
    public function findFormFieldByAlias(Form $form, $fieldAlias)
1132
    {
1133
        foreach ($form->getFields() as $field) {
1134
            if ($field->getAlias() === $fieldAlias) {
1135
                return $field;
1136
            }
1137
        }
1138
1139
        return null;
1140
    }
1141
}
1142