Completed
Push — staging ( 34bcbe...4e27d0 )
by Woeler
80:05 queued 68:07
created

ZohoIntegration::prepareFieldsForSync()   B

Complexity

Conditions 11
Paths 88

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 15
c 1
b 0
f 0
nc 88
nop 3
dl 0
loc 28
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\CoreBundle\Helper\InputHelper;
15
use Mautic\FormBundle\Entity\Form;
16
use Mautic\LeadBundle\Entity\Company;
17
use Mautic\LeadBundle\Entity\Lead;
18
use Mautic\LeadBundle\Helper\IdentifyCompanyHelper;
19
use Mautic\PluginBundle\Entity\IntegrationEntity;
20
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
21
use Mautic\PluginBundle\Exception\ApiErrorException;
22
use MauticPlugin\MauticCrmBundle\Api\Zoho\Mapper;
23
use MauticPlugin\MauticCrmBundle\Api\Zoho\Xml\Writer;
24
use MauticPlugin\MauticCrmBundle\Api\ZohoApi;
25
use Symfony\Component\Console\Helper\ProgressBar;
26
use Symfony\Component\Console\Output\ConsoleOutput;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Form\FormBuilder;
29
30
/**
31
 * Class ZohoIntegration.
32
 *
33
 * @method ZohoApi getApiHelper
34
 */
35
class ZohoIntegration extends CrmAbstractIntegration
36
{
37
    /**
38
     * Returns the name of the social integration that must match the name of the file.
39
     *
40
     * @return string
41
     */
42
    public function getName()
43
    {
44
        return 'Zoho';
45
    }
46
47
    /**
48
     * {@inheritdoc}
49
     *
50
     * @return array
51
     */
52
    public function getRequiredKeyFields()
53
    {
54
        return [
55
            $this->getClientIdKey()     => 'mautic.zoho.form.email',
56
            $this->getClientSecretKey() => 'mautic.zoho.form.password',
57
        ];
58
    }
59
60
    /**
61
     * @return string
62
     */
63
    public function getClientIdKey()
64
    {
65
        return 'EMAIL_ID';
66
    }
67
68
    /**
69
     * @return string
70
     */
71
    public function getClientSecretKey()
72
    {
73
        return 'PASSWORD';
74
    }
75
76
    /**
77
     * @return string
78
     */
79
    public function getAuthTokenKey()
80
    {
81
        return 'AUTHTOKEN';
82
    }
83
84
    /**
85
     * @return string
86
     */
87
    public function getDatacenter()
88
    {
89
        $featureSettings = $this->getKeys();
90
91
        return !empty($featureSettings['datacenter']) ? $featureSettings['datacenter'] : 'zoho.com';
92
    }
93
94
    /**
95
     * @param bool $isJson
96
     *
97
     * @return string
98
     */
99
    public function getApiUrl($isJson = true)
100
    {
101
        return sprintf('https://crm.%s/crm/private/%s', $this->getDatacenter(), $isJson ? 'json' : 'xml');
102
    }
103
104
    /**
105
     * @return array
106
     */
107
    public function getFormSettings()
108
    {
109
        return [
110
            'requires_callback'      => false,
111
            'requires_authorization' => true,
112
        ];
113
    }
114
115
    /**
116
     * @return array
117
     */
118
    public function getSupportedFeatures()
119
    {
120
        return ['push_lead', 'get_leads', 'push_leads'];
121
    }
122
123
    /**
124
     * @param $rows
125
     *
126
     * @return array
127
     */
128
    protected function formatZohoData($rows)
129
    {
130
        if (isset($rows['FL'])) {
131
            $rows = [$rows];
132
        }
133
        $fieldsValues = [];
134
        foreach ($rows as $row) {
135
            if (isset($row['FL'])) {
136
                $fl = $row['FL'];
137
                if (isset($fl['val'])) {
138
                    $fl = [$fl];
139
                }
140
                foreach ($fl as $field) {
141
                    // Fix boolean comparison
142
                    $value = $field['content'];
143
                    if ($field['content'] === 'true') {
144
                        $value = true;
145
                    } elseif ($field['content'] === 'false') {
146
                        $value = false;
147
                    }
148
149
                    $fieldsValues[$row['no'] - 1][$this->getFieldKey($field['val'])] = $value;
150
                }
151
            }
152
        }
153
154
        return $fieldsValues;
155
    }
156
157
    /**
158
     * Amend mapped lead data before creating to Mautic.
159
     *
160
     * @param array  $data
161
     * @param string $object
162
     *
163
     * @return array
164
     */
165
    public function amendLeadDataBeforeMauticPopulate($data, $object = null)
166
    {
167
        if ('company' === $object) {
168
            $object = 'Accounts';
169
        } elseif ('Lead' === $object || 'Contact' === $object) {
170
            $object .= 's'; // pluralize object name for Zoho
171
        }
172
173
        $config = $this->mergeConfigToFeatureSettings([]);
174
175
        $result = [];
176
        if (isset($data[$object])) {
177
            $entity = null;
178
            /** @var IntegrationEntityRepository $integrationEntityRepo */
179
            $integrationEntityRepo = $this->em->getRepository('MauticPluginBundle:IntegrationEntity');
180
            $rows                  = $data[$object];
181
            /** @var array $rows */
182
            foreach ($rows as $row) {
183
                if (is_array($row)) {
184
                    $objects             = $this->formatZohoData($row);
185
                    $integrationEntities = [];
186
                    /** @var array $objects */
187
                    foreach ($objects as $recordId => $entityData) {
188
                        $isModified = false;
189
                        if ('Accounts' === $object) {
190
                            $recordId = $entityData['ACCOUNTID'];
191
                            // first try to find integration entity
192
                            $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
193
                                'Zoho',
194
                                $object,
195
                                'company',
196
                                null,
197
                                null,
198
                                null,
199
                                false,
200
                                0,
201
                                0,
202
                                [$recordId]
203
                            );
204
                            if (count($integrationId)) { // company exists, then update local fields
205
                                /** @var Company $entity */
206
                                $entity        = $this->companyModel->getEntity($integrationId[0]['internal_entity_id']);
207
                                $matchedFields = $this->populateMauticLeadData($entityData, $config, 'company');
208
209
                                // Match that data with mapped lead fields
210
                                $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic_company');
211
                                if (!empty($fieldsToUpdateInMautic)) {
212
                                    $fieldsToUpdateInMautic = array_intersect_key($config['companyFields'], $fieldsToUpdateInMautic);
213
                                    $newMatchedFields       = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic));
214
                                } else {
215
                                    $newMatchedFields = $matchedFields;
216
                                }
217
                                if (!isset($newMatchedFields['companyname'])) {
218
                                    if (isset($newMatchedFields['companywebsite'])) {
219
                                        $newMatchedFields['companyname'] = $newMatchedFields['companywebsite'];
220
                                    }
221
                                }
222
223
                                // update values if already empty
224
                                foreach ($matchedFields as $field => $value) {
225
                                    if (empty($entity->getFieldValue($field))) {
226
                                        $newMatchedFields[$field] = $value;
227
                                    }
228
                                }
229
230
                                // remove unchanged fields
231
                                foreach ($newMatchedFields as $k => $v) {
232
                                    if ($entity->getFieldValue($k) === $v) {
233
                                        unset($newMatchedFields[$k]);
234
                                    }
235
                                }
236
237
                                if (count($newMatchedFields)) {
238
                                    $this->companyModel->setFieldValues($entity, $newMatchedFields, false, false);
0 ignored issues
show
Unused Code introduced by
The call to Mautic\LeadBundle\Model\...Model::setFieldValues() has too many arguments starting with false. ( Ignorable by Annotation )

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

238
                                    $this->companyModel->/** @scrutinizer ignore-call */ 
239
                                                         setFieldValues($entity, $newMatchedFields, false, false);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
239
                                    $this->companyModel->saveEntity($entity, false);
240
                                    $isModified = true;
241
                                }
242
                            } else {
243
                                $entity = $this->getMauticCompany($entityData, 'Accounts');
244
                            }
245
                            if ($entity) {
246
                                $result[] = $entity->getName();
247
                            }
248
                            $mauticObjectReference = 'company';
249
                        } elseif ('Leads' === $object) {
250
                            $recordId = $entityData['LEADID'];
251
252
                            // first try to find integration entity
253
                            $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
254
                                'Zoho',
255
                                $object,
256
                                'lead',
257
                                null,
258
                                null,
259
                                null,
260
                                false,
261
                                0,
262
                                0,
263
                                [$recordId]
264
                            );
265
266
                            if (count($integrationId)) { // lead exists, then update
267
                                /** @var Lead $entity */
268
                                $entity        = $this->leadModel->getEntity($integrationId[0]['internal_entity_id']);
269
                                $matchedFields = $this->populateMauticLeadData($entityData, $config);
270
271
                                // Match that data with mapped lead fields
272
                                $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic');
273
                                if (!empty($fieldsToUpdateInMautic)) {
274
                                    $fieldsToUpdateInMautic = array_intersect_key($config['leadFields'], $fieldsToUpdateInMautic);
275
                                    $newMatchedFields       = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic));
276
                                } else {
277
                                    $newMatchedFields = $matchedFields;
278
                                }
279
280
                                // update values if already empty
281
                                foreach ($matchedFields as $field => $value) {
282
                                    if (empty($entity->getFieldValue($field))) {
283
                                        $newMatchedFields[$field] = $value;
284
                                    }
285
                                }
286
287
                                // remove unchanged fields
288
                                foreach ($newMatchedFields as $k => $v) {
289
                                    if ($entity->getFieldValue($k) === $v) {
290
                                        unset($newMatchedFields[$k]);
291
                                    }
292
                                }
293
294
                                if (count($newMatchedFields)) {
295
                                    $this->leadModel->setFieldValues($entity, $newMatchedFields, false, false);
296
                                    $this->leadModel->saveEntity($entity, false);
297
                                    $isModified = true;
298
                                }
299
                            } else {
300
                                /** @var Lead $entity */
301
                                $entity = $this->getMauticLead($entityData, true, null, null, $object);
302
                            }
303
304
                            if ($entity) {
305
                                $result[] = $entity->getEmail();
306
307
                                // Associate lead company
308
                                if (!empty($entityData['Company'])
309
                                    && $entityData['Company'] !== $this->translator->trans(
310
                                        'mautic.integration.form.lead.unknown'
311
                                    )
312
                                ) {
313
                                    $company = IdentifyCompanyHelper::identifyLeadsCompany(
314
                                        ['company' => $entityData['Company']],
315
                                        null,
316
                                        $this->companyModel
317
                                    );
318
319
                                    if (!empty($company[2])) {
320
                                        $syncLead = $this->companyModel->addLeadToCompany($company[2], $entity);
0 ignored issues
show
Unused Code introduced by
The assignment to $syncLead is dead and can be removed.
Loading history...
321
                                        $this->em->detach($company[2]);
322
                                    }
323
                                }
324
                            }
325
326
                            $mauticObjectReference = 'lead';
327
                        } elseif ('Contacts' === $object) {
328
                            $recordId = $entityData['CONTACTID'];
329
330
                            $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
331
                                'Zoho',
332
                                $object,
333
                                'lead',
334
                                null,
335
                                null,
336
                                null,
337
                                false,
338
                                0,
339
                                0,
340
                                [$recordId]
341
                            );
