mautic / plugins /MauticCrmBundle /Integration /HubspotIntegration.php
chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
raw
history blame contribute delete
21.6 kB
<?php
namespace MauticPlugin\MauticCrmBundle\Integration;
use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\Helper\ArrayHelper;
use Mautic\CoreBundle\Helper\CacheStorageHelper;
use Mautic\CoreBundle\Helper\EncryptionHelper;
use Mautic\CoreBundle\Helper\PathsHelper;
use Mautic\CoreBundle\Helper\UserHelper;
use Mautic\CoreBundle\Model\NotificationModel;
use Mautic\LeadBundle\DataObject\LeadManipulator;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\StagesChangeLog;
use Mautic\LeadBundle\Model\CompanyModel;
use Mautic\LeadBundle\Model\DoNotContact;
use Mautic\LeadBundle\Model\FieldModel;
use Mautic\LeadBundle\Model\LeadModel;
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
use Mautic\PluginBundle\Model\IntegrationEntityModel;
use Mautic\StageBundle\Entity\Stage;
use MauticPlugin\MauticCrmBundle\Api\HubspotApi;
use Monolog\Logger;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Router;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @method HubspotApi getApiHelper()
*/
class HubspotIntegration extends CrmAbstractIntegration
{
public const ACCESS_KEY = 'accessKey';
public function __construct(
EventDispatcherInterface $eventDispatcher,
CacheStorageHelper $cacheStorageHelper,
EntityManager $entityManager,
Session $session,
RequestStack $requestStack,
Router $router,
TranslatorInterface $translator,
Logger $logger,
EncryptionHelper $encryptionHelper,
LeadModel $leadModel,
CompanyModel $companyModel,
PathsHelper $pathsHelper,
NotificationModel $notificationModel,
FieldModel $fieldModel,
IntegrationEntityModel $integrationEntityModel,
DoNotContact $doNotContact,
protected UserHelper $userHelper
) {
parent::__construct(
$eventDispatcher,
$cacheStorageHelper,
$entityManager,
$session,
$requestStack,
$router,
$translator,
$logger,
$encryptionHelper,
$leadModel,
$companyModel,
$pathsHelper,
$notificationModel,
$fieldModel,
$integrationEntityModel,
$doNotContact
);
}
public function getName(): string
{
return 'Hubspot';
}
/**
* @return array<string, string>
*/
public function getRequiredKeyFields(): array
{
return [];
}
public function getApiKey(): string
{
return 'hapikey';
}
/**
* Get the array key for the auth token.
*/
public function getAuthTokenKey(): string
{
return 'hapikey';
}
public function getSupportedFeatures(): array
{
return ['push_lead', 'get_leads'];
}
/**
* @param bool $inAuthorization
*
* @return mixed|string|null
*/
public function getBearerToken($inAuthorization = false)
{
$tokenData = $this->getKeys();
return $tokenData[self::ACCESS_KEY] ?? null;
}
/**
* @return array<string, bool>
*/
public function getFormSettings(): array
{
return [
'requires_callback' => false,
'requires_authorization' => false,
];
}
public function getAuthenticationType(): string
{
return $this->getBearerToken() ? 'oauth2' : 'key';
}
public function getApiUrl(): string
{
return 'https://api.hubapi.com';
}
/**
* Get if data priority is enabled in the integration or not default is false.
*/
public function getDataPriority(): bool
{
return true;
}
/**
* Get available company fields for choices in the config UI.
*
* @param array $settings
*
* @return array
*/
public function getFormCompanyFields($settings = [])
{
return $this->getFormFieldsByObject('company', $settings);
}
/**
* @param array $settings
*
* @return array|mixed
*/
public function getFormLeadFields($settings = [])
{
return $this->getFormFieldsByObject('contacts', $settings);
}
/**
* @return mixed[]
*/
public function getAvailableLeadFields($settings = []): array
{
if ($fields = parent::getAvailableLeadFields()) {
return $fields;
}
$hubsFields = [];
$silenceExceptions = $settings['silence_exceptions'] ?? true;
if (isset($settings['feature_settings']['objects'])) {
$hubspotObjects = $settings['feature_settings']['objects'];
} else {
$settings = $this->settings->getFeatureSettings();
$hubspotObjects = $settings['objects'] ?? ['contacts'];
}
try {
if ($this->isAuthorized()) {
if (!empty($hubspotObjects) and is_array($hubspotObjects)) {
foreach ($hubspotObjects as $object) {
// Check the cache first
$settings['cache_suffix'] = $cacheSuffix = '.'.$object;
if ($fields = parent::getAvailableLeadFields($settings)) {
$hubsFields[$object] = $fields;
continue;
}
$leadFields = $this->getApiHelper()->getLeadFields($object);
if (isset($leadFields)) {
foreach ($leadFields as $fieldInfo) {
$hubsFields[$object][$fieldInfo['name']] = [
'type' => 'string',
'label' => $fieldInfo['label'],
'required' => ('email' === $fieldInfo['name']),
];
if (!empty($fieldInfo['readOnlyValue'])) {
$hubsFields[$object][$fieldInfo['name']]['update_mautic'] = 1;
$hubsFields[$object][$fieldInfo['name']]['readOnly'] = 1;
}
}
}
$this->cache->set('leadFields'.$cacheSuffix, $hubsFields[$object]);
}
}
}
} catch (\Exception $e) {
$this->logIntegrationError($e);
if (!$silenceExceptions) {
throw $e;
}
}
return $hubsFields;
}
/**
* @param array $objects
*
* @return array
*/
protected function cleanPriorityFields($fieldsToUpdate, $objects = null)
{
if (null === $objects) {
$objects = ['Leads', 'Contacts'];
}
if (isset($fieldsToUpdate['leadFields'])) {
// Pass in the whole config
$fields = $fieldsToUpdate['leadFields'];
} else {
$fields = array_flip($fieldsToUpdate);
}
return $this->prepareFieldsForSync($fields, $fieldsToUpdate, $objects);
}
/**
* Format the lead data to the structure that HubSpot requires for the createOrUpdate request.
*
* @param array $leadData All the lead fields mapped
*/
public function formatLeadDataForCreateOrUpdate($leadData, $lead, $updateLink = false): array
{
$formattedLeadData = [];
if (!$updateLink) {
foreach ($leadData as $field => $value) {
if ('lifecyclestage' == $field || 'associatedcompanyid' == $field) {
continue;
}
$formattedLeadData['properties'][] = [
'property' => $field,
'value' => $value,
];
}
}
return $formattedLeadData;
}
public function isAuthorized(): bool
{
$keys = $this->getKeys();
return isset($keys[$this->getAuthTokenKey()]) || isset($keys[self::ACCESS_KEY]);
}
/**
* @return mixed
*/
public function getHubSpotApiKey()
{
$tokenData = $this->getKeys();
return $tokenData[$this->getAuthTokenKey()];
}
/**
* @param FormBuilder $builder
* @param array $data
* @param string $formArea
*/
public function appendToForm(&$builder, $data, $formArea): void
{
if ('keys' === $formArea) {
$builder->add(
self::ACCESS_KEY,
TextType::class,
[
'label' => 'mautic.hubspot.form.accessKey',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
],
'required' => false,
]
);
$builder->add(
$this->getApiKey(),
TextType::class,
[
'label' => 'mautic.hubspot.form.apikey',
'label_attr' => ['class' => 'control-label'],
'attr' => [
'class' => 'form-control',
'readonly' => true,
],
'required' => false,
]
);
}
if ('features' == $formArea) {
$builder->add(
'objects',
ChoiceType::class,
[
'choices' => [
'mautic.hubspot.object.contact' => 'contacts',
'mautic.hubspot.object.company' => 'company',
],
'expanded' => true,
'multiple' => true,
'label' => $this->getTranslator()->trans('mautic.crm.form.objects_to_pull_from', ['%crm%' => 'Hubspot']),
'label_attr' => ['class' => ''],
'placeholder' => false,
'required' => false,
]
);
}
}
/**
* @return array
*/
public function amendLeadDataBeforeMauticPopulate($data, $object)
{
if (!isset($data['properties'])) {
return [];
}
foreach ($data['properties'] as $key => $field) {
$value = str_replace(';', '|', $field['value']);
$fieldsValues[$key] = $value;
}
if ('Lead' == $object && !isset($fieldsValues['email'])) {
foreach ($data['identity-profiles'][0]['identities'] as $identifiedProfile) {
if ('EMAIL' == $identifiedProfile['type']) {
$fieldsValues['email'] = $identifiedProfile['value'];
}
}
}
return $fieldsValues;
}
/**
* @param array $params
* @param array $result
* @param string $object
*
* @return array|null
*/
public function getLeads($params = [], $query = null, &$executed = null, $result = [], $object = 'Lead')
{
if (!is_array($executed)) {
$executed = [
0 => 0,
1 => 0,
];
}
try {
if ($this->isAuthorized()) {
$config = $this->mergeConfigToFeatureSettings();
$fields = implode('&property=', array_keys($config['leadFields']));
$params['post_append_to_query'] = '&property='.$fields.'&property=lifecyclestage';
$params['Count'] = 100;
$data = $this->getApiHelper()->getContacts($params);
if (isset($data['contacts'])) {
foreach ($data['contacts'] as $contact) {
if (is_array($contact)) {
$contactData = $this->amendLeadDataBeforeMauticPopulate($contact, 'Lead');
$contact = $this->getMauticLead($contactData);
if ($contact && !$contact->isNewlyCreated()) { // updated
$executed[0] = $executed[0] + 1;
} elseif ($contact && $contact->isNewlyCreated()) { // newly created
$executed[1] = $executed[1] + 1;
}
if ($contact) {
$this->em->detach($contact);
}
}
}
if ($data['has-more']) {
$params['vidOffset'] = $data['vid-offset'];
$params['timeOffset'] = $data['time-offset'];
$this->getLeads($params, $query, $executed);
}
}
return $executed;
}
} catch (\Exception $e) {
$this->logIntegrationError($e);
}
return $executed;
}
/**
* @param array $params
* @param bool $id
*/
public function getCompanies($params = [], $id = false, &$executed = null)
{
$results = [];
try {
if ($this->isAuthorized()) {
$params['Count'] = 100;
$data = $this->getApiHelper()->getCompanies($params, $id);
if ($id) {
$results['results'][] = array_merge($results, $data);
} else {
$results['results'] = array_merge($results, $data['results']);
}
foreach ($results['results'] as $company) {
if (isset($company['properties'])) {
$companyData = $this->amendLeadDataBeforeMauticPopulate($company, null);
$company = $this->getMauticCompany($companyData);
if ($id) {
return $company;
}
if ($company) {
++$executed;
$this->em->detach($company);
}
}
}
if (isset($data['hasMore']) and $data['hasMore']) {
$params['offset'] = $data['offset'];
if ($params['offset'] < strtotime($params['start'])) {
$this->getCompanies($params, $id, $executed);
}
}
return $executed;
}
} catch (\Exception $e) {
$this->logIntegrationError($e);
}
return $executed;
}
/**
* Create or update existing Mautic lead from the integration's profile data.
*
* @param mixed $data Profile data from integration
* @param bool|true $persist Set to false to not persist lead to the database in this method
* @param array|null $socialCache
* @param mixed|null $identifiers
* @param string|null $object
*
* @return Lead
*/
public function getMauticLead($data, $persist = true, $socialCache = null, $identifiers = null, $object = null)
{
if (is_object($data)) {
// Convert to array in all levels
$data = json_encode(json_decode($data, true));
} elseif (is_string($data)) {
// Assume JSON
$data = json_decode($data, true);
}
if (isset($data['lifecyclestage'])) {
$stageName = $data['lifecyclestage'];
unset($data['lifecyclestage']);
}
if (isset($data['associatedcompanyid'])) {
$company = $this->getCompanies([], $data['associatedcompanyid']);
unset($data['associatedcompanyid']);
}
if ($lead = parent::getMauticLead($data, false, $socialCache, $identifiers, $object)) {
if (isset($stageName)) {
$stage = $this->em->getRepository(Stage::class)->getStageByName($stageName);
if (empty($stage)) {
$stage = new Stage();
$stage->setName($stageName);
$stages[$stageName] = $stage;
}
if (!$lead->getStage() && $lead->getStage() != $stage) {
$lead->setStage($stage);
// add a contact stage change log
$log = new StagesChangeLog();
$log->setStage($stage);
$log->setEventName($stage->getId().':'.$stage->getName());
$log->setLead($lead);
$log->setActionName(
$this->translator->trans(
'mautic.stage.import.action.name',
[
'%name%' => $this->userHelper->getUser()->getUsername(),
]
)
);
$log->setDateAdded(new \DateTime());
$lead->stageChangeLog($log);
}
}
if ($persist && !empty($lead->getChanges(true))) {
// Only persist if instructed to do so as it could be that calling code needs to manipulate the lead prior to executing event listeners
try {
$lead->setManipulator(new LeadManipulator(
'plugin',
$this->getName(),
null,
$this->getDisplayName()
));
$this->leadModel->saveEntity($lead, false);
if (isset($company)) {
$this->leadModel->addToCompany($lead, $company);
$this->em->detach($company);
}
} catch (\Exception $exception) {
$this->logger->warning($exception->getMessage());
return;
}
}
}
return $lead;
}
/**
* @param Lead $lead
* @param array $config
*
* @return array|bool
*/
public function pushLead($lead, $config = [])
{
$config = $this->mergeConfigToFeatureSettings($config);
if (empty($config['leadFields'])) {
return [];
}
$object = 'contacts';
$createFields = $config['leadFields'];
$readOnlyFields = $this->getReadOnlyFields($object);
$createFields = array_filter(
$createFields,
function ($createField, $key) use ($readOnlyFields) {
if (!isset($readOnlyFields[$key])) {
return $createField;
}
},
ARRAY_FILTER_USE_BOTH
);
$mappedData = $this->populateLeadData(
$lead,
[
'leadFields' => $createFields,
'object' => $object,
'feature_settings' => ['objects' => $config['objects']],
]
);
$this->amendLeadDataBeforePush($mappedData);
if (empty($mappedData)) {
return false;
}
if ($this->isAuthorized()) {
$leadData = $this->getApiHelper()->createLead($mappedData, $lead);
if (!empty($leadData['vid'])) {
/** @var IntegrationEntityRepository $integrationEntityRepo */
$integrationEntityRepo = $this->em->getRepository(\Mautic\PluginBundle\Entity\IntegrationEntity::class);
$integrationId = $integrationEntityRepo->getIntegrationsEntityId($this->getName(), $object, 'lead', $lead->getId());
$integrationEntity = (empty($integrationId)) ?
$this->createIntegrationEntity(
$object,
$leadData['vid'],
'lead',
$lead->getId(),
[],
false
) : $integrationEntityRepo->getEntity($integrationId[0]['id']);
$integrationEntity->setLastSyncDate($this->getLastSyncDate());
$this->getIntegrationEntityRepository()->saveEntity($integrationEntity);
$this->em->detach($integrationEntity);
}
return true;
}
return false;
}
/**
* Amend mapped lead data before pushing to CRM.
*/
public function amendLeadDataBeforePush(&$mappedData): void
{
foreach ($mappedData as &$data) {
$data = str_replace('|', ';', $data);
}
}
/**
* @throws \Exception
*/
private function getReadOnlyFields($object): ?array
{
$fields = ArrayHelper::getValue($object, $this->getAvailableLeadFields(), []);
return array_filter(
$fields,
function ($field) {
if (!empty($field['readOnly'])) {
return $field;
}
}
);
}
}