1 | <?php |
||||||
2 | |||||||
3 | /* |
||||||
4 | * @copyright 2014 Mautic Contributors. All rights reserved |
||||||
5 | * @author Mautic |
||||||
6 | * |
||||||
7 | * @link http://mautic.org |
||||||
8 | * |
||||||
9 | * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html |
||||||
10 | */ |
||||||
11 | |||||||
12 | namespace MauticPlugin\MauticCrmBundle\Integration; |
||||||
13 | |||||||
14 | use Joomla\Http\Response; |
||||||
15 | use Mautic\LeadBundle\Entity\Company; |
||||||
16 | use Mautic\LeadBundle\Entity\Lead; |
||||||
17 | use Mautic\LeadBundle\Helper\IdentifyCompanyHelper; |
||||||
18 | use Mautic\PluginBundle\Entity\IntegrationEntity; |
||||||
19 | use Mautic\PluginBundle\Entity\IntegrationEntityRepository; |
||||||
20 | use Mautic\PluginBundle\Exception\ApiErrorException; |
||||||
21 | use Symfony\Component\Console\Helper\ProgressBar; |
||||||
22 | use Symfony\Component\Console\Output\ConsoleOutput; |
||||||
23 | use Symfony\Component\Console\Output\OutputInterface; |
||||||
24 | use Symfony\Component\Form\Extension\Core\Type\ChoiceType; |
||||||
25 | use Symfony\Component\Form\FormBuilder; |
||||||
26 | |||||||
27 | class DynamicsIntegration extends CrmAbstractIntegration |
||||||
28 | { |
||||||
29 | public function getName() |
||||||
30 | { |
||||||
31 | return 'Dynamics'; |
||||||
32 | } |
||||||
33 | |||||||
34 | public function getDisplayName() |
||||||
35 | { |
||||||
36 | return 'Dynamics CRM'; |
||||||
37 | } |
||||||
38 | |||||||
39 | /** |
||||||
40 | * @return array |
||||||
41 | */ |
||||||
42 | public function getSupportedFeatures() |
||||||
43 | { |
||||||
44 | return ['push_lead', 'get_leads', 'push_leads']; |
||||||
45 | } |
||||||
46 | |||||||
47 | /** |
||||||
48 | * Return's authentication method such as oauth2, oauth1a, key, etc. |
||||||
49 | * |
||||||
50 | * @return string |
||||||
51 | */ |
||||||
52 | public function getAuthenticationType() |
||||||
53 | { |
||||||
54 | return 'oauth2'; |
||||||
55 | } |
||||||
56 | |||||||
57 | /** |
||||||
58 | * Return array of key => label elements that will be converted to inputs to |
||||||
59 | * obtain from the user. |
||||||
60 | * |
||||||
61 | * @return array |
||||||
62 | */ |
||||||
63 | public function getRequiredKeyFields() |
||||||
64 | { |
||||||
65 | return [ |
||||||
66 | 'resource' => 'mautic.integration.dynamics.resource', |
||||||
67 | 'client_id' => 'mautic.integration.dynamics.client_id', |
||||||
68 | 'client_secret' => 'mautic.integration.dynamics.client_secret', |
||||||
69 | ]; |
||||||
70 | } |
||||||
71 | |||||||
72 | /** |
||||||
73 | * @param FormBuilder $builder |
||||||
74 | * @param array $data |
||||||
75 | * @param string $formArea |
||||||
76 | */ |
||||||
77 | public function appendToForm(&$builder, $data, $formArea) |
||||||
78 | { |
||||||
79 | $builder->add( |
||||||
80 | 'updateBlanks', |
||||||
81 | ChoiceType::class, |
||||||
82 | [ |
||||||
83 | 'choices' => [ |
||||||
84 | 'mautic.integrations.blanks' => 'updateBlanks', |
||||||
85 | ], |
||||||
86 | 'expanded' => true, |
||||||
87 | 'multiple' => true, |
||||||
88 | 'label' => 'mautic.integrations.form.blanks', |
||||||
89 | 'label_attr' => ['class' => 'control-label'], |
||||||
90 | 'placeholder' => false, |
||||||
91 | 'required' => false, |
||||||
92 | ] |
||||||
93 | ); |
||||||
94 | if ('features' === $formArea) { |
||||||
95 | $builder->add( |
||||||
96 | 'objects', |
||||||
97 | ChoiceType::class, |
||||||
98 | [ |
||||||
99 | 'choices' => [ |
||||||
100 | 'mautic.dynamics.object.contact' => 'contacts', |
||||||
101 | 'mautic.dynamics.object.company' => 'company', |
||||||
102 | ], |
||||||
103 | 'expanded' => true, |
||||||
104 | 'multiple' => true, |
||||||
105 | 'label' => 'mautic.dynamics.form.objects_to_pull_from', |
||||||
106 | 'label_attr' => ['class' => ''], |
||||||
107 | 'placeholder' => false, |
||||||
108 | 'required' => false, |
||||||
109 | ] |
||||||
110 | ); |
||||||
111 | } |
||||||
112 | } |
||||||
113 | |||||||
114 | /** |
||||||
115 | * {@inheritdoc} |
||||||
116 | */ |
||||||
117 | public function sortFieldsAlphabetically() |
||||||
118 | { |
||||||
119 | return false; |
||||||
120 | } |
||||||
121 | |||||||
122 | /** |
||||||
123 | * Get the array key for the auth token. |
||||||
124 | * |
||||||
125 | * @return string |
||||||
126 | */ |
||||||
127 | public function getAuthTokenKey() |
||||||
128 | { |
||||||
129 | return 'access_token'; |
||||||
130 | } |
||||||
131 | |||||||
132 | /** |
||||||
133 | * Get the keys for the refresh token and expiry. |
||||||
134 | * |
||||||
135 | * @return array |
||||||
136 | */ |
||||||
137 | public function getRefreshTokenKeys() |
||||||
138 | { |
||||||
139 | return ['refresh_token', 'expires_on']; |
||||||
140 | } |
||||||
141 | |||||||
142 | /** |
||||||
143 | * @return string |
||||||
144 | */ |
||||||
145 | public function getApiUrl() |
||||||
146 | { |
||||||
147 | return $this->keys['resource']; |
||||||
148 | } |
||||||
149 | |||||||
150 | /** |
||||||
151 | * {@inheritdoc} |
||||||
152 | * |
||||||
153 | * @return string |
||||||
154 | */ |
||||||
155 | public function getAccessTokenUrl() |
||||||
156 | { |
||||||
157 | return 'https://login.microsoftonline.com/common/oauth2/token'; |
||||||
158 | } |
||||||
159 | |||||||
160 | /** |
||||||
161 | * {@inheritdoc} |
||||||
162 | * |
||||||
163 | * @return string |
||||||
164 | */ |
||||||
165 | public function getAuthenticationUrl() |
||||||
166 | { |
||||||
167 | return 'https://login.microsoftonline.com/common/oauth2/authorize'; |
||||||
168 | } |
||||||
169 | |||||||
170 | /** |
||||||
171 | * {@inheritdoc} |
||||||
172 | */ |
||||||
173 | public function getAuthLoginUrl() |
||||||
174 | { |
||||||
175 | $url = parent::getAuthLoginUrl(); |
||||||
176 | |||||||
177 | return $url.('&resource='.urlencode($this->keys['resource'])); |
||||||
178 | } |
||||||
179 | |||||||
180 | /** |
||||||
181 | * {@inheritdoc} |
||||||
182 | * |
||||||
183 | * @param bool $inAuthorization |
||||||
184 | */ |
||||||
185 | public function getBearerToken($inAuthorization = false) |
||||||
186 | { |
||||||
187 | if (!$inAuthorization && isset($this->keys[$this->getAuthTokenKey()])) { |
||||||
188 | return $this->keys[$this->getAuthTokenKey()]; |
||||||
189 | } |
||||||
190 | |||||||
191 | return false; |
||||||
192 | } |
||||||
193 | |||||||
194 | /** |
||||||
195 | * {@inheritdoc} |
||||||
196 | * |
||||||
197 | * @return bool |
||||||
198 | */ |
||||||
199 | public function getDataPriority() |
||||||
200 | { |
||||||
201 | return true; |
||||||
202 | } |
||||||
203 | |||||||
204 | /** |
||||||
205 | * {@inheritdoc} |
||||||
206 | * |
||||||
207 | * @param $section |
||||||
208 | * |
||||||
209 | * @return string|array |
||||||
210 | */ |
||||||
211 | public function getFormNotes($section) |
||||||
212 | { |
||||||
213 | if ('custom' === $section) { |
||||||
214 | return [ |
||||||
215 | 'template' => 'MauticCrmBundle:Integration:dynamics.html.php', |
||||||
216 | 'parameters' => [ |
||||||
217 | ], |
||||||
218 | ]; |
||||||
219 | } |
||||||
220 | |||||||
221 | return parent::getFormNotes($section); |
||||||
222 | } |
||||||
223 | |||||||
224 | /** |
||||||
225 | * @param $lead |
||||||
226 | * @param $config |
||||||
227 | * |
||||||
228 | * @return array |
||||||
229 | */ |
||||||
230 | public function populateLeadData($lead, $config = [], $object = 'Contacts') |
||||||
231 | { |
||||||
232 | if ('company' === $object) { |
||||||
233 | $object = 'accounts'; |
||||||
234 | } |
||||||
235 | $config['object'] = $object; |
||||||
236 | |||||||
237 | return parent::populateLeadData($lead, $config); |
||||||
238 | } |
||||||
239 | |||||||
240 | /** |
||||||
241 | * Get available company fields for choices in the config UI. |
||||||
242 | * |
||||||
243 | * @param array $settings |
||||||
244 | * |
||||||
245 | * @return array |
||||||
246 | */ |
||||||
247 | public function getFormCompanyFields($settings = []) |
||||||
248 | { |
||||||
249 | return $this->getFormFieldsByObject('accounts', $settings); |
||||||
250 | } |
||||||
251 | |||||||
252 | /** |
||||||
253 | * @param array $settings |
||||||
254 | * |
||||||
255 | * @return array|mixed |
||||||
256 | */ |
||||||
257 | public function getFormLeadFields($settings = []) |
||||||
258 | { |
||||||
259 | return $this->getFormFieldsByObject('contacts', $settings); |
||||||
260 | } |
||||||
261 | |||||||
262 | /** |
||||||
263 | * @param array $settings |
||||||
264 | * |
||||||
265 | * @return array|bool |
||||||
266 | * |
||||||
267 | * @throws ApiErrorException |
||||||
268 | */ |
||||||
269 | public function getAvailableLeadFields($settings = []) |
||||||
270 | { |
||||||
271 | $dynamicsFields = []; |
||||||
272 | $silenceExceptions = isset($settings['silence_exceptions']) ? $settings['silence_exceptions'] : true; |
||||||
273 | if (isset($settings['feature_settings']['objects'])) { |
||||||
274 | $dynamicsObjects = $settings['feature_settings']['objects']; |
||||||
275 | } else { |
||||||
276 | $settings = $this->settings->getFeatureSettings(); |
||||||
277 | $dynamicsObjects = isset($settings['objects']) ? $settings['objects'] : ['contacts']; |
||||||
278 | } |
||||||
279 | try { |
||||||
280 | if ($this->isAuthorized()) { |
||||||
281 | if (!empty($dynamicsObjects) && is_array($dynamicsObjects)) { |
||||||
282 | foreach ($dynamicsObjects as $dynamicsObject) { |
||||||
283 | // Check the cache first |
||||||
284 | $settings['cache_suffix'] = $cacheSuffix = '.'.$dynamicsObject; |
||||||
285 | if ($fields = parent::getAvailableLeadFields($settings)) { |
||||||
286 | $dynamicsFields[$dynamicsObject] = $fields; |
||||||
287 | continue; |
||||||
288 | } |
||||||
289 | $leadObject = $this->getApiHelper()->getLeadFields($dynamicsObject); |
||||||
290 | if (null === $leadObject || !array_key_exists('value', $leadObject)) { |
||||||
291 | return []; |
||||||
292 | } |
||||||
293 | /** @var array $opts */ |
||||||
294 | $fields = $leadObject['value']; |
||||||
295 | foreach ($fields as $field) { |
||||||
296 | $type = 'string'; |
||||||
297 | $fieldType = $field['AttributeTypeName']['Value']; |
||||||
298 | if (in_array($fieldType, [ |
||||||
299 | 'LookupType', |
||||||
300 | 'OwnerType', |
||||||
301 | 'PicklistType', |
||||||
302 | 'StateType', |
||||||
303 | 'StatusType', |
||||||
304 | 'UniqueidentifierType', |
||||||
305 | ], true)) { |
||||||
306 | continue; |
||||||
307 | } elseif (in_array($fieldType, [ |
||||||
308 | 'DoubleType', |
||||||
309 | 'IntegerType', |
||||||
310 | 'MoneyType', |
||||||
311 | ], true)) { |
||||||
312 | $type = 'int'; |
||||||
313 | } elseif ('Boolean' === $fieldType) { |
||||||
314 | $type = 'boolean'; |
||||||
315 | } elseif ('DateTimeType' === $fieldType) { |
||||||
316 | $type = 'datetime'; |
||||||
317 | } |
||||||
318 | $dynamicsFields[$dynamicsObject][$field['LogicalName']] = [ |
||||||
319 | 'type' => $type, |
||||||
320 | 'label' => $field['DisplayName']['UserLocalizedLabel']['Label'], |
||||||
321 | 'dv' => $field['LogicalName'], |
||||||
322 | 'required' => 'ApplicationRequired' === $field['RequiredLevel']['Value'], |
||||||
323 | ]; |
||||||
324 | } |
||||||
325 | $this->cache->set('leadFields'.$cacheSuffix, $dynamicsFields[$dynamicsObject]); |
||||||
326 | } |
||||||
327 | } |
||||||
328 | } |
||||||
329 | } catch (ApiErrorException $exception) { |
||||||
330 | $this->logIntegrationError($exception); |
||||||
331 | if (!$silenceExceptions) { |
||||||
332 | throw $exception; |
||||||
333 | } |
||||||
334 | |||||||
335 | return false; |
||||||
336 | } |
||||||
337 | |||||||
338 | return $dynamicsFields; |
||||||
339 | } |
||||||
340 | |||||||
341 | /** |
||||||
342 | * @param Lead $lead |
||||||
343 | * @param array $config |
||||||
344 | * |
||||||
345 | * @return array|bool |
||||||
346 | */ |
||||||
347 | public function pushLead($lead, $config = []) |
||||||
348 | { |
||||||
349 | $config = $this->mergeConfigToFeatureSettings($config); |
||||||
350 | |||||||
351 | if (empty($config['leadFields'])) { |
||||||
352 | return []; |
||||||
353 | } |
||||||
354 | |||||||
355 | $mappedData = $this->populateLeadData($lead, $config, 'contacts'); |
||||||
356 | |||||||
357 | $this->amendLeadDataBeforePush($mappedData); |
||||||
358 | |||||||
359 | if (empty($mappedData)) { |
||||||
360 | return false; |
||||||
361 | } |
||||||
362 | |||||||
363 | try { |
||||||
364 | if ($this->isAuthorized()) { |
||||||
365 | $object = 'contacts'; |
||||||
366 | /** @var IntegrationEntityRepository $integrationEntityRepo */ |
||||||
367 | $integrationEntityRepo = $this->em->getRepository('MauticPluginBundle:IntegrationEntity'); |
||||||
368 | $integrationId = $integrationEntityRepo->getIntegrationsEntityId('Dynamics', $object, 'lead', $lead->getId()); |
||||||
369 | if (!empty($integrationId)) { |
||||||
370 | $integrationEntityId = $integrationId[0]['integration_entity_id']; |
||||||
371 | $this->getApiHelper()->updateLead($mappedData, $integrationEntityId); |
||||||
372 | |||||||
373 | return $integrationEntityId; |
||||||
374 | } |
||||||
375 | /** @var Response $response */ |
||||||
376 | $response = $this->getApiHelper()->createLead($mappedData, $lead); |
||||||
0 ignored issues
–
show
|
|||||||
377 | // OData-EntityId: https://clientname.crm.dynamics.com/api/data/v8.2/contacts(9844333b-c955-e711-80f1-c4346bad526c) |
||||||
378 | $header = $response->headers['OData-EntityId']; |
||||||
379 | if (preg_match('/contacts\((.+)\)/', $header, $out)) { |
||||||
380 | $id = $out[1]; |
||||||
381 | if (empty($integrationId)) { |
||||||
382 | $integrationEntity = new IntegrationEntity(); |
||||||
383 | $integrationEntity->setDateAdded(new \DateTime()); |
||||||
384 | $integrationEntity->setIntegration('Dynamics'); |
||||||
385 | $integrationEntity->setIntegrationEntity($object); |
||||||
386 | $integrationEntity->setIntegrationEntityId($id); |
||||||
387 | $integrationEntity->setInternalEntity('lead'); |
||||||
388 | $integrationEntity->setInternalEntityId($lead->getId()); |
||||||
389 | } else { |
||||||
390 | $integrationEntity = $integrationEntityRepo->getEntity($integrationId[0]['id']); |
||||||
391 | } |
||||||
392 | $integrationEntity->setLastSyncDate(new \DateTime()); |
||||||
393 | $this->em->persist($integrationEntity); |
||||||
394 | $this->em->flush($integrationEntity); |
||||||
395 | |||||||
396 | return $id; |
||||||
397 | } |
||||||
398 | |||||||
399 | return true; |
||||||
400 | } |
||||||
401 | } catch (\Exception $e) { |
||||||
402 | $this->logIntegrationError($e); |
||||||
403 | } |
||||||
404 | |||||||
405 | return false; |
||||||
406 | } |
||||||
407 | |||||||
408 | /** |
||||||
409 | * @param array $params |
||||||
410 | * @param array|null $query |
||||||
411 | * |
||||||
412 | * @return int|null |
||||||
413 | */ |
||||||
414 | public function getLeads($params = [], $query = null, &$executed = null, $result = [], $object = 'contacts') |
||||||
415 | { |
||||||
416 | if ('Contact' === $object) { |
||||||
417 | $object = 'contacts'; |
||||||
418 | } |
||||||
419 | $executed = 0; |
||||||
420 | $MAX_RECORDS = 200; // Default max records is 5000 |
||||||
421 | try { |
||||||
422 | if ($this->isAuthorized()) { |
||||||
423 | $config = $this->mergeConfigToFeatureSettings(); |
||||||
424 | $fields = $config['leadFields']; |
||||||
425 | $config['object'] = $object; |
||||||
426 | $aFields = $this->getAvailableLeadFields($config); |
||||||
427 | $mappedData = []; |
||||||
428 | foreach (array_keys($fields) as $k) { |
||||||
429 | if (isset($aFields[$object][$k])) { |
||||||
430 | $mappedData[] = $aFields[$object][$k]['dv']; |
||||||
431 | } |
||||||
432 | } |
||||||
433 | $oparams['request_settings']['headers']['Prefer'] = 'odata.maxpagesize='.$MAX_RECORDS; |
||||||
434 | $oparams['$select'] = implode(',', $mappedData); |
||||||
435 | if (isset($params['fetchAll'], $params['start']) && !$params['fetchAll']) { |
||||||
436 | $oparams['$filter'] = sprintf('modifiedon ge %sZ', substr($params['start'], 0, '-6')); |
||||||
437 | } |
||||||
438 | |||||||
439 | if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { |
||||||
440 | $progress = new ProgressBar($params['output']); |
||||||
441 | $progress->start(); |
||||||
442 | } |
||||||
443 | |||||||
444 | while (true) { |
||||||
445 | $data = $this->getApiHelper()->getLeads($oparams); |
||||||
446 | |||||||
447 | if (!isset($data['value'])) { |
||||||
448 | break; // no more data, exit loop |
||||||
449 | } |
||||||
450 | |||||||
451 | $result = $this->amendLeadDataBeforeMauticPopulate($data, $object); |
||||||
452 | $executed += array_key_exists('value', $data) ? count($data['value']) : count($result); |
||||||
453 | |||||||
454 | if (isset($params['output'])) { |
||||||
455 | if ($params['output']->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { |
||||||
456 | $params['output']->writeln($result); |
||||||
457 | } else { |
||||||
458 | $progress->advance(count($result)); |
||||||
459 | } |
||||||
460 | } |
||||||
461 | |||||||
462 | if (!isset($data['@odata.nextLink'])) { |
||||||
463 | break; // default exit |
||||||
464 | } |
||||||
465 | |||||||
466 | // prepare next loop |
||||||
467 | $nextLink = $data['@odata.nextLink']; |
||||||
468 | $oparams['$skiptoken'] = urldecode(substr($nextLink, strpos($nextLink, '$skiptoken=') + 11)); |
||||||
469 | } |
||||||
470 | |||||||
471 | if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { |
||||||
472 | $progress->finish(); |
||||||
473 | } |
||||||
474 | } |
||||||
475 | } catch (\Exception $e) { |
||||||
476 | $this->logIntegrationError($e); |
||||||
477 | } |
||||||
478 | |||||||
479 | return $executed; |
||||||
480 | } |
||||||
481 | |||||||
482 | /** |
||||||
483 | * @param array $params |
||||||
484 | * |
||||||
485 | * @return int|null |
||||||
486 | */ |
||||||
487 | public function getCompanies($params = []) |
||||||
488 | { |
||||||
489 | $executed = 0; |
||||||
490 | $MAX_RECORDS = 200; // Default max records is 5000 |
||||||
491 | $object = 'company'; |
||||||
492 | try { |
||||||
493 | if ($this->isAuthorized()) { |
||||||
494 | $config = $this->mergeConfigToFeatureSettings(); |
||||||
495 | $fields = $config['companyFields']; |
||||||
496 | $config['object'] = $object; |
||||||
497 | $aFields = $this->getAvailableLeadFields($config); |
||||||
498 | $mappedData = []; |
||||||
499 | if (isset($aFields['company'])) { |
||||||
500 | $aFields = $aFields['company']; |
||||||
501 | } |
||||||
502 | foreach (array_keys($fields) as $k) { |
||||||
503 | $mappedData[] = $aFields[$k]['dv']; |
||||||
504 | } |
||||||
505 | $oparams['request_settings']['headers']['Prefer'] = 'odata.maxpagesize='.$MAX_RECORDS; |
||||||
506 | $oparams['$select'] = implode(',', $mappedData); |
||||||
507 | if (isset($params['fetchAll'], $params['start']) && !$params['fetchAll']) { |
||||||
508 | $oparams['$filter'] = sprintf('modifiedon ge %sZ', substr($params['start'], 0, '-6')); |
||||||
509 | } |
||||||
510 | |||||||
511 | if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { |
||||||
512 | $progress = new ProgressBar($params['output']); |
||||||
513 | $progress->start(); |
||||||
514 | } |
||||||
515 | |||||||
516 | while (true) { |
||||||
517 | $data = $this->getApiHelper()->getCompanies($oparams); |
||||||
518 | if (!isset($data['value'])) { |
||||||
519 | break; // no more data, exit loop |
||||||
520 | } |
||||||
521 | |||||||
522 | $result = $this->amendLeadDataBeforeMauticPopulate($data, $object); |
||||||
523 | $executed += count($result); |
||||||
524 | |||||||
525 | if (isset($params['output'])) { |
||||||
526 | if ($params['output']->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { |
||||||
527 | $params['output']->writeln($result); |
||||||
528 | } else { |
||||||
529 | $progress->advance(count($result)); |
||||||
530 | } |
||||||
531 | } |
||||||
532 | |||||||
533 | if (!isset($data['@odata.nextLink'])) { |
||||||
534 | break; // default exit |
||||||
535 | } |
||||||
536 | |||||||
537 | // prepare next loop |
||||||
538 | $nextLink = $data['@odata.nextLink']; |
||||||
539 | $oparams['$skiptoken'] = urldecode(substr($nextLink, strpos($nextLink, '$skiptoken=') + 11)); |
||||||
540 | } |
||||||
541 | |||||||
542 | if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) { |
||||||
543 | $progress->finish(); |
||||||
544 | } |
||||||
545 | } |
||||||
546 | } catch (\Exception $e) { |
||||||
547 | $this->logIntegrationError($e); |
||||||
548 | } |
||||||
549 | |||||||
550 | return $executed; |
||||||
551 | } |
||||||
552 | |||||||
553 | /** |
||||||
554 | * Amend mapped lead data before creating to Mautic. |
||||||
555 | * |
||||||
556 | * @param array $data |
||||||
557 | * @param string $object |
||||||
558 | * |
||||||
559 | * @return array |
||||||
560 | */ |
||||||
561 | public function amendLeadDataBeforeMauticPopulate($data, $object = null) |
||||||
562 | { |
||||||
563 | if ('company' === $object) { |
||||||
564 | $object = 'accounts'; |
||||||
565 | } elseif ('Lead' === $object || 'Contact' === $object) { |
||||||
566 | $object = 'contacts'; |
||||||
567 | } |
||||||
568 | |||||||
569 | $config = $this->mergeConfigToFeatureSettings([]); |
||||||
570 | |||||||
571 | $result = []; |
||||||
572 | if (isset($data['value'])) { |
||||||
573 | $this->em->getConnection()->getConfiguration()->setSQLLogger(null); |
||||||
574 | $entity = null; |
||||||
575 | /** @var IntegrationEntityRepository $integrationEntityRepo */ |
||||||
576 | $integrationEntityRepo = $this->em->getRepository('MauticPluginBundle:IntegrationEntity'); |
||||||
577 | $objects = $data['value']; |
||||||
578 | $integrationEntities = []; |
||||||
579 | /** @var array $objects */ |
||||||
580 | foreach ($objects as $entityData) { |
||||||
581 | $isModified = false; |
||||||
582 | if ('accounts' === $object) { |
||||||
583 | $recordId = $entityData['accountid']; |
||||||
584 | // first try to find integration entity |
||||||
585 | $integrationId = $integrationEntityRepo->getIntegrationsEntityId('Dynamics', $object, 'company', |
||||||
586 | null, null, null, false, 0, 0, "'".$recordId."'"); |
||||||
587 | if (count($integrationId)) { // company exists, then update local fields |
||||||
588 | /** @var Company $entity */ |
||||||
589 | $entity = $this->companyModel->getEntity($integrationId[0]['internal_entity_id']); |
||||||
590 | $matchedFields = $this->populateMauticLeadData($entityData, $config, 'company'); |
||||||
591 | |||||||
592 | // Match that data with mapped lead fields |
||||||
593 | $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic_company'); |
||||||
594 | if (!empty($fieldsToUpdateInMautic)) { |
||||||
595 | $fieldsToUpdateInMautic = array_intersect_key($config['companyFields'], array_flip($fieldsToUpdateInMautic)); |
||||||
596 | $newMatchedFields = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic)); |
||||||
597 | } else { |
||||||
598 | $newMatchedFields = $matchedFields; |
||||||
599 | } |
||||||
600 | if (!isset($newMatchedFields['companyname'])) { |
||||||
601 | if (isset($newMatchedFields['companywebsite'])) { |
||||||
602 | $newMatchedFields['companyname'] = $newMatchedFields['companywebsite']; |
||||||
603 | } |
||||||
604 | } |
||||||
605 | |||||||
606 | // update values if already empty |
||||||
607 | foreach ($matchedFields as $field => $value) { |
||||||
608 | if (empty($entity->getFieldValue($field))) { |
||||||
609 | $newMatchedFields[$field] = $value; |
||||||
610 | } |
||||||
611 | } |
||||||
612 | |||||||
613 | // remove unchanged fields |
||||||
614 | foreach ($newMatchedFields as $k => $v) { |
||||||
615 | if ($entity->getFieldValue($k) === $v) { |
||||||
616 | unset($newMatchedFields[$k]); |
||||||
617 | } |
||||||
618 | } |
||||||
619 | |||||||
620 | if (count($newMatchedFields)) { |
||||||
621 | $this->companyModel->setFieldValues($entity, $newMatchedFields, false, false); |
||||||
0 ignored issues
–
show
The call to
Mautic\LeadBundle\Model\...Model::setFieldValues() has too many arguments starting with false .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.
Loading history...
|
|||||||
622 | $this->companyModel->saveEntity($entity, false); |
||||||
623 | $isModified = true; |
||||||
624 | } |
||||||
625 | } else { |
||||||
626 | $entity = $this->getMauticCompany($entityData, 'company'); |
||||||
627 | } |
||||||
628 | if ($entity) { |
||||||
629 | $result[] = $entity->getName(); |
||||||
630 | } |
||||||
631 | $mauticObjectReference = 'company'; |
||||||
632 | } elseif ('contacts' === $object) { |
||||||
633 | $recordId = $entityData['contactid']; |
||||||
634 | // first try to find integration entity |
||||||
635 | $integrationId = $integrationEntityRepo->getIntegrationsEntityId('Dynamics', $object, 'lead', |
||||||
636 | null, null, null, false, 0, 0, "'".$recordId."'"); |
||||||
637 | if (count($integrationId)) { // lead exists, then update |
||||||
638 | /** @var Lead $entity */ |
||||||
639 | $entity = $this->leadModel->getEntity($integrationId[0]['internal_entity_id']); |
||||||
640 | $matchedFields = $this->populateMauticLeadData($entityData, $config); |
||||||
641 | |||||||
642 | // Match that data with mapped lead fields |
||||||
643 | $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic'); |
||||||
644 | if (!empty($fieldsToUpdateInMautic)) { |
||||||
645 | $fieldsToUpdateInMautic = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdateInMautic)); |
||||||
646 | $newMatchedFields = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic)); |
||||||
647 | } else { |
||||||
648 | $newMatchedFields = $matchedFields; |
||||||
649 | } |
||||||
650 | |||||||
651 | // update values if already empty |
||||||
652 | foreach ($matchedFields as $field => $value) { |
||||||
653 | if (empty($entity->getFieldValue($field))) { |
||||||
654 | $newMatchedFields[$field] = $value; |
||||||
655 | } |
||||||
656 | } |
||||||
657 | |||||||
658 | // remove unchanged fields |
||||||
659 | foreach ($newMatchedFields as $k => $v) { |
||||||
660 | if ($entity->getFieldValue($k) === $v) { |
||||||
661 | unset($newMatchedFields[$k]); |
||||||
662 | } |
||||||
663 | } |
||||||
664 | |||||||
665 | if (count($newMatchedFields)) { |
||||||
666 | $this->leadModel->setFieldValues($entity, $newMatchedFields, false, false); |
||||||
667 | $this->leadModel->saveEntity($entity, false); |
||||||
668 | $isModified = true; |
||||||
669 | } |
||||||
670 | } else { |
||||||
671 | /** @var Lead $entity */ |
||||||
672 | $entity = $this->getMauticLead($entityData); |
||||||
673 | } |
||||||
674 | |||||||
675 | if ($entity) { |
||||||
676 | $result[] = $entity->getEmail(); |
||||||
677 | } |
||||||
678 | |||||||
679 | // Associate lead company |
||||||
680 | if (!empty($entityData['parentcustomerid']) // company |
||||||
681 | && $entityData['parentcustomerid'] !== $this->translator->trans( |
||||||
682 | 'mautic.integration.form.lead.unknown' |
||||||
683 | ) |
||||||
684 | ) { |
||||||
685 | $company = IdentifyCompanyHelper::identifyLeadsCompany( |
||||||
686 | ['company' => $entityData['parentcustomerid']], |
||||||
687 | null, |
||||||
688 | $this->companyModel |
||||||
689 | ); |
||||||
690 | |||||||
691 | if (!empty($company[2])) { |
||||||
692 | $syncLead = $this->companyModel->addLeadToCompany($company[2], $entity); |
||||||
0 ignored issues
–
show
|
|||||||
693 | $this->em->detach($company[2]); |
||||||
694 | } |
||||||
695 | } |
||||||
696 | |||||||
697 | $mauticObjectReference = 'lead'; |
||||||
698 | } else { |
||||||
699 | $this->logIntegrationError( |
||||||
700 | new \Exception( |
||||||
701 | sprintf('Received an unexpected object "%s"', $object) |
||||||
702 | ) |
||||||
703 | ); |
||||||
704 | continue; |
||||||
705 | } |
||||||
706 | |||||||
707 | if ($entity) { |
||||||
708 | $integrationId = $integrationEntityRepo->getIntegrationsEntityId( |
||||||
709 | 'Dynamics', |
||||||
710 | $object, |
||||||
711 | $mauticObjectReference, |
||||||
712 | $entity->getId() |
||||||
713 | ); |
||||||
714 | |||||||
715 | if (0 === count($integrationId)) { |
||||||
716 | $integrationEntity = new IntegrationEntity(); |
||||||
717 | $integrationEntity->setDateAdded(new \DateTime()); |
||||||
718 | $integrationEntity->setIntegration('Dynamics'); |
||||||
719 | $integrationEntity->setIntegrationEntity($object); |
||||||
720 | $integrationEntity->setIntegrationEntityId($recordId); |
||||||
721 | $integrationEntity->setInternalEntity($mauticObjectReference); |
||||||
722 | $integrationEntity->setInternalEntityId($entity->getId()); |
||||||
723 | $integrationEntities[] = $integrationEntity; |
||||||
724 | } else { |
||||||
725 | $integrationEntity = $integrationEntityRepo->getEntity($integrationId[0]['id']); |
||||||
726 | if ($isModified) { |
||||||
727 | $integrationEntity->setLastSyncDate(new \DateTime()); |
||||||
728 | $integrationEntities[] = $integrationEntity; |
||||||
729 | } |
||||||
730 | } |
||||||
731 | $this->em->detach($entity); |
||||||
732 | unset($entity); |
||||||
733 | } |
||||||
734 | } |
||||||
735 | |||||||
736 | $integrationEntityRepo->saveEntities($integrationEntities); |
||||||
737 | $this->em->clear('Mautic\PluginBundle\Entity\IntegrationEntity'); |
||||||
738 | $this->em->clear(); |
||||||
739 | |||||||
740 | unset($integrationEntityRepo, $integrationEntities); |
||||||
741 | } |
||||||
742 | |||||||
743 | return $result; |
||||||
744 | } |
||||||
745 | |||||||
746 | /** |
||||||
747 | * @param array $params |
||||||
748 | * |
||||||
749 | * @return mixed |
||||||
750 | */ |
||||||
751 | public function pushLeads($params = []) |
||||||
752 | { |
||||||
753 | $MAX_RECORDS = (isset($params['limit']) && $params['limit'] < 100) ? $params['limit'] : 100; |
||||||
754 | if (isset($params['fetchAll']) && $params['fetchAll']) { |
||||||
755 | $params['start'] = null; |
||||||
756 | $params['end'] = null; |
||||||
757 | } |
||||||
758 | $object = 'contacts'; |
||||||
759 | $config = $this->mergeConfigToFeatureSettings(); |
||||||
760 | $integrationEntityRepo = $this->em->getRepository('MauticPluginBundle:IntegrationEntity'); |
||||||
761 | $fieldsToUpdateInCrm = isset($config['update_mautic']) ? array_keys($config['update_mautic'], 0) : []; |
||||||
762 | $leadFields = array_unique(array_values($config['leadFields'])); |
||||||
763 | $totalUpdated = $totalCreated = $totalErrors = 0; |
||||||
764 | |||||||
765 | if ($key = array_search('mauticContactTimelineLink', $leadFields)) { |
||||||
766 | unset($leadFields[$key]); |
||||||
767 | } |
||||||
768 | if ($key = array_search('mauticContactIsContactableByEmail', $leadFields)) { |
||||||
769 | unset($leadFields[$key]); |
||||||
770 | } |
||||||
771 | |||||||
772 | if (empty($leadFields)) { |
||||||
773 | return [0, 0, 0]; |
||||||
774 | } |
||||||
775 | |||||||
776 | $fields = implode(', l.', $leadFields); |
||||||
777 | $fields = 'l.'.$fields; |
||||||
778 | |||||||
779 | $availableFields = $this->getAvailableLeadFields(['feature_settings' => ['objects' => [$object]]]); |
||||||
780 | $fieldsToUpdate[$object] = array_values(array_intersect(array_keys($availableFields[$object]), $fieldsToUpdateInCrm)); |
||||||
781 | $fieldsToUpdate[$object] = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdate[$object])); |
||||||
782 | |||||||
783 | $progress = false; |
||||||
784 | $totalToUpdate = array_sum($integrationEntityRepo->findLeadsToUpdate('Dynamics', 'lead', $fields, false, $params['start'], $params['end'], [$object])); |
||||||
785 | $totalToCreate = $integrationEntityRepo->findLeadsToCreate('Dynamics', $fields, false, $params['start'], $params['end']); |
||||||
786 | $totalCount = $totalToCreate + $totalToUpdate; |
||||||
787 | |||||||
788 | if (defined('IN_MAUTIC_CONSOLE')) { |
||||||
789 | // start with update |
||||||
790 | if ($totalToUpdate + $totalToCreate) { |
||||||
791 | $output = new ConsoleOutput(); |
||||||
792 | $output->writeln("About $totalToUpdate to update and about $totalToCreate to create/update"); |
||||||
793 | $output->writeln('<info>This could take some time. Please wait until the process is completed</info>'); |
||||||
794 | $progress = new ProgressBar($output, $totalCount); |
||||||
795 | } |
||||||
796 | } |
||||||
797 | |||||||
798 | // Start with contacts so we know who is a contact when we go to process converted leads |
||||||
799 | $leadsToCreateInD = []; |
||||||
800 | $leadsToUpdateInD = []; |
||||||
801 | $integrationEntities = []; |
||||||
802 | |||||||
803 | $toUpdate = $integrationEntityRepo->findLeadsToUpdate('Dynamics', 'lead', $fields, $totalToUpdate, $params['start'], $params['end'], $object, [])[$object]; |
||||||
804 | |||||||
805 | if (is_array($toUpdate)) { |
||||||
806 | $totalUpdated += count($toUpdate); |
||||||
807 | foreach ($toUpdate as $lead) { |
||||||
808 | if (isset($lead['email']) && !empty($lead['email'])) { |
||||||
809 | $key = mb_strtolower($this->cleanPushData($lead['email'])); |
||||||
810 | $lead = $this->getCompoundMauticFields($lead); |
||||||
811 | $lead['integration_entity'] = $object; |
||||||
812 | $leadsToUpdateInD[$key] = $lead; |
||||||
813 | $integrationEntity = $this->em->getReference('MauticPluginBundle:IntegrationEntity', $lead['id']); |
||||||
814 | $integrationEntities[] = $integrationEntity->setLastSyncDate(new \DateTime()); |
||||||
815 | } |
||||||
816 | } |
||||||
817 | } |
||||||
818 | unset($toUpdate); |
||||||
819 | |||||||
820 | //create lead records, including deleted on D side (last_sync = null) |
||||||
821 | /** @var array $leadsToCreate */ |
||||||
822 | $leadsToCreate = $integrationEntityRepo->findLeadsToCreate('Dynamics', $fields, $totalToCreate, $params['start'], $params['end']); |
||||||
823 | if (is_array($leadsToCreate)) { |
||||||
824 | $totalCreated += count($leadsToCreate); |
||||||
825 | foreach ($leadsToCreate as $lead) { |
||||||
826 | if (isset($lead['email']) && !empty($lead['email'])) { |
||||||
827 | $key = mb_strtolower($this->cleanPushData($lead['email'])); |
||||||
828 | $lead = $this->getCompoundMauticFields($lead); |
||||||
829 | $lead['integration_entity'] = $object; |
||||||
830 | $leadsToCreateInD[$key] = $lead; |
||||||
831 | } |
||||||
832 | } |
||||||
833 | } |
||||||
834 | unset($leadsToCreate); |
||||||
835 | |||||||
836 | if (count($integrationEntities)) { |
||||||
837 | // Persist updated entities if applicable |
||||||
838 | $integrationEntityRepo->saveEntities($integrationEntities); |
||||||
839 | $this->em->clear(IntegrationEntity::class); |
||||||
840 | } |
||||||
841 | |||||||
842 | // update contacts |
||||||
843 | $leadData = []; |
||||||
844 | $rowNum = 0; |
||||||
845 | foreach ($leadsToUpdateInD as $lead) { |
||||||
846 | $mappedData = []; |
||||||
847 | if (defined('IN_MAUTIC_CONSOLE') && $progress) { |
||||||
848 | $progress->advance(); |
||||||
849 | } |
||||||
850 | $existingPerson = $this->getExistingRecord('emailaddress1', $lead['email'], $object); |
||||||
851 | |||||||
852 | $objectFields = $this->prepareFieldsForPush($availableFields[$object]); |
||||||
853 | $fieldsToUpdate[$object] = $this->getBlankFieldsToUpdate($fieldsToUpdate[$object], $existingPerson, $objectFields, $config); |
||||||
854 | |||||||
855 | // Match that data with mapped lead fields |
||||||
856 | foreach ($fieldsToUpdate[$object] as $k => $v) { |
||||||
857 | foreach ($lead as $dk => $dv) { |
||||||
858 | if ($v === $dk) { |
||||||
859 | if ($dv) { |
||||||
860 | if (isset($availableFields[$object][$k])) { |
||||||
861 | $mappedData[$availableFields[$object][$k]['dv']] = $dv; |
||||||
862 | } |
||||||
863 | } |
||||||
864 | } |
||||||
865 | } |
||||||
866 | } |
||||||
867 | $leadData[$lead['integration_entity_id']] = $mappedData; |
||||||
868 | |||||||
869 | ++$rowNum; |
||||||
870 | // SEND 100 RECORDS AT A TIME |
||||||
871 | if ($MAX_RECORDS === $rowNum) { |
||||||
872 | $this->getApiHelper()->updateLeads($leadData, $object); |
||||||
873 | $leadData = []; |
||||||
874 | $rowNum = 0; |
||||||
875 | } |
||||||
876 | } |
||||||
877 | $this->getApiHelper()->updateLeads($leadData, $object); |
||||||
878 | |||||||
879 | // create contacts |
||||||
880 | $leadData = []; |
||||||
881 | $rowNum = 0; |
||||||
882 | foreach ($leadsToCreateInD as $lead) { |
||||||
883 | $mappedData = []; |
||||||
884 | if (defined('IN_MAUTIC_CONSOLE') && $progress) { |
||||||
885 | $progress->advance(); |
||||||
886 | } |
||||||
887 | // Match that data with mapped lead fields |
||||||
888 | foreach ($config['leadFields'] as $k => $v) { |
||||||
889 | foreach ($lead as $dk => $dv) { |
||||||
890 | if ($v === $dk) { |
||||||
891 | if ($dv) { |
||||||
892 | if (isset($availableFields[$object][$k])) { |
||||||
893 | $mappedData[$availableFields[$object][$k]['dv']] = $dv; |
||||||
894 | } |
||||||
895 | } |
||||||
896 | } |
||||||
897 | } |
||||||
898 | } |
||||||
899 | $leadData[$lead['internal_entity_id']] = $mappedData; |
||||||
900 | |||||||
901 | ++$rowNum; |
||||||
902 | // SEND 100 RECORDS AT A TIME |
||||||
903 | if ($MAX_RECORDS === $rowNum) { |
||||||
904 | $ids = $this->getApiHelper()->createLeads($leadData, $object); |
||||||
905 | $this->createIntegrationEntities($ids, $object, $integrationEntityRepo); |
||||||
906 | $leadData = []; |
||||||
907 | $rowNum = 0; |
||||||
908 | } |
||||||
909 | } |
||||||
910 | $ids = $this->getApiHelper()->createLeads($leadData, $object); |
||||||
911 | $this->createIntegrationEntities($ids, $object, $integrationEntityRepo); |
||||||
912 | |||||||
913 | if ($progress) { |
||||||
914 | $progress->finish(); |
||||||
915 | $output->writeln(''); |
||||||
916 | } |
||||||
917 | |||||||
918 | return [$totalUpdated, $totalCreated, $totalErrors]; |
||||||
919 | } |
||||||
920 | |||||||
921 | /** |
||||||
922 | * @param array $ids |
||||||
923 | * @param $object |
||||||
924 | * @param IntegrationEntityRepository $integrationEntityRepo |
||||||
925 | */ |
||||||
926 | private function createIntegrationEntities($ids, $object, $integrationEntityRepo) |
||||||
927 | { |
||||||
928 | foreach ($ids as $oid => $leadId) { |
||||||
929 | $this->logger->debug('CREATE INTEGRATION ENTITY: '.$oid); |
||||||
930 | $integrationId = $integrationEntityRepo->getIntegrationsEntityId('Dynamics', $object, |
||||||
931 | 'lead', null, null, null, false, 0, 0, |
||||||
932 | "'".$oid."'" |
||||||
933 | ); |
||||||
934 | |||||||
935 | if (0 === count($integrationId)) { |
||||||
936 | $this->createIntegrationEntity($object, $oid, 'lead', $leadId); |
||||||
937 | } |
||||||
938 | } |
||||||
939 | } |
||||||
940 | |||||||
941 | private function getExistingRecord($seachColumn, $searchValue, $object = 'contacts') |
||||||
942 | { |
||||||
943 | $availableFields = $this->getAvailableLeadFields(); |
||||||
944 | $oparams['$select'] = implode(',', array_keys($availableFields[$object])); |
||||||
945 | $oparams['$filter'] = $seachColumn.' eq \''.$searchValue.'\''; |
||||||
946 | $data = $this->getApiHelper()->getLeads($oparams); |
||||||
947 | |||||||
948 | return (isset($data['value'][0]) && !empty($data['value'][0])) ? $data['value'][0] : []; |
||||||
949 | } |
||||||
950 | } |
||||||
951 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.