/** * @param Event|int $event * @param Campaign $campaign * @param \Mautic\LeadBundle\Entity\Lead|null $lead * @param \Mautic\CoreBundle\Entity\IpAddress|null $ipAddress * @param bool $systemTriggered * * @return LeadEventLog * * @throws \Doctrine\ORM\ORMException */ public function getLogEntity($event, $campaign, $lead = null, $ipAddress = null, $systemTriggered = false) { $log = new LeadEventLog(); if ($ipAddress == null) { // Lead triggered from system IP $ipAddress = $this->ipLookupHelper->getIpAddress(); } $log->setIpAddress($ipAddress); if (!$event instanceof Event) { $event = $this->em->getReference('MauticCampaignBundle:Event', $event); } $log->setEvent($event); if (!$campaign instanceof Campaign) { $campaign = $this->em->getReference('MauticCampaignBundle:Campaign', $campaign); } $log->setCampaign($campaign); if ($lead == null) { $lead = $this->leadModel->getCurrentLead(); } $log->setLead($lead); $log->setDateTriggered(new \DateTime()); $log->setSystemTriggered($systemTriggered); // Save some RAM for batch processing unset($event, $campaign, $lead); return $log; }
/** * Triggers a specific stage change (this function is not being used any more but leaving it in case we do want to move stages through different actions) * * @param $type * @param mixed $eventDetails passthrough from function triggering action to the callback function * @param mixed $typeId Something unique to the triggering event to prevent unnecessary duplicate calls * @param Lead $lead * * @return void */ public function triggerAction($type, $eventDetails = null, $typeId = null, Lead $lead = null) { //only trigger actions for anonymous users if (!$this->security->isAnonymous()) { return; } if ($typeId !== null && MAUTIC_ENV === 'prod') { $triggeredEvents = $this->session->get('mautic.triggered.stage.actions', array()); if (in_array($typeId, $triggeredEvents)) { return; } $triggeredEvents[] = $typeId; $this->session->set('mautic.triggered.stage.actions', $triggeredEvents); } //find all the actions for published stages /** @var \Mautic\StageBundle\Entity\StageRepository $repo */ $repo = $this->getRepository(); $availableStages = $repo->getPublishedByType($type); if (null === $lead) { $lead = $this->leadModel->getCurrentLead(); if (null === $lead || !$lead->getId()) { return; } } //get available actions $availableActions = $this->getStageActions(); //get a list of actions that has already been performed on this lead $completedActions = $repo->getCompletedLeadActions($type, $lead->getId()); $persist = array(); foreach ($availableStages as $action) { //if it's already been done, then skip it if (isset($completedActions[$action->getId()])) { continue; } //make sure the action still exists if (!isset($availableActions['actions'][$action->getName()])) { continue; } $parsed = explode('.', $action->getName()); $lead->stageChangeLogEntry($parsed[0], $action->getId() . ": " . $action->getName(), 'Moved by an action'); $lead->setStage($action); $log = new LeadStageLog(); $log->setStage($action); $log->setLead($lead); $log->setDateFired(new \DateTime()); $persist[] = $log; } if (!empty($persist)) { $this->leadModel->saveEntity($lead); $this->getRepository()->saveEntities($persist); // Detach logs to reserve memory $this->em->clear('Mautic\\StageBundle\\Entity\\LeadStageLog'); } }
/** * @param PageDisplayEvent $event */ public function onPageDisplay(PageDisplayEvent $event) { $page = $event->getPage(); $leadId = $this->security->isAnonymous() ? $this->leadModel->getCurrentLead()->getId() : null; $tokens = $this->generateTokensFromContent($event, $leadId, ['page', $page->getId()]); $content = $event->getContent(); if (!empty($tokens)) { $content = str_ireplace(array_keys($tokens), $tokens, $content); } $event->setContent($content); }
/** * Gets the campaigns a specific lead is part of * * @param Lead $lead * @param bool $forList * * @return mixed */ public function getLeadCampaigns(Lead $lead = null, $forList = false) { static $campaigns = array(); if ($lead === null) { $lead = $this->leadModel->getCurrentLead(); } if (!isset($campaigns[$lead->getId()])) { $repo = $this->getRepository(); $leadId = $lead->getId(); //get the campaigns the lead is currently part of $campaigns[$leadId] = $repo->getPublishedCampaigns(null, $lead->getId(), $forList); } return $campaigns[$lead->getId()]; }
/** * Triggers a specific event * * @param array $event * @param Lead $lead * @param bool $force * * @return bool Was event triggered */ public function triggerEvent($event, Lead $lead = null, $force = false) { //only trigger events for anonymous users if (!$force && !$this->security->isAnonymous()) { return false; } if ($lead == null) { $lead = $this->leadModel->getCurrentLead(); } if (!$force) { //get a list of events that has already been performed on this lead $appliedEvents = $this->getEventRepository()->getLeadTriggeredEvents($lead->getId()); //if it's already been done, then skip it if (isset($appliedEvents[$event['id']])) { return false; } } $availableEvents = $this->getEvents(); $eventType = $event['type']; //make sure the event still exists if (!isset($availableEvents[$eventType])) { return false; } $settings = $availableEvents[$eventType]; $args = array('event' => $event, 'lead' => $lead, 'factory' => $this->factory, 'config' => $event['properties']); if (is_callable($settings['callback'])) { if (is_array($settings['callback'])) { $reflection = new \ReflectionMethod($settings['callback'][0], $settings['callback'][1]); } elseif (strpos($settings['callback'], '::') !== false) { $parts = explode('::', $settings['callback']); $reflection = new \ReflectionMethod($parts[0], $parts[1]); } else { $reflection = new \ReflectionMethod(null, $settings['callback']); } $pass = array(); foreach ($reflection->getParameters() as $param) { if (isset($args[$param->getName()])) { $pass[] = $args[$param->getName()]; } else { $pass[] = null; } } return $reflection->invokeArgs($this, $pass); } return false; }
/** * @param Form $form * @param $formHtml */ public function populateValuesWithLead(Form $form, &$formHtml) { $formName = $form->generateFormName(); $lead = $this->leadModel->getCurrentLead(); $fields = $form->getFields(); /** @var \Mautic\FormBundle\Entity\Field $f */ foreach ($fields as $f) { $leadField = $f->getLeadField(); $isAutoFill = $f->getIsAutoFill(); if (isset($leadField) && $isAutoFill) { $value = $lead->getFieldValue($leadField); if (!empty($value)) { $this->fieldHelper->populateField($f, $value, $formName, $formHtml); } } } }
/** * Generate the form's html * * @param Form $entity * @param bool $persist * * @return string */ public function generateHtml(Form $entity, $persist = true) { //generate cached HTML $theme = $entity->getTemplate(); $submissions = null; $lead = $this->leadModel->getCurrentLead(); if (!empty($theme)) { $theme .= '|'; } if ($entity->usesProgressiveProfiling()) { $submissions = $this->getRepository()->getFormResults($entity, ['leadId' => $lead->getId(), 'limit' => 200]); } $html = $this->templatingHelper->getTemplating()->render($theme . 'MauticFormBundle:Builder:form.html.php', ['form' => $entity, 'theme' => $theme, 'submissions' => $submissions, 'lead' => $lead]); if (!$entity->usesProgressiveProfiling()) { $entity->setCachedHtml($html); if ($persist) { //bypass model function as events aren't needed for this $this->getRepository()->saveEntity($entity); } } return $html; }
/** * Create/update lead from form submit. * * @param $form * @param array $leadFieldMatches * * @return Lead */ protected function createLeadFromSubmit($form, array $leadFieldMatches, $leadFields) { //set the mapped data $inKioskMode = $form->isInKioskMode(); if (!$inKioskMode) { // Default to currently tracked lead $lead = $this->leadModel->getCurrentLead(); $leadId = $lead->getId(); $currentFields = $this->leadModel->flattenFields($lead->getFields()); $this->logger->debug('FORM: Not in kiosk mode so using current contact ID #' . $lead->getId()); } else { // Default to a new lead in kiosk mode $lead = new Lead(); $lead->setNewlyCreated(true); $currentFields = $leadFieldMatches; $leadId = null; $this->logger->debug('FORM: In kiosk mode so assuming a new contact'); } $uniqueLeadFields = $this->leadFieldModel->getUniqueIdentiferFields(); // Closure to get data and unique fields $getData = function ($currentFields, $uniqueOnly = false) use($leadFields, $uniqueLeadFields) { $uniqueFieldsWithData = $data = []; foreach ($leadFields as $alias => $properties) { $data[$alias] = ''; if (isset($currentFields[$alias])) { $value = $currentFields[$alias]; $data[$alias] = $value; // make sure the value is actually there and the field is one of our uniques if (!empty($value) && array_key_exists($alias, $uniqueLeadFields)) { $uniqueFieldsWithData[$alias] = $value; } } } return $uniqueOnly ? $uniqueFieldsWithData : [$data, $uniqueFieldsWithData]; }; // Closure to help search for a conflict $checkForIdentifierConflict = function ($fieldSet1, $fieldSet2) { // Find fields in both sets $potentialConflicts = array_keys(array_intersect_key($fieldSet1, $fieldSet2)); $this->logger->debug('FORM: Potential conflicts ' . implode(', ', array_keys($potentialConflicts)) . ' = ' . implode(', ', $potentialConflicts)); $conflicts = []; foreach ($potentialConflicts as $field) { if (!empty($fieldSet1[$field]) && !empty($fieldSet2[$field])) { if (strtolower($fieldSet1[$field]) !== strtolower($fieldSet2[$field])) { $conflicts[] = $field; } } } return [count($conflicts), $conflicts]; }; // Get data for the form submission list($data, $uniqueFieldsWithData) = $getData($leadFieldMatches); $this->logger->debug('FORM: Unique fields submitted include ' . implode(', ', $uniqueFieldsWithData)); // Check for duplicate lead /** @var \Mautic\LeadBundle\Entity\Lead[] $leads */ $leads = !empty($uniqueFieldsWithData) ? $this->em->getRepository('MauticLeadBundle:Lead')->getLeadsByUniqueFields($uniqueFieldsWithData, $leadId) : []; $uniqueFieldsCurrent = $getData($currentFields, true); if (count($leads)) { $this->logger->debug(count($leads) . ' found based on unique identifiers'); /** @var \Mautic\LeadBundle\Entity\Lead $foundLead */ $foundLead = $leads[0]; $this->logger->debug('FORM: Testing contact ID# ' . $foundLead->getId() . ' for conflicts'); // Check for a conflict with the currently tracked lead $foundLeadFields = $this->leadModel->flattenFields($foundLead->getFields()); // Get unique identifier fields for the found lead then compare with the lead currently tracked $uniqueFieldsFound = $getData($foundLeadFields, true); list($hasConflict, $conflicts) = $checkForIdentifierConflict($uniqueFieldsFound, $uniqueFieldsCurrent); if ($inKioskMode || $hasConflict) { // Use the found lead without merging because there is some sort of conflict with unique identifiers or in kiosk mode and thus should not merge $lead = $foundLead; if ($hasConflict) { $this->logger->debug('FORM: Conflicts found in ' . implode(', ', $conflicts) . ' so not merging'); } else { $this->logger->debug('FORM: In kiosk mode so not merging'); } } else { $this->logger->debug('FORM: Merging contacts ' . $lead->getId() . ' and ' . $foundLead->getId()); // Merge the found lead with currently tracked lead $lead = $this->leadModel->mergeLeads($lead, $foundLead); } // Update unique fields data for comparison with submitted data $currentFields = $this->leadModel->flattenFields($lead->getFields()); $uniqueFieldsCurrent = $getData($currentFields, true); } if (!$inKioskMode) { // Check for conflicts with the submitted data and the currently tracked lead list($hasConflict, $conflicts) = $checkForIdentifierConflict($uniqueFieldsWithData, $uniqueFieldsCurrent); $this->logger->debug('FORM: Current unique contact fields ' . implode(', ', array_keys($uniqueFieldsCurrent)) . ' = ' . implode(', ', $uniqueFieldsCurrent)); $this->logger->debug('FORM: Submitted unique contact fields ' . implode(', ', array_keys($uniqueFieldsWithData)) . ' = ' . implode(', ', $uniqueFieldsWithData)); if ($hasConflict) { // There's a conflict so create a new lead $lead = new Lead(); $lead->setNewlyCreated(true); $this->logger->debug('FORM: Conflicts found in ' . implode(', ', $conflicts) . ' between current tracked contact and submitted data so assuming a new contact'); } } //check for existing IP address $ipAddress = $this->ipLookupHelper->getIpAddress(); //no lead was found by a mapped email field so create a new one if ($lead->isNewlyCreated()) { if (!$inKioskMode) { $lead->addIpAddress($ipAddress); $this->logger->debug('FORM: Associating ' . $ipAddress->getIpAddress() . ' to contact'); } } elseif (!$inKioskMode) { $leadIpAddresses = $lead->getIpAddresses(); if (!$leadIpAddresses->contains($ipAddress)) { $lead->addIpAddress($ipAddress); $this->logger->debug('FORM: Associating ' . $ipAddress->getIpAddress() . ' to contact'); } } //set the mapped fields $this->leadModel->setFieldValues($lead, $data, false); if (!empty($event)) { $event->setIpAddress($ipAddress); $lead->addPointsChangeLog($event); } // last active time $lead->setLastActive(new \DateTime()); //create a new lead $this->leadModel->saveEntity($lead, false); if (!$inKioskMode) { // Set the current lead which will generate tracking cookies $this->leadModel->setCurrentLead($lead); } else { // Set system current lead which will still allow execution of events without generating tracking cookies $this->leadModel->setSystemCurrentLead($lead); } return $lead; }
/** * @param Request $request * @param string $code * * @throws \Doctrine\ORM\ORMException * @throws \Exception */ public function hitVideo($request, $code = '200') { //don't skew results with in-house hits if (!$this->security->isAnonymous()) { //return; } $lead = $this->leadModel->getCurrentLead(); $guid = $request->get('guid'); $hit = $this->getHitForLeadByGuid($lead, $guid); $hit->setGuid($guid); $hit->setDateHit(new \Datetime()); $hit->setDuration($request->get('duration')); $hit->setUrl($request->get('url')); $hit->setTimeWatched($request->get('total_watched')); //check for existing IP $ipAddress = $this->ipLookupHelper->getIpAddress(); $hit->setIpAddress($ipAddress); // Store query array $query = $request->query->all(); unset($query['d']); $hit->setQuery($query); $hit->setLead($lead); //glean info from the IP address if ($details = $ipAddress->getIpDetails()) { $hit->setCountry($details['country']); $hit->setRegion($details['region']); $hit->setCity($details['city']); $hit->setIsp($details['isp']); $hit->setOrganization($details['organization']); } $hit->setCode($code); if (!$hit->getReferer()) { $hit->setReferer($request->server->get('HTTP_REFERER')); } $hit->setUserAgent($request->server->get('HTTP_USER_AGENT')); $hit->setRemoteHost($request->server->get('REMOTE_HOST')); //get a list of the languages the user prefers $browserLanguages = $request->server->get('HTTP_ACCEPT_LANGUAGE'); if (!empty($browserLanguages)) { $languages = explode(',', $browserLanguages); foreach ($languages as $k => $l) { if ($pos = strpos(';q=', $l) !== false) { //remove weights $languages[$k] = substr($l, 0, $pos); } } $hit->setBrowserLanguages($languages); } // Wrap in a try/catch to prevent deadlock errors on busy servers try { $this->em->persist($hit); $this->em->flush($hit); } catch (\Exception $exception) { if (MAUTIC_ENV === 'dev') { throw $exception; } else { $this->logger->addError($exception->getMessage(), ['exception' => $exception]); } } if ($this->dispatcher->hasListeners(PageEvents::VIDEO_ON_HIT)) { $event = new VideoHitEvent($hit, $request, $code); $this->dispatcher->dispatch(PageEvents::VIDEO_ON_HIT, $event); } }
/** * @param $asset * @param null $request * @param string $code * @param array $systemEntry * * @throws \Doctrine\ORM\ORMException * @throws \Exception */ public function trackDownload($asset, $request = null, $code = '200', $systemEntry = []) { // Don't skew results with in-house downloads if (empty($systemEntry) && !$this->security->isAnonymous()) { return; } if ($request == null) { $request = $this->request; } $download = new Download(); $download->setDateDownload(new \Datetime()); // Download triggered by lead if (empty($systemEntry)) { //check for any clickthrough info $clickthrough = $request->get('ct', false); if (!empty($clickthrough)) { $clickthrough = $this->decodeArrayFromUrl($clickthrough); if (!empty($clickthrough['lead'])) { $lead = $this->leadModel->getEntity($clickthrough['lead']); if ($lead !== null) { $this->leadModel->setLeadCookie($clickthrough['lead']); list($trackingId, $trackingNewlyGenerated) = $this->leadModel->getTrackingCookie(); $leadClickthrough = true; $this->leadModel->setCurrentLead($lead); } } if (!empty($clickthrough['channel'])) { if (count($clickthrough['channel']) === 1) { $channelId = reset($clickthrough['channel']); $channel = key($clickthrough['channel']); } else { $channel = $clickthrough['channel'][0]; $channelId = (int) $clickthrough['channel'][1]; } $download->setSource($channel); $download->setSourceId($channelId); } elseif (!empty($clickthrough['source'])) { $download->setSource($clickthrough['source'][0]); $download->setSourceId($clickthrough['source'][1]); } if (!empty($clickthrough['email'])) { $emailRepo = $this->em->getRepository('MauticEmailBundle:Email'); if ($emailEntity = $emailRepo->getEntity($clickthrough['email'])) { $download->setEmail($emailEntity); } } } if (empty($leadClickthrough)) { list($lead, $trackingId, $trackingNewlyGenerated) = $this->leadModel->getCurrentLead(true); } $download->setLead($lead); } else { $trackingId = ''; if (isset($systemEntry['lead'])) { $lead = $systemEntry['lead']; if (!$lead instanceof Lead) { $leadId = is_array($lead) ? $lead['id'] : $lead; $lead = $this->em->getReference('MauticLeadBundle:Lead', $leadId); } $download->setLead($lead); } if (!empty($systemEntry['source'])) { $download->setSource($systemEntry['source'][0]); $download->setSourceId($systemEntry['source'][1]); } if (isset($systemEntry['email'])) { $email = $systemEntry['email']; if (!$email instanceof Email) { $emailId = is_array($email) ? $email['id'] : $email; $email = $this->em->getReference('MauticEmailBundle:Email', $emailId); } $download->setEmail($email); } if (isset($systemEntry['tracking_id'])) { $trackingId = $systemEntry['tracking_id']; $trackingNewlyGenerated = false; } elseif ($this->security->isAnonymous() && !defined('IN_MAUTIC_CONSOLE')) { // If the session is anonymous and not triggered via CLI, assume the lead did something to trigger the // system forced download such as an email list($trackingId, $trackingNewlyGenerated) = $this->leadModel->getTrackingCookie(); } } $isUnique = true; if (!empty($trackingNewlyGenerated)) { // Cookie was just generated so this is definitely a unique download $isUnique = $trackingNewlyGenerated; } elseif (!empty($trackingId)) { // Determine if this is a unique download $isUnique = $this->getDownloadRepository()->isUniqueDownload($asset->getId(), $trackingId); } $download->setTrackingId($trackingId); if (!empty($asset) && empty($systemEntry)) { $download->setAsset($asset); $this->getRepository()->upDownloadCount($asset->getId(), 1, $isUnique); } //check for existing IP $ipAddress = $this->ipLookupHelper->getIpAddress(); $download->setCode($code); $download->setIpAddress($ipAddress); if ($request !== null) { $download->setReferer($request->server->get('HTTP_REFERER')); } // Dispatch event if ($this->dispatcher->hasListeners(AssetEvents::ASSET_ON_LOAD)) { $event = new AssetLoadEvent($download, $isUnique); $this->dispatcher->dispatch(AssetEvents::ASSET_ON_LOAD, $event); } // Wrap in a try/catch to prevent deadlock errors on busy servers try { $this->em->persist($download); $this->em->flush($download); } catch (\Exception $e) { if (MAUTIC_ENV === 'dev') { throw $e; } else { error_log($e); } } $this->em->detach($download); }
/** * Triggers a specific point change * * @param $type * @param mixed $eventDetails passthrough from function triggering action to the callback function * @param mixed $typeId Something unique to the triggering event to prevent unnecessary duplicate calls * @param Lead $lead *25 * @return void */ public function triggerAction($type, $eventDetails = null, $typeId = null, Lead $lead = null) { //only trigger actions for anonymous users if (!$this->security->isAnonymous()) { return; } if ($typeId !== null && MAUTIC_ENV === 'prod') { //let's prevent some unnecessary DB calls $triggeredEvents = $this->session->get('mautic.triggered.point.actions', array()); if (in_array($typeId, $triggeredEvents)) { return; } $triggeredEvents[] = $typeId; $this->session->set('mautic.triggered.point.actions', $triggeredEvents); } //find all the actions for published points /** @var \Mautic\PointBundle\Entity\PointRepository $repo */ $repo = $this->getRepository(); $availablePoints = $repo->getPublishedByType($type); $ipAddress = $this->ipLookupHelper->getIpAddress(); if (null === $lead) { $lead = $this->leadModel->getCurrentLead(); if (null === $lead || !$lead->getId()) { return; } } //get available actions $availableActions = $this->getPointActions(); //get a list of actions that has already been performed on this lead $completedActions = $repo->getCompletedLeadActions($type, $lead->getId()); $persist = array(); foreach ($availablePoints as $action) { //if it's already been done, then skip it if (isset($completedActions[$action->getId()])) { continue; } //make sure the action still exists if (!isset($availableActions['actions'][$action->getType()])) { continue; } $settings = $availableActions['actions'][$action->getType()]; $args = array('action' => array('id' => $action->getId(), 'type' => $action->getType(), 'name' => $action->getName(), 'properties' => $action->getProperties(), 'points' => $action->getDelta()), 'lead' => $lead, 'factory' => $this->factory, 'eventDetails' => $eventDetails); $callback = isset($settings['callback']) ? $settings['callback'] : array('\\Mautic\\PointBundle\\Helper\\EventHelper', 'engagePointAction'); if (is_callable($callback)) { if (is_array($callback)) { $reflection = new \ReflectionMethod($callback[0], $callback[1]); } elseif (strpos($callback, '::') !== false) { $parts = explode('::', $callback); $reflection = new \ReflectionMethod($parts[0], $parts[1]); } else { $reflection = new \ReflectionMethod(null, $callback); } $pass = array(); foreach ($reflection->getParameters() as $param) { if (isset($args[$param->getName()])) { $pass[] = $args[$param->getName()]; } else { $pass[] = null; } } $pointsChange = $reflection->invokeArgs($this, $pass); if ($pointsChange) { $delta = $action->getDelta(); $lead->adjustPoints($delta); $parsed = explode('.', $action->getType()); $lead->addPointsChangeLogEntry($parsed[0], $action->getId() . ": " . $action->getName(), $parsed[1], $delta, $ipAddress); $event = new PointActionEvent($action, $lead); $this->dispatcher->dispatch(PointEvents::POINT_ON_ACTION, $event); $log = new LeadPointLog(); $log->setIpAddress($ipAddress); $log->setPoint($action); $log->setLead($lead); $log->setDateFired(new \DateTime()); $persist[] = $log; } } } if (!empty($persist)) { $this->leadModel->saveEntity($lead); $this->getRepository()->saveEntities($persist); // Detach logs to reserve memory $this->em->clear('Mautic\\PointBundle\\Entity\\LeadPointLog'); } }