342
                            if (count($integrationId)) { // contact exists, then update
343
                                /** @var Lead $entity */
344
                                $entity        = $this->leadModel->getEntity($integrationId[0]['internal_entity_id']);
345
                                $matchedFields = $this->populateMauticLeadData($entityData, $config);
346
347
                                // Match that data with mapped lead fields
348
                                $fieldsToUpdateInMautic = $this->getPriorityFieldsForMautic($config, $object, 'mautic');
349
                                if (!empty($fieldsToUpdateInMautic)) {
350
                                    $fieldsToUpdateInMautic = array_intersect_key($config['leadFields'], $fieldsToUpdateInMautic);
351
                                    $newMatchedFields       = array_intersect_key($matchedFields, array_flip($fieldsToUpdateInMautic));
352
                                } else {
353
                                    $newMatchedFields = $matchedFields;
354
                                }
355
356
                                // update values if already empty
357
                                foreach ($matchedFields as $field => $value) {
358
                                    if (empty($entity->getFieldValue($field))) {
359
                                        $newMatchedFields[$field] = $value;
360
                                    }
361
                                }
362
363
                                // remove unchanged fields
364
                                foreach ($newMatchedFields as $k => $v) {
365
                                    if ($entity->getFieldValue($k) === $v) {
366
                                        unset($newMatchedFields[$k]);
367
                                    }
368
                                }
369
370
                                if (count($newMatchedFields)) {
371
                                    $this->leadModel->setFieldValues($entity, $newMatchedFields, false, false);
372
                                    $this->leadModel->saveEntity($entity, false);
373
                                    $isModified = true;
374
                                }
375
                            } else {
376
                                /** @var Lead $entity */
377
                                $entity = $this->getMauticLead($entityData, true, null, null, $object);
378
                            }
379
380
                            if ($entity) {
381
                                $result[] = $entity->getEmail();
382
383
                                // Associate lead company
384
                                if (!empty($entityData['AccountName'])
385
                                    && $entityData['AccountName'] !== $this->translator->trans(
386
                                        'mautic.integration.form.lead.unknown'
387
                                    )
388
                                ) {
389
                                    $company = IdentifyCompanyHelper::identifyLeadsCompany(
390
                                        ['company' => $entityData['AccountName']],
391
                                        null,
392
                                        $this->companyModel
393
                                    );
394
395
                                    if (!empty($company[2])) {
396
                                        $syncLead = $this->companyModel->addLeadToCompany($company[2], $entity);
397
                                        $this->em->detach($company[2]);
398
                                    }
399
                                }
400
                            }
