Issues (3627)

MauticCrmBundle/Integration/ZohoIntegration.php (2 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 Mautic\FormBundle\Entity\Form;
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 MauticPlugin\MauticCrmBundle\Api\Zoho\Mapper;
22
use MauticPlugin\MauticCrmBundle\Api\ZohoApi;
23
use Symfony\Component\Console\Helper\ProgressBar;
24
use Symfony\Component\Console\Output\ConsoleOutput;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
27
use Symfony\Component\Form\FormBuilder;
28
29
/**
30
 * @method ZohoApi getApiHelper
31
 */
32
class ZohoIntegration extends CrmAbstractIntegration
33
{
34
    /**
35
     * Returns the name of the social integration that must match the name of the file.
36
     *
37
     * @return string
38
     */
39
    public function getName()
40
    {
41
        return 'Zoho';
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     *
47
     * @return string
48
     */
49
    public function getAuthenticationType()
50
    {
51
        return 'oauth2';
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     *
57
     * @return array
58
     */
59
    public function getRequiredKeyFields()
60
    {
61
        return [
62
            $this->getClientIdKey()     => 'mautic.zoho.form.client_id',
63
            $this->getClientSecretKey() => 'mautic.zoho.form.client_secret',
64
        ];
65
    }
66
67
    /**
68
     * @return string
69
     */
70
    public function getClientIdKey()
71
    {
72
        return 'client_id';
73
    }
74
75
    /**
76
     * @return string
77
     */
78
    public function getClientSecretKey()
79
    {
80
        return 'client_secret';
81
    }
82
83
    /**
84
     * @return string
85
     */
86
    public function getAuthTokenKey()
87
    {
88
        return 'access_token';
89
    }
90
91
    /**
92
     * @return string
93
     */
94
    public function getAuthScope()
95
    {
96
        return 'ZohoCRM.modules.ALL,ZohoCRM.settings.ALL,ZohoCRM.bulk.all,ZohoCRM.users.all,ZohoCRM.org.all';
97
    }
98
99
    /**
100
     * @return string
101
     */
102
    public function getDatacenter()
103
    {
104
        $featureSettings = $this->getKeys();
105
106
        return !empty($featureSettings['datacenter']) ? $featureSettings['datacenter'] : 'zoho.com';
107
    }
108
109
    /**
110
     * @return string
111
     */
112
    public function getApiUrl()
113
    {
114
        return sprintf('https://accounts.%s', $this->getDatacenter());
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     *
120
     * @return string
121
     */
122
    public function getAccessTokenUrl()
123
    {
124
        return $this->getApiUrl().'/oauth/v2/token';
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     *
130
     * @return string
131
     */
132
    public function getAuthenticationUrl()
133
    {
134
        return $this->getApiUrl().'/oauth/v2/auth';
135
    }
136
137
    /**
138
     * @return array
139
     */
140
    public function getSupportedFeatures()
141
    {
142
        return ['push_lead', 'get_leads', 'push_leads'];
143
    }
144
145
    /**
146
     * Refresh tokens.
147
     */
148
    public function getRefreshTokenKeys()
149
    {
150
        return [
151
            'refresh_token',
152
            'expires',
153
        ];
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     *
159
     * @param $data
160
     */
161
    public function prepareResponseForExtraction($data)
162
    {
163
        // Extract expiry and set expires for zoho
164
        if (is_array($data) && isset($data['expires_in'])) {
165
            $data['expires'] = $data['expires_in'] + time();
166
        }
167
168
        return $data;
169
    }
170
171
    /**
172
     * Amend mapped lead data before creating to Mautic.
173
     *
174
     * @param array  $data
175
     * @param string $object
176
     *
177
     * @return array
178
     */
179
    public function amendLeadDataBeforeMauticPopulate($data, $object = null)
180
    {
181
        if ('company' === $object) {
182
            $object = 'Accounts';
183
        } elseif ('Lead' === $object || 'Contact' === $object) {
184
            $object .= 's'; // pluralize object name for Zoho
185
        }
186
187
        $config = $this->mergeConfigToFeatureSettings([]);
188
189
        $result = [];
190
        if (isset($data['data'])) {
191
            $entity = null;
192
            /** @var IntegrationEntityRepository $integrationEntityRepo */
193
            $integrationEntityRepo = $this->em->getRepository('MauticPluginBundle:IntegrationEntity');
194
            $objects               = $data['data'];
195
            /** @var array $rows */
196
            // foreach ($rows as $row) {
197
            $integrationEntities = [];
198
            /** @var array $objects */
199
            foreach ($objects as $recordId => $entityData) {
200
                $isModified = false;
201
                if ('Accounts' === $object) {
202
                    $recordId = $entityData['id'];
203
                    // first try to find integration entity
204
                    $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
205
                        'Zoho',
206
                        $object,
207
                        'company',
208
                        null,
209
                        null,
210
                        null,
211
                        false,
212
                        0,
213
                        0,
214
                        [$recordId]
215
                    );
216
                    if (count($integrationId)) { // company exists, then update local fields
217
                        /** @var Company $entity */
218
                        $entity        = $this->companyModel->getEntity($integrationId[0]['internal_entity_id']);
219
                        $matchedFields = $this->populateMauticLeadData($entityData, $config, 'company');
220
221
                        // Match that data with mapped lead fields
222
                        $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic_company');
223
                        if (!empty($fieldsToUpdateInMautic)) {
224
                            $fieldsToUpdateInMautic = array_intersect_key($config['companyFields'], $fieldsToUpdateInMautic);
225
                            $newMatchedFields       = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic));
226
                        } else {
227
                            $newMatchedFields = $matchedFields;
228
                        }
229
                        if (!isset($newMatchedFields['companyname'])) {
230
                            if (isset($newMatchedFields['companywebsite'])) {
231
                                $newMatchedFields['companyname'] = $newMatchedFields['companywebsite'];
232
                            }
233
                        }
234
235
                        // update values if already empty
236
                        foreach ($matchedFields as $field => $value) {
237
                            if (empty($entity->getFieldValue($field))) {
238
                                $newMatchedFields[$field] = $value;
239
                            }
240
                        }
241
242
                        // remove unchanged fields
243
                        foreach ($newMatchedFields as $k => $v) {
244
                            if ($entity->getFieldValue($k) === $v) {
245
                                unset($newMatchedFields[$k]);
246
                            }
247
                        }
248
249
                        if (count($newMatchedFields)) {
250
                            $this->companyModel->setFieldValues($entity, $newMatchedFields, false);
251
                            $this->companyModel->saveEntity($entity, false);
252
                            $isModified = true;
253
                        }
254
                    } else {
255
                        $entity = $this->getMauticCompany($entityData, 'Accounts');
256
                    }
257
                    if ($entity) {
258
                        $result[] = $entity->getName();
259
                    }
260
                    $mauticObjectReference = 'company';
261
                } elseif ('Leads' === $object) {
262
                    $recordId = $entityData['id'];
263
                    // first try to find integration entity
264
                    $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
265
                        'Zoho',
266
                        $object,
267
                        'lead',
268
                        null,
269
                        null,
270
                        null,
271
                        false,
272
                        0,
273
                        0,
274
                        [$recordId]
275
                    );
276
277
                    if (count($integrationId)) { // lead exists, then update
278
                        /** @var Lead $entity */
279
                        $entity        = $this->leadModel->getEntity($integrationId[0]['internal_entity_id']);
280
                        $matchedFields = $this->populateMauticLeadData($entityData, $config, $object);
281
282
                        // Match that data with mapped lead fields
283
                        $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic');
284
285
                        if (!empty($fieldsToUpdateInMautic)) {
286
                            $fieldsToUpdateInMautic = array_intersect_key($config['leadFields'], $fieldsToUpdateInMautic);
287
                            $newMatchedFields       = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic));
288
                        } else {
289
                            $newMatchedFields = $matchedFields;
290
                        }
291
292
                        // update values if already empty
293
                        foreach ($matchedFields as $field => $value) {
294
                            if (empty($entity->getFieldValue($field))) {
295
                                $newMatchedFields[$field] = $value;
296
                            }
297
                        }
298
                        // remove unchanged fields
299
                        foreach ($newMatchedFields as $k => $v) {
300
                            if ($entity->getFieldValue($k) === $v) {
301
                                unset($newMatchedFields[$k]);
302
                            }
303
                        }
304
                        if (count($newMatchedFields)) {
305
                            $this->leadModel->setFieldValues($entity, $newMatchedFields, false, false);
306
                            $this->leadModel->saveEntity($entity, false);
307
                            $isModified = true;
308
                        }
309
                    } else {
310
                        /** @var Lead $entity */
311
                        $entity = $this->getMauticLead($entityData, true, null, null, $object);
312
                    }
313
314
                    if ($entity) {
315
                        $result[] = $entity->getEmail();
316
                    }
317
318
                    // Associate lead company
319
                    if (!empty($entityData['Company'])
320
                        && $entityData['Company'] !== $this->translator->trans(
321
                            'mautic.integration.form.lead.unknown'
322
                        )
323
                    ) {
324
                        $company = IdentifyCompanyHelper::identifyLeadsCompany(
325
                            ['company' => $entityData['Company']],
326
                            null,
327
                            $this->companyModel
328
                        );
329
330
                        if (!empty($company[2])) {
331
                            $syncLead = $this->companyModel->addLeadToCompany($company[2], $entity);
332
                            $this->em->detach($company[2]);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\EntityManager::detach() has been deprecated: 2.7 This method is being removed from the ORM and won't have any replacement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

332
                            /** @scrutinizer ignore-deprecated */ $this->em->detach($company[2]);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
333
                        }
334
                    }
335
336
                    $mauticObjectReference = 'lead';
337
                } elseif ('Contacts' === $object) {
338
                    $recordId = $entityData['id'];
339
340
                    $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
341
                        'Zoho',
342
                        $object,
343
                        'lead',
344
                        null,
345
                        null,
346
                        null,
347
                        false,
348
                        0,
349
                        0,
350
                        [$recordId]
351
                    );
352
                    if (count($integrationId)) { // contact exists, then update
353
                        /** @var Lead $entity */
354
                        $entity        = $this->leadModel->getEntity($integrationId[0]['internal_entity_id']);
355
                        $matchedFields = $this->populateMauticLeadData($entityData, $config, $object);
356
357
                        // Match that data with mapped lead fields
358
                        $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic');
359
                        if (!empty($fieldsToUpdateInMautic)) {
360
                            $fieldsToUpdateInMautic = array_intersect_key($config['leadFields'], $fieldsToUpdateInMautic);
361
                            $newMatchedFields       = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic));
362
                        } else {
363
                            $newMatchedFields = $matchedFields;
364
                        }
365
366
                        // update values if already empty
367
                        foreach ($matchedFields as $field => $value) {
368
                            if (empty($entity->getFieldValue($field))) {
369
                                $newMatchedFields[$field] = $value;
370
                            }
371
                        }
372
373
                        // remove unchanged fields
374
                        foreach ($newMatchedFields as $k => $v) {
375
                            if ($entity->getFieldValue($k) === $v) {
376
                                unset($newMatchedFields[$k]);
377
                            }
378
                        }
379
380
                        if (count($newMatchedFields)) {
381
                            $this->leadModel->setFieldValues($entity, $newMatchedFields, false, false);
382
                            $this->leadModel->saveEntity($entity, false);
383
                            $isModified = true;
384
                        }
385
                    } else {
386
                        /** @var Lead $entity */
387
                        $entity = $this->getMauticLead($entityData, true, null, null, $object);
388
                    }
389
390
                    if ($entity) {
391
                        $result[] = $entity->getEmail();
392
393
                        // Associate lead company
394
                        if (!empty($entityData['AccountName'])
395
                            && $entityData['AccountName'] !== $this->translator->trans(
396
                                'mautic.integration.form.lead.unknown'
397
                            )
398
                        ) {
399
                            $company = IdentifyCompanyHelper::identifyLeadsCompany(
400
                                ['company' => $entityData['AccountName']],
401
                                null,
402
                                $this->companyModel
403
                            );
404
405
                            if (!empty($company[2])) {
406
                                $syncLead = $this->companyModel->addLeadToCompany($company[2], $entity);
407
                                $this->em->detach($company[2]);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\EntityManager::detach() has been deprecated: 2.7 This method is being removed from the ORM and won't have any replacement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

407
                                /** @scrutinizer ignore-deprecated */ $this->em->detach($company[2]);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
408
                            }
409
                        }
410
                    }
411
412
                    $mauticObjectReference = 'lead';
413
                } else {
414
                    $this->logIntegrationError(
415
                        new \Exception(
416
                            sprintf('Received an unexpected object "%s"', $object)
417
                        )
418
                    );
419
                    continue;
420
                }
421
422
                if ($entity) {
423
                    $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
424
                        'Zoho',
425
                        $object,
426
                        $mauticObjectReference,
427
                        $entity->getId()
428
                    );
429
430
                    if (0 === count($integrationId)) {
431
                        $integrationEntity = new IntegrationEntity();
432
                        $integrationEntity->setDateAdded(new \DateTime());
433
                        $integrationEntity->setIntegration('Zoho');
434
                        $integrationEntity->setIntegrationEntity($object);
435
                        $integrationEntity->setIntegrationEntityId($recordId);
436
                        $integrationEntity->setInternalEntity($mauticObjectReference);
437
                        $integrationEntity->setInternalEntityId($entity->getId());
438
                        $integrationEntities[] = $integrationEntity;
439
                    } else {
440
                        $integrationEntity = $integrationEntityRepo->getEntity($integrationId[0]['id']);
441
                        if ($isModified) {
442
                            $integrationEntity->setLastSyncDate(new \DateTime());
443
                            $integrationEntities[] = $integrationEntity;
444
                        }
445
                    }
446
                    $this->em->detach($entity);
447
                    unset($entity);
448
                } else {
449
                    continue;
450
                }
