mautic / app /bundles /CampaignBundle /Executioner /RealTimeExecutioner.php
chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
<?php
namespace Mautic\CampaignBundle\Executioner;
use Doctrine\Common\Collections\ArrayCollection;
use Mautic\CampaignBundle\Entity\Event;
use Mautic\CampaignBundle\Entity\EventRepository;
use Mautic\CampaignBundle\EventCollector\Accessor\Event\DecisionAccessor;
use Mautic\CampaignBundle\EventCollector\EventCollector;
use Mautic\CampaignBundle\Executioner\Event\DecisionExecutioner as Executioner;
use Mautic\CampaignBundle\Executioner\Exception\CampaignNotExecutableException;
use Mautic\CampaignBundle\Executioner\Exception\DecisionNotApplicableException;
use Mautic\CampaignBundle\Executioner\Helper\DecisionHelper;
use Mautic\CampaignBundle\Executioner\Result\Responses;
use Mautic\CampaignBundle\Executioner\Scheduler\EventScheduler;
use Mautic\CampaignBundle\Helper\ChannelExtractor;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Model\LeadModel;
use Mautic\LeadBundle\Tracker\ContactTracker;
use Psr\Log\LoggerInterface;
class RealTimeExecutioner
{
/**
* @var Lead
*/
private $contact;
/**
* @var array
*/
private $events;
private ?Responses $responses = null;
public function __construct(
private LoggerInterface $logger,
private LeadModel $leadModel,
private EventRepository $eventRepository,
private EventExecutioner $executioner,
private Executioner $decisionExecutioner,
private EventCollector $collector,
private EventScheduler $scheduler,
private ContactTracker $contactTracker,
private DecisionHelper $decisionHelper
) {
}
/**
* @param string $type
* @param mixed $passthrough
* @param string|null $channel
* @param int|null $channelId
*
* @return Responses
*
* @throws Dispatcher\Exception\LogNotProcessedException
* @throws Dispatcher\Exception\LogPassedAndFailedException
* @throws Exception\CannotProcessEventException
* @throws Scheduler\Exception\NotSchedulableException
*/
public function execute($type, $passthrough = null, $channel = null, $channelId = null)
{
$this->responses = new Responses();
$now = new \DateTime();
$this->logger->debug('CAMPAIGN: Campaign triggered for event type '.$type.'('.$channel.' / '.$channelId.')');
// Kept for BC support although not sure we need this
defined('MAUTIC_CAMPAIGN_NOT_SYSTEM_TRIGGERED') or define('MAUTIC_CAMPAIGN_NOT_SYSTEM_TRIGGERED', 1);
try {
$this->fetchCurrentContact();
} catch (CampaignNotExecutableException $exception) {
$this->logger->debug('CAMPAIGN: '.$exception->getMessage());
return $this->responses;
}
try {
$this->fetchCampaignData($type);
} catch (CampaignNotExecutableException $exception) {
$this->logger->debug('CAMPAIGN: '.$exception->getMessage());
return $this->responses;
}
/** @var Event $event */
foreach ($this->events as $event) {
try {
$this->evaluateDecisionForContact($event, $passthrough, $channel, $channelId);
} catch (DecisionNotApplicableException $exception) {
$this->logger->debug('CAMPAIGN: Event ID '.$event->getId().' is not applicable ('.$exception->getMessage().')');
continue;
}
$children = $event->getPositiveChildren();
if (!$children->count()) {
$this->logger->debug('CAMPAIGN: Event ID '.$event->getId().' has no positive children');
continue;
}
$this->executeAssociatedEvents($children, $now);
}
// Save any changes to the contact done by the listeners
if ($this->contact->getChanges()) {
$this->leadModel->saveEntity($this->contact, false);
}
return $this->responses;
}
/**
* @throws Dispatcher\Exception\LogNotProcessedException
* @throws Dispatcher\Exception\LogPassedAndFailedException
* @throws Exception\CannotProcessEventException
* @throws Scheduler\Exception\NotSchedulableException
*/
private function executeAssociatedEvents(ArrayCollection $children, \DateTime $now): void
{
$children = clone $children;
/** @var Event $child */
foreach ($children as $key => $child) {
$executionDate = $this->scheduler->getExecutionDateTime($child, $now);
$this->logger->debug(
'CAMPAIGN: Event ID# '.$child->getId().
' to be executed on '.$executionDate->format('Y-m-d H:i:s e')
);
if ($this->scheduler->shouldSchedule($executionDate, $now)) {
$this->scheduler->scheduleForContact($child, $executionDate, $this->contact);
$children->remove($key);
}
}
if ($children->count()) {
$this->executioner->executeEventsForContact($children, $this->contact, $this->responses);
}
}
/**
* @param mixed $passthrough
* @param string|null $channel
* @param int|null $channelId
*
* @throws DecisionNotApplicableException
* @throws Exception\CannotProcessEventException
*/
private function evaluateDecisionForContact(Event $event, $passthrough = null, $channel = null, $channelId = null): void
{
$this->logger->debug('CAMPAIGN: Executing '.$event->getType().' ID '.$event->getId().' for contact ID '.$this->contact->getId());
$this->decisionHelper->checkIsDecisionApplicableForContact($event, $this->contact, $channel, $channelId);
/** @var DecisionAccessor $config */
$config = $this->collector->getEventConfig($event);
$this->decisionExecutioner->evaluateForContact($config, $event, $this->contact, $passthrough, $channel, $channelId);
}
/**
* @throws CampaignNotExecutableException
*/
private function fetchCurrentContact(): void
{
$this->contact = $this->contactTracker->getContact();
if (!$this->contact instanceof Lead || !$this->contact->getId()) {
throw new CampaignNotExecutableException('Unidentifiable contact');
}
$this->logger->debug('CAMPAIGN: Current contact ID# '.$this->contact->getId());
}
/**
* @throws CampaignNotExecutableException
*/
private function fetchCampaignData($type): void
{
if (!$this->events = $this->eventRepository->getContactPendingEvents($this->contact->getId(), $type)) {
throw new CampaignNotExecutableException('Contact does not have any applicable '.$type.' associations.');
}
// 2.14 BC break workaround - pre 2.14 had a bug that recorded channelId for decisions as 1 regardless of actually ID
// if channelIdField was an array and only one item was selected. That caused the channel ID check in evaluateDecisionForContact
// to fail resulting in the decision never being evaluated. Therefore we are going to self heal these decisions.
/** @var Event $event */
foreach ($this->events as $event) {
if (1 === $event->getChannelId()) {
ChannelExtractor::setChannel($event, $event, $this->collector->getEventConfig($event));
$this->eventRepository->saveEntity($event);
}
}
$this->logger->debug('CAMPAIGN: Found '.count($this->events).' events to analyze for contact ID '.$this->contact->getId());
}
}