401
402
                            $mauticObjectReference = 'lead';
403
                        } else {
404
                            $this->logIntegrationError(
405
                                new \Exception(
406
                                    sprintf('Received an unexpected object "%s"', $object)
407
                                )
408
                            );
409
                            continue;
410
                        }
411
412
                        if ($entity) {
413
                            $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
414
                                'Zoho',
415
                                $object,
416
                                $mauticObjectReference,
417
                                $entity->getId()
418
                            );
419
420
                            if (0 === count($integrationId)) {
421
                                $integrationEntity = new IntegrationEntity();
422
                                $integrationEntity->setDateAdded(new \DateTime());
423
                                $integrationEntity->setIntegration('Zoho');
424
                                $integrationEntity->setIntegrationEntity($object);
425
                                $integrationEntity->setIntegrationEntityId($recordId);
426
                                $integrationEntity->setInternalEntity($mauticObjectReference);
427
                                $integrationEntity->setInternalEntityId($entity->getId());
428
                                $integrationEntities[] = $integrationEntity;
429
                            } else {
430
                                $integrationEntity = $integrationEntityRepo->getEntity($integrationId[0]['id']);
431
                                if ($isModified) {
432
                                    $integrationEntity->setLastSyncDate(new \DateTime());
433
                                    $integrationEntities[] = $integrationEntity;
434
                                }
435
                            }
436
                            $this->em->detach($entity);
437
                            unset($entity);
438
                        } else {
439
                            continue;
440
                        }
441
                    }
442
443
                    $this->em->getRepository('MauticPluginBundle:IntegrationEntity')->saveEntities($integrationEntities);
444
                    $this->em->clear('Mautic\PluginBundle\Entity\IntegrationEntity');
445
                }
446
            }
447
            unset($integrationEntities);
448
        }
449
450
        return $result;
451
    }
452
453
    /**
454
     * @param array  $params
455
     * @param string $query
456
     * @param        $executed
457
     * @param array  $result
458
     * @param string $object
459
     *
460
     * @return int
461
     */
462
    public function getLeads($params, $query, &$executed, $result = [], $object = 'Lead')
463
    {
464
        if ('Lead' === $object || 'Contact' === $object) {
465
            $object .= 's'; // pluralize object name for Zoho
466
        }
467
468
        $executed = 0;
469
470
        try {
471
            if ($this->isAuthorized()) {
472
                $config           = $this->mergeConfigToFeatureSettings();
473
                $fields           = $config['leadFields'];
474
                $config['object'] = $object;
475
                $aFields          = $this->getAvailableLeadFields($config);
476
                $mappedData       = [];
477
478
                foreach (array_keys($fields) as $k) {
479
                    if (isset($aFields[$object][$k])) {
480
                        $mappedData[] = $aFields[$object][$k]['dv'];
481
                    }
482
                }
483
484
                $maxRecords               = 200;
485
                $fields                   = implode(',', $mappedData);
486
                $oparams['selectColumns'] = $object.'('.$fields.')';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$oparams was never initialized. Although not strictly required by PHP, it is generally a good practice to add $oparams = array(); before regardless.
Loading history...
487
                $oparams['toIndex']       = $maxRecords; // maximum number of records
488
                if (isset($params['fetchAll'], $params['start']) && !$params['fetchAll']) {
489
                    $oparams['lastModifiedTime'] = date('Y-m-d H:i:s', strtotime($params['start']));
490
                }
491
492
                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
493
                    $progress = new ProgressBar($params['output']);
494
                    $progress->start();
495
                }
496
497
                while (true) {
498
                    // {"response":{"nodata":{"code":"4422","message":"There is no data to show"},"uri":"/crm/private/json/Contacts/getRecords"}}
499
                    $data = $this->getApiHelper()->getLeads($oparams, $object);
500
                    if (!isset($data[$object])) {
501
                        break; // no more data, exit loop
502
                    }
503
                    $result = $this->amendLeadDataBeforeMauticPopulate($data, $object);
504
                    $executed += count($result);
505
                    if (isset($params['output'])) {
506
                        if ($params['output']->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
507
                            $params['output']->writeln($result);
508
                        } else {
509
                            $progress->advance();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $progress does not seem to be defined for all execution paths leading up to this point.
Loading history...
510
                        }
511
                    }
512
513
                    // prepare next loop
514
                    $oparams['fromIndex'] = $oparams['toIndex'] + 1;
515
                    $oparams['toIndex'] += $maxRecords;
516
                }
517
518
                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
519
                    $progress->finish();
520
                }
521
            }
522
        } catch (\Exception $e) {
523
            $this->logIntegrationError($e);
524
        }
525
526
        return $executed;
527
    }
528
529
    /**
530
     * @param array $params
531
     * @param null  $query
532
     * @param null  $executed
533
     * @param array $result
534
     *
535
     * @return int|null
536
     */
537
    public function getCompanies($params = [], $query = null, &$executed = null, &$result = [])
0 ignored issues
show
Unused Code introduced by
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

537
    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...
538
    {
539
        $executed = 0;
540
        $object   = 'company';
541
542
        try {
543
            if ($this->isAuthorized()) {
544
                $config           = $this->mergeConfigToFeatureSettings();
545
                $fields           = $config['companyFields'];
546
                $config['object'] = $object;
547
                $aFields          = $this->getAvailableLeadFields($config);
548
                $mappedData       = [];
549
                if (isset($aFields['company'])) {
550
                    $aFields = $aFields['company'];
551
                }
552
                foreach (array_keys($fields) as $k) {
553
                    $mappedData[] = $aFields[$k]['dv'];
554
                }
555
556
                $fields = implode(',', $mappedData);
557
558
                $maxRecords               = 200;
559
                $oparams['selectColumns'] = 'Accounts('.$fields.')';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$oparams was never initialized. Although not strictly required by PHP, it is generally a good practice to add $oparams = array(); before regardless.
Loading history...
560
                $oparams['toIndex']       = $maxRecords; // maximum number of records
561
                if (isset($params['fetchAll'], $params['start']) && !$params['fetchAll']) {
562
                    $oparams['lastModifiedTime'] = date('Y-m-d H:i:s', strtotime($params['start']));
563
                }
564
565
                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
566
                    $progress = new ProgressBar($params['output']);
567
                    $progress->start();
568
                }
569
570
                while (true) {
571
                    $data = $this->getApiHelper()->getCompanies($oparams);
572
                    if (!isset($data['Accounts'])) {
573
                        break; // no more data, exit loop
574
                    }
575
                    $result = $this->amendLeadDataBeforeMauticPopulate($data, $object);
576
                    $executed += count($result);
577
                    if (isset($params['output'])) {
578
                        if ($params['output']->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
579
                            $params['output']->writeln($result);
580
                        } else {
581
                            $progress->advance();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $progress does not seem to be defined for all execution paths leading up to this point.
Loading history...
582
                        }
583
                    }
584
585
                    // prepare next loop
586
                    $oparams['fromIndex'] = $oparams['toIndex'] + 1;
587
                    $oparams['toIndex'] += $maxRecords;
588
                }
589
590
                if (isset($params['output']) && $params['output']->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
591
                    $progress->finish();
592
                }
593
594
                return $executed;
595
            }
596
        } catch (\Exception $e) {
597
            $this->logIntegrationError($e);
598
        }
