Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
<?php
namespace Mautic\LeadBundle\Services;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\LeadBundle\Model\FieldModel;
use Symfony\Contracts\Translation\TranslatorInterface;
class ContactColumnsDictionary
{
/**
* @var mixed[]
*/
private array $fieldList = [];
public function __construct(
protected FieldModel $fieldModel,
private TranslatorInterface $translator,
private CoreParametersHelper $coreParametersHelper,
) {
}
public function getColumns(): array
{
$columns = array_flip($this->coreParametersHelper->get('contact_columns', []));
$fields = $this->getFields();
foreach ($columns as $alias=>&$column) {
if (isset($fields[$alias])) {
$column = $fields[$alias];
}
}
return $columns;
}
public function getFields(): array
{
if ([] === $this->fieldList) {
$this->fieldList['name'] = sprintf(
'%s %s',
$this->translator->trans('mautic.core.firstname'),
$this->translator->trans('mautic.core.lastname')
);
$this->fieldList['email'] = $this->translator->trans('mautic.core.type.email');
$this->fieldList['location'] = $this->translator->trans('mautic.lead.lead.thead.location');
$this->fieldList['stage'] = $this->translator->trans('mautic.lead.stage.label');
$this->fieldList['points'] = $this->translator->trans('mautic.lead.points');
$this->fieldList['last_active'] = $this->translator->trans('mautic.lead.lastactive');
$this->fieldList['id'] = $this->translator->trans('mautic.core.id');
$this->fieldList = $this->fieldList + $this->fieldModel->getFieldList(false);
}
return $this->fieldList;
}
}

View File

