Spaces:
No application file
No application file
| 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('/{(dynamiccontent)=(\w+)(?:\/}|}(?:([^{]*(?:{(?!\/\1})[^{]*)*){\/\1})?)/is', $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('/{dwc=(.*?)}/', $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->getRealDynamicContent($dwc->getSlotName(), $lead, $dwc) : ''; | |
| $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); | |
| } | |
| } | |