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
introduced
by
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 |