chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
<?php
namespace Mautic\LeadBundle\Model;
use Doctrine\ORM\EntityManagerInterface;
use Mautic\CategoryBundle\Model\CategoryModel;
use Mautic\CoreBundle\Helper\Chart\BarChart;
use Mautic\CoreBundle\Helper\Chart\ChartQuery;
use Mautic\CoreBundle\Helper\Chart\LineChart;
use Mautic\CoreBundle\Helper\Chart\PieChart;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\CoreBundle\Helper\ProgressBarHelper;
use Mautic\CoreBundle\Helper\UserHelper;
use Mautic\CoreBundle\Model\FormModel;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadField;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Entity\LeadListRepository;
use Mautic\LeadBundle\Entity\ListLead;
use Mautic\LeadBundle\Entity\ListLeadRepository;
use Mautic\LeadBundle\Entity\OperatorListTrait;
use Mautic\LeadBundle\Event\LeadListEvent;
use Mautic\LeadBundle\Event\LeadListFiltersChoicesEvent;
use Mautic\LeadBundle\Event\ListChangeEvent;
use Mautic\LeadBundle\Event\ListPreProcessListEvent;
use Mautic\LeadBundle\Form\Type\ListType;
use Mautic\LeadBundle\Helper\SegmentCountCacheHelper;
use Mautic\LeadBundle\LeadEvents;
use Mautic\LeadBundle\Segment\ContactSegmentService;
use Mautic\LeadBundle\Segment\Exception\FieldNotFoundException;
use Mautic\LeadBundle\Segment\Exception\SegmentNotFoundException;
use Mautic\LeadBundle\Segment\Exception\TableNotFoundException;
use Mautic\LeadBundle\Segment\Stat\ChartQuery\SegmentContactsLineChartQuery;
use Mautic\LeadBundle\Segment\Stat\SegmentChartQueryFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* @extends FormModel<LeadList>
*/
class ListModel extends FormModel
{
use OperatorListTrait;
/**
* @var mixed[]
*/
private array $choiceFieldsCache = [];
public function __construct(
protected CategoryModel $categoryModel,
CoreParametersHelper $coreParametersHelper,
private ContactSegmentService $leadSegmentService,
private SegmentChartQueryFactory $segmentChartQueryFactory,
private RequestStack $requestStack,
private SegmentCountCacheHelper $segmentCountCacheHelper,
EntityManagerInterface $em,
CorePermissions $security,
EventDispatcherInterface $dispatcher,
UrlGeneratorInterface $router,
Translator $translator,
UserHelper $userHelper,
LoggerInterface $mauticLogger
) {
parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper);
}
/**
* Used by addLead and removeLead functions.
*/
private array $leadChangeLists = [];
/**
* @return LeadListRepository
*/
public function getRepository()
{
/** @var LeadListRepository $repo */
$repo = $this->em->getRepository(LeadList::class);
$repo->setDispatcher($this->dispatcher);
$repo->setTranslator($this->translator);
return $repo;
}
/**
* Returns the repository for the table that houses the leads associated with a list.
*
* @return ListLeadRepository
*/
public function getListLeadRepository()
{
return $this->em->getRepository(ListLead::class);
}
public function getPermissionBase(): string
{
return 'lead:lists';
}
/**
* @param bool $unlock
*
* @throws \Doctrine\DBAL\Exception
*/
public function saveEntity($entity, $unlock = true): void
{
$isNew = ($entity->getId()) ? false : true;
// set some defaults
$this->setTimestamps($entity, $isNew, $unlock);
$alias = $entity->getAlias();
if (empty($alias)) {
$alias = $entity->getName();
}
$alias = $this->cleanAlias($alias, '', 0, '-');
// make sure alias is not already taken
$repo = $this->getRepository();
$testAlias = $alias;
$existing = $repo->getLists(null, $testAlias, $entity->getId());
$count = count($existing);
$aliasTag = $count;
while ($count) {
$testAlias = $alias.$aliasTag;
$existing = $repo->getLists(null, $testAlias, $entity->getId());
$count = count($existing);
++$aliasTag;
}
if ($testAlias != $alias) {
$alias = $testAlias;
}
$entity->setAlias($alias);
$publicName = $entity->getPublicName();
if (empty($publicName)) {
$entity->setPublicName($entity->getName());
}
$event = $this->dispatchEvent('pre_save', $entity, $isNew);
$repo->saveEntity($entity);
$this->dispatchEvent('post_save', $entity, $isNew, $event);
}
/**
* @param string|null $action
* @param array $options
*
* @return FormInterface<LeadList>
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function createForm($entity, FormFactoryInterface $formFactory, $action = null, $options = []): FormInterface
{
if (!$entity instanceof LeadList) {
throw new MethodNotAllowedHttpException(['LeadList'], 'Entity must be of class LeadList()');
}
if (!empty($action)) {
$options['action'] = $action;
}
return $formFactory->create(ListType::class, $entity, $options);
}
/**
* Get a specific entity or generate a new one if id is empty.
*/
public function getEntity($id = null): ?LeadList
{
if (null === $id) {
return new LeadList();
}
return parent::getEntity($id);
}
/**
* @throws MethodNotAllowedHttpException
*/
protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null): ?Event
{
if (!$entity instanceof LeadList) {
throw new MethodNotAllowedHttpException(['LeadList'], 'Entity must be of class LeadList()');
}
switch ($action) {
case 'pre_save':
$name = LeadEvents::LIST_PRE_SAVE;
break;
case 'post_save':
$name = LeadEvents::LIST_POST_SAVE;
break;
case 'pre_delete':
$name = LeadEvents::LIST_PRE_DELETE;
break;
case 'post_delete':
$name = LeadEvents::LIST_POST_DELETE;
break;
case 'pre_unpublish':
$name = LeadEvents::LIST_PRE_UNPUBLISH;
break;
default:
return null;
}
if ($this->dispatcher->hasListeners($name)) {
if (empty($event)) {
$event = new LeadListEvent($entity, $isNew);
$event->setEntityManager($this->em);
}
$this->dispatcher->dispatch($event, $name);
return $event;
} else {
return null;
}
}
/**
* Get a list of field choices for filters.
*
* @return mixed[]
*/
public function getChoiceFields(string $search = ''): array
{
if ($this->choiceFieldsCache) {
return $this->choiceFieldsCache;
}
$choices = [];
$choices['lead']['tags'] =
[
'label' => $this->translator->trans('mautic.lead.list.filter.tags'),
'properties' => [
'type' => 'tags',
],
'operators' => $this->getOperatorsForFieldType('multiselect'),
'object' => 'lead',
];
// Add custom choices
if ($this->dispatcher->hasListeners(LeadEvents::LIST_FILTERS_CHOICES_ON_GENERATE)) {
$event = new LeadListFiltersChoicesEvent([], $this->getOperatorsForFieldType(), $this->translator, $this->requestStack->getCurrentRequest(), $search);
$this->dispatcher->dispatch($event, LeadEvents::LIST_FILTERS_CHOICES_ON_GENERATE);
$choices = $event->getChoices();
}
// Order choices by label.
foreach ($choices as $key => $choice) {
$cmp = fn ($a, $b): int => strcmp($a['label'], $b['label']);
uasort($choice, $cmp);
$choices[$key] = $choice;
}
$this->choiceFieldsCache = $choices;
return $choices;
}
/**
* @param string $alias
*
* @return array
*/
public function getUserLists($alias = '')
{
$user = !$this->security->isGranted('lead:lists:viewother') ? $this->userHelper->getUser() : null;
return $this->em->getRepository(LeadList::class)->getLists($user, $alias);
}
/**
* Get a list of global lead lists.
*
* @return mixed
*/
public function getGlobalLists()
{
return $this->em->getRepository(LeadList::class)->getGlobalLists();
}
/**
* Get a list of preference center lead lists.
*
* @return mixed
*/
public function getPreferenceCenterLists()
{
return $this->em->getRepository(LeadList::class)->getPreferenceCenterList();
}
/**
* @param int $limit
* @param bool|int $maxLeads
*
* @throws \Exception
*/
public function rebuildListLeads(LeadList $leadList, $limit = 100, $maxLeads = false, OutputInterface $output = null): int
{
defined('MAUTIC_REBUILDING_LEAD_LISTS') or define('MAUTIC_REBUILDING_LEAD_LISTS', 1);
$segmentId = $leadList->getId();
$dtHelper = new DateTimeHelper();
$batchLimiters = ['dateTime' => $dtHelper->toUtcString()];
$list = ['id' => $segmentId, 'filters' => $leadList->getFilters()];
$this->dispatcher->dispatch(
new ListPreProcessListEvent($list, false), LeadEvents::LIST_PRE_PROCESS_LIST
);
try {
// Get a count of leads to add
$newLeadsCount = $this->leadSegmentService->getNewLeadListLeadsCount($leadList, $batchLimiters);
} catch (FieldNotFoundException) {
// A field from filter does not exist anymore. Do not rebuild.
return 0;
} catch (SegmentNotFoundException) {
// A segment from filter does not exist anymore. Do not rebuild.
return 0;
} catch (TableNotFoundException $e) {
// Invalid filter table, filter definition is not well asset or it is deleted. Do not rebuild but log.
$this->logger->error($e->getMessage());
return 0;
}
// Ensure the same list is used each batch <- would love to know how
$batchLimiters['maxId'] = (int) $newLeadsCount[$segmentId]['maxId'];
// Number of total leads to process
$leadCount = (int) $newLeadsCount[$segmentId]['count'];
$this->logger->info('Segment QB - No new leads for segment found');
if ($output) {
$output->writeln($this->translator->trans('mautic.lead.list.rebuild.to_be_added', ['%leads%' => $leadCount, '%batch%' => $limit]));
}
// Handle by batches
$start = $leadsProcessed = 0;
// Try to save some memory
gc_enable();
if ($leadCount) {
$maxCount = $maxLeads ?: $leadCount;
if ($output) {
$progress = ProgressBarHelper::init($output, $maxCount);
$progress->start();
}
// Add leads
while ($start < $leadCount) {
// Keep CPU down for large lists; sleep per $limit batch
$this->batchSleep();
$this->logger->debug(sprintf('Segment QB - Fetching new leads for segment [%d] %s', $segmentId, $leadList->getName()));
$newLeadList = $this->leadSegmentService->getNewLeadListLeads($leadList, $batchLimiters, $limit);
if (empty($newLeadList[$segmentId])) {
// Somehow ran out of leads so break out
break;
}
$this->logger->debug(sprintf('Segment QB - Adding %d new leads to segment [%d] %s', count($newLeadList[$segmentId]), $segmentId, $leadList->getName()));
foreach ($newLeadList[$segmentId] as $l) {
$this->logger->debug(sprintf('Segment QB - Adding lead #%s to segment [%d] %s', $l['id'], $segmentId, $leadList->getName()));
$this->addLead($l, $leadList, false, true, -1, $dtHelper->getLocalDateTime());
++$leadsProcessed;
if ($output && $leadsProcessed < $maxCount) {
$progress->setProgress($leadsProcessed);
}
if ($maxLeads && $leadsProcessed >= $maxLeads) {
break;
}
}
$this->logger->info(sprintf('Segment QB - Added %d new leads to segment [%d] %s', count($newLeadList[$segmentId]), $segmentId, $leadList->getName()));
$start += $limit;
// Dispatch batch event
if ($this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_BATCH_CHANGE)) {
$this->dispatcher->dispatch(
new ListChangeEvent($newLeadList[$segmentId], $leadList, true),
LeadEvents::LEAD_LIST_BATCH_CHANGE
);
}
unset($newLeadList);
// Free some memory
gc_collect_cycles();
if ($maxLeads && $leadsProcessed >= $maxLeads) {
if ($output) {
$progress->finish();
$output->writeln('');
}
return $leadsProcessed;
}
}
if ($output) {
$progress->finish();
$output->writeln('');
}
}
// Unset max ID to prevent capping at newly added max ID
unset($batchLimiters['maxId']);
$orphanLeadsCount = $this->leadSegmentService->getOrphanedLeadListLeadsCount($leadList);
// Ensure the same list is used each batch
$batchLimiters['maxId'] = (int) $orphanLeadsCount[$segmentId]['maxId'];
// Restart batching
$start = 0;
$leadCount = $orphanLeadsCount[$segmentId]['count'];
if ($output) {
$output->writeln($this->translator->trans('mautic.lead.list.rebuild.to_be_removed', ['%leads%' => $leadCount, '%batch%' => $limit]));
}
if ($leadCount) {
$maxCount = $maxLeads ?: $leadCount;
if ($output) {
$progress = ProgressBarHelper::init($output, $maxCount);
$progress->start();
}
// Remove leads
while ($start < $leadCount) {
// Keep CPU down for large lists; sleep per $limit batch
$this->batchSleep();
$removeLeadList = $this->leadSegmentService->getOrphanedLeadListLeads($leadList, [], $limit);
if (empty($removeLeadList[$segmentId])) {
// Somehow ran out of leads so break out
break;
}
$processedLeads = [];
foreach ($removeLeadList[$segmentId] as $l) {
$this->removeLead($l, $leadList, false, true, true);
$processedLeads[] = $l;
++$leadsProcessed;
if ($output && $leadsProcessed < $maxCount) {
$progress->setProgress($leadsProcessed);
}
if ($maxLeads && $leadsProcessed >= $maxLeads) {
break;
}
}
// Dispatch batch event
if (count($processedLeads) && $this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_BATCH_CHANGE)) {
$this->dispatcher->dispatch(
new ListChangeEvent($processedLeads, $leadList, false),
LeadEvents::LEAD_LIST_BATCH_CHANGE
);
}
$start += $limit;
unset($removeLeadList);
// Free some memory
gc_collect_cycles();
if ($maxLeads && $leadsProcessed >= $maxLeads) {
if ($output) {
$progress->finish();
$output->writeln('');
}
return $leadsProcessed;
}
}
if ($output) {
$progress->finish();
$output->writeln('');
}
}
$totalLeadCount = $this->getRepository()->getLeadCount($segmentId);
$this->segmentCountCacheHelper->setSegmentContactCount($segmentId, (int) $totalLeadCount);
return $leadsProcessed;
}
/**
* Add lead to lists.
*
* @param array|int|Lead $lead
* @param array|LeadList $lists
* @param bool $manuallyAdded
* @param bool $batchProcess
* @param int $searchListLead 0 = reference, 1 = yes, -1 = known to not exist
* @param \DateTime $dateManipulated
*
* @throws \Exception
*/
public function addLead($lead, $lists, $manuallyAdded = false, $batchProcess = false, $searchListLead = 1, $dateManipulated = null): void
{
if (null == $dateManipulated) {
$dateManipulated = new \DateTime();
}
if (!$lead instanceof Lead) {
$leadId = (is_array($lead) && isset($lead['id'])) ? $lead['id'] : $lead;
$lead = $this->em->getReference(Lead::class, $leadId);
} else {
$leadId = $lead->getId();
}
if (!$lists instanceof LeadList) {
// make sure they are ints
$searchForLists = [];
foreach ($lists as &$l) {
$l = (int) $l;
if (!isset($this->leadChangeLists[$l])) {
$searchForLists[] = $l;
}
}
if (!empty($searchForLists)) {
$listEntities = $this->getEntities([
'filter' => [
'force' => [
[
'column' => 'l.id',
'expr' => 'in',
'value' => $searchForLists,
],
],
],
]);
foreach ($listEntities as $list) {
$this->leadChangeLists[$list->getId()] = $list;
}
}
unset($listEntities, $searchForLists);
} else {
$this->leadChangeLists[$lists->getId()] = $lists;
$lists = [$lists->getId()];
}
if (!is_array($lists)) {
$lists = [$lists];
}
$persistLists = [];
$dispatchEvents = [];
foreach ($lists as $listId) {
if (!isset($this->leadChangeLists[$listId])) {
// List no longer exists in the DB so continue to the next
continue;
}
if (-1 == $searchListLead) {
$listLead = null;
} elseif ($searchListLead) {
$listLead = $this->getListLeadRepository()->findOneBy(
[
'lead' => $lead,
'list' => $this->leadChangeLists[$listId],
]
);
} else {
$listLead = $this->em->getReference(ListLead::class,
[
'lead' => $leadId,
'list' => $listId,
]
);
}
if (null != $listLead) {
if ($manuallyAdded && $listLead->wasManuallyRemoved()) {
$listLead->setManuallyRemoved(false);
$listLead->setManuallyAdded($manuallyAdded);
$persistLists[] = $listLead;
$dispatchEvents[] = $listId;
} else {
// Detach from Doctrine
$this->em->detach($listLead);
continue;
}
} else {
$listLead = new ListLead();
$listLead->setList($this->em->getReference(LeadList::class, $listId));
$listLead->setLead($lead);
$listLead->setManuallyAdded($manuallyAdded);
$listLead->setDateAdded($dateManipulated);
$persistLists[] = $listLead;
$dispatchEvents[] = $listId;
}
$this->segmentCountCacheHelper->incrementSegmentContactCount($listId);
}
if (!empty($persistLists)) {
$this->getRepository()->saveEntities($persistLists);
}
// Clear ListLead entities from Doctrine memory
$this->getRepository()->detachEntities($persistLists);
if ($batchProcess) {
// Detach for batch processing to preserve memory
$this->em->detach($lead);
} elseif (!empty($dispatchEvents) && $this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_CHANGE)) {
foreach ($dispatchEvents as $listId) {
$event = new ListChangeEvent($lead, $this->leadChangeLists[$listId]);
$this->dispatcher->dispatch($event, LeadEvents::LEAD_LIST_CHANGE);
unset($event);
}
}
unset($lead, $persistLists, $lists);
}
/**
* Remove a lead from lists.
*
* @param bool $manuallyRemoved
* @param bool $batchProcess
* @param bool $skipFindOne
*
* @throws \Exception
*/
public function removeLead($lead, $lists, $manuallyRemoved = false, $batchProcess = false, $skipFindOne = false): void
{
if (!$lead instanceof Lead) {
$leadId = (is_array($lead) && isset($lead['id'])) ? $lead['id'] : $lead;
$lead = $this->em->getReference(Lead::class, $leadId);
} else {
$leadId = $lead->getId();
}
if (!$lists instanceof LeadList) {
// make sure they are ints
$searchForLists = [];
foreach ($lists as &$l) {
$l = (int) $l;
if (!isset($this->leadChangeLists[$l])) {
$searchForLists[] = $l;
}
}
if (!empty($searchForLists)) {
$listEntities = $this->getEntities([
'filter' => [
'force' => [
[
'column' => 'l.id',
'expr' => 'in',
'value' => $searchForLists,
],
],
],
]);
foreach ($listEntities as $list) {
$this->leadChangeLists[$list->getId()] = $list;
}
}
unset($listEntities, $searchForLists);
} else {
$this->leadChangeLists[$lists->getId()] = $lists;
$lists = [$lists->getId()];
}
if (!is_array($lists)) {
$lists = [$lists];
}
$persistLists = [];
$deleteLists = [];
$dispatchEvents = [];
foreach ($lists as $listId) {
if (!isset($this->leadChangeLists[$listId])) {
// List no longer exists in the DB so continue to the next
continue;
}
$listLead = (!$skipFindOne) ?
$this->getListLeadRepository()->findOneBy([
'lead' => $lead,
'list' => $this->leadChangeLists[$listId],
]) :
$this->em->getReference(ListLead::class, [
'lead' => $leadId,
'list' => $listId,
]);
if (null == $listLead) {
// Lead is not part of this list
continue;
}
if (($manuallyRemoved && $listLead->wasManuallyAdded()) || (!$manuallyRemoved && !$listLead->wasManuallyAdded())) {
// lead was manually added and now manually removed or was not manually added and now being removed
$deleteLists[] = $listLead;
$dispatchEvents[] = $listId;
} elseif ($manuallyRemoved && !$listLead->wasManuallyAdded()) {
$listLead->setManuallyRemoved(true);
$persistLists[] = $listLead;
$dispatchEvents[] = $listId;
}
$this->segmentCountCacheHelper->decrementSegmentContactCount($listId);
unset($listLead);
}
if (!empty($persistLists)) {
$this->getRepository()->saveEntities($persistLists);
}
if (!empty($deleteLists)) {
$this->getRepository()->deleteEntities($deleteLists);
}
// Clear ListLead entities from Doctrine memory
$this->getListLeadRepository()->detachEntities($persistLists);
$this->getListLeadRepository()->detachEntities($deleteLists);
if ($batchProcess) {
// Detach for batch processing to preserve memory
$this->em->detach($lead);
} elseif (!empty($dispatchEvents) && $this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_CHANGE)) {
foreach ($dispatchEvents as $listId) {
$event = new ListChangeEvent($lead, $this->leadChangeLists[$listId], false);
$this->dispatcher->dispatch($event, LeadEvents::LEAD_LIST_CHANGE);
unset($event);
}
}
unset($lead, $deleteLists, $persistLists, $lists);
}
/**
* Batch sleep according to settings.
*/
protected function batchSleep()
{
$leadSleepTime = $this->coreParametersHelper->get('batch_lead_sleep_time', false);
if (false === $leadSleepTime) {
$leadSleepTime = $this->coreParametersHelper->get('batch_sleep_time', 1);
}
if (empty($leadSleepTime)) {
return;
}
if ($leadSleepTime < 1) {
usleep($leadSleepTime * 1_000_000);
} else {
sleep($leadSleepTime);
}
}
/**
* Get a list of top (by leads added) lists.
*
* @param int $limit
* @param \DateTime $dateFrom
* @param \DateTime $dateTo
* @param bool $canViewOthers
*
* @return array
*/
public function getTopLists($limit = 10, $dateFrom = null, $dateTo = null, $canViewOthers = true)
{
$q = $this->em->getConnection()->createQueryBuilder();
$q->select('COUNT(t.date_added) AS leads, ll.id, ll.name, ll.alias')
->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 't')
->join('t', MAUTIC_TABLE_PREFIX.'lead_lists', 'll', 'll.id = t.leadlist_id')
->orderBy('leads', 'DESC')
->where($q->expr()->eq('ll.is_published', ':published'))
->setParameter('published', true)
->groupBy('ll.id')
->setMaxResults($limit);
if (!$canViewOthers) {
$q->andWhere('ll.created_by = :userId')
->setParameter('userId', $this->userHelper->getUser()->getId());
}
$chartQuery = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
$chartQuery->applyDateFilters($q, 'date_added');
return $q->execute()->fetchAllAssociative();
}
/**
* Get a list of top (by leads added) lists.
*
* @param int $limit
* @param string $dateFrom
* @param string $dateTo
*
* @return array
*/
public function getLifeCycleSegments($limit, $dateFrom, $dateTo, $canViewOthers, $segments)
{
if (!empty($segments)) {
$segmentlist = "'".implode("','", $segments)."'";
}
$q = $this->em->getConnection()->createQueryBuilder();
$q->select('COUNT(t.date_added) AS leads, ll.id, ll.name as name,ll.alias as alias')
->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 't')
->join('t', MAUTIC_TABLE_PREFIX.'lead_lists', 'll', 'll.id = t.leadlist_id')
->join('t', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = t.lead_id')
->orderBy('leads', 'DESC')
->where($q->expr()->eq('ll.is_published', ':published'))
->setParameter('published', true)
->groupBy('ll.id');
if ($limit) {
$q->setMaxResults($limit);
}
if (!empty($segments)) {
$q->andWhere('ll.id IN ('.$segmentlist.')');
}
if (!empty($dateFrom)) {
$q->andWhere("l.date_added >= '".$dateFrom->format('Y-m-d')."'");
}
if (!empty($dateTo)) {
$q->andWhere("l.date_added <= '".$dateTo->format('Y-m-d')." 23:59:59'");
}
if (!$canViewOthers) {
$q->andWhere('ll.created_by = :userId')
->setParameter('userId', $this->userHelper->getUser()->getId());
}
$results = $q->executeQuery()->fetchAllAssociative();
if (in_array(0, $segments)) {
$qAll = $this->em->getConnection()->createQueryBuilder();
$qAll->select('COUNT(t.date_added) AS leads, 0 as id, "All Contacts" as name, "" as alias')
->from(MAUTIC_TABLE_PREFIX.'leads', 't');
if (!$canViewOthers) {
$qAll->andWhere('ll.created_by = :userId')
->setParameter('userId', $this->userHelper->getUser()->getId());
}
if (!empty($dateFrom)) {
$qAll->andWhere("t.date_added >= '".$dateFrom->format('Y-m-d')."'");
}
if (!empty($dateTo)) {
$qAll->andWhere("t.date_added <= '".$dateTo->format('Y-m-d')." 23:59:59'");
}
$resultsAll = $qAll->executeQuery()->fetchAllAssociative();
$results = array_merge($results, $resultsAll);
}
return $results;
}
/**
* @param bool $canViewOthers
*/
public function getLifeCycleSegmentChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat, $filter, $canViewOthers, $listName): array
{
$chart = new PieChart();
$query = new ChartQuery($this->em->getConnection(), $dateFrom, $dateTo);
if (!$canViewOthers) {
$filter['owner_id'] = $this->userHelper->getUser()->getId();
}
if (isset($filter['flag'])) {
unset($filter['flag']);
}
$allLists = $query->getCountQuery('leads', 'id', 'date_added', null);
$lists = $query->count('leads', 'id', 'date_added', $filter, null);
$all = $query->fetchCount($allLists);
$identified = $lists;
$chart->setDataset($listName, $identified);
if (isset($filter['leadlist_id']['value'])) {
$chart->setDataset(
$this->translator->trans('mautic.lead.lifecycle.graph.pie.all.lists'),
$all
);
}
return $chart->render(false);
}
/**
* @param array $filter
* @param bool $canViewOthers
*/
public function getStagesBarChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = [], $canViewOthers = true): array
{
$data['values'] = [];
$data['labels'] = [];
$q = $this->em->getConnection()->createQueryBuilder();
$q->select('count(l.id) as leads, s.name as stage')
->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 't')
->join('t', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = t.lead_id')
->join('t', MAUTIC_TABLE_PREFIX.'stages', 's', 's.id=l.stage_id')
->orderBy('leads', 'DESC')
->where($q->expr()->eq('s.is_published', ':published'))
->andWhere($q->expr()->gte('t.date_added', ':date_from'))
->setParameter('date_from', $dateFrom->format('Y-m-d'))
->andWhere($q->expr()->lte('t.date_added', ':date_to'))
->setParameter('date_to', $dateTo->format('Y-m-d 23:59:59'))
->setParameter('published', true);
if (isset($filter['leadlist_id']['value'])) {
$q->andWhere($q->expr()->eq('t.leadlist_id', ':leadlistid'))->setParameter('leadlistid', $filter['leadlist_id']['value']);
}
$q->groupBy('s.name');
if (!$canViewOthers) {
$q->andWhere('s.created_by = :userId')
->setParameter('userId', $this->userHelper->getUser()->getId());
}
$results = $q->executeQuery()->fetchAllAssociative();
foreach ($results as $result) {
$data['labels'][] = substr($result['stage'], 0, 12);
$data['values'][] = $result['leads'];
}
$data['xAxes'][] = ['display' => true];
$data['yAxes'][] = ['display' => true];
$baseData = [
'label' => $this->translator->trans('mautic.lead.leads'),
'data' => $data['values'],
];
$chart = new BarChart($data['labels']);
$datasets[] = array_merge($baseData, $chart->generateColors(3));
return [
'labels' => $data['labels'],
'datasets' => $datasets,
'options' => [
'xAxes' => $data['xAxes'],
'yAxes' => $data['yAxes'],
], ];
}
/**
* @param array $filter
* @param bool $canViewOthers
*/
public function getDeviceGranularityData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = [], $canViewOthers = true): array
{
$data['values'] = [];
$data['labels'] = [];
$q = $this->em->getConnection()->createQueryBuilder();
$q->select('count(l.id) as leads, ds.device')
->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 't')
->join('t', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.id = t.lead_id')
->join('t', MAUTIC_TABLE_PREFIX.'page_hits', 'h', 'h.lead_id=l.id')
->join('h', MAUTIC_TABLE_PREFIX.'lead_devices', 'ds', 'ds.id = h.device_id')
->orderBy('ds.device', 'DESC')
->andWhere($q->expr()->gte('t.date_added', ':date_from'))
->setParameter('date_from', $dateFrom->format('Y-m-d'))
->andWhere($q->expr()->lte('t.date_added', ':date_to'))
->setParameter('date_to', $dateTo->format('Y-m-d 23:59:59'));
if (isset($filter['leadlist_id']['value'])) {
$q->andWhere($q->expr()->eq('t.leadlist_id', ':leadlistid'))->setParameter(
'leadlistid',
$filter['leadlist_id']['value']
);
}
$q->groupBy('ds.device');
if (!$canViewOthers) {
$q->andWhere('l.created_by = :userId')
->setParameter('userId', $this->userHelper->getUser()->getId());
}
$results = $q->executeQuery()->fetchAllAssociative();
foreach ($results as $result) {
$data['labels'][] = substr(empty($result['device']) ? $this->translator->trans('mautic.core.no.info') : $result['device'], 0, 12);
$data['values'][] = $result['leads'];
}
$data['xAxes'][] = ['display' => true];
$data['yAxes'][] = ['display' => true];
$baseData = [
'label' => $this->translator->trans('mautic.core.device'),
'data' => $data['values'],
];
$chart = new BarChart($data['labels']);
$datasets[] = array_merge($baseData, $chart->generateColors(2));
return [
'labels' => $data['labels'],
'datasets' => $datasets,
'options' => [
'xAxes' => $data['xAxes'],
'yAxes' => $data['yAxes'],
],
];
}
/**
* Get line chart data of hits.
*
* @param string $unit {@link php.net/manual/en/function.date.php#refsect1-function.date-parameters}
* @param string $dateFormat
* @param array $filter
*/
public function getSegmentContactsLineChartData($unit, \DateTime $dateFrom, \DateTime $dateTo, $dateFormat = null, $filter = []): array
{
$chart = new LineChart($unit, $dateFrom, $dateTo, $dateFormat);
$query = new SegmentContactsLineChartQuery($this->em->getConnection(), $dateFrom, $dateTo, $filter);
// added line everytime
$chart->setDataset($this->translator->trans('mautic.lead.segments.contacts.added'), $this->segmentChartQueryFactory->getContactsAdded($query));
$chart->setDataset($this->translator->trans('mautic.lead.segments.contacts.removed'), $this->segmentChartQueryFactory->getContactsRemoved($query));
$chart->setDataset($this->translator->trans('mautic.lead.segments.contacts.total'), $this->segmentChartQueryFactory->getContactsTotal($query, $this));
return $chart->render();
}
/**
* Is custom field used in at least one defined segment?
*/
public function isFieldUsed(LeadField $field): bool
{
return 0 < $this->getFieldSegments($field)->count();
}
public function getFieldSegments(LeadField $field)
{
$alias = $field->getAlias();
$aliasLength = mb_strlen($alias);
$likeContent = "%;s:5:\"field\";s:{$aliasLength}:\"{$alias}\";%";
$filter = [
'force' => [
['column' => 'l.filters', 'expr' => 'LIKE', 'value'=> $likeContent],
],
];
return $this->getEntities(['filter' => $filter]);
}
/**
* @param $segmentId *
*
* @return array
*/
public function getSegmentsWithDependenciesOnSegment($segmentId, $returnProperty = 'name')
{
$filter = [
'force' => [
['column' => 'l.filters', 'expr' => 'LIKE', 'value'=>'%s:8:"leadlist"%'],
['column' => 'l.id', 'expr' => 'neq', 'value'=>$segmentId],
],
];
$entities = $this->getEntities(
[
'filter' => $filter,
]
);
$dependents = [];
$accessor = PropertyAccess::createPropertyAccessor();
foreach ($entities as $entity) {
$retrFilters = $entity->getFilters();
foreach ($retrFilters as $eachFilter) {
// BC support for old filters where the field existed outside of properties.
$filter = $eachFilter['properties']['filter'] ?? $eachFilter['filter'];
if ($filter && 'leadlist' === $eachFilter['type'] && in_array($segmentId, $filter)) {
if ($returnProperty && $value = $accessor->getValue($entity, $returnProperty)) {
$dependents[] = $value;
} else {
$dependents[] = $entity;
}
break;
}
}
}
return $dependents;
}
/**
* @return array<int, int>
*/
public function getSegmentIdsWithDependenciesOnEmail(int $emailId): array
{
$entities = $this->getEntities(
[
'filter' => [
'force' => [
[
'column' => 'l.filters',
'expr' => 'LIKE',
'value' => '%"lead_email_%',
],
],
],
]
);
$emailFilterTypes = ['lead_email_received', 'lead_email_sent'];
$dependents = [];
foreach ($entities as $entity) {
foreach ($entity->getFilters() as $entityFilter) {
// BC support for old filters where the field existed outside of properties.
$filter = $entityFilter['properties']['filter'] ?? $entityFilter['filter'];
if ($filter && in_array($entityFilter['type'], $emailFilterTypes) && in_array($emailId, $filter)) {
$dependents[] = $entity->getId();
break;
}
}
}
return array_unique($dependents);
}
/**
* Get segments which are used as a dependent by other segments to prevent batch deletion of them.
*
* @param array $segmentIds
*/
public function canNotBeDeleted($segmentIds): array
{
$entities = $this->getEntities(
[
'filter' => [
'force' => [
['column' => 'l.filters', 'expr' => 'LIKE', 'value'=>'%s:8:"leadlist"%'],
],
],
]
);
$idsNotToBeDeleted = [];
$namesNotToBeDeleted = [];
$dependency = [];
foreach ($entities as $entity) {
$retrFilters = $entity->getFilters();
foreach ($retrFilters as $eachFilter) {
if ('leadlist' !== $eachFilter['type']) {
continue;
}
$idsNotToBeDeleted = array_unique(array_merge($idsNotToBeDeleted, $eachFilter['filter']));
$bcFilterValue = $eachFilter['filter'] ?? [];
$filterValue = $eachFilter['properties']['filter'] ?? $bcFilterValue;
foreach ($filterValue as $val) {
if (!empty($dependency[$val])) {
$dependency[$val] = array_merge($dependency[$val], [$entity->getId()]);
$dependency[$val] = array_unique($dependency[$val]);
} else {
$dependency[$val] = [$entity->getId()];
}
}
}
}
foreach ($dependency as $key => $value) {
if (array_intersect($value, $segmentIds) === $value) {
$idsNotToBeDeleted = array_unique(array_diff($idsNotToBeDeleted, [$key]));
}
}
$idsNotToBeDeleted = array_intersect($segmentIds, $idsNotToBeDeleted);
foreach ($idsNotToBeDeleted as $val) {
$namesNotToBeDeleted[$val] = $this->getEntity($val)->getName();
}
return $namesNotToBeDeleted;
}
/**
* Get a list of source choices.
*/
public function getSourceLists(string $sourceType = null): array
{
$choices = [];
switch ($sourceType) {
case 'categories':
case null:
$choices['categories'] = [];
$categories = $this->categoryModel->getLookupResults('segment');
foreach ($categories as $category) {
$choices['categories'][$category['id']] = $category['title'];
}
}
foreach ($choices as &$typeChoices) {
asort($typeChoices);
}
return (null == $sourceType) ? $choices : $choices[$sourceType];
}
/**
* @param array<int> $listIds
*
* @return array<int>
*
* @throws \Exception
*/
public function getSegmentContactCountFromCache(array $listIds): array
{
$leadCounts = [];
foreach ($listIds as $listId) {
$leadCounts[$listId] = $this->segmentCountCacheHelper->getSegmentContactCount($listId);
}
return $leadCounts;
}
public function leadListExists(int $id): bool
{
return $this->getRepository()->leadListExists($id);
}
/**
* @param array<int> $listIds
*
* @return array<int>
*
* @throws \Exception
*/
public function getSegmentContactCount(array $listIds): array
{
$leadCounts = [];
foreach ($listIds as $listId) {
if ($this->segmentCountCacheHelper->hasSegmentContactCount($listId)) {
$leadCounts[$listId] = $this->segmentCountCacheHelper->getSegmentContactCount($listId);
} else {
$count = $this->getRepository()->getLeadCount($listId);
$leadCounts[$listId] = $count;
$this->segmentCountCacheHelper->setSegmentContactCount($listId, $count);
}
}
return $leadCounts;
}
/**
* @param array<int,int> $segmentsFilter
*
* @return array<int,LeadList>
*/
public function getSegmentsBuildTime(int $limit = 10, string $order = 'DESC', array $segmentsFilter = [], bool $canViewOthers = true): array
{
$criteria = ['isPublished' => true];
if (!$canViewOthers) {
$criteria['createdBy'] = $this->userHelper->getUser()->getId();
}
if (!empty($segmentsFilter)) {
$criteria['id'] = $segmentsFilter;
}
return $this->getRepository()->findBy($criteria, ['lastBuiltTime' => $order], $limit);
}
}