599
600
        return $executed;
601
    }
602
603
    /**
604
     * @param Form|FormBuilder $builder
605
     * @param array            $data
606
     * @param string           $formArea
607
     *
608
     * @throws \InvalidArgumentException
609
     */
610
    public function appendToForm(&$builder, $data, $formArea)
611
    {
612
        if ($formArea == 'features') {
613
            $builder->add(
0 ignored issues
show
Bug introduced by
The method add() does not exist on Mautic\FormBundle\Entity\Form. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

613
            $builder->/** @scrutinizer ignore-call */ 
614
                      add(
Loading history...
614
                'updateBlanks',
615
                'choice',
616
                [
617
                    'choices' => [
618
                        'updateBlanks' => 'mautic.integrations.blanks',
619
                    ],
620
                    'expanded'    => true,
621
                    'multiple'    => true,
622
                    'label'       => 'mautic.integrations.form.blanks',
623
                    'label_attr'  => ['class' => 'control-label'],
624
                    'empty_value' => false,
625
                    'required'    => false,
626
                ]
627
            );
628
        }
629
        if ($formArea === 'keys') {
630
            $builder->add(
631
                'datacenter',
632
                'choice',
633
                [
634
                    'choices' => [
635
                        'zoho.com'    => 'mautic.plugin.zoho.zone_us',
636
                        'zoho.eu'     => 'mautic.plugin.zoho.zone_europe',
637
                        'zoho.co.jp'  => 'mautic.plugin.zoho.zone_japan',
638
                        'zoho.com.cn' => 'mautic.plugin.zoho.zone_china',
639
                    ],
640
                    'label'       => 'mautic.plugin.zoho.zone_select',
641
                    'empty_value' => false,
642
                    'required'    => true,
643
                    'attr'        => [
644
                        'tooltip' => 'mautic.plugin.zoho.zone.tooltip',
645
                    ],
646
                ]
647
            );
648
        } elseif ('features' === $formArea) {
649
            $builder->add(
650
                'objects',
651
                'choice',
652
                [
653
                    'choices' => [
654
                        'Leads'    => 'mautic.zoho.object.lead',
655
                        'Contacts' => 'mautic.zoho.object.contact',
656
                        'company'  => 'mautic.zoho.object.account',
657
                    ],
658
                    'expanded'    => true,
659
                    'multiple'    => true,
660
                    'label'       => $this->getTranslator()->trans('mautic.crm.form.objects_to_pull_from', ['%crm%' => 'Zoho']),
661
                    'label_attr'  => ['class' => ''],
662
                    'empty_value' => false,
663
                    'required'    => false,
664
                ]
665
            );
666
        }
667
    }
668
669
    /**
670
     * @param array $settings
671
     * @param array $parameters
672
     *
673
     * @return bool|string
674
     *
675
     * @throws \InvalidArgumentException
676
     */
677
    public function authCallback($settings = [], $parameters = [])
678
    {
679
        $request_url = sprintf('https://accounts.%s/apiauthtoken/nb/create', $this->getDatacenter());
680
        $parameters  = array_merge(
681
            $parameters,
682
            [
683
                'SCOPE'    => 'ZohoCRM/crmapi',
684
                'EMAIL_ID' => $this->keys[$this->getClientIdKey()],
685
                'PASSWORD' => $this->keys[$this->getClientSecretKey()],
686
            ]
687
        );
688
689
        $response = $this->makeRequest($request_url, $parameters, 'POST', ['authorize_session' => true]);
690
691
        if ($response['RESULT'] == 'FALSE') {
692
            return $this->translator->trans('mautic.zoho.auth_error', ['%cause%' => (isset($response['CAUSE']) ? $response['CAUSE'] : 'UNKNOWN')]);
693
        }
694
695
        return $this->extractAuthKeys($response);
696
    }
697
698
    /**
699
     * {@inheritdoc}
700
     *
701
     * @param string $data
702
     * @param bool   $postAuthorization
703
     *
704
     * @return mixed
705
     */
706
    public function parseCallbackResponse($data, $postAuthorization = false)
707
    {
708
        if ($postAuthorization) {
709
            /*
710
            #
711
            #Wed Feb 29 03:07:33 PST 2012
712
            AUTHTOKEN=bad18eba1ff45jk7858b8ae88a77fa30
713
            RESULT=TRUE
714
            */
715
            preg_match_all('(\w*=\w*)', $data, $matches);
716
            if (!empty($matches[0])) {
717
                /** @var array $match */
718
                $match      = $matches[0];
719
                $attributes = [];
720
                foreach ($match as $string_attribute) {
721
                    $parts                 = explode('=', $string_attribute);
722
                    $attributes[$parts[0]] = $parts[1];
723
                }
724
725
                return $attributes;
726
            }
727
728
            return [];
729
        }
730
731
        return parent::parseCallbackResponse($data, $postAuthorization);
732
    }
733
734
    /**
735
     * Get available company fields for choices in the config UI.
736
     *
737
     * @param array $settings
738
     *
739
     * @return array
740
     */
741
    public function getFormCompanyFields($settings = [])
742
    {
743
        return $this->getFormFieldsByObject('Accounts', $settings);
744
    }
745
746
    /**
747
     * @param array $settings
748
     *
749
     * @return array|mixed
750
     */
751
    public function getFormLeadFields($settings = [])
752
    {
753
        $leadFields    = $this->getFormFieldsByObject('Leads', $settings);
754
        $contactFields = $this->getFormFieldsByObject('Contacts', $settings);
755
756
        return array_merge($leadFields, $contactFields);
757
    }
758
759
    /**
760
     * @param array $settings
761
     *
762
     * @return array|bool
763
     *
764
     * @throws ApiErrorException
765
     */
766
    public function getAvailableLeadFields($settings = [])
767
    {
768
        $zohoFields        = [];
769
        $silenceExceptions = isset($settings['silence_exceptions']) ? $settings['silence_exceptions'] : true;
770
771
        if (isset($settings['feature_settings']['objects'])) {
772
            $zohoObjects = $settings['feature_settings']['objects'];
773
        } else {
774
            $settings    = $this->settings->getFeatureSettings();
775
            $zohoObjects = isset($settings['objects']) ? $settings['objects'] : ['Leads'];
776
        }
777
778
        try {
779
            if ($this->isAuthorized()) {
780
                if (!empty($zohoObjects) && is_array($zohoObjects)) {
781
                    foreach ($zohoObjects as $key => $zohoObject) {
782
                        // Check the cache first
783
                        $settings['cache_suffix'] = $cacheSuffix = '.'.$zohoObject;
784
                        if ($fields = parent::getAvailableLeadFields($settings)) {
785
                            $zohoFields[$zohoObject] = $fields;
786
787
                            continue;
788
                        }
789
                        $leadObject = $this->getApiHelper()->getLeadFields($zohoObject);
790
791
                        if (null === $leadObject || (isset($leadObject['response']) && isset($leadObject['response']['error']))) {
792
                            return [];
793
                        }
794
795
                        $objKey = 'company' === $zohoObject ? 'Accounts' : $zohoObject;
796
                        /** @var array $opts */
797
                        $opts = $leadObject[$objKey]['section'];
798
                        foreach ($opts as $optgroup) {
799
                            if (!array_key_exists(0, $optgroup['FL'])) {
800
                                $optgroup['FL'] = [$optgroup['FL']];
801
                            }
802
                            foreach ($optgroup['FL'] as $field) {
803
                                if (!(bool) $field['isreadonly'] || in_array($field['type'], ['OwnerLookup'], true)) {
804
                                    continue;
805
                                }
806
807
                                $zohoFields[$zohoObject][$this->getFieldKey($field['dv'])] = [
808
                                    'type'     => 'string',
809
                                    'label'    => $field['label'],
810
                                    'dv'       => $field['dv'],
811
                                    'required' => $field['req'] === 'true',
812
                                ];
813
                            }
814
                        }
815
                        if (empty($settings['ignore_field_cache'])) {
816
                            $this->cache->set('leadFields'.$cacheSuffix, $zohoFields[$zohoObject]);
817
                        }
818
                    }
819
                }
820
            }
821
        } catch (ApiErrorException $exception) {
822
            $this->logIntegrationError($exception);
823
824
            if (!$silenceExceptions) {
825
                if (strpos($exception->getMessage(), 'Invalid Ticket Id') !== false) {
826
                    // Use a bit more friendly message
827
                    $exception = new ApiErrorException('There was an issue with communicating with Zoho. Please try to reauthorize.');
828
                }
829
830
                throw $exception;
831
            }
832
833
            return false;
834
        }
835
836
        return $zohoFields;
837
    }
838
839
    /**
840
     * {@inheritdoc}
841
     *
842
     * @param $key
843
     * @param $field
844
     *
845
     * @return array
846
     */
847
    public function convertLeadFieldKey($key, $field)
848
    {
849
        return [$key, $field['dv']];
850
    }
851
852
    /**
853
     * @param Lead  $lead
854
     * @param array $config
855
     *
856
     * @return string
857
     */
858
    public function populateLeadData($lead, $config = [])
859
    {
860
        $config['object'] = 'Leads';
861
        $mappedData       = parent::populateLeadData($lead, $config);
862
        $writer           = new Writer($config['object']);
863
        if ($lead instanceof Lead) {
0 ignored issues
show
introduced by
$lead is always a sub-type of Mautic\LeadBundle\Entity\Lead.
Loading history...
864
            $row = $writer->row($lead->getId());
865
        } else {
866
            $row = $writer->row($lead['id']);
867
        }
868
        foreach ($mappedData as $name => $value) {
869
            $row->add($name, $value);
870
        }
871
872
        return $writer->write();
873
    }
874
875
    /**
876
     * @param $dv
877
     *
878
     * @return string
879
     */
880
    protected function getFieldKey($dv)
881
    {
882
        return InputHelper::alphanum(InputHelper::transliterate($dv));
883
    }
884
885
    /**
886
     * @param array $params
887
     *
888
     * @return mixed
889
     */
890
    public function pushLeads($params = [])
891
    {
892
        $maxRecords = (isset($params['limit']) && $params['limit'] < 100) ? $params['limit'] : 100;
893
        if (isset($params['fetchAll']) && $params['fetchAll']) {
894
            $params['start'] = null;
895
            $params['end']   = null;
896
        }
897
        $config                = $this->mergeConfigToFeatureSettings();
898
        $integrationEntityRepo = $this->em->getRepository('MauticPluginBundle:IntegrationEntity');
899
        $fieldsToUpdateInZoho  = isset($config['update_mautic']) ? array_keys($config['update_mautic'], 0) : [];
900
        $leadFields            = array_unique(array_values($config['leadFields']));
901
        $totalUpdated          = $totalCreated          = $totalErrors          = 0;
902
        if ($key = array_search('mauticContactTimelineLink', $leadFields)) {
903
            unset($leadFields[$key]);
904
        }
905
        if ($key = array_search('mauticContactIsContactableByEmail', $leadFields)) {
906
            unset($leadFields[$key]);
907
        }
908
        if (empty($leadFields)) {
909
            return [0, 0, 0];
910
        }
911
912
        $fields = implode(', l.', $leadFields);
913
        $fields = 'l.'.$fields;
914
915
        $availableFields            = $this->getAvailableLeadFields(['feature_settings' => ['objects' => ['Leads', 'Contacts']]]);
916
        $fieldsToUpdate['Leads']    = array_values(array_intersect(array_keys($availableFields['Leads']), $fieldsToUpdateInZoho));
0 ignored issues
show
Comprehensibility Best Practice introduced by
$fieldsToUpdate was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fieldsToUpdate = array(); before regardless.
Loading history...
917
        $fieldsToUpdate['Contacts'] = array_values(array_intersect(array_keys($availableFields['Contacts']), $fieldsToUpdateInZoho));
918
        $fieldsToUpdate['Leads']    = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdate['Leads']));
