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; |
||
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; |
||
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) { |
||
0 ignored issues
–
show
|
|||
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) { |
||
0 ignored issues
–
show
The expression
$this->persistIntegrationEntities of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
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) { |
||
0 ignored issues
–
show
The expression
$this->deleteIntegrationEntities of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
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 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.