@@ -0,0 +1,283 @@
<?php
namespace Mautic\LeadBundle\Services;
use Mautic\LeadBundle\Event\SegmentDictionaryGenerationEvent;
use Mautic\LeadBundle\Exception\FilterNotFoundException;
use Mautic\LeadBundle\LeadEvents;
use Mautic\LeadBundle\Segment\Query\Filter\BaseFilterQueryBuilder;
use Mautic\LeadBundle\Segment\Query\Filter\ChannelClickQueryBuilder;
use Mautic\LeadBundle\Segment\Query\Filter\DoNotContactFilterQueryBuilder;
use Mautic\LeadBundle\Segment\Query\Filter\ForeignFuncFilterQueryBuilder;
use Mautic\LeadBundle\Segment\Query\Filter\ForeignValueFilterQueryBuilder;
use Mautic\LeadBundle\Segment\Query\Filter\IntegrationCampaignFilterQueryBuilder;
use Mautic\LeadBundle\Segment\Query\Filter\SessionsFilterQueryBuilder;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ContactSegmentFilterDictionary
{
/**
* @var mixed[]
*/
private $filters = [];
public function __construct(
private EventDispatcherInterface $dispatcher,
) {
}
/**
* @return mixed[]
*/
public function getFilters()
{
if (empty($this->filters)) {
$this->setDefaultFilters();
$this->fetchFiltersFromSubscribers();
}
return $this->filters;
}
/**
* @param string $filterKey
*
* @return mixed[]
*
* @throws FilterNotFoundException
*/
public function getFilter($filterKey)
{
if (array_key_exists($filterKey, $this->getFilters())) {
return $this->filters[$filterKey];
}
throw new FilterNotFoundException("Filter '{$filterKey}' does not exist");
}
/**
* @param string $filterKey
* @param string $property
*
* @return string|int
*
* @throws FilterNotFoundException
*/
public function getFilterProperty($filterKey, $property)
{
$filter = $this->getFilter($filterKey);
if (array_key_exists($property, $filter)) {
return $filter[$property];
}
throw new FilterNotFoundException("Filter '{$filterKey}' does not have property '{$property}' exist");
}
private function setDefaultFilters(): void
{
$this->filters['lead_email_read_count'] = [
'type' => ForeignFuncFilterQueryBuilder::getServiceId(),
'foreign_table' => 'email_stats',
'foreign_table_field' => 'lead_id',
'table' => 'leads',
'table_field' => 'id',
'func' => 'sum',
'field' => 'open_count',
'null_value' => 0,
];
$this->filters['lead_email_received'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table_field' => 'lead_id',
'foreign_table' => 'email_stats',
'field' => 'email_id',
'where' => 'email_stats.is_read = 1',
];
$this->filters['hit_url_count'] = [
'type' => ForeignFuncFilterQueryBuilder::getServiceId(),
'foreign_table' => 'page_hits',
'foreign_table_field' => 'lead_id',
'table' => 'leads',
'table_field' => 'id',
'func' => 'count',
'field' => 'id',
];
$this->filters['lead_email_read_date'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'email_stats',
'field' => 'date_read',
];
$this->filters['lead_email_sent_date'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'email_stats',
'field' => 'date_sent',
];
$this->filters['hit_url_date'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'page_hits',
'field' => 'date_hit',
];
$this->filters['dnc_bounced'] = [
'type' => DoNotContactFilterQueryBuilder::getServiceId(),
];
$this->filters['dnc_bounced_sms'] = [
'type' => DoNotContactFilterQueryBuilder::getServiceId(),
];
$this->filters['dnc_unsubscribed'] = [
'type' => DoNotContactFilterQueryBuilder::getServiceId(),
];
$this->filters['dnc_manual_email'] = [
'type' => DoNotContactFilterQueryBuilder::getServiceId(),
];
$this->filters['dnc_unsubscribed_sms'] = [
'type' => DoNotContactFilterQueryBuilder::getServiceId(),
];
$this->filters['dnc_manual_sms'] = [
'type' => DoNotContactFilterQueryBuilder::getServiceId(),
];
$this->filters['leadlist'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_lists_leads',
'field' => 'leadlist_id',
'where' => 'lead_lists_leads.manually_removed = 0',
];
$this->filters['globalcategory'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_categories',
'field' => 'category_id',
];
$this->filters['tags'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_tags_xref',
'field' => 'tag_id',
];
$this->filters['lead_email_sent'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'email_stats',
'field' => 'email_id',
];
$this->filters['device_type'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_devices',
'field' => 'device',
];
$this->filters['device_brand'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_devices',
'field' => 'device_brand',
];
$this->filters['device_os'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_devices',
'field' => 'device_os_name',
];
$this->filters['device_model'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_devices',
'field' => 'device_model',
];
$this->filters['stage'] = [
'type' => BaseFilterQueryBuilder::getServiceId(),
'foreign_table' => 'leads',
'field' => 'stage_id',
];
$this->filters['notification'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'push_ids',
'field' => 'id',
];
$this->filters['page_id'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'page_hits',
'foreign_field' => 'page_id',
];
$this->filters['redirect_id'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'page_hits',
'foreign_field' => 'redirect_id',
];
$this->filters['source'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'page_hits',
'foreign_field' => 'source',
];
$this->filters['hit_url'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'page_hits',
'field' => 'url',
];
$this->filters['referer'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'page_hits',
];
$this->filters['source_id'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'page_hits',
];
$this->filters['url_title'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'page_hits',
];
$this->filters['email_id'] = [ // kept as email_id for BC
'type' => ChannelClickQueryBuilder::getServiceId(),
];
$this->filters['email_clicked_link_date'] = [
'type' => ChannelClickQueryBuilder::getServiceId(),
];
$this->filters['sms_clicked_link'] = [
'type' => ChannelClickQueryBuilder::getServiceId(),
];
$this->filters['sms_clicked_link_date'] = [
'type' => ChannelClickQueryBuilder::getServiceId(),
];
$this->filters['sessions'] = [
'type' => SessionsFilterQueryBuilder::getServiceId(),
];
$this->filters['integration_campaigns'] = [
'type' => IntegrationCampaignFilterQueryBuilder::getServiceId(),
];
$this->filters['utm_campaign'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_utmtags',
];
$this->filters['utm_content'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_utmtags',
];
$this->filters['utm_medium'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_utmtags',
];
$this->filters['utm_source'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_utmtags',
];
$this->filters['utm_term'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'lead_utmtags',
];
$this->filters['campaign'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'campaign_leads',
'field' => 'campaign_id',
'where' => 'campaign_leads.manually_removed = 0',
];
$this->filters['lead_asset_download'] = [
'type' => ForeignValueFilterQueryBuilder::getServiceId(),
'foreign_table' => 'asset_downloads',
'field' => 'asset_id',
];
}
/**
* Other bundles can add more filters by subscribing to this event.
*/
private function fetchFiltersFromSubscribers(): void
{
if ($this->dispatcher->hasListeners(LeadEvents::SEGMENT_DICTIONARY_ON_GENERATE)) {
$event = new SegmentDictionaryGenerationEvent($this->filters);
$this->dispatcher->dispatch($event, LeadEvents::SEGMENT_DICTIONARY_ON_GENERATE);
$this->filters = $event->getTranslations();
}
}
}

View File