451
            }
452
453
            $this->em->getRepository('MauticPluginBundle:IntegrationEntity')->saveEntities($integrationEntities);
454
            $this->em->clear('Mautic\PluginBundle\Entity\IntegrationEntity');
455
            //}
456
            unset($integrationEntities);
457
        }
458
459
        return $result;
460
    }
461
462
    /**
463
     * @param array  $params
464
     * @param string $query
465
     * @param        $executed
466
     * @param array  $result
467
     * @param string $object
468
     *
469
     * @return int
470
     */
471
    public function getLeads($params, $query, &$executed, $result = [], $object = 'Lead')
472
    {
473
        if ('Lead' === $object || 'Contact' === $object) {
474
            $object .= 's'; // pluralize object name for Zoho
475
        }
476
477
        $executed = 0;
478
479
        try {
480
            if ($this->isAuthorized()) {
481
                $config           = $this->mergeConfigToFeatureSettings();
482
                $fields           = $config['leadFields'];
483
                $config['object'] = $object;
484
                $aFields          = $this->getAvailableLeadFields($config);
485
                $mappedData       = [];
486
487
                foreach (array_keys($fields) as $k) {
488
                    if (isset($aFields[$object][$k])) {
489
                        $mappedData[] = $aFields[$object][$k]['api_name'];
490
                    }
491
                }
492
493
                $maxRecords          = 200;
494
                $fields              = implode(',', $mappedData);
495
                $oparams['fields']   = $fields;
496
                $oparams['per_page'] = $maxRecords; // maximum number of records
497
                if (isset($params['fetchAll'], $params['start']) && !$params['fetchAll']) {
498
                    $oparams['lastModifiedTime'] = date('c', strtotime($params['start']));
499
                }
500
501
                if (!array_key_exists('page', $oparams)) {
502
                    $oparams['page'] = 1;
503
                }
504
505
                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
506
                    $progress = new ProgressBar($params['output']);
507
                    $progress->start();
508
                }
509
510
                while (true) {
511
                    $data = $this->getApiHelper()->getLeads($oparams, $object);
512
513
                    if (!isset($data['data'])) {
514
                        break; // no more data, exit loop
515
                    }
516
                    $result   = $this->amendLeadDataBeforeMauticPopulate($data, $object);
517
                    $executed += count($result);
518
                    if (isset($params['output'])) {
519
                        if ($params['output']->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
520
                            $params['output']->writeln($result);
521
                        } else {
522
                            $progress->advance();
523
                        }
524
                    }
525
526
                    // prepare next loop
527
                    ++$oparams['page'];
528
                }
529
530
                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
531
                    $progress->finish();
532
                }
533
            }
