259 lines
7.8 KiB
PHP
Executable File
259 lines
7.8 KiB
PHP
Executable File
<?php
|
|
|
|
namespace Mautic\DynamicContentBundle\Helper;
|
|
|
|
use Mautic\CampaignBundle\Executioner\RealTimeExecutioner;
|
|
use Mautic\CoreBundle\Event\TokenReplacementEvent;
|
|
use Mautic\DynamicContentBundle\DynamicContentEvents;
|
|
use Mautic\DynamicContentBundle\Entity\DynamicContent;
|
|
use Mautic\DynamicContentBundle\Event\ContactFiltersEvaluateEvent;
|
|
use Mautic\DynamicContentBundle\Model\DynamicContentModel;
|
|
use Mautic\EmailBundle\EventListener\MatchFilterForLeadTrait;
|
|
use Mautic\LeadBundle\Entity\Lead;
|
|
use Mautic\LeadBundle\Entity\Tag;
|
|
use Mautic\LeadBundle\Model\LeadModel;
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|
|
|
class DynamicContentHelper
|
|
{
|
|
use MatchFilterForLeadTrait;
|
|
|
|
/**
|
|
* @const DYNAMIC_CONTENT_REGEX
|
|
*/
|
|
public const DYNAMIC_CONTENT_REGEX = '/{(dynamiccontent)=(\w+)(?:\/}|}(?:([^{]*(?:{(?!\/\1})[^{]*)*){\/\1})?)/is';
|
|
|
|
/**
|
|
* @const DYNAMIC_WEB_CONTENT_REGEX
|
|
*/
|
|
public const DYNAMIC_WEB_CONTENT_REGEX = '/{dwc=(.*?)}/';
|
|
|
|
public function __construct(
|
|
protected DynamicContentModel $dynamicContentModel,
|
|
protected RealTimeExecutioner $realTimeExecutioner,
|
|
protected EventDispatcherInterface $dispatcher,
|
|
protected LeadModel $leadModel,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @param string $slot
|
|
* @param Lead|array $lead
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getDynamicContentForLead($slot, $lead)
|
|
{
|
|
// Attempt campaign slots first
|
|
$dwcActionResponse = $this->realTimeExecutioner->execute('dwc.decision', $slot, 'dynamicContent')->getActionResponses('dwc.push_content');
|
|
if (!empty($dwcActionResponse)) {
|
|
return array_shift($dwcActionResponse);
|
|
}
|
|
|
|
// Attempt stored content second
|
|
$data = $this->dynamicContentModel->getSlotContentForLead($slot, $lead);
|
|
if (!empty($data)) {
|
|
$content = $data['content'];
|
|
$dwc = $this->dynamicContentModel->getEntity($data['id']);
|
|
if ($dwc instanceof DynamicContent) {
|
|
$content = $this->getRealDynamicContent($slot, $lead, $dwc);
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
// Finally attempt standalone DWC
|
|
return $this->getDynamicContentSlotForLead($slot, $lead);
|
|
}
|
|
|
|
/**
|
|
* @param string $slotName
|
|
* @param Lead|array $lead
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getDynamicContentSlotForLead($slotName, $lead)
|
|
{
|
|
$leadArray = [];
|
|
if ($lead instanceof Lead) {
|
|
$leadArray = $this->convertLeadToArray($lead);
|
|
}
|
|
|
|
$dwcs = $this->getDwcsBySlotName($slotName, true);
|
|
/** @var DynamicContent $dwc */
|
|
foreach ($dwcs as $dwc) {
|
|
if ($dwc->getIsCampaignBased()) {
|
|
continue;
|
|
}
|
|
if ($lead && $this->filtersMatchContact($dwc->getFilters(), $leadArray)) {
|
|
return $lead ? $this->getRealDynamicContent($dwc->getSlotName(), $lead, $dwc) : '';
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* @param string $content
|
|
* @param Lead|array $lead
|
|
*
|
|
* @return string Content with the {content} tokens replaced with dynamic content
|
|
*/
|
|
public function replaceTokensInContent($content, $lead)
|
|
{
|
|
// Find all dynamic content tags
|
|
preg_match_all(self::DYNAMIC_CONTENT_REGEX, $content, $matches, PREG_SET_ORDER);
|
|
|
|
foreach ($matches as $match) {
|
|
$slot = $match[2];
|
|
$defaultContent = $match[3];
|
|
|
|
$dwcContent = $this->getDynamicContentForLead($slot, $lead);
|
|
|
|
if (!$dwcContent) {
|
|
$dwcContent = $defaultContent;
|
|
}
|
|
|
|
$content = str_replace($matches[0], $dwcContent, $content);
|
|
}
|
|
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* @param string $content
|
|
* @param Lead|null $lead
|
|
*/
|
|
public function findDwcTokens($content, $lead): array
|
|
{
|
|
preg_match_all(self::DYNAMIC_WEB_CONTENT_REGEX, $content, $matches);
|
|
|
|
$tokens = [];
|
|
if (!empty($matches[1])) {
|
|
foreach ($matches[1] as $key => $slotName) {
|
|
$token = $matches[0][$key];
|
|
if (!empty($tokens[$token])) {
|
|
continue;
|
|
}
|
|
|
|
$dwcs = $this->getDwcsBySlotName($slotName);
|
|
|
|
/** @var DynamicContent $dwc */
|
|
foreach ($dwcs as $dwc) {
|
|
if ($dwc->getIsCampaignBased()) {
|
|
continue;
|
|
}
|
|
$content = $lead ? $this->getDynamicContentSlotForLead($dwc->getSlotName(), $lead) : '';
|
|
$tokens[$token]['content'] = $content;
|
|
$tokens[$token]['filters'] = $dwc->getFilters();
|
|
}
|
|
}
|
|
|
|
unset($matches);
|
|
}
|
|
|
|
return $tokens;
|
|
}
|
|
|
|
/**
|
|
* @param string $slot
|
|
* @param Lead|mixed[] $lead
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getRealDynamicContent($slot, $lead, DynamicContent $dwc)
|
|
{
|
|
$content = $dwc->getContent();
|
|
// Determine a translation based on contact's preferred locale
|
|
/** @var DynamicContent $translation */
|
|
list($ignore, $translation) = $this->dynamicContentModel->getTranslatedEntity($dwc, $lead);
|
|
if ($translation !== $dwc) {
|
|
// Use translated version of content
|
|
$dwc = $translation;
|
|
$content = $dwc->getContent();
|
|
}
|
|
$stat = $this->dynamicContentModel->createStatEntry($dwc, $lead, $slot);
|
|
|
|
$tokenEvent = new TokenReplacementEvent($content, $lead, ['slot' => $slot, 'dynamic_content_id' => $dwc->getId()]);
|
|
$tokenEvent->setStat($stat);
|
|
$this->dispatcher->dispatch($tokenEvent, DynamicContentEvents::TOKEN_REPLACEMENT);
|
|
|
|
return $tokenEvent->getContent();
|
|
}
|
|
|
|
/**
|
|
* @param string $slotName
|
|
* @param bool $publishedOnly
|
|
*
|
|
* @return array|\Doctrine\ORM\Tools\Pagination\Paginator
|
|
*/
|
|
public function getDwcsBySlotName($slotName, $publishedOnly = false)
|
|
{
|
|
$filter = [
|
|
'where' => [
|
|
[
|
|
'col' => 'e.slotName',
|
|
'expr' => 'eq',
|
|
'val' => $slotName,
|
|
],
|
|
],
|
|
];
|
|
|
|
if ($publishedOnly) {
|
|
$filter['where'][] = [
|
|
'col' => 'e.isPublished',
|
|
'expr' => 'eq',
|
|
'val' => 1,
|
|
];
|
|
}
|
|
|
|
return $this->dynamicContentModel->getEntities(
|
|
[
|
|
'filter' => $filter,
|
|
'ignore_paginator' => true,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param Lead $lead
|
|
*/
|
|
public function convertLeadToArray($lead): array
|
|
{
|
|
return array_merge(
|
|
$lead->getProfileFields(),
|
|
[
|
|
'tags' => array_map(
|
|
fn (Tag $v) => $v->getId(),
|
|
$lead->getTags()->toArray()
|
|
),
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param mixed[] $filters
|
|
* @param mixed[] $contactArray
|
|
*/
|
|
private function filtersMatchContact(array $filters, array $contactArray): bool
|
|
{
|
|
if (empty($contactArray['id'])) {
|
|
return false;
|
|
}
|
|
|
|
// We attempt even listeners first
|
|
if ($this->dispatcher->hasListeners(DynamicContentEvents::ON_CONTACTS_FILTER_EVALUATE)) {
|
|
/** @var Lead $contact */
|
|
$contact = $this->leadModel->getEntity($contactArray['id']);
|
|
|
|
$event = new ContactFiltersEvaluateEvent($filters, $contact);
|
|
$this->dispatcher->dispatch($event, DynamicContentEvents::ON_CONTACTS_FILTER_EVALUATE);
|
|
if ($event->isMatch()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return $this->matchFilterForLead($filters, $contactArray);
|
|
}
|
|
}
|