From 125820b7dfdafc873ce339eb74c0b78e125a5a1b Mon Sep 17 00:00:00 2001 From: John Linhart Date: Fri, 24 Aug 2018 17:51:07 +0200 Subject: [PATCH 1/2] New campaign webhook helper tests --- .../Tests/Helper/CampaignHelperTest.php | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 app/bundles/WebhookBundle/Tests/Helper/CampaignHelperTest.php diff --git a/app/bundles/WebhookBundle/Tests/Helper/CampaignHelperTest.php b/app/bundles/WebhookBundle/Tests/Helper/CampaignHelperTest.php new file mode 100644 index 00000000000..13dd6aa87b7 --- /dev/null +++ b/app/bundles/WebhookBundle/Tests/Helper/CampaignHelperTest.php @@ -0,0 +1,128 @@ +contact = $this->createMock(Lead::class); + $this->connector = $this->createMock(Http::class); + $this->ipCollection = new ArrayCollection(); + $this->campaignHelper = new CampaignHelper($this->connector); + + $this->ipCollection->add((new IpAddress())->setIpAddress('127.0.0.1')); + $this->ipCollection->add((new IpAddress())->setIpAddress('127.0.0.2')); + + $this->contact->expects($this->once()) + ->method('getProfileFields') + ->willReturn(['email' => 'john@doe.email', 'company' => 'Mautic']); + + $this->contact->expects($this->once()) + ->method('getIpAddresses') + ->willReturn($this->ipCollection); + } + + public function testFireWebhookWithGet() + { + $expectedUrl = 'https://mautic.org?test=tee&email=john%40doe.email&IP=127.0.0.1%2C127.0.0.2'; + + $this->connector->expects($this->once()) + ->method('get') + ->with($expectedUrl, ['test' => 'tee', 'company' => 'Mautic'], 10) + ->willReturn((object) ['code' => 200]); + + $this->campaignHelper->fireWebhook($this->provideSampleConfig(), $this->contact); + } + + public function testFireWebhookWithPost() + { + $config = $this->provideSampleConfig('post'); + $expectedUrl = 'https://mautic.org?test=tee&email=john%40doe.email&IP=127.0.0.1%2C127.0.0.2'; + + $this->connector->expects($this->once()) + ->method('post') + ->with('https://mautic.org', ['test' => 'tee', 'email' => 'john@doe.email', 'IP' => '127.0.0.1,127.0.0.2'], ['test' => 'tee', 'company' => 'Mautic'], 10) + ->willReturn((object) ['code' => 200]); + + $this->campaignHelper->fireWebhook($config, $this->contact); + } + + public function testFireWebhookWhenReturningNotFound() + { + $this->connector->expects($this->once()) + ->method('get') + ->willReturn((object) ['code' => 404]); + + $this->expectException(\OutOfRangeException::class); + + $this->campaignHelper->fireWebhook($this->provideSampleConfig(), $this->contact); + } + + private function provideSampleConfig($method = 'get') + { + return [ + 'url' => 'https://mautic.org', + 'method' => $method, + 'timeout' => 10, + 'additional_data' => [ + 'list' => [ + [ + 'label' => 'test', + 'value' => 'tee', + ], + [ + 'label' => 'email', + 'value' => '{contactfield=email}', + ], + [ + 'label' => 'IP', + 'value' => '{contactfield=ipAddress}', + ], + ], + ], + 'headers' => [ + 'list' => [ + [ + 'label' => 'test', + 'value' => 'tee', + ], + [ + 'label' => 'company', + 'value' => '{contactfield=company}', + ], + ], + ], + ]; + } +} From c045c9c2ea6e3a02a5a52adc744d2c56b9728ee6 Mon Sep 17 00:00:00 2001 From: John Linhart Date: Fri, 24 Aug 2018 17:50:51 +0200 Subject: [PATCH 2/2] Implement new helper to send campaign webhooks, add contact IP addresses as new value --- app/bundles/WebhookBundle/Config/config.php | 10 +- .../EventListener/CampaignSubscriber.php | 84 ++------- .../WebhookBundle/Helper/CampaignHelper.php | 173 ++++++++++++++++++ 3 files changed, 194 insertions(+), 73 deletions(-) create mode 100644 app/bundles/WebhookBundle/Helper/CampaignHelper.php diff --git a/app/bundles/WebhookBundle/Config/config.php b/app/bundles/WebhookBundle/Config/config.php index b4612f54662..3006c0aa83a 100644 --- a/app/bundles/WebhookBundle/Config/config.php +++ b/app/bundles/WebhookBundle/Config/config.php @@ -87,7 +87,7 @@ 'mautic.webhook.campaign.subscriber' => [ 'class' => \Mautic\WebhookBundle\EventListener\CampaignSubscriber::class, 'arguments' => [ - 'mautic.http.connector', + 'mautic.webhook.campaign.helper', ], ], ], @@ -101,6 +101,14 @@ ], ], ], + 'others' => [ + 'mautic.webhook.campaign.helper' => [ + 'class' => \Mautic\WebhookBundle\Helper\CampaignHelper::class, + 'arguments' => [ + 'mautic.http.connector', + ], + ], + ], ], 'parameters' => [ diff --git a/app/bundles/WebhookBundle/EventListener/CampaignSubscriber.php b/app/bundles/WebhookBundle/EventListener/CampaignSubscriber.php index 6c47c66cecf..bea9a043f27 100644 --- a/app/bundles/WebhookBundle/EventListener/CampaignSubscriber.php +++ b/app/bundles/WebhookBundle/EventListener/CampaignSubscriber.php @@ -11,33 +11,26 @@ namespace Mautic\WebhookBundle\EventListener; -use Joomla\Http\Http; use Mautic\CampaignBundle\CampaignEvents; use Mautic\CampaignBundle\Event as Events; use Mautic\CampaignBundle\Event\CampaignExecutionEvent; use Mautic\CoreBundle\EventListener\CommonSubscriber; -use Mautic\CoreBundle\Helper\AbstractFormFieldHelper; -use Mautic\LeadBundle\Helper\TokenHelper; +use Mautic\WebhookBundle\Helper\CampaignHelper; use Mautic\WebhookBundle\WebhookEvents; -/** - * Class CampaignSubscriber. - */ class CampaignSubscriber extends CommonSubscriber { /** - * @var Http + * @var CampaignHelper */ - protected $connector; + protected $campaignHelper; /** - * CampaignSubscriber constructor. - * - * @param Http $connector + * @param CampaignHelper $campaignHelper */ - public function __construct(Http $connector) + public function __construct(CampaignHelper $campaignHelper) { - $this->connector = $connector; + $this->campaignHelper = $campaignHelper; } /** @@ -58,66 +51,14 @@ public static function getSubscribedEvents() */ public function onCampaignTriggerAction(CampaignExecutionEvent $event) { - if (!$event->checkContext('campaign.sendwebhook')) { - return; - } - $lead = $event->getLead(); - $config = $event->getConfig(); - try { - $url = $config['url']; - $method = $config['method']; - $data = !empty($config['additional_data']['list']) ? $config['additional_data']['list'] : ''; - $data = array_flip(AbstractFormFieldHelper::parseList($data)); - // replace contacts tokens - foreach ($data as $key => $value) { - $data[$key] = urldecode(TokenHelper::findLeadTokens($value, $lead->getProfileFields(), true)); - } - $headers = !empty($config['headers']['list']) ? $config['headers']['list'] : ''; - $headers = array_flip(AbstractFormFieldHelper::parseList($headers)); - foreach ($headers as $key => $value) { - $headers[$key] = urldecode(TokenHelper::findLeadTokens($value, $lead->getProfileFields(), true)); - } - $timeout = $config['timeout']; - - switch ($method) { - case 'get': - $response = $this->connector->get( - $url.(parse_url($url, PHP_URL_QUERY) ? '&' : '?').http_build_query($data), - $headers, - $timeout - ); - break; - case 'post': - case 'put': - case 'patch': - $response = $this->connector->$method( - $url, - $data, - $headers, - $timeout - ); - break; - - case 'delete': - $response = $this->connector->delete( - $url, - $headers, - $timeout, - $data - ); - break; - default: - return; + if ($event->checkContext('campaign.sendwebhook')) { + try { + $this->campaignHelper->fireWebhook($event->getConfig(), $event->getLead()); + $event->setResult(true); + } catch (\Exception $e) { + $event->setFailed($e->getMessage()); } - - if (in_array($response->code, [200, 201])) { - return $event->setResult(true); - } - } catch (\Exception $e) { - return $event->setFailed($e->getMessage()); } - - return $event->setFailed($this->translator->trans('Error code').': '.$response->code); } /** @@ -127,7 +68,6 @@ public function onCampaignTriggerAction(CampaignExecutionEvent $event) */ public function onCampaignBuild(Events\CampaignBuilderEvent $event) { - //Add action to remote url call $sendWebhookAction = [ 'label' => 'mautic.webhook.event.sendwebhook', 'description' => 'mautic.webhook.event.sendwebhook_desc', diff --git a/app/bundles/WebhookBundle/Helper/CampaignHelper.php b/app/bundles/WebhookBundle/Helper/CampaignHelper.php new file mode 100644 index 00000000000..4899c81a8be --- /dev/null +++ b/app/bundles/WebhookBundle/Helper/CampaignHelper.php @@ -0,0 +1,173 @@ + [key1 => val1, key2 => val1]]. + * + * @var array + */ + private $contactsValues = []; + + /** + * @param Http $connector + */ + public function __construct(Http $connector) + { + $this->connector = $connector; + } + + /** + * Prepares the neccessary data transformations and then makes the HTTP request. + * + * @param array $config + * @param Lead $contact + */ + public function fireWebhook(array $config, Lead $contact) + { + // dump($config);die; + $payload = $this->getPayload($config, $contact); + $headers = $this->getHeaders($config, $contact); + $this->makeRequest($config['url'], $config['method'], $config['timeout'], $headers, $payload); + } + + /** + * Gets the payload fields from the config and if there are tokens it translates them to contact values. + * + * @param array $config + * @param Lead $contact + * + * @return array + */ + private function getPayload(array $config, Lead $contact) + { + $payload = !empty($config['additional_data']['list']) ? $config['additional_data']['list'] : ''; + $payload = array_flip(AbstractFormFieldHelper::parseList($payload)); + + return $this->getTokenValues($payload, $contact); + } + + /** + * Gets the payload fields from the config and if there are tokens it translates them to contact values. + * + * @param array $config + * @param Lead $contact + * + * @return array + */ + private function getHeaders(array $config, Lead $contact) + { + $headers = !empty($config['headers']['list']) ? $config['headers']['list'] : ''; + $headers = array_flip(AbstractFormFieldHelper::parseList($headers)); + + return $this->getTokenValues($headers, $contact); + } + + /** + * @param string $url + * @param string $method + * @param int $timeout + * @param array $headers + * @param array $payload + * + * @throws \InvalidArgumentException + * @throws \OutOfRangeException + */ + private function makeRequest($url, $method, $timeout, array $headers, array $payload) + { + switch ($method) { + case 'get': + $payload = $url.(parse_url($url, PHP_URL_QUERY) ? '&' : '?').http_build_query($payload); + $response = $this->connector->get($payload, $headers, $timeout); + break; + case 'post': + case 'put': + case 'patch': + $response = $this->connector->$method($url, $payload, $headers, $timeout); + break; + case 'delete': + $response = $this->connector->delete($url, $headers, $timeout, $payload); + break; + default: + throw new \InvalidArgumentException('HTTP method "'.$method.' is not supported."'); + } + + if (!in_array($response->code, [200, 201])) { + throw new \OutOfRangeException('Campaign webhook response returned error code: '.$response->code); + } + } + + /** + * Translates tokens to values. + * + * @param array $rawTokens + * @param Lead $contact + * + * @return array + */ + private function getTokenValues(array $rawTokens, Lead $contact) + { + $values = []; + $contactValues = $this->getContactValues($contact); + + foreach ($rawTokens as $key => $value) { + $values[$key] = urldecode(TokenHelper::findLeadTokens($value, $contactValues, true)); + } + + return $values; + } + + /** + * Gets array of contact values. + * + * @param Lead $contact + * + * @return array + */ + private function getContactValues(Lead $contact) + { + if (empty($this->contactsValues[$contact->getId()])) { + $this->contactsValues[$contact->getId()] = $contact->getProfileFields(); + $this->contactsValues[$contact->getId()]['ipAddress'] = $this->ipAddressesToCsv($contact->getIpAddresses()); + } + + return $this->contactsValues[$contact->getId()]; + } + + /** + * @param Collection $ipAddresses + * + * @return string + */ + private function ipAddressesToCsv(Collection $ipAddresses) + { + $addresses = []; + foreach ($ipAddresses as $ipAddress) { + $addresses[] = $ipAddress->getIpAddress(); + } + + return implode(',', $addresses); + } +}