Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form object refactoring #11234

Closed
wants to merge 62 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
405e3ad
Adding mappedObject and mappedField columns to the FormField entity
escopecz Apr 15, 2020
1e8abe9
Adding Mapped Object field to the form field edit form
escopecz Apr 16, 2020
28ebb5f
Removing duplicated interface
escopecz Apr 16, 2020
dde88f6
Field collect event added
escopecz Apr 16, 2020
6b5ca9d
Switch mapping field options when object changes
escopecz Apr 17, 2020
9a7a933
Set the 'data-list-type' option param similarly as previous implement…
escopecz Apr 30, 2020
c1cd73f
Refactoring used mapped fields from sesssion to cache
escopecz May 5, 2020
220cdc4
Implementing removing already used mapped fields
escopecz May 6, 2020
0749e27
Replacing clearing leadfields session with clearing mappedFieldCollector
escopecz May 21, 2020
6579b61
Fixing a countable error that happens on PHP7
escopecz May 21, 2020
93d44f8
Tests for showForContact()
escopecz May 21, 2020
a031c8e
Refactoring FieldValueTransformer to use mapped field instead of lead…
escopecz May 22, 2020
1a80497
Speeding up test from 11s to 8s
escopecz May 22, 2020
6c1424d
Speeding up SubmissionModelTest, removing inheritance in tests, not c…
escopecz May 22, 2020
6c4e67d
Removing refactored (commented out) code
escopecz May 22, 2020
31a3127
Testing populateValuesWithLead()
escopecz May 22, 2020
400c9e2
Replacing leadField with mappedField
escopecz May 22, 2020
c4917fe
Refactoring getLeadField() with getMappedField()
escopecz May 22, 2020
f2c0701
Refactoring getLeadField() to getMappedField() for SubmissionModel
escopecz May 22, 2020
c175152
Refactoring occurances of 'leadField' with 'mappedField' in templates
escopecz May 22, 2020
87bc7ea
Refactoring getLeadField() with getMappedField() in citrix subscriber
escopecz May 22, 2020
bca86e4
Renaming collector to better describe what it stores
escopecz May 26, 2020
0520879
Adding method getMappedFieldObjects that will return all objects from…
escopecz May 26, 2020
ec5f239
Refactoring templates from contact and company fields to mapped field…
escopecz May 26, 2020
6562064
Refactoring out of contact and company fields to mapped fields part 2
escopecz May 28, 2020
668d8c5
Shorter, readable syntax
escopecz May 29, 2020
2c768b1
Mapped fields must be passed to select via country template
escopecz May 29, 2020
580cdec
The param cannot be null, but the value can. Retyping
escopecz May 29, 2020
b5f0f90
Ensure we can edit a mapped field and the original value exists
escopecz May 29, 2020
ce00ed6
Renaming private props for new use
escopecz May 29, 2020
df20724
Use field aliases over IDs as we used to
escopecz May 29, 2020
314672a
Toggle the "ingerit choices from mapped field" option for the list fi…
escopecz May 29, 2020
cbc6d36
Fixing company icon
escopecz May 29, 2020
e827024
Adding object to the notice about linked field
escopecz May 29, 2020
877d937
Storing the original mapped field to a hidden field so we could exclu…
escopecz May 29, 2020
c3668c1
Removing unnecessary question mark
escopecz Jun 1, 2020
44a6b5a
CS and PHPSTAN fixes
escopecz Jun 1, 2020
7232d06
Adding missing dependency to a test
escopecz Jun 1, 2020
14603cc
mapped field must be type of string
escopecz Jun 1, 2020
bf00f33
Functional Form API test v1 added
escopecz Jun 1, 2020
247cb27
Fixing tests
escopecz Jun 1, 2020
fb19ee1
Ensuring the leadField will be filled from the mappedField and vice v…
escopecz Jun 1, 2020
ae32125
Trying to test submissions
escopecz Jun 2, 2020
dd1a6f6
More unit tests
escopecz Jun 2, 2020
f03aa1e
$leadFieldMatches should contain also company fields
escopecz Jun 3, 2020
720da0b
New unit tests
escopecz Jun 3, 2020
495a713
Removing unused method
escopecz Jun 3, 2020
03b3dae
Removing unused property
escopecz Jun 4, 2020
9df1cab
Fixing pre-selected mapping + test
escopecz Jun 4, 2020
a365fc4
Ensure the $type is string (can be null)
escopecz Jun 4, 2020
ad20d71
Test getting form field choices for specific object
escopecz Jun 4, 2020
5f493e8
Fixing progressive profiling
escopecz Jun 4, 2020
30c0d94
Removing dead code
escopecz Jun 9, 2020
fb7d748
Ensuring the variables are always defined
escopecz Jun 9, 2020
4307f13
return type comment fix
escopecz Jun 9, 2020
681d4f6
Adding tests requested in CR
escopecz Jun 9, 2020
00df99d
Fixed FormApiControllerFunctionalTest
fedys Oct 6, 2020
eaf07de
Fixing adding new captcha field to a form
escopecz Feb 22, 2021
52f1e31
Test fixes
escopecz Jun 9, 2022
b5ff46f
CS fixes
escopecz Jun 9, 2022
9dc21ed
WIP test fixes
escopecz Jun 9, 2022
8fbe5a6
STAN fixes
escopecz Jun 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions app/bundles/FormBundle/Assets/js/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,42 @@ Mautic.formBuilderNewComponentInit = function () {
mQuery(this).val('');
mQuery(this).trigger('chosen:updated');
});
}
};

