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); |
||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
542 | $this->em->flush($entity); |
||||
0 ignored issues
–
show
It seems like
$entity can also be of type true ; however, parameter $entity of Doctrine\ORM\EntityManager::flush() does only seem to accept array|null|object , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
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 |