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 Mautic\PluginBundle\Integration; |
||
13 | |||
14 | use Doctrine\ORM\EntityManager; |
||
15 | use Joomla\Http\HttpFactory; |
||
16 | use Mautic\CoreBundle\Entity\FormEntity; |
||
17 | use Mautic\CoreBundle\Helper\CacheStorageHelper; |
||
18 | use Mautic\CoreBundle\Helper\EncryptionHelper; |
||
19 | use Mautic\CoreBundle\Helper\PathsHelper; |
||
20 | use Mautic\CoreBundle\Model\NotificationModel; |
||
21 | use Mautic\LeadBundle\DataObject\LeadManipulator; |
||
22 | use Mautic\LeadBundle\Entity\DoNotContact; |
||
23 | use Mautic\LeadBundle\Entity\Lead; |
||
24 | use Mautic\LeadBundle\Model\CompanyModel; |
||
25 | use Mautic\LeadBundle\Model\DoNotContact as DoNotContactModel; |
||
26 | use Mautic\LeadBundle\Model\FieldModel; |
||
27 | use Mautic\LeadBundle\Model\LeadModel; |
||
28 | use Mautic\PluginBundle\Entity\Integration; |
||
29 | use Mautic\PluginBundle\Entity\IntegrationEntity; |
||
30 | use Mautic\PluginBundle\Entity\IntegrationEntityRepository; |
||
31 | use Mautic\PluginBundle\Event\PluginIntegrationAuthCallbackUrlEvent; |
||
32 | use Mautic\PluginBundle\Event\PluginIntegrationFormBuildEvent; |
||
33 | use Mautic\PluginBundle\Event\PluginIntegrationFormDisplayEvent; |
||
34 | use Mautic\PluginBundle\Event\PluginIntegrationKeyEvent; |
||
35 | use Mautic\PluginBundle\Event\PluginIntegrationRequestEvent; |
||
36 | use Mautic\PluginBundle\Exception\ApiErrorException; |
||
37 | use Mautic\PluginBundle\Helper\Cleaner; |
||
38 | use Mautic\PluginBundle\Helper\oAuthHelper; |
||
39 | use Mautic\PluginBundle\Model\IntegrationEntityModel; |
||
40 | use Mautic\PluginBundle\PluginEvents; |
||
41 | use Monolog\Logger; |
||
42 | use Psr\Log\LoggerInterface; |
||
43 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
||
44 | use Symfony\Component\Form\FormBuilder; |
||
45 | use Symfony\Component\HttpFoundation\Request; |
||
46 | use Symfony\Component\HttpFoundation\RequestStack; |
||
47 | use Symfony\Component\HttpFoundation\Session\Session; |
||
48 | use Symfony\Component\HttpFoundation\Session\SessionInterface; |
||
49 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface; |
||
50 | use Symfony\Component\Routing\Router; |
||
51 | use Symfony\Component\Translation\TranslatorInterface; |
||
52 | |||
53 | /** |
||
54 | * @method pushLead(Lead $lead, array $config = []) |
||
55 | * @method pushLeadToCampaign(Lead $lead, mixed $integrationCampaign, mixed $integrationMemberStatus) |
||
56 | * @method getLeads(array $params, string $query, &$executed, array $result = [], $object = 'Lead') |
||
57 | * @method getCompanies(array $params) |
||
58 | */ |
||
59 | abstract class AbstractIntegration implements UnifiedIntegrationInterface |
||
60 | { |
||
61 | const FIELD_TYPE_STRING = 'string'; |
||
62 | const FIELD_TYPE_BOOL = 'boolean'; |
||
63 | const FIELD_TYPE_NUMBER = 'number'; |
||
64 | const FIELD_TYPE_DATETIME = 'datetime'; |
||
65 | const FIELD_TYPE_DATE = 'date'; |
||
66 | |||
67 | /** |
||
68 | * @var bool |
||
69 | */ |
||
70 | protected $coreIntegration = false; |
||
71 | |||
72 | /** |
||
73 | * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface |
||
74 | */ |
||
75 | protected $dispatcher; |
||
76 | |||
77 | /** |
||
78 | * @var Integration |
||
79 | */ |
||
80 | protected $settings; |
||
81 | |||
82 | /** |
||
83 | * @var array Decrypted keys |
||
84 | */ |
||
85 | protected $keys = []; |
||
86 | |||
87 | /** |
||
88 | * @var CacheStorageHelper |
||
89 | */ |
||
90 | protected $cache; |
||
91 | |||
92 | /** |
||
93 | * @var \Doctrine\ORM\EntityManager |
||
94 | */ |
||
95 | protected $em; |
||
96 | |||
97 | /** |
||
98 | * @var SessionInterface|null |
||
99 | */ |
||
100 | protected $session; |
||
101 | |||
102 | /** |
||
103 | * @var Request|null |
||
104 | */ |
||
105 | protected $request; |
||
106 | |||
107 | /** |
||
108 | * @var Router |
||
109 | */ |
||
110 | protected $router; |
||
111 | |||
112 | /** |
||
113 | * @var LoggerInterface |
||
114 | */ |
||
115 | protected $logger; |
||
116 | |||
117 | /** |
||
118 | * @var TranslatorInterface |
||
119 | */ |
||
120 | protected $translator; |
||
121 | |||
122 | /** |
||
123 | * @var EncryptionHelper |
||
124 | */ |
||
125 | protected $encryptionHelper; |
||
126 | |||
127 | /** |
||
128 | * @var LeadModel |
||
129 | */ |
||
130 | protected $leadModel; |
||
131 | |||
132 | /** |
||
133 | * @var CompanyModel |
||
134 | */ |
||
135 | protected $companyModel; |
||
136 | |||
137 | /** |
||
138 | * @var PathsHelper |
||
139 | */ |
||
140 | protected $pathsHelper; |
||
141 | |||
142 | /** |
||
143 | * @var NotificationModel |
||
144 | */ |
||
145 | protected $notificationModel; |
||
146 | |||
147 | /** |
||
148 | * @var FieldModel |
||
149 | */ |
||
150 | protected $fieldModel; |
||
151 | |||
152 | /** |
||
153 | * Used for notifications. |
||
154 | * |
||
155 | * @var array|null |
||
156 | */ |
||
157 | protected $adminUsers; |
||
158 | |||
159 | /** |
||
160 | * @var array |
||
161 | */ |
||
162 | protected $notifications = []; |
||
163 | |||
164 | /** |
||
165 | * @var string|null |
||
166 | */ |
||
167 | protected $lastIntegrationError; |
||
168 | |||
169 | /** |
||
170 | * @var array |
||
171 | */ |
||
172 | protected $mauticDuplicates = []; |
||
173 | |||
174 | /** |
||
175 | * @var array |
||
176 | */ |
||
177 | protected $salesforceIdMapping = []; |
||
178 | |||
179 | /** |
||
180 | * @var array |
||
181 | */ |
||
182 | protected $deleteIntegrationEntities = []; |
||
183 | |||
184 | /** |
||
185 | * @var array |
||
186 | */ |
||
187 | protected $persistIntegrationEntities = []; |
||
188 | |||
189 | /** |
||
190 | * @var IntegrationEntityModel |
||
191 | */ |
||
192 | protected $integrationEntityModel; |
||
193 | |||
194 | /** |
||
195 | * @var DoNotContactModel |
||
196 | */ |
||
197 | protected $doNotContact; |
||
198 | |||
199 | /** |
||
200 | * @var array |
||
201 | */ |
||
202 | protected $commandParameters = []; |
||
203 | |||
204 | public function __construct( |
||
205 | EventDispatcherInterface $eventDispatcher, |
||
206 | CacheStorageHelper $cacheStorageHelper, |
||
207 | EntityManager $entityManager, |
||
208 | Session $session, |
||
209 | RequestStack $requestStack, |
||
210 | Router $router, |
||
211 | TranslatorInterface $translator, |
||
212 | Logger $logger, |
||
213 | EncryptionHelper $encryptionHelper, |
||
214 | LeadModel $leadModel, |
||
215 | CompanyModel $companyModel, |
||
216 | PathsHelper $pathsHelper, |
||
217 | NotificationModel $notificationModel, |
||
218 | FieldModel $fieldModel, |
||
219 | IntegrationEntityModel $integrationEntityModel, |
||
220 | DoNotContactModel $doNotContact |
||
221 | ) { |
||
222 | $this->dispatcher = $eventDispatcher; |
||
223 | $this->cache = $cacheStorageHelper->getCache($this->getName()); |
||
224 | $this->em = $entityManager; |
||
225 | $this->session = (!defined('IN_MAUTIC_CONSOLE')) ? $session : null; |
||
226 | $this->request = (!defined('IN_MAUTIC_CONSOLE')) ? $requestStack->getCurrentRequest() : null; |
||
227 | $this->router = $router; |
||
228 | $this->translator = $translator; |
||
229 | $this->logger = $logger; |
||
230 | $this->encryptionHelper = $encryptionHelper; |
||
231 | $this->leadModel = $leadModel; |
||
232 | $this->companyModel = $companyModel; |
||
233 | $this->pathsHelper = $pathsHelper; |
||
234 | $this->notificationModel = $notificationModel; |
||
235 | $this->fieldModel = $fieldModel; |
||
236 | $this->integrationEntityModel = $integrationEntityModel; |
||
237 | $this->doNotContact = $doNotContact; |
||
238 | } |
||
239 | |||
240 | public function setCommandParameters(array $params) |
||
241 | { |
||
242 | $this->commandParameters = $params; |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * @return CacheStorageHelper |
||
247 | */ |
||
248 | public function getCache() |
||
249 | { |
||
250 | return $this->cache; |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * @return \Mautic\CoreBundle\Translation\Translator |
||
255 | */ |
||
256 | public function getTranslator() |
||
257 | { |
||
258 | return $this->translator; |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * @return bool |
||
263 | */ |
||
264 | public function isCoreIntegration() |
||
265 | { |
||
266 | return $this->coreIntegration; |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Determines what priority the integration should have against the other integrations. |
||
271 | * |
||
272 | * @return int |
||
273 | */ |
||
274 | public function getPriority() |
||
275 | { |
||
276 | return 9999; |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * Determines if DNC records should be updated by date or by priority. |
||
281 | * |
||
282 | * @return int |
||
283 | */ |
||
284 | public function updateDncByDate() |
||
285 | { |
||
286 | return false; |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Returns the name of the social integration that must match the name of the file |
||
291 | * For example, IcontactIntegration would need Icontact here. |
||
292 | * |
||
293 | * @return string |
||
294 | */ |
||
295 | abstract public function getName(); |
||
296 | |||
297 | /** |
||
298 | * Name to display for the integration. e.g. iContact Uses value of getName() by default. |
||
299 | * |
||
300 | * @return string |
||
301 | */ |
||
302 | public function getDisplayName() |
||
303 | { |
||
304 | return $this->getName(); |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Returns a description shown in the config form. |
||
309 | * |
||
310 | * @return string |
||
311 | */ |
||
312 | public function getDescription() |
||
313 | { |
||
314 | return ''; |
||
315 | } |
||
316 | |||
317 | /** |
||
318 | * Get icon for Integration. |
||
319 | * |
||
320 | * @return string |
||
321 | */ |
||
322 | public function getIcon() |
||
323 | { |
||
324 | $systemPath = $this->pathsHelper->getSystemPath('root'); |
||
325 | $bundlePath = $this->pathsHelper->getSystemPath('bundles'); |
||
326 | $pluginPath = $this->pathsHelper->getSystemPath('plugins'); |
||
327 | $genericIcon = $bundlePath.'/PluginBundle/Assets/img/generic.png'; |
||
328 | |||
329 | $name = $this->getName(); |
||
330 | $bundle = $this->settings->getPlugin()->getBundle(); |
||
331 | $icon = $pluginPath.'/'.$bundle.'/Assets/img/'.strtolower($name).'.png'; |
||
332 | |||
333 | if (file_exists($systemPath.'/'.$icon)) { |
||
334 | return $icon; |
||
335 | } |
||
336 | |||
337 | return $genericIcon; |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Get the type of authentication required for this API. Values can be none, key, oauth2 or callback |
||
342 | * (will call $this->authenticationTypeCallback). |
||
343 | * |
||
344 | * @return string |
||
345 | */ |
||
346 | abstract public function getAuthenticationType(); |
||
347 | |||
348 | /** |
||
349 | * Get if data priority is enabled in the integration or not default is false. |
||
350 | * |
||
351 | * @return string |
||
352 | */ |
||
353 | public function getDataPriority() |
||
354 | { |
||
355 | return false; |
||
0 ignored issues
–
show
|
|||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Get a list of supported features for this integration. |
||
360 | * |
||
361 | * Options are: |
||
362 | * cloud_storage - Asset remote storage |
||
363 | * public_profile - Lead social profile |
||
364 | * public_activity - Lead social activity |
||
365 | * share_button - Landing page share button |
||
366 | * sso_service - SSO using 3rd party service via sso_login and sso_login_check routes |
||
367 | * sso_form - SSO using submitted credentials through the login form |
||
368 | * |
||
369 | * @return array |
||
370 | */ |
||
371 | public function getSupportedFeatures() |
||
372 | { |
||
373 | return []; |
||
374 | } |
||
375 | |||
376 | /** |
||
377 | * Get a list of tooltips for the specified supported features. |
||
378 | * This allows you to add detail / informational tooltips to your |
||
379 | * supported feature checkbox group. |
||
380 | * |
||
381 | * Example: |
||
382 | * 'cloud_storage' => 'mautic.integration.form.features.cloud_storage.tooltip' |
||
383 | * |
||
384 | * @return array |
||
385 | */ |
||
386 | public function getSupportedFeatureTooltips() |
||
387 | { |
||
388 | return []; |
||
389 | } |
||
390 | |||
391 | /** |
||
392 | * Returns the field the integration needs in order to find the user. |
||
393 | * |
||
394 | * @return mixed |
||
395 | */ |
||
396 | public function getIdentifierFields() |
||
397 | { |
||
398 | return []; |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * Allows integration to set a custom form template. |
||
403 | * |
||
404 | * @return string |
||
405 | */ |
||
406 | public function getFormTemplate() |
||
407 | { |
||
408 | return 'MauticPluginBundle:Integration:form.html.php'; |
||
409 | } |
||
410 | |||
411 | /** |
||
412 | * Allows integration to set a custom theme folder. |
||
413 | * |
||
414 | * @return string |
||
415 | */ |
||
416 | public function getFormTheme() |
||
417 | { |
||
418 | return 'MauticPluginBundle:FormTheme\Integration'; |
||
419 | } |
||
420 | |||
421 | /** |
||
422 | * Set the social integration entity. |
||
423 | */ |
||
424 | public function setIntegrationSettings(Integration $settings) |
||
425 | { |
||
426 | $this->settings = $settings; |
||
427 | |||
428 | $this->keys = $this->getDecryptedApiKeys(); |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * Get the social integration entity. |
||
433 | * |
||
434 | * @return Integration |
||
435 | */ |
||
436 | public function getIntegrationSettings() |
||
437 | { |
||
438 | return $this->settings; |
||
439 | } |
||
440 | |||
441 | /** |
||
442 | * Persist settings to the database. |
||
443 | */ |
||
444 | public function persistIntegrationSettings() |
||
445 | { |
||
446 | $this->em->persist($this->settings); |
||
447 | $this->em->flush(); |
||
448 | } |
||
449 | |||
450 | /** |
||
451 | * Merge api keys. |
||
452 | * |
||
453 | * @param $mergeKeys |
||
454 | * @param $withKeys |
||
455 | * @param bool|false $return Returns the key array rather than setting them |
||
456 | * |
||
457 | * @return void|array |
||
458 | */ |
||
459 | public function mergeApiKeys($mergeKeys, $withKeys = [], $return = false) |
||
460 | { |
||
461 | $settings = $this->settings; |
||
462 | if (empty($withKeys)) { |
||
463 | $withKeys = $this->keys; |
||
464 | } |
||
465 | |||
466 | foreach ($withKeys as $k => $v) { |
||
467 | if (!empty($mergeKeys[$k])) { |
||
468 | $withKeys[$k] = $mergeKeys[$k]; |
||
469 | } |
||
470 | unset($mergeKeys[$k]); |
||
471 | } |
||
472 | |||
473 | //merge remaining new keys |
||
474 | $withKeys = array_merge($withKeys, $mergeKeys); |
||
475 | |||
476 | if ($return) { |
||
477 | $this->keys = $this->dispatchIntegrationKeyEvent( |
||
478 | PluginEvents::PLUGIN_ON_INTEGRATION_KEYS_MERGE, |
||
479 | $withKeys |
||
480 | ); |
||
481 | |||
482 | return $this->keys; |
||
483 | } else { |
||
484 | $this->encryptAndSetApiKeys($withKeys, $settings); |
||
485 | |||
486 | //reset for events that depend on rebuilding auth objects |
||
487 | $this->setIntegrationSettings($settings); |
||
488 | } |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * Encrypts and saves keys to the entity. |
||
493 | */ |
||
494 | public function encryptAndSetApiKeys(array $keys, Integration $entity) |
||
495 | { |
||
496 | /** @var PluginIntegrationKeyEvent $event */ |
||
497 | $keys = $this->dispatchIntegrationKeyEvent( |
||
498 | PluginEvents::PLUGIN_ON_INTEGRATION_KEYS_ENCRYPT, |
||
499 | $keys |
||
500 | ); |
||
501 | |||
502 | // Update keys |
||
503 | $this->keys = array_merge($this->keys, $keys); |
||
504 | |||
505 | $encrypted = $this->encryptApiKeys($keys); |
||
506 | $entity->setApiKeys($encrypted); |
||
507 | } |
||
508 | |||
509 | /** |
||
510 | * Returns already decrypted keys. |
||
511 | * |
||
512 | * @return mixed |
||
513 | */ |
||
514 | public function getKeys() |
||
515 | { |
||
516 | return $this->keys; |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * Returns decrypted API keys. |
||
521 | * |
||
522 | * @param bool $entity |
||
523 | * |
||
524 | * @return array |
||
525 | */ |
||
526 | public function getDecryptedApiKeys($entity = false) |
||
527 | { |
||
528 | static $decryptedKeys = []; |
||
529 | |||
530 | if (!$entity) { |
||
531 | $entity = $this->settings; |
||
532 | } |
||
533 | |||
534 | $keys = $entity->getApiKeys(); |
||
535 | |||
536 | $serialized = serialize($keys); |
||
537 | if (empty($decryptedKeys[$serialized])) { |
||
538 | $decrypted = $this->decryptApiKeys($keys, true); |
||
539 | if (0 !== count($keys) && 0 === count($decrypted)) { |
||
540 | $decrypted = $this->decryptApiKeys($keys); |
||
541 | $this->encryptAndSetApiKeys($decrypted, $entity); |
||
542 | $this->em->flush($entity); |
||
543 | } |
||
544 | $decryptedKeys[$serialized] = $this->dispatchIntegrationKeyEvent( |
||
545 | PluginEvents::PLUGIN_ON_INTEGRATION_KEYS_DECRYPT, |
||
546 | $decrypted |
||
547 | ); |
||
548 | } |
||
549 | |||
550 | return $decryptedKeys[$serialized]; |
||
551 | } |
||
552 | |||
553 | /** |
||
554 | * Encrypts API keys. |
||
555 | * |
||
556 | * @return array |
||
557 | */ |
||
558 | public function encryptApiKeys(array $keys) |
||
559 | { |
||
560 | $encrypted = []; |
||
561 | |||
562 | foreach ($keys as $name => $key) { |
||
563 | $key = $this->encryptionHelper->encrypt($key); |
||
564 | $encrypted[$name] = $key; |
||
565 | } |
||
566 | |||
567 | return $encrypted; |
||
568 | } |
||
569 | |||
570 | /** |
||
571 | * Decrypts API keys. |
||
572 | * |
||
573 | * @param bool $mainDecryptOnly |
||
574 | * |
||
575 | * @return array |
||
576 | */ |
||
577 | public function decryptApiKeys(array $keys, $mainDecryptOnly = false) |
||
578 | { |
||
579 | $decrypted = []; |
||
580 | |||
581 | foreach ($keys as $name => $key) { |
||
582 | $key = $this->encryptionHelper->decrypt($key, $mainDecryptOnly); |
||
583 | if (false === $key) { |
||
584 | return []; |
||
585 | } |
||
586 | $decrypted[$name] = $key; |
||
587 | } |
||
588 | |||
589 | return $decrypted; |
||
590 | } |
||
591 | |||
592 | /** |
||
593 | * Get the array key for clientId. |
||
594 | * |
||
595 | * @return string |
||
596 | */ |
||
597 | public function getClientIdKey() |
||
598 | { |
||
599 | switch ($this->getAuthenticationType()) { |
||
600 | case 'oauth1a': |
||
601 | return 'consumer_id'; |
||
602 | case 'oauth2': |
||
603 | return 'client_id'; |
||
604 | case 'key': |
||
605 | return 'key'; |
||
606 | default: |
||
607 | return ''; |
||
608 | } |
||
609 | } |
||
610 | |||
611 | /** |
||
612 | * Get the array key for client secret. |
||
613 | * |
||
614 | * @return string |
||
615 | */ |
||
616 | public function getClientSecretKey() |
||
617 | { |
||
618 | switch ($this->getAuthenticationType()) { |
||
619 | case 'oauth1a': |
||
620 | return 'consumer_secret'; |
||
621 | case 'oauth2': |
||
622 | return 'client_secret'; |
||
623 | case 'basic': |
||
624 | return 'password'; |
||
625 | default: |
||
626 | return ''; |
||
627 | } |
||
628 | } |
||
629 | |||
630 | /** |
||
631 | * Array of keys to mask in the config form. |
||
632 | * |
||
633 | * @return array |
||
634 | */ |
||
635 | public function getSecretKeys() |
||
636 | { |
||
637 | return [$this->getClientSecretKey()]; |
||
638 | } |
||
639 | |||
640 | /** |
||
641 | * Get the array key for the auth token. |
||
642 | * |
||
643 | * @return string |
||
644 | */ |
||
645 | public function getAuthTokenKey() |
||
646 | { |
||
647 | switch ($this->getAuthenticationType()) { |
||
648 | case 'oauth2': |
||
649 | return 'access_token'; |
||
650 | case 'oauth1a': |
||
651 | return 'oauth_token'; |
||
652 | default: |
||
653 | return ''; |
||
654 | } |
||
655 | } |
||
656 | |||
657 | /** |
||
658 | * Get the keys for the refresh token and expiry. |
||
659 | * |
||
660 | * @return array |
||
661 | */ |
||
662 | public function getRefreshTokenKeys() |
||
663 | { |
||
664 | return []; |
||
665 | } |
||
666 | |||
667 | /** |
||
668 | * Get a list of keys required to make an API call. Examples are key, clientId, clientSecret. |
||
669 | * |
||
670 | * @return array |
||
671 | */ |
||
672 | public function getRequiredKeyFields() |
||
673 | { |
||
674 | switch ($this->getAuthenticationType()) { |
||
675 | case 'oauth1a': |
||
676 | return [ |
||
677 | 'consumer_id' => 'mautic.integration.keyfield.consumerid', |
||
678 | 'consumer_secret' => 'mautic.integration.keyfield.consumersecret', |
||
679 | ]; |
||
680 | case 'oauth2': |
||
681 | return [ |
||
682 | 'client_id' => 'mautic.integration.keyfield.clientid', |
||
683 | 'client_secret' => 'mautic.integration.keyfield.clientsecret', |
||
684 | ]; |
||
685 | case 'key': |
||
686 | return [ |
||
687 | 'key' => 'mautic.integration.keyfield.api', |
||
688 | ]; |
||
689 | case 'basic': |
||
690 | return [ |
||
691 | 'username' => 'mautic.integration.keyfield.username', |
||
692 | 'password' => 'mautic.integration.keyfield.password', |
||
693 | ]; |
||
694 | default: |
||
695 | return []; |
||
696 | } |
||
697 | } |
||
698 | |||
699 | /** |
||
700 | * Extract the tokens returned by the oauth callback. |
||
701 | * |
||
702 | * @param string $data |
||
703 | * @param bool $postAuthorization |
||
704 | * |
||
705 | * @return mixed |
||
706 | */ |
||
707 | public function parseCallbackResponse($data, $postAuthorization = false) |
||
708 | { |
||
709 | if (!$parsed = json_decode($data, true)) { |
||
710 | parse_str($data, $parsed); |
||
711 | } |
||
712 | |||
713 | return $parsed; |
||
714 | } |
||
715 | |||
716 | /** |
||
717 | * Generic error parser. |
||
718 | * |
||
719 | * @param $response |
||
720 | * |
||
721 | * @return string |
||
722 | */ |
||
723 | public function getErrorsFromResponse($response) |
||
724 | { |
||
725 | if (is_object($response)) { |
||
726 | if (!empty($response->errors)) { |
||
727 | $errors = []; |
||
728 | foreach ($response->errors as $e) { |
||
729 | $errors[] = $e->message; |
||
730 | } |
||
731 | |||
732 | return implode('; ', $errors); |
||
733 | } elseif (!empty($response->error->message)) { |
||
734 | return $response->error->message; |
||
735 | } else { |
||
736 | return (string) $response; |
||
737 | } |
||
738 | } elseif (is_array($response)) { |
||
739 | if (isset($response['error_description'])) { |
||
740 | return $response['error_description']; |
||
741 | } elseif (isset($response['error'])) { |
||
742 | if (is_array($response['error'])) { |
||
743 | if (isset($response['error']['message'])) { |
||
744 | return $response['error']['message']; |
||
745 | } else { |
||
746 | return implode(', ', $response['error']); |
||
747 | } |
||
748 | } else { |
||
749 | return $response['error']; |
||
750 | } |
||
751 | } elseif (isset($response['errors'])) { |
||
752 | $errors = []; |
||
753 | foreach ($response['errors'] as $err) { |
||
754 | if (is_array($err)) { |
||
755 | if (isset($err['message'])) { |
||
756 | $errors[] = $err['message']; |
||
757 | } else { |
||
758 | $errors[] = implode(', ', $err); |
||
759 | } |
||
760 | } else { |
||
761 | $errors[] = $err; |
||
762 | } |
||
763 | } |
||
764 | |||
765 | return implode('; ', $errors); |
||
766 | } |
||
767 | |||
768 | return $response; |
||
769 | } else { |
||
770 | return $response; |
||
771 | } |
||
772 | } |
||
773 | |||
774 | /** |
||
775 | * Make a basic call using cURL to get the data. |
||
776 | * |
||
777 | * @param $url |
||
778 | * @param array $parameters |
||
779 | * @param string $method |
||
780 | * @param array $settings |
||
781 | * |
||
782 | * @return mixed|string |
||
783 | */ |
||
784 | public function makeRequest($url, $parameters = [], $method = 'GET', $settings = []) |
||
785 | { |
||
786 | // If not authorizing the session itself, check isAuthorized which will refresh tokens if applicable |
||
787 | if (empty($settings['authorize_session'])) { |
||
788 | $this->isAuthorized(); |
||
789 | } |
||
790 | |||
791 | $method = strtoupper($method); |
||
792 | $authType = (empty($settings['auth_type'])) ? $this->getAuthenticationType() : $settings['auth_type']; |
||
793 | |||
794 | [$parameters, $headers] = $this->prepareRequest($url, $parameters, $method, $settings, $authType); |
||
795 | |||
796 | if (empty($settings['ignore_event_dispatch'])) { |
||
797 | $event = $this->dispatcher->dispatch( |
||
798 | PluginEvents::PLUGIN_ON_INTEGRATION_REQUEST, |
||
799 | new PluginIntegrationRequestEvent($this, $url, $parameters, $headers, $method, $settings, $authType) |
||
800 | ); |
||
801 | |||
802 | $headers = $event->getHeaders(); |
||
803 | $parameters = $event->getParameters(); |
||
804 | } |
||
805 | |||
806 | if (!isset($settings['query'])) { |
||
807 | $settings['query'] = []; |
||
808 | } |
||
809 | |||
810 | if (isset($parameters['append_to_query'])) { |
||
811 | $settings['query'] = array_merge( |
||
812 | $settings['query'], |
||
813 | $parameters['append_to_query'] |
||
814 | ); |
||
815 | |||
816 | unset($parameters['append_to_query']); |
||
817 | } |
||
818 | |||
819 | if (isset($parameters['post_append_to_query'])) { |
||
820 | $postAppend = $parameters['post_append_to_query']; |
||
821 | unset($parameters['post_append_to_query']); |
||
822 | } |
||
823 | |||
824 | if (!$this->isConfigured()) { |
||
825 | return [ |
||
826 | 'error' => [ |
||
827 | 'message' => $this->translator->trans( |
||
828 | 'mautic.integration.missingkeys' |
||
829 | ), |
||
830 | ], |
||
831 | ]; |
||
832 | } |
||
833 | |||
834 | if ('GET' == $method && !empty($parameters)) { |
||
835 | $parameters = array_merge($settings['query'], $parameters); |
||
836 | $query = http_build_query($parameters); |
||
837 | $url .= (false === strpos($url, '?')) ? '?'.$query : '&'.$query; |
||
838 | } elseif (!empty($settings['query'])) { |
||
839 | $query = http_build_query($settings['query']); |
||
840 | $url .= (false === strpos($url, '?')) ? '?'.$query : '&'.$query; |
||
841 | } |
||
842 | |||
843 | if (isset($postAppend)) { |
||
844 | $url .= $postAppend; |
||
845 | } |
||
846 | |||
847 | // Check for custom content-type header |
||
848 | if (!empty($settings['content_type'])) { |
||
849 | $settings['encoding_headers_set'] = true; |
||
850 | $headers[] = "Content-Type: {$settings['content_type']}"; |
||
851 | } |
||
852 | |||
853 | if ('GET' !== $method) { |
||
854 | if (!empty($parameters)) { |
||
855 | if ('oauth1a' == $authType) { |
||
856 | $parameters = http_build_query($parameters); |
||
857 | } |
||
858 | if (!empty($settings['encode_parameters'])) { |
||
859 | if ('json' == $settings['encode_parameters']) { |
||
860 | //encode the arguments as JSON |
||
861 | $parameters = json_encode($parameters); |
||
862 | if (empty($settings['encoding_headers_set'])) { |
||
863 | $headers[] = 'Content-Type: application/json'; |
||
864 | } |
||
865 | } |
||
866 | } |
||
867 | } elseif (isset($settings['post_data'])) { |
||
868 | $parameters = $settings['post_data']; |
||
869 | } |
||
870 | } |
||
871 | |||
872 | $options = [ |
||
873 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, |
||
874 | CURLOPT_HEADER => 1, |
||
875 | CURLOPT_RETURNTRANSFER => 1, |
||
876 | CURLOPT_FOLLOWLOCATION => 0, |
||
877 | CURLOPT_REFERER => $this->getRefererUrl(), |
||
878 | CURLOPT_USERAGENT => $this->getUserAgent(), |
||
879 | ]; |
||
880 | |||
881 | if (isset($settings['curl_options']) && is_array($settings['curl_options'])) { |
||
882 | $options = $settings['curl_options'] + $options; |
||
883 | } |
||
884 | |||
885 | if (isset($settings['ssl_verifypeer'])) { |
||
886 | $options[CURLOPT_SSL_VERIFYPEER] = $settings['ssl_verifypeer']; |
||
887 | } |
||
888 | |||
889 | $connector = HttpFactory::getHttp( |
||
890 | [ |
||
891 | 'transport.curl' => $options, |
||
892 | ] |
||
893 | ); |
||
894 | |||
895 | $parseHeaders = (isset($settings['headers'])) ? array_merge($headers, $settings['headers']) : $headers; |
||
896 | // HTTP library requires that headers are in key => value pairs |
||
897 | $headers = []; |
||
898 | if (is_array($parseHeaders)) { |
||
899 | foreach ($parseHeaders as $key => $value) { |
||
900 | // Ignore string keys which assume it is already parsed and avoids splitting up a value that includes colons (such as a date/time) |
||
901 | if (!is_string($key) && false !== strpos($value, ':')) { |
||
902 | [$key, $value] = explode(':', $value); |
||
903 | $key = trim($key); |
||
904 | $value = trim($value); |
||
905 | } |
||
906 | |||
907 | $headers[$key] = $value; |
||
908 | } |
||
909 | } |
||
910 | |||
911 | try { |
||
912 | $timeout = (isset($settings['request_timeout'])) ? (int) $settings['request_timeout'] : 10; |
||
913 | switch ($method) { |
||
914 | case 'GET': |
||
915 | $result = $connector->get($url, $headers, $timeout); |
||
916 | break; |
||
917 | case 'POST': |
||
918 | case 'PUT': |
||
919 | case 'PATCH': |
||
920 | $connectorMethod = strtolower($method); |
||
921 | $result = $connector->$connectorMethod($url, $parameters, $headers, $timeout); |
||
922 | break; |
||
923 | case 'DELETE': |
||
924 | $result = $connector->delete($url, $headers, $timeout); |
||
925 | break; |
||
926 | } |
||
927 | } catch (\Exception $exception) { |
||
928 | return ['error' => ['message' => $exception->getMessage(), 'code' => $exception->getCode()]]; |
||
929 | } |
||
930 | if (empty($settings['ignore_event_dispatch'])) { |
||
931 | $event->setResponse($result); |
||
932 | $this->dispatcher->dispatch( |
||
933 | PluginEvents::PLUGIN_ON_INTEGRATION_RESPONSE, |
||
934 | $event |
||
935 | ); |
||
936 | } |
||
937 | if (!empty($settings['return_raw'])) { |
||
938 | return $result; |
||
939 | } else { |
||
940 | return $this->parseCallbackResponse($result->body, !empty($settings['authorize_session'])); |
||
941 | } |
||
942 | } |
||
943 | |||
944 | /** |
||
945 | * @param $integrationEntity |
||
946 | * @param $integrationEntityId |
||
947 | * @param $internalEntity |
||
948 | * @param $internalEntityId |
||
949 | * @param bool $persist |
||
950 | */ |
||
951 | public function createIntegrationEntity( |
||
952 | $integrationEntity, |
||
953 | $integrationEntityId, |
||
954 | $internalEntity, |
||
955 | $internalEntityId, |
||
956 | array $internal = null, |
||
957 | $persist = true |
||
958 | ) { |
||
959 | $date = (defined('MAUTIC_DATE_MODIFIED_OVERRIDE')) ? \DateTime::createFromFormat('U', MAUTIC_DATE_MODIFIED_OVERRIDE) |
||
960 | : new \DateTime(); |
||
961 | $entity = new IntegrationEntity(); |
||
962 | $entity->setDateAdded($date) |
||
963 | ->setLastSyncDate($date) |
||
964 | ->setIntegration($this->getName()) |
||
965 | ->setIntegrationEntity($integrationEntity) |
||
966 | ->setIntegrationEntityId($integrationEntityId) |
||
967 | ->setInternalEntity($internalEntity) |
||
968 | ->setInternal($internal) |
||
969 | ->setInternalEntityId($internalEntityId); |
||
970 | |||
971 | if ($persist) { |
||
972 | $this->em->getRepository('MauticPluginBundle:IntegrationEntity')->saveEntity($entity); |
||
973 | } |
||
974 | |||
975 | return $entity; |
||
976 | } |
||
977 | |||
978 | /** |
||
979 | * @return IntegrationEntityRepository |
||
980 | */ |
||
981 | public function getIntegrationEntityRepository() |
||
982 | { |
||
983 | return $this->em->getRepository('MauticPluginBundle:IntegrationEntity'); |
||
984 | } |
||
985 | |||
986 | /** |
||
987 | * Method to prepare the request parameters. Builds array of headers and parameters. |
||
988 | * |
||
989 | * @param $url |
||
990 | * @param $parameters |
||
991 | * @param $method |
||
992 | * @param $settings |
||
993 | * @param $authType |
||
994 | * |
||
995 | * @return array |
||
996 | */ |
||
997 | public function prepareRequest($url, $parameters, $method, $settings, $authType) |
||
998 | { |
||
999 | $clientIdKey = $this->getClientIdKey(); |
||
1000 | $clientSecretKey = $this->getClientSecretKey(); |
||
1001 | $authTokenKey = $this->getAuthTokenKey(); |
||
1002 | $authToken = ''; |
||
1003 | if (isset($settings['override_auth_token'])) { |
||
1004 | $authToken = $settings['override_auth_token']; |
||
1005 | } elseif (isset($this->keys[$authTokenKey])) { |
||
1006 | $authToken = $this->keys[$authTokenKey]; |
||
1007 | } |
||
1008 | |||
1009 | // Override token parameter key if neede |
||
1010 | if (!empty($settings[$authTokenKey])) { |
||
1011 | $authTokenKey = $settings[$authTokenKey]; |
||
1012 | } |
||
1013 | |||
1014 | $headers = []; |
||
1015 | |||
1016 | if (!empty($settings['authorize_session'])) { |
||
1017 | switch ($authType) { |
||
1018 | case 'oauth1a': |
||
1019 | $requestTokenUrl = $this->getRequestTokenUrl(); |
||
1020 | if (!array_key_exists('append_callback', $settings) && !empty($requestTokenUrl)) { |
||
1021 | $settings['append_callback'] = false; |
||
1022 | } |
||
1023 | $oauthHelper = new oAuthHelper($this, $this->request, $settings); |
||
1024 | $headers = $oauthHelper->getAuthorizationHeader($url, $parameters, $method); |
||
1025 | break; |
||
1026 | case 'oauth2': |
||
1027 | if ($bearerToken = $this->getBearerToken(true)) { |
||
1028 | $headers = [ |
||
1029 | "Authorization: Basic {$bearerToken}", |
||
1030 | 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8', |
||
1031 | ]; |
||
1032 | $parameters['grant_type'] = 'client_credentials'; |
||
1033 | } else { |
||
1034 | $defaultGrantType = (!empty($settings['refresh_token'])) ? 'refresh_token' |
||
1035 | : 'authorization_code'; |
||
1036 | $grantType = (!isset($settings['grant_type'])) ? $defaultGrantType |
||
1037 | : $settings['grant_type']; |
||
1038 | |||
1039 | $useClientIdKey = (empty($settings[$clientIdKey])) ? $clientIdKey : $settings[$clientIdKey]; |
||
1040 | $useClientSecretKey = (empty($settings[$clientSecretKey])) ? $clientSecretKey |
||
1041 | : $settings[$clientSecretKey]; |
||
1042 | $parameters = array_merge( |
||
1043 | $parameters, |
||
1044 | [ |
||
1045 | $useClientIdKey => $this->keys[$clientIdKey], |
||
1046 | $useClientSecretKey => isset($this->keys[$clientSecretKey]) ? $this->keys[$clientSecretKey] : '', |
||
1047 | 'grant_type' => $grantType, |
||
1048 | ] |
||
1049 | ); |
||
1050 | |||
1051 | if (!empty($settings['refresh_token']) && !empty($this->keys[$settings['refresh_token']])) { |
||
1052 | $parameters[$settings['refresh_token']] = $this->keys[$settings['refresh_token']]; |
||
1053 | } |
||
1054 | |||
1055 | if ('authorization_code' == $grantType) { |
||
1056 | $parameters['code'] = $this->request->get('code'); |
||
1057 | } |
||
1058 | if (empty($settings['ignore_redirecturi'])) { |
||
1059 | $callback = $this->getAuthCallbackUrl(); |
||
1060 | $parameters['redirect_uri'] = $callback; |
||
1061 | } |
||
1062 | } |
||
1063 | break; |
||
1064 | } |
||
1065 | } else { |
||
1066 | switch ($authType) { |
||
1067 | case 'basic': |
||
1068 | $headers = [ |
||
1069 | 'Authorization' => 'Basic '.base64_encode($this->keys['username'].':'.$this->keys['password']), |
||
1070 | ]; |
||
1071 | break; |
||
1072 | case 'oauth1a': |
||
1073 | $oauthHelper = new oAuthHelper($this, $this->request, $settings); |
||
1074 | $headers = $oauthHelper->getAuthorizationHeader($url, $parameters, $method); |
||
1075 | break; |
||
1076 | case 'oauth2': |
||
1077 | if ($bearerToken = $this->getBearerToken()) { |
||
1078 | $headers = [ |
||
1079 | "Authorization: Bearer {$bearerToken}", |
||
1080 | //"Content-Type: application/x-www-form-urlencoded;charset=UTF-8" |
||
1081 | ]; |
||
1082 | } else { |
||
1083 | if (!empty($settings['append_auth_token'])) { |
||
1084 | // Workaround because $settings cannot be manipulated here |
||
1085 | $parameters['append_to_query'] = [ |
||
1086 | $authTokenKey => $authToken, |
||
1087 | ]; |
||
1088 | } else { |
||
1089 | $parameters[$authTokenKey] = $authToken; |
||
1090 | } |
||
1091 | |||
1092 | $headers = [ |
||
1093 | "oauth-token: $authTokenKey", |
||
1094 | "Authorization: OAuth {$authToken}", |
||
1095 | ]; |
||
1096 | } |
||
1097 | break; |
||
1098 | case 'key': |
||
1099 | $parameters[$authTokenKey] = $authToken; |
||
1100 | break; |
||
1101 | } |
||
1102 | } |
||
1103 | |||
1104 | return [$parameters, $headers]; |
||
1105 | } |
||
1106 | |||
1107 | /** |
||
1108 | * Generate the auth login URL. Note that if oauth2, response_type=code is assumed. If this is not the case, |
||
1109 | * override this function. |
||
1110 | * |
||
1111 | * @return string |
||
1112 | */ |
||
1113 | public function getAuthLoginUrl() |
||
1114 | { |
||
1115 | $authType = $this->getAuthenticationType(); |
||
1116 | |||
1117 | if ('oauth2' == $authType) { |
||
1118 | $callback = $this->getAuthCallbackUrl(); |
||
1119 | $clientIdKey = $this->getClientIdKey(); |
||
1120 | $state = $this->getAuthLoginState(); |
||
1121 | $url = $this->getAuthenticationUrl() |
||
1122 | .'?client_id='.$this->keys[$clientIdKey] |
||
1123 | .'&response_type=code' |
||
1124 | .'&redirect_uri='.urlencode($callback) |
||
1125 | .'&state='.$state; |
||
1126 | |||
1127 | if ($scope = $this->getAuthScope()) { |
||
1128 | $url .= '&scope='.urlencode($scope); |
||
1129 | } |
||
1130 | |||
1131 | if ($this->session) { |
||
1132 | $this->session->set($this->getName().'_csrf_token', $state); |
||
1133 | } |
||
1134 | |||
1135 | return $url; |
||
1136 | } else { |
||
1137 | return $this->router->generate( |
||
1138 | 'mautic_integration_auth_callback', |
||
1139 | ['integration' => $this->getName()] |
||
1140 | ); |
||
1141 | } |
||
1142 | } |
||
1143 | |||
1144 | /** |
||
1145 | * State variable to append to login url (usually used in oAuth flows). |
||
1146 | * |
||
1147 | * @return string |
||
1148 | */ |
||
1149 | public function getAuthLoginState() |
||
1150 | { |
||
1151 | return hash('sha1', uniqid(mt_rand())); |
||
1152 | } |
||
1153 | |||
1154 | /** |
||
1155 | * Get the scope for auth flows. |
||
1156 | * |
||
1157 | * @return string |
||
1158 | */ |
||
1159 | public function getAuthScope() |
||
1160 | { |
||
1161 | return ''; |
||
1162 | } |
||
1163 | |||
1164 | /** |
||
1165 | * Gets the URL for the built in oauth callback. |
||
1166 | * |
||
1167 | * @return string |
||
1168 | */ |
||
1169 | public function getAuthCallbackUrl() |
||
1170 | { |
||
1171 | $defaultUrl = $this->router->generate( |
||
1172 | 'mautic_integration_auth_callback', |
||
1173 | ['integration' => $this->getName()], |
||
1174 | UrlGeneratorInterface::ABSOLUTE_URL //absolute |
||
1175 | ); |
||
1176 | |||
1177 | /** @var PluginIntegrationAuthCallbackUrlEvent $event */ |
||
1178 | $event = $this->dispatcher->dispatch( |
||
1179 | PluginEvents::PLUGIN_ON_INTEGRATION_GET_AUTH_CALLBACK_URL, |
||
1180 | new PluginIntegrationAuthCallbackUrlEvent($this, $defaultUrl) |
||
1181 | ); |
||
1182 | |||
1183 | return $event->getCallbackUrl(); |
||
1184 | } |
||
1185 | |||
1186 | /** |
||
1187 | * Retrieves and stores tokens returned from oAuthLogin. |
||
1188 | * |
||
1189 | * @param array $settings |
||
1190 | * @param array $parameters |
||
1191 | * |
||
1192 | * @return bool|string false if no error; otherwise the error string |
||
1193 | * |
||
1194 | * @throws ApiErrorException if OAuth2 state does not match |
||
1195 | */ |
||
1196 | public function authCallback($settings = [], $parameters = []) |
||
1197 | { |
||
1198 | $authType = $this->getAuthenticationType(); |
||
1199 | |||
1200 | switch ($authType) { |
||
1201 | case 'oauth2': |
||
1202 | if ($this->session) { |
||
1203 | $state = $this->session->get($this->getName().'_csrf_token', false); |
||
1204 | $givenState = ($this->request->isXmlHttpRequest()) ? $this->request->request->get('state') : $this->request->get('state'); |
||
1205 | |||
1206 | if ($state && $state !== $givenState) { |
||
1207 | $this->session->remove($this->getName().'_csrf_token'); |
||
1208 | throw new ApiErrorException($this->translator->trans('mautic.integration.auth.invalid.state')); |
||
1209 | } |
||
1210 | } |
||
1211 | |||
1212 | if (!empty($settings['use_refresh_token'])) { |
||
1213 | // Try refresh token |
||
1214 | $refreshTokenKeys = $this->getRefreshTokenKeys(); |
||
1215 | |||
1216 | if (!empty($refreshTokenKeys)) { |
||
1217 | [$refreshTokenKey, $expiryKey] = $refreshTokenKeys; |
||
1218 | |||
1219 | $settings['refresh_token'] = $refreshTokenKey; |
||
1220 | } |
||
1221 | } |
||
1222 | break; |
||
1223 | |||
1224 | case 'oauth1a': |
||
1225 | // After getting request_token and authorizing, post back to access_token |
||
1226 | $settings['append_callback'] = true; |
||
1227 | $settings['include_verifier'] = true; |
||
1228 | |||
1229 | // Get request token returned from Twitter and submit it to get access_token |
||
1230 | $settings['request_token'] = ($this->request) ? $this->request->get('oauth_token') : ''; |
||
1231 | |||
1232 | break; |
||
1233 | } |
||
1234 | |||
1235 | $settings['authorize_session'] = true; |
||
1236 | |||
1237 | $method = (!isset($settings['method'])) ? 'POST' : $settings['method']; |
||
1238 | $data = $this->makeRequest($this->getAccessTokenUrl(), $parameters, $method, $settings); |
||
1239 | |||
1240 | return $this->extractAuthKeys($data); |
||
1241 | } |
||
1242 | |||
1243 | /** |
||
1244 | * Extacts the auth keys from response and saves entity. |
||
1245 | * |
||
1246 | * @param $data |
||
1247 | * @param $tokenOverride |
||
1248 | * |
||
1249 | * @return bool|string false if no error; otherwise the error string |
||
1250 | */ |
||
1251 | public function extractAuthKeys($data, $tokenOverride = null) |
||
1252 | { |
||
1253 | //check to see if an entity exists |
||
1254 | $entity = $this->getIntegrationSettings(); |
||
1255 | if (null == $entity) { |
||
1256 | $entity = new Integration(); |
||
1257 | $entity->setName($this->getName()); |
||
1258 | } |
||
1259 | // Prepare the keys for extraction such as renaming, setting expiry, etc |
||
1260 | $data = $this->prepareResponseForExtraction($data); |
||
1261 | |||
1262 | //parse the response |
||
1263 | $authTokenKey = ($tokenOverride) ? $tokenOverride : $this->getAuthTokenKey(); |
||
1264 | if (is_array($data) && isset($data[$authTokenKey])) { |
||
1265 | $keys = $this->mergeApiKeys($data, null, true); |
||
1266 | $encrypted = $this->encryptApiKeys($keys); |
||
1267 | $entity->setApiKeys($encrypted); |
||
1268 | |||
1269 | if ($this->session) { |
||
1270 | $this->session->set($this->getName().'_tokenResponse', $data); |
||
1271 | } |
||
1272 | |||
1273 | $error = false; |
||
1274 | } elseif (is_array($data) && isset($data['access_token'])) { |
||
1275 | if ($this->session) { |
||
1276 | $this->session->set($this->getName().'_tokenResponse', $data); |
||
1277 | } |
||
1278 | $error = false; |
||
1279 | } else { |
||
1280 | $error = $this->getErrorsFromResponse($data); |
||
1281 | if (empty($error)) { |
||
1282 | $error = $this->translator->trans( |
||
1283 | 'mautic.integration.error.genericerror', |
||
1284 | [], |
||
1285 | 'flashes' |
||
1286 | ); |
||
1287 | } |
||
1288 | } |
||
1289 | |||
1290 | //save the data |
||
1291 | $this->em->persist($entity); |
||
1292 | $this->em->flush(); |
||
1293 | |||
1294 | $this->setIntegrationSettings($entity); |
||
1295 | |||
1296 | return $error; |
||
1297 | } |
||
1298 | |||
1299 | /** |
||
1300 | * Called in extractAuthKeys before key comparison begins to give opportunity to set expiry, rename keys, etc. |
||
1301 | * |
||
1302 | * @param $data |
||
1303 | * |
||
1304 | * @return mixed |
||
1305 | */ |
||
1306 | public function prepareResponseForExtraction($data) |
||
1307 | { |
||
1308 | return $data; |
||
1309 | } |
||
1310 | |||
1311 | /** |
||
1312 | * Checks to see if the integration is configured by checking that required keys are populated. |
||
1313 | * |
||
1314 | * @return bool |
||
1315 | */ |
||
1316 | public function isConfigured() |
||
1317 | { |
||
1318 | $requiredTokens = $this->getRequiredKeyFields(); |
||
1319 | foreach ($requiredTokens as $token => $label) { |
||
1320 | if (empty($this->keys[$token])) { |
||
1321 | return false; |
||
1322 | } |
||
1323 | } |
||
1324 | |||
1325 | return true; |
||
1326 | } |
||
1327 | |||
1328 | /** |
||
1329 | * Checks if an integration is authorized and/or authorizes the request. |
||
1330 | * |
||
1331 | * @return bool |
||
1332 | */ |
||
1333 | public function isAuthorized() |
||
1334 | { |
||
1335 | if (!$this->isConfigured()) { |
||
1336 | return false; |
||
1337 | } |
||
1338 | |||
1339 | $type = $this->getAuthenticationType(); |
||
1340 | $authTokenKey = $this->getAuthTokenKey(); |
||
1341 | |||
1342 | switch ($type) { |
||
1343 | case 'oauth1a': |
||
1344 | case 'oauth2': |
||
1345 | $refreshTokenKeys = $this->getRefreshTokenKeys(); |
||
1346 | if (!isset($this->keys[$authTokenKey])) { |
||
1347 | $valid = false; |
||
1348 | } elseif (!empty($refreshTokenKeys)) { |
||
1349 | [$refreshTokenKey, $expiryKey] = $refreshTokenKeys; |
||
1350 | if (!empty($this->keys[$refreshTokenKey]) && !empty($expiryKey) && isset($this->keys[$expiryKey]) |
||
1351 | && time() > $this->keys[$expiryKey] |
||
1352 | ) { |
||
1353 | //token has expired so try to refresh it |
||
1354 | $error = $this->authCallback(['refresh_token' => $refreshTokenKey]); |
||
1355 | $valid = (empty($error)); |
||
1356 | } else { |
||
1357 | // The refresh token doesn't have an expiry so the integration will have to check for expired sessions and request new token |
||
1358 | $valid = true; |
||
1359 | } |
||
1360 | } else { |
||
1361 | $valid = true; |
||
1362 | } |
||
1363 | break; |
||
1364 | case 'key': |
||
1365 | $valid = isset($this->keys['api_key']); |
||
1366 | break; |
||
1367 | case 'rest': |
||
1368 | $valid = isset($this->keys[$authTokenKey]); |
||
1369 | break; |
||
1370 | case 'basic': |
||
1371 | $valid = (!empty($this->keys['username']) && !empty($this->keys['password'])); |
||
1372 | break; |
||
1373 | default: |
||
1374 | $valid = true; |
||
1375 | break; |
||
1376 | } |
||
1377 | |||
1378 | return $valid; |
||
1379 | } |
||
1380 | |||
1381 | /** |
||
1382 | * Get the URL required to obtain an oauth2 access token. |
||
1383 | * |
||
1384 | * @return string |
||
1385 | */ |
||
1386 | public function getAccessTokenUrl() |
||
1387 | { |
||
1388 | return ''; |
||
1389 | } |
||
1390 | |||
1391 | /** |
||
1392 | * Get the authentication/login URL for oauth2 access. |
||
1393 | * |
||
1394 | * @return string |
||
1395 | */ |
||
1396 | public function getAuthenticationUrl() |
||
1397 | { |
||
1398 | return ''; |
||
1399 | } |
||
1400 | |||
1401 | /** |
||
1402 | * Get request token for oauth1a authorization request. |
||
1403 | * |
||
1404 | * @param array $settings |
||
1405 | * |
||
1406 | * @return mixed|string |
||
1407 | */ |
||
1408 | public function getRequestToken($settings = []) |
||
1409 | { |
||
1410 | // Child classes can easily pass in custom settings this way |
||
1411 | $settings = array_merge( |
||
1412 | ['authorize_session' => true, 'append_callback' => false, 'ssl_verifypeer' => true], |
||
1413 | $settings |
||
1414 | ); |
||
1415 | |||
1416 | // init result to empty string |
||
1417 | $result = ''; |
||
1418 | |||
1419 | $url = $this->getRequestTokenUrl(); |
||
1420 | if (!empty($url)) { |
||
1421 | $result = $this->makeRequest( |
||
1422 | $url, |
||
1423 | [], |
||
1424 | 'POST', |
||
1425 | $settings |
||
1426 | ); |
||
1427 | } |
||
1428 | |||
1429 | return $result; |
||
1430 | } |
||
1431 | |||
1432 | /** |
||
1433 | * Url to post in order to get the request token if required; leave empty if not required. |
||
1434 | * |
||
1435 | * @return string |
||
1436 | */ |
||
1437 | public function getRequestTokenUrl() |
||
1438 | { |
||
1439 | return ''; |
||
1440 | } |
||
1441 | |||
1442 | /** |
||
1443 | * Generate a bearer token. |
||
1444 | * |
||
1445 | * @param $inAuthorization |
||
1446 | * |
||
1447 | * @return string |
||
1448 | */ |
||
1449 | public function getBearerToken($inAuthorization = false) |
||
1450 | { |
||
1451 | return ''; |
||
1452 | } |
||
1453 | |||
1454 | /** |
||
1455 | * Get an array of public activity. |
||
1456 | * |
||
1457 | * @param $identifier |
||
1458 | * @param $socialCache |
||
1459 | * |
||
1460 | * @return array |
||
1461 | */ |
||
1462 | public function getPublicActivity($identifier, &$socialCache) |
||
1463 | { |
||
1464 | return []; |
||
1465 | } |
||
1466 | |||
1467 | /** |
||
1468 | * Get an array of public data. |
||
1469 | * |
||
1470 | * @param $identifier |
||
1471 | * @param $socialCache |
||
1472 | * |
||
1473 | * @return array |
||
1474 | */ |
||
1475 | public function getUserData($identifier, &$socialCache) |
||
1476 | { |
||
1477 | return []; |
||
1478 | } |
||
1479 | |||
1480 | /** |
||
1481 | * Generates current URL to set as referer for curl calls. |
||
1482 | * |
||
1483 | * @return string |
||
1484 | */ |
||
1485 | protected function getRefererUrl() |
||
1486 | { |
||
1487 | return ($this->request) ? $this->request->getRequestUri() : null; |
||
1488 | } |
||
1489 | |||
1490 | /** |
||
1491 | * Generate a user agent string. |
||
1492 | * |
||
1493 | * @return string |
||
1494 | */ |
||
1495 | protected function getUserAgent() |
||
1496 | { |
||
1497 | return ($this->request) ? $this->request->server->get('HTTP_USER_AGENT') : null; |
||
1498 | } |
||
1499 | |||
1500 | /** |
||
1501 | * Get a list of available fields from the connecting API. |
||
1502 | * |
||
1503 | * @param array $settings |
||
1504 | * |
||
1505 | * @return array |
||
1506 | */ |
||
1507 | public function getAvailableLeadFields($settings = []) |
||
1508 | { |
||
1509 | if (empty($settings['ignore_field_cache'])) { |
||
1510 | $cacheSuffix = (isset($settings['cache_suffix'])) ? $settings['cache_suffix'] : ''; |
||
1511 | if ($fields = $this->cache->get('leadFields'.$cacheSuffix)) { |
||
1512 | return $fields; |
||
1513 | } |
||
1514 | } |
||
1515 | |||
1516 | return []; |
||
1517 | } |
||
1518 | |||
1519 | /** |
||
1520 | * @return array |
||
1521 | */ |
||
1522 | public function cleanUpFields(Integration $entity, array $mauticLeadFields, array $mauticCompanyFields) |
||
1523 | { |
||
1524 | $featureSettings = $entity->getFeatureSettings(); |
||
1525 | $submittedFields = (isset($featureSettings['leadFields'])) ? $featureSettings['leadFields'] : []; |
||
1526 | $submittedCompanyFields = (isset($featureSettings['companyFields'])) ? $featureSettings['companyFields'] : []; |
||
1527 | $submittedObjects = (isset($featureSettings['objects'])) ? $featureSettings['objects'] : []; |
||
1528 | $missingRequiredFields = []; |
||
1529 | |||
1530 | // add special case in order to prevent it from being removed |
||
1531 | $mauticLeadFields['mauticContactId'] = ''; |
||
1532 | $mauticLeadFields['mauticContactTimelineLink'] = ''; |
||
1533 | $mauticLeadFields['mauticContactIsContactableByEmail'] = ''; |
||
1534 | |||
1535 | //make sure now non-existent aren't saved |
||
1536 | $settings = [ |
||
1537 | 'ignore_field_cache' => false, |
||
1538 | ]; |
||
1539 | $settings['feature_settings']['objects'] = $submittedObjects; |
||
1540 | $availableIntegrationFields = $this->getAvailableLeadFields($settings); |
||
1541 | $leadFields = []; |
||
1542 | |||
1543 | /** |
||
1544 | * @param $mappedFields |
||
1545 | * @param $integrationFields |
||
1546 | * @param $mauticFields |
||
1547 | * @param $fieldType |
||
1548 | */ |
||
1549 | $cleanup = function (&$mappedFields, $integrationFields, $mauticFields, $fieldType) use (&$missingRequiredFields, &$featureSettings) { |
||
1550 | $updateKey = ('companyFields' === $fieldType) ? 'update_mautic_company' : 'update_mautic'; |
||
1551 | $removeFields = array_keys(array_diff_key($mappedFields, $integrationFields)); |
||
1552 | |||
1553 | // Find all the mapped fields that no longer exist in Mautic |
||
1554 | if ($nonExistentFields = array_diff($mappedFields, array_keys($mauticFields))) { |
||
1555 | // Remove those fields |
||
1556 | $removeFields = array_merge($removeFields, array_keys($nonExistentFields)); |
||
1557 | } |
||
1558 | |||
1559 | foreach ($removeFields as $field) { |
||
1560 | unset($mappedFields[$field]); |
||
1561 | |||
1562 | if (isset($featureSettings[$updateKey])) { |
||
1563 | unset($featureSettings[$updateKey][$field]); |
||
1564 | } |
||
1565 | } |
||
1566 | |||
1567 | // Check that the remaining fields have an updateKey set |
||
1568 | foreach ($mappedFields as $field => $mauticField) { |
||
1569 | if (!isset($featureSettings[$updateKey][$field])) { |
||
1570 | // Assume it's mapped to Mautic |
||
1571 | $featureSettings[$updateKey][$field] = 1; |
||
1572 | } |
||
1573 | } |
||
1574 | |||
1575 | // Check if required fields are missing |
||
1576 | $required = $this->getRequiredFields($integrationFields, $fieldType); |
||
1577 | if (array_diff_key($required, $mappedFields)) { |
||
1578 | $missingRequiredFields[$fieldType] = true; |
||
1579 | } |
||
1580 | }; |
||
1581 | |||
1582 | if ($submittedObjects) { |
||
1583 | if (in_array('company', $submittedObjects)) { |
||
1584 | // special handling for company fields |
||
1585 | if (isset($availableIntegrationFields['company'])) { |
||
1586 | $cleanup($submittedCompanyFields, $availableIntegrationFields['company'], $mauticCompanyFields, 'companyFields'); |
||
1587 | $featureSettings['companyFields'] = $submittedCompanyFields; |
||
1588 | unset($availableIntegrationFields['company']); |
||
1589 | } |
||
1590 | } |
||
1591 | |||
1592 | // Rest of the objects are merged and assumed to be leadFields |
||
1593 | // BC compatibility If extends fields to objects - 0 === contacts |
||
1594 | if (isset($availableIntegrationFields[0])) { |
||
1595 | $leadFields = array_merge($leadFields, $availableIntegrationFields[0]); |
||
1596 | } |
||
1597 | |||
1598 | foreach ($submittedObjects as $object) { |
||
1599 | if (isset($availableIntegrationFields[$object])) { |
||
1600 | $leadFields = array_merge($leadFields, $availableIntegrationFields[$object]); |
||
1601 | } |
||
1602 | } |
||
1603 | } else { |
||
1604 | // Cleanup assuming there are no objects as keys |
||
1605 | $leadFields = $availableIntegrationFields; |
||
1606 | } |
||
1607 | |||
1608 | if (!empty($leadFields)) { |
||
1609 | $cleanup($submittedFields, $leadFields, $mauticLeadFields, 'leadFields'); |
||
1610 | $featureSettings['leadFields'] = $submittedFields; |
||
1611 | } |
||
1612 | |||
1613 | $entity->setFeatureSettings($featureSettings); |
||
1614 | |||
1615 | return $missingRequiredFields; |
||
1616 | } |
||
1617 | |||
1618 | /** |
||
1619 | * @param string $fieldType |
||
1620 | * |
||
1621 | * @return array |
||
1622 | */ |
||
1623 | public function getRequiredFields(array $fields, $fieldType = '') |
||
1624 | { |
||
1625 | //use $fieldType to determine if email should be required. we use email as unique identifier for contacts only, |
||
1626 | // if any other fieldType use integrations own field types |
||
1627 | $requiredFields = []; |
||
1628 | foreach ($fields as $field => $details) { |
||
1629 | if ('leadFields' === $fieldType) { |
||
1630 | if ((is_array($details) && !empty($details['required'])) || 'email' === $field |
||
1631 | || (isset($details['optionLabel']) |
||
1632 | && 'email' == strtolower( |
||
1633 | $details['optionLabel'] |
||
1634 | )) |
||
1635 | ) { |
||
1636 | $requiredFields[$field] = $field; |
||
1637 | } |
||
1638 | } else { |
||
1639 | if ((is_array($details) && !empty($details['required'])) |
||
1640 | ) { |
||
1641 | $requiredFields[$field] = $field; |
||
1642 | } |
||
1643 | } |
||
1644 | } |
||
1645 | |||
1646 | return $requiredFields; |
||
1647 | } |
||
1648 | |||
1649 | /** |
||
1650 | * Match lead data with integration fields. |
||
1651 | * |
||
1652 | * @param $lead |
||
1653 | * @param $config |
||
1654 | * |
||
1655 | * @return array |
||
1656 | */ |
||
1657 | public function populateLeadData($lead, $config = []) |
||
1658 | { |
||
1659 | if (!isset($config['leadFields'])) { |
||
1660 | $config = $this->mergeConfigToFeatureSettings($config); |
||
1661 | |||
1662 | if (empty($config['leadFields'])) { |
||
1663 | return []; |
||
1664 | } |
||
1665 | } |
||
1666 | |||
1667 | if ($lead instanceof Lead) { |
||
1668 | $fields = $lead->getProfileFields(); |
||
1669 | $leadId = $lead->getId(); |
||
1670 | } else { |
||
1671 | $fields = $lead; |
||
1672 | $leadId = $lead['id']; |
||
1673 | } |
||
1674 | |||
1675 | $object = isset($config['object']) ? $config['object'] : null; |
||
1676 | $leadFields = $config['leadFields']; |
||
1677 | $availableFields = $this->getAvailableLeadFields($config); |
||
1678 | |||
1679 | if ($object) { |
||
1680 | $availableFields = $availableFields[$config['object']]; |
||
1681 | } else { |
||
1682 | $availableFields = (isset($availableFields[0])) ? $availableFields[0] : $availableFields; |
||
1683 | } |
||
1684 | |||
1685 | $unknown = $this->translator->trans('mautic.integration.form.lead.unknown'); |
||
1686 | $matched = []; |
||
1687 | |||
1688 | foreach ($availableFields as $key => $field) { |
||
1689 | $integrationKey = $matchIntegrationKey = $this->convertLeadFieldKey($key, $field); |
||
1690 | if (is_array($integrationKey)) { |
||
1691 | [$integrationKey, $matchIntegrationKey] = $integrationKey; |
||
1692 | } elseif (!isset($config['leadFields'][$integrationKey])) { |
||
1693 | continue; |
||
1694 | } |
||
1695 | |||
1696 | if (isset($leadFields[$integrationKey])) { |
||
1697 | if ('mauticContactTimelineLink' === $leadFields[$integrationKey]) { |
||
1698 | $matched[$integrationKey] = $this->getContactTimelineLink($leadId); |
||
1699 | |||
1700 | continue; |
||
1701 | } |
||
1702 | if ('mauticContactIsContactableByEmail' === $leadFields[$integrationKey]) { |
||
1703 | $matched[$integrationKey] = $this->getLeadDoNotContact($leadId); |
||
1704 | |||
1705 | continue; |
||
1706 | } |
||
1707 | if ('mauticContactId' === $leadFields[$integrationKey]) { |
||
1708 | $matched[$integrationKey] = $lead->getId(); |
||
1709 | continue; |
||
1710 | } |
||
1711 | $mauticKey = $leadFields[$integrationKey]; |
||
1712 | if (isset($fields[$mauticKey]) && '' !== $fields[$mauticKey] && null !== $fields[$mauticKey]) { |
||
1713 | $matched[$matchIntegrationKey] = $this->cleanPushData( |
||
1714 | $fields[$mauticKey], |
||
1715 | (isset($field['type'])) ? $field['type'] : 'string' |
||
1716 | ); |
||
1717 | } |
||
1718 | } |
||
1719 | |||
1720 | if (!empty($field['required']) && empty($matched[$matchIntegrationKey])) { |
||
1721 | $matched[$matchIntegrationKey] = $unknown; |
||
1722 | } |
||
1723 | } |
||
1724 | |||
1725 | return $matched; |
||
1726 | } |
||
1727 | |||
1728 | /** |
||
1729 | * Match Company data with integration fields. |
||
1730 | * |
||
1731 | * @param $entity |
||
1732 | * @param $config |
||
1733 | * |
||
1734 | * @return array |
||
1735 | */ |
||
1736 | public function populateCompanyData($entity, $config = []) |
||
1737 | { |
||
1738 | if (!isset($config['companyFields'])) { |
||
1739 | $config = $this->mergeConfigToFeatureSettings($config); |
||
1740 | |||
1741 | if (empty($config['companyFields'])) { |
||
1742 | return []; |
||
1743 | } |
||
1744 | } |
||
1745 | |||
1746 | if ($entity instanceof Lead) { |
||
1747 | $fields = $entity->getPrimaryCompany(); |
||
1748 | } else { |
||
1749 | $fields = $entity['primaryCompany']; |
||
1750 | } |
||
1751 | |||
1752 | $companyFields = $config['companyFields']; |
||
1753 | $availableFields = $this->getAvailableLeadFields($config)['company']; |
||
1754 | $unknown = $this->translator->trans('mautic.integration.form.lead.unknown'); |
||
1755 | $matched = []; |
||
1756 | |||
1757 | foreach ($availableFields as $key => $field) { |
||
1758 | $integrationKey = $this->convertLeadFieldKey($key, $field); |
||
1759 | |||
1760 | if (isset($companyFields[$key])) { |
||
1761 | $mauticKey = $companyFields[$key]; |
||
1762 | if (isset($fields[$mauticKey]) && !empty($fields[$mauticKey])) { |
||
1763 | $matched[$integrationKey] = $this->cleanPushData($fields[$mauticKey], (isset($field['type'])) ? $field['type'] : 'string'); |
||
1764 | } |
||
1765 | } |
||
1766 | |||
1767 | if (!empty($field['required']) && empty($matched[$integrationKey])) { |
||
1768 | $matched[$integrationKey] = $unknown; |
||
1769 | } |
||
1770 | } |
||
1771 | |||
1772 | return $matched; |
||
1773 | } |
||
1774 | |||
1775 | /** |
||
1776 | * Takes profile data from an integration and maps it to Mautic's lead fields. |
||
1777 | * |
||
1778 | * @param $data |
||
1779 | * @param array $config |
||
1780 | * @param null $object |
||
1781 | * |
||
1782 | * @return array |
||
1783 | */ |
||
1784 | public function populateMauticLeadData($data, $config = [], $object = null) |
||
1785 | { |
||
1786 | // Glean supported fields from what was returned by the integration |
||
1787 | $gleanedData = $data; |
||
1788 | |||
1789 | if (null == $object) { |
||
1790 | $object = 'lead'; |
||
1791 | } |
||
1792 | if ('company' == $object) { |
||
1793 | if (!isset($config['companyFields'])) { |
||
1794 | $config = $this->mergeConfigToFeatureSettings($config); |
||
1795 | |||
1796 | if (empty($config['companyFields'])) { |
||
1797 | return []; |
||
1798 | } |
||
1799 | } |
||
1800 | |||
1801 | $fields = $config['companyFields']; |
||
1802 | } |
||
1803 | if ('lead' == $object) { |
||
1804 | if (!isset($config['leadFields'])) { |
||
1805 | $config = $this->mergeConfigToFeatureSettings($config); |
||
1806 | |||
1807 | if (empty($config['leadFields'])) { |
||
1808 | return []; |
||
1809 | } |
||
1810 | } |
||
1811 | $fields = $config['leadFields']; |
||
1812 | } |
||
1813 | |||
1814 | $matched = []; |
||
1815 | foreach ($gleanedData as $key => $field) { |
||
1816 | if (isset($fields[$key]) && isset($gleanedData[$key]) |
||
1817 | && $this->translator->trans('mautic.integration.form.lead.unknown') !== $gleanedData[$key] |
||
1818 | ) { |
||
1819 | $matched[$fields[$key]] = $gleanedData[$key]; |
||
1820 | } |
||
1821 | } |
||
1822 | |||
1823 | return $matched; |
||
1824 | } |
||
1825 | |||
1826 | /** |
||
1827 | * Create or update existing Mautic lead from the integration's profile data. |
||
1828 | * |
||
1829 | * @param mixed $data Profile data from integration |
||
1830 | * @param bool|true $persist Set to false to not persist lead to the database in this method |
||
1831 | * @param array|null $socialCache |
||
1832 | * @param mixed||null $identifiers |
||
1833 | * |
||
1834 | * @return Lead |
||
1835 | */ |
||
1836 | public function getMauticLead($data, $persist = true, $socialCache = null, $identifiers = null) |
||
1837 | { |
||
1838 | if (is_object($data)) { |
||
1839 | // Convert to array in all levels |
||
1840 | $data = json_encode(json_decode($data), true); |
||
1841 | } elseif (is_string($data)) { |
||
1842 | // Assume JSON |
||
1843 | $data = json_decode($data, true); |
||
1844 | } |
||
1845 | |||
1846 | // Match that data with mapped lead fields |
||
1847 | $matchedFields = $this->populateMauticLeadData($data); |
||
1848 | |||
1849 | if (empty($matchedFields)) { |
||
1850 | return; |
||
1851 | } |
||
1852 | |||
1853 | // Find unique identifier fields used by the integration |
||
1854 | /** @var \Mautic\LeadBundle\Model\LeadModel $leadModel */ |
||
1855 | $leadModel = $this->leadModel; |
||
1856 | $uniqueLeadFields = $this->fieldModel->getUniqueIdentifierFields(); |
||
1857 | $uniqueLeadFieldData = []; |
||
1858 | |||
1859 | foreach ($matchedFields as $leadField => $value) { |
||
1860 | if (array_key_exists($leadField, $uniqueLeadFields) && !empty($value)) { |
||
1861 | $uniqueLeadFieldData[$leadField] = $value; |
||
1862 | } |
||
1863 | } |
||
1864 | |||
1865 | // Default to new lead |
||
1866 | $lead = new Lead(); |
||
1867 | $lead->setNewlyCreated(true); |
||
1868 | |||
1869 | if (count($uniqueLeadFieldData)) { |
||
1870 | $existingLeads = $this->em->getRepository('MauticLeadBundle:Lead') |
||
1871 | ->getLeadsByUniqueFields($uniqueLeadFieldData); |
||
1872 | |||
1873 | if (!empty($existingLeads)) { |
||
1874 | $lead = array_shift($existingLeads); |
||
1875 | } |
||
1876 | } |
||
1877 | |||
1878 | $leadModel->setFieldValues($lead, $matchedFields, false, false); |
||
1879 | |||
1880 | // Update the social cache |
||
1881 | $leadSocialCache = $lead->getSocialCache(); |
||
1882 | if (!isset($leadSocialCache[$this->getName()])) { |
||
1883 | $leadSocialCache[$this->getName()] = []; |
||
1884 | } |
||
1885 | |||
1886 | if (null !== $socialCache) { |
||
1887 | $leadSocialCache[$this->getName()] = array_merge($leadSocialCache[$this->getName()], $socialCache); |
||
1888 | } |
||
1889 | |||
1890 | // Check for activity while here |
||
1891 | if (null !== $identifiers && in_array('public_activity', $this->getSupportedFeatures())) { |
||
1892 | $this->getPublicActivity($identifiers, $leadSocialCache[$this->getName()]); |
||
1893 | } |
||
1894 | |||
1895 | $lead->setSocialCache($leadSocialCache); |
||
1896 | |||
1897 | // Update the internal info integration object that has updated the record |
||
1898 | if (isset($data['internal'])) { |
||
1899 | $internalInfo = $lead->getInternal(); |
||
1900 | $internalInfo[$this->getName()] = $data['internal']; |
||
1901 | $lead->setInternal($internalInfo); |
||
1902 | } |
||
1903 | |||
1904 | if ($persist && !empty($lead->getChanges(true))) { |
||
1905 | // Only persist if instructed to do so as it could be that calling code needs to manipulate the lead prior to executing event listeners |
||
1906 | try { |
||
1907 | $lead->setManipulator(new LeadManipulator( |
||
1908 | 'plugin', |
||
1909 | $this->getName(), |
||
1910 | null, |
||
1911 | $this->getDisplayName() |
||
1912 | )); |
||
1913 | $leadModel->saveEntity($lead, false); |
||
1914 | } catch (\Exception $exception) { |
||
1915 | $this->logger->addWarning($exception->getMessage()); |
||
1916 | |||
1917 | return; |
||
1918 | } |
||
1919 | } |
||
1920 | |||
1921 | return $lead; |
||
1922 | } |
||
1923 | |||
1924 | /** |
||
1925 | * Merges a config from integration_list with feature settings. |
||
1926 | * |
||
1927 | * @param array $config |
||
1928 | * |
||
1929 | * @return array|mixed |
||
1930 | */ |
||
1931 | public function mergeConfigToFeatureSettings($config = []) |
||
1932 | { |
||
1933 | $featureSettings = $this->settings->getFeatureSettings(); |
||
1934 | |||
1935 | if (isset($config['config']) |
||
1936 | && (empty($config['integration']) |
||
1937 | || (!empty($config['integration']) |
||
1938 | && $config['integration'] == $this->getName())) |
||
1939 | ) { |
||
1940 | $featureSettings = array_merge($featureSettings, $config['config']); |
||
1941 | } |
||
1942 | |||
1943 | return $featureSettings; |
||
1944 | } |
||
1945 | |||
1946 | /** |
||
1947 | * Return key recognized by integration. |
||
1948 | * |
||
1949 | * @param $key |
||
1950 | * @param $field |
||
1951 | * |
||
1952 | * @return mixed |
||
1953 | */ |
||
1954 | public function convertLeadFieldKey($key, $field) |
||
1955 | { |
||
1956 | return $key; |
||
1957 | } |
||
1958 | |||
1959 | /** |
||
1960 | * Sets whether fields should be sorted alphabetically or by the order the integration feeds. |
||
1961 | */ |
||
1962 | public function sortFieldsAlphabetically() |
||
1963 | { |
||
1964 | return true; |
||
1965 | } |
||
1966 | |||
1967 | /** |
||
1968 | * Used to match local field name with remote field name. |
||
1969 | * |
||
1970 | * @param string $field |
||
1971 | * @param string $subfield |
||
1972 | * |
||
1973 | * @return mixed |
||
1974 | */ |
||
1975 | public function matchFieldName($field, $subfield = '') |
||
1976 | { |
||
1977 | if (!empty($field) && !empty($subfield)) { |
||
1978 | return $subfield.ucfirst($field); |
||
1979 | } |
||
1980 | |||
1981 | return $field; |
||
1982 | } |
||
1983 | |||
1984 | /** |
||
1985 | * Convert and assign the data to assignable fields. |
||
1986 | * |
||
1987 | * @param mixed $data |
||
1988 | * |
||
1989 | * @return array |
||
1990 | */ |
||
1991 | protected function matchUpData($data) |
||
1992 | { |
||
1993 | $info = []; |
||
1994 | $available = $this->getAvailableLeadFields(); |
||
1995 | |||
1996 | foreach ($available as $field => $fieldDetails) { |
||
1997 | if (is_array($data)) { |
||
1998 | if (!isset($data[$field]) and !is_object($data)) { |
||
1999 | $info[$field] = ''; |
||
2000 | continue; |
||
2001 | } else { |
||
2002 | $values = $data[$field]; |
||
2003 | } |
||
2004 | } else { |
||
2005 | if (!isset($data->$field)) { |
||
2006 | $info[$field] = ''; |
||
2007 | continue; |
||
2008 | } else { |
||
2009 | $values = $data->$field; |
||
2010 | } |
||
2011 | } |
||
2012 | |||
2013 | switch ($fieldDetails['type']) { |
||
2014 | case 'string': |
||
2015 | case 'boolean': |
||
2016 | $info[$field] = $values; |
||
2017 | break; |
||
2018 | case 'object': |
||
2019 | foreach ($fieldDetails['fields'] as $f) { |
||
2020 | if (isset($values->$f)) { |
||
2021 | $fn = $this->matchFieldName($field, $f); |
||
2022 | |||
2023 | $info[$fn] = $values->$f; |
||
2024 | } |
||
2025 | } |
||
2026 | break; |
||
2027 | case 'array_object': |
||
2028 | $objects = []; |
||
2029 | if (!empty($values)) { |
||
2030 | foreach ($values as $v) { |
||
2031 | if (isset($v->value)) { |
||
2032 | $objects[] = $v->value; |
||
2033 | } |
||
2034 | } |
||
2035 | } |
||
2036 | $fn = (isset($fieldDetails['fields'][0])) ? $this->matchFieldName( |
||
2037 | $field, |
||
2038 | $fieldDetails['fields'][0] |
||
2039 | ) : $field; |
||
2040 | $info[$fn] = implode('; ', $objects); |
||
2041 | |||
2042 | break; |
||
2043 | } |
||
2044 | } |
||
2045 | |||
2046 | return $info; |
||
2047 | } |
||
2048 | |||
2049 | /** |
||
2050 | * Get the path to the profile templates for this integration. |
||
2051 | */ |
||
2052 | public function getSocialProfileTemplate() |
||
2053 | { |
||
2054 | return null; |
||
2055 | } |
||
2056 | |||
2057 | /** |
||
2058 | * Checks to ensure an image still exists before caching. |
||
2059 | * |
||
2060 | * @param string $url |
||
2061 | * |
||
2062 | * @return bool |
||
2063 | */ |
||
2064 | public function checkImageExists($url) |
||
2065 | { |
||
2066 | $ch = curl_init($url); |
||
2067 | curl_setopt($ch, CURLOPT_NOBODY, true); |
||
2068 | curl_setopt( |
||
2069 | $ch, |
||
2070 | CURLOPT_USERAGENT, |
||
2071 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13' |
||
2072 | ); |
||
2073 | curl_exec($ch); |
||
2074 | $retcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); |
||
2075 | curl_close($ch); |
||
2076 | |||
2077 | return 200 == $retcode; |
||
2078 | } |
||
2079 | |||
2080 | /** |
||
2081 | * @return \Mautic\CoreBundle\Model\NotificationModel |
||
2082 | */ |
||
2083 | public function getNotificationModel() |
||
2084 | { |
||
2085 | return $this->notificationModel; |
||
2086 | } |
||
2087 | |||
2088 | public function logIntegrationError(\Exception $e, Lead $contact = null) |
||
2089 | { |
||
2090 | $logger = $this->logger; |
||
2091 | |||
2092 | if ($e instanceof ApiErrorException) { |
||
2093 | if (null === $this->adminUsers) { |
||
2094 | $this->adminUsers = $this->em->getRepository('MauticUserBundle:User')->getEntities( |
||
2095 | [ |
||
2096 | 'filter' => [ |
||
2097 | 'force' => [ |
||
2098 | [ |
||
2099 | 'column' => 'r.isAdmin', |
||
2100 | 'expr' => 'eq', |
||
2101 | 'value' => true, |
||
2102 | ], |
||
2103 | ], |
||
2104 | ], |
||
2105 | ] |
||
2106 | ); |
||
2107 | } |
||
2108 | |||
2109 | $errorMessage = $e->getMessage(); |
||
2110 | $errorHeader = $this->getTranslator()->trans( |
||
2111 | 'mautic.integration.error', |
||
2112 | [ |
||
2113 | '%name%' => $this->getName(), |
||
2114 | ] |
||
2115 | ); |
||
2116 | |||
2117 | if ($contact || $contact = $e->getContact()) { |
||
2118 | // Append a link to the contact |
||
2119 | $contactId = $contact->getId(); |
||
2120 | $contactName = $contact->getPrimaryIdentifier(); |
||
2121 | } elseif ($contactId = $e->getContactId()) { |
||
2122 | $contactName = $this->getTranslator()->trans('mautic.integration.error.generic_contact_name', ['%id%' => $contactId]); |
||
2123 | } |
||
2124 | |||
2125 | $this->lastIntegrationError = $errorHeader.': '.$errorMessage; |
||
2126 | |||
2127 | if ($contactId) { |
||
2128 | $contactLink = $this->router->generate( |
||
2129 | 'mautic_contact_action', |
||
2130 | [ |
||
2131 | 'objectAction' => 'view', |
||
2132 | 'objectId' => $contactId, |
||
2133 | ], |
||
2134 | UrlGeneratorInterface::ABSOLUTE_URL |
||
2135 | ); |
||
2136 | $errorMessage .= ' <a href="'.$contactLink.'">'.$contactName.'</a>'; |
||
2137 | } |
||
2138 | |||
2139 | // Prevent a flood of the same messages |
||
2140 | $messageHash = md5($errorMessage); |
||
2141 | if (!array_key_exists($messageHash, $this->notifications)) { |
||
2142 | foreach ($this->adminUsers as $user) { |
||
2143 | $this->getNotificationModel()->addNotification( |
||
2144 | $errorMessage, |
||
2145 | $this->getName(), |
||
2146 | false, |
||
2147 | $errorHeader, |
||
2148 | 'text-danger fa-exclamation-circle', |
||
2149 | null, |
||
2150 | $user |
||
2151 | ); |
||
2152 | } |
||
2153 | |||
2154 | $this->notifications[$messageHash] = true; |
||
2155 | } |
||
2156 | } |
||
2157 | |||
2158 | $logger->addError('INTEGRATION ERROR: '.$this->getName().' - '.(('dev' == MAUTIC_ENV) ? (string) $e : $e->getMessage())); |
||
2159 | } |
||
2160 | |||
2161 | /** |
||
2162 | * @return string|null |
||
2163 | */ |
||
2164 | public function getLastIntegrationError() |
||
2165 | { |
||
2166 | return $this->lastIntegrationError; |
||
2167 | } |
||
2168 | |||
2169 | /** |
||
2170 | * @return $this |
||
2171 | */ |
||
2172 | public function resetLastIntegrationError() |
||
2173 | { |
||
2174 | $this->lastIntegrationError = null; |
||
2175 | |||
2176 | return $this; |
||
2177 | } |
||
2178 | |||
2179 | /** |
||
2180 | * Returns notes specific to sections of the integration form (if applicable). |
||
2181 | * |
||
2182 | * @param $section |
||
2183 | * |
||
2184 | * @return string |
||
2185 | */ |
||
2186 | public function getFormNotes($section) |
||
2187 | { |
||
2188 | if ('leadfield_match' == $section) { |
||
2189 | return ['mautic.integration.form.field_match_notes', 'info']; |
||
2190 | } else { |
||
2191 | return ['', 'info']; |
||
2192 | } |
||
2193 | } |
||
2194 | |||
2195 | /** |
||
2196 | * Allows appending extra data to the config. |
||
2197 | * |
||
2198 | * @param FormBuilder|Form $builder |
||
2199 | * @param array $data |
||
2200 | * @param string $formArea Section of form being built keys|features|integration |
||
2201 | * keys can be used to store login/request related settings; keys are encrypted |
||
2202 | * features can be used for configuring share buttons, etc |
||
2203 | * integration is called when adding an integration to events like point triggers, |
||
2204 | * campaigns actions, forms actions, etc |
||
2205 | */ |
||
2206 | public function appendToForm(&$builder, $data, $formArea) |
||
2207 | { |
||
2208 | } |
||
2209 | |||
2210 | /** |
||
2211 | * @param FormBuilder $builder |
||
2212 | * @param array $options |
||
2213 | */ |
||
2214 | public function modifyForm($builder, $options) |
||
2215 | { |
||
2216 | $this->dispatcher->dispatch( |
||
2217 | PluginEvents::PLUGIN_ON_INTEGRATION_FORM_BUILD, |
||
2218 | new PluginIntegrationFormBuildEvent($this, $builder, $options) |
||
2219 | ); |
||
2220 | } |
||
2221 | |||
2222 | /** |
||
2223 | * Returns settings for the integration form. |
||
2224 | * |
||
2225 | * @return array |
||
2226 | */ |
||
2227 | public function getFormSettings() |
||
2228 | { |
||
2229 | $type = $this->getAuthenticationType(); |
||
2230 | $enableDataPriority = $this->getDataPriority(); |
||
2231 | switch ($type) { |
||
2232 | case 'oauth1a': |
||
2233 | case 'oauth2': |
||
2234 | $callback = true; |
||
2235 | $requiresAuthorization = true; |
||
2236 | break; |
||
2237 | default: |
||
2238 | $callback = false; |
||
2239 | $requiresAuthorization = false; |
||
2240 | break; |
||
2241 | } |
||
2242 | |||
2243 | return [ |
||
2244 | 'requires_callback' => $callback, |
||
2245 | 'requires_authorization' => $requiresAuthorization, |
||
2246 | 'default_features' => [], |
||
2247 | 'enable_data_priority' => $enableDataPriority, |
||
2248 | ]; |
||
2249 | } |
||
2250 | |||
2251 | /** |
||
2252 | * @return array |
||
2253 | */ |
||
2254 | public function getFormDisplaySettings() |
||
2255 | { |
||
2256 | /** @var PluginIntegrationFormDisplayEvent $event */ |
||
2257 | $event = $this->dispatcher->dispatch( |
||
2258 | PluginEvents::PLUGIN_ON_INTEGRATION_FORM_DISPLAY, |
||
2259 | new PluginIntegrationFormDisplayEvent($this, $this->getFormSettings()) |
||
2260 | ); |
||
2261 | |||
2262 | return $event->getSettings(); |
||
2263 | } |
||
2264 | |||
2265 | /** |
||
2266 | * Get available fields for choices in the config UI. |
||
2267 | * |
||
2268 | * @param array $settings |
||
2269 | * |
||
2270 | * @return array |
||
2271 | */ |
||
2272 | public function getFormLeadFields($settings = []) |
||
2273 | { |
||
2274 | if (isset($settings['feature_settings']['objects']['company'])) { |
||
2275 | unset($settings['feature_settings']['objects']['company']); |
||
2276 | } |
||
2277 | |||
2278 | return ($this->isAuthorized()) ? $this->getAvailableLeadFields($settings) : []; |
||
2279 | } |
||
2280 | |||
2281 | /** |
||
2282 | * Get available company fields for choices in the config UI. |
||
2283 | * |
||
2284 | * @param array $settings |
||
2285 | * |
||
2286 | * @return array |
||
2287 | */ |
||
2288 | public function getFormCompanyFields($settings = []) |
||
2289 | { |
||
2290 | $settings['feature_settings']['objects']['company'] = 'company'; |
||
2291 | |||
2292 | return ($this->isAuthorized()) ? $this->getAvailableLeadFields($settings) : []; |
||
2293 | } |
||
2294 | |||
2295 | /** |
||
2296 | * returns template to render on popup window after trying to run OAuth. |
||
2297 | * |
||
2298 | * @return string|null |
||
2299 | */ |
||
2300 | public function getPostAuthTemplate() |
||
2301 | { |
||
2302 | return null; |
||
2303 | } |
||
2304 | |||
2305 | /** |
||
2306 | * @param $contactId |
||
2307 | * |
||
2308 | * @return string |
||
2309 | */ |
||
2310 | public function getContactTimelineLink($contactId) |
||
2311 | { |
||
2312 | return $this->router->generate( |
||
2313 | 'mautic_plugin_timeline_view', |
||
2314 | ['integration' => $this->getName(), 'leadId' => $contactId], |
||
2315 | UrlGeneratorInterface::ABSOLUTE_URL |
||
2316 | ); |
||
2317 | } |
||
2318 | |||
2319 | /** |
||
2320 | * @param $eventName |
||
2321 | * @param array $keys |
||
2322 | * |
||
2323 | * @return array |
||
2324 | */ |
||
2325 | protected function dispatchIntegrationKeyEvent($eventName, $keys = []) |
||
2326 | { |
||
2327 | /** @var PluginIntegrationKeyEvent $event */ |
||
2328 | $event = $this->dispatcher->dispatch( |
||
2329 | $eventName, |
||
2330 | new PluginIntegrationKeyEvent($this, $keys) |
||
2331 | ); |
||
2332 | |||
2333 | return $event->getKeys(); |
||
2334 | } |
||
2335 | |||
2336 | /** |
||
2337 | * Cleans the identifier for api calls. |
||
2338 | * |
||
2339 | * @param mixed $identifier |
||
2340 | * |
||
2341 | * @return string |
||
2342 | */ |
||
2343 | protected function cleanIdentifier($identifier) |
||
2344 | { |
||
2345 | if (is_array($identifier)) { |
||
2346 | foreach ($identifier as &$i) { |
||
2347 | $i = urlencode($i); |
||
2348 | } |
||
2349 | } else { |
||
2350 | $identifier = urlencode($identifier); |
||
2351 | } |
||
2352 | |||
2353 | return $identifier; |
||
2354 | } |
||
2355 | |||
2356 | /** |
||
2357 | * @param $value |
||
2358 | * @param string $fieldType |
||
2359 | * |
||
2360 | * @return bool|float|string |
||
2361 | */ |
||
2362 | public function cleanPushData($value, $fieldType = self::FIELD_TYPE_STRING) |
||
2363 | { |
||
2364 | return Cleaner::clean($value, $fieldType); |
||
2365 | } |
||
2366 | |||
2367 | /** |
||
2368 | * @return \Monolog\Logger|LoggerInterface |
||
2369 | */ |
||
2370 | public function getLogger() |
||
2371 | { |
||
2372 | return $this->logger; |
||
2373 | } |
||
2374 | |||
2375 | /** |
||
2376 | * @param $leadsToSync |
||
2377 | * @param bool|\Exception $error |
||
2378 | * |
||
2379 | * @return int Number ignored due to being duplicates |
||
2380 | * |
||
2381 | * @throws ApiErrorException |
||
2382 | * @throws \Exception |
||
2383 | */ |
||
2384 | protected function cleanupFromSync(&$leadsToSync = [], $error = false) |
||
2385 | { |
||
2386 | $duplicates = 0; |
||
2387 | if ($this->mauticDuplicates) { |
||
2388 | // Create integration entities for these to be ignored until they are updated |
||
2389 | foreach ($this->mauticDuplicates as $id => $dup) { |
||
2390 | $this->persistIntegrationEntities[] = $this->createIntegrationEntity('Lead', null, $dup, $id, [], false); |
||
2391 | ++$duplicates; |
||
2392 | } |
||
2393 | |||
2394 | $this->mauticDuplicates = []; |
||
2395 | } |
||
2396 | |||
2397 | $integrationEntityRepo = $this->getIntegrationEntityRepository(); |
||
2398 | if (!empty($leadsToSync)) { |
||
2399 | // Let's only sync thos that have actual changes to prevent a loop |
||
2400 | $integrationEntityRepo->saveEntities($leadsToSync); |
||
2401 | $this->em->clear(Lead::class); |
||
2402 | $leadsToSync = []; |
||
2403 | } |
||
2404 | |||
2405 | // Persist updated entities if applicable |
||
2406 | if ($this->persistIntegrationEntities) { |
||
2407 | $integrationEntityRepo->saveEntities($this->persistIntegrationEntities); |
||
2408 | $this->persistIntegrationEntities = []; |
||
2409 | } |
||
2410 | |||
2411 | // If there are any deleted, mark it as so to prevent them from being queried over and over or recreated |
||
2412 | if ($this->deleteIntegrationEntities) { |
||
2413 | $integrationEntityRepo->deleteEntities($this->deleteIntegrationEntities); |
||
2414 | $this->deleteIntegrationEntities = []; |
||
2415 | } |
||
2416 | |||
2417 | $this->em->clear(IntegrationEntity::class); |
||
2418 | |||
2419 | if ($error) { |
||
2420 | if ($error instanceof \Exception) { |
||
2421 | throw $error; |
||
2422 | } |
||
2423 | |||
2424 | throw new ApiErrorException($error); |
||
2425 | } |
||
2426 | |||
2427 | return $duplicates; |
||
2428 | } |
||
2429 | |||
2430 | /** |
||
2431 | * @param array $mapping array of [$mauticId => ['entity' => FormEntity, 'integration_entity_id' => $integrationId]] |
||
2432 | * @param $integrationEntity |
||
2433 | * @param $internalEntity |
||
2434 | * @param array $params |
||
2435 | */ |
||
2436 | protected function buildIntegrationEntities(array $mapping, $integrationEntity, $internalEntity, $params = []) |
||
2437 | { |
||
2438 | $integrationEntityRepo = $this->getIntegrationEntityRepository(); |
||
2439 | $integrationEntities = $integrationEntityRepo->getIntegrationEntities( |
||
2440 | $this->getName(), |
||
2441 | $integrationEntity, |
||
2442 | $internalEntity, |
||
2443 | array_keys($mapping) |
||
2444 | ); |
||
2445 | |||
2446 | // Find those that don't exist and create them |
||
2447 | $createThese = array_diff_key($mapping, $integrationEntities); |
||
2448 | |||
2449 | foreach ($mapping as $internalEntityId => $entity) { |
||
2450 | if (is_array($entity)) { |
||
2451 | $integrationEntityId = $entity['integration_entity_id']; |
||
2452 | $internalEntityObject = $entity['entity']; |
||
2453 | } else { |
||
2454 | $integrationEntityId = $entity; |
||
2455 | $internalEntityObject = null; |
||
2456 | } |
||
2457 | |||
2458 | if (isset($createThese[$internalEntityId])) { |
||
2459 | $entity = $this->createIntegrationEntity( |
||
2460 | $integrationEntity, |
||
2461 | $integrationEntityId, |
||
2462 | $internalEntity, |
||
2463 | $internalEntityId, |
||
2464 | [], |
||
2465 | false |
||
2466 | ); |
||
2467 | $entity->setLastSyncDate($this->getLastSyncDate($internalEntityObject, $params, false)); |
||
2468 | $integrationEntities[$internalEntityId] = $entity; |
||
2469 | } else { |
||
2470 | $integrationEntities[$internalEntityId]->setLastSyncDate($this->getLastSyncDate($internalEntityObject, $params, false)); |
||
2471 | } |
||
2472 | } |
||
2473 | |||
2474 | $integrationEntityRepo->saveEntities($integrationEntities); |
||
2475 | $this->em->clear(IntegrationEntity::class); |
||
2476 | } |
||
2477 | |||
2478 | /** |
||
2479 | * @param null $entity |
||
2480 | * @param array $params |
||
2481 | * @param bool $ignoreEntityChanges |
||
2482 | * |
||
2483 | * @return bool|\DateTime|null |
||
2484 | */ |
||
2485 | protected function getLastSyncDate($entity = null, $params = [], $ignoreEntityChanges = true) |
||
2486 | { |
||
2487 | $isNew = method_exists($entity, 'isNew') && $entity->isNew(); |
||
2488 | if (!$isNew && !$ignoreEntityChanges && isset($params['start']) && $entity && method_exists($entity, 'getChanges')) { |
||
2489 | // Check to see if this contact was modified prior to the fetch so that the push catches it |
||
2490 | /** @var FormEntity $entity */ |
||
2491 | $changes = $entity->getChanges(true); |
||
2492 | if (empty($changes) || isset($changes['dateModified'])) { |
||
2493 | $startSyncDate = \DateTime::createFromFormat(\DateTime::ISO8601, $params['start']); |
||
2494 | $entityDateModified = $entity->getDateModified(); |
||
2495 | |||
2496 | if (isset($changes['dateModified'])) { |
||
2497 | $originalDateModified = \DateTime::createFromFormat(\DateTime::ISO8601, $changes['dateModified'][0]); |
||
2498 | } elseif ($entityDateModified) { |
||
2499 | $originalDateModified = $entityDateModified; |
||
2500 | } else { |
||
2501 | $originalDateModified = $entity->getDateAdded(); |
||
2502 | } |
||
2503 | |||
2504 | if ($originalDateModified >= $startSyncDate) { |
||
2505 | // Return null so that the push sync catches |
||
2506 | return null; |
||
2507 | } |
||
2508 | } |
||
2509 | } |
||
2510 | |||
2511 | return (defined('MAUTIC_DATE_MODIFIED_OVERRIDE')) ? \DateTime::createFromFormat('U', MAUTIC_DATE_MODIFIED_OVERRIDE) |
||
2512 | : new \DateTime(); |
||
2513 | } |
||
2514 | |||
2515 | /** |
||
2516 | * @param $fields |
||
2517 | * @param $keys |
||
2518 | * @param null $object |
||
2519 | * |
||
2520 | * @return mixed |
||
2521 | */ |
||
2522 | public function prepareFieldsForSync($fields, $keys, $object = null) |
||
2523 | { |
||
2524 | return $fields; |
||
2525 | } |
||
2526 | |||
2527 | /** |
||
2528 | * Function used to format unformated fields coming from FieldsTypeTrait |
||
2529 | * (usually used in campaign actions). |
||
2530 | * |
||
2531 | * @param $fields |
||
2532 | * |
||
2533 | * @return array |
||
2534 | */ |
||
2535 | public function formatMatchedFields($fields) |
||
2536 | { |
||
2537 | $formattedFields = []; |
||
2538 | |||
2539 | if (isset($fields['m_1'])) { |
||
2540 | $xfields = count($fields) / 3; |
||
2541 | for ($i = 1; $i < $xfields; ++$i) { |
||
2542 | if (isset($fields['i_'.$i]) && isset($fields['m_'.$i])) { |
||
2543 | $formattedFields[$fields['i_'.$i]] = $fields['m_'.$i]; |
||
2544 | } else { |
||
2545 | continue; |
||
2546 | } |
||
2547 | } |
||
2548 | } |
||
2549 | |||
2550 | if (!empty($formattedFields)) { |
||
2551 | $fields = $formattedFields; |
||
2552 | } |
||
2553 | |||
2554 | return $fields; |
||
2555 | } |
||
2556 | |||
2557 | /** |
||
2558 | * @param $leadId |
||
2559 | * @param string $channel |
||
2560 | * |
||
2561 | * @return int |
||
2562 | */ |
||
2563 | public function getLeadDoNotContact($leadId, $channel = 'email') |
||
2564 | { |
||
2565 | $isDoNotContact = 0; |
||
2566 | if ($lead = $this->leadModel->getEntity($leadId)) { |
||
2567 | $isContactableReason = $this->doNotContact->isContactable($lead, $channel); |
||
2568 | if (DoNotContact::IS_CONTACTABLE !== $isContactableReason) { |
||
2569 | $isDoNotContact = 1; |
||
2570 | } |
||
2571 | } |
||
2572 | |||
2573 | return $isDoNotContact; |
||
2574 | } |
||
2575 | |||
2576 | /** |
||
2577 | * Get pseudo fields from mautic, these are lead properties we want to map to integration fields. |
||
2578 | * |
||
2579 | * @param $lead |
||
2580 | * |
||
2581 | * @return mixed |
||
2582 | */ |
||
2583 | public function getCompoundMauticFields($lead) |
||
2584 | { |
||
2585 | if ($lead['internal_entity_id']) { |
||
2586 | $lead['mauticContactId'] = $lead['internal_entity_id']; |
||
2587 | $lead['mauticContactTimelineLink'] = $this->getContactTimelineLink($lead['internal_entity_id']); |
||
2588 | $lead['mauticContactIsContactableByEmail'] = $this->getLeadDoNotContact($lead['internal_entity_id']); |
||
2589 | } |
||
2590 | |||
2591 | return $lead; |
||
2592 | } |
||
2593 | |||
2594 | /** |
||
2595 | * @param $fieldName |
||
2596 | * |
||
2597 | * @return bool |
||
2598 | */ |
||
2599 | public function isCompoundMauticField($fieldName) |
||
2600 | { |
||
2601 | $compoundFields = [ |
||
2602 | 'mauticContactTimelineLink' => 'mauticContactTimelineLink', |
||
2603 | 'mauticContactId' => 'mauticContactId', |
||
2604 | ]; |
||
2605 | |||
2606 | if (true === $this->updateDncByDate()) { |
||
2607 | $compoundFields['mauticContactIsContactableByEmail'] = 'mauticContactIsContactableByEmail'; |
||
2608 | } |
||
2609 | |||
2610 | return isset($compoundFields[$fieldName]); |
||
2611 | } |
||
2612 | |||
2613 | /** |
||
2614 | * Update the record in each system taking the last modified record. |
||
2615 | * |
||
2616 | * @param string $channel |
||
2617 | * |
||
2618 | * @return int |
||
2619 | * |
||
2620 | * @throws ApiErrorException |
||
2621 | */ |
||
2622 | public function getLeadDoNotContactByDate($channel, $records, $object, $lead, $integrationData, $params = []) |
||
2623 | { |
||
2624 | return $records; |
||
2625 | } |
||
2626 | } |
||
2627 |