534
        } catch (\Exception $e) {
535
            $this->logIntegrationError($e);
536
        }
537
538
        return $executed;
539
    }
540
541
    /**
542
     * @param array $params
543
     * @param null  $query
544
     * @param null  $executed
545
     * @param array $result
546
     *
547
     * @return int|null
548
     */
549
    public function getCompanies($params = [], $query = null, &$executed = null, &$result = [])
550
    {
551
        $executed = 0;
552
        $object   = 'company';
553
554
        try {
555
            if ($this->isAuthorized()) {
556
                $config           = $this->mergeConfigToFeatureSettings();
557
                $fields           = $config['companyFields'];
558
                $config['object'] = $object;
559
                $aFields          = $this->getAvailableLeadFields($config);
560
                $mappedData       = [];
561
562
                foreach (array_keys($fields) as $k) {
563
                    if (isset($aFields[$object][$k])) {
564
                        $mappedData[] = $aFields[$object][$k]['api_name'];
565
                    }
566
                }
567
568
                $maxRecords          = 200;
569
                $fields              = implode(',', $mappedData);
570
                $oparams['fields']   = $fields;
571
                $oparams['per_page'] = $maxRecords; // maximum number of records
572
                if (isset($params['fetchAll'], $params['start']) && !$params['fetchAll']) {
573
                    $oparams['lastModifiedTime'] = date('c', strtotime($params['start']));
574
                }
575
576
                if (!array_key_exists('page', $oparams)) {
577
                    $oparams['page'] = 1;
578
                }
579
580
                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
581
                    $progress = new ProgressBar($params['output']);
582
                    $progress->start();
583
                }
584
585
                while (true) {
586
                    $data = $this->getApiHelper()->getCompanies($oparams);
587
                    if (!isset($data['data'])) {
588
                        break; // no more data, exit loop
589
                    }
590
                    $result   = $this->amendLeadDataBeforeMauticPopulate($data, $object);
591
                    $executed += count($result);
592
                    if (isset($params['output'])) {
593
                        if ($params['output']->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
594
                            $params['output']->writeln($result);
595
                        } else {
596
                            $progress->advance();
597
                        }
598
                    }
599
600
                    // prepare next loop
601
                    ++$oparams['page'];
602
                }
603
604
                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
605
                    $progress->finish();
606
                }
607
            }
