From 7d8aa9e72ebea766f4d14f4386af1997d07191c7 Mon Sep 17 00:00:00 2001 From: Igor Mukhin Date: Fri, 19 Nov 2021 17:37:29 +0200 Subject: [PATCH 1/2] Added: FBC to user data --- src/DataMapper/RequestDataMapper.php | 13 ++ src/DependencyInjection/Configuration.php | 4 + .../SetonoSyliusFacebookExtension.php | 3 +- src/EventListener/StoreFbcSubscriber.php | 60 ++++++++ src/Manager/FbcManager.php | 135 ++++++++++++++++++ src/Manager/FbcManagerInterface.php | 14 ++ src/Resources/config/services.xml | 1 + src/Resources/config/services/data_mapper.xml | 1 + .../config/services/event_listener.xml | 8 ++ src/Resources/config/services/manager.xml | 16 +++ .../DependencyInjection/ConfigurationTest.php | 1 + 11 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 src/EventListener/StoreFbcSubscriber.php create mode 100644 src/Manager/FbcManager.php create mode 100644 src/Manager/FbcManagerInterface.php create mode 100644 src/Resources/config/services/manager.xml diff --git a/src/DataMapper/RequestDataMapper.php b/src/DataMapper/RequestDataMapper.php index 8bb3c4f..962bdb6 100644 --- a/src/DataMapper/RequestDataMapper.php +++ b/src/DataMapper/RequestDataMapper.php @@ -4,12 +4,20 @@ namespace Setono\SyliusFacebookPlugin\DataMapper; +use Setono\SyliusFacebookPlugin\Manager\FbcManagerInterface; use Setono\SyliusFacebookPlugin\ServerSide\ServerSideEventInterface; use Symfony\Component\HttpFoundation\Request; use Webmozart\Assert\Assert; /* not final */ class RequestDataMapper implements DataMapperInterface { + protected FbcManagerInterface $fbcManager; + + public function __construct(FbcManagerInterface $fbcManager) + { + $this->fbcManager = $fbcManager; + } + /** * @psalm-assert-if-true Request $context['request'] */ @@ -37,5 +45,10 @@ public function map(object $source, ServerSideEventInterface $target, array $con /** @psalm-suppress PossiblyNullArgument */ $userData->setClientUserAgent($request->headers->get('User-Agent')); + + $fbc = $this->fbcManager->getFbcValue(); + if (is_string($fbc)) { + $userData->setFbc($fbc); + } } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index f8ffe4b..477db78 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -56,6 +56,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue(30 * 24 * 60 * 60) // 30 days ->info('The number of seconds to wait until remove sent event') ->end() + ->integerNode('fbc_ttl') + ->defaultValue(28 * 24 * 60 * 60) // 28 days + ->info('Time to live for fbc cookie') + ->end() ->end() ; diff --git a/src/DependencyInjection/SetonoSyliusFacebookExtension.php b/src/DependencyInjection/SetonoSyliusFacebookExtension.php index a60e11e..e294b17 100644 --- a/src/DependencyInjection/SetonoSyliusFacebookExtension.php +++ b/src/DependencyInjection/SetonoSyliusFacebookExtension.php @@ -18,7 +18,7 @@ public function load(array $configs, ContainerBuilder $container): void /** * @psalm-suppress PossiblyNullArgument * - * @var array{api_version: string, access_token: string, test_event_code: string|null, send_delay: int, cleanup_delay:int, driver: string, resources: array} $config + * @var array{api_version: string, access_token: string, test_event_code: string|null, send_delay: int, cleanup_delay:int, fbc_ttl: int, driver: string, resources: array} $config */ $config = $this->processConfiguration($this->getConfiguration([], $container), $configs); $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); @@ -28,6 +28,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('setono_sylius_facebook.test_event_code', $config['test_event_code']); $container->setParameter('setono_sylius_facebook.send_delay', $config['send_delay']); $container->setParameter('setono_sylius_facebook.cleanup_delay', $config['cleanup_delay']); + $container->setParameter('setono_sylius_facebook.fbc_ttl', $config['fbc_ttl']); $loader->load('services.xml'); diff --git a/src/EventListener/StoreFbcSubscriber.php b/src/EventListener/StoreFbcSubscriber.php new file mode 100644 index 0000000..c224265 --- /dev/null +++ b/src/EventListener/StoreFbcSubscriber.php @@ -0,0 +1,60 @@ +fbcManager = $fbcManager; + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::RESPONSE => 'setFbcCookie', + ]; + } + + public function setFbcCookie(ResponseEvent $event): void + { + if (!$this->isRequestEligible()) { + return; + } + + $fbcCookie = $this->fbcManager->getFbcCookie(); + if (null === $fbcCookie) { + return; + } + + $response = $event->getResponse(); + $response->headers->setCookie($fbcCookie); + } +} diff --git a/src/Manager/FbcManager.php b/src/Manager/FbcManager.php new file mode 100644 index 0000000..630cd5c --- /dev/null +++ b/src/Manager/FbcManager.php @@ -0,0 +1,135 @@ +requestStack = $requestStack; + $this->fbcTtl = $fbcTtl; + $this->fbcCookieName = $fbcCookieName; + } + + public function getFbcCookie(): ?Cookie + { + $fbc = $this->getFbcValue(); + if (null === $fbc) { + return null; + } + + return Cookie::create( + $this->fbcCookieName, + $fbc, + time() + $this->fbcTtl + ); + } + + /** + * We call it twice per request: + * 1. When populate fbc to UserData (on request) + * 2. When setting a fbc cookie (on response) + */ + public function getFbcValue(): ?string + { + // We already have fbc generated at previous call + if (null !== $this->generatedFbc) { + return $this->generatedFbc; + } + + $request = $this->requestStack->getCurrentRequest(); + if (null === $request) { + return null; + } + + /** @var string|null $fbc */ + $fbc = $request->cookies->get($this->fbcCookieName); + + /** @var string|null $fbclid */ + $fbclid = $request->query->get('fbclid'); + + // We have both fbc and fbclid + if (is_string($fbclid) && is_string($fbc)) { + // So should decide if we should regenerate it. + // Extracting fbclid from fbc to compare + $existingFbclid = $this->extractFbclid($fbc); + + // If fbclid is the same - we shouldn't regenerate fbc + // and use old one from cookie with old timestamp + if ($existingFbclid !== $fbclid) { + return $this->generateFbc($fbclid); + } + } + + // We have fbc cookie and shouldn't try to + // regenerate it from fbclid (as it is empty) + if (is_string($fbc)) { + return $fbc; + } + + // Have no fbc cookie, but have fbclid + // to generate fbc from it + if (is_string($fbclid)) { + return $this->generateFbc($fbclid); + } + + // We have no fbc cookie and no fbclid, so can't generate + return null; + } + + private function generateFbc(string $fbclid): string + { + $creationTime = ceil(microtime(true) * 1000); + + $fbc = sprintf( + 'fb.1.%s.%s', + $creationTime, + $fbclid + ); + + $this->generatedFbc = $fbc; + + return $fbc; + } + + private function extractFbclid(string $fbc): ?string + { + if (false === preg_match('/fb\.1\.(\d+)\.(.+)/', $fbc, $m)) { + return null; + } + + if (!isset($m[2])) { + return null; + } + + /** @var string $fbclid */ + $fbclid = $m[2]; + + return $fbclid; + } +} diff --git a/src/Manager/FbcManagerInterface.php b/src/Manager/FbcManagerInterface.php new file mode 100644 index 0000000..a566340 --- /dev/null +++ b/src/Manager/FbcManagerInterface.php @@ -0,0 +1,14 @@ + + diff --git a/src/Resources/config/services/data_mapper.xml b/src/Resources/config/services/data_mapper.xml index 58924db..40e3c41 100644 --- a/src/Resources/config/services/data_mapper.xml +++ b/src/Resources/config/services/data_mapper.xml @@ -33,6 +33,7 @@ + diff --git a/src/Resources/config/services/event_listener.xml b/src/Resources/config/services/event_listener.xml index 0c7292b..ece90db 100644 --- a/src/Resources/config/services/event_listener.xml +++ b/src/Resources/config/services/event_listener.xml @@ -13,6 +13,14 @@ + + + + + + diff --git a/src/Resources/config/services/manager.xml b/src/Resources/config/services/manager.xml new file mode 100644 index 0000000..6585feb --- /dev/null +++ b/src/Resources/config/services/manager.xml @@ -0,0 +1,16 @@ + + + + + + + %setono_sylius_facebook.fbc_ttl% + + + + + + diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index 2e3e127..88dcdda 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -58,6 +58,7 @@ public function processed_value_contains_required_and_default_values(): void 'api_version' => 'v12.0', 'send_delay' => 300, 'cleanup_delay' => 2592000, + 'fbc_ttl' => 2419200, ]); } } From 7bc2a8b1d8acdb471a49cd163fd0cb4b802a7800 Mon Sep 17 00:00:00 2001 From: Igor Mukhin Date: Wed, 24 Nov 2021 19:12:57 +0200 Subject: [PATCH 2/2] Added: FBP to user data --- src/DataMapper/RequestDataMapper.php | 11 ++- src/DependencyInjection/Configuration.php | 4 + .../SetonoSyliusFacebookExtension.php | 3 +- ...ubscriber.php => SetCookiesSubscriber.php} | 27 ++++-- src/Manager/FbpManager.php | 89 +++++++++++++++++++ src/Manager/FbpManagerInterface.php | 14 +++ src/Resources/config/services/data_mapper.xml | 1 + .../config/services/event_listener.xml | 5 +- src/Resources/config/services/manager.xml | 10 +++ .../DependencyInjection/ConfigurationTest.php | 1 + 10 files changed, 154 insertions(+), 11 deletions(-) rename src/EventListener/{StoreFbcSubscriber.php => SetCookiesSubscriber.php} (64%) create mode 100644 src/Manager/FbpManager.php create mode 100644 src/Manager/FbpManagerInterface.php diff --git a/src/DataMapper/RequestDataMapper.php b/src/DataMapper/RequestDataMapper.php index 962bdb6..9024b9f 100644 --- a/src/DataMapper/RequestDataMapper.php +++ b/src/DataMapper/RequestDataMapper.php @@ -5,6 +5,7 @@ namespace Setono\SyliusFacebookPlugin\DataMapper; use Setono\SyliusFacebookPlugin\Manager\FbcManagerInterface; +use Setono\SyliusFacebookPlugin\Manager\FbpManagerInterface; use Setono\SyliusFacebookPlugin\ServerSide\ServerSideEventInterface; use Symfony\Component\HttpFoundation\Request; use Webmozart\Assert\Assert; @@ -13,9 +14,12 @@ { protected FbcManagerInterface $fbcManager; - public function __construct(FbcManagerInterface $fbcManager) + protected FbpManagerInterface $fbpManager; + + public function __construct(FbcManagerInterface $fbcManager, FbpManagerInterface $fbpManager) { $this->fbcManager = $fbcManager; + $this->fbpManager = $fbpManager; } /** @@ -50,5 +54,10 @@ public function map(object $source, ServerSideEventInterface $target, array $con if (is_string($fbc)) { $userData->setFbc($fbc); } + + $fbp = $this->fbpManager->getFbpValue(); + if (is_string($fbp)) { + $userData->setFbp($fbp); + } } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 477db78..5cd9fed 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -60,6 +60,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultValue(28 * 24 * 60 * 60) // 28 days ->info('Time to live for fbc cookie') ->end() + ->integerNode('fbp_ttl') + ->defaultValue(365 * 24 * 60 * 60) // 365 days + ->info('Time to live for fbp cookie') + ->end() ->end() ; diff --git a/src/DependencyInjection/SetonoSyliusFacebookExtension.php b/src/DependencyInjection/SetonoSyliusFacebookExtension.php index e294b17..b1a3944 100644 --- a/src/DependencyInjection/SetonoSyliusFacebookExtension.php +++ b/src/DependencyInjection/SetonoSyliusFacebookExtension.php @@ -18,7 +18,7 @@ public function load(array $configs, ContainerBuilder $container): void /** * @psalm-suppress PossiblyNullArgument * - * @var array{api_version: string, access_token: string, test_event_code: string|null, send_delay: int, cleanup_delay:int, fbc_ttl: int, driver: string, resources: array} $config + * @var array{api_version: string, access_token: string, test_event_code: string|null, send_delay: int, cleanup_delay:int, fbc_ttl: int, fbp_ttl: int, driver: string, resources: array} $config */ $config = $this->processConfiguration($this->getConfiguration([], $container), $configs); $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); @@ -29,6 +29,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('setono_sylius_facebook.send_delay', $config['send_delay']); $container->setParameter('setono_sylius_facebook.cleanup_delay', $config['cleanup_delay']); $container->setParameter('setono_sylius_facebook.fbc_ttl', $config['fbc_ttl']); + $container->setParameter('setono_sylius_facebook.fbp_ttl', $config['fbp_ttl']); $loader->load('services.xml'); diff --git a/src/EventListener/StoreFbcSubscriber.php b/src/EventListener/SetCookiesSubscriber.php similarity index 64% rename from src/EventListener/StoreFbcSubscriber.php rename to src/EventListener/SetCookiesSubscriber.php index c224265..b4f4793 100644 --- a/src/EventListener/StoreFbcSubscriber.php +++ b/src/EventListener/SetCookiesSubscriber.php @@ -8,22 +8,26 @@ use Setono\SyliusFacebookPlugin\Context\PixelContextInterface; use Setono\SyliusFacebookPlugin\Generator\PixelEventsGeneratorInterface; use Setono\SyliusFacebookPlugin\Manager\FbcManagerInterface; +use Setono\SyliusFacebookPlugin\Manager\FbpManagerInterface; use Symfony\Bundle\SecurityBundle\Security\FirewallMap; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; -final class StoreFbcSubscriber extends AbstractSubscriber +final class SetCookiesSubscriber extends AbstractSubscriber { private FbcManagerInterface $fbcManager; + private FbpManagerInterface $fbpManager; + public function __construct( RequestStack $requestStack, FirewallMap $firewallMap, PixelContextInterface $pixelContext, PixelEventsGeneratorInterface $pixelEventsGenerator, BotDetectorInterface $botDetector, - FbcManagerInterface $fbcManager + FbcManagerInterface $fbcManager, + FbpManagerInterface $fbpManager ) { parent::__construct( $requestStack, @@ -34,27 +38,36 @@ public function __construct( ); $this->fbcManager = $fbcManager; + $this->fbpManager = $fbpManager; } public static function getSubscribedEvents(): array { return [ - KernelEvents::RESPONSE => 'setFbcCookie', + KernelEvents::RESPONSE => 'setCookies', ]; } - public function setFbcCookie(ResponseEvent $event): void + public function setCookies(ResponseEvent $event): void { if (!$this->isRequestEligible()) { return; } - $fbcCookie = $this->fbcManager->getFbcCookie(); - if (null === $fbcCookie) { + $request = $this->requestStack->getCurrentRequest(); + if (null === $request || $request->isXmlHttpRequest()) { return; } $response = $event->getResponse(); - $response->headers->setCookie($fbcCookie); + $fbcCookie = $this->fbcManager->getFbcCookie(); + if (null !== $fbcCookie) { + $response->headers->setCookie($fbcCookie); + } + + $fbpCookie = $this->fbpManager->getFbpCookie(); + if (null !== $fbpCookie) { + $response->headers->setCookie($fbpCookie); + } } } diff --git a/src/Manager/FbpManager.php b/src/Manager/FbpManager.php new file mode 100644 index 0000000..d9f6309 --- /dev/null +++ b/src/Manager/FbpManager.php @@ -0,0 +1,89 @@ +requestStack = $requestStack; + $this->clientIdProvider = $clientIdProvider; + $this->fbpTtl = $fbpTtl; + $this->fbpCookieName = $fbpCookieName; + } + + public function getfbpCookie(): ?Cookie + { + $fbp = $this->getfbpValue(); + if (null === $fbp) { + return null; + } + + return Cookie::create( + $this->fbpCookieName, + $fbp, + time() + $this->fbpTtl + ); + } + + /** + * We call it twice per request: + * 1. When populate fbp to UserData (on request) + * 2. When setting a fbp cookie (on response) + */ + public function getFbpValue(): ?string + { + $request = $this->requestStack->getCurrentRequest(); + if (null === $request) { + return null; + } + + /** @var string|null $fbp */ + $fbp = $request->cookies->get($this->fbpCookieName); + + // We have fbp cookie and shouldn't try to + // regenerate it from fbplid (as it is empty) + if (is_string($fbp)) { + return $fbp; + } + + $clientId = (string) $this->clientIdProvider->getClientId(); + + return $this->generateFbp($clientId); + } + + private function generateFbp(string $clientId): string + { + $creationTime = ceil(microtime(true) * 1000); + + return sprintf( + 'fb.1.%s.%s', + $creationTime, + $clientId + ); + } +} diff --git a/src/Manager/FbpManagerInterface.php b/src/Manager/FbpManagerInterface.php new file mode 100644 index 0000000..4022dc8 --- /dev/null +++ b/src/Manager/FbpManagerInterface.php @@ -0,0 +1,14 @@ + + diff --git a/src/Resources/config/services/event_listener.xml b/src/Resources/config/services/event_listener.xml index ece90db..fc9c85b 100644 --- a/src/Resources/config/services/event_listener.xml +++ b/src/Resources/config/services/event_listener.xml @@ -13,10 +13,11 @@ - + diff --git a/src/Resources/config/services/manager.xml b/src/Resources/config/services/manager.xml index 6585feb..78ac34e 100644 --- a/src/Resources/config/services/manager.xml +++ b/src/Resources/config/services/manager.xml @@ -12,5 +12,15 @@ + + + + %setono_sylius_facebook.fbp_ttl% + + + + diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index 88dcdda..cba639d 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -59,6 +59,7 @@ public function processed_value_contains_required_and_default_values(): void 'send_delay' => 300, 'cleanup_delay' => 2592000, 'fbc_ttl' => 2419200, + 'fbp_ttl' => 31536000, ]); } }