/** * 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 string|Stat $stat * @param $request * @param bool $viaBrowser */ public function hitEmail($stat, $request, $viaBrowser = false) { if (!$stat instanceof Stat) { $stat = $this->getEmailStatus($stat); } if (!$stat) { return; } $email = $stat->getEmail(); if ((int) $stat->isRead()) { if ($viaBrowser && !$stat->getViewedInBrowser()) { //opened via browser so note it $stat->setViewedInBrowser($viaBrowser); } } $readDateTime = new DateTimeHelper(); $stat->setLastOpened($readDateTime->getDateTime()); $lead = $stat->getLead(); if ($lead !== null) { // Set the lead as current lead $this->leadModel->setCurrentLead($lead); } $firstTime = false; if (!$stat->getIsRead()) { $firstTime = true; $stat->setIsRead(true); $stat->setDateRead($readDateTime->getDateTime()); // Only up counts if associated with both an email and lead if ($email && $lead) { try { $this->getRepository()->upCount($email->getId(), 'read', 1, $email->isVariant()); } catch (\Exception $exception) { error_log($exception); } } } if ($viaBrowser) { $stat->setViewedInBrowser($viaBrowser); } $stat->addOpenDetails(['datetime' => $readDateTime->toUtcString(), 'useragent' => $request->server->get('HTTP_USER_AGENT'), 'inBrowser' => $viaBrowser]); //check for existing IP $ipAddress = $this->ipLookupHelper->getIpAddress(); $stat->setIpAddress($ipAddress); if ($this->dispatcher->hasListeners(EmailEvents::EMAIL_ON_OPEN)) { $event = new EmailOpenEvent($stat, $request, $firstTime); $this->dispatcher->dispatch(EmailEvents::EMAIL_ON_OPEN, $event); } //device granularity $dd = new DeviceDetector($request->server->get('HTTP_USER_AGENT')); $dd->parse(); $deviceRepo = $this->leadModel->getDeviceRepository(); $emailOpenDevice = $deviceRepo->getDevice(null, $lead, $dd->getDeviceName(), $dd->getBrand(), $dd->getModel()); if (empty($emailOpenDevice)) { $emailOpenDevice = new LeadDevice(); $emailOpenDevice->setClientInfo($dd->getClient()); $emailOpenDevice->setDevice($dd->getDeviceName()); $emailOpenDevice->setDeviceBrand($dd->getBrand()); $emailOpenDevice->setDeviceModel($dd->getModel()); $emailOpenDevice->setDeviceOs($dd->getOs()); $emailOpenDevice->setDateOpen($readDateTime->toUtcString()); $emailOpenDevice->setLead($lead); try { $this->em->persist($emailOpenDevice); $this->em->flush($emailOpenDevice); } catch (\Exception $exception) { if (MAUTIC_ENV === 'dev') { throw $exception; } else { $this->logger->addError($exception->getMessage(), ['exception' => $exception]); } } } else { $emailOpenDevice = $deviceRepo->getEntity($emailOpenDevice['id']); } if ($email) { $this->em->persist($email); $this->em->flush($email); } if (isset($emailOpenDevice) and is_object($emailOpenDevice)) { $emailOpenStat = new StatDevice(); $emailOpenStat->setIpAddress($ipAddress); $emailOpenStat->setDevice($emailOpenDevice); $emailOpenStat->setDateOpened($readDateTime->toUtcString()); $emailOpenStat->setStat($stat); $this->em->persist($emailOpenStat); $this->em->flush($emailOpenStat); } $this->em->persist($stat); $this->em->flush(); }
/** * @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); }