608
        } catch (\Exception $e) {
609
            $this->logIntegrationError($e);
610
        }
611
612
        return $executed;
613
    }
614
615
    /**
616
     * {@inheritdoc}
617
     *
618
     * @param array $data
619
     * @param array $config
620
     * @param null  $object
621
     *
622
     * @return array
623
     */
624
    public function populateMauticLeadData($data, $config = [], $object = 'Leads')
625
    {
626
        // Match that data with mapped lead fields
627
        $aFields       = $this->getAvailableLeadFields($config);
628
        $matchedFields = [];
629
630
        $fieldsName = ('company' === $object) ? 'companyFields' : 'leadFields';
631
632
        if (isset($aFields[$object])) {
633
            $aFields = $aFields[$object];
634
        }
635
        foreach ($aFields as $k => $v) {
636
            foreach ($data as $dk => $dv) {
637
                if ($dk === $v['api_name']) {
638
                    $matchedFields[$config[$fieldsName][$k]] = $dv;
639
                }
640
            }
641
        }
642
643
        return $matchedFields;
644
    }
645
646
    /**
647
     * Generate the auth login URL.  Note that if oauth2, response_type=code is assumed.  If this is not the case,
648
     * override this function.
649
     *
650
     * @return string
651
     */
652
    public function getAuthLoginUrl()
653
    {
654
        $authType = $this->getAuthenticationType();
655
656
        if ('oauth2' == $authType) {
657
            $callback    = $this->getAuthCallbackUrl();
658
            $clientIdKey = $this->getClientIdKey();
659
            $state       = $this->getAuthLoginState();
660
            $url         = $this->getAuthenticationUrl()
661
                .'?client_id='.$this->keys[$clientIdKey]
662
                .'&response_type=code'
663
                .'&redirect_uri='.urlencode($callback)
664
                .'&state='.$state.'&prompt=consent&access_type=offline';
665
666
            if ($scope = $this->getAuthScope()) {
667
                $url .= '&scope='.urlencode($scope);
668
            }
669
670
            if ($this->session) {
671
                $this->session->set($this->getName().'_csrf_token', $state);
672
            }
673
674
            return $url;
675
        } else {
676
            return $this->router->generate(
677
                'mautic_integration_auth_callback',
678
                ['integration' => $this->getName()]
679
            );
680
        }
681
    }
682
683
    /**
684
     * @param Form|FormBuilder $builder
685
     * @param array            $data
686
     * @param string           $formArea
687
     *
688
     * @throws \InvalidArgumentException
689
     */
690
    public function appendToForm(&$builder, $data, $formArea)
691
    {
692
        if ('features' == $formArea) {
693
            $builder->add(
694
                'updateBlanks',
695
                ChoiceType::class,
696
                [
697
                    'choices' => [
698
                        'mautic.integrations.blanks' => 'updateBlanks',
699
                    ],
700
                    'expanded'    => true,
701
                    'multiple'    => true,
702
                    'label'       => 'mautic.integrations.form.blanks',
703
                    'label_attr'  => ['class' => 'control-label'],
704
                    'placeholder' => false,
705
                    'required'    => false,
706
                ]
707
            );
708
        }
709
        if ('keys' === $formArea) {
710
            $builder->add(
711
                'datacenter',
712
                ChoiceType::class,
713
                [
714
                    'choices' => [
715
                        'mautic.plugin.zoho.zone_us'     => 'zoho.com',
716
                        'mautic.plugin.zoho.zone_europe' => 'zoho.eu',
717
                        'mautic.plugin.zoho.zone_japan'  => 'zoho.co.jp',
718
                        'mautic.plugin.zoho.zone_china'  => 'zoho.com.cn',
719
                    ],
720
                    'label'       => 'mautic.plugin.zoho.zone_select',
721
                    'placeholder' => false,
722
                    'required'    => true,
723
                    'attr'        => [
724
                        'tooltip' => 'mautic.plugin.zoho.zone.tooltip',
725
                    ],
726
                ]
727
            );
728
        } elseif ('features' === $formArea) {
729
            $builder->add(
730
                'objects',
731
                ChoiceType::class,
732
                [
733
                    'choices' => [
734
                        'mautic.zoho.object.lead'    => 'Leads',
735
                        'mautic.zoho.object.contact' => 'Contacts',
736
                        'mautic.zoho.object.account' => 'company',
737
                    ],
738
                    'expanded'    => true,
739
                    'multiple'    => true,
740
                    'label'       => $this->getTranslator()->trans('mautic.crm.form.objects_to_pull_from', ['%crm%' => 'Zoho']),
741
                    'label_attr'  => ['class' => ''],
742
                    'placeholder' => false,
743
                    'required'    => false,
744
                ]
745
            );
746
        }
747
    }
748
749
    /**
750
     * Get available company fields for choices in the config UI.
751
     *
752
     * @param array $settings
753
     *
754
     * @return array
755
     */
756
    public function getFormCompanyFields($settings = [])
757
    {
758
        return $this->getFormFieldsByObject('Accounts', $settings);
759
    }
760
761
    /**
762
     * @param array $settings
763
     *
764
     * @return array|mixed
765
     */