919
        $fieldsToUpdate['Contacts'] = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdate['Contacts']));
920
921
        $progress      = false;
922
        $totalToUpdate = array_sum(
923
            $integrationEntityRepo->findLeadsToUpdate('Zoho', 'lead', $fields, false, $params['start'], $params['end'], ['Contacts', 'Leads'])
0 ignored issues
show
Bug introduced by
It seems like $integrationEntityRepo->...y('Contacts', 'Leads')) can also be of type object; however, parameter $array of array_sum() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

923
            /** @scrutinizer ignore-type */ $integrationEntityRepo->findLeadsToUpdate('Zoho', 'lead', $fields, false, $params['start'], $params['end'], ['Contacts', 'Leads'])
Loading history...
924
        );
925
        $totalToCreate = $integrationEntityRepo->findLeadsToCreate('Zoho', $fields, false, $params['start'], $params['end']);
926
        $totalCount    = $totalToCreate + $totalToUpdate;
927
928
        if (defined('IN_MAUTIC_CONSOLE')) {
929
            // start with update
930
            if ($totalToUpdate + $totalToCreate) {
931
                $output = new ConsoleOutput();
932
                $output->writeln("About $totalToUpdate to update and about $totalToCreate to create/update");
933
                $progress = new ProgressBar($output, $totalCount);
934
            }
935
        }
