Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Controller\LeadAccessTrait;
|
||||
use Mautic\LeadBundle\Entity\Company;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Helper\IdentifyCompanyHelper;
|
||||
use Mautic\LeadBundle\Model\CompanyModel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<Company>
|
||||
*/
|
||||
class CompanyApiController extends CommonApiController
|
||||
{
|
||||
use CustomFieldsApiControllerTrait;
|
||||
use LeadAccessTrait;
|
||||
|
||||
/**
|
||||
* @var CompanyModel|null
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper)
|
||||
{
|
||||
$companyModel = $modelFactory->getModel('lead.company');
|
||||
\assert($companyModel instanceof CompanyModel);
|
||||
|
||||
$this->model = $companyModel;
|
||||
$this->entityClass = Company::class;
|
||||
$this->entityNameOne = 'company';
|
||||
$this->entityNameMulti = 'companies';
|
||||
$this->serializerGroups[] = 'companyDetails';
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
public function getNewEntity(array $params)
|
||||
{
|
||||
$leadCompanyModel = $this->getModel('lead.company');
|
||||
\assert($leadCompanyModel instanceof CompanyModel);
|
||||
[$company, $companyEntities] = IdentifyCompanyHelper::findCompany($params, $leadCompanyModel);
|
||||
if (count($companyEntities)) {
|
||||
return $this->model->getEntity($company['id']);
|
||||
}
|
||||
|
||||
return $this->model->getEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Lead &$entity
|
||||
* @param string $action
|
||||
*/
|
||||
protected function preSaveEntity(&$entity, $form, $parameters, $action = 'edit')
|
||||
{
|
||||
$this->setCustomFieldValues($entity, $form, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a contact to a company.
|
||||
*
|
||||
* @param int $companyId Company ID
|
||||
* @param int $contactId Contact ID
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function addContactAction($companyId, $contactId)
|
||||
{
|
||||
$company = $this->model->getEntity($companyId);
|
||||
$view = $this->view(['success' => 1], Response::HTTP_OK);
|
||||
|
||||
if (null === $company) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$contact = $this->checkLeadAccess($contactId, 'edit');
|
||||
if ($contact instanceof Response) {
|
||||
return $contact;
|
||||
}
|
||||
|
||||
$this->model->addLeadToCompany($company, $contact);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given contact from a company.
|
||||
*
|
||||
* @param int $companyId List ID
|
||||
* @param int $contactId Lead ID
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function removeContactAction($companyId, $contactId)
|
||||
{
|
||||
$company = $this->model->getEntity($companyId);
|
||||
$view = $this->view(['success' => 1], Response::HTTP_OK);
|
||||
|
||||
if (null === $company) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$contactModel = $this->getModel('lead');
|
||||
$contact = $contactModel->getEntity($contactId);
|
||||
|
||||
// Does the contact exist and the user has permission to edit
|
||||
if (null === $contact) {
|
||||
return $this->notFound();
|
||||
} elseif (!$this->security->hasEntityAccess('lead:leads:editown', 'lead:leads:editother', $contact->getPermissionUser())) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$this->model->removeLeadFromCompany($company, $contact);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
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 (!empty($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);
|
||||
}
|
||||
|
||||
#[\Symfony\Contracts\Service\Attribute\Required]
|
||||
public function setRequestStack(RequestStack $requestStack): void
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\CoreBundle\Entity\FormEntity;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Controller\LeadAccessTrait;
|
||||
use Mautic\LeadBundle\Entity\LeadDevice;
|
||||
use Mautic\LeadBundle\Model\DeviceModel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<LeadDevice>
|
||||
*/
|
||||
class DeviceApiController extends CommonApiController
|
||||
{
|
||||
use LeadAccessTrait;
|
||||
|
||||
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper)
|
||||
{
|
||||
$leadDeviceModel = $modelFactory->getModel('lead.device');
|
||||
\assert($leadDeviceModel instanceof DeviceModel);
|
||||
|
||||
$this->model = $leadDeviceModel;
|
||||
$this->entityClass = LeadDevice::class;
|
||||
$this->entityNameOne = 'device';
|
||||
$this->entityNameMulti = 'devices';
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LeadDevice &$entity
|
||||
* @param string $action
|
||||
*/
|
||||
protected function preSaveEntity(&$entity, $form, $parameters, $action = 'edit')
|
||||
{
|
||||
$lead = null;
|
||||
if (!empty($parameters['lead'])) {
|
||||
$lead = $parameters['lead'];
|
||||
} elseif (!empty($parameters['contact'])) {
|
||||
$lead = $parameters['contact'];
|
||||
}
|
||||
if ($lead) {
|
||||
$lead = $this->checkLeadAccess($lead, $action);
|
||||
|
||||
if ($lead instanceof Response) {
|
||||
return $lead;
|
||||
}
|
||||
|
||||
$entity->setLead($lead);
|
||||
unset($parameters['lead'], $parameters['contact']);
|
||||
} elseif ('new' === $action) {
|
||||
return $this->returnError('contact ID is mandatory', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LeadDevice|FormEntity $entity
|
||||
*/
|
||||
protected function checkEntityAccess($entity, $action = 'view')
|
||||
{
|
||||
return parent::checkEntityAccess($entity->getLead(), $action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\LeadField;
|
||||
use Mautic\LeadBundle\Field\Exception\AbortColumnCreateException;
|
||||
use Mautic\LeadBundle\Field\Exception\AbortColumnUpdateException;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<LeadField>
|
||||
*/
|
||||
class FieldApiController extends CommonApiController
|
||||
{
|
||||
/**
|
||||
* Can have value of 'contact' or 'company'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldObject;
|
||||
|
||||
/**
|
||||
* @var FieldModel|null
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper, FieldModel $fieldModel)
|
||||
{
|
||||
$request = $requestStack->getCurrentRequest();
|
||||
\assert(null !== $request);
|
||||
|
||||
$this->model = $fieldModel;
|
||||
$this->fieldObject = $request->get('object');
|
||||
$this->entityClass = LeadField::class;
|
||||
$this->entityNameOne = 'field';
|
||||
$this->entityNameMulti = 'fields';
|
||||
$this->routeParams = ['object' => $this->fieldObject];
|
||||
|
||||
if ('contact' === $this->fieldObject) {
|
||||
$this->fieldObject = 'lead';
|
||||
}
|
||||
|
||||
$repo = $this->model->getRepository();
|
||||
$tableAlias = $repo->getTableAlias();
|
||||
$this->listFilters[] = [
|
||||
'column' => $tableAlias.'.object',
|
||||
'expr' => 'eq',
|
||||
'value' => $this->fieldObject,
|
||||
];
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
protected function saveEntity($entity, int $statusCode): int
|
||||
{
|
||||
try {
|
||||
return parent::saveEntity($entity, $statusCode);
|
||||
} catch (AbortColumnCreateException) {
|
||||
// Field has been queued
|
||||
return Response::HTTP_ACCEPTED;
|
||||
} catch (AbortColumnUpdateException) {
|
||||
// Field has been queued
|
||||
return Response::HTTP_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes and returns an array of where statements from the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getWhereFromRequest(Request $request)
|
||||
{
|
||||
$where = parent::getWhereFromRequest($request);
|
||||
|
||||
$where[] = [
|
||||
'col' => 'object',
|
||||
'expr' => 'eq',
|
||||
'val' => $this->fieldObject,
|
||||
];
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|void
|
||||
*/
|
||||
protected function prepareParametersForBinding(Request $request, $parameters, $entity, $action)
|
||||
{
|
||||
$parameters['object'] = $this->fieldObject;
|
||||
// Workaround for mispelled isUniqueIdentifer.
|
||||
if (isset($parameters['isUniqueIdentifier'])) {
|
||||
$parameters['isUniqueIdentifer'] = $parameters['isUniqueIdentifier'];
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LeadField &$entity
|
||||
* @param string $action
|
||||
*/
|
||||
protected function preSaveEntity(&$entity, $form, $parameters, $action = 'edit')
|
||||
{
|
||||
if (isset($parameters['properties'])) {
|
||||
$result = $this->model->setFieldProperties($entity, $parameters['properties']);
|
||||
|
||||
if (true !== $result) {
|
||||
return $this->returnError($this->translator->trans($result, [], 'validators'), Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,736 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\CoreBundle\Entity\IpAddress;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\ArrayHelper;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\DateTimeHelper;
|
||||
use Mautic\CoreBundle\Helper\InputHelper;
|
||||
use Mautic\CoreBundle\Helper\IpLookupHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Controller\FrequencyRuleTrait;
|
||||
use Mautic\LeadBundle\Controller\LeadDetailsTrait;
|
||||
use Mautic\LeadBundle\DataObject\LeadManipulator;
|
||||
use Mautic\LeadBundle\Deduplicate\ContactMerger;
|
||||
use Mautic\LeadBundle\Deduplicate\Exception\SameContactException;
|
||||
use Mautic\LeadBundle\Entity\DoNotContact;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Model\DoNotContact as DoNotContactModel;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<Lead>
|
||||
*/
|
||||
class LeadApiController extends CommonApiController
|
||||
{
|
||||
use CustomFieldsApiControllerTrait;
|
||||
use FrequencyRuleTrait;
|
||||
use LeadDetailsTrait;
|
||||
|
||||
public const MODEL_ID = 'lead.lead';
|
||||
|
||||
/**
|
||||
* @var LeadModel|null
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
private DoNotContactModel $doNotContactModel;
|
||||
|
||||
public function __construct(
|
||||
CorePermissions $security,
|
||||
Translator $translator,
|
||||
EntityResultHelper $entityResultHelper,
|
||||
RouterInterface $router,
|
||||
FormFactoryInterface $formFactory,
|
||||
DoNotContactModel $doNotContactModel,
|
||||
AppVersion $appVersion,
|
||||
private ContactMerger $contactMerger,
|
||||
private UserHelper $userHelper,
|
||||
private IpLookupHelper $ipLookupHelper,
|
||||
RequestStack $requestStack,
|
||||
ManagerRegistry $doctrine,
|
||||
ModelFactory $modelFactory,
|
||||
EventDispatcherInterface $dispatcher,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
) {
|
||||
$this->doNotContactModel = $doNotContactModel;
|
||||
|
||||
$leadModel = $modelFactory->getModel(self::MODEL_ID);
|
||||
\assert($leadModel instanceof LeadModel);
|
||||
$this->model = $leadModel;
|
||||
$this->entityClass = Lead::class;
|
||||
$this->entityNameOne = 'contact';
|
||||
$this->entityNameMulti = 'contacts';
|
||||
$this->serializerGroups = ['leadDetails', 'frequencyRulesList', 'doNotContactList', 'userList', 'stageList', 'publishDetails', 'ipAddress', 'tagList', 'utmtagsList'];
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of users for lead owner edits.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getOwnersAction(Request $request)
|
||||
{
|
||||
if (!$this->security->isGranted(
|
||||
['lead:leads:create', 'lead:leads:editown', 'lead:leads:editother'],
|
||||
'MATCH_ONE'
|
||||
)
|
||||
) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$filter = $request->query->get('filter', null);
|
||||
$limit = $request->query->get('limit', null);
|
||||
$start = $request->query->get('start', null);
|
||||
$users = $this->model->getLookupResults('user', $filter, $limit, $start);
|
||||
$view = $this->view($users, Response::HTTP_OK);
|
||||
$context = $view->getContext()->setGroups(['userList']);
|
||||
$view->setContext($context);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
protected function getTotalCountTtl(): ?int
|
||||
{
|
||||
return $this->coreParametersHelper->get('contact_api_count_cache_ttl', 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of custom fields.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getFieldsAction()
|
||||
{
|
||||
if (!$this->security->isGranted(['lead:leads:editown', 'lead:leads:editother'], 'MATCH_ONE')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$fields = $this->getModel('lead.field')->getEntities(
|
||||
[
|
||||
'filter' => [
|
||||
'force' => [
|
||||
[
|
||||
'column' => 'f.isPublished',
|
||||
'expr' => 'eq',
|
||||
'value' => true,
|
||||
'object' => 'lead',
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$view = $this->view($fields, Response::HTTP_OK);
|
||||
$context = $view->getContext()->setGroups(['leadFieldList']);
|
||||
$view->setContext($context);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of notes on a specific lead.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getNotesAction(Request $request, $id)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
if (!$this->security->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$results = $this->getModel('lead.note')->getEntities(
|
||||
[
|
||||
'start' => $request->query->get('start', '0'),
|
||||
'limit' => $request->query->get('limit', $this->coreParametersHelper->get('default_pagelimit')),
|
||||
'filter' => [
|
||||
'string' => $request->query->get('search', ''),
|
||||
'force' => [
|
||||
[
|
||||
'column' => 'n.lead',
|
||||
'expr' => 'eq',
|
||||
'value' => $entity,
|
||||
],
|
||||
],
|
||||
],
|
||||
'orderBy' => $request->query->get('orderBy', 'n.dateAdded'),
|
||||
'orderByDir' => $request->query->get('orderByDir', 'DESC'),
|
||||
]
|
||||
);
|
||||
|
||||
[$notes, $count] = $this->prepareEntitiesForView($results);
|
||||
|
||||
$view = $this->view(
|
||||
[
|
||||
'total' => $count,
|
||||
'notes' => $notes,
|
||||
],
|
||||
Response::HTTP_OK
|
||||
);
|
||||
|
||||
$context = $view->getContext()->setGroups(['leadNoteDetails']);
|
||||
$view->setContext($context);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of devices on a specific lead.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getDevicesAction(Request $request, $id)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
if (!$this->security->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$results = $this->getModel('lead.device')->getEntities(
|
||||
[
|
||||
'start' => $request->query->get('start', '0'),
|
||||
'limit' => $request->query->get('limit', $this->coreParametersHelper->get('default_pagelimit')),
|
||||
'filter' => [
|
||||
'string' => $request->query->get('search', ''),
|
||||
'force' => [
|
||||
[
|
||||
'column' => 'd.lead',
|
||||
'expr' => 'eq',
|
||||
'value' => $entity,
|
||||
],
|
||||
],
|
||||
],
|
||||
'orderBy' => $request->query->get('orderBy', 'd.dateAdded'),
|
||||
'orderByDir' => $request->query->get('orderByDir', 'DESC'),
|
||||
]
|
||||
);
|
||||
|
||||
[$devices, $count] = $this->prepareEntitiesForView($results);
|
||||
|
||||
$view = $this->view(
|
||||
[
|
||||
'total' => $count,
|
||||
'devices' => $devices,
|
||||
],
|
||||
Response::HTTP_OK
|
||||
);
|
||||
|
||||
$context = $view->getContext()->setGroups(['leadDeviceDetails']);
|
||||
$view->setContext($context);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of contact segments the contact is in.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getListsAction($id)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
if (null !== $entity) {
|
||||
if (!$this->security->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$lists = $this->model->getLists($entity, true, true);
|
||||
|
||||
foreach ($lists as &$l) {
|
||||
unset($l['leads'][0]['leadlist_id']);
|
||||
unset($l['leads'][0]['lead_id']);
|
||||
|
||||
$l = array_merge($l, $l['leads'][0]);
|
||||
|
||||
unset($l['leads']);
|
||||
}
|
||||
|
||||
$view = $this->view(
|
||||
[
|
||||
'total' => count($lists),
|
||||
'lists' => $lists,
|
||||
],
|
||||
Response::HTTP_OK
|
||||
);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of contact companies the contact is in.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getCompaniesAction($id)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
if (!$this->security->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$companies = $this->model->getCompanies($entity);
|
||||
|
||||
$view = $this->view(
|
||||
[
|
||||
'total' => count($companies),
|
||||
'companies' => $companies,
|
||||
],
|
||||
Response::HTTP_OK
|
||||
);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of campaigns the lead is part of.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getCampaignsAction($id)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
if (null !== $entity) {
|
||||
if (!$this->security->hasEntityAccess('lead:leads:viewown', 'lead:leads:viewother', $entity->getPermissionUser())) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
/** @var \Mautic\CampaignBundle\Model\CampaignModel $campaignModel */
|
||||
$campaignModel = $this->getModel('campaign');
|
||||
$campaigns = $campaignModel->getLeadCampaigns($entity, true);
|
||||
|
||||
foreach ($campaigns as &$c) {
|
||||
if (!empty($c['lists'])) {
|
||||
$c['listMembership'] = array_keys($c['lists']);
|
||||
unset($c['lists']);
|
||||
}
|
||||
|
||||
unset($c['leads'][0]['campaign_id']);
|
||||
unset($c['leads'][0]['lead_id']);
|
||||
|
||||
$c = array_merge($c, $c['leads'][0]);
|
||||
|
||||
unset($c['leads']);
|
||||
}
|
||||
|
||||
$view = $this->view(
|
||||
[
|
||||
'total' => count($campaigns),
|
||||
'campaigns' => $campaigns,
|
||||
],
|
||||
Response::HTTP_OK
|
||||
);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of contact events.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getActivityAction(Request $request, $id)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
if (!$this->checkEntityAccess($entity)) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
return $this->getAllActivityAction($request, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of contact events.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getAllActivityAction(Request $request, $lead = null)
|
||||
{
|
||||
$canViewOwn = $this->security->isGranted('lead:leads:viewown');
|
||||
$canViewOthers = $this->security->isGranted('lead:leads:viewother');
|
||||
|
||||
if (!$canViewOthers && !$canViewOwn) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$filters = $this->sanitizeEventFilter(InputHelper::clean($request->query->all()['filters'] ?? $request->request->all()['filters'] ?? []));
|
||||
$limit = (int) $request->get('limit', 25);
|
||||
$page = (int) $request->get('page', 1);
|
||||
$order = InputHelper::clean($request->get('order', ['timestamp', 'DESC']));
|
||||
|
||||
[$events, $serializerGroups] = $this->model->getEngagements($lead, $filters, $order, $page, $limit, false);
|
||||
|
||||
$view = $this->view($events);
|
||||
$context = $view->getContext()->setGroups($serializerGroups);
|
||||
$view->setContext($context);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a DNC to the contact.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function addDncAction(Request $request, $id, $channel)
|
||||
{
|
||||
$entity = $this->model->getEntity((int) $id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
if (!$this->checkEntityAccess($entity, 'edit')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$channelId = (int) $request->request->get('channelId');
|
||||
if ($channelId) {
|
||||
$channel = [$channel => $channelId];
|
||||
}
|
||||
|
||||
// If no reason is set, default to 3 (manual)
|
||||
$reason = (int) $request->request->get('reason', DoNotContact::MANUAL);
|
||||
|
||||
// If a reason is set, but it's empty or 0, show an error.
|
||||
if (0 === $reason) {
|
||||
return $this->returnError(
|
||||
'Invalid reason code given',
|
||||
Response::HTTP_BAD_REQUEST,
|
||||
['Reason code needs to be an integer and higher than 0.']
|
||||
);
|
||||
}
|
||||
|
||||
$comments = InputHelper::clean($request->request->get('comments'));
|
||||
|
||||
$doNotContact = $this->doNotContactModel;
|
||||
$doNotContact->addDncForContact($entity->getId(), $channel, $reason, $comments);
|
||||
$view = $this->view([$this->entityNameOne => $entity]);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a DNC from the contact.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function removeDncAction($id, $channel)
|
||||
{
|
||||
$doNotContact = $this->doNotContactModel;
|
||||
|
||||
$entity = $this->model->getEntity((int) $id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
if (!$this->checkEntityAccess($entity, 'edit')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$result = $doNotContact->removeDncForContact($entity->getId(), $channel);
|
||||
$view = $this->view(
|
||||
[
|
||||
'recordFound' => $result,
|
||||
$this->entityNameOne => $entity,
|
||||
]
|
||||
);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/Remove a UTM Tagset to/from the contact.
|
||||
*
|
||||
* @param int $id
|
||||
* @param string $method
|
||||
* @param array<mixed>|int $data
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
protected function applyUtmTagsAction($id, $method, $data)
|
||||
{
|
||||
$entity = $this->model->getEntity((int) $id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
if (!$this->checkEntityAccess($entity, 'edit')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
// calls add/remove method as appropriate
|
||||
$result = $this->model->$method($entity, $data);
|
||||
|
||||
if (false === $result) {
|
||||
return $this->badRequest();
|
||||
}
|
||||
|
||||
if ('removeUtmTags' == $method) {
|
||||
$view = $this->view(
|
||||
[
|
||||
'recordFound' => $result,
|
||||
$this->entityNameOne => $entity,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$view = $this->view([$this->entityNameOne => $entity]);
|
||||
}
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a UTM Tagset to the contact.
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function addUtmTagsAction(Request $request, $id)
|
||||
{
|
||||
return $this->applyUtmTagsAction($id, 'addUTMTags', $request->request->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a UTM Tagset for the contact.
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $utmid
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function removeUtmTagsAction($id, $utmid)
|
||||
{
|
||||
return $this->applyUtmTagsAction($id, 'removeUtmTags', (int) $utmid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new entity from provided params.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getNewEntity(array $params)
|
||||
{
|
||||
return $this->model->checkForDuplicateContact($params);
|
||||
}
|
||||
|
||||
protected function prepareParametersForBinding(Request $request, $parameters, $entity, $action)
|
||||
{
|
||||
// Unset the tags from params to avoid a validation error
|
||||
if (isset($parameters['tags'])) {
|
||||
unset($parameters['tags']);
|
||||
}
|
||||
|
||||
// keep existing tags
|
||||
foreach ($entity->getTags() as $tag) {
|
||||
$parameters['tags'][] = $tag->getId();
|
||||
}
|
||||
|
||||
// keep existing owner if it is not set or should be reset to null
|
||||
if (!array_key_exists('owner', $parameters) && $entity->getOwner()) {
|
||||
$parameters['owner'] = $entity->getOwner()->getId();
|
||||
}
|
||||
// keep existing stage if it is not set or should be reset to null
|
||||
if (!array_key_exists('stage', $parameters) && $entity->getStage()) {
|
||||
$parameters['stage'] = $entity->getStage()->getId();
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Lead $entity
|
||||
* @param array $parameters
|
||||
* @param string $action
|
||||
*/
|
||||
protected function preSaveEntity(&$entity, $form, $parameters, $action = 'edit')
|
||||
{
|
||||
if ('edit' === $action) {
|
||||
// Merge existing duplicate contact based on unique fields if exist
|
||||
// new endpoints will leverage getNewEntity in order to return the correct status codes
|
||||
$existingEntity = $this->model->checkForDuplicateContact($this->entityRequestParameters);
|
||||
\assert($existingEntity instanceof Lead);
|
||||
|
||||
$contactMerger = $this->contactMerger;
|
||||
|
||||
if ($entity->getId() && $existingEntity->getId()) {
|
||||
try {
|
||||
$entity = $contactMerger->merge($entity, $existingEntity);
|
||||
} catch (SameContactException) {
|
||||
}
|
||||
} elseif ($existingEntity->getId()) {
|
||||
$entity = $existingEntity;
|
||||
}
|
||||
}
|
||||
|
||||
$manipulatorObject = $this->inBatchMode ? 'api-batch' : 'api-single';
|
||||
|
||||
$entity->setManipulator(new LeadManipulator(
|
||||
'lead',
|
||||
$manipulatorObject,
|
||||
null,
|
||||
$this->userHelper->getUser()->getName()
|
||||
));
|
||||
|
||||
if (isset($parameters['companies'])) {
|
||||
$this->model->modifyCompanies($entity, $parameters['companies']);
|
||||
unset($parameters['companies']);
|
||||
}
|
||||
|
||||
if (isset($parameters['owner'])) {
|
||||
$owner = $this->getModel('user.user')->getEntity((int) $parameters['owner']);
|
||||
$entity->setOwner($owner);
|
||||
unset($parameters['owner']);
|
||||
}
|
||||
|
||||
if (isset($parameters['stage'])) {
|
||||
$stage = $this->getModel('stage.stage')->getEntity((int) $parameters['stage']);
|
||||
$entity->setStage($stage);
|
||||
unset($parameters['stage']);
|
||||
}
|
||||
|
||||
if (isset($this->entityRequestParameters['tags'])) {
|
||||
$this->model->modifyTags($entity, $this->entityRequestParameters['tags'], null, false);
|
||||
}
|
||||
|
||||
// Since the request can be from 3rd party, check for an IP address if included
|
||||
if (isset($this->entityRequestParameters['ipAddress'])) {
|
||||
$ipAddress = $this->ipLookupHelper->getIpAddress($this->entityRequestParameters['ipAddress']);
|
||||
\assert($ipAddress instanceof IpAddress);
|
||||
|
||||
if (!$entity->getIpAddresses()->contains($ipAddress)) {
|
||||
$entity->addIpAddress($ipAddress);
|
||||
}
|
||||
|
||||
unset($this->entityRequestParameters['ipAddress']);
|
||||
}
|
||||
|
||||
// Check for lastActive date
|
||||
if (isset($this->entityRequestParameters['lastActive'])) {
|
||||
$lastActive = new DateTimeHelper($this->entityRequestParameters['lastActive']);
|
||||
$entity->setLastActive($lastActive->getDateTime());
|
||||
unset($this->entityRequestParameters['lastActive']);
|
||||
}
|
||||
|
||||
// Batch DNC settings
|
||||
if (!empty($parameters['doNotContact']) && is_array($parameters['doNotContact'])) {
|
||||
foreach ($parameters['doNotContact'] as $dnc) {
|
||||
$channel = !empty($dnc['channel']) ? $dnc['channel'] : 'email';
|
||||
$comments = !empty($dnc['comments']) ? $dnc['comments'] : '';
|
||||
|
||||
$reason = (int) ArrayHelper::getValue('reason', $dnc, DoNotContact::MANUAL);
|
||||
|
||||
$doNotContact = $this->doNotContactModel;
|
||||
|
||||
if (DoNotContact::IS_CONTACTABLE === $reason) {
|
||||
if (!empty($entity->getId())) {
|
||||
// Remove DNC record
|
||||
$doNotContact->removeDncForContact($entity->getId(), $channel, false);
|
||||
}
|
||||
} elseif (empty($entity->getId())) {
|
||||
// Contact doesn't exist yet. Directly create a DNC record on the entity.
|
||||
$doNotContact->createDncRecord($entity, $channel, $reason, $comments);
|
||||
} else {
|
||||
// Add DNC record to existing contact
|
||||
$doNotContact->addDncForContact($entity->getId(), $channel, $reason, $comments, false);
|
||||
}
|
||||
}
|
||||
unset($parameters['doNotContact']);
|
||||
}
|
||||
|
||||
if (!empty($parameters['frequencyRules'])) {
|
||||
$viewParameters = [];
|
||||
$data = $this->getFrequencyRuleFormData($entity, null, null, false, $parameters['frequencyRules']);
|
||||
|
||||
if (true !== $frequencyForm = $this->getFrequencyRuleForm($entity, $viewParameters, $data)) {
|
||||
$formErrors = $this->getFormErrorMessages($frequencyForm);
|
||||
$msg = $this->getFormErrorMessage($formErrors);
|
||||
|
||||
if (!$msg) {
|
||||
$msg = $this->translator->trans('mautic.core.error.badrequest', [], 'flashes');
|
||||
}
|
||||
|
||||
return $this->returnError($msg, Response::HTTP_BAD_REQUEST, $formErrors);
|
||||
}
|
||||
|
||||
unset($parameters['frequencyRules']);
|
||||
}
|
||||
|
||||
$isPostOrPatch = 'POST' === $this->requestStack->getCurrentRequest()->getMethod() || 'PATCH' === $this->requestStack->getCurrentRequest()->getMethod();
|
||||
$this->setCustomFieldValues($entity, $form, $parameters, $isPostOrPatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to be used in FrequencyRuleTrait.
|
||||
*/
|
||||
protected function isFormCancelled(?FormInterface $form = null): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to be used in FrequencyRuleTrait.
|
||||
*/
|
||||
protected function isFormValid(FormInterface $form, ?array $data = null): bool
|
||||
{
|
||||
$form->submit($data, 'PATCH' !== $this->requestStack->getCurrentRequest()->getMethod());
|
||||
|
||||
return $form->isSubmitted() && $form->isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Lead $entity
|
||||
*/
|
||||
protected function detachEntity(object $entity): void
|
||||
{
|
||||
if (empty($entity->getPreviousId())) {
|
||||
parent::detachEntity($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Controller\LeadAccessTrait;
|
||||
use Mautic\LeadBundle\Entity\LeadList;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Mautic\LeadBundle\Model\ListModel;
|
||||
use Mautic\LeadBundle\Security\Permissions\LeadPermissions;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<LeadList>
|
||||
*/
|
||||
class ListApiController extends CommonApiController
|
||||
{
|
||||
use LeadAccessTrait;
|
||||
|
||||
/**
|
||||
* @var ListModel|null
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper)
|
||||
{
|
||||
$listModel = $modelFactory->getModel('lead.list');
|
||||
\assert($listModel instanceof ListModel);
|
||||
|
||||
$this->model = $listModel;
|
||||
$this->entityClass = LeadList::class;
|
||||
$this->entityNameOne = 'list';
|
||||
$this->entityNameMulti = 'lists';
|
||||
$this->serializerGroups = [
|
||||
'leadListDetails',
|
||||
'userList',
|
||||
'publishDetails',
|
||||
'ipAddress',
|
||||
'categoryList',
|
||||
];
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This conversion won't be needed in couple of years.
|
||||
*
|
||||
* The 'filter' and 'display' fields used to be part of each segment filter root array.
|
||||
* Those fields were moved to 'properties' subarray. We have to ensure BC and remove them
|
||||
* from filter root array so Symfony forms would not fail with unknown field error.
|
||||
*/
|
||||
protected function prepareParametersForBinding(Request $request, $parameters, $entity, $action)
|
||||
{
|
||||
if (empty($parameters['filters']) || !is_array($parameters['filters'])) {
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
foreach ($parameters['filters'] as $key => $filter) {
|
||||
$bcFilterValue = $filter['filter'] ?? null;
|
||||
$filterValue = $filter['properties']['filter'] ?? $bcFilterValue;
|
||||
$parameters['filters'][$key]['properties']['filter'] = $filterValue;
|
||||
|
||||
if (!empty($filter['display']) && !isset($filter['properties']['display'])) {
|
||||
$parameters['filters'][$key]['properties']['display'] = $filter['display'];
|
||||
}
|
||||
|
||||
unset($parameters['filters'][$key]['filter'], $parameters['filters'][$key]['display']);
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a list of smart lists for the user.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getListsAction()
|
||||
{
|
||||
$listModel = $this->getModel('lead.list');
|
||||
\assert($listModel instanceof ListModel);
|
||||
$lists = $listModel->getUserLists();
|
||||
$view = $this->view($lists, Response::HTTP_OK);
|
||||
$context = $view->getContext()->setGroups(['leadListList']);
|
||||
$view->setContext($context);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a lead to a list.
|
||||
*
|
||||
* @param int $id List ID
|
||||
* @param int $leadId Lead ID
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function addLeadAction($id, $leadId)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$contact = $this->checkLeadAccess($leadId, 'edit');
|
||||
if ($contact instanceof Response) {
|
||||
return $contact;
|
||||
}
|
||||
|
||||
// Does the user have access to the list
|
||||
$lists = $this->model->getUserLists();
|
||||
if (!isset($lists[$id])) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$leadModel = $this->getModel('lead');
|
||||
\assert($leadModel instanceof LeadModel);
|
||||
$leadModel->addToLists($leadId, $entity);
|
||||
|
||||
$view = $this->view(['success' => 1], Response::HTTP_OK);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a leads to a list.
|
||||
*
|
||||
* @param int $id segement ID
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function addLeadsAction(Request $request, $id)
|
||||
{
|
||||
$contactIds = $request->request->all()['ids'] ?? null;
|
||||
if (null === $contactIds) {
|
||||
return $this->returnError('mautic.core.error.badrequest', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$entity = $this->model->getEntity($id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
// Does the user have access to the list
|
||||
$lists = $this->model->getUserLists();
|
||||
if (!isset($lists[$id])) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$responseDetail = [];
|
||||
foreach ($contactIds as $contactId) {
|
||||
$contact = $this->checkLeadAccess($contactId, 'edit');
|
||||
if ($contact instanceof Response) {
|
||||
$responseDetail[$contactId] = ['success' => false];
|
||||
} else {
|
||||
$leadModel = $this->getModel('lead');
|
||||
\assert($leadModel instanceof LeadModel);
|
||||
/* @var \Mautic\LeadBundle\Entity\Lead $contact */
|
||||
$leadModel->addToLists($contact, $entity);
|
||||
$responseDetail[$contact->getId()] = ['success' => true];
|
||||
}
|
||||
}
|
||||
|
||||
$view = $this->view(['success' => 1, 'details' => $responseDetail], Response::HTTP_OK);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given contact from a list.
|
||||
*
|
||||
* @param int $id List ID
|
||||
* @param int $leadId Lead ID
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
*/
|
||||
public function removeLeadAction($id, $leadId)
|
||||
{
|
||||
$entity = $this->model->getEntity($id);
|
||||
|
||||
if (null === $entity) {
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
$contact = $this->checkLeadAccess($leadId, 'edit');
|
||||
if ($contact instanceof Response) {
|
||||
return $contact;
|
||||
}
|
||||
|
||||
// Does the user have access to the list
|
||||
$lists = $this->model->getUserLists();
|
||||
if (!isset($lists[$id])) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$leadModel = $this->getModel('lead');
|
||||
\assert($leadModel instanceof LeadModel);
|
||||
$leadModel->removeFromLists($leadId, $entity);
|
||||
|
||||
$view = $this->view(['success' => 1], Response::HTTP_OK);
|
||||
|
||||
return $this->handleView($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if user has permission to access retrieved entity.
|
||||
*
|
||||
* @param mixed $entity
|
||||
* @param string $action view|create|edit|publish|delete
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkEntityAccess($entity, $action = 'view')
|
||||
{
|
||||
if ('create' == $action || 'edit' == $action || 'view' == $action) {
|
||||
return $this->security->isGranted(LeadPermissions::LISTS_VIEW_OWN);
|
||||
} elseif ('delete' == $action) {
|
||||
return $this->security->hasEntityAccess(
|
||||
true, LeadPermissions::LISTS_DELETE_OTHER, $entity->getCreatedBy()
|
||||
);
|
||||
}
|
||||
|
||||
return parent::checkEntityAccess($entity, $action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Controller\LeadAccessTrait;
|
||||
use Mautic\LeadBundle\Entity\LeadNote;
|
||||
use Mautic\LeadBundle\Model\NoteModel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<LeadNote>
|
||||
*/
|
||||
class NoteApiController extends CommonApiController
|
||||
{
|
||||
use LeadAccessTrait;
|
||||
|
||||
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper)
|
||||
{
|
||||
$leadNoteModel = $modelFactory->getModel('lead.note');
|
||||
\assert($leadNoteModel instanceof NoteModel);
|
||||
|
||||
$this->model = $leadNoteModel;
|
||||
$this->entityClass = LeadNote::class;
|
||||
$this->entityNameOne = 'note';
|
||||
$this->entityNameMulti = 'notes';
|
||||
$this->serializerGroups = ['leadNoteDetails', 'leadList'];
|
||||
|
||||
// When a user passes in a note like "This is <strong>text</strong>", this will
|
||||
// keep the HTML that was passed in.
|
||||
$this->dataInputMasks = ['text' => 'html'];
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Mautic\LeadBundle\Entity\Lead &$entity
|
||||
* @param string $action
|
||||
*/
|
||||
protected function preSaveEntity(&$entity, $form, $parameters, $action = 'edit')
|
||||
{
|
||||
if (!empty($parameters['lead'])) {
|
||||
$lead = $this->checkLeadAccess($parameters['lead'], $action);
|
||||
|
||||
if ($lead instanceof Response) {
|
||||
return $lead;
|
||||
}
|
||||
|
||||
$entity->setLead($lead);
|
||||
unset($parameters['lead']);
|
||||
} elseif ('new' === $action) {
|
||||
return $this->returnError('lead ID is mandatory', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\Tag;
|
||||
use Mautic\LeadBundle\Model\TagModel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<Tag>
|
||||
*/
|
||||
class TagApiController extends CommonApiController
|
||||
{
|
||||
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper)
|
||||
{
|
||||
$leadTagModel = $modelFactory->getModel('lead.tag');
|
||||
\assert($leadTagModel instanceof TagModel);
|
||||
|
||||
$this->model = $leadTagModel;
|
||||
$this->entityClass = Tag::class;
|
||||
$this->entityNameOne = 'tag';
|
||||
$this->entityNameMulti = 'tags';
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new entity from provided params.
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getNewEntity(array $params)
|
||||
{
|
||||
if (empty($params[$this->entityNameOne])) {
|
||||
throw new \InvalidArgumentException($this->translator->trans('mautic.lead.api.tag.required', [], 'validators'));
|
||||
}
|
||||
|
||||
$tagModel = $this->model;
|
||||
\assert($tagModel instanceof TagModel);
|
||||
|
||||
return $tagModel->getRepository()->getTagByNameOrCreateNewOne($params[$this->entityNameOne]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user