766
    public function getFormLeadFields($settings = [])
767
    {
768
        $leadFields    = $this->getFormFieldsByObject('Leads', $settings);
769
        $contactFields = $this->getFormFieldsByObject('Contacts', $settings);
770
771
        return array_merge($leadFields, $contactFields);
772
    }
773
774
    /**
775
     * @param array $settings
776
     *
777
     * @return array|bool
778
     *
779
     * @throws ApiErrorException
780
     */
781
    public function getAvailableLeadFields($settings = [])
782
    {
783
        $zohoFields        = [];
784
        $silenceExceptions = isset($settings['silence_exceptions']) ? $settings['silence_exceptions'] : true;
785
786
        if (isset($settings['feature_settings']['objects'])) {
787
            $zohoObjects = $settings['feature_settings']['objects'];
788
        } else {
789
            $settings    = $this->settings->getFeatureSettings();
790
            $zohoObjects = isset($settings['objects']) ? $settings['objects'] : ['Leads'];
791
        }
792
793
        try {
794
            if ($this->isAuthorized()) {
795
                if (!empty($zohoObjects) && is_array($zohoObjects)) {
796
                    foreach ($zohoObjects as $zohoObject) {
797
                        // Check the cache first
798
                        $settings['cache_suffix'] = $cacheSuffix = '.'.$zohoObject;
799
                        if ($fields = parent::getAvailableLeadFields($settings)) {
800
                            $zohoFields[$zohoObject] = $fields;
801
                            continue;
802
                        }
803
                        $leadObject = $this->getApiHelper()->getLeadFields($zohoObject);
804
805
                        if (null === $leadObject || (isset($leadObject['status']) && 'error' === $leadObject['status'])) {
806
                            return [];
807
                        }
808
809
                        /** @var array $opts */
810
                        $opts = $leadObject['fields'];
811
                        foreach ($opts as $field) {
812
                            if (true == $field['read_only']) {
813
                                continue;
814
                            }
815
816
                            $is_required = false;
817
                            if (true == $field['system_mandatory']) {
818
                                $is_required = true;
819
                            }
820
821
                            $zohoFields[$zohoObject][$field['api_name']] = [
822
                                'type'     => 'string',
823
                                'label'    => $field['display_label'],
824
                                'api_name' => $field['api_name'],
825
                                'required' => $is_required,
826
                            ];
827
                        }
828
                        if (empty($settings['ignore_field_cache'])) {
829
                            $this->cache->set('leadFields'.$cacheSuffix, $zohoFields[$zohoObject]);
830
                        }
831
                    }
832
                }
833
            }
834
        } catch (ApiErrorException $exception) {
835
            $this->logIntegrationError($exception);
836
837
            if (!$silenceExceptions) {
838
                if (false !== strpos($exception->getMessage(), 'Invalid Ticket Id')) {
839
                    // Use a bit more friendly message
840
                    $exception = new ApiErrorException('There was an issue with communicating with Zoho. Please try to reauthorize.');
841
                }
842
843
                throw $exception;
844
            }
845
846
            return false;
847
        }
848
849
        return $zohoFields;
850
    }
851
852
    /**
853
     * @param array $params
854
     *
855
     * @return mixed
856
     */
857
    public function pushLeads($params = [])
858
    {
859
        $maxRecords = (isset($params['limit']) && $params['limit'] < 100) ? $params['limit'] : 100;
860
        if (isset($params['fetchAll']) && $params['fetchAll']) {
861
            $params['start'] = null;
862
            $params['end']   = null;
863
        }
864
        $config                = $this->mergeConfigToFeatureSettings();
865
        $integrationEntityRepo = $this->em->getRepository('MauticPluginBundle:IntegrationEntity');
866
        $fieldsToUpdateInZoho  = isset($config['update_mautic']) ? array_keys($config['update_mautic'], 0) : [];
867
        $leadFields            = array_unique(array_values($config['leadFields']));
868
        $totalUpdated          = $totalCreated = $totalErrors = 0;
869
        if ($key = array_search('mauticContactTimelineLink', $leadFields)) {
870
            unset($leadFields[$key]);
871
        }
872
        if ($key = array_search('mauticContactIsContactableByEmail', $leadFields)) {
873
            unset($leadFields[$key]);
874
        }
875
        if (empty($leadFields)) {
876
            return [0, 0, 0];
877
        }
878
879
        $fields = implode(', l.', $leadFields);
880
        $fields = 'l.'.$fields;
881
882
        $availableFields            = $this->getAvailableLeadFields(['feature_settings' => ['objects' => ['Leads', 'Contacts']]]);
883
        $fieldsToUpdate['Leads']    = array_values(array_intersect(array_keys($availableFields['Leads']), $fieldsToUpdateInZoho));
884
        $fieldsToUpdate['Contacts'] = array_values(array_intersect(array_keys($availableFields['Contacts']), $fieldsToUpdateInZoho));
885
        $fieldsToUpdate['Leads']    = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdate['Leads']));
886
        $fieldsToUpdate['Contacts'] = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdate['Contacts']));
887
888
        $progress      = false;
889
        $totalToUpdate = array_sum(
890
            $integrationEntityRepo->findLeadsToUpdate('Zoho', 'lead', $fields, false, $params['start'], $params['end'], ['Contacts', 'Leads'])
891
        );
892
        $totalToCreate = $integrationEntityRepo->findLeadsToCreate('Zoho', $fields, false, $params['start'], $params['end']);
893
        $totalCount    = $totalToCreate + $totalToUpdate;
894
895
        if (defined('IN_MAUTIC_CONSOLE')) {
896
            // start with update
897
            if ($totalToUpdate + $totalToCreate) {
898
                $output = new ConsoleOutput();
899
                $output->writeln("About $totalToUpdate to update and about $totalToCreate to create/update");
900
                $progress = new ProgressBar($output, $totalCount);
901
            }
902
        }
903
904
        // Start with contacts so we know who is a contact when we go to process converted leads
905
        $leadsToCreateInZ    = [];
906
        $leadsToUpdateInZ    = [];
907
        $isContact           = [];
