Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Event\ListChangeEvent;
|
||||
|
||||
class CampaignEventHelper
|
||||
{
|
||||
public static function validatePointChange($event, Lead $lead): bool
|
||||
{
|
||||
$properties = $event['properties'];
|
||||
$checkPoints = $properties['points'];
|
||||
|
||||
if (!empty($checkPoints)) {
|
||||
$points = $lead->getPoints();
|
||||
if ($points < $checkPoints) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function validateListChange(ListChangeEvent $eventDetails, $event): bool
|
||||
{
|
||||
$limitAddTo = $event['properties']['addedTo'];
|
||||
$limitRemoveFrom = $event['properties']['removedFrom'];
|
||||
$list = $eventDetails->getList();
|
||||
|
||||
if ($eventDetails->wasAdded() && !empty($limitAddTo) && !in_array($list->getId(), $limitAddTo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($eventDetails->wasRemoved() && !empty($limitRemoveFrom) && !in_array($list->getId(), $limitRemoveFrom)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\CoreBundle\Helper\ClickthroughHelper;
|
||||
use Mautic\CoreBundle\Helper\IpLookupHelper;
|
||||
use Mautic\EmailBundle\Entity\Stat;
|
||||
use Mautic\EmailBundle\Entity\StatRepository;
|
||||
use Mautic\EmailBundle\Helper\BotRatioHelper;
|
||||
use Mautic\LeadBundle\DataObject\LeadManipulator;
|
||||
use Mautic\LeadBundle\Deduplicate\ContactMerger;
|
||||
use Mautic\LeadBundle\Deduplicate\Exception\SameContactException;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Event\ContactIdentificationEvent;
|
||||
use Mautic\LeadBundle\Exception\ContactNotFoundException;
|
||||
use Mautic\LeadBundle\LeadEvents;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Mautic\LeadBundle\Tracker\ContactTracker;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class ContactRequestHelper
|
||||
{
|
||||
/**
|
||||
* @var Lead|null
|
||||
*/
|
||||
private $trackedContact;
|
||||
|
||||
private array $queryFields = [];
|
||||
|
||||
private array $publiclyUpdatableFieldValues = [];
|
||||
|
||||
public function __construct(
|
||||
private LeadModel $leadModel,
|
||||
private ContactTracker $contactTracker,
|
||||
private IpLookupHelper $ipLookupHelper,
|
||||
private RequestStack $requestStack,
|
||||
private LoggerInterface $logger,
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
private ContactMerger $contactMerger,
|
||||
private StatRepository $statRepository,
|
||||
private BotRatioHelper $botRatioHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getContactFromQuery(array $queryFields = []): ?Lead
|
||||
{
|
||||
$request = $this->getCurrentRequest();
|
||||
if ($request && $request->cookies->get('Blocked-Tracking')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ipAddress = $this->ipLookupHelper->getIpAddress();
|
||||
if (!$ipAddress->isTrackable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dateTime = new \DateTime();
|
||||
$userAgent = $request ? $request->server->get('HTTP_USER_AGENT') : '';
|
||||
if (!empty($queryFields['ct'])) {
|
||||
$queryFields['ct'] = (is_array($queryFields['ct'])) ? $queryFields['ct'] : ClickthroughHelper::decodeArrayFromUrl($queryFields['ct']);
|
||||
}
|
||||
|
||||
if (isset($queryFields['ct']['stat'])) {
|
||||
/** @var Stat $stat */
|
||||
$stat = $this->statRepository->findOneBy(['trackingHash' => $queryFields['ct']['stat']]);
|
||||
if (null !== $stat && $this->botRatioHelper->isHitByBot($stat, $dateTime, $ipAddress, (string) $userAgent)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
unset($queryFields['page_url']); // This is set now automatically by PageModel
|
||||
$this->queryFields = $queryFields;
|
||||
|
||||
try {
|
||||
$foundContact = $this->getContactFromUrl();
|
||||
$this->trackedContact = $foundContact;
|
||||
$this->contactTracker->setTrackedContact($this->trackedContact);
|
||||
} catch (ContactNotFoundException) {
|
||||
}
|
||||
|
||||
if (!$this->trackedContact) {
|
||||
$this->trackedContact = $this->contactTracker->getContact();
|
||||
}
|
||||
|
||||
if (!$this->trackedContact) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->prepareContactFromRequest();
|
||||
|
||||
return $this->trackedContact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ContactNotFoundException
|
||||
*/
|
||||
private function getContactFromUrl(): Lead
|
||||
{
|
||||
$request = $this->getCurrentRequest();
|
||||
|
||||
if ($request && $request->cookies->get('Blocked-Tracking')) {
|
||||
throw new ContactNotFoundException();
|
||||
}
|
||||
|
||||
// Check for a lead requested through clickthrough query parameter
|
||||
if (isset($this->queryFields['ct'])) {
|
||||
$clickthrough = (is_array($this->queryFields['ct'])) ? $this->queryFields['ct'] : ClickthroughHelper::decodeArrayFromUrl($this->queryFields['ct']);
|
||||
} elseif ($request && $clickthrough = $request->get('ct', [])) {
|
||||
$clickthrough = ClickthroughHelper::decodeArrayFromUrl($clickthrough);
|
||||
}
|
||||
|
||||
if (!is_array($clickthrough)) {
|
||||
throw new ContactNotFoundException();
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->getContactFromClickthrough($clickthrough);
|
||||
} catch (ContactNotFoundException) {
|
||||
}
|
||||
|
||||
/* @var Lead $foundContact */
|
||||
if (!empty($this->queryFields)) {
|
||||
[$foundContact, $this->publiclyUpdatableFieldValues] = $this->leadModel->checkForDuplicateContact(
|
||||
$this->queryFields,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
if ($this->trackedContact && $this->trackedContact->getId() && $foundContact->getId()) {
|
||||
try {
|
||||
$foundContact = $this->contactMerger->merge($this->trackedContact, $foundContact);
|
||||
} catch (SameContactException) {
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($this->trackedContact) or $foundContact->getId() !== $this->trackedContact->getId()) {
|
||||
// A contact was found by a publicly updatable field
|
||||
if (!$foundContact->isNew()) {
|
||||
return $foundContact;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new ContactNotFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify a contact through a clickthrough URL.
|
||||
*
|
||||
* @return Lead
|
||||
*
|
||||
* @throws ContactNotFoundException
|
||||
*/
|
||||
private function getContactFromClickthrough(array $clickthrough)
|
||||
{
|
||||
$event = new ContactIdentificationEvent($clickthrough);
|
||||
$this->eventDispatcher->dispatch($event, LeadEvents::ON_CLICKTHROUGH_IDENTIFICATION);
|
||||
|
||||
if ($contact = $event->getIdentifiedContact()) {
|
||||
$this->logger->debug("LEAD: Contact ID# {$contact->getId()} tracked through clickthrough query by the ".$event->getIdentifier().' channel');
|
||||
|
||||
// Merge tracked visitor into the clickthrough contact
|
||||
return $this->mergeWithTrackedContact($contact);
|
||||
}
|
||||
|
||||
throw new ContactNotFoundException();
|
||||
}
|
||||
|
||||
private function prepareContactFromRequest(): void
|
||||
{
|
||||
$ipAddress = $this->ipLookupHelper->getIpAddress();
|
||||
$contactIpAddresses = $this->trackedContact->getIpAddresses();
|
||||
if (!$contactIpAddresses->contains($ipAddress)) {
|
||||
$this->trackedContact->addIpAddress($ipAddress);
|
||||
}
|
||||
|
||||
if (!empty($this->publiclyUpdatableFieldValues)) {
|
||||
$this->leadModel->setFieldValues(
|
||||
$this->trackedContact,
|
||||
$this->publiclyUpdatableFieldValues,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// Assume a web request as this is likely a tracking request from DWC or tracking code
|
||||
$this->trackedContact->setManipulator(
|
||||
new LeadManipulator(
|
||||
'page',
|
||||
'hit',
|
||||
null,
|
||||
$this->queryFields['page_url'] ?? ''
|
||||
)
|
||||
);
|
||||
|
||||
if (isset($this->queryFields['tags'])) {
|
||||
$this->leadModel->modifyTags($this->trackedContact, $this->queryFields['tags']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lead
|
||||
*/
|
||||
private function mergeWithTrackedContact(Lead $foundContact)
|
||||
{
|
||||
if ($this->trackedContact && $this->trackedContact->getId() && $this->trackedContact->isAnonymous()) {
|
||||
try {
|
||||
return $this->contactMerger->merge($this->trackedContact, $foundContact);
|
||||
} catch (SameContactException) {
|
||||
}
|
||||
}
|
||||
|
||||
return $foundContact;
|
||||
}
|
||||
|
||||
private function getCurrentRequest(): ?Request
|
||||
{
|
||||
return $this->requestStack->getCurrentRequest();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\CoreBundle\Helper\DateTimeHelper;
|
||||
|
||||
/**
|
||||
* Helper class custom field operations.
|
||||
*/
|
||||
class CustomFieldHelper
|
||||
{
|
||||
public const TYPE_BOOLEAN = 'boolean';
|
||||
|
||||
public const TYPE_NUMBER = 'number';
|
||||
|
||||
public const TYPE_SELECT = 'select';
|
||||
|
||||
/**
|
||||
* Fixes value type for specific field types.
|
||||
*
|
||||
* @param string $type
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function fixValueType($type, $value)
|
||||
{
|
||||
if (null === $value) {
|
||||
// do not transform null values
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
self::TYPE_NUMBER => is_numeric($value) || '' === $value ? (float) $value : $value,
|
||||
self::TYPE_BOOLEAN => (bool) $value,
|
||||
self::TYPE_SELECT => is_scalar($value) ? (string) $value : $value,
|
||||
default => $value,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value This value can be at least array, string, null and maybe others
|
||||
*
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
public static function fieldValueTransfomer(array $field, $value, ?DateTimeHelper $dateTimeHelper = null)
|
||||
{
|
||||
if (null === $value) {
|
||||
// do not transform null values
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $field['type'];
|
||||
switch ($type) {
|
||||
case 'datetime':
|
||||
case 'date':
|
||||
case 'time':
|
||||
// Not sure if this happens anywhere but just in case do not transform empty strings
|
||||
if ('' === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!($value instanceof \DateTimeInterface) && !is_string($value)) {
|
||||
throw new \InvalidArgumentException('Wrong type given. String or DateTimeInterface expected.');
|
||||
}
|
||||
|
||||
$dtHelper = $dateTimeHelper ?: new DateTimeHelper($value, null, 'local');
|
||||
$dtHelper->setDateTime($value);
|
||||
|
||||
switch ($type) {
|
||||
case 'datetime':
|
||||
$value = $dtHelper->toLocalString('Y-m-d H:i:s');
|
||||
break;
|
||||
case 'date':
|
||||
$value = $dtHelper->toLocalString('Y-m-d');
|
||||
break;
|
||||
case 'time':
|
||||
$value = $dtHelper->toLocalString('H:i:s');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all fields values.
|
||||
*
|
||||
* @param mixed[] $fields
|
||||
* @param mixed[] $values
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public static function fieldsValuesTransformer(array $fields, array $values, ?DateTimeHelper $dateTimeHelper = null): array
|
||||
{
|
||||
foreach ($values as $alias => &$value) {
|
||||
if (!empty($fields[$alias]) && is_array($fields[$alias])) {
|
||||
$value = self::fieldValueTransfomer($fields[$alias], $value, $dateTimeHelper);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\CoreBundle\Helper\Serializer;
|
||||
|
||||
/**
|
||||
* Helper class custom field operations.
|
||||
*/
|
||||
class CustomFieldValueHelper
|
||||
{
|
||||
public const TYPE_BOOLEAN = 'boolean';
|
||||
|
||||
public const TYPE_SELECT = 'select';
|
||||
|
||||
public const TYPE_MULTISELECT = 'multiselect';
|
||||
|
||||
public static function normalizeValues(array $customFields): array
|
||||
{
|
||||
if (isset($customFields['core'])) {
|
||||
foreach ($customFields as $group => $fields) {
|
||||
foreach ($fields as $alias => $field) {
|
||||
if (is_array($field)) {
|
||||
$customFields[$group][$alias]['normalizedValue'] = self::normalizeValue($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($customFields as $alias => &$field) {
|
||||
if (is_array($field)) {
|
||||
$customFields[$alias]['normalizedValue'] = self::normalizeValue($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $customFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public static function normalizeValue(array $field)
|
||||
{
|
||||
$value = $field['value'] ?? '';
|
||||
$type = $field['type'] ?? null;
|
||||
$properties = $field['properties'] ?? null;
|
||||
|
||||
return self::normalize($value, $type, $properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function setValueFromPropertiesList(array $properties, $value)
|
||||
{
|
||||
if (isset($properties['list']) && is_array($properties['list'])) {
|
||||
$list = $properties['list'];
|
||||
if (!is_array($list)) {
|
||||
return $value;
|
||||
}
|
||||
foreach ($list as $property) {
|
||||
if (isset($property[$value])) {
|
||||
return $property[$value];
|
||||
} elseif (isset($property['value']) && $property['value'] == $value) {
|
||||
return $property['label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param string|null $type
|
||||
* @param string|array<int, string>|null $properties
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public static function normalize($value, $type, $properties)
|
||||
{
|
||||
if ('' !== $value && $type && $properties) {
|
||||
if (!is_array($properties)) {
|
||||
$properties = Serializer::decode($properties);
|
||||
}
|
||||
switch ($type) {
|
||||
case self::TYPE_BOOLEAN:
|
||||
foreach ($properties as $key => $property) {
|
||||
if ('yes' === $key && !isset($properties[1])) {
|
||||
$properties[1] = $property;
|
||||
} elseif ('no' === $key && !isset($properties[0])) {
|
||||
$properties[0] = $property;
|
||||
}
|
||||
}
|
||||
if (isset($properties[$value])) {
|
||||
$value = $properties[$value];
|
||||
}
|
||||
break;
|
||||
case self::TYPE_SELECT:
|
||||
$value = self::setValueFromPropertiesList($properties, $value);
|
||||
break;
|
||||
case self::TYPE_MULTISELECT:
|
||||
$values = explode('|', $value);
|
||||
foreach ($values as &$val) {
|
||||
$val = self::setValueFromPropertiesList($properties, $val);
|
||||
}
|
||||
$value = implode('|', $values);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\ChannelBundle\Helper\ChannelListHelper;
|
||||
use Mautic\LeadBundle\Entity\DoNotContact;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class DncFormatterHelper
|
||||
{
|
||||
/**
|
||||
* @var array<int, string>|null
|
||||
*/
|
||||
private ?array $dncReasons = null;
|
||||
|
||||
public function __construct(
|
||||
private TranslatorInterface $translator,
|
||||
private ChannelListHelper $channelListHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available DNC reasons.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getDncReasons(): array
|
||||
{
|
||||
if (null === $this->dncReasons) {
|
||||
$this->dncReasons = [
|
||||
DoNotContact::IS_CONTACTABLE => $this->translator->trans('mautic.lead.report.dnc_contactable'),
|
||||
DoNotContact::UNSUBSCRIBED => $this->translator->trans('mautic.lead.report.dnc_unsubscribed'),
|
||||
DoNotContact::BOUNCED => $this->translator->trans('mautic.lead.report.dnc_bounced'),
|
||||
DoNotContact::MANUAL => $this->translator->trans('mautic.lead.report.dnc_manual'),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->dncReasons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the label for a specific DNC reason.
|
||||
*
|
||||
* @throws \InvalidArgumentException if the reason ID is invalid
|
||||
*/
|
||||
public function getDncReasonLabel(int $reasonId): string
|
||||
{
|
||||
$reasons = $this->getDncReasons();
|
||||
|
||||
if (!isset($reasons[$reasonId])) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid DNC reason ID: %d', $reasonId));
|
||||
}
|
||||
|
||||
return $reasons[$reasonId];
|
||||
}
|
||||
|
||||
public function printReasonWithChannel(int $reason, string $channel): string
|
||||
{
|
||||
return $this->getDncReasonLabel($reason).': '.$this->channelListHelper->getChannelLabel($channel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\LeadBundle\Field\FieldList;
|
||||
|
||||
class FakeContactHelper
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FieldList $fieldList,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string, int|string|array<int|string, mixed>|null>
|
||||
*/
|
||||
public function prepareFakeContactWithPrimaryCompany(): array
|
||||
{
|
||||
$contact = $this->prepareFakeEntity('lead');
|
||||
|
||||
$company = $this->prepareFakeEntity('company');
|
||||
|
||||
$company['is_primary'] = 1;
|
||||
|
||||
$contact['companies'][] = $company;
|
||||
|
||||
return $contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string, int|string|array<int|string, mixed>|null>
|
||||
*/
|
||||
private function prepareFakeEntity(string $object): array
|
||||
{
|
||||
$fields = $this->fieldList->getFieldList(false, false, [
|
||||
'isPublished' => true,
|
||||
'object' => $object,
|
||||
]);
|
||||
|
||||
array_walk($fields, function (&$field): void {
|
||||
$field = "[$field]";
|
||||
});
|
||||
|
||||
$fields['id'] = 0;
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\LeadBundle\Entity\LeadField;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
|
||||
class FieldAliasHelper
|
||||
{
|
||||
public function __construct(
|
||||
private FieldModel $fieldModel,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the alias and if it's not unique it will make it unique.
|
||||
*/
|
||||
public function makeAliasUnique(LeadField $field): LeadField
|
||||
{
|
||||
// alias cannot be changed for existing fields
|
||||
if ($field->getId()) {
|
||||
return $field;
|
||||
}
|
||||
|
||||
// set alias as name if alias is empty
|
||||
$alias = ($field->getAlias() ?: $field->getName()) ?: '';
|
||||
|
||||
// clean the alias
|
||||
$alias = $this->fieldModel->cleanAlias($alias, 'f_', 25);
|
||||
|
||||
// make sure alias is not already taken
|
||||
$repo = $this->fieldModel->getRepository();
|
||||
$testAlias = $alias;
|
||||
$aliases = $repo->getAliases($field->getId(), false, true, null);
|
||||
$count = (int) in_array($testAlias, $aliases);
|
||||
$aliasTag = $count;
|
||||
|
||||
while ($count) {
|
||||
$testAlias = $alias.$aliasTag;
|
||||
$count = (int) in_array($testAlias, $aliases);
|
||||
++$aliasTag;
|
||||
}
|
||||
|
||||
if ($testAlias !== $alias) {
|
||||
$alias = $testAlias;
|
||||
}
|
||||
|
||||
$field->setAlias($alias);
|
||||
|
||||
return $field;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\CoreBundle\Helper\AbstractFormFieldHelper;
|
||||
use Symfony\Component\Intl\Locales;
|
||||
|
||||
class FormFieldHelper extends AbstractFormFieldHelper
|
||||
{
|
||||
private static array $types = [
|
||||
'text' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'textarea' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'multiselect' => [
|
||||
'properties' => [
|
||||
'list' => [
|
||||
'required' => true,
|
||||
'error_msg' => 'mautic.lead.field.select.listmissing',
|
||||
],
|
||||
],
|
||||
],
|
||||
'select' => [
|
||||
'properties' => [
|
||||
'list' => [
|
||||
'required' => true,
|
||||
'error_msg' => 'mautic.lead.field.select.listmissing',
|
||||
],
|
||||
],
|
||||
],
|
||||
'boolean' => [
|
||||
'properties' => [
|
||||
'yes' => [
|
||||
'required' => true,
|
||||
'error_msg' => 'mautic.lead.field.boolean.yesmissing',
|
||||
],
|
||||
'no' => [
|
||||
'required' => true,
|
||||
'error_msg' => 'mautic.lead.field.boolean.nomissing',
|
||||
],
|
||||
],
|
||||
],
|
||||
'lookup' => [
|
||||
'properties' => [
|
||||
'list' => [],
|
||||
],
|
||||
],
|
||||
'date' => [
|
||||
'properties' => [
|
||||
'format' => [],
|
||||
],
|
||||
],
|
||||
'datetime' => [
|
||||
'properties' => [
|
||||
'format' => [],
|
||||
],
|
||||
],
|
||||
'time' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'timezone' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'email' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'html' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'number' => [
|
||||
'properties' => [
|
||||
'roundmode' => [],
|
||||
'scale' => [],
|
||||
],
|
||||
],
|
||||
'tel' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'url' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'country' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'region' => [
|
||||
'properties' => [],
|
||||
],
|
||||
'locale' => [
|
||||
'properties' => [],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Set the translation key prefix.
|
||||
*/
|
||||
public function setTranslationKeyPrefix(): void
|
||||
{
|
||||
$this->translationKeyPrefix = 'mautic.lead.field.type.';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTypes()
|
||||
{
|
||||
return self::$types;
|
||||
}
|
||||
|
||||
public static function getListTypes(): array
|
||||
{
|
||||
return ['select', 'multiselect', 'boolean', 'lookup', 'country', 'region', 'timezone', 'locale'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: bool, 1:string}
|
||||
*/
|
||||
public static function validateProperties($type, &$properties): array
|
||||
{
|
||||
if (!array_key_exists($type, self::$types)) {
|
||||
// ensure the field type is supported
|
||||
return [false, 'mautic.lead.field.typenotrecognized'];
|
||||
}
|
||||
|
||||
$fieldType = self::$types[$type]['properties'];
|
||||
foreach ($fieldType as $key => $property) {
|
||||
$value = array_key_exists($key, $properties) ? $properties[$key] : null;
|
||||
if (!empty($property['required']) && empty($value)) {
|
||||
return [false, $property['error_msg']];
|
||||
}
|
||||
}
|
||||
|
||||
return [true, ''];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function getCountryChoices(): array
|
||||
{
|
||||
$customFile = $_ENV['MAUTIC_UPLOAD_DIR'].'/countries.json';
|
||||
$listFile = file_exists($customFile) ? $customFile : __DIR__.'/../../CoreBundle/Assets/json/countries.json';
|
||||
$json = file_get_contents($listFile);
|
||||
$countries = json_decode($json);
|
||||
|
||||
return array_combine($countries, $countries);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, string>>
|
||||
*/
|
||||
public static function getRegionChoices(): array
|
||||
{
|
||||
$customFile = $_ENV['MAUTIC_UPLOAD_DIR'].'/regions.json';
|
||||
$listFile = file_exists($customFile) ? $customFile : __DIR__.'/../../CoreBundle/Assets/json/regions.json';
|
||||
$json = file_get_contents($listFile);
|
||||
$regions = json_decode($json);
|
||||
|
||||
$choices = [];
|
||||
|
||||
foreach ($regions as $country => $regionGroup) {
|
||||
$choices[$country] = array_combine($regionGroup, $regionGroup);
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Symfony deprecated and changed Symfony\Component\Form\Extension\Core\Type\TimezoneType::getTimezones to private
|
||||
* in 3.0 - so duplicated code here.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function getTimezonesChoices()
|
||||
{
|
||||
static $timezones;
|
||||
|
||||
if (null === $timezones) {
|
||||
$timezones = [];
|
||||
|
||||
foreach (\DateTimeZone::listIdentifiers() as $timezone) {
|
||||
$parts = explode('/', $timezone);
|
||||
|
||||
if (count($parts) > 2) {
|
||||
$region = $parts[0];
|
||||
$name = $parts[1].' - '.$parts[2];
|
||||
} elseif (count($parts) > 1) {
|
||||
$region = $parts[0];
|
||||
$name = $parts[1];
|
||||
} else {
|
||||
$region = 'Other';
|
||||
$name = $parts[0];
|
||||
}
|
||||
|
||||
$timezones[$region][str_replace('_', ' ', $name)] = $timezone;
|
||||
}
|
||||
}
|
||||
|
||||
return $timezones;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locale choices.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function getLocaleChoices(): array
|
||||
{
|
||||
return array_flip(Locales::getNames());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date field choices.
|
||||
*/
|
||||
public function getDateChoices(): array
|
||||
{
|
||||
return [
|
||||
'anniversary' => $this->translator->trans('mautic.campaign.event.timed.choice.anniversary'),
|
||||
'+P0D' => $this->translator->trans('mautic.campaign.event.timed.choice.today'),
|
||||
'-P1D' => $this->translator->trans('mautic.campaign.event.timed.choice.yesterday'),
|
||||
'+P1D' => $this->translator->trans('mautic.campaign.event.timed.choice.tomorrow'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Company;
|
||||
use Mautic\LeadBundle\Exception\UniqueFieldNotFoundException;
|
||||
use Mautic\LeadBundle\Model\CompanyModel;
|
||||
|
||||
class IdentifyCompanyHelper
|
||||
{
|
||||
/**
|
||||
* @param array $data
|
||||
* @param mixed $lead
|
||||
*/
|
||||
public static function identifyLeadsCompany($data, $lead, CompanyModel $companyModel): array
|
||||
{
|
||||
$addContactToCompany = true;
|
||||
|
||||
$parameters = self::normalizeParameters($data);
|
||||
|
||||
if (!self::hasCompanyParameters($parameters, $companyModel)) {
|
||||
return [null, false, null];
|
||||
}
|
||||
|
||||
try {
|
||||
$companies = $companyModel->checkForDuplicateCompanies($parameters);
|
||||
} catch (UniqueFieldNotFoundException) {
|
||||
return [null, false, null];
|
||||
}
|
||||
|
||||
if (!empty($companies)) {
|
||||
$companyEntity = end($companies);
|
||||
$companyData = $companyEntity->getProfileFields();
|
||||
|
||||
if ($lead) {
|
||||
$companyLeadRepo = $companyModel->getCompanyLeadRepository();
|
||||
$companyLead = $companyLeadRepo->getCompaniesByLeadId($lead->getId(), $companyEntity->getId());
|
||||
if (!empty($companyLead)) {
|
||||
$addContactToCompany = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$companyData = $parameters;
|
||||
|
||||
// create new company
|
||||
$companyEntity = new Company();
|
||||
$companyModel->setFieldValues($companyEntity, $companyData, true);
|
||||
$companyModel->saveEntity($companyEntity);
|
||||
$companyData['id'] = $companyEntity->getId();
|
||||
}
|
||||
|
||||
return [$companyData, $addContactToCompany, $companyEntity];
|
||||
}
|
||||
|
||||
public static function findCompany(array $data, CompanyModel $companyModel): array
|
||||
{
|
||||
$parameters = self::normalizeParameters($data);
|
||||
|
||||
if (!self::hasCompanyParameters($parameters, $companyModel)) {
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
try {
|
||||
$companyEntities = $companyModel->checkForDuplicateCompanies($parameters);
|
||||
} catch (UniqueFieldNotFoundException) {
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
$companyData = $parameters;
|
||||
if (!empty($companyEntities)) {
|
||||
$key = array_key_last($companyEntities);
|
||||
$companyData['id'] = $companyEntities[$key]->getId();
|
||||
}
|
||||
|
||||
return [$companyData, $companyEntities];
|
||||
}
|
||||
|
||||
private static function hasCompanyParameters(array $parameters, CompanyModel $companyModel): bool
|
||||
{
|
||||
$companyFields = $companyModel->fetchCompanyFields();
|
||||
foreach ($parameters as $alias => $value) {
|
||||
foreach ($companyFields as $companyField) {
|
||||
if ($companyField['alias'] === $alias) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $parameters
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
private static function normalizeParameters(array $parameters): array
|
||||
{
|
||||
if (isset($parameters['company'])) {
|
||||
$parameters['companyname'] = filter_var($parameters['company']);
|
||||
unset($parameters['company']);
|
||||
}
|
||||
|
||||
$fields= ['country', 'city', 'state'];
|
||||
foreach ($fields as $field) {
|
||||
if (isset($parameters[$field]) && !isset($parameters['company'.$field])) {
|
||||
$parameters['company'.$field] = $parameters[$field];
|
||||
unset($parameters[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if email address' domain has a DNS MX record. Returns the domain if found.
|
||||
*
|
||||
* @param string $email
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected static function domainExists($email)
|
||||
{
|
||||
if (!strstr($email, '@')) { // not a valid email adress
|
||||
return false;
|
||||
}
|
||||
|
||||
[$user, $domain] = explode('@', $email);
|
||||
$arr = dns_get_record($domain, DNS_MX);
|
||||
|
||||
if (empty($arr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($arr[0]['host'] === $domain) {
|
||||
return $domain;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\LeadBundle\Entity\DoNotContact;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Event as Events;
|
||||
use Mautic\LeadBundle\LeadEvents;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class LeadChangeEventDispatcher
|
||||
{
|
||||
private ?Lead $lead = null;
|
||||
|
||||
private ?array $changes = null;
|
||||
|
||||
public function __construct(
|
||||
private EventDispatcherInterface $dispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
public function dispatchEvents(Events\LeadEvent $event, array $changes): void
|
||||
{
|
||||
$this->lead = $event->getLead();
|
||||
$this->changes = $changes;
|
||||
|
||||
$this->dispatchDateIdentifiedEvent($event);
|
||||
$this->dispatchPointChangeEvent($event);
|
||||
$this->dispatchUtmTagsChangeEvent();
|
||||
$this->dispatchDncChangeEvent();
|
||||
}
|
||||
|
||||
private function dispatchDateIdentifiedEvent(Events\LeadEvent $event): void
|
||||
{
|
||||
if (!isset($this->changes['dateIdentified'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch($event, LeadEvents::LEAD_IDENTIFIED);
|
||||
}
|
||||
|
||||
private function dispatchPointChangeEvent(Events\LeadEvent $event): void
|
||||
{
|
||||
if (!isset($this->changes['points'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->lead->imported) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int) $this->changes['points'][0] <= 0 && (int) $this->changes['points'][1] <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->isNew()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pointsEvent = new Events\PointsChangeEvent($this->lead, $this->changes['points'][0], $this->changes['points'][1]);
|
||||
$this->dispatcher->dispatch($pointsEvent, LeadEvents::LEAD_POINTS_CHANGE);
|
||||
}
|
||||
|
||||
private function dispatchUtmTagsChangeEvent(): void
|
||||
{
|
||||
if (!isset($this->changes['utmtags'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$utmTagsEvent = new Events\LeadUtmTagsEvent($this->lead, $this->changes['utmtags']);
|
||||
$this->dispatcher->dispatch($utmTagsEvent, LeadEvents::LEAD_UTMTAGS_ADD);
|
||||
}
|
||||
|
||||
private function dispatchDncChangeEvent(): void
|
||||
{
|
||||
if (!isset($this->changes['dnc_channel_status'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->changes['dnc_channel_status'] as $channel => $status) {
|
||||
$oldStatus = $status['old_reason'] ?? DoNotContact::IS_CONTACTABLE;
|
||||
$newStatus = $status['reason'];
|
||||
|
||||
$event = new Events\ChannelSubscriptionChange($this->lead, $channel, $oldStatus, $newStatus);
|
||||
$this->dispatcher->dispatch($event, LeadEvents::CHANNEL_SUBSCRIPTION_CHANGED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\LeadBundle\Entity\CompanyLeadRepository;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
|
||||
class PrimaryCompanyHelper
|
||||
{
|
||||
public function __construct(
|
||||
private CompanyLeadRepository $companyLeadRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getProfileFieldsWithPrimaryCompany(Lead $lead)
|
||||
{
|
||||
return $this->mergeInPrimaryCompany(
|
||||
$this->companyLeadRepository->getCompaniesByLeadId($lead->getId()),
|
||||
$lead->getProfileFields()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function mergePrimaryCompanyWithProfileFields($contactId, array $profileFields)
|
||||
{
|
||||
return $this->mergeInPrimaryCompany(
|
||||
$this->companyLeadRepository->getCompaniesByLeadId($contactId),
|
||||
$profileFields
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function mergeInPrimaryCompany(array $companies, array $profileFields)
|
||||
{
|
||||
foreach ($companies as $company) {
|
||||
if (empty($company['is_primary'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($company['id'], $company['score'], $company['date_added'], $company['date_associated'], $company['is_primary']);
|
||||
|
||||
return array_merge($profileFields, $company);
|
||||
}
|
||||
|
||||
return $profileFields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\CoreBundle\Helper\ProgressBarHelper;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Progress
|
||||
{
|
||||
/**
|
||||
* Total number of items representing 100%.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $total = 0;
|
||||
|
||||
/**
|
||||
* Currently proccessed items.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $done = 0;
|
||||
|
||||
/**
|
||||
* @var ProgressBar|null
|
||||
*/
|
||||
protected $bar;
|
||||
|
||||
public function __construct(
|
||||
protected ?OutputInterface $output = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns count of all items.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTotal()
|
||||
{
|
||||
return $this->total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set total value.
|
||||
*
|
||||
* @param int $total
|
||||
*
|
||||
* @return Progress
|
||||
*/
|
||||
public function setTotal($total)
|
||||
{
|
||||
$this->total = (int) $total;
|
||||
|
||||
if ($this->output) {
|
||||
$this->bar = ProgressBarHelper::init($this->output, $this->total);
|
||||
$this->bar->start();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns count of processed items.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDone()
|
||||
{
|
||||
return $this->done;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set total value.
|
||||
*
|
||||
* @return Progress
|
||||
*/
|
||||
public function setDone($done)
|
||||
{
|
||||
$this->done = (int) $done;
|
||||
|
||||
if ($this->bar) {
|
||||
$this->bar->setProgress($this->done);
|
||||
|
||||
if ($this->isFinished()) {
|
||||
$this->bar->finish();
|
||||
$this->output->writeln('');
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase done count by 1.
|
||||
*
|
||||
* @return Progress
|
||||
*/
|
||||
public function increase()
|
||||
{
|
||||
$this->setDone($this->done + 1);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checked if the progress is 100 or more %.
|
||||
*/
|
||||
public function isFinished(): bool
|
||||
{
|
||||
return $this->done >= $this->total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind Progress from simple array.
|
||||
*
|
||||
* @return Progress
|
||||
*/
|
||||
public function bindArray(array $progress)
|
||||
{
|
||||
if (isset($progress[0])) {
|
||||
$this->setDone($progress[0]);
|
||||
}
|
||||
|
||||
if (isset($progress[1])) {
|
||||
$this->setTotal($progress[1]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this object to a simple array.
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
$this->done,
|
||||
$this->total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts percentage of the progress.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function toPercent()
|
||||
{
|
||||
return ($this->total) ? ceil(($this->done / $this->total) * 100) : 100;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\CacheBundle\Cache\CacheProviderInterface;
|
||||
use Psr\Cache\InvalidArgumentException;
|
||||
|
||||
class SegmentCountCacheHelper
|
||||
{
|
||||
public function __construct(
|
||||
private CacheProviderInterface $cacheProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function getSegmentContactCount(int $segmentId): int
|
||||
{
|
||||
return (int) $this->cacheProvider->getItem($this->generateCacheKey($segmentId))->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setSegmentContactCount(int $segmentId, int $count): void
|
||||
{
|
||||
$item = $this->cacheProvider->getItem($this->generateCacheKey($segmentId));
|
||||
$item->set($count);
|
||||
$this->cacheProvider->save($item);
|
||||
|
||||
if ($this->hasSegmentIdForReCount($segmentId)) {
|
||||
$this->cacheProvider->deleteItem($this->generateCacheKeyForRecount($segmentId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function hasSegmentContactCount(int $segmentId): bool
|
||||
{
|
||||
return $this->cacheProvider->hasItem($this->generateCacheKey($segmentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function hasSegmentIdForReCount(int $segmentId): bool
|
||||
{
|
||||
return $this->cacheProvider->hasItem($this->generateCacheKeyForRecount($segmentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function invalidateSegmentContactCount(int $segmentId): void
|
||||
{
|
||||
$item = $this->cacheProvider->getItem($this->generateCacheKeyForRecount($segmentId));
|
||||
$item->set(true);
|
||||
$this->cacheProvider->save($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function incrementSegmentContactCount(int $segmentId): void
|
||||
{
|
||||
$count = $this->hasSegmentContactCount($segmentId) ? $this->getSegmentContactCount($segmentId) : 0;
|
||||
$this->setSegmentContactCount($segmentId, ++$count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function deleteSegmentContactCount(int $segmentId): void
|
||||
{
|
||||
if ($this->hasSegmentContactCount($segmentId)) {
|
||||
$this->cacheProvider->deleteItem($this->generateCacheKey($segmentId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function decrementSegmentContactCount(int $segmentId): void
|
||||
{
|
||||
if ($this->hasSegmentContactCount($segmentId)) {
|
||||
$count = $this->getSegmentContactCount($segmentId);
|
||||
|
||||
if ($count <= 0) {
|
||||
$count = 1;
|
||||
}
|
||||
|
||||
$this->setSegmentContactCount($segmentId, --$count);
|
||||
}
|
||||
}
|
||||
|
||||
private function generateCacheKey(int $segmentId): string
|
||||
{
|
||||
return sprintf('%s.%s.%s', 'segment', $segmentId, 'lead');
|
||||
}
|
||||
|
||||
private function generateCacheKeyForRecount(int $segmentId): string
|
||||
{
|
||||
return sprintf('%s.%s', $this->generateCacheKey($segmentId), 'recount');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Helper;
|
||||
|
||||
use Mautic\CoreBundle\Helper\DateTimeHelper;
|
||||
use Mautic\CoreBundle\Helper\ParamsLoaderHelper;
|
||||
use Mautic\LeadBundle\Entity\LeadRepository;
|
||||
|
||||
class TokenHelper
|
||||
{
|
||||
/**
|
||||
* @const REGEX
|
||||
*/
|
||||
public const REGEX = '/({|%7B)contactfield=(.*?)(}|%7D)/';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $parameters;
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param array $lead
|
||||
* @param bool $replace If true, search/replace will be executed on $content and the modified $content returned
|
||||
* rather than an array of found matches
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public static function findLeadTokens($content, $lead, $replace = false)
|
||||
{
|
||||
if (!$lead) {
|
||||
return $replace ? $content : [];
|
||||
}
|
||||
|
||||
// Search for bracket or bracket encoded
|
||||
$tokenList = [];
|
||||
$foundMatches = preg_match_all(self::REGEX, $content, $matches);
|
||||
$foundDateMatches = preg_match_all('/({|%7B)datetime=(.*?)(}|%7D)/', $content, $dateMatches);
|
||||
|
||||
if ($foundMatches || $foundDateMatches) {
|
||||
foreach ($matches[2] as $key => $match) {
|
||||
$token = $matches[0][$key];
|
||||
|
||||
if (isset($tokenList[$token])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$alias = self::getFieldAlias($match);
|
||||
$defaultValue = self::getTokenDefaultValue($match);
|
||||
$tokenList[$token] = self::getTokenValue($lead, $alias, $defaultValue);
|
||||
}
|
||||
|
||||
foreach ($dateMatches[2] as $key => $match) {
|
||||
$token = $dateMatches[0][$key];
|
||||
|
||||
if (isset($tokenList[$token])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dt = new DateTimeHelper($match);
|
||||
$tokenList[$token] = $dt->toLocalString(DateTimeHelper::FORMAT_DB);
|
||||
}
|
||||
|
||||
if ($replace) {
|
||||
$content = str_replace(array_keys($tokenList), $tokenList, $content);
|
||||
}
|
||||
}
|
||||
|
||||
return $replace ? $content : $tokenList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns correct token value from provided list of tokens and the concrete token.
|
||||
*
|
||||
* @param array $tokens like ['{contactfield=website}' => 'https://mautic.org']
|
||||
* @param string $token like '{contactfield=website|https://default.url}'
|
||||
*
|
||||
* @return string empty string if no match
|
||||
*/
|
||||
public static function getValueFromTokens(array $tokens, $token)
|
||||
{
|
||||
$token = str_replace(['{', '}'], '', $token);
|
||||
$alias = self::getFieldAlias($token);
|
||||
$default = self::getTokenDefaultValue($token);
|
||||
|
||||
return empty($tokens["{{$alias}}"]) ? $default : $tokens["{{$alias}}"];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
private static function getTokenValue(array $lead, $alias, $defaultValue)
|
||||
{
|
||||
$value = '';
|
||||
if (isset($lead[$alias])) {
|
||||
$value = $lead[$alias];
|
||||
} elseif (!empty($lead['companies'])) {
|
||||
foreach ($lead['companies'] as $company) {
|
||||
if (isset($company['is_primary'], $company[$alias]) && 1 === (int) $company['is_primary']) {
|
||||
$value = $company[$alias];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ('' !== $value) {
|
||||
switch ($defaultValue) {
|
||||
case 'label':
|
||||
$value = self::getNormalizeValue($alias, $value);
|
||||
break;
|
||||
case 'true':
|
||||
$value = urlencode($value);
|
||||
break;
|
||||
case 'datetime':
|
||||
case 'date':
|
||||
case 'time':
|
||||
$dt = new DateTimeHelper($value);
|
||||
$date = $dt->getDateTime()->format(
|
||||
self::getParameter('date_format_dateonly')
|
||||
);
|
||||
$time = $dt->getDateTime()->format(
|
||||
self::getParameter('date_format_timeonly')
|
||||
);
|
||||
switch ($defaultValue) {
|
||||
case 'datetime':
|
||||
$value = $date.' '.$time;
|
||||
break;
|
||||
case 'date':
|
||||
$value = $date;
|
||||
break;
|
||||
case 'time':
|
||||
$value = $time;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (in_array($defaultValue, ['true', 'date', 'time', 'datetime', 'label'])) {
|
||||
return $value;
|
||||
} else {
|
||||
return '' !== $value ? $value : $defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static function getTokenDefaultValue($match): string
|
||||
{
|
||||
$fallbackCheck = explode('|', $match);
|
||||
if (!isset($fallbackCheck[1])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $fallbackCheck[1];
|
||||
}
|
||||
|
||||
private static function getFieldAlias($match): string
|
||||
{
|
||||
$fallbackCheck = explode('|', $match);
|
||||
|
||||
return $fallbackCheck[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $parameter
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function getParameter($parameter)
|
||||
{
|
||||
if (null === self::$parameters) {
|
||||
self::$parameters = (new ParamsLoaderHelper())->getParameters();
|
||||
}
|
||||
|
||||
return self::$parameters[$parameter];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
private static function getNormalizeValue(string $alias, $value)
|
||||
{
|
||||
$field = array_merge(LeadRepository::getLeadFieldRepository()->getFields()[$alias], ['value' => $value]);
|
||||
|
||||
return CustomFieldValueHelper::normalizeValue($field);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user