Skip to content

Commit

Permalink
Merge pull request #6531 from mautic/release-2.14.1
Browse files Browse the repository at this point in the history
Release 2.14.1-beta
  • Loading branch information
heathdutton committed Sep 6, 2018
2 parents 25dcf05 + 215762b commit 9e651c1
Show file tree
Hide file tree
Showing 176 changed files with 2,624 additions and 540 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -60,6 +60,7 @@ Installing from source is only recommended if you are comfortable using the comm
- recommended: `openssl`, `opcache` / `apcu` / `memcached`
- recommended for development: `xdebug`
3. Recommended memory limit: minimally 256 MB for testing, 512 MB and more for production.
4. Disabling `ONLY_FULL_GROUP_BY` on the mySQL server.

## Installation

Expand Down
4 changes: 2 additions & 2 deletions app/AppKernel.php
Expand Up @@ -41,7 +41,7 @@ class AppKernel extends Kernel
*
* @const integer
*/
const PATCH_VERSION = 0;
const PATCH_VERSION = 1;

/**
* Extra version identifier.
Expand All @@ -51,7 +51,7 @@ class AppKernel extends Kernel
*
* @const string
*/
const EXTRA_VERSION = '';
const EXTRA_VERSION = '-beta';

/**
* @var array
Expand Down
6 changes: 4 additions & 2 deletions app/bundles/CampaignBundle/Command/TriggerCampaignCommand.php
Expand Up @@ -24,6 +24,7 @@
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
Expand Down Expand Up @@ -240,7 +241,8 @@ protected function configure()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
$quiet = $input->getOption('quiet');
$this->output = $quiet ? new NullOutput() : $output;
$this->kickoffOnly = $input->getOption('kickoff-only');
$this->scheduleOnly = $input->getOption('scheduled-only');
$this->inactiveOnly = $input->getOption('inactive-only') || $input->getOption('negative-only');
Expand All @@ -264,7 +266,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
defined('MAUTIC_CAMPAIGN_SYSTEM_TRIGGERED') or define('MAUTIC_CAMPAIGN_SYSTEM_TRIGGERED', 1);

$id = $input->getOption('campaign-id');
if (!$this->checkRunStatus($input, $output, $id)) {
if (!$this->checkRunStatus($input, $this->output, $id)) {
return 0;
}

Expand Down
22 changes: 13 additions & 9 deletions app/bundles/CampaignBundle/Entity/ContactLimiterTrait.php
Expand Up @@ -56,11 +56,13 @@ private function updateQueryFromContactLimiter($alias, DbalQueryBuilder $qb, Con
->setParameter('maxContactId', $maxContactId);
}

if ($threadId = $contactLimiter->getThreadId() && $maxThreads = $contactLimiter->getMaxThreads()) {
if ($threadId <= $maxThreads) {
$qb->andWhere("MOD(($alias.lead_id + :threadShift), :maxThreads) = 0")
->setParameter('threadShift', $threadId - 1)
->setParameter('maxThreads', $maxThreads);
if ($threadId = $contactLimiter->getThreadId()) {
if ($maxThreads = $contactLimiter->getMaxThreads()) {
if ($threadId <= $maxThreads) {
$qb->andWhere("MOD(($alias.lead_id + :threadShift), :maxThreads) = 0")
->setParameter('threadShift', $threadId - 1)
->setParameter('maxThreads', $maxThreads);
}
}
}

Expand Down Expand Up @@ -107,10 +109,12 @@ private function updateOrmQueryFromContactLimiter($alias, OrmQueryBuilder $qb, C
->setParameter('maxContactId', $maxContactId);
}

if ($threadId = $contactLimiter->getThreadId() && $maxThreads = $contactLimiter->getMaxThreads()) {
$qb->andWhere("MOD((IDENTITY($alias.lead) + :threadShift), :maxThreads) = 0")
->setParameter('threadShift', $threadId - 1)
->setParameter('maxThreads', $maxThreads);
if ($threadId = $contactLimiter->getThreadId()) {
if ($maxThreads = $contactLimiter->getMaxThreads()) {
$qb->andWhere("MOD((IDENTITY($alias.lead) + :threadShift), :maxThreads) = 0")
->setParameter('threadShift', $threadId - 1)
->setParameter('maxThreads', $maxThreads);
}
}

if (!$isCount && $limit = $contactLimiter->getBatchLimit()) {
Expand Down
3 changes: 3 additions & 0 deletions app/bundles/CampaignBundle/Entity/LeadEventLog.php
Expand Up @@ -112,9 +112,12 @@ public static function loadMetadata(ORM\ClassMetadata $metadata)
$builder->setTable('campaign_lead_event_log')
->setCustomRepositoryClass('Mautic\CampaignBundle\Entity\LeadEventLogRepository')
->addIndex(['is_scheduled', 'lead_id'], 'campaign_event_upcoming_search')
->addIndex(['campaign_id', 'is_scheduled', 'trigger_date'], 'campaign_event_schedule_counts')
->addIndex(['date_triggered'], 'campaign_date_triggered')
->addIndex(['lead_id', 'campaign_id', 'rotation'], 'campaign_leads')
->addIndex(['channel', 'channel_id', 'lead_id'], 'campaign_log_channel')
->addIndex(['campaign_id', 'event_id', 'date_triggered'], 'campaign_actions')
->addIndex(['campaign_id', 'date_triggered', 'event_id', 'non_action_path_taken'], 'campaign_stats')
->addUniqueConstraint(['event_id', 'lead_id', 'rotation'], 'campaign_rotation');

$builder->addId();
Expand Down
43 changes: 38 additions & 5 deletions app/bundles/CampaignBundle/Entity/LeadRepository.php
Expand Up @@ -373,12 +373,13 @@ public function getContactRotations(array $contactIds, $campaignId)
}

/**
* @param int $campaignId
* @param $campaignId
* @param ContactLimiter $limiter
* @param bool $campaignCanBeRestarted
*
* @return CountResult
*/
public function getCountsForCampaignContactsBySegment($campaignId, ContactLimiter $limiter)
public function getCountsForCampaignContactsBySegment($campaignId, ContactLimiter $limiter, $campaignCanBeRestarted = false)
{
if (!$segments = $this->getCampaignSegments($campaignId)) {
return new CountResult(0, 0, 0);
Expand All @@ -397,18 +398,23 @@ public function getCountsForCampaignContactsBySegment($campaignId, ContactLimite
$this->updateQueryFromContactLimiter('ll', $qb, $limiter, true);
$this->updateQueryWithExistingMembershipExclusion($campaignId, $qb);

if (!$campaignCanBeRestarted) {
$this->updateQueryWithHistoryExclusion($campaignId, $qb);
}

$result = $qb->execute()->fetch();

return new CountResult($result['the_count'], $result['min_id'], $result['max_id']);
}

/**
* @param int $campaignId
* @param $campaignId
* @param ContactLimiter $limiter
* @param bool $campaignCanBeRestarted
*
* @return array
*/
public function getCampaignContactsBySegments($campaignId, ContactLimiter $limiter)
public function getCampaignContactsBySegments($campaignId, ContactLimiter $limiter, $campaignCanBeRestarted = false)
{
if (!$segments = $this->getCampaignSegments($campaignId)) {
return [];
Expand All @@ -427,6 +433,10 @@ public function getCampaignContactsBySegments($campaignId, ContactLimiter $limit
$this->updateQueryFromContactLimiter('ll', $qb, $limiter, true);
$this->updateQueryWithExistingMembershipExclusion($campaignId, $qb);

if (!$campaignCanBeRestarted) {
$this->updateQueryWithHistoryExclusion($campaignId, $qb);
}

$results = $qb->execute()->fetchAll();

$contacts = [];
Expand Down Expand Up @@ -577,7 +587,7 @@ private function updateQueryWithExistingMembershipExclusion($campaignId, QueryBu
}

/**
* @param int $campaignId
* @param array $segments
* @param QueryBuilder $qb
*/
private function updateQueryWithSegmentMembershipExclusion(array $segments, QueryBuilder $qb)
Expand All @@ -602,4 +612,27 @@ private function updateQueryWithSegmentMembershipExclusion(array $segments, Quer
sprintf('NOT EXISTS (%s)', $subq->getSQL())
);
}

/**
* Exclude contacts with any previous campaign history; this is mainly BC for pre 2.14.0 where the membership entry was deleted.
*
* @param $campaignId
* @param QueryBuilder $qb
*/
private function updateQueryWithHistoryExclusion($campaignId, QueryBuilder $qb)
{
$subq = $this->getEntityManager()->getConnection()->createQueryBuilder()
->select('null')
->from(MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'el')
->where(
$qb->expr()->andX(
$qb->expr()->eq('el.lead_id', 'll.lead_id'),
$qb->expr()->eq('el.campaign_id', (int) $campaignId)
)
);

$qb->andWhere(
sprintf('NOT EXISTS (%s)', $subq->getSQL())
);
}
}
Expand Up @@ -98,18 +98,15 @@ public function onCampaignBuild(CampaignBuilderEvent $event)
*/
public function onJumpToEvent(PendingEvent $campaignEvent)
{
foreach ($campaignEvent->getPending() as $log) {
$event = $log->getEvent();
$jumpTarget = $this->getJumpTargetForEvent($event, 'e.id');
$event = $campaignEvent->getEvent();
$jumpTarget = $this->getJumpTargetForEvent($event, 'e.id');

if ($jumpTarget === null) {
$campaignEvent->passWithError($jumpTarget, $this->translator->trans('mautic.campaign.campaign.jump_to_event.target_not_exist'));
continue;
}

$this->eventExecutioner->executeForContacts($jumpTarget, $campaignEvent->getContacts());
if ($jumpTarget === null) {
$campaignEvent->passWithError($jumpTarget, $this->translator->trans('mautic.campaign.campaign.jump_to_event.target_not_exist'));
}

$this->eventExecutioner->executeForContacts($jumpTarget, $campaignEvent->getContacts());

$campaignEvent->passRemaining();
}

Expand Down
2 changes: 2 additions & 0 deletions app/bundles/CampaignBundle/EventListener/ReportSubscriber.php
Expand Up @@ -205,6 +205,8 @@ public function onReportGenerate(ReportGeneratorEvent $event)
$event->addCompanyLeftJoin($qb);
}

$event->applyDateFilters($qb, 'date_triggered', 'log');

$event->setQueryBuilder($qb);
}

Expand Down
Expand Up @@ -284,7 +284,6 @@ public function executeEventsForContacts(ArrayCollection $events, ArrayCollectio
foreach ($jumpEvents as $key => $event) {
$config = $this->collector->getEventConfig($event);
$jumpLogs[$key] = $this->eventLogger->fetchRotationAndGenerateLogsFromContacts($event, $config, $contacts, $isInactive);
$this->eventLogger->persistCollection($jumpLogs[$key]);
}

// Increment the campaign rotation for the given contacts and current campaign
Expand Down
37 changes: 19 additions & 18 deletions app/bundles/CampaignBundle/Executioner/InactiveExecutioner.php
Expand Up @@ -221,22 +221,24 @@ private function prepareForExecution()
if (!$totalDecisions) {
throw new NoEventsFoundException();
}
$totalContacts = 0;
if (!($this->output instanceof NullOutput)) {
$totalContacts = $this->inactiveContactFinder->getContactCount($this->campaign->getId(), $this->decisions->getKeys(), $this->limiter);

$this->output->writeln(
$this->translator->trans(
'mautic.campaign.trigger.decision_count_analyzed',
[
'%decisions%' => $totalDecisions,
'%leads%' => $totalContacts,
'%batch%' => $this->limiter->getBatchLimit(),
]
)
);

$totalContacts = $this->inactiveContactFinder->getContactCount($this->campaign->getId(), $this->decisions->getKeys(), $this->limiter);

$this->output->writeln(
$this->translator->trans(
'mautic.campaign.trigger.decision_count_analyzed',
[
'%decisions%' => $totalDecisions,
'%leads%' => $totalContacts,
'%batch%' => $this->limiter->getBatchLimit(),
]
)
);

if (!$totalContacts) {
throw new NoContactsFoundException();
if (!$totalContacts) {
throw new NoContactsFoundException();
}
}

// Approximate total count because the query to fetch contacts will filter out those that have not arrived to this point in the campaign yet
Expand All @@ -248,7 +250,6 @@ private function prepareForExecution()
* @throws Dispatcher\Exception\LogNotProcessedException
* @throws Dispatcher\Exception\LogPassedAndFailedException
* @throws Exception\CannotProcessEventException
* @throws NoContactsFoundException
* @throws Scheduler\Exception\NotSchedulableException
*/
private function executeEvents()
Expand All @@ -267,7 +268,7 @@ private function executeEvents()
$contacts = $this->inactiveContactFinder->getContacts($this->campaign->getId(), $decisionEvent, $this->limiter);

// Loop over all contacts till we've processed all those applicable for this decision
while ($contacts->count()) {
while ($contacts && $contacts->count()) {
// Get the max contact ID before any are removed
$batchMinContactId = max($contacts->getKeys()) + 1;

Expand Down Expand Up @@ -316,7 +317,7 @@ private function executeEvents()
}

/**
* @param ArrayCollection $children
* @param ArrayCollection $events
* @param ArrayCollection $contacts
* @param Counter $childrenCounter
* @param \DateTime $earliestLastActiveDateTime
Expand Down
39 changes: 22 additions & 17 deletions app/bundles/CampaignBundle/Executioner/KickoffExecutioner.php
Expand Up @@ -161,24 +161,29 @@ private function prepareForExecution()

$this->rootEvents = $this->campaign->getRootEvents();
$totalRootEvents = $this->rootEvents->count();
$this->logger->debug('CAMPAIGN: Processing the following events: '.implode(', ', $this->rootEvents->getKeys()));

$totalContacts = $this->kickoffContactFinder->getContactCount($this->campaign->getId(), $this->rootEvents->getKeys(), $this->limiter);
$totalKickoffEvents = $totalRootEvents * $totalContacts;

$this->output->writeln(
$this->translator->trans(
'mautic.campaign.trigger.event_count',
[
'%events%' => $totalKickoffEvents,
'%batch%' => $this->limiter->getBatchLimit(),
]
)
);

if (!$totalKickoffEvents) {
if (!$totalRootEvents) {
throw new NoEventsFoundException();
}
$this->logger->debug('CAMPAIGN: Processing the following events: '.implode(', ', $this->rootEvents->getKeys()));
$totalKickoffEvents = 0;
if (!($this->output instanceof NullOutput)) {
$totalContacts = $this->kickoffContactFinder->getContactCount($this->campaign->getId(), $this->rootEvents->getKeys(), $this->limiter);
$totalKickoffEvents = $totalRootEvents * $totalContacts;

$this->output->writeln(
$this->translator->trans(
'mautic.campaign.trigger.event_count',
[
'%events%' => $totalKickoffEvents,
'%batch%' => $this->limiter->getBatchLimit(),
]
)
);

if (!$totalKickoffEvents) {
throw new NoEventsFoundException();
}
}

$this->progressBar = ProgressBarHelper::init($this->output, $totalKickoffEvents);
$this->progressBar->start();
Expand All @@ -199,7 +204,7 @@ private function executeOrScheduleEvent()

// Loop over contacts until the entire campaign is executed
$contacts = $this->kickoffContactFinder->getContacts($this->campaign->getId(), $this->limiter);
while ($contacts->count()) {
while ($contacts && $contacts->count()) {
$batchMinContactId = max($contacts->getKeys()) + 1;
$rootEvents = clone $this->rootEvents;

Expand Down

0 comments on commit 9e651c1

Please sign in to comment.