908
        $integrationEntities = [];
909
910
        // Fetch them separately so we can determine which oneas are already there
911
        $toUpdate = $integrationEntityRepo->findLeadsToUpdate(
912
            'Zoho',
913
            'lead',
914
            $fields,
915
            $totalToUpdate,
916
            $params['start'],
917
            $params['end'],
918
            'Contacts',
919
            []
920
        )['Contacts'];
921
922
        if (is_array($toUpdate)) {
923
            foreach ($toUpdate as $lead) {
924
                if (isset($lead['email']) && !empty($lead['email'])) {
925
                    $key                        = mb_strtolower($this->cleanPushData($lead['email']));
926
                    $lead['integration_entity'] = 'Contacts';
927
                    $leadsToUpdateInZ[$key]     = $lead;
928
                    $isContact[$key]            = $lead;
929
                }
930
            }
931
        }
932
933
        // Switch to Lead
934
        $toUpdate = $integrationEntityRepo->findLeadsToUpdate(
935
            'Zoho',
936
            'lead',
937
            $fields,
938
            $totalToUpdate,
939
            $params['start'],
940
            $params['end'],
941
            'Leads',
942
            []
943
        )['Leads'];
944
945
        if (is_array($toUpdate)) {
946
            foreach ($toUpdate as $lead) {
947
                if (isset($lead['email']) && !empty($lead['email'])) {
948
                    $key  = mb_strtolower($this->cleanPushData($lead['email']));
949
                    $lead = $this->getCompoundMauticFields($lead);
950
                    if (isset($isContact[$key])) {
951
                        $isContact[$key] = $lead; // lead-converted
952
                    } else {
953
                        $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
954
                            'Zoho',
955
                            'Leads',
956
                            'lead',
957
                            $lead['internal_entity_id']
958
                        );
959
960
                        $lead['integration_entity'] = 'Leads';
961
                        $leadsToUpdateInZ[$key]     = $lead;
962
                        $integrationEntity          = $this->em->getReference('MauticPluginBundle:IntegrationEntity', $integrationId[0]['id']);
963
                        $integrationEntities[]      = $integrationEntity->setLastSyncDate(new \DateTime());
964
                    }
965
                }
966
            }
967
        }
968
        unset($toUpdate);
969
970
        // convert ignored contacts
971
        foreach ($isContact as $email => $lead) {
972
            $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
973
                'Zoho',
974
                'Leads',
975
                'lead',
976
                $lead['internal_entity_id']
977
            );
978
            if (count($integrationId)) { // lead exists, then update
979
                $integrationEntity     = $this->em->getReference('MauticPluginBundle:IntegrationEntity', $integrationId[0]['id']);
980
                $integrationEntity->setLastSyncDate(new \DateTime());
981
                $integrationEntity->setInternalEntity('lead-converted');
982
                $integrationEntities[] = $integrationEntity;
983
                unset($leadsToUpdateInZ[$email]);
984
            }
985
        }
986
987
        //create lead records, including deleted on Zoho side (last_sync = null)
988
        /** @var array $leadsToCreate */
989
        $leadsToCreate = $integrationEntityRepo->findLeadsToCreate('Zoho', $fields, $totalToCreate, $params['start'], $params['end']);
990
991
        if (is_array($leadsToCreate)) {
992
            foreach ($leadsToCreate as $lead) {
993
                if (isset($lead['email']) && !empty($lead['email'])) {
994
                    $key                        = mb_strtolower($this->cleanPushData($lead['email']));
995
                    $lead                       = $this->getCompoundMauticFields($lead);
996
                    $lead['integration_entity'] = 'Leads';
997
                    $leadsToCreateInZ[$key]     = $lead;
998
                }
999
            }
1000
        }
1001
        unset($leadsToCreate);
1002
1003
        if (count($integrationEntities)) {
1004
            // Persist updated entities if applicable
1005
            $integrationEntityRepo->saveEntities($integrationEntities);
1006
            $this->em->clear(IntegrationEntity::class);
1007
        }
1008
1009
        // update leads and contacts
1010
        $mapper = new Mapper($availableFields);
1011
        foreach (['Leads', 'Contacts'] as $zObject) {
1012
            $counter = 1;
1013
            $mapper->setObject($zObject);
1014
            foreach ($leadsToUpdateInZ as $lead) {
1015
                if ($zObject !== $lead['integration_entity']) {
1016
                    continue;
1017
                }
1018
1019
                if ($progress) {
1020
                    $progress->advance();
1021
                }
1022
1023
                $existingPerson           = $this->getExistingRecord('Email', $lead['email'], $zObject);
1024
                $objectFields             = $this->prepareFieldsForPush($availableFields[$zObject]);
1025
                $fieldsToUpdate[$zObject] = $this->getBlankFieldsToUpdate($fieldsToUpdate[$zObject], $existingPerson, $objectFields, $config);
1026
1027
                $totalUpdated += $mapper
1028
                    ->setMappedFields($fieldsToUpdate[$zObject])
1029
                    ->setContact($lead)
1030
                    ->map($lead['internal_entity_id'], $lead['integration_entity_id']);
1031
                ++$counter;
1032
1033
                // ONLY 100 RECORDS CAN BE SENT AT A TIME
1034
                if ($maxRecords === $counter) {
1035
                    $this->updateContactInZoho($mapper, $zObject, $totalUpdated, $totalErrors);
1036
                    $counter = 1;
1037
                }
1038
            }
1039
1040
            if ($counter > 1) {
1041
                $this->updateContactInZoho($mapper, $zObject, $totalUpdated, $totalErrors);
1042
            }
1043
        }
1044
1045
        // create leads and contacts
