/** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $options = $input->getOptions(); $force = $options['force']; $package = $options['update-package']; $appRoot = dirname($this->getContainer()->getParameter('kernel.root_dir')); /** @var \Symfony\Bundle\FrameworkBundle\Translation\Translator $translator */ $translator = $this->getContainer()->get('translator'); $translator->setLocale($this->getContainer()->get('mautic.factory')->getParameter('locale')); if ($package) { if (!file_exists($package)) { $output->writeln('<error>' . $translator->trans('mautic.core.update.archive_no_such_file') . '</error>'); return 1; } } if (!$force) { /** @var \Symfony\Component\Console\Helper\SymfonyQuestionHelper $helper */ $helper = $this->getHelperSet()->get('question'); $question = new ConfirmationQuestion($translator->trans('mautic.core.update.confirm_application_update') . " ", false); if (!$helper->ask($input, $output, $question)) { $output->writeln($translator->trans('mautic.core.update.aborted')); return 0; } } // Start a progress bar, don't give a max number of steps because it is conditional $progressBar = ProgressBarHelper::init($output); $progressBar->setFormat('Step %current% [%bar%] <info>%message%</info>'); if ($package) { $progressBar->setMessage($translator->trans('mautic.core.command.update.step.loading_package') . " "); $progressBar->advance(); $zipFile = $package; $version = basename($package); } else { $progressBar->setMessage($translator->trans('mautic.core.command.update.step.loading_update_information') . " "); $progressBar->advance(); $updateHelper = $this->getContainer()->get('mautic.helper.update'); $update = $updateHelper->fetchData(); if (!isset($update['package'])) { $output->writeln("\n\n<error>" . $translator->trans('mautic.core.update.no_cache_data') . '</error>'); return 1; } $progressBar->setMessage($translator->trans('mautic.core.command.update.step.download_update_package') . " "); $progressBar->advance(); // Fetch the update package $package = $updateHelper->fetchPackage($update['package']); if ($package['error']) { $output->writeln("\n\n<error>" . $translator->trans($package['message']) . '</error>'); return 1; } $zipFile = $this->getContainer()->getParameter('kernel.cache_dir') . '/' . basename($update['package']); $version = $update['version']; } $progressBar->setMessage($translator->trans('mautic.core.command.update.step.validate_update_package') . " "); $progressBar->advance(); $zipper = new \ZipArchive(); $archive = $zipper->open($zipFile); if ($archive !== true) { // Get the exact error switch ($archive) { case \ZipArchive::ER_EXISTS: $error = 'mautic.core.update.archive_file_exists'; break; case \ZipArchive::ER_INCONS: case \ZipArchive::ER_INVAL: case \ZipArchive::ER_MEMORY: $error = 'mautic.core.update.archive_zip_corrupt'; break; case \ZipArchive::ER_NOENT: $error = 'mautic.core.update.archive_no_such_file'; break; case \ZipArchive::ER_NOZIP: $error = 'mautic.core.update.archive_not_valid_zip'; break; case \ZipArchive::ER_READ: case \ZipArchive::ER_SEEK: case \ZipArchive::ER_OPEN: default: $error = 'mautic.core.update.archive_could_not_open'; break; } $output->writeln("\n\n<error>" . $translator->trans('mautic.core.update.error', ['%error%' => $translator->trans($error)]) . '</error>'); return 1; } // Extract the archive file now in place $progressBar->setMessage($translator->trans('mautic.core.update.step.extracting.package') . " "); $progressBar->advance(); if (!$zipper->extractTo($appRoot)) { $output->writeln("\n\n<error>" . $translator->trans('mautic.core.update.error', ['%error%' => $translator->trans('mautic.core.update.error_extracting_package')]) . '</error>'); return 1; } $zipper->close(); // Define this just in case defined('MAUTIC_ENV') or define('MAUTIC_ENV', isset($options['env']) ? $options['env'] : 'prod'); // Make sure we have a deleted_files list otherwise we can't process this step if (file_exists($appRoot . '/deleted_files.txt')) { $progressBar->setMessage($translator->trans('mautic.core.update.remove.deleted.files') . " "); $progressBar->advance(); $deletedFiles = json_decode(file_get_contents($appRoot . '/deleted_files.txt'), true); $errorLog = []; // Before looping over the deleted files, add in our upgrade specific files $deletedFiles += ['deleted_files.txt', 'upgrade.php']; foreach ($deletedFiles as $file) { $path = $appRoot . '/' . $file; if (file_exists($path)) { // Try setting the permissions to 777 just to make sure we can get rid of the file @chmod($path, 0777); if (!@unlink($path)) { // Failed to delete, reset the permissions to 644 for safety @chmod($path, 0644); $errorLog[] = $translator->trans('mautic.core.update.error.removing.file', ['%path%' => $file]); } } } // If there were any errors, add them to the error log if (count($errorLog)) { // Check if the error log exists first if (file_exists($appRoot . '/upgrade_errors.txt')) { $errors = file_get_contents($appRoot . '/upgrade_errors.txt'); } else { $errors = ''; } $errors .= implode(PHP_EOL, $errorLog); @file_put_contents($appRoot . '/upgrade_errors.txt', $errors); } } // Clear the dev and prod cache instances to reset the system $progressBar->setMessage($translator->trans('mautic.core.update.clear.cache') . " "); $progressBar->advance(); $cacheHelper = $this->getContainer()->get('mautic.helper.cache'); $cacheHelper->nukeCache(); // Update languages $supportedLanguages = $this->getContainer()->get('mautic.factory')->getParameter('supported_languages'); // If there is only one language, assume it is 'en_US' and skip this if (count($supportedLanguages) > 1) { $progressBar->setMessage($translator->trans('mautic.core.command.update.step.update_languages' . " ")); $progressBar->advance(); /** @var \Mautic\CoreBundle\Helper\LanguageHelper $languageHelper */ $languageHelper = $this->getContainer()->get('mautic.factory')->getHelper('language'); // First, update the cached language data $result = $languageHelper->fetchLanguages(true); // Only continue if not in error if (!isset($result['error'])) { foreach ($supportedLanguages as $locale => $name) { // We don't need to update en_US, that comes with the main package if ($locale == 'en_US') { continue; } // Update time $extractResult = $languageHelper->extractLanguagePackage($locale); if ($extractResult['error']) { $output->writeln("\n\n<error>" . $translator->trans('mautic.core.update.error_updating_language', ['%language%' => $name]) . '</error>'); } } } } // Migrate the database to the current version $progressBar->setMessage($translator->trans('mautic.core.update.migrating.database.schema' . " ")); $progressBar->advance(); $migrationApplication = new Application($this->getContainer()->get('kernel')); $migrationApplication->setAutoExit(false); $migrationCommandArgs = new ArgvInput(['console', 'doctrine:migrations:migrate', '--quiet', '--no-interaction']); $migrationCommandArgs->setInteractive(false); $migrateExitCode = $migrationApplication->run($migrationCommandArgs, new NullOutput()); unset($migrationApplication); $progressBar->setMessage($translator->trans('mautic.core.update.step.wrapping_up' . " ")); $progressBar->advance(); // Clear the cached update data and the download package now that we've updated if (empty($package)) { @unlink($zipFile); } else { @unlink($this->getContainer()->getParameter('kernel.cache_dir') . '/lastUpdateCheck.txt'); } @unlink($appRoot . '/deleted_files.txt'); @unlink($appRoot . '/upgrade.php'); // Update successful $progressBar->setMessage($translator->trans('mautic.core.update.update_successful', ['%version%' => $version]) . " "); $progressBar->finish(); // Check for a post install message if ($postMessage = $this->getContainer()->get('session')->get('post_upgrade_message', false)) { $postMessage = strip_tags($postMessage); $this->getContainer()->get('session')->remove('post_upgrade_message'); $output->writeln("\n\n<info>{$postMessage}</info>"); } // Output the error (if exists) from the migrate command after we've finished the progress bar if ($migrateExitCode !== 0) { $output->writeln("\n\n<error>" . $translator->trans('mautic.core.update.error_performing_migration') . '</error>'); } return 0; }
/** * Find and trigger the negative events, i.e. the events with a no decision path. * * @param Campaign $campaign * @param int $totalEventCount * @param int $limit * @param bool $max * @param OutputInterface $output * @param bool|false $returnCounts If true, returns array of counters * * @return int */ public function triggerNegativeEvents($campaign, &$totalEventCount = 0, $limit = 100, $max = false, OutputInterface $output = null, $returnCounts = false) { defined('MAUTIC_CAMPAIGN_SYSTEM_TRIGGERED') or define('MAUTIC_CAMPAIGN_SYSTEM_TRIGGERED', 1); $this->logger->debug('CAMPAIGN: Triggering negative events'); $campaignId = $campaign->getId(); $campaignName = $campaign->getName(); $repo = $this->getRepository(); $campaignRepo = $this->getCampaignRepository(); // Get events to avoid large number of joins $campaignEvents = $repo->getCampaignEvents($campaignId); // Get an array of events that are non-action based $nonActionEvents = []; $actionEvents = []; foreach ($campaignEvents as $id => $e) { if (!empty($e['decisionPath']) && !empty($e['parent_id']) && $campaignEvents[$e['parent_id']]['eventType'] != 'condition') { if ($e['decisionPath'] == 'no') { $nonActionEvents[$e['parent_id']][$id] = $e; } elseif ($e['decisionPath'] == 'yes') { $actionEvents[$e['parent_id']][] = $id; } } } $this->logger->debug('CAMPAIGN: Processing the children of the following events: ' . implode(', ', array_keys($nonActionEvents))); if (empty($nonActionEvents)) { // No non-action events associated with this campaign unset($campaignEvents); return 0; } // Get a count $leadCount = $campaignRepo->getCampaignLeadCount($campaignId); if ($output) { $output->writeln($this->translator->trans('mautic.campaign.trigger.lead_count_analyzed', ['%leads%' => $leadCount, '%batch%' => $limit])); } $start = $leadProcessedCount = $lastRoundPercentage = $executedEventCount = $evaluatedEventCount = $negativeExecutedCount = $negativeEvaluatedCount = 0; $nonActionEventCount = $leadCount * count($nonActionEvents); $eventSettings = $this->campaignModel->getEvents(); $maxCount = $max ? $max : $nonActionEventCount; // Try to save some memory gc_enable(); if ($leadCount) { if ($output) { $progress = ProgressBarHelper::init($output, $maxCount); $progress->start(); if ($max) { $progress->advance($totalEventCount); } } $sleepBatchCount = 0; $batchDebugCounter = 1; while ($start <= $leadCount) { $this->logger->debug('CAMPAIGN: Batch #' . $batchDebugCounter); // Get batched campaign ids $campaignLeads = $campaignRepo->getCampaignLeads($campaignId, $start, $limit, ['cl.lead_id, cl.date_added']); $campaignLeadIds = []; $campaignLeadDates = []; foreach ($campaignLeads as $r) { $campaignLeadIds[] = $r['lead_id']; $campaignLeadDates[$r['lead_id']] = $r['date_added']; } unset($campaignLeads); $this->logger->debug('CAMPAIGN: Processing the following contacts: ' . implode(', ', $campaignLeadIds)); foreach ($nonActionEvents as $parentId => $events) { // Just a check to ensure this is an appropriate action if ($campaignEvents[$parentId]['eventType'] == 'action') { $this->logger->debug('CAMPAIGN: Parent event ID #' . $parentId . ' is an action.'); continue; } // Get only leads who have had the action prior to the decision executed $grandParentId = $campaignEvents[$parentId]['parent_id']; // Get the lead log for this batch of leads limiting to those that have already triggered // the decision's parent and haven't executed this level in the path yet if ($grandParentId) { $this->logger->debug('CAMPAIGN: Checking for contacts based on grand parent execution.'); $leadLog = $repo->getEventLog($campaignId, $campaignLeadIds, [$grandParentId], array_keys($events), true); $applicableLeads = array_keys($leadLog); } else { $this->logger->debug('CAMPAIGN: Checking for contacts based on exclusion due to being at root level'); // The event has no grandparent (likely because the decision is first in the campaign) so find leads that HAVE // already executed the events in the root level and exclude them $havingEvents = isset($actionEvents[$parentId]) ? array_merge($actionEvents[$parentId], array_keys($events)) : array_keys($events); $leadLog = $repo->getEventLog($campaignId, $campaignLeadIds, $havingEvents); $unapplicableLeads = array_keys($leadLog); // Only use leads that are not applicable $applicableLeads = array_diff($campaignLeadIds, $unapplicableLeads); unset($unapplicableLeads); } if (empty($applicableLeads)) { $this->logger->debug('CAMPAIGN: No events are applicable'); continue; } $this->logger->debug('CAMPAIGN: These contacts have have not gone down the positive path: ' . implode(', ', $applicableLeads)); // Get the leads $leads = $this->leadModel->getEntities(['filter' => ['force' => [['column' => 'l.id', 'expr' => 'in', 'value' => $applicableLeads]]], 'orderBy' => 'l.id', 'orderByDir' => 'asc']); if (!count($leads)) { // Just a precaution in case non-existent leads are lingering in the campaign leads table $this->logger->debug('CAMPAIGN: No contact entities found.'); continue; } // Loop over the non-actions and determine if it has been processed for this lead $leadDebugCounter = 1; /** @var \Mautic\LeadBundle\Entity\Lead $lead */ foreach ($leads as $lead) { ++$negativeEvaluatedCount; // Set lead for listeners $this->leadModel->setSystemCurrentLead($lead); $this->logger->debug('CAMPAIGN: contact ID #' . $lead->getId() . '; #' . $leadDebugCounter . ' in batch #' . $batchDebugCounter); // Prevent path if lead has already gone down this path if (!isset($leadLog[$lead->getId()]) || !array_key_exists($parentId, $leadLog[$lead->getId()])) { // Get date to compare against $utcDateString = $grandParentId ? $leadLog[$lead->getId()][$grandParentId]['date_triggered'] : $campaignLeadDates[$lead->getId()]; // Convert to local DateTime $grandParentDate = (new DateTimeHelper($utcDateString))->getLocalDateTime(); // Non-decision has not taken place yet, so cycle over each associated action to see if timing is right $eventTiming = []; $executeAction = false; foreach ($events as $id => $e) { if ($sleepBatchCount == $limit) { // Keep CPU down $this->batchSleep(); $sleepBatchCount = 0; } else { ++$sleepBatchCount; } if (isset($leadLog[$lead->getId()]) && array_key_exists($id, $leadLog[$lead->getId()])) { $this->logger->debug('CAMPAIGN: Event (ID #' . $id . ') has already been executed'); unset($e); continue; } if (!isset($eventSettings[$e['eventType']][$e['type']])) { $this->logger->debug('CAMPAIGN: Event (ID #' . $id . ') no longer exists'); unset($e); continue; } // First get the timing for all the 'non-decision' actions $eventTiming[$id] = $this->checkEventTiming($e, $grandParentDate, true); if ($eventTiming[$id] === true) { // Includes events to be executed now then schedule the rest if applicable $executeAction = true; } unset($e); } if (!$executeAction) { $negativeEvaluatedCount += count($nonActionEvents); // Timing is not appropriate so move on to next lead unset($eventTiming); continue; } if ($max && $totalEventCount + count($nonActionEvents) >= $max) { // Hit the max or will hit the max while mid-process for the lead if ($output) { $progress->finish(); $output->writeln(''); } $counts = ['events' => $nonActionEventCount, 'evaluated' => $negativeEvaluatedCount, 'executed' => $negativeExecutedCount, 'totalEvaluated' => $evaluatedEventCount, 'totalExecuted' => $executedEventCount]; $this->logger->debug('CAMPAIGN: Counts - ' . var_export($counts, true)); return $returnCounts ? $counts : $executedEventCount; } $decisionLogged = false; // Execute or schedule events $this->logger->debug('CAMPAIGN: Processing the following events for contact ID# ' . $lead->getId() . ': ' . implode(', ', array_keys($eventTiming))); foreach ($eventTiming as $id => $eventTriggerDate) { // Set event $event = $events[$id]; $event['campaign'] = ['id' => $campaignId, 'name' => $campaignName]; // Set lead in case this is triggered by the system $this->leadModel->setSystemCurrentLead($lead); if ($this->executeEvent($event, $campaign, $lead, $eventSettings, false, null, $eventTriggerDate, false, $evaluatedEventCount, $executedEventCount, $totalEventCount)) { if (!$decisionLogged) { // Log the decision $log = $this->getLogEntity($parentId, $campaign, $lead, null, true); $log->setDateTriggered(new \DateTime()); $log->setNonActionPathTaken(true); $repo->saveEntity($log); $this->em->detach($log); unset($log); $decisionLogged = true; } ++$negativeExecutedCount; } unset($utcDateString, $grandParentDate); } } else { $this->logger->debug('CAMPAIGN: Decision has already been executed.'); } $currentCount = $max ? $totalEventCount : $negativeEvaluatedCount; if ($output && $currentCount < $maxCount) { $progress->setProgress($currentCount); } ++$leadDebugCounter; // Save RAM $this->em->detach($lead); unset($lead); } } // Next batch $start += $limit; // Save RAM $this->em->clear('Mautic\\LeadBundle\\Entity\\Lead'); $this->em->clear('Mautic\\UserBundle\\Entity\\User'); unset($leads, $campaignLeadIds, $leadLog); $currentCount = $max ? $totalEventCount : $negativeEvaluatedCount; if ($output && $currentCount < $maxCount) { $progress->setProgress($currentCount); } // Free some memory gc_collect_cycles(); ++$batchDebugCounter; } if ($output) { $progress->finish(); $output->writeln(''); } } $counts = ['events' => $nonActionEventCount, 'evaluated' => $negativeEvaluatedCount, 'executed' => $negativeExecutedCount, 'totalEvaluated' => $evaluatedEventCount, 'totalExecuted' => $executedEventCount]; $this->logger->debug('CAMPAIGN: Counts - ' . var_export($counts, true)); return $returnCounts ? $counts : $executedEventCount; }
/** * Rebuild lead lists * * @param LeadList $entity * @param int $limit * @param bool $maxLeads * @param OutputInterface $output * * @return int */ public function rebuildListLeads(LeadList $entity, $limit = 1000, $maxLeads = false, OutputInterface $output = null) { defined('MAUTIC_REBUILDING_LEAD_LISTS') or define('MAUTIC_REBUILDING_LEAD_LISTS', 1); $id = $entity->getId(); $list = array('id' => $id, 'filters' => $entity->getFilters()); $dtHelper = new DateTimeHelper(); $batchLimiters = array('dateTime' => $dtHelper->toUtcString()); $localDateTime = $dtHelper->getLocalDateTime(); // Get a count of leads to add $newLeadsCount = $this->getLeadsByList($list, true, array('countOnly' => true, 'newOnly' => true, 'batchLimiters' => $batchLimiters)); // Ensure the same list is used each batch $batchLimiters['maxId'] = (int) $newLeadsCount[$id]['maxId']; // Number of total leads to process $leadCount = (int) $newLeadsCount[$id]['count']; if ($output) { $output->writeln($this->translator->trans('mautic.lead.list.rebuild.to_be_added', array('%leads%' => $leadCount, '%batch%' => $limit))); } // Handle by batches $start = $lastRoundPercentage = $leadsProcessed = 0; // Try to save some memory gc_enable(); if ($leadCount) { $maxCount = $maxLeads ? $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(); $newLeadList = $this->getLeadsByList($list, true, array('newOnly' => true, 'limit' => $limit, 'batchLimiters' => $batchLimiters)); if (empty($newLeadList[$id])) { // Somehow ran out of leads so break out break; } foreach ($newLeadList[$id] as $l) { $this->addLead($l, $entity, false, true, -1, $localDateTime); unset($l); $leadsProcessed++; if ($output && $leadsProcessed < $maxCount) { $progress->setProgress($leadsProcessed); } if ($maxLeads && $leadsProcessed >= $maxLeads) { break; } } $start += $limit; // Dispatch batch event if ($this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_BATCH_CHANGE)) { $event = new ListChangeEvent($newLeadList[$id], $entity, true); $this->dispatcher->dispatch(LeadEvents::LEAD_LIST_BATCH_CHANGE, $event); unset($event); } 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(''); } } // Get a count of leads to be removed $removeLeadCount = $this->getLeadsByList($list, true, array('countOnly' => true, 'nonMembersOnly' => true, 'batchLimiters' => $batchLimiters)); // Restart batching $start = $lastRoundPercentage = 0; $leadCount = $removeLeadCount[$id]['count']; if ($output) { $output->writeln($this->translator->trans('mautic.lead.list.rebuild.to_be_removed', array('%leads%' => $leadCount, '%batch%' => $limit))); } if ($leadCount) { $maxCount = $maxLeads ? $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->getLeadsByList($list, true, array('limit' => $limit, 'nonMembersOnly' => true, 'batchLimiters' => $batchLimiters)); if (empty($removeLeadList[$id])) { // Somehow ran out of leads so break out break; } foreach ($removeLeadList[$id] as $l) { $this->removeLead($l, $entity, false, true, true); $leadsProcessed++; if ($output && $leadsProcessed < $maxCount) { $progress->setProgress($leadsProcessed); } if ($maxLeads && $leadsProcessed >= $maxLeads) { break; } } // Dispatch batch event if ($this->dispatcher->hasListeners(LeadEvents::LEAD_LIST_BATCH_CHANGE)) { $event = new ListChangeEvent($removeLeadList[$id], $entity, false); $this->dispatcher->dispatch(LeadEvents::LEAD_LIST_BATCH_CHANGE, $event); unset($event); } $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(''); } } return $leadsProcessed; }
/** * @param Campaign $campaign * @param int $limit * @param bool $maxLeads * @param OutputInterface $output * * @return int */ public function rebuildCampaignLeads(Campaign $campaign, $limit = 1000, $maxLeads = false, OutputInterface $output = null) { defined('MAUTIC_REBUILDING_CAMPAIGNS') or define('MAUTIC_REBUILDING_CAMPAIGNS', 1); $repo = $this->getRepository(); // Get a list of lead lists this campaign is associated with $lists = $repo->getCampaignListIds($campaign->getId()); $batchLimiters = array('dateTime' => (new DateTimeHelper())->toUtcString()); if (count($lists)) { // Get a count of new leads $newLeadsCount = $repo->getCampaignLeadsFromLists($campaign->getId(), $lists, array('countOnly' => true, 'batchLimiters' => $batchLimiters)); // Ensure the same list is used each batch $batchLimiters['maxId'] = (int) $newLeadsCount['maxId']; // Number of total leads to process $leadCount = (int) $newLeadsCount['count']; } else { // No lists to base campaign membership off of so ignore $leadCount = 0; } if ($output) { $output->writeln($this->translator->trans('mautic.campaign.rebuild.to_be_added', array('%leads%' => $leadCount, '%batch%' => $limit))); } // Handle by batches $start = $leadsProcessed = 0; // Try to save some memory gc_enable(); if ($leadCount) { $maxCount = $maxLeads ? $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(); // Get a count of new leads $newLeadList = $repo->getCampaignLeadsFromLists($campaign->getId(), $lists, array('limit' => $limit, 'batchLimiters' => $batchLimiters)); $start += $limit; foreach ($newLeadList as $l) { $this->addLeads($campaign, array($l), false, true, -1); $leadsProcessed++; if ($output && $leadsProcessed < $maxCount) { $progress->setProgress($leadsProcessed); } unset($l); if ($maxLeads && $leadsProcessed >= $maxLeads) { // done for this round, bye bye if ($output) { $progress->finish(); } return $leadsProcessed; } } unset($newLeadList); // Free some memory gc_collect_cycles(); } if ($output) { $progress->finish(); $output->writeln(''); } } // Get a count of leads to be removed $removeLeadCount = $repo->getCampaignOrphanLeads($campaign->getId(), $lists, array('countOnly' => true, 'batchLimiters' => $batchLimiters)); // Restart batching $start = $lastRoundPercentage = 0; $leadCount = $removeLeadCount['count']; $batchLimiters['maxId'] = $removeLeadCount['maxId']; if ($output) { $output->writeln($this->translator->trans('mautic.lead.list.rebuild.to_be_removed', array('%leads%' => $leadCount, '%batch%' => $limit))); } if ($leadCount) { $maxCount = $maxLeads ? $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 = $repo->getCampaignOrphanLeads($campaign->getId(), $lists, array('limit' => $limit, 'batchLimiters' => $batchLimiters)); foreach ($removeLeadList as $l) { $this->removeLeads($campaign, array($l), false, true, true); $leadsProcessed++; if ($output && $leadsProcessed < $maxCount) { $progress->setProgress($leadsProcessed); } if ($maxLeads && $leadsProcessed >= $maxLeads) { // done for this round, bye bye $progress->finish(); return $leadsProcessed; } } $start += $limit; unset($removeLeadList); // Free some memory gc_collect_cycles(); } if ($output) { $progress->finish(); $output->writeln(''); } } return $leadsProcessed; }