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);
0 ignored issues
show
The assignment to $syncLead is dead and can be removed.
Loading history...
332
                            $this->em->detach($company[2]);
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]);
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 = [])
0 ignored issues
show
The parameter $query is not used and could be removed. ( Ignorable by Annotation )

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

549
    public function getCompanies($params = [], /** @scrutinizer ignore-unused */ $query = null, &$executed = null, &$result = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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