Spaces:
No application file
No application file
| namespace MauticPlugin\MauticCrmBundle\Integration; | |
| use Doctrine\ORM\EntityManager; | |
| use Mautic\CoreBundle\Form\Type\ButtonGroupType; | |
| use Mautic\CoreBundle\Helper\CacheStorageHelper; | |
| use Mautic\CoreBundle\Helper\EncryptionHelper; | |
| use Mautic\CoreBundle\Helper\PathsHelper; | |
| use Mautic\CoreBundle\Model\NotificationModel; | |
| use Mautic\LeadBundle\Entity\Company; | |
| use Mautic\LeadBundle\Entity\Lead; | |
| use Mautic\LeadBundle\Model\CompanyModel; | |
| use Mautic\LeadBundle\Model\DoNotContact; | |
| use Mautic\LeadBundle\Model\FieldModel; | |
| use Mautic\LeadBundle\Model\LeadModel; | |
| use Mautic\PluginBundle\Entity\IntegrationEntity; | |
| use Mautic\PluginBundle\Entity\IntegrationEntityRepository; | |
| use Mautic\PluginBundle\Exception\ApiErrorException; | |
| use Mautic\PluginBundle\Model\IntegrationEntityModel; | |
| use Mautic\UserBundle\Model\UserModel; | |
| use Monolog\Logger; | |
| use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
| use Symfony\Component\Form\Extension\Core\Type\ChoiceType; | |
| use Symfony\Component\Form\FormBuilder; | |
| use Symfony\Component\HttpFoundation\RequestStack; | |
| use Symfony\Component\HttpFoundation\Session\Session; | |
| use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | |
| use Symfony\Component\Routing\Router; | |
| use Symfony\Component\Validator\Constraints\NotBlank; | |
| use Symfony\Contracts\Translation\TranslatorInterface; | |
| class SugarcrmIntegration extends CrmAbstractIntegration | |
| { | |
| /** | |
| * @var string[] | |
| */ | |
| private array $objects = [ | |
| 'Leads', | |
| 'Contacts', | |
| 'Accounts', | |
| ]; | |
| /** | |
| * @var string[] | |
| */ | |
| private array $sugarDncKeys = ['email_opt_out', 'invalid_email']; | |
| private $authorizationError; | |
| 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, | |
| protected DoNotContact $doNotContactModel, | |
| private UserModel $userModel | |
| ) { | |
| parent::__construct( | |
| $eventDispatcher, | |
| $cacheStorageHelper, | |
| $entityManager, | |
| $session, | |
| $requestStack, | |
| $router, | |
| $translator, | |
| $logger, | |
| $encryptionHelper, | |
| $leadModel, | |
| $companyModel, | |
| $pathsHelper, | |
| $notificationModel, | |
| $fieldModel, | |
| $integrationEntityModel, | |
| $doNotContactModel | |
| ); | |
| } | |
| /** | |
| * Returns the name of the social integration that must match the name of the file. | |
| */ | |
| public function getName(): string | |
| { | |
| return 'Sugarcrm'; | |
| } | |
| public function getSupportedFeatures(): array | |
| { | |
| // Only push_lead is currently supported for version 7 | |
| return ['push_lead', 'get_leads', 'push_leads']; | |
| } | |
| /** | |
| * Get the array key for clientId. | |
| */ | |
| public function getClientIdKey(): string | |
| { | |
| return 'client_id'; | |
| } | |
| /** | |
| * Get the array key for client secret. | |
| */ | |
| public function getClientSecretKey(): string | |
| { | |
| return 'client_secret'; | |
| } | |
| public function getSecretKeys(): array | |
| { | |
| return [ | |
| 'client_secret', | |
| 'password', | |
| ]; | |
| } | |
| /** | |
| * Get the array key for the auth token. | |
| */ | |
| public function getAuthTokenKey(): string | |
| { | |
| return (isset($this->keys['version']) && '6' == $this->keys['version']) ? 'id' : 'access_token'; | |
| } | |
| /** | |
| * SugarCRM 7 refresh tokens. | |
| */ | |
| public function getRefreshTokenKeys(): array | |
| { | |
| return [ | |
| 'refresh_token', | |
| 'expires', | |
| ]; | |
| } | |
| public function getAccessTokenUrl(): string | |
| { | |
| $apiUrl = ('6' == $this->keys['version']) ? 'service/v4_1/rest.php' : 'rest/v10/oauth2/token'; | |
| return sprintf('%s/%s', $this->keys['sugarcrm_url'], $apiUrl); | |
| } | |
| /** | |
| * @return string | |
| */ | |
| public function getAuthLoginUrl() | |
| { | |
| return $this->router->generate('mautic_integration_auth_callback', ['integration' => $this->getName()]); | |
| } | |
| /** | |
| * Retrieves and stores tokens returned from oAuthLogin. | |
| * | |
| * @param array $settings | |
| * @param array $parameters | |
| * | |
| * @return array | |
| */ | |
| public function authCallback($settings = [], $parameters = []) | |
| { | |
| if (isset($this->keys['version']) && '6' == $this->keys['version']) { | |
| $success = $this->isAuthorized(); | |
| if (!$success) { | |
| return $this->authorizationError; | |
| } else { | |
| return false; | |
| } | |
| } else { | |
| $settings = [ | |
| 'grant_type' => 'password', | |
| 'ignore_redirecturi' => true, | |
| ]; | |
| $parameters = [ | |
| 'username' => $this->keys['username'], | |
| 'password' => $this->keys['password'], | |
| 'platform' => 'base', | |
| ]; | |
| return parent::authCallback($settings, $parameters); | |
| } | |
| } | |
| /** | |
| * @return array<string, string> | |
| */ | |
| public function getRequiredKeyFields(): array | |
| { | |
| return [ | |
| 'sugarcrm_url' => 'mautic.sugarcrm.form.url', | |
| 'client_id' => 'mautic.sugarcrm.form.clientkey', | |
| 'client_secret' => 'mautic.sugarcrm.form.clientsecret', | |
| 'username' => 'mautic.sugarcrm.form.username', | |
| 'password' => 'mautic.sugarcrm.form.password', | |
| ]; | |
| } | |
| /** | |
| * Get available company fields for choices in the config UI. | |
| * | |
| * @param array $settings | |
| * | |
| * @return array | |
| */ | |
| public function getFormCompanyFields($settings = []) | |
| { | |
| return $this->getFormFieldsByObject('company', $settings); | |
| } | |
| /** | |
| * Get available fields for choices in the config UI. | |
| * | |
| * @param array $settings | |
| */ | |
| public function getFormLeadFields($settings = []): array | |
| { | |
| if (!$this->isAuthorized()) { | |
| return []; | |
| } | |
| if (isset($settings['feature_settings']['objects'])) { | |
| // combine keys with values | |
| $settings['feature_settings']['objects'] = array_combine( | |
| array_values($settings['feature_settings']['objects']), | |
| $settings['feature_settings']['objects'] | |
| ); | |
| } | |
| // unset company object | |
| if (isset($settings['feature_settings']['objects']['company'])) { | |
| unset($settings['feature_settings']['objects']['company']); | |
| } | |
| if (empty($settings['feature_settings']['objects'])) { | |
| // BC force add Leads and Contacts from Integration | |
| $settings['feature_settings']['objects']['Leads'] = 'Leads'; | |
| $settings['feature_settings']['objects']['Contacts'] = 'Contacts'; | |
| } | |
| $fields = []; | |
| // merge all arrays from level 1 | |
| $fieldsromObjects = $this->getAvailableLeadFields($settings); | |
| foreach ($fieldsromObjects as $fieldsFromObject) { | |
| $fields = array_merge($fields, $fieldsFromObject); | |
| } | |
| return $fields; | |
| } | |
| /** | |
| * @param array $settings | |
| * | |
| * @throws \Exception | |
| */ | |
| public function getAvailableLeadFields($settings = []): array | |
| { | |
| $sugarFields = []; | |
| $silenceExceptions = $settings['silence_exceptions'] ?? true; | |
| $sugarObjects = []; | |
| if (!empty($settings['feature_settings']['objects'])) { | |
| $sugarObjects = $settings['feature_settings']['objects']; | |
| } else { | |
| $sugarObjects['Leads'] = 'Leads'; | |
| $sugarObjects['Contacts'] = 'Contacts'; | |
| $settings['feature_settings']['objects'] = $sugarObjects; | |
| } | |
| $isRequired = fn (array $field, $object) => match (true) { | |
| 'Leads' === $object && ('webtolead_email1' === $field['name'] || 'email1' === $field['name']), 'Contacts' === $object && 'email1' === $field['name'], 'id' !== $field['name'] && !empty($field['required']) => true, | |
| default => false, | |
| }; | |
| try { | |
| if (!empty($sugarObjects) and is_array($sugarObjects)) { | |
| foreach ($sugarObjects as $sObject) { | |
| if ('Accounts' === $sObject) { | |
| // Match Sugar object to Mautic's | |
| $sObject = 'company'; | |
| } | |
| $sObject = trim($sObject); | |
| if ($this->isAuthorized()) { | |
| // Check the cache first | |
| $settings['cache_suffix'] = $cacheSuffix = '.'.$sObject; | |
| if ($fields = parent::getAvailableLeadFields($settings)) { | |
| if (('company' === $sObject && isset($fields['id'])) || isset($fields['id__'.$sObject])) { | |
| $sugarFields[$sObject] = $fields; | |
| continue; | |
| } | |
| } | |
| if (!isset($sugarFields[$sObject])) { | |
| $fields = $this->getApiHelper()->getLeadFields($sObject); | |
| if (null != $fields && !empty($fields)) { | |
| if (isset($fields['module_fields']) && !empty($fields['module_fields'])) { | |
| // 6.x/community | |
| foreach ($fields['module_fields'] as $fieldInfo) { | |
| if (isset($fieldInfo['name']) && (!in_array($fieldInfo['type'], ['id', 'assigned_user_name', 'link', 'relate']) || ('id' == $fieldInfo['type'] && 'id' == $fieldInfo['name']) | |
| ) | |
| ) { | |
| $type = 'string'; | |
| $fieldName = (!str_contains($fieldInfo['name'], | |
| 'webtolead_email')) ? $fieldInfo['name'] : str_replace('webtolead_', | |
| '', $fieldInfo['name']); | |
| // make these congruent as some come in with colons and some do not | |
| $label = str_replace(':', '', $fieldInfo['label']); | |
| if ('company' !== $sObject) { | |
| $sugarFields[$sObject][$fieldName.'__'.$sObject] = [ | |
| 'type' => $type, | |
| 'label' => $sObject.'-'.$label, | |
| 'required' => $isRequired($fieldInfo, $sObject), | |
| 'group' => $sObject, | |
| 'optionLabel' => $fieldInfo['label'], | |
| ]; | |
| } else { | |
| $sugarFields[$sObject][$fieldName] = [ | |
| 'type' => $type, | |
| 'label' => $label, | |
| 'required' => $isRequired($fieldInfo, $sObject), | |
| ]; | |
| } | |
| } | |
| } | |
| } elseif (isset($fields['fields']) && !empty($fields['fields'])) { | |
| // 7.x | |
| foreach ($fields['fields'] as $fieldInfo) { | |
| if (isset($fieldInfo['name']) && empty($fieldInfo['readonly']) | |
| && (!in_array( | |
| $fieldInfo['type'], | |
| ['id', 'team_list', 'link', 'relate'] | |
| ) | |
| || ('id' == $fieldInfo['type'] && 'id' == $fieldInfo['name']) | |
| ) | |
| ) { | |
| if (!empty($fieldInfo['comment'])) { | |
| $label = $fieldInfo['comment']; | |
| } elseif (!empty($fieldInfo['help'])) { | |
| $label = $fieldInfo['help']; | |
| } else { | |
| $label = ucfirst(str_replace('_', ' ', $fieldInfo['name'])); | |
| } | |
| // make these congruent as some come in with colons and some do not | |
| $label = str_replace(':', '', $label); | |
| $fieldName = (!str_contains($fieldInfo['name'], 'webtolead_email')) | |
| ? $fieldInfo['name'] | |
| : str_replace( | |
| 'webtolead_', | |
| '', | |
| $fieldInfo['name'] | |
| ); | |
| $type = 'string'; | |
| if ('company' !== $sObject) { | |
| $sugarFields[$sObject][$fieldName.'__'.$sObject] = [ | |
| 'type' => $type, | |
| 'label' => $sObject.'-'.$label, | |
| 'required' => $isRequired($fieldInfo, $sObject), | |
| 'group' => $sObject, | |
| 'optionLabel' => $label, | |
| ]; | |
| } else { | |
| $sugarFields[$sObject][$fieldName] = [ | |
| 'type' => $type, | |
| 'label' => $label, | |
| 'required' => $isRequired($fieldInfo, $sObject), | |
| ]; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| $this->cache->set('leadFields'.$cacheSuffix, $sugarFields[$sObject]); | |
| } | |
| } else { | |
| throw new ApiErrorException($this->authorizationError); | |
| } | |
| } | |
| } | |
| } catch (\Exception $e) { | |
| $this->logIntegrationError($e); | |
| if (!$silenceExceptions) { | |
| throw $e; | |
| } | |
| } | |
| return $sugarFields; | |
| } | |
| /** | |
| * @return mixed | |
| */ | |
| public function getFetchQuery($params) | |
| { | |
| return $params; | |
| } | |
| /** | |
| * @param array $params | |
| * @param array|null $query | |
| * | |
| * @return int|null | |
| */ | |
| public function getCompanies($params = [], $query = null, $executed = null) | |
| { | |
| $executed = null; | |
| $sugarObject = 'Accounts'; | |
| $params['max_results'] = 100; | |
| if (!isset($params['offset'])) { | |
| // First call | |
| $params['offset'] = 0; | |
| } | |
| $query = $params; | |
| try { | |
| if ($this->isAuthorized()) { | |
| $result = $this->getApiHelper()->getLeads($query, $sugarObject); | |
| $params['offset'] = $result['next_offset']; | |
| $executed += $this->amendLeadDataBeforeMauticPopulate($result, $sugarObject); | |
| if ( | |
| (isset($result['total_count']) && $result['total_count'] > $params['offset']) // Sugar 6 | |
| || (!isset($result['total_count']) && $params['offset'] > -1)) { // Sugar 7 | |
| $result = null; | |
| $executed += $this->getCompanies($params, null, $executed); | |
| } | |
| return $executed; | |
| } | |
| } catch (\Exception $e) { | |
| $this->logIntegrationError($e); | |
| } | |
| return $executed; | |
| } | |
| /** | |
| * @param array $params | |
| * | |
| * @return int|null | |
| * | |
| * @throws \Exception | |
| * To be modified | |
| */ | |
| public function pushLeadActivity($params = []) | |
| { | |
| $executed = null; | |
| $query = $this->getFetchQuery($params); | |
| $config = $this->mergeConfigToFeatureSettings([]); | |
| /** @var SugarApi $apiHelper */ | |
| $apiHelper = $this->getApiHelper(); | |
| $sugarObjects[] = 'Leads'; | |
| if (isset($config['objects']) && !empty($config['objects'])) { | |
| $sugarObjects = $config['objects']; | |
| } | |
| /** @var IntegrationEntityRepository $integrationEntityRepo */ | |
| $integrationEntityRepo = $this->em->getRepository(IntegrationEntity::class); | |
| $startDate = new \DateTime($query['start']); | |
| $endDate = new \DateTime($query['end']); | |
| $limit = 100; | |
| foreach ($sugarObjects as $object) { | |
| try { | |
| if ($this->isAuthorized()) { | |
| // Get first batch | |
| $start = 0; | |
| $sugarIds = $integrationEntityRepo->getIntegrationsEntityId( | |
| 'Sugarcrm', | |
| $object, | |
| 'lead', | |
| null, | |
| $startDate->format('Y-m-d H:i:s'), | |
| $endDate->format('Y-m-d H:i:s'), | |
| true, | |
| $start, | |
| $limit | |
| ); | |
| while (!empty($sugarIds)) { | |
| $executed += count($sugarIds); | |
| // Extract a list of lead Ids | |
| $leadIds = []; | |
| foreach ($sugarIds as $ids) { | |
| $leadIds[] = $ids['internal_entity_id']; | |
| } | |
| // Collect lead activity for this batch | |
| $leadActivity = $this->getLeadData( | |
| $startDate, | |
| $endDate, | |
| $leadIds | |
| ); | |
| $sugarLeadData = []; | |
| foreach ($sugarIds as $ids) { | |
| $leadId = $ids['internal_entity_id']; | |
| if (isset($leadActivity[$leadId])) { | |
| $sugarId = $ids['integration_entity_id']; | |
| $sugarLeadData[$sugarId] = $leadActivity[$leadId]; | |
| $sugarLeadData[$sugarId]['id'] = $ids['integration_entity_id']; | |
| $sugarLeadData[$sugarId]['leadId'] = $ids['internal_entity_id']; | |
| $sugarLeadData[$sugarId]['leadUrl'] = $this->router->generate( | |
| 'mautic_plugin_timeline_view', | |
| ['integration' => 'Sugarcrm', 'leadId' => $leadId], | |
| UrlGeneratorInterface::ABSOLUTE_URL | |
| ); | |
| } | |
| } | |
| if (!empty($sugarLeadData)) { | |
| $apiHelper->createLeadActivity($sugarLeadData, $object); | |
| } | |
| // Get the next batch | |
| $start += $limit; | |
| $sugarIds = $integrationEntityRepo->getIntegrationsEntityId( | |
| 'Sugarcrm', | |
| $object, | |
| 'lead', | |
| null, | |
| $startDate->format('Y-m-d H:i:s'), | |
| $endDate->format('Y-m-d H:i:s'), | |
| true, | |
| $start, | |
| $limit | |
| ); | |
| } | |
| } | |
| } catch (\Exception $e) { | |
| $this->logIntegrationError($e); | |
| } | |
| } | |
| return $executed; | |
| } | |
| /** | |
| * @param array $params | |
| * @param array|null $query | |
| * | |
| * @return int|null | |
| */ | |
| public function getLeads($params = [], $query = null, &$executed = null, $result = [], $object = 'Leads') | |
| { | |
| $params['max_results'] = 100; | |
| if (!isset($params['offset'])) { | |
| // First call | |
| $params['offset'] = 0; | |
| } | |
| $query = $params; | |
| try { | |
| if ($this->isAuthorized()) { | |
| if ('Activity' !== $object and 'company' !== $object) { | |
| $result = $this->getApiHelper()->getLeads($query, $object); | |
| $params['offset'] = $result['next_offset']; | |
| $executed += $this->amendLeadDataBeforeMauticPopulate($result, $object); | |
| if ( | |
| (isset($result['total_count']) && $result['total_count'] > $params['offset']) // Sugar 6 | |
| || (!isset($result['total_count']) && $params['offset'] > -1)) { // Sugar 7 | |
| $params['object'] = $object; | |
| $executed += $this->getLeads($params, null, $executed, [], $object); | |
| } | |
| } | |
| return $executed; | |
| } | |
| } catch (\Exception $e) { | |
| $this->logIntegrationError($e); | |
| } | |
| return $executed; | |
| } | |
| /** | |
| * @return string | |
| */ | |
| public function getErrorsFromResponse($response) | |
| { | |
| if ('6' == $this->keys['version']) { | |
| if (!empty($response['name'])) { | |
| return $response['description']; | |
| } else { | |
| return $this->translator->trans('mautic.integration.error.genericerror', [], 'flashes'); | |
| } | |
| } else { | |
| return parent::getErrorsFromResponse($response); | |
| } | |
| } | |
| public function getAuthenticationType(): string | |
| { | |
| return (isset($this->keys['version']) && '6' == $this->keys['version']) ? 'rest' : 'oauth2'; | |
| } | |
| public function getDataPriority(): bool | |
| { | |
| return true; | |
| } | |
| /** | |
| * @return array | |
| */ | |
| public function prepareRequest($url, $parameters, $method, $settings, $authType) | |
| { | |
| if ('oauth2' == $authType && empty($settings['authorize_session']) && isset($this->keys['access_token'])) { | |
| // Append the access token as the oauth-token header | |
| $headers = [ | |
| "oauth-token: {$this->keys['access_token']}", | |
| ]; | |
| return [$parameters, $headers]; | |
| } else { | |
| return parent::prepareRequest($url, $parameters, $method, $settings, $authType); | |
| } | |
| } | |
| /** | |
| * @return bool | |
| */ | |
| public function isAuthorized() | |
| { | |
| if (!$this->isConfigured()) { | |
| return false; | |
| } | |
| if (!isset($this->keys['version'])) { | |
| return false; | |
| } | |
| if ('6' == $this->keys['version']) { | |
| $loginParams = [ | |
| 'user_auth' => [ | |
| 'user_name' => $this->keys['username'], | |
| 'password' => md5($this->keys['password']), | |
| 'version' => '1', | |
| ], | |
| 'application_name' => 'Mautic', | |
| 'name_value_list' => [], | |
| 'method' => 'login', | |
| 'input_type' => 'JSON', | |
| 'response_type' => 'JSON', | |
| ]; | |
| $parameters = [ | |
| 'method' => 'login', | |
| 'input_type' => 'JSON', | |
| 'response_type' => 'JSON', | |
| 'rest_data' => json_encode($loginParams), | |
| ]; | |
| $settings['auth_type'] = 'rest'; | |
| $settings['authorize_session'] = true; | |
| $response = $this->makeRequest($this->getAccessTokenUrl(), $parameters, 'GET', $settings); | |
| unset($response['module'], $response['name_value_list']); | |
| $error = $this->extractAuthKeys($response, 'id'); | |
| $this->authorizationError = $error; | |
| return empty($error); | |
| } else { | |
| // SugarCRM 7 uses password grant type so login each time to ensure session is valid | |
| $this->authCallback(); | |
| return parent::isAuthorized(); | |
| } | |
| } | |
| public function prepareResponseForExtraction($data) | |
| { | |
| // Extract expiry and set expires for 7.x | |
| if (is_array($data) && isset($data['expires_in'])) { | |
| $data['expires'] = $data['expires_in'] + time(); | |
| } | |
| return $data; | |
| } | |
| /** | |
| * Amend mapped lead data before creating to Mautic. | |
| * | |
| * @param array $data | |
| * @param string $object | |
| */ | |
| public function amendLeadDataBeforeMauticPopulate($data, $object): int | |
| { | |
| $settings['feature_settings']['objects'][] = $object; | |
| $fields = array_keys($this->getAvailableLeadFields($settings)); | |
| $params['fields'] = implode(',', $fields); | |
| $count = 0; | |
| $entity = null; | |
| /** @var IntegrationEntityRepository $integrationEntityRepo */ | |
| $integrationEntityRepo = $this->em->getRepository(IntegrationEntity::class); | |
| $companyRepo = $this->em->getRepository(Company::class); | |
| $sugarRejectedLeads = []; | |
| if (isset($data['entry_list'])) { | |
| $SUGAR_VERSION = '6'; | |
| $RECORDS_LIST_NAME = 'entry_list'; | |
| $MODULE_FIELD_NAME = 'module_name'; | |
| } | |
| if (isset($data['records'])) { | |
| $SUGAR_VERSION = '7'; | |
| $RECORDS_LIST_NAME = 'records'; | |
| $MODULE_FIELD_NAME = '_module'; | |
| } | |
| if (isset($data[$RECORDS_LIST_NAME]) and 'Activity' !== $object) { | |
| // Get assigned user ids | |
| $assignedUserIds = []; | |
| $onwerEmailByAssignedUserId = []; | |
| if ('Leads' == $object || 'Contacts' == $object || 'Accounts' == $object) { | |
| foreach ($data[$RECORDS_LIST_NAME] as $record) { | |
| if ('6' == $SUGAR_VERSION) { | |
| foreach ($record['name_value_list'] as $item) { | |
| if ('assigned_user_id' == $item['name'] && $item['value'] && '' != $item['value']) { | |
| $assignedUserIds[] = $item['value']; | |
| } | |
| } | |
| } else { | |
| if (isset($record['assigned_user_id']) && '' != $record['assigned_user_id']) { | |
| $assignedUserIds[] = $record['assigned_user_id']; | |
| } | |
| } | |
| } | |
| } | |
| if (!empty($assignedUserIds)) { | |
| $assignedUserIds = array_unique($assignedUserIds); | |
| $onwerEmailByAssignedUserId = $this->getApiHelper()->getEmailBySugarUserId(['ids' => $assignedUserIds]); | |
| } | |
| // Get all leads emails | |
| $checkEmailsInSugar = []; | |
| if ('Leads' == $object) { | |
| if ('6' == $SUGAR_VERSION) { | |
| foreach ($data[$RECORDS_LIST_NAME] as $record) { | |
| foreach ($record['name_value_list'] as $item) { | |
| if ('email1' == $item['name'] && $item['value'] && '' != $item['value']) { | |
| $checkEmailsInSugar[] = $item['value']; | |
| } | |
| } | |
| } | |
| } else { | |
| if (isset($record['email1']) && '' != $record['email1']) { | |
| $checkEmailsInSugar[] = $record['email1']; | |
| } | |
| } | |
| } | |
| if (!empty($checkEmailsInSugar)) { | |
| $sugarLeads = $this->getApiHelper()->getLeads(['checkemail_contacts' => $checkEmailsInSugar, 'offset' => 0, 'max_results' => 1000], 'Contacts'); | |
| if (isset($sugarLeads[$RECORDS_LIST_NAME])) { | |
| foreach ($sugarLeads[$RECORDS_LIST_NAME] as $record) { | |
| $sugarLeadRecord = []; | |
| if ('6' == $SUGAR_VERSION) { | |
| foreach ($record['name_value_list'] as $item) { | |
| if ('email1' == $item['name'] && $item['value'] && '' != $item['value']) { | |
| $sugarRejectedLeads[] = $item['value']; | |
| } | |
| } | |
| } else { | |
| if (isset($record['email1']) && '' != $record['email1']) { | |
| $sugarRejectedLeads[] = $record['email1']; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| foreach ($data[$RECORDS_LIST_NAME] as $record) { | |
| $integrationEntities = []; | |
| $dataObject = []; | |
| if (isset($record[$MODULE_FIELD_NAME]) && 'Accounts' == $record[$MODULE_FIELD_NAME]) { | |
| $newName = ''; | |
| } else { | |
| $newName = '__'.$object; | |
| } | |
| if ('6' == $SUGAR_VERSION) { | |
| foreach ($record['name_value_list'] as $item) { | |
| if ('Activity' !== $object) { | |
| if ($this->checkIfSugarCrmMultiSelectString($item['value'])) { | |
| $convertedMultiSelectString = $this->convertSuiteCrmToMauticMultiSelect($item['value']); | |
| $dataObject[$item['name'].$newName] = $convertedMultiSelectString; | |
| } else { | |
| $dataObject[$item['name'].$newName] = $item['value']; | |
| } | |
| if ('date_entered' == $item['name']) { | |
| $itemDateEntered = new \DateTime($item['value']); | |
| } | |
| if ('date_modified' == $item['name']) { | |
| $itemDateModified = new \DateTime($item['value']); | |
| } | |
| } | |
| } | |
| } else { | |
| if ('Activity' !== $object) { | |
| if (isset($record['date_entered']) && '' != $record['date_entered']) { | |
| $itemDateEntered = new \DateTime($record['date_entered']); | |
| } | |
| if (isset($record['date_modified']) && '' != $record['date_modified']) { | |
| $itemDateEntered = new \DateTime($record['date_modified']); | |
| } | |
| foreach ($record as $k => $item) { | |
| $dataObject[$k.$newName] = $item; | |
| } | |
| } | |
| } | |
| if ('Leads' == $object && isset($dataObject['email1__Leads']) && null != $dataObject['email1__Leads'] | |
| && '' != $dataObject['email1__Leads'] && in_array($dataObject['email1__Leads'], $sugarRejectedLeads)) { | |
| continue; // Lead email is already in Sugar Contacts. Do not carry on | |
| } | |
| if (!empty($dataObject)) { | |
| if ('Leads' == $object or 'Contacts' == $object) { | |
| if (isset($dataObject['assigned_user_id__'.$object])) { | |
| $auid = $dataObject['assigned_user_id__'.$object]; | |
| if (isset($onwerEmailByAssignedUserId[$auid])) { | |
| $dataObject['owner_email'] = $onwerEmailByAssignedUserId[$auid]; | |
| } | |
| } | |
| $mauticObjectReference = 'lead'; | |
| $entity = $this->getMauticLead($dataObject, true, null, null, $object); | |
| $detachClass = Lead::class; | |
| $company = null; | |
| $this->fetchDncToMautic($entity, $data); | |
| if ($entity && isset($dataObject['account_id'.$newName]) && '' != trim($dataObject['account_id'.$newName])) { | |
| $integrationCompanyEntity = $integrationEntityRepo->findOneBy( | |
| [ | |
| 'integration' => 'Sugarcrm', | |
| 'integrationEntity' => 'Accounts', | |
| 'internalEntity' => 'company', | |
| 'integrationEntityId' => $dataObject['account_id'.$newName], | |
| ] | |
| ); | |
| if (isset($integrationCompanyEntity)) { | |
| $companyId = $integrationCompanyEntity->getInternalEntityId(); | |
| $company = $companyRepo->find($companyId); | |
| $this->companyModel->addLeadToCompany($company, $entity); | |
| $companyRepo->detachEntity($company); | |
| $this->em->detach($entity); | |
| } | |
| } | |
| } elseif ('Accounts' === $object) { | |
| $entity = $this->getMauticCompany($dataObject, $object); | |
| $detachClass = Company::class; | |
| $mauticObjectReference = 'company'; | |
| } else { | |
| $this->logIntegrationError( | |
| new \Exception( | |
| sprintf('Received an unexpected object without an internalObjectReference "%s"', $object) | |
| ) | |
| ); | |
| continue; | |
| } | |
| if ($entity) { | |
| $integrationId = $integrationEntityRepo->getIntegrationsEntityId( | |
| 'Sugarcrm', | |
| $object, | |
| $mauticObjectReference, | |
| $entity->getId() | |
| ); | |
| if (null == $integrationId) { | |
| $integrationEntity = new IntegrationEntity(); | |
| $integrationEntity->setDateAdded(new \DateTime()); | |
| $integrationEntity->setLastSyncDate(new \DateTime()); | |
| $integrationEntity->setIntegration('Sugarcrm'); | |
| $integrationEntity->setIntegrationEntity($object); | |
| $integrationEntity->setIntegrationEntityId($record['id']); | |
| $integrationEntity->setInternalEntity($mauticObjectReference); | |
| $integrationEntity->setInternalEntityId($entity->getId()); | |
| $integrationEntities[] = $integrationEntity; | |
| } else { | |
| $integrationEntity = $integrationEntityRepo->getEntity($integrationId[0]['id']); | |
| $integrationEntity->setLastSyncDate(new \DateTime()); | |
| $integrationEntities[] = $integrationEntity; | |
| } | |
| $this->em->detach($entity); | |
| $entityRepository = $this->em->getRepository($detachClass); | |
| $entityRepository->detachEntity($entity); | |
| unset($entity); | |
| } else { | |
| continue; | |
| } | |
| ++$count; | |
| } | |
| $this->em->getRepository(IntegrationEntity::class)->saveEntities($integrationEntities); | |
| $this->integrationEntityModel->getRepository()->detachEntities($integrationEntities); | |
| } | |
| unset($data); | |
| unset($integrationEntities); | |
| unset($dataObject); | |
| } | |
| return $count; | |
| } | |
| /** | |
| * @param \Mautic\PluginBundle\Integration\Form|FormBuilder $builder | |
| * @param array $data | |
| * @param string $formArea | |
| */ | |
| public function appendToForm(&$builder, $data, $formArea): void | |
| { | |
| if ('keys' == $formArea) { | |
| $builder->add('version', ButtonGroupType::class, [ | |
| 'choices' => [ | |
| '6.x/community' => '6', | |
| '7.x' => '7', | |
| ], | |
| 'label' => 'mautic.sugarcrm.form.version', | |
| 'constraints' => [ | |
| new NotBlank([ | |
| 'message' => 'mautic.core.value.required', | |
| ]), | |
| ], | |
| 'required' => true, | |
| ]); | |
| } | |
| if ('features' == $formArea) { | |
| $builder->add( | |
| 'updateOwner', | |
| ChoiceType::class, | |
| [ | |
| 'choices' => [ | |
| 'mautic.sugarcrm.updateOwner' => 'updateOwner', | |
| ], | |
| 'expanded' => true, | |
| 'multiple' => true, | |
| 'label' => 'mautic.sugarcrm.form.updateOwner', | |
| 'label_attr' => ['class' => 'control-label'], | |
| 'placeholder' => false, | |
| 'required' => false, | |
| 'attr' => [ | |
| 'onclick' => 'Mautic.postForm(mQuery(\'form[name="integration_details"]\'),\'\');', | |
| ], | |
| ] | |
| ); | |
| $builder->add( | |
| 'updateDnc', | |
| ChoiceType::class, | |
| [ | |
| 'choices' => [ | |
| 'mautic.sugarcrm.updateDnc' => 'updateDnc', | |
| ], | |
| 'expanded' => true, | |
| 'multiple' => true, | |
| 'label' => 'mautic.sugarcrm.form.updateDnc', | |
| 'label_attr' => ['class' => 'control-label'], | |
| 'placeholder' => false, | |
| 'required' => false, | |
| 'attr' => [ | |
| 'onclick' => 'Mautic.postForm(mQuery(\'form[name="integration_details"]\'),\'\');', | |
| ], | |
| ] | |
| ); | |
| $builder->add( | |
| 'updateBlanks', | |
| ChoiceType::class, | |
| [ | |
| 'choices' => [ | |
| 'mautic.integrations.blanks' => 'updateBlanks', | |
| ], | |
| 'expanded' => true, | |
| 'multiple' => true, | |
| 'label' => 'mautic.integrations.form.blanks', | |
| 'label_attr' => ['class' => 'control-label'], | |
| 'placeholder' => false, | |
| 'required' => false, | |
| ] | |
| ); | |
| $builder->add( | |
| 'objects', | |
| ChoiceType::class, | |
| [ | |
| 'choices' => [ | |
| 'mautic.sugarcrm.object.lead' => 'Leads', | |
| 'mautic.sugarcrm.object.contact' => 'Contacts', | |
| 'mautic.sugarcrm.object.company' => 'company', | |
| ], | |
| 'expanded' => true, | |
| 'multiple' => true, | |
| 'label' => 'mautic.sugarcrm.form.objects_to_pull_from', | |
| 'label_attr' => ['class' => ''], | |
| 'placeholder' => false, | |
| 'required' => false, | |
| ] | |
| ); | |
| $builder->add( | |
| 'activityEvents', | |
| ChoiceType::class, | |
| [ | |
| 'choices' => array_flip($this->leadModel->getEngagementTypes()), // Choice type expects labels as keys | |
| 'label' => 'mautic.salesforce.form.activity_included_events', | |
| 'label_attr' => [ | |
| 'class' => 'control-label', | |
| 'data-toggle' => 'tooltip', | |
| 'title' => $this->translator->trans('mautic.salesforce.form.activity.events.tooltip'), | |
| ], | |
| 'multiple' => true, | |
| 'empty_data' => ['point.gained', 'form.submitted', 'email.read'], // BC with pre 2.11.0 | |
| 'required' => false, | |
| ] | |
| ); | |
| } | |
| } | |
| /** | |
| * @param Lead $lead | |
| * @param array $config | |
| * | |
| * @return array|bool | |
| */ | |
| public function pushLead($lead, $config = []) | |
| { | |
| $config = $this->mergeConfigToFeatureSettings($config); | |
| if (empty($config['leadFields'])) { | |
| return []; | |
| } | |
| $object = 'Leads'; // Sugar objects, default is Leads | |
| // Check if lead has alredy been synched | |
| /** @var IntegrationEntityRepository $integrationEntityRepo */ | |
| $integrationEntityRepo = $this->em->getRepository(IntegrationEntity::class); | |
| // Check if it is a sugar CRM alredy synched lead | |
| $integrationId = $integrationEntityRepo->getIntegrationsEntityId('Sugarcrm', $object, 'lead', $lead->getId()); | |
| if (empty($integrationId)) { | |
| // Check if it is a sugar CRM alredy synched lead | |
| $integrationId = $integrationEntityRepo->getIntegrationsEntityId('Sugarcrm', 'Contacts', 'lead', $lead->getId()); | |
| if (!empty($integrationId)) { | |
| $object = 'Contacts'; | |
| } | |
| } | |
| if (!empty($integrationId)) { | |
| $integrationEntity = $integrationEntityRepo->getEntity($integrationId[0]['id']); | |
| $lastSyncDate = $integrationEntity->getLastSyncDate(); | |
| $addedSyncDate = $integrationEntity->getDateAdded(); | |
| if ($addedSyncDate > $lastSyncDate) { | |
| $lastSyncDate = $addedSyncDate; | |
| } | |
| $leadDateModified = $lead->getDateModified(); | |
| $leadDateAdded = $lead->getDateAdded(); | |
| $leadLastDate = $leadDateModified; | |
| if ($leadDateAdded > $leadDateModified) { | |
| $leadLastDate = $leadDateAdded; | |
| } | |
| if ($lastSyncDate >= $leadLastDate) { | |
| return false; | |
| } // Do not push lead if it was already synched | |
| } | |
| $fieldsToUpdateInSugar = isset($config['update_mautic']) ? array_keys($config['update_mautic'], 0) : []; | |
| $leadSugarFieldsToCreate = $this->cleanSugarData($config, array_keys($config['leadFields']), $object); | |
| $fieldsToUpdateInLeadsSugar = $this->cleanSugarData($config, $fieldsToUpdateInSugar, $object); | |
| $leadFields = array_intersect_key($leadSugarFieldsToCreate, $fieldsToUpdateInLeadsSugar); | |
| $mappedData[$object] = $this->populateLeadData($lead, ['leadFields' => $leadFields, 'object' => $object]); | |
| $this->amendLeadDataBeforePush($mappedData[$object]); | |
| if (empty($mappedData[$object])) { | |
| return false; | |
| } | |
| if (!empty($integrationId)) { | |
| $integrationEntity = $integrationEntityRepo->findOneBy( | |
| [ | |
| 'integration' => 'Sugarcrm', | |
| 'integrationEntity' => $object, | |
| 'internalEntity' => 'lead', | |
| 'internalEntityId' => $lead->getId(), | |
| ] | |
| ); | |
| $mappedData[$object]['id'] = $integrationEntity->getIntegrationEntityId(); | |
| } | |
| try { | |
| if ($this->isAuthorized()) { | |
| if (!is_null($lead->getOwner())) { | |
| $sugarOwnerId = $this->getApiHelper()->getIdBySugarEmail(['emails' => [$lead->getOwner()->getEmail()]]); | |
| if (!empty($sugarOwnerId)) { | |
| $mappedData[$object]['assigned_user_id'] = array_values($sugarOwnerId)[0]; | |
| } | |
| } | |
| $createdLeadData = $this->getApiHelper()->createLead($mappedData[$object], $lead); | |
| if (isset($createdLeadData['id'])) { | |
| if (empty($integrationId)) { | |
| $integrationEntity = new IntegrationEntity(); | |
| $integrationEntity->setDateAdded(new \DateTime()); | |
| $integrationEntity->setLastSyncDate(new \DateTime()); | |
| $integrationEntity->setIntegration('Sugarcrm'); | |
| $integrationEntity->setIntegrationEntity($object); | |
| $integrationEntity->setIntegrationEntityId($createdLeadData['id']); | |
| $integrationEntity->setInternalEntity('lead'); | |
| $integrationEntity->setInternalEntityId($lead->getId()); | |
| } else { | |
| $integrationEntity = $integrationEntityRepo->getEntity($integrationId[0]['id']); | |
| } | |
| $integrationEntity->setLastSyncDate(new \DateTime()); | |
| $this->em->persist($integrationEntity); | |
| $this->em->flush($integrationEntity); | |
| } | |
| return true; | |
| } | |
| } catch (\Exception $e) { | |
| $this->logIntegrationError($e); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Return key recognized by integration. | |
| */ | |
| public function convertLeadFieldKey(string $key, $field): string | |
| { | |
| $search = []; | |
| foreach ($this->objects as $object) { | |
| $search[] = '__'.$object; | |
| } | |
| return str_replace($search, '', $key); | |
| } | |
| /** | |
| * @param array $fields | |
| * @param array $keys | |
| * @param string $object | |
| */ | |
| public function cleanSugarData($fields, $keys, $object): array | |
| { | |
| $leadFields = []; | |
| foreach ($keys as $key) { | |
| if (strstr($key, '__'.$object)) { | |
| $newKey = str_replace('__'.$object, '', $key); | |
| // $leadFields[$object][$newKey] = $fields['leadFields'][$key]; | |
| $leadFields[$newKey] = $fields['leadFields'][$key]; | |
| } | |
| } | |
| return $leadFields; | |
| } | |
| /** | |
| * @param array $params | |
| * | |
| * @return mixed[] | |
| */ | |
| public function pushLeads($params = []): array | |
| { | |
| [$fromDate, $toDate] = $this->getSyncTimeframeDates($params); | |
| $limit = $params['limit']; | |
| $config = $this->mergeConfigToFeatureSettings(); | |
| $integrationEntityRepo = $this->em->getRepository(IntegrationEntity::class); | |
| $mauticData = $leadsToUpdate = $fields = []; | |
| $fieldsToUpdateInSugar = isset($config['update_mautic']) ? array_keys($config['update_mautic'], 0) : []; | |
| $leadFields = $config['leadFields']; | |
| if (!empty($leadFields)) { | |
| if ($keys = array_keys($leadFields, 'mauticContactTimelineLink')) { | |
| foreach ($keys as $key) { | |
| unset($leadFields[$key]); | |
| } | |
| } | |
| if ($keys = array_keys($leadFields, 'mauticContactIsContactableByEmail')) { | |
| foreach ($keys as $key) { | |
| unset($leadFields[$key]); | |
| } | |
| } | |
| $fields = implode(', l.', $leadFields); | |
| $fields = 'l.owner_id,l.'.$fields; | |
| $result = 0; | |
| // Leads fields | |
| $leadSugarFieldsToCreate = $this->cleanSugarData($config, array_keys($config['leadFields']), 'Leads'); | |
| $fieldsToUpdateInLeadsSugar = $this->cleanSugarData($config, $fieldsToUpdateInSugar, 'Leads'); | |
| $leadSugarFields = array_intersect_key($leadSugarFieldsToCreate, $fieldsToUpdateInLeadsSugar); | |
| // Contacts fields | |
| $contactSugarFields = $this->cleanSugarData($config, array_keys($config['leadFields']), 'Contacts'); | |
| $fieldsToUpdateInContactsSugar = $this->cleanSugarData($config, $fieldsToUpdateInSugar, 'Contacts'); | |
| $contactSugarFields = array_intersect_key($contactSugarFields, $fieldsToUpdateInContactsSugar); | |
| $availableFields = $this->getAvailableLeadFields(['feature_settings' => ['objects' => ['Leads', 'Contacts']]]); | |
| // update lead/contact records | |
| $leadsToUpdate = $integrationEntityRepo->findLeadsToUpdate($this->getName(), 'lead', $fields, $limit, $fromDate, $toDate, ['Contacts', 'Leads']); | |
| } | |
| $checkEmailsInSugar = []; | |
| $deletedSugarLeads = []; | |
| foreach ($leadsToUpdate as $object => $records) { | |
| foreach ($records as $lead) { | |
| if (isset($lead['email']) && !empty($lead['email'])) { | |
| $lead = $this->getCompoundMauticFields($lead); | |
| $checkEmailsInSugar[$object][mb_strtolower($lead['email'])] = $lead; | |
| } | |
| } | |
| } | |
| // Only get the max limit | |
| if ($limit) { | |
| $limit -= count($leadsToUpdate); | |
| } | |
| // create lead records | |
| if (null === $limit || $limit && !empty($fields)) { | |
| $leadsToCreate = $integrationEntityRepo->findLeadsToCreate('Sugarcrm', $fields, $limit, $fromDate, $toDate); | |
| foreach ($leadsToCreate as $lead) { | |
| if (isset($lead['email'])) { | |
| $lead = $this->getCompoundMauticFields($lead); | |
| $checkEmailsInSugar['Leads'][mb_strtolower($lead['email'])] = $lead; | |
| } | |
| } | |
| } | |
| foreach ($checkEmailsInSugar as $object => $checkObjectEmailsInSugar) { | |
| [$checkEmailsUpdatedInSugar, $deletedRedords] = $this->getObjectDataToUpdate($checkObjectEmailsInSugar, $mauticData, $availableFields, $contactSugarFields, $leadSugarFields, $object); | |
| // recheck synced records that might have been deleted in Sugar (deleted records don't come back in the query) | |
| foreach ($checkEmailsUpdatedInSugar as $key => $deletedSugarRedords) { | |
| if (isset($deletedSugarRedords['integration_entity_id']) && !empty($deletedSugarRedords['integration_entity_id'])) { | |
| $deletedSugarLeads[$key] = $deletedSugarRedords['integration_entity_id']; | |
| } | |
| unset($checkEmailsUpdatedInSugar[$key]); | |
| } | |
| } | |
| if (!empty($checkEmailsUpdatedInSugar)) { | |
| $checkEmailsInSugar = array_merge($checkEmailsUpdatedInSugar, $checkEmailsInSugar); | |
| } | |
| // If there are any deleted, mark it as so to prevent them from being queried over and over or recreated | |
| if ($deletedSugarLeads) { | |
| $integrationEntityRepo->markAsDeleted($deletedSugarLeads, $this->getName(), 'lead'); | |
| } | |
| // Create any left over | |
| if ($checkEmailsInSugar && isset($checkEmailsInSugar['Leads'])) { | |
| [$checkEmailsInSugar, $deletedSugarLeads] = $this->getObjectDataToUpdate($checkEmailsInSugar['Leads'], $mauticData, $availableFields, $contactSugarFields, $leadSugarFields, 'Leads'); | |
| $ownerAssignedUserIdByEmail = null; | |
| foreach ($checkEmailsInSugar as $lead) { | |
| if (isset($lead['email'])) { | |
| $lead['owner_email'] = $this->getOwnerEmail($lead); | |
| if ($lead['owner_email']) { | |
| $ownerAssignedUserIdByEmail = $this->getApiHelper()->getIdBySugarEmail(['emails' => [$lead['owner_email']]]); | |
| } | |
| $this->buildCompositeBody( | |
| $mauticData, | |
| $availableFields, | |
| $leadSugarFieldsToCreate, // use all matched fields when creating new records in Sugar | |
| 'Leads', | |
| $lead, | |
| $ownerAssignedUserIdByEmail | |
| ); | |
| } | |
| } | |
| } | |
| /** @var SugarcrmApi $apiHelper */ | |
| $apiHelper = $this->getApiHelper(); | |
| if (!empty($mauticData)) { | |
| $result = $apiHelper->syncLeadsToSugar($mauticData); | |
| } | |
| return $this->processCompositeResponse($result); | |
| } | |
| /** | |
| * Update body to sync. | |
| */ | |
| private function pushDncToSugar(array $lead, array &$body): void | |
| { | |
| $features = $this->settings->getFeatureSettings(); | |
| // update DNC sync disabled | |
| if (empty($features['updateDnc'])) { | |
| return; | |
| } | |
| $leadEntity = $this->leadModel->getEntity($lead['internal_entity_id']); | |
| /** @var \Mautic\LeadBundle\Entity\DoNotContact[] $dncEntries */ | |
| $dncEntries = $this->doNotContactModel->getDncRepo()->getEntriesByLeadAndChannel($leadEntity, 'email'); | |
| $sugarDncKeys = array_combine(array_values($this->sugarDncKeys), $this->sugarDncKeys); | |
| foreach ($dncEntries as $dncEntry) { | |
| if (empty($sugarDncKeys)) { | |
| continue; | |
| } | |
| // If DNC exists set to 1 | |
| switch ($dncEntry->getReason()) { | |
| case 1: | |
| case 3: | |
| $body[] = ['name' => 'email_opt_out', 'value' => 1]; | |
| unset($sugarDncKeys['email_opt_out']); | |
| break; | |
| case 2: | |
| $body[] = ['name' => 'invalid_email', 'value' => 1]; | |
| unset($sugarDncKeys['invalid_email']); | |
| break; | |
| } | |
| } | |
| // uncheck | |
| // If DNC doesn't exist set to 1 | |
| foreach ($sugarDncKeys as $sugarDncKey) { | |
| $body[] = ['name' => $sugarDncKey, 'value' => 0]; | |
| } | |
| } | |
| private function fetchDncToMautic(Lead $lead = null, array $data): void | |
| { | |
| if (is_null($lead)) { | |
| return; | |
| } | |
| $features = $this->settings->getFeatureSettings(); | |
| if (empty($features['updateDnc'])) { | |
| return; | |
| } | |
| // try find opt_out value for lead | |
| $isContactable = true; | |
| foreach ($data['relationship_list'] as $relationshipList) { | |
| foreach ($relationshipList['link_list'] as $links) { | |
| if ('email_addresses' == $links['name']) { | |
| foreach ($links['records'] as $records) { | |
| if (!empty($records['link_value']['email_address']['value']) && $records['link_value']['email_address']['value'] == $lead->getEmail() && !empty($records['link_value']['opt_out']['value'])) { | |
| $isContactable = false; | |
| break 3; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| $reason = \Mautic\LeadBundle\Entity\DoNotContact::UNSUBSCRIBED; | |
| if (!$isContactable) { | |
| $this->doNotContactModel->addDncForContact($lead->getId(), 'email', $reason, $this->getName()); | |
| } else { | |
| $this->doNotContactModel->removeDncForContact($lead->getId(), 'email', true, $reason); | |
| } | |
| } | |
| /** | |
| * @param string $object | |
| * | |
| * @return array The first element is made up of records that exist in Mautic, but which no longer have a match in CRM. | |
| * We therefore assume that they've been deleted in CRM and will mark them as deleted in the pushLeads function (~line 1320). | |
| * The second element contains Ids of records that were explicitly marked as deleted in CRM. ATM, nothing is done with this data. | |
| */ | |
| public function getObjectDataToUpdate($checkEmailsInSugar, &$mauticData, $availableFields, $contactSugarFields, $leadSugarFields, $object = 'Leads'): array | |
| { | |
| $config = $this->mergeConfigToFeatureSettings([]); | |
| $queryParam = ('Leads' == $object) ? 'checkemail' : 'checkemail_contacts'; | |
| $sugarLead = $this->getApiHelper()->getLeads([$queryParam => array_keys($checkEmailsInSugar), 'offset' => 0, 'max_results' => 1000], $object); | |
| $deletedSugarLeads = $sugarLeadRecords = []; | |
| if (isset($sugarLead['entry_list'])) { | |
| // Sugar 6.X | |
| $sugarLeadRecords = []; | |
| foreach ($sugarLead['entry_list'] as $k => $record) { | |
| $sugarLeadRecord = []; | |
| $sugarLeadRecord['id'] = $record['id']; | |
| $sugarLeadRecord['module_name'] = $record['module_name']; | |
| foreach ($record['name_value_list'] as $item) { | |
| $sugarLeadRecord[$item['name']] = $item['value']; | |
| } | |
| if (!isset($sugarLeadRecord['email1'])) { | |
| foreach ($sugarLead['relationship_list'][$k]['link_list'] as $links) { | |
| if ('email_addresses' == $links['name']) { | |
| foreach ($links['records'] as $records) { | |
| foreach ($records as $contactEmails) { | |
| foreach ($contactEmails as $anyAddress) { | |
| if ('email_address' == $anyAddress['name'] && !empty($anyAddress['value'])) { | |
| $sugarLeadRecord['email1'] = $anyAddress['value']; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| $sugarLeadRecords[] = $sugarLeadRecord; | |
| } | |
| } elseif (isset($sugarLead['records'])) { // Sugar 7 | |
| $sugarLeadRecords = $sugarLead['records']; | |
| } | |
| foreach ($sugarLeadRecords as $sugarLeadRecord) { | |
| if (isset($sugarLeadRecord) && $sugarLeadRecord) { | |
| $email = $sugarLeadRecord['email1']; | |
| $key = mb_strtolower($email); | |
| $leadOwnerEmails = []; | |
| if (!empty($checkEmailsInSugar)) { | |
| foreach ($checkEmailsInSugar as $emailKey => $mauticRecord) { | |
| if ($key == $emailKey) { | |
| $isConverted = (isset($sugarLeadRecord['contact_id']) | |
| && null != $sugarLeadRecord['contact_id'] | |
| && '' != $sugarLeadRecord['contact_id']); | |
| $sugarIdMapping[$checkEmailsInSugar[$key]['internal_entity_id']] = ($isConverted) ? $sugarLeadRecord['contact_id'] : $sugarLeadRecord['id']; | |
| $lead['owner_email'] = $this->getOwnerEmail($mauticRecord); | |
| if ($lead['owner_email']) { | |
| $leadOwnerEmails[] = $lead['owner_email']; | |
| } | |
| $ownerAssignedUserIdByEmail = $this->getApiHelper()->getIdBySugarEmail(['emails' => array_unique($leadOwnerEmails)]); | |
| if (empty($sugarLeadRecord['deleted']) || 0 == $sugarLeadRecord['deleted']) { | |
| $sugarFieldMappings = $this->prepareFieldsForPush($availableFields); | |
| if (isset($sugarFieldMappings['Contacts']) && !empty($sugarFieldMappings['Contacts'])) { | |
| $contactSugarFields = $this->getBlankFieldsToUpdate($contactSugarFields, $sugarLeadRecord, $sugarFieldMappings['Contacts'], $config); | |
| } | |
| if (isset($sugarFieldMappings['Leads']) && !empty($sugarFieldMappings['Leads'])) { | |
| $leadSugarFields = $this->getBlankFieldsToUpdate($leadSugarFields, $sugarLeadRecord, $sugarFieldMappings['Leads'], $config); | |
| } | |
| $this->buildCompositeBody( | |
| $mauticData, | |
| $availableFields, | |
| $isConverted || ('Contacts' == $object) ? $contactSugarFields : $leadSugarFields, | |
| $isConverted || ('Contacts' == $object) ? 'Contacts' : 'Leads', | |
| $checkEmailsInSugar[$key], | |
| $ownerAssignedUserIdByEmail, | |
| $isConverted ? $sugarLeadRecord['contact_id'] : $sugarLeadRecord['id'] | |
| ); | |
| } else { | |
| // @todo - Should return also deleted contacts from Sugar | |
| $deletedSugarLeads[] = $sugarLeadRecord['id']; | |
| if (!empty($sugarLeadRecord['contact_id']) || '' != $sugarLeadRecord['contact_id']) { | |
| $deletedSugarLeads[] = $sugarLeadRecord['contact_id']; | |
| } | |
| } | |
| unset($checkEmailsInSugar[$key]); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return [$checkEmailsInSugar, $deletedSugarLeads]; | |
| } | |
| /** | |
| * @return array | |
| */ | |
| public function getSugarLeadId($lead) | |
| { | |
| /** @var IntegrationEntityRepository $integrationEntityRepo */ | |
| $integrationEntityRepo = $this->em->getRepository(IntegrationEntity::class); | |
| // try searching for lead as this has been changed before in updated done to the plugin | |
| $result = $integrationEntityRepo->getIntegrationsEntityId('Sugarcrm', null, 'lead', $lead->getId()); | |
| return $result; | |
| } | |
| /** | |
| * @param array $lead | |
| */ | |
| protected function getOwnerEmail($lead) | |
| { | |
| if (isset($lead['owner_id']) && !empty($lead['owner_id'])) { | |
| /** @var \Mautic\UserBundle\Entity\User $user */ | |
| $user = $this->userModel->getEntity($lead['owner_id']); | |
| return $user->getEmail(); | |
| } | |
| return null; | |
| } | |
| protected function buildCompositeBody(&$mauticData, $availableFields, $fieldsToUpdateInSugarUpdate, $object, $lead, $onwerAssignedUserIdByEmail = null, $objectId = null) | |
| { | |
| $body = []; | |
| if (isset($lead['email']) && !empty($lead['email'])) { | |
| // update and create (one query) every 200 records | |
| foreach ($fieldsToUpdateInSugarUpdate as $sugarField => $mauticField) { | |
| $required = !empty($availableFields[$object][$sugarField.'__'.$object]['required']); | |
| if (isset($lead[$mauticField])) { | |
| if (str_contains($lead[$mauticField], '|')) { | |
| // Transform Mautic Multi Select into SugarCRM/SuiteCRM Multi Select format | |
| $value = $this->convertMauticToSuiteCrmMultiSelect($lead[$mauticField]); | |
| } else { | |
| $value = $lead[$mauticField]; | |
| } | |
| $body[] = ['name' => $sugarField, 'value' => $value]; | |
| } elseif ($required) { | |
| $value = $this->translator->trans('mautic.integration.form.lead.unknown'); | |
| $body[] = ['name' => $sugarField, 'value' => $value]; | |
| } | |
| } | |
| if (!empty($body)) { | |
| $id = $lead['internal_entity_id'].'-'.$object.(!empty($lead['id']) ? '-'.$lead['id'] : ''); | |
| $body[] = ['name' => 'reference_id', 'value' => $id]; | |
| if ($objectId) { | |
| $body[] = ['name' => 'id', 'value' => $objectId]; | |
| } | |
| if (isset($onwerAssignedUserIdByEmail) && isset($lead['owner_email']) && isset($onwerAssignedUserIdByEmail[$lead['owner_email']])) { | |
| $body[] = ['name' => 'assigned_user_id', 'value' => $onwerAssignedUserIdByEmail[$lead['owner_email']]]; | |
| } | |
| // pushd DNC to Sugar CRM | |
| $this->pushDncToSugar($lead, $body); | |
| $mauticData[$object][] = $body; | |
| } | |
| } | |
| } | |
| /** | |
| * @param array $response | |
| */ | |
| protected function processCompositeResponse($response): array | |
| { | |
| $created = 0; | |
| $errored = 0; | |
| $updated = 0; | |
| $object = 'Lead'; | |
| $persistEntities = []; | |
| if (is_array($response)) { | |
| foreach ($response as $item) { | |
| $contactId = $integrationEntityId = null; | |
| if (!empty($item['reference_id'])) { | |
| $reference = explode('-', $item['reference_id']); | |
| if (3 === count($reference)) { | |
| [$contactId, $object, $integrationEntityId] = $reference; | |
| } else { | |
| [$contactId, $object] = $reference; | |
| } | |
| } | |
| if (isset($item['ko']) && $item['ko']) { | |
| $this->logIntegrationError(new \Exception($item['error'])); | |
| if ($integrationEntityId) { | |
| $integrationEntity = $this->em->getReference(IntegrationEntity::class, $integrationEntityId); | |
| $integrationEntity->setLastSyncDate(new \DateTime()); | |
| $persistEntities[] = $integrationEntity; | |
| } elseif ($contactId) { | |
| $integrationEntity = new IntegrationEntity(); | |
| $integrationEntity->setDateAdded(new \DateTime()); | |
| $integrationEntity->setLastSyncDate(new \DateTime()); | |
| $integrationEntity->setIntegration($this->getName()); | |
| $integrationEntity->setIntegrationEntity($object); | |
| $integrationEntity->setInternalEntity('lead-error'); | |
| $integrationEntity->setInternal(['error' => $item['error']]); | |
| $integrationEntity->setInternalEntityId($contactId); | |
| $persistEntities[] = $integrationEntity; | |
| ++$errored; | |
| } | |
| } elseif (!$item['ko']) { | |
| if ($item['new']) { | |
| // New object created | |
| $integrationEntity = new IntegrationEntity(); | |
| $integrationEntity->setDateAdded(new \DateTime()); | |
| $integrationEntity->setLastSyncDate(new \DateTime()); | |
| $integrationEntity->setIntegration($this->getName()); | |
| $integrationEntity->setIntegrationEntity($object); | |
| $integrationEntity->setIntegrationEntityId($item['id']); | |
| $integrationEntity->setInternalEntity('lead'); | |
| $integrationEntity->setInternalEntityId($contactId); | |
| $persistEntities[] = $integrationEntity; | |
| ++$created; | |
| } else { | |
| // Record was updated | |
| if ($integrationEntityId) { | |
| $integrationEntity = $this->em->getReference(IntegrationEntity::class, $integrationEntityId); | |
| $integrationEntity->setLastSyncDate(new \DateTime()); | |
| } else { | |
| // Found in Sugarcrm so create a new record for it | |
| $integrationEntity = new IntegrationEntity(); | |
| $integrationEntity->setDateAdded(new \DateTime()); | |
| $integrationEntity->setLastSyncDate(new \DateTime()); | |
| $integrationEntity->setIntegration($this->getName()); | |
| $integrationEntity->setIntegrationEntity($object); | |
| $integrationEntity->setIntegrationEntityId($item['id']); | |
| $integrationEntity->setInternalEntity('lead'); | |
| $integrationEntity->setInternalEntityId($contactId); | |
| } | |
| $persistEntities[] = $integrationEntity; | |
| ++$updated; | |
| } | |
| } else { | |
| $error = 'Unknown status code '.$item['httpStatusCode']; | |
| $this->logIntegrationError(new \Exception($error.' ('.$item['reference_id'].')')); | |
| } | |
| } | |
| if ($persistEntities) { | |
| $this->em->getRepository(IntegrationEntity::class)->saveEntities($persistEntities); | |
| $this->integrationEntityModel->getRepository()->detachEntities($persistEntities); | |
| unset($persistEntities); | |
| } | |
| } | |
| return [$updated, $created, $errored]; | |
| } | |
| /** | |
| * @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; | |
| } else { | |
| $fields = array_flip($fieldsToUpdate); | |
| } | |
| return $this->prepareFieldsForSync($fields, $fieldsToUpdate, $objects); | |
| } | |
| /** | |
| * @param array $fields | |
| * @param array $keys | |
| * @param mixed $object | |
| * | |
| * @return array | |
| */ | |
| public function prepareFieldsForSync($fields, $keys, $object = null) | |
| { | |
| $leadFields = []; | |
| if (null === $object) { | |
| $object = 'Lead'; | |
| } | |
| $objects = (!is_array($object)) ? [$object] : $object; | |
| if (is_string($object) && 'Accounts' === $object) { | |
| return $fields['companyFields'] ?? $fields; | |
| } | |
| if (isset($fields['leadFields'])) { | |
| $fields = $fields['leadFields']; | |
| $keys = array_keys($fields); | |
| } | |
| foreach ($objects as $obj) { | |
| if (!isset($leadFields[$obj])) { | |
| $leadFields[$obj] = []; | |
| } | |
| foreach ($keys as $key) { | |
| if (strpos($key, '__'.$obj)) { | |
| $newKey = str_replace('__'.$obj, '', $key); | |
| if ('Id' === $newKey) { | |
| // Don't map Id for push | |
| continue; | |
| } | |
| $leadFields[$obj][$newKey] = $fields[$key]; | |
| } | |
| } | |
| } | |
| return (is_array($object)) ? $leadFields : $leadFields[$object]; | |
| } | |
| /** | |
| * @param string $priorityObject | |
| * | |
| * @return mixed | |
| */ | |
| protected function getPriorityFieldsForMautic($config, $object = null, $priorityObject = 'mautic') | |
| { | |
| $fields = parent::getPriorityFieldsForMautic($config, $object, $priorityObject); | |
| return ($object && isset($fields[$object])) ? $fields[$object] : $fields; | |
| } | |
| protected function prepareFieldsForPush($fields): array | |
| { | |
| $fieldMappings = []; | |
| $required = []; | |
| $config = $this->mergeConfigToFeatureSettings(); | |
| $contactFields = $this->cleanSugarData($config, array_keys($config['leadFields']), 'Contacts'); | |
| $leadFields = $this->cleanSugarData($config, array_keys($config['leadFields']), 'Leads'); | |
| if (!empty($contactFields)) { | |
| foreach ($fields['Contacts'] as $key => $field) { | |
| if ($field['required']) { | |
| $required[$key] = $field; | |
| } | |
| } | |
| $fieldMappings['Contacts']['required'] = [ | |
| 'fields' => $required, | |
| ]; | |
| $fieldMappings['Contacts']['create'] = $contactFields; | |
| } | |
| if (!empty($leadFields)) { | |
| foreach ($fields['Leads'] as $key => $field) { | |
| if ($field['required']) { | |
| $required[$key] = $field; | |
| } | |
| } | |
| $fieldMappings['Leads']['required'] = [ | |
| 'fields' => $required, | |
| ]; | |
| $fieldMappings['Leads']['create'] = $leadFields; | |
| } | |
| return $fieldMappings; | |
| } | |
| /** | |
| * Converts Mautic Multi-Select String into the format used to store Multi-Select values used by SuiteCRM / SugarCRM 6.x. | |
| */ | |
| public function convertMauticToSuiteCrmMultiSelect($mauticMultiSelectStringToConvert): string | |
| { | |
| // $mauticMultiSelectStringToConvert = 'test|enhancedapi|dataservices'; | |
| $multiSelectArrayValues = explode('|', $mauticMultiSelectStringToConvert); | |
| $convertedSugarCrmMultiSelectString = ''; | |
| foreach ($multiSelectArrayValues as $item) { | |
| $convertedSugarCrmMultiSelectString = $convertedSugarCrmMultiSelectString.'^'.$item.'^,'; | |
| } | |
| return substr($convertedSugarCrmMultiSelectString, 0, -1); | |
| } | |
| /** | |
| * Checks if a string contains SuiteCRM / SugarCRM 6.x Multi-Select values. | |
| * | |
| * @param string $stringToCheck | |
| */ | |
| public function checkIfSugarCrmMultiSelectString($stringToCheck): bool | |
| { | |
| // Regular Express to check SugarCRM/SuiteCRM Multi-Select format below | |
| // example format: '^choice1^,^choice2^,^choice_3^' | |
| $regex = '/(\^)(?:([A-Za-z0-9\-\_]+))(\^)/'; | |
| if (preg_match($regex, $stringToCheck)) { | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Converts a SuiteCRM / SugarCRM 6.x Multi-Select String into the format used to store Multi-Select values used by Mautic. | |
| */ | |
| public function convertSuiteCrmToMauticMultiSelect($suiteCrmMultiSelectStringToConvert): string | |
| { | |
| // Mautic Multi Select format - 'choice1|choice2|choice_3' | |
| $regexString = '/(\^)(?:([A-Za-z0-9\-\_]+))(\^)/'; | |
| preg_match_all($regexString, $suiteCrmMultiSelectStringToConvert, $matches, PREG_SET_ORDER, 0); | |
| $convertedString = ''; | |
| foreach ($matches as $innerArray) { | |
| $convertedString = $convertedString.$innerArray[2].'|'; | |
| } | |
| return substr($convertedString, 0, -1); | |
| } | |
| } | |