936
937
        // Start with contacts so we know who is a contact when we go to process converted leads
938
        $leadsToCreateInZ    = [];
939
        $leadsToUpdateInZ    = [];
940
        $isContact           = [];
941
        $integrationEntities = [];
942
943
        // Fetch them separately so we can determine which oneas are already there
944
        $toUpdate = $integrationEntityRepo->findLeadsToUpdate(
945
            'Zoho',
946
            'lead',
947
            $fields,
948
            $totalToUpdate,
949
            $params['start'],
950
            $params['end'],
951
            'Contacts',
952
            []
953
        )['Contacts'];
954
955
        if (is_array($toUpdate)) {
956
            foreach ($toUpdate as $lead) {
957
                if (isset($lead['email']) && !empty($lead['email'])) {
958
                    $key                        = mb_strtolower($this->cleanPushData($lead['email']));
959
                    $lead['integration_entity'] = 'Contacts';
960
                    $leadsToUpdateInZ[$key]     = $lead;
961
                    $isContact[$key]            = $lead;
962
                }
963
            }
964
        }
965
966
        // Switch to Lead
967
        $toUpdate = $integrationEntityRepo->findLeadsToUpdate(
968
            'Zoho',
969
            'lead',
970
            $fields,
971
            $totalToUpdate,
972
            $params['start'],
973
            $params['end'],
974
            'Leads',
975
            []
976
        )['Leads'];
977
978
        if (is_array($toUpdate)) {
979
            foreach ($toUpdate as $lead) {
980
                if (isset($lead['email']) && !empty($lead['email'])) {
981
                    $key  = mb_strtolower($this->cleanPushData($lead['email']));
982
                    $lead = $this->getCompoundMauticFields($lead);
983
                    if (isset($isContact[$key])) {
984
                        $isContact[$key] = $lead; // lead-converted
985
                    } else {
986
                        $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
987
                            'Zoho',
988
                            'Leads',
989
                            'lead',
990
                            $lead['internal_entity_id']
991
                        );
992
993
                        $lead['integration_entity'] = 'Leads';
994
                        $leadsToUpdateInZ[$key]     = $lead;
995
                        $integrationEntity          = $this->em->getReference('MauticPluginBundle:IntegrationEntity', $integrationId[0]['id']);
996
                        $integrationEntities[]      = $integrationEntity->setLastSyncDate(new \DateTime());
997
                    }
998
                }
999
            }
1000
        }
1001
        unset($toUpdate);
1002
1003
        // convert ignored contacts
1004
        foreach ($isContact as $email => $lead) {
1005
            $integrationId = $integrationEntityRepo->getIntegrationsEntityId(
1006
                'Zoho',
1007
                'Leads',
1008
                'lead',
1009
                $lead['internal_entity_id']
1010
            );
1011
            if (count($integrationId)) { // lead exists, then update
1012
                $integrationEntity     = $this->em->getReference('MauticPluginBundle:IntegrationEntity', $integrationId[0]['id']);
1013
                $integrationEntity->setLastSyncDate(new \DateTime());
1014
                $integrationEntity->setInternalEntity('lead-converted');
1015
                $integrationEntities[] = $integrationEntity;
1016
                unset($leadsToUpdateInZ[$email]);
1017
            }
1018
        }
1019
1020
        //create lead records, including deleted on Zoho side (last_sync = null)
1021
        /** @var array $leadsToCreate */
1022
        $leadsToCreate = $integrationEntityRepo->findLeadsToCreate('Zoho', $fields, $totalToCreate, $params['start'], $params['end']);
1023
1024
        if (is_array($leadsToCreate)) {
0 ignored issues
show
introduced by
The condition is_array($leadsToCreate) is always true.
Loading history...
1025
            foreach ($leadsToCreate as $lead) {
1026
                if (isset($lead['email']) && !empty($lead['email'])) {
1027
                    $key                        = mb_strtolower($this->cleanPushData($lead['email']));
1028
                    $lead                       = $this->getCompoundMauticFields($lead);
1029
                    $lead['integration_entity'] = 'Leads';
1030
                    $leadsToCreateInZ[$key]     = $lead;
1031
                }
1032
            }
1033
        }
1034
        unset($leadsToCreate);
1035
1036
        if (count($integrationEntities)) {
1037
            // Persist updated entities if applicable
1038
            $integrationEntityRepo->saveEntities($integrationEntities);
1039
            $this->em->clear(IntegrationEntity::class);
1040
        }
1041
1042
        // update leads and contacts
1043
        $mapper = new Mapper($availableFields);
1044
        foreach (['Leads', 'Contacts'] as $zObject) {
1045
            $counter = 1;
1046
            $mapper->setObject($zObject);
1047
            foreach ($leadsToUpdateInZ as $email => $lead) {
1048
                if ($zObject !== $lead['integration_entity']) {
1049
                    continue;
1050
                }
1051
1052
                if ($progress) {
1053
                    $progress->advance();
1054
                }
1055
1056
                $existingPerson           = $this->getExistingRecord('email', $lead['email'], $zObject);
1057
                $objectFields             = $this->prepareFieldsForPush($availableFields[$zObject]);
1058
                $fieldsToUpdate[$zObject] = $this->getBlankFieldsToUpdate($fieldsToUpdate[$zObject], $existingPerson, $objectFields, $config);
1059
1060
                $totalUpdated += $mapper
1061
                    ->setMappedFields($fieldsToUpdate[$zObject])
1062
                    ->setContact($lead)
1063
                    ->map($lead['internal_entity_id'], $lead['integration_entity_id']);
1064
                ++$counter;
1065
1066
                // ONLY 100 RECORDS CAN BE SENT AT A TIME
1067
                if ($maxRecords === $counter) {
1068
                    $this->updateContactInZoho($mapper, $zObject, $totalUpdated, $totalErrors);
1069
                    $counter = 1;
1070
                }
1071
            }
1072
1073
            if ($counter > 1) {
1074
                $this->updateContactInZoho($mapper, $zObject, $totalUpdated, $totalErrors);
1075
            }
1076
        }
1077
1078
        // create leads and contacts