Mautic.changeSelectOptions = function(selectEl, options) {
selectEl.empty();
mQuery.each(options, function(key, field) {
selectEl.append(
mQuery('<option></option>')
.attr('value', field.value)
.attr('data-list-type', field.isListType ? 1 : 0)
.text(field.label)
);
});
selectEl.trigger('chosen:updated');
};

Mautic.fetchFieldsOnObjectChange = function() {
var fieldSelect = mQuery('select#formfield_mappedField');
fieldSelect.attr('disable', true);
mQuery.ajax({
url: mauticAjaxUrl + "?action=form:getFieldsForObject",
data: {
mappedObject: mQuery('select#formfield_mappedObject').val(),
mappedField: mQuery('input#formfield_originalMappedField').val(),
formId: mQuery('input#mauticform_sessionId').val()
},
success: function (response) {
Mautic.changeSelectOptions(fieldSelect, response.fields);
},
error: function (response, textStatus, errorThrown) {
Mautic.processAjaxError(response, textStatus, errorThrown);
},
complete: function () {
fieldSelect.removeAttr('disable');
}
});
};

Mautic.updateFormFields = function () {
Mautic.activateLabelLoadingIndicator('campaignevent_properties_field');
Expand Down Expand Up @@ -344,4 +379,4 @@ Mautic.selectFormType = function(formType) {

mQuery('.form-type-modal').remove();
mQuery('.form-type-modal-backdrop').remove();
};
};
56 changes: 56 additions & 0 deletions app/bundles/FormBundle/Collection/FieldCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collection;

use Mautic\FormBundle\Crate\FieldCrate;
use Mautic\FormBundle\Exception\FieldNotFoundException;

/**
* @extends \ArrayIterator<int,FieldCrate>
*/
final class FieldCollection extends \ArrayIterator
{
/**
* @return array<string,string>
*/
public function toChoices(): array
{
$choices = [];

/** @var FieldCrate $field */
foreach ($this as $field) {
$choices[$field->getName()] = $field->getKey();
}

return $choices;
}

public function getFieldByKey(string $key): FieldCrate
{
/** @var FieldCrate $field */
foreach ($this as $field) {
if ($key === $field->getKey()) {
return $field;
}
}

throw new FieldNotFoundException("Field with key {$key} was not found.");
}

/**
* @param string[] $keys
*/
public function removeFieldsWithKeys(array $keys, string $keyToKeep = null): FieldCollection
{
return new self(
array_filter(
$this->getArrayCopy(),
function (FieldCrate $field) use ($keys, $keyToKeep) {
return ($keyToKeep && $field->getKey() === $keyToKeep) || !in_array($field->getKey(), $keys, true);
}
)
);
}
}
12 changes: 12 additions & 0 deletions app/bundles/FormBundle/Collection/MappedObjectCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collection;

/**
* @extends \ArrayIterator<string,FieldCollection>
*/
final class MappedObjectCollection extends \ArrayIterator
{
}
28 changes: 28 additions & 0 deletions app/bundles/FormBundle/Collection/ObjectCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collection;

use Mautic\FormBundle\Crate\ObjectCrate;

/**
* @extends \ArrayIterator<int,ObjectCrate>
*/
final class ObjectCollection extends \ArrayIterator
{
/**
* @return array<string,string>
*/
public function toChoices(): array
{
$choices = [];

/** @var ObjectCrate $object */
foreach ($this as $object) {
$choices[$object->getName()] = $object->getKey();
}

return $choices;
}
}
81 changes: 81 additions & 0 deletions app/bundles/FormBundle/Collector/AlreadyMappedFieldCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collector;

use Mautic\CacheBundle\Cache\CacheProviderInterface;