1046
        foreach (['Leads', 'Contacts'] as $zObject) {
1047
            $counter = 1;
1048
            $mapper->setObject($zObject);
1049
            foreach ($leadsToCreateInZ as $lead) {
1050
                if ($zObject !== $lead['integration_entity']) {
1051
                    continue;
1052
                }
1053
                if ($progress) {
1054
                    $progress->advance();
1055
                }
1056
1057
                $totalCreated += $mapper
1058
                    ->setMappedFields($config['leadFields'])
1059
                    ->setContact($lead)
1060
                    ->map($lead['internal_entity_id']);
1061
                ++$counter;
1062
1063
                // ONLY 100 RECORDS CAN BE SENT AT A TIME
1064
                if ($maxRecords === $counter) {
1065
                    $this->createContactInZoho($mapper, $zObject, $totalCreated, $totalErrors);
1066
                    $counter = 1;
1067
                }
1068
            }
1069
1070
            if ($counter > 1) {
1071
                $this->createContactInZoho($mapper, $zObject, $totalCreated, $totalErrors);
1072
            }
1073
        }
1074
1075
        if ($progress) {
1076
            $progress->finish();
1077
            $output->writeln('');
1078
        }
1079
1080
        return [$totalUpdated, $totalCreated, $totalErrors, $totalCount - ($totalCreated + $totalUpdated + $totalErrors)];
1081
    }
1082
1083
    /**
1084
     * @param Lead|array $lead
1085
     * @param array      $config
1086
     *
1087
     * @return array|bool
1088
     */
1089
    public function pushLead($lead, $config = [])
1090
    {
1091
        $config  = $this->mergeConfigToFeatureSettings($config);
1092
        $zObject = 'Leads';
1093
1094
        $fieldsToUpdateInZoho       = isset($config['update_mautic']) ? array_keys($config['update_mautic'], 0) : [];
1095
        $availableFields            = $this->getAvailableLeadFields(['feature_settings' => ['objects' => ['Leads', 'Contacts']]]);
1096
        $fieldsToUpdate['Leads']    = array_values(array_intersect(array_keys($availableFields['Leads']), $fieldsToUpdateInZoho));
1097
        $fieldsToUpdate['Contacts'] = array_values(array_intersect(array_keys($availableFields['Contacts']), $fieldsToUpdateInZoho));
1098
        $fieldsToUpdate['Leads']    = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdate['Leads']));
1099
        $fieldsToUpdate['Contacts'] = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdate['Contacts']));
1100
        $objectFields               = $this->prepareFieldsForPush($availableFields[$zObject]);
1101
        $existingPerson             = $this->getExistingRecord('Email', $lead->getEmail(), $zObject);
1102
        $fieldsToUpdate[$zObject]   = $this->getBlankFieldsToUpdate($fieldsToUpdate[$zObject], $existingPerson, $objectFields, $config);
1103
1104
        if (empty($config['leadFields'])) {
1105
            return [];
1106
        }
1107
1108
        $mapper = new Mapper($availableFields);
1109
        $mapper->setObject($zObject);
1110
1111
        $integrationEntityRepo = $this->em->getRepository('MauticPluginBundle:IntegrationEntity');
1112
        $integrationId         = $integrationEntityRepo->getIntegrationsEntityId('Zoho', $zObject, 'lead', $lead->getId());
1113
1114
        $counter      = 0;
1115
        $errorCounter = 0;
1116
1117
        try {
1118
            if ($this->isAuthorized()) {
1119
                if (!empty($existingPerson) && empty($integrationId)) {
1120
                    $this->createIntegrationEntity($zObject, $existingPerson['id'], 'lead', $lead->getId());
1121
1122
                    $mapper
1123
                        ->setMappedFields($fieldsToUpdate[$zObject])
1124
                        ->setContact($lead->getProfileFields())
1125
                        ->map($lead->getId(), $existingPerson['id']);
1126
                    $this->updateContactInZoho($mapper, $zObject, $counter, $errorCounter);
1127
                } elseif (!empty($existingPerson) && !empty($integrationId)) { // contact exists, then update
1128
                    $mapper
1129
                        ->setMappedFields($fieldsToUpdate[$zObject])
1130
                        ->setContact($lead->getProfileFields())
1131
                        ->map($lead->getId(), $existingPerson['id']);
1132
                    $this->updateContactInZoho($mapper, $zObject, $counter, $errorCounter);
1133
                } else {
1134
                    $mapper
1135
                        ->setMappedFields($config['leadFields'])
1136
                        ->setContact($lead->getProfileFields())
1137
                        ->map($lead->getId());
1138
                    $this->createContactInZoho($mapper, $zObject, $counter, $errorCounter);
1139
                }
1140
1141
                return true;
1142
            }
1143
        } catch (\Exception $e) {
1144
            $this->logIntegrationError($e);
1145
        }
1146
1147
        return false;
1148
    }
1149
1150
    /**
1151
     * @param $fields
1152
     * @param $sfRecord
1153
     * @param $config
1154
     * @param $objectFields
1155
     */
1156
    public function getBlankFieldsToUpdate($fields, $sfRecord, $objectFields, $config)
1157
    {
1158
        //check if update blank fields is selected
1159
        if (isset($config['updateBlanks']) && isset($config['updateBlanks'][0]) && 'updateBlanks' == $config['updateBlanks'][0]) {
1160
            foreach ($sfRecord as $fieldName => $sfField) {
1161
                if (array_key_exists($fieldName, $objectFields['required']['fields'])) {
1162
                    continue; // this will be treated differently
1163
                }
1164
                if ('null' === $sfField && array_key_exists($fieldName, $objectFields['create']) && !array_key_exists($fieldName, $fields)) {
1165
                    //map to mautic field
1166
                    $fields[$fieldName] = $objectFields['create'][$fieldName];
1167
                }
1168
            }
1169
        }
1170
1171
        return $fields;
1172
    }
1173
1174
    /**
1175
     * Get if data priority is enabled in the integration or not default is false.
1176
     *
1177
     * @return string
1178
     */
1179
    public function getDataPriority()
1180
    {
1181
        return true;
1182
    }
1183
1184
    /**
1185
     * @param array  $response
1186
     * @param string $zObject
1187
     * @param bool   $createIntegrationEntity
1188
     *
1189
     * @return int
1190
     *
1191
     * @throws \MauticPlugin\MauticCrmBundle\Api\Zoho\Exception\MatchingKeyNotFoundException
1192
     */