1079
        foreach (['Leads', 'Contacts'] as $zObject) {
1080
            $counter = 1;
1081
            $mapper->setObject($zObject);
1082
            foreach ($leadsToCreateInZ as $email => $lead) {
1083
                if ($zObject !== $lead['integration_entity']) {
1084
                    continue;
1085
                }
1086
                if ($progress) {
1087
                    $progress->advance();
1088
                }
1089
1090
                $totalCreated += $mapper
1091
                    ->setMappedFields($config['leadFields'])
1092
                    ->setContact($lead)
1093
                    ->map($lead['internal_entity_id']);
1094
                ++$counter;
1095
1096
                // ONLY 100 RECORDS CAN BE SENT AT A TIME
1097
                if ($maxRecords === $counter) {
1098
                    $this->createContactInZoho($mapper, $zObject, $totalCreated, $totalErrors);
1099
                    $counter = 1;
1100
                }
1101
            }
1102
1103
            if ($counter > 1) {
1104
                $this->createContactInZoho($mapper, $zObject, $totalCreated, $totalErrors);
1105
            }
1106
        }
1107
1108
        if ($progress) {
1109
            $progress->finish();
1110
            $output->writeln('');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $output does not seem to be defined for all execution paths leading up to this point.
Loading history...
1111
        }
1112
1113
        return [$totalUpdated, $totalCreated, $totalErrors, $totalCount - ($totalCreated + $totalUpdated + $totalErrors)];
1114
    }
1115
1116
    /**
1117
     * @param Lead|array $lead
1118
     * @param array      $config
1119
     *
1120
     * @return array|bool
1121
     */
1122
    public function pushLead($lead, $config = [])
1123
    {
1124
        $config  = $this->mergeConfigToFeatureSettings($config);
1125
        $zObject = 'Leads';
1126
1127
        $fieldsToUpdateInZoho       = isset($config['update_mautic']) ? array_keys($config['update_mautic'], 0) : [];
1128
        $availableFields            = $this->getAvailableLeadFields(['feature_settings' => ['objects' => ['Leads', 'Contacts']]]);
1129
        $fieldsToUpdate['Leads']    = array_values(array_intersect(array_keys($availableFields['Leads']), $fieldsToUpdateInZoho));
0 ignored issues
show
Comprehensibility Best Practice introduced by
$fieldsToUpdate was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fieldsToUpdate = array(); before regardless.
Loading history...
1130
        $fieldsToUpdate['Contacts'] = array_values(array_intersect(array_keys($availableFields['Contacts']), $fieldsToUpdateInZoho));
1131
        $fieldsToUpdate['Leads']    = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdate['Leads']));
1132
        $fieldsToUpdate['Contacts'] = array_intersect_key($config['leadFields'], array_flip($fieldsToUpdate['Contacts']));
1133
        $objectFields               = $this->prepareFieldsForPush($availableFields[$zObject]);
1134
        $existingPerson             = $this->getExistingRecord('email', $lead->getEmail(), $zObject);
1135
        $fieldsToUpdate[$zObject]   = $this->getBlankFieldsToUpdate($fieldsToUpdate[$zObject], $existingPerson, $objectFields, $config);
1136
1137
        if (empty($config['leadFields'])) {
1138
            return [];
1139
        }
1140
1141
        $mappedData = $this->populateLeadData($lead, $config);
0 ignored issues
show
Bug introduced by
It seems like $lead can also be of type array; however, parameter $lead of MauticPlugin\MauticCrmBu...ion::populateLeadData() does only seem to accept Mautic\LeadBundle\Entity\Lead, maybe add an additional type check? ( Ignorable by Annotation )

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

1141
        $mappedData = $this->populateLeadData(/** @scrutinizer ignore-type */ $lead, $config);
Loading history...
1142
1143
        $this->amendLeadDataBeforePush($mappedData);
1144
1145
        if (empty($mappedData)) {
1146
            return false;
1147
        }
1148
        $mapper    = new Mapper($availableFields);
1149
        $mapper->setObject($zObject);
1150
1151
        $integrationEntityRepo = $this->em->getRepository('MauticPluginBundle:IntegrationEntity');
1152
        $integrationId         = $integrationEntityRepo->getIntegrationsEntityId('Zoho', $zObject, 'lead', $lead->getId());
1153
1154
        $counter      = 0;
1155
        $errorCounter = 0;
1156
        try {
1157
            if ($this->isAuthorized()) {
1158
                if (!empty($existingPerson) && empty($integrationId)) {
1159
                    /** @var IntegrationEntity $integrationEntity */
1160
                    $integrationEntity = $this->createIntegrationEntity($zObject, $existingPerson['LEADID'], 'lead', $lead->getId());
1161
                    $mapper
1162
                        ->setMappedFields($fieldsToUpdate[$zObject])
1163
                        ->setContact($lead->getProfileFields())
1164
                        ->map($lead->getId(), $integrationEntity->getIntegrationEntityId());
1165
                    $this->updateContactInZoho($mapper, $zObject, $counter, $errorCounter);
1166
                } elseif (!empty($existingPerson) && !empty($integrationId)) { // contact exists, then update
1167
                    $mapper
1168
                        ->setMappedFields($fieldsToUpdate[$zObject])
1169
                        ->setContact($lead->getProfileFields())
1170
                        ->map($lead->getId(), $integrationId[0]['integration_entity_id']);
1171
                    $this->updateContactInZoho($mapper, $zObject, $counter, $errorCounter);
1172
                } else {
1173
                    $mapper
1174
                        ->setMappedFields($config['leadFields'])
1175
                        ->setContact($lead->getProfileFields())
1176
                        ->map($lead->getId());
1177
                    $this->createContactInZoho($mapper, $zObject, $counter, $errorCounter);
1178
                }
1179
1180
                return true;
1181
            }
1182
        } catch (\Exception $e) {
1183
            $this->logIntegrationError($e);
1184
        }
1185
1186
        return false;
1187
    }
1188
1189
    /**
1190
     * @param $fields
1191
     * @param $sfRecord
1192
     * @param $config
1193
     * @param $objectFields
1194
     */
1195
    public function getBlankFieldsToUpdate($fields, $sfRecord, $objectFields, $config)
1196
    {
1197
        //check if update blank fields is selected
1198
        if (isset($config['updateBlanks']) && isset($config['updateBlanks'][0]) && $config['updateBlanks'][0] == 'updateBlanks') {
1199
            foreach ($sfRecord as $fieldName => $sfField) {
1200
                if (array_key_exists($fieldName, $objectFields['required']['fields'])) {
1201
                    continue; // this will be treated differently
1202
                }
1203
                if ($sfField === 'null' && array_key_exists($fieldName, $objectFields['create']) && !array_key_exists($fieldName, $fields)) {
1204
                    //map to mautic field
1205
                    $fields[$fieldName] = $objectFields['create'][$fieldName];
1206
                }
1207
            }
1208
        }
1209
1210
        return $fields;
1211
    }
1212
1213
    /**
1214
     * Get if data priority is enabled in the integration or not default is false.
1215
     *
1216
     * @return string
1217
     */
1218
    public function getDataPriority()
1219
    {
1220
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type string.
Loading history...
1221
    }