/**
* We need to store mapped fields in the form field builder so we could remove the used ones from the select box.
*/
final class AlreadyMappedFieldCollector implements AlreadyMappedFieldCollectorInterface
{
private const EXPIRATION_IN_SECONDS = 18000; // 5 hours

private CacheProviderInterface $cacheProvider;

public function __construct(CacheProviderInterface $cacheProvider)
{
$this->cacheProvider = $cacheProvider;
}

public function getFields(string $formId, string $object): array
{
$cacheItem = $this->cacheProvider->getItem($this->buildCacheKey($formId, $object));

return json_decode($cacheItem->get() ?? '[]', true);
}

public function addField(string $formId, string $object, string $fieldKey): void
{
$this->fetchAndSave($formId, $object, function (array $fields) use ($fieldKey) {
if (!in_array($fieldKey, $fields, true)) {
$fields[] = $fieldKey;
}

return $fields;
});
}

public function removeField(string $formId, string $object, string $fieldKey): void
{
$this->fetchAndSave($formId, $object, function (array $fields) use ($fieldKey) {
$cacheKey = array_search($fieldKey, $fields, true);

if (false !== $cacheKey) {
unset($fields[$cacheKey]);

// Reset indexes.
$fields = array_values($fields);
}

return $fields;
});
}

public function removeAllForForm(string $formId): void
{
$this->cacheProvider->invalidateTags([$this->buildCacheTag($formId)]);
}

private function fetchAndSave(string $formId, string $object, callable $callback): void
{
$cacheItem = $this->cacheProvider->getItem($this->buildCacheKey($formId, $object));
$fields = json_decode($cacheItem->get() ?? '[]', true);
$cacheItem->set(json_encode($callback($fields)));
$cacheItem->expiresAfter(self::EXPIRATION_IN_SECONDS);
$cacheItem->tag($this->buildCacheTag($formId));
$this->cacheProvider->save($cacheItem);
}

private function buildCacheKey(string $formId, string $object): string
{
return sprintf('mautic.form.%s.object.%s.fields.mapped', $formId, $object);
}

private function buildCacheTag(string $formId): string
{
return sprintf('mautic.form.%s.fields.mapped', $formId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collector;

interface AlreadyMappedFieldCollectorInterface
{
/**
* @param string $formId can be a string hash for new forms
*
* @return mixed[]
*/
public function getFields(string $formId, string $object): array;

public function addField(string $formId, string $object, string $fieldKey): void;

public function removeField(string $formId, string $object, string $fieldKey): void;

/**
* Removes all mapped fields for the specified form.
*/
public function removeAllForForm(string $formId): void;
}
41 changes: 41 additions & 0 deletions app/bundles/FormBundle/Collector/FieldCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collector;

use Mautic\FormBundle\Collection\FieldCollection;
use Mautic\FormBundle\Event\FieldCollectEvent;
use Mautic\FormBundle\FormEvents;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

final class FieldCollector implements FieldCollectorInterface
{
private EventDispatcherInterface $dispatcher;

/**
* @var FieldCollection[]
*/
private array $fieldCollections = [];

public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}

public function getFields(string $object): FieldCollection
{
if (!isset($this->fieldCollections[$object])) {
$this->collect($object);
}

return $this->fieldCollections[$object];
}

private function collect(string $object): void
{
$event = new FieldCollectEvent($object);
$this->dispatcher->dispatch(FormEvents::ON_FIELD_COLLECT, $event);
$this->fieldCollections[$object] = $event->getFields();
}
}
12 changes: 12 additions & 0 deletions app/bundles/FormBundle/Collector/FieldCollectorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collector;

use Mautic\FormBundle\Collection\FieldCollection;

interface FieldCollectorInterface
{
public function getFields(string $object): FieldCollection;
}
30 changes: 30 additions & 0 deletions app/bundles/FormBundle/Collector/MappedObjectCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collector;

use Mautic\FormBundle\Collection\MappedObjectCollection;

final class MappedObjectCollector implements MappedObjectCollectorInterface
{
private FieldCollectorInterface $fieldCollector;

public function __construct(FieldCollectorInterface $fieldCollector)
{
$this->fieldCollector = $fieldCollector;
}

public function buildCollection(string ...$objects): MappedObjectCollection
{
$mappedObjectCollection = new MappedObjectCollection();

foreach ($objects as $object) {
if ($object) {
$mappedObjectCollection->offsetSet($object, $this->fieldCollector->getFields($object));
}
}

return $mappedObjectCollection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collector;

use Mautic\FormBundle\Collection\MappedObjectCollection;

interface MappedObjectCollectorInterface
{
public function buildCollection(string ...$objects): MappedObjectCollection;
}
37 changes: 37 additions & 0 deletions app/bundles/FormBundle/Collector/ObjectCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Mautic\FormBundle\Collector;

use Mautic\FormBundle\Collection\ObjectCollection;
use Mautic\FormBundle\Event\ObjectCollectEvent;
use Mautic\FormBundle\FormEvents;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

final class ObjectCollector implements ObjectCollectorInterface
{
private EventDispatcherInterface $dispatcher;
private ?ObjectCollection $objects = null;

public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}

public function getObjects(): ObjectCollection
{
if (null === $this->objects) {
$this->collect();
}

return $this->objects;
}

private function collect(): void
{
$event = new ObjectCollectEvent();
$this->dispatcher->dispatch(FormEvents::ON_OBJECT_COLLECT, $event);
$this->objects = $event->getObjects();
}
}
Loading