1193
    private function consumeResponse($response, $zObject, $createIntegrationEntity = false, Mapper $mapper = null)
1194
    {
1195
        $rows = $response;
1196
        if (isset($rows['data'][0])) {
1197
            $rows = $rows['data'];
1198
        }
1199
1200
        $failed = 0;
1201
        foreach ($rows as $key => $row) {
1202
            $mauticId = $mapper->getContactIdByKey($key);
1203
1204
            if ('SUCCESS' === $row['code'] && $createIntegrationEntity) {
1205
                $zohoId = $row['details']['id'];
1206
                $this->logger->debug('CREATE INTEGRATION ENTITY: '.$zohoId);
1207
                $integrationId = $this->getIntegrationEntityRepository()->getIntegrationsEntityId(
1208
                    'Zoho',
1209
                    $zObject,
1210
                    'lead',
1211
                    null,
1212
                    null,
1213
                    null,
1214
                    false,
1215
                    0,
1216
                    0,
1217
                    $zohoId
1218
                );
1219
1220
                if (0 === count($integrationId)) {
1221
                    $this->createIntegrationEntity($zObject, $zohoId, 'lead', $mauticId);
1222
                }
1223
            } elseif (isset($row['status']) && 'error' === $row['status']) {
1224
                ++$failed;
1225
                $exception = new ApiErrorException($row['message']);
1226
                $exception->setContactId($mauticId);
1227
                $this->logIntegrationError($exception);
1228
            }
1229
        }
1230
1231
        return $failed;
1232
    }
1233
1234
    /**
1235
     * @param string $seachColumn
1236
     * @param string $searchValue
1237
     * @param string $object
1238
     *
1239
     * @return array
1240
     */
1241
    private function getExistingRecord($seachColumn, $searchValue, $object = 'Leads')
1242
    {
1243
        $availableFields = $this->getAvailableLeadFields(['feature_settings' => ['objects' => ['Leads', 'Contacts']]]);
1244
        $records         = $this->getApiHelper()->getSearchRecords($seachColumn, $searchValue, $object);
1245
        $idField         = [
1246
            'id' => [
1247
                'type'     => 'string',
1248
                'label'    => 'ID',
1249
                'api_name' => 'id',
1250
                'required' => true,
1251
            ],
1252
        ];
1253
1254
        return $this->parseZohoRecord($records, array_merge($availableFields[$object], $idField));
1255
    }
1256
1257
    /**
1258
     * @param $data
1259
     * @param $fields
1260
     *
1261
     * @return array
1262
     */
1263
    private function parseZohoRecord($data, $fields)
1264
    {
1265
        $parsedData = [];
1266
        if (empty($data['data'])) {
1267
            return $parsedData;
1268
        }
1269
1270
        $records = $data['data'][0];
1271
        foreach ($fields as $field) {
1272
            foreach ($records as $recordKey => $recordValue) {
1273
                if ($recordKey === $field['api_name']) {
1274
                    $parsedData[$recordKey] = $recordValue;
1275
                    continue;
1276
                }
1277
            }
1278
        }
1279
1280
        return $parsedData;
1281
    }
1282
1283
    /**
1284
     * @param string $object
1285
     * @param int    $counter
1286
     * @param int    $errorCounter
1287
     */
1288
    private function updateContactInZoho(Mapper $mapper, $object, &$counter, &$errorCounter)
1289
    {
1290
        $response     = $this->getApiHelper()->updateLead($mapper->getArray(), $object);
1291
        $failed       = $this->consumeResponse($response, $object, false, $mapper);
1292
        $counter -= $failed;
1293
        $errorCounter += $failed;
1294
    }
1295
1296
    /**
1297
     * @param string $object
1298
     * @param int    $counter
1299
     * @param int    $errorCounter
1300
     */
1301
    private function createContactInZoho(Mapper $mapper, $object, &$counter, &$errorCounter)
1302
    {
1303
        $response     = $this->getApiHelper()->createLead($mapper->getArray(), $object);
1304
        $failed       = $this->consumeResponse($response, $object, true, $mapper);
1305
        $counter -= $failed;
1306
        $errorCounter += $failed;
1307
    }
1308
1309
    /**
1310
     * @param       $fieldsToUpdate
1311
     * @param array $objects
1312
     *
1313
     * @return array
1314
     */
1315
    protected function cleanPriorityFields($fieldsToUpdate, $objects = null)
1316
    {
1317
        if (null === $objects) {
1318
            $objects = ['Leads', 'Contacts'];
1319
        }
1320
1321
        if (isset($fieldsToUpdate['leadFields'])) {
1322
            // Pass in the whole config
1323
            $fields = $fieldsToUpdate;
1324
        } else {
1325
            $fields = array_flip($fieldsToUpdate);
1326
        }
1327
1328
        return $this->prepareFieldsForSync($fields, $fieldsToUpdate, $objects);
1329
    }
1330
1331
    /**
1332
     * @param array $fields
1333
     * @param array $keys
1334
     * @param mixed $object
1335
     *
1336
     * @return array
1337
     */
1338
    public function prepareFieldsForSync($fields, $keys, $object = null)
1339
    {
1340
        $leadFields = [];
1341
        if (null === $object) {
1342
            $object = 'Leads';
1343
        }
1344
1345
        $objects = (!is_array($object)) ? [$object] : $object;
1346
        if (is_string($object) && 'Accounts' === $object) {
1347
            return isset($fields['companyFields']) ? $fields['companyFields'] : $fields;
1348
        }
1349
1350
        if (isset($fields['leadFields'])) {
1351
            $fields = $fields['leadFields'];
1352
            $keys   = array_keys($fields);
1353
        }
1354
1355
        foreach ($objects as $obj) {
1356
            if (!isset($leadFields[$obj])) {
1357
                $leadFields[$obj] = [];
1358
            }
1359
1360
            foreach ($keys as $key) {
1361
                $leadFields[$obj][$key] = $fields[$key];
1362
            }
1363
        }
1364
1365
        return (is_array($object)) ? $leadFields : $leadFields[$object];
1366
    }
1367
}
1368