1222
1223
    /**
1224
     * @param      $response
1225
     * @param      $zObject
1226
     * @param bool $createIntegrationEntity
1227
     *
1228
     * @return int
1229
     */
1230
    private function consumeResponse($response, $zObject, $createIntegrationEntity = false)
1231
    {
1232
        $text = preg_replace('/_/', ' ', implode('', array_keys($response))).'='.implode('', array_values($response));
1233
        $xml  = simplexml_load_string($text);
1234
        $doc  = json_decode(json_encode((array) $xml), true);
1235
        $rows = $doc['result'];
1236
1237
        // row[] will be an array of responses if there were multiple or an array of a single response if not
1238
        if (isset($rows['row'][0])) {
1239
            $rows = $rows['row'];
1240
        }
1241
1242
        $failed = 0;
1243
        foreach ($rows as $row) {
1244
            $leadId = $row['@attributes']['no'];
1245
1246
            if (isset($row['success']) && $createIntegrationEntity) {
1247
                $fl = $row['success']['details']['FL'];
1248
                if (isset($fl[0])) {
1249
                    $this->logger->debug('CREATE INTEGRATION ENTITY: '.$fl[0]);
1250
                    $integrationId = $this->getIntegrationEntityRepository()->getIntegrationsEntityId(
1251
                        'Zoho',
1252
                        $zObject,
1253
                        'lead',
1254
                        null,
1255
                        null,
1256
                        null,
1257
                        false,
1258
                        0,
1259
                        0,
1260
                        [$fl[0]]
1261
                    );
1262
1263
                    if (0 === count($integrationId)) {
1264
                        $this->createIntegrationEntity($zObject, $fl[0], 'lead', $leadId);
1265
                    } else {
1266
                    }
1267
                }
1268
            } elseif (isset($row['error'])) {
1269
                ++$failed;
1270
                $exception = new ApiErrorException($row['error']['details'].' ('.$row['error']['code'].')');
1271
                $exception->setContactId($leadId);
1272
                $this->logIntegrationError($exception);
1273
            }
1274
        }
1275
1276
        return $failed;
1277
    }
1278
1279
    /**
1280
     * @param        $seachColumn
1281
     * @param        $searchValue
1282
     * @param string $object
1283
     *
1284
     * @return mixed
1285
     */
1286
    private function getExistingRecord($seachColumn, $searchValue, $object = 'Leads')
1287
    {
1288
        $availableFields = $this->getAvailableLeadFields(['feature_settings' => ['objects' => ['Leads', 'Contacts']]]);
1289
        $selectColumns   = implode(',', array_keys($availableFields[$object]));
1290
        $records         = $this->getApiHelper()->getSearchRecords($selectColumns, $seachColumn, $searchValue, $object);
1291
        $parsedRecords   = $this->parseZohoRecord($records, array_merge($availableFields[$object], ['LEADID' => ['dv'=>'LEADID']]), $object);
1292
1293
        return $parsedRecords;
1294
    }
1295
1296
    /**
1297
     * @param        $data
1298
     * @param        $fields
1299
     * @param string $object
1300
     *
1301
     * @return mixed
1302
     */
1303
    private function parseZohoRecord($data, $fields, $object = 'Leads')
1304
    {
1305
        $parsedData = [];
1306
1307
        if (!empty($data['response']['result'][$object]) && isset($data['response']['result'][$object]['row']['FL'])) {
1308
            $records = $data['response']['result'][$object]['row']['FL'];
1309
            foreach ($fields as $key => $field) {
1310
                foreach ($records as $record) {
1311
                    if ($record['val'] == $field['dv']) {
1312
                        $parsedData[$key] = $record['content'];
1313
                        continue;
1314
                    }
1315
                }
1316
            }
1317
        }
1318
1319
        return $parsedData;
1320
    }
1321
1322
    /**
1323
     * @param Mapper $mapper
1324
     * @param        $object
1325
     * @param        $counter
1326
     * @param        $totalCounter
1327
     */
1328
    private function updateContactInZoho(Mapper $mapper, $object, &$counter, &$errorCounter)
1329
    {
1330
        $response = $this->getApiHelper()->updateLead($mapper->getXml(), null, $object);
1331
        $failed   = $this->consumeResponse($response, $object);
1332
        $counter -= $failed;
1333
        $errorCounter += $failed;
1334
    }
1335
1336
    /**
1337
     * @param Mapper $mapper
1338
     * @param        $object
1339
     * @param        $counter
1340
     * @param        $totalCounter
1341
     */
1342
    private function createContactInZoho(Mapper $mapper, $object, &$counter, &$errorCounter)
1343
    {
1344
        $response = $this->getApiHelper()->createLead($mapper->getXml(), null, $object);
1345
        $failed   = $this->consumeResponse($response, $object, true);
1346
        $counter -= $failed;
1347
        $errorCounter += $failed;
1348
    }
1349
1350
    /**
1351
     * @param       $fieldsToUpdate
1352
     * @param array $objects
1353
     *
1354
     * @return array
1355
     */
1356
    protected function cleanPriorityFields($fieldsToUpdate, $objects = null)
1357
    {
1358
        if (null === $objects) {
1359
            $objects = ['Leads', 'Contacts'];
1360
        }
1361
1362
        if (isset($fieldsToUpdate['leadFields'])) {
1363
            // Pass in the whole config
1364
            $fields = $fieldsToUpdate;
1365
        } else {
1366
            $fields = array_flip($fieldsToUpdate);
1367
        }
1368
1369
        $fieldsToUpdate = $this->prepareFieldsForSync($fields, $fieldsToUpdate, $objects);
1370
1371
        return $fieldsToUpdate;
1372
    }
1373
1374
    /**
1375
     * @param array $fields
1376
     * @param array $keys
1377
     * @param mixed $object
1378
     *
1379
     * @return array
1380
     */
1381
    public function prepareFieldsForSync($fields, $keys, $object = null)
1382
    {
1383
        $leadFields = [];
1384
        if (null === $object) {
1385
            $object = 'Leads';
1386
        }
1387
1388
        $objects = (!is_array($object)) ? [$object] : $object;
1389
        if (is_string($object) && 'Accounts' === $object) {
1390
            return isset($fields['companyFields']) ? $fields['companyFields'] : $fields;
1391
        }
1392
1393
        if (isset($fields['leadFields'])) {
1394
            $fields = $fields['leadFields'];
1395
            $keys   = array_keys($fields);
1396
        }
1397
1398
        foreach ($objects as $obj) {
1399
            if (!isset($leadFields[$obj])) {
1400
                $leadFields[$obj] = [];
1401
            }
1402
1403
            foreach ($keys as $key) {
1404
                $leadFields[$obj][$key] = $fields[$key];
1405
            }
1406
        }
1407
1408
        return (is_array($object)) ? $leadFields : $leadFields[$object];
1409
    }
1410
}
1411