Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker;
|
||||
|
||||
use Mautic\CoreBundle\Entity\IpAddress;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\IpLookupHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadRepository;
|
||||
use Mautic\LeadBundle\Event\LeadChangeEvent;
|
||||
use Mautic\LeadBundle\Event\LeadEvent;
|
||||
use Mautic\LeadBundle\Event\LeadGetCurrentEvent;
|
||||
use Mautic\LeadBundle\LeadEvents;
|
||||
use Mautic\LeadBundle\Model\DefaultValueTrait;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Mautic\LeadBundle\Tracker\Service\ContactTrackingService\ContactTrackingServiceInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
class ContactTracker
|
||||
{
|
||||
use DefaultValueTrait;
|
||||
|
||||
private ?Lead $systemContact = null;
|
||||
|
||||
private ?Lead $trackedContact = null;
|
||||
|
||||
private FieldModel $leadFieldModel;
|
||||
|
||||
private ?bool $useSystemContact = null;
|
||||
|
||||
public function __construct(
|
||||
private LeadRepository $leadRepository,
|
||||
private ContactTrackingServiceInterface $contactTrackingService,
|
||||
private DeviceTracker $deviceTracker,
|
||||
private CorePermissions $security,
|
||||
private LoggerInterface $logger,
|
||||
private IpLookupHelper $ipLookupHelper,
|
||||
private RequestStack $requestStack,
|
||||
private CoreParametersHelper $coreParametersHelper,
|
||||
private EventDispatcherInterface $dispatcher,
|
||||
FieldModel $leadFieldModel,
|
||||
) {
|
||||
$this->leadFieldModel = $leadFieldModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lead|null
|
||||
*/
|
||||
public function getContact()
|
||||
{
|
||||
if (null !== $this->getRequest() && $this->getRequest()->cookies->get('Blocked-Tracking')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($systemContact = $this->getSystemContact()) {
|
||||
return $systemContact;
|
||||
} elseif ($this->isUserSession()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($this->trackedContact)) {
|
||||
$this->trackedContact = $this->getCurrentContact();
|
||||
$this->generateTrackingCookies();
|
||||
}
|
||||
|
||||
if ($request = $this->getRequest()) {
|
||||
$this->logger->debug('CONTACT: Tracking session for contact ID# '.$this->trackedContact->getId().' through '.$request->getMethod().' '.$request->getRequestUri());
|
||||
}
|
||||
|
||||
// Log last active for the tracked contact
|
||||
if (!defined('MAUTIC_LEAD_LASTACTIVE_LOGGED')) {
|
||||
$this->leadRepository->updateLastActive($this->trackedContact->getId());
|
||||
define('MAUTIC_LEAD_LASTACTIVE_LOGGED', 1);
|
||||
}
|
||||
|
||||
return $this->trackedContact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the contact and generate cookies for future tracking.
|
||||
*/
|
||||
public function setTrackedContact(Lead $trackedContact): void
|
||||
{
|
||||
$this->logger->debug("CONTACT: {$trackedContact->getId()} set as current lead.");
|
||||
|
||||
if ($this->useSystemContact()) {
|
||||
// Overwrite system current lead
|
||||
$this->setSystemContact($trackedContact);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Take note of previously tracked in order to dispatched change event
|
||||
$previouslyTrackedContact = (is_null($this->trackedContact)) ? null : $this->trackedContact;
|
||||
$previouslyTrackedId = $this->getTrackingId();
|
||||
|
||||
// Set the newly tracked contact
|
||||
$this->trackedContact = $trackedContact;
|
||||
|
||||
// Hydrate custom field data
|
||||
$fields = $trackedContact->getFields();
|
||||
if (empty($fields)) {
|
||||
$this->hydrateCustomFieldData($trackedContact);
|
||||
}
|
||||
|
||||
// Set last active
|
||||
$this->trackedContact->setLastActive(new \DateTime());
|
||||
|
||||
// If for whatever reason this contact has not been saved yet, don't generate tracking cookies
|
||||
if (!$trackedContact->getId()) {
|
||||
// Delete existing cookies to prevent tracking as someone else
|
||||
$this->deviceTracker->clearTrackingCookies();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate cookies for the newly tracked contact
|
||||
$this->generateTrackingCookies();
|
||||
|
||||
if ($previouslyTrackedContact && $previouslyTrackedContact->getId() != $this->trackedContact->getId()) {
|
||||
$this->dispatchContactChangeEvent($previouslyTrackedContact, $previouslyTrackedId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* System contact bypasses cookie tracking.
|
||||
*/
|
||||
public function setSystemContact(?Lead $lead = null): void
|
||||
{
|
||||
if (null !== $lead) {
|
||||
$this->logger->debug("LEAD: {$lead->getId()} set as system lead.");
|
||||
|
||||
$fields = $lead->getFields();
|
||||
if (empty($fields)) {
|
||||
$this->hydrateCustomFieldData($lead);
|
||||
}
|
||||
}
|
||||
|
||||
$this->systemContact = $lead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTrackingId()
|
||||
{
|
||||
// Use the new method first
|
||||
if ($trackedDevice = $this->deviceTracker->getTrackedDevice()) {
|
||||
return $trackedDevice->getTrackingId();
|
||||
}
|
||||
|
||||
// That failed, so look for the old cookies
|
||||
return $this->contactTrackingService->getTrackedIdentifier();
|
||||
}
|
||||
|
||||
public function setUseSystemContact(?bool $useSystemContact): void
|
||||
{
|
||||
$this->useSystemContact = $useSystemContact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lead|null
|
||||
*/
|
||||
private function getSystemContact()
|
||||
{
|
||||
if ($this->useSystemContact() && $this->systemContact) {
|
||||
$this->logger->debug('CONTACT: System lead is being used');
|
||||
|
||||
return $this->systemContact;
|
||||
}
|
||||
|
||||
if ($this->isUserSession()) {
|
||||
$this->logger->debug('CONTACT: In a Mautic user session');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lead|null
|
||||
*/
|
||||
private function getCurrentContact()
|
||||
{
|
||||
$event = new LeadGetCurrentEvent($this->getRequest());
|
||||
$this->dispatcher->dispatch($event);
|
||||
|
||||
if ($contact = $event->getContact()) {
|
||||
return $contact;
|
||||
}
|
||||
|
||||
if ($lead = $this->getContactByTrackedDevice()) {
|
||||
return $lead;
|
||||
}
|
||||
|
||||
return $this->getContactByIpAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lead|null
|
||||
*/
|
||||
public function getContactByTrackedDevice()
|
||||
{
|
||||
$lead = null;
|
||||
|
||||
// Return null for leads that are from a non-trackable IP, prevent anonymous lead with a non-trackable IP to be tracked
|
||||
$ip = $this->ipLookupHelper->getIpAddress();
|
||||
if ($ip && !$ip->isTrackable()) {
|
||||
return $lead;
|
||||
}
|
||||
|
||||
// Is there a device being tracked?
|
||||
if ($trackedDevice = $this->deviceTracker->getTrackedDevice()) {
|
||||
$lead = $trackedDevice->getLead();
|
||||
|
||||
// Lead associations are not hydrated with custom field values by default
|
||||
$this->hydrateCustomFieldData($lead);
|
||||
}
|
||||
|
||||
if (null === $lead) {
|
||||
// Check to see if a contact is being tracked via the old cookie method in order to migrate them to the new
|
||||
$lead = $this->contactTrackingService->getTrackedLead();
|
||||
}
|
||||
|
||||
if ($lead) {
|
||||
$this->logger->debug("CONTACT: Existing lead found with ID# {$lead->getId()}.");
|
||||
}
|
||||
|
||||
return $lead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lead
|
||||
*/
|
||||
private function getContactByIpAddress()
|
||||
{
|
||||
$ip = $this->ipLookupHelper->getIpAddress();
|
||||
// if no trackingId cookie set the lead is not tracked yet so create a new one
|
||||
if ($ip && !$ip->isTrackable()) {
|
||||
// Don't save leads that are from a non-trackable IP by default
|
||||
return $this->createNewContact($ip, false);
|
||||
}
|
||||
|
||||
if ($this->coreParametersHelper->get('track_contact_by_ip')) {
|
||||
/** @var Lead[] $leads */
|
||||
$leads = $this->leadRepository->getLeadsByIp($ip->getIpAddress());
|
||||
if (count($leads)) {
|
||||
$lead = $leads[0];
|
||||
$this->logger->debug("CONTACT: Existing lead found with ID# {$lead->getId()}.");
|
||||
|
||||
return $lead;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createNewContact($ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $persist
|
||||
*/
|
||||
private function createNewContact(?IpAddress $ip = null, $persist = true): Lead
|
||||
{
|
||||
// let's create a lead
|
||||
$lead = new Lead();
|
||||
$lead->setNewlyCreated(true);
|
||||
|
||||
if ($ip) {
|
||||
$lead->addIpAddress($ip);
|
||||
}
|
||||
|
||||
if ($persist && !defined('MAUTIC_NON_TRACKABLE_REQUEST')) {
|
||||
// Dispatch events for new lead to write create log, ip address change, etc
|
||||
$event = new LeadEvent($lead, true);
|
||||
$this->dispatcher->dispatch($event, LeadEvents::LEAD_PRE_SAVE);
|
||||
$this->setEntityDefaultValues($lead);
|
||||
$this->leadRepository->saveEntity($lead);
|
||||
$this->hydrateCustomFieldData($lead);
|
||||
|
||||
$this->dispatcher->dispatch($event, LeadEvents::LEAD_POST_SAVE);
|
||||
|
||||
$this->logger->debug("CONTACT: New lead created with ID# {$lead->getId()}.");
|
||||
}
|
||||
|
||||
return $lead;
|
||||
}
|
||||
|
||||
private function hydrateCustomFieldData(?Lead $lead = null): void
|
||||
{
|
||||
if (null === $lead) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hydrate fields with custom field data
|
||||
$fields = $this->leadRepository->getFieldValues($lead->getId());
|
||||
$lead->setFields($fields);
|
||||
}
|
||||
|
||||
private function useSystemContact(): bool
|
||||
{
|
||||
if (null !== $this->useSystemContact) {
|
||||
return $this->useSystemContact;
|
||||
}
|
||||
|
||||
return $this->isUserSession() || $this->systemContact || defined('IN_MAUTIC_CONSOLE') || null === $this->getRequest();
|
||||
}
|
||||
|
||||
private function isUserSession(): bool
|
||||
{
|
||||
return !$this->security->isAnonymous();
|
||||
}
|
||||
|
||||
private function dispatchContactChangeEvent(Lead $previouslyTrackedContact, $previouslyTrackedId): void
|
||||
{
|
||||
$newTrackingId = $this->getTrackingId();
|
||||
$this->logger->debug(
|
||||
"CONTACT: Tracking code changed from $previouslyTrackedId for contact ID# {$previouslyTrackedContact->getId()} to $newTrackingId for contact ID# {$this->trackedContact->getId()}"
|
||||
);
|
||||
|
||||
if (null !== $previouslyTrackedId) {
|
||||
if ($this->dispatcher->hasListeners(LeadEvents::CURRENT_LEAD_CHANGED)) {
|
||||
$event = new LeadChangeEvent($previouslyTrackedContact, $previouslyTrackedId, $this->trackedContact, $newTrackingId);
|
||||
$this->dispatcher->dispatch($event, LeadEvents::CURRENT_LEAD_CHANGED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function generateTrackingCookies(): void
|
||||
{
|
||||
if ($this->trackedContact->getId() && $request = $this->getRequest()) {
|
||||
$this->deviceTracker->createDeviceFromUserAgent($this->trackedContact, $request->server->get('HTTP_USER_AGENT'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getRequest(): ?Request
|
||||
{
|
||||
return $this->requestStack->getCurrentRequest();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadDevice;
|
||||
use Mautic\LeadBundle\Tracker\Factory\DeviceDetectorFactory\DeviceDetectorFactoryInterface;
|
||||
use Mautic\LeadBundle\Tracker\Service\DeviceCreatorService\DeviceCreatorServiceInterface;
|
||||
use Mautic\LeadBundle\Tracker\Service\DeviceTrackingService\DeviceTrackingServiceInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class DeviceTracker
|
||||
{
|
||||
private bool $deviceWasChanged = false;
|
||||
|
||||
/**
|
||||
* @var LeadDevice[]
|
||||
*/
|
||||
private array $trackedDevice = [];
|
||||
|
||||
public function __construct(
|
||||
private DeviceCreatorServiceInterface $deviceCreatorService,
|
||||
private DeviceDetectorFactoryInterface $deviceDetectorFactory,
|
||||
private DeviceTrackingServiceInterface $deviceTrackingService,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LeadDevice|null
|
||||
*/
|
||||
public function createDeviceFromUserAgent(Lead $trackedContact, $userAgent)
|
||||
{
|
||||
$signature = $trackedContact->getId().$userAgent;
|
||||
if (isset($this->trackedDevice[$signature])) {
|
||||
// Prevent subsequent calls within the same session from creating multiple entries
|
||||
return $this->trackedDevice[$signature];
|
||||
}
|
||||
|
||||
$this->trackedDevice[$signature] = $trackedDevice = $this->deviceTrackingService->getTrackedDevice();
|
||||
|
||||
$deviceDetector = $this->deviceDetectorFactory->create($userAgent);
|
||||
$deviceDetector->parse();
|
||||
$currentDevice = $this->deviceCreatorService->getCurrentFromDetector($deviceDetector, $trackedContact);
|
||||
|
||||
if ( // Do not create a new device if
|
||||
// ... the device is new
|
||||
$trackedDevice && $trackedDevice->getId() // ... the device is the same
|
||||
&& $trackedDevice->getSignature() === $currentDevice->getSignature() // ... the contact given is the same as the owner of the device tracked
|
||||
&& $trackedDevice->getLead()->getId() === $trackedContact->getId()
|
||||
) {
|
||||
return $trackedDevice;
|
||||
}
|
||||
|
||||
// New device so record it and track it
|
||||
$this->deviceWasChanged = true;
|
||||
|
||||
$this->trackedDevice[$signature] = $this->deviceTrackingService->trackCurrentDevice($currentDevice, true);
|
||||
|
||||
return $this->trackedDevice[$signature];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LeadDevice|null
|
||||
*/
|
||||
public function getTrackedDevice()
|
||||
{
|
||||
$trackedDevice = $this->deviceTrackingService->getTrackedDevice();
|
||||
|
||||
if (null !== $trackedDevice) {
|
||||
$this->logger->debug("LEAD: Tracking ID for this device is {$trackedDevice->getTrackingId()}");
|
||||
}
|
||||
|
||||
return $trackedDevice;
|
||||
}
|
||||
|
||||
public function wasDeviceChanged(): bool
|
||||
{
|
||||
return $this->deviceWasChanged;
|
||||
}
|
||||
|
||||
public function clearTrackingCookies(): void
|
||||
{
|
||||
$this->deviceTrackingService->clearTrackingCookies();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker\Factory\DeviceDetectorFactory;
|
||||
|
||||
use DeviceDetector\Cache\PSR6Bridge;
|
||||
use DeviceDetector\DeviceDetector;
|
||||
use Mautic\CacheBundle\Cache\CacheProvider;
|
||||
|
||||
final class DeviceDetectorFactory implements DeviceDetectorFactoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private CacheProvider $cacheProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userAgent
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create($userAgent): DeviceDetector
|
||||
{
|
||||
$detector = new DeviceDetector((string) $userAgent);
|
||||
$bridge = new PSR6Bridge($this->cacheProvider->getCacheAdapter());
|
||||
$detector->setCache($bridge);
|
||||
|
||||
return $detector;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker\Factory\DeviceDetectorFactory;
|
||||
|
||||
use DeviceDetector\DeviceDetector;
|
||||
|
||||
/**
|
||||
* Interface DeviceDetectorFactoryInterface.
|
||||
*/
|
||||
interface DeviceDetectorFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @param string $userAgent
|
||||
*
|
||||
* @return DeviceDetector
|
||||
*/
|
||||
public function create($userAgent);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker\Service\ContactTrackingService;
|
||||
|
||||
use Mautic\CoreBundle\Helper\CookieHelper;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadDeviceRepository;
|
||||
use Mautic\LeadBundle\Entity\LeadRepository;
|
||||
use Mautic\LeadBundle\Entity\MergeRecordRepository;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Used to ensure that contacts tracked under the old method are continued to be tracked under the new.
|
||||
*/
|
||||
final class ContactTrackingService implements ContactTrackingServiceInterface
|
||||
{
|
||||
public function __construct(
|
||||
private CookieHelper $cookieHelper,
|
||||
private LeadDeviceRepository $leadDeviceRepository,
|
||||
private LeadRepository $leadRepository,
|
||||
private MergeRecordRepository $mergeRecordRepository,
|
||||
private RequestStack $requestStack,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lead|null
|
||||
*/
|
||||
public function getTrackedLead()
|
||||
{
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
if (null === $request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$trackingId = $this->getTrackedIdentifier();
|
||||
if (null === $trackingId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$leadId = $this->cookieHelper->getCookie($trackingId, null);
|
||||
if (null === $leadId) {
|
||||
$leadId = $request->get('mtc_id', null);
|
||||
if (null === $leadId) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$lead = $this->leadRepository->getEntity($leadId);
|
||||
if (null === $lead) {
|
||||
// Check if this contact was merged into another and if so, return the new contact
|
||||
$lead = $this->mergeRecordRepository->findMergedContact($leadId);
|
||||
|
||||
if (null === $lead) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Hydrate fields with custom field data
|
||||
$fields = $this->leadRepository->getFieldValues($lead->getId());
|
||||
$lead->setFields($fields);
|
||||
}
|
||||
|
||||
$anotherDeviceAlreadyTracked = $this->leadDeviceRepository->isAnyLeadDeviceTracked($lead);
|
||||
|
||||
return $anotherDeviceAlreadyTracked ? null : $lead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTrackedIdentifier()
|
||||
{
|
||||
return $this->cookieHelper->getCookie('mautic_session_id', null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker\Service\ContactTrackingService;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
|
||||
/**
|
||||
* Interface ContactTrackingInterface.
|
||||
*/
|
||||
interface ContactTrackingServiceInterface
|
||||
{
|
||||
/**
|
||||
* Return current tracked Lead.
|
||||
*
|
||||
* @return Lead|null
|
||||
*/
|
||||
public function getTrackedLead();
|
||||
|
||||
/**
|
||||
* @return string|null Unique identifier
|
||||
*/
|
||||
public function getTrackedIdentifier();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker\Service\DeviceCreatorService;
|
||||
|
||||
use DeviceDetector\DeviceDetector;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadDevice;
|
||||
|
||||
final class DeviceCreatorService implements DeviceCreatorServiceInterface
|
||||
{
|
||||
public function getCurrentFromDetector(DeviceDetector $deviceDetector, Lead $assignedLead): LeadDevice
|
||||
{
|
||||
$device = new LeadDevice();
|
||||
$device->setClientInfo($deviceDetector->getClient());
|
||||
$device->setDevice($deviceDetector->getDeviceName());
|
||||
$device->setDeviceBrand($deviceDetector->getBrandName());
|
||||
$device->setDeviceModel($deviceDetector->getModel());
|
||||
$device->setDeviceOs($deviceDetector->getOs());
|
||||
$device->setDateAdded(new \DateTime());
|
||||
$device->setLead($assignedLead);
|
||||
|
||||
return $device;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker\Service\DeviceCreatorService;
|
||||
|
||||
use DeviceDetector\DeviceDetector;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadDevice;
|
||||
|
||||
/**
|
||||
* Interface DeviceCreatorServiceInterface.
|
||||
*/
|
||||
interface DeviceCreatorServiceInterface
|
||||
{
|
||||
/**
|
||||
* @return LeadDevice|null Null is returned if device can't be detected
|
||||
*/
|
||||
public function getCurrentFromDetector(DeviceDetector $deviceDetector, Lead $assignedLead);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker\Service\DeviceTrackingService;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\CoreBundle\Helper\CookieHelper;
|
||||
use Mautic\CoreBundle\Helper\RandomHelper\RandomHelperInterface;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\LeadBundle\Entity\LeadDevice;
|
||||
use Mautic\LeadBundle\Entity\LeadDeviceRepository;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
final class DeviceTrackingService implements DeviceTrackingServiceInterface
|
||||
{
|
||||
private ?LeadDevice $trackedDevice = null;
|
||||
|
||||
public function __construct(
|
||||
private CookieHelper $cookieHelper,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private LeadDeviceRepository $leadDeviceRepository,
|
||||
private RandomHelperInterface $randomHelper,
|
||||
private RequestStack $requestStack,
|
||||
private CorePermissions $security,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isTracked(): bool
|
||||
{
|
||||
return null !== $this->getTrackedDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?LeadDevice
|
||||
*/
|
||||
public function getTrackedDevice()
|
||||
{
|
||||
if (!$this->security->isAnonymous()) {
|
||||
// Do not track Mautic users
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->trackedDevice) {
|
||||
return $this->trackedDevice;
|
||||
}
|
||||
|
||||
$trackingId = $this->getTrackedIdentifier();
|
||||
if (null === $trackingId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->leadDeviceRepository->getByTrackingId($trackingId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $replaceExistingTracking
|
||||
*
|
||||
* @return LeadDevice
|
||||
*/
|
||||
public function trackCurrentDevice(LeadDevice $device, $replaceExistingTracking = false)
|
||||
{
|
||||
$trackedDevice = $this->getTrackedDevice();
|
||||
if (null !== $trackedDevice && false === $replaceExistingTracking) {
|
||||
return $trackedDevice;
|
||||
}
|
||||
|
||||
if (null !== $existingDevice = $this->leadDeviceRepository->findExistingDevice($device)) {
|
||||
$device = $existingDevice;
|
||||
}
|
||||
|
||||
if (empty($device->getTrackingId())) {
|
||||
// Ensure all devices have a tracking ID (new devices will not and pre 2.13.0 devices may not)
|
||||
$device->setTrackingId($this->getUniqueTrackingIdentifier());
|
||||
|
||||
$this->entityManager->persist($device);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
$this->createTrackingCookies($device);
|
||||
|
||||
// Store the device in case a service uses this within the same session
|
||||
$this->trackedDevice = $device;
|
||||
|
||||
return $device;
|
||||
}
|
||||
|
||||
public function clearTrackingCookies(): void
|
||||
{
|
||||
$this->cookieHelper->deleteCookie('mautic_device_id');
|
||||
$this->cookieHelper->deleteCookie('mtc_id');
|
||||
}
|
||||
|
||||
private function getTrackedIdentifier(): ?string
|
||||
{
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
if (null === $request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->trackedDevice) {
|
||||
// Use the device tracked in case the cookies were just created
|
||||
return $this->trackedDevice->getTrackingId();
|
||||
}
|
||||
|
||||
$deviceTrackingId = $this->cookieHelper->getCookie('mautic_device_id', null);
|
||||
if (null === $deviceTrackingId) {
|
||||
$deviceTrackingId = $request->get('mautic_device_id', null);
|
||||
}
|
||||
|
||||
return $deviceTrackingId;
|
||||
}
|
||||
|
||||
private function getUniqueTrackingIdentifier(): string
|
||||
{
|
||||
do {
|
||||
$generatedIdentifier = $this->randomHelper->generate(23);
|
||||
$device = $this->leadDeviceRepository->getByTrackingId($generatedIdentifier);
|
||||
} while (null !== $device);
|
||||
|
||||
return $generatedIdentifier;
|
||||
}
|
||||
|
||||
private function createTrackingCookies(LeadDevice $device): void
|
||||
{
|
||||
// Device cookie
|
||||
$this->cookieHelper->setCookie('mautic_device_id', $device->getTrackingId(), 31_536_000, sameSite: Cookie::SAMESITE_NONE);
|
||||
|
||||
// Mainly for landing pages so that JS has the same access as 3rd party tracking code
|
||||
$this->cookieHelper->setCookie('mtc_id', $device->getLead()->getId(), null, sameSite: Cookie::SAMESITE_NONE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tracker\Service\DeviceTrackingService;
|
||||
|
||||
use Mautic\LeadBundle\Entity\LeadDevice;
|
||||
|
||||
/**
|
||||
* Interface DeviceTrackingServiceInterface.
|
||||
*/
|
||||
interface DeviceTrackingServiceInterface
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isTracked();
|
||||
|
||||
/**
|
||||
* @return LeadDevice|null
|
||||
*/
|
||||
public function getTrackedDevice();
|
||||
|
||||
/**
|
||||
* @param bool $replaceExistingTracking
|
||||
*
|
||||
* @return LeadDevice
|
||||
*/
|
||||
public function trackCurrentDevice(LeadDevice $device, $replaceExistingTracking = false);
|
||||
|
||||
public function clearTrackingCookies();
|
||||
}
|
||||
Reference in New Issue
Block a user