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); |
|
|
|
|
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); |
|
|
|
|
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.')'; |
|
|
|
|
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(); |
|
|
|
|
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 = []) |
|
|
|
|
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.')'; |
|
|
|
|
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(); |
|
|
|
|
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( |
|
|
|
|
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) { |
|
|
|
|
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)); |
|
|
|
|
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']) |
|
|
|
|
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)) { |
|
|
|
|
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(''); |
|
|
|
|
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)); |
|
|
|
|
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); |
|
|
|
|
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; |
|
|
|
|
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
|
|
|
|
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.