@@ -0,0 +1,319 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Services;
use Mautic\CacheBundle\Cache\CacheProviderInterface;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\EmailBundle\Entity\StatRepository;
use Mautic\FormBundle\Entity\SubmissionRepository;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\PageBundle\Entity\HitRepository;
class PeakInteractionTimer
{
public const DEFAULT_BEST_HOUR_START = 9; // 9 AM
public const DEFAULT_BEST_HOUR_END = 12; // 12 PM
public const DEFAULT_BEST_DAYS = [2, 1, 4]; // Tuesday, Monday, Thursday
public const DEFAULT_FETCH_INTERACTIONS_FROM = '-60 days';
public const DEFAULT_FETCH_LIMIT = 50;
public const DEFAULT_CACHE_TIMEOUT = 43800; // in minutes ~ 1 month
public const MIN_INTERACTIONS = 5;
public const DEFAULT_MAX_OPTIMAL_DAYS = 3;
private const MINUTES_START_OF_HOUR = 0; // Start of the hour
private const HOUR_FORMAT = 'G'; // 0 through 23
private const DAY_FORMAT = 'N'; // ISO 8601 numeric representation of the day of the week
private ?\DateTimeZone $defaultTimezone = null;
private int $cacheTimeout;
private int $bestHourStart;
private int $bestDefaultHourStart;
private int $bestHourEnd;
private int $bestDefaultHourEnd;
/** @var int[] */
private array $bestDays;
/** @var int[] */
private array $bestDefaultDays;
private string $fetchInteractionsFrom;
private int $fetchLimit;
private int $maxOptimalDays;
public function __construct(
private CoreParametersHelper $coreParametersHelper,
private StatRepository $statRepository,
private HitRepository $hitRepository,
private SubmissionRepository $submissionRepository,
private CacheProviderInterface $cacheProvider,
) {
$this->cacheTimeout = $this->coreParametersHelper->get('peak_interaction_timer_cache_timeout');
$this->bestDefaultHourStart = $this->coreParametersHelper->get('peak_interaction_timer_best_default_hour_start');
$this->bestDefaultHourEnd = $this->coreParametersHelper->get('peak_interaction_timer_best_default_hour_end');
$this->bestDefaultDays = $this->coreParametersHelper->get('peak_interaction_timer_best_default_days');
$this->fetchInteractionsFrom = $this->coreParametersHelper->get('peak_interaction_timer_fetch_interactions_from');
$this->fetchLimit = $this->coreParametersHelper->get('peak_interaction_timer_fetch_limit');
$this->maxOptimalDays = count($this->bestDefaultDays);
}
/**
* Get the optimal time for a contact.
*/
public function getOptimalTime(Lead $contact): \DateTime
{
$this->resetBias();
$currentDateTime = $this->getContactDateTime($contact);
$interactions = $this->getContactInteractions($contact, $currentDateTime->getTimezone());
if (count($interactions) >= self::MIN_INTERACTIONS) {
$hours = array_column($interactions, 'hourOfDay');
[$this->bestHourStart, $this->bestHourEnd] = $this->calculateOptimalTime($hours);
}
return $this->isTimeOptimal($currentDateTime)
? $currentDateTime
: $this->getAdjustedDateTime($currentDateTime);
}
/**
* Get the optimal time and day for a contact.
*/
public function getOptimalTimeAndDay(Lead $contact): \DateTime
{
$this->resetBias();
$currentDateTime = $this->getContactDateTime($contact);
$interactions = $this->getContactInteractions($contact, $currentDateTime->getTimezone());
if (count($interactions) >= self::MIN_INTERACTIONS) {
$hours = array_column($interactions, 'hourOfDay');
$days = array_column($interactions, 'dayOfWeek');
[$this->bestHourStart, $this->bestHourEnd] = $this->calculateOptimalTime($hours);
$this->bestDays = $this->calculateOptimalDays($days);
}
return $this->isDayAndTimeOptimal($currentDateTime)
? $currentDateTime
: $this->findOptimalDateTime($currentDateTime);
}
private function resetBias(): void
{
$this->bestHourStart = (int) $this->bestDefaultHourStart;
$this->bestHourEnd = (int) $this->bestDefaultHourEnd;
$bestDays = array_map('intval', $this->bestDefaultDays);
$this->bestDays = !empty($bestDays) ? $bestDays : self::DEFAULT_BEST_DAYS;
$this->maxOptimalDays = count($this->bestDays);
}
private function isTimeOptimal(\DateTime $dateTime): bool
{
$hour = (int) $dateTime->format(self::HOUR_FORMAT);
return $hour >= $this->bestHourStart && $hour < $this->bestHourEnd;
}
private function isDayAndTimeOptimal(\DateTime $dateTime): bool
{
return in_array((int) $dateTime->format(self::DAY_FORMAT), $this->bestDays, true) && $this->isTimeOptimal($dateTime);
}
private function getAdjustedDateTime(\DateTime $dateTime): \DateTime
{
$adjustedDateTime = clone $dateTime;
$adjustedDateTime->setTime($this->bestHourStart, self::MINUTES_START_OF_HOUR);
return $adjustedDateTime <= $dateTime
? $adjustedDateTime->modify('+1 day')
: $adjustedDateTime;
}
private function findOptimalDateTime(\DateTime $dateTime): \DateTime
{
$optimalDateTime = $this->getAdjustedDateTime($dateTime);
while (!in_array((int) $optimalDateTime->format(self::DAY_FORMAT), $this->bestDays, true)) {
$optimalDateTime->modify('+1 day');
}
return $optimalDateTime;
}
private function getContactDateTime(Lead $contact): \DateTime
{
$timezone = $contact->getTimezone() ? new \DateTimeZone($contact->getTimezone()) : $this->getDefaultTimezone();
return $this->getCurrentDateTime($timezone);
}
protected function getCurrentDateTime(\DateTimeZone $timezone): \DateTime
{
return new \DateTime('now', $timezone);
}
private function getDefaultTimezone(): \DateTimeZone
{
return $this->defaultTimezone ??= new \DateTimeZone(
$this->coreParametersHelper->get('default_timezone', 'UTC')
);
}
/**
* @return array<int, array<string, mixed>>
*/
private function getContactInteractions(Lead $contact, \DateTimeZone $dateTimeZone): array
{
$cacheItem = $this->cacheProvider->getItem('contact.interactions.'.$contact->getId());
if ($cacheItem->isHit()) {
$interactions = $cacheItem->get();
} else {
$fetchInteractionsFromDate = $this->getCurrentDateTime($dateTimeZone)
->modify($this->fetchInteractionsFrom);
$emailReads = $this->getLeadStats($contact->getId(), $fetchInteractionsFromDate);
$pageHits = $this->getLeadHits($contact->getId(), $fetchInteractionsFromDate);
$formSubmissions = $this->getFormSubmissions($contact->getId(), $fetchInteractionsFromDate);
$emailReadInteractions = $this->processInteractions($emailReads, 'email.read', $dateTimeZone);
$pageHitInteractions = $this->processInteractions($pageHits, 'page.hit', $dateTimeZone);
$formInteractions = $this->processInteractions($formSubmissions, 'form.submit', $dateTimeZone);
$interactions = array_merge($emailReadInteractions, $pageHitInteractions, $formInteractions);
$cacheItem->set($interactions);
$cacheItem->expiresAfter($this->cacheTimeout * 60);
$this->cacheProvider->save($cacheItem);
}
return $interactions;
}
/**
* @param array<int, array<string, mixed>> $interactionsData
*
* @return array<int, array<string, mixed>>
*
* @throws \Exception
*/
private function processInteractions(array $interactionsData, string $type, \DateTimeZone $dateTimeZone): array
{
$interactions = [];
$registeredInteractions = []; // Keep track of registered interactions to ensure one interaction type per hour
foreach ($interactionsData as $interaction) {
$dateKey = match ($type) {
'email.read' => 'dateRead',
'page.hit' => 'dateHit',
'form.submit' => 'dateSubmitted',
default => throw new \Exception('Unhandled interaction type: '.$type),
};
$interactionDate = $interaction[$dateKey];
$interactionDate->setTimezone($dateTimeZone);
$interactionKey = $type.':'.$interactionDate->format('Y-m-d_H');
if (!in_array($interactionKey, $registeredInteractions)) {
$interactions[] = [
'type' => $type,
'date' => $interactionDate->format('Y-m-d H:i:s'),
'hourOfDay' => (int) $interactionDate->format(self::HOUR_FORMAT),
'dayOfWeek' => (int) $interactionDate->format(self::DAY_FORMAT),
'time' => $interactionDate->format('H:i:s'),
];
$registeredInteractions[] = $interactionKey;
}
}
return $interactions;
}
/**
* @return array<int, array<string, mixed>>
*/
private function getLeadStats(int $leadId, ?\DateTime $fromDate = null): array
{
return $this->statRepository->getLeadStats($leadId, [
'order' => ['timestamp', 'DESC'],
'limit' => $this->fetchLimit,
'state' => 'read',
'basic_select' => true,
'fromDate' => $fromDate,
]);
}
/**
* @return array<int, array<string, mixed>>
*/
private function getLeadHits(int $leadId, ?\DateTime $fromDate = null): array
{
return $this->hitRepository->getLeadHits($leadId, [
'order' => ['timestamp', 'DESC'],
'limit' => $this->fetchLimit,
'fromDate' => $fromDate,
]);
}
/**
* @return array<int, array<string, mixed>>
*/
private function getFormSubmissions(int $leadId, ?\DateTime $fromDate = null): array
{
return $this->submissionRepository->getSubmissions([
'leadId' => $leadId,
'order' => ['timestamp', 'DESC'],
'limit' => $this->fetchLimit,
'fromDate' => $fromDate,
]);
}
/**
* Calculates the optimal time range based on an array of elements.
*
* @param int[] $elements Hours (0-23)
*
* @return int[] Hours (0-23)
*/
private function calculateOptimalTime(array $elements): array
{
sort($elements);
$count = count($elements);
if ($count > 0) {
$middleIndex = (int) floor(($count - 1) / 2);
$result = $elements[$middleIndex];
} else {
throw new \Exception('Not enough elements to calculate optimal time');
}
$start = ($result + 23) % 24; // hour before
$end = ($result + 1) % 24; // hour after
// Return the start and end hours as an array
return [$start, $end];
}
/**
* Calculates the optimal days based on the frequency of elements.
*
* @param int[] $elements Days of the week (ISO 8601)
*
* @return int[] Days of the week (ISO 8601)
*
* @throws \Exception
*/
private function calculateOptimalDays(array $elements): array
{
if (0 === count($elements)) {
throw new \Exception('Not enough elements to calculate optimal days');
}
// Count the frequency of each element.
$frequency = array_count_values($elements);
// Sort frequencies in descending order.
arsort($frequency);
// Get the elements sorted by frequency.
$optimalDays = array_keys($frequency);
// Return the top elements up to the max optimal days limit.
return array_slice($optimalDays, 0, min($this->maxOptimalDays, count($optimalDays)));
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Services;
use Mautic\CoreBundle\Helper\Tree\IntNode;
use Mautic\CoreBundle\Helper\Tree\NodeInterface;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Model\ListModel;
use Symfony\Component\Routing\RouterInterface;
class SegmentDependencyTreeFactory
{
/**
* @var int[]
*/
private array $usedSegmentIds = [];
public function __construct(
private ListModel $segmentModel,
private RouterInterface $router,
) {
}
public function buildTree(LeadList $segment, ?NodeInterface $rootNode = null): NodeInterface
{
$rootNode ??= new IntNode($segment->getId());
$childSegments = $this->findChildSegments($segment);
$rootNode->addParam('name', $segment->getName());
$rootNode->addParam('link', $this->generateSegmentDetailRoute($segment));
$this->usedSegmentIds[] = $segment->getId();
foreach ($childSegments as $childSegment) {
$childNode = new IntNode($childSegment->getId());
$rootNode->addChild($childNode);
$childNode->addParam('name', $childSegment->getName());
$childNode->addParam('link', $this->generateSegmentDetailRoute($childSegment));
// Be aware of the loops here. We must stop building children
// and report the problem instead if there is a loop or duplicate segments.
if (!in_array($childSegment->getId(), $this->usedSegmentIds)) {
$this->buildTree($childSegment, $childNode);
} else {
$childNode->addParam('message', 'This segment already exists in the segment dependency tree');
}
}
return $rootNode;
}
/**
* @return LeadList[]
*/
private function findChildSegments(LeadList $segment): array
{
$segmentMembershipFilters = array_filter(
$segment->getFilters(),
fn (array $filter): bool => 'leadlist' === $filter['type']
);
if (!$segmentMembershipFilters) {
return [];
}
$childSegmentIds = [];
foreach ($segmentMembershipFilters as $filter) {
// Old segments don't use properties array.
$segmentIds = $filter['properties']['filter'] ?? $filter['filter'];
foreach ($segmentIds as $childSegmentId) {
$childSegmentIds[] = (int) $childSegmentId;
}
}
return $this->segmentModel->getRepository()->findBy(['id' => $childSegmentIds]);
}
private function generateSegmentDetailRoute(LeadList $segment): string
{
return $this->router->generate(
'mautic_segment_action',
[
'objectAction' => 'view',
'objectId' => $segment->getId(),
]
);
}
}