Spaces:
No application file
No application file
namespace Mautic\LeadBundle\Controller\Api; | |
use Doctrine\ORM\Tools\Pagination\Paginator; | |
use Mautic\CoreBundle\Cache\ResultCacheOptions; | |
use Mautic\LeadBundle\Entity\Company; | |
use Mautic\LeadBundle\Entity\CustomFieldEntityInterface; | |
use Mautic\LeadBundle\Entity\Lead; | |
use Mautic\LeadBundle\Entity\LeadField; | |
use Mautic\LeadBundle\Model\FieldModel; | |
use Symfony\Component\Form\Form; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
trait CustomFieldsApiControllerTrait | |
{ | |
private ?RequestStack $requestStack = null; | |
/** | |
* @var mixed[] | |
*/ | |
private $fieldCache = []; | |
/** | |
* Remove IpAddress and lastActive as it'll be handled outside the form. | |
* | |
* @param mixed[] $parameters | |
* @param Lead|Company $entity | |
* @param string $action | |
* | |
* @return mixed|void | |
*/ | |
protected function prepareParametersForBinding(Request $request, $parameters, $entity, $action) | |
{ | |
if ('company' === $this->entityNameOne) { | |
$object = 'company'; | |
} else { | |
$object = 'lead'; | |
unset($parameters['lastActive'], $parameters['tags'], $parameters['ipAddress']); | |
} | |
if (in_array($request->getMethod(), ['POST', 'PUT'])) { | |
// If a new contact or PUT update (complete representation of the objectd), set empty fields to field defaults if the parameter | |
// is not defined in the request | |
/** @var FieldModel $fieldModel */ | |
$fieldModel = $this->getModel('lead.field'); | |
$fields = $fieldModel->getFieldListWithProperties($object); | |
foreach ($fields as $alias => $field) { | |
// Set the default value if the parameter is not included in the request, there is no value for the given entity, and a default is defined | |
$currentValue = $entity->getFieldValue($alias); | |
if (!isset($parameters[$alias]) && ('' === $currentValue || null == $currentValue) && '' !== $field['defaultValue'] && null !== $field['defaultValue']) { | |
$parameters[$alias] = $field['defaultValue']; | |
} | |
} | |
} | |
return $parameters; | |
} | |
/** | |
* Flatten fields into an 'all' key for dev convenience. | |
*/ | |
protected function preSerializeEntity(object $entity, string $action = 'view'): void | |
{ | |
if ($entity instanceof CustomFieldEntityInterface) { | |
$fields = $entity->getFields(); | |
$fields['all'] = $entity->getProfileFields(); | |
// Temporary hack to address numbers being type casted to float which broke some API implementations because M2 used to return | |
// these as strings and values are normalized in a dozen differneet ways throughout LeadModel::setFieldValues methods and became | |
// too risky to hotfix | |
$fields = $this->fixNumbers($fields); | |
$entity->setFields($fields); | |
} | |
} | |
/** | |
* @param mixed[] $fields | |
* | |
* @return mixed[] | |
*/ | |
private function fixNumbers(array $fields): array | |
{ | |
$numberFields = []; | |
foreach ($fields as $group => $groupFields) { | |
if ('all' === $group) { | |
continue; | |
} | |
foreach ($groupFields as $field => $fieldDefinition) { | |
if ('points' === $field) { | |
// Points were always a number in M2 | |
$numberFields[$field] = (int) $fields[$group][$field]['value']; | |
} | |
if ('number' !== $fieldDefinition['type'] || null === $fields[$group][$field]['value']) { | |
continue; | |
} | |
// Some requests don't seem to have properties unserialized by default (even in M2) | |
if (!isset($fieldDefinition['properties'])) { | |
$fieldDefinition['properties'] = []; | |
} | |
$properties = is_string($fieldDefinition['properties']) ? unserialize($fieldDefinition['properties']) : $fieldDefinition['properties']; | |
$fields[$group][$field]['value'] = empty($properties['scale']) ? (int) $fields[$group][$field]['value'] | |
: (float) $fields[$group][$field]['value']; | |
$fields[$group][$field]['normalizedValue'] = empty($properties['scale']) ? (int) $fields[$group][$field]['normalizedValue'] | |
: (float) $fields[$group][$field]['normalizedValue']; | |
$numberFields[$field] = $fields[$group][$field]['value']; | |
} | |
} | |
// Fix "all" fields | |
$fields['all'] = array_merge($fields['all'], $numberFields); | |
return $fields; | |
} | |
/** | |
* @return array<string, mixed> | |
*/ | |
protected function getEntityFormOptions(): array | |
{ | |
$object = ('company' === $this->entityNameOne) ? 'company' : 'lead'; | |
if (isset($this->fieldCache[$object])) { | |
return $this->fieldCache[$object]; | |
} | |
$model = $this->getModel('lead.field'); | |
\assert($model instanceof FieldModel); | |
$fields = $model->getEntities( | |
[ | |
'filter' => [ | |
'force' => [ | |
[ | |
'column' => 'f.isPublished', | |
'expr' => 'eq', | |
'value' => true, | |
], | |
[ | |
'column' => 'f.object', | |
'expr' => 'eq', | |
'value' => $object, | |
], | |
], | |
], | |
'hydration_mode' => 'HYDRATE_ARRAY', | |
'result_cache' => new ResultCacheOptions(LeadField::CACHE_NAMESPACE), | |
] | |
); | |
\assert($fields instanceof Paginator); | |
$this->fieldCache[$object] = ['fields' => $fields->getIterator()]; | |
return $this->fieldCache[$object]; | |
} | |
/** | |
* @param Lead|Company $entity | |
* @param Form $form | |
* @param mixed[] $parameters | |
* @param bool $isPostOrPatch | |
* | |
* @return bool|void | |
*/ | |
protected function setCustomFieldValues($entity, $form, $parameters, $isPostOrPatch = false) | |
{ | |
// set the custom field values | |
// pull the data from the form in order to apply the form's formatting | |
foreach ($form as $f) { | |
$parameters[$f->getName()] = $f->getData(); | |
} | |
if ($isPostOrPatch) { | |
// Don't overwrite the contacts accumulated points | |
if (isset($parameters['points']) && empty($parameters['points'])) { | |
unset($parameters['points']); | |
} | |
// When merging a contact because of a unique identifier match in POST /api/contacts//new or PATCH /api/contacts//edit all 0 values must be unset because | |
// we have to assume 0 was not meant to overwrite an existing value. Other empty values will be caught by LeadModel::setFieldValues | |
$parameters = array_filter( | |
$parameters, | |
function ($value): bool { | |
if (is_numeric($value)) { | |
return 0 !== (int) $value; | |
} | |
return true; | |
} | |
); | |
} | |
$overwriteWithBlank = !$isPostOrPatch; | |
if (isset($parameters['overwriteWithBlank']) && !empty($parameters['overwriteWithBlank'])) { | |
$overwriteWithBlank = true; | |
unset($parameters['overwriteWithBlank']); | |
} | |
$this->model->setFieldValues($entity, $parameters, $overwriteWithBlank); | |
} | |
/** | |
* @param string $object | |
* | |
* @return void | |
*/ | |
protected function setCleaningRules($object = 'lead') | |
{ | |
$leadFieldModel = $this->getModel('lead.field'); | |
\assert($leadFieldModel instanceof FieldModel); | |
$fields = $leadFieldModel->getFieldListWithProperties($object); | |
foreach ($fields as $field) { | |
if (!empty($field['properties']['allowHtml'])) { | |
$this->dataInputMasks[$field['alias']] = 'html'; /** @phpstan-ignore-line this is accessing a property from the parent class. Terrible. Refactor for M6. */ | |
} | |
} | |
} | |
public function setRequestStack(RequestStack $requestStack): void | |
{ | |
$this->requestStack = $requestStack; | |
} | |
} | |