Issues (3627)

Integration/DynamicsIntegration.php (6 issues)

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);
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;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$oparams was never initialized. Although not strictly required by PHP, it is generally a good practice to add $oparams = array(); before regardless.
Loading history...
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));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $progress does not seem to be defined for all execution paths leading up to this point.
Loading history...
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;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$oparams was never initialized. Although not strictly required by PHP, it is generally a good practice to add $oparams = array(); before regardless.
Loading history...
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));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $progress does not seem to be defined for all execution paths leading up to this point.
Loading history...
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);
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);
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));
0 ignored issues
show
Comprehensibility Best Practice introduced by
$fieldsToUpdate was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fieldsToUpdate = array(); before regardless.
Loading history...
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('');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $output does not seem to be defined for all execution paths leading up to this point.
Loading history...
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