diff --git a/src/DataMapper/RequestDataMapper.php b/src/DataMapper/RequestDataMapper.php
index 8bb3c4f..9024b9f 100644
--- a/src/DataMapper/RequestDataMapper.php
+++ b/src/DataMapper/RequestDataMapper.php
@@ -4,12 +4,24 @@
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;
/* not final */ class RequestDataMapper implements DataMapperInterface
{
+ protected FbcManagerInterface $fbcManager;
+
+ protected FbpManagerInterface $fbpManager;
+
+ public function __construct(FbcManagerInterface $fbcManager, FbpManagerInterface $fbpManager)
+ {
+ $this->fbcManager = $fbcManager;
+ $this->fbpManager = $fbpManager;
+ }
+
/**
* @psalm-assert-if-true Request $context['request']
*/
@@ -37,5 +49,15 @@ 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);
+ }
+
+ $fbp = $this->fbpManager->getFbpValue();
+ if (is_string($fbp)) {
+ $userData->setFbp($fbp);
+ }
}
}
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index f8ffe4b..5cd9fed 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -56,6 +56,14 @@ 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()
+ ->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 a60e11e..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, 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'));
@@ -28,6 +28,8 @@ 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']);
+ $container->setParameter('setono_sylius_facebook.fbp_ttl', $config['fbp_ttl']);
$loader->load('services.xml');
diff --git a/src/EventListener/SetCookiesSubscriber.php b/src/EventListener/SetCookiesSubscriber.php
new file mode 100644
index 0000000..b4f4793
--- /dev/null
+++ b/src/EventListener/SetCookiesSubscriber.php
@@ -0,0 +1,73 @@
+fbcManager = $fbcManager;
+ $this->fbpManager = $fbpManager;
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ KernelEvents::RESPONSE => 'setCookies',
+ ];
+ }
+
+ public function setCookies(ResponseEvent $event): void
+ {
+ if (!$this->isRequestEligible()) {
+ return;
+ }
+
+ $request = $this->requestStack->getCurrentRequest();
+ if (null === $request || $request->isXmlHttpRequest()) {
+ return;
+ }
+
+ $response = $event->getResponse();
+ $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/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 @@
+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/data_mapper.xml b/src/Resources/config/services/data_mapper.xml
index 58924db..0f494b1 100644
--- a/src/Resources/config/services/data_mapper.xml
+++ b/src/Resources/config/services/data_mapper.xml
@@ -33,6 +33,8 @@
+
+
diff --git a/src/Resources/config/services/event_listener.xml b/src/Resources/config/services/event_listener.xml
index 0c7292b..fc9c85b 100644
--- a/src/Resources/config/services/event_listener.xml
+++ b/src/Resources/config/services/event_listener.xml
@@ -13,6 +13,15 @@
+
+
+
+
+
+
+
diff --git a/src/Resources/config/services/manager.xml b/src/Resources/config/services/manager.xml
new file mode 100644
index 0000000..78ac34e
--- /dev/null
+++ b/src/Resources/config/services/manager.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ %setono_sylius_facebook.fbc_ttl%
+
+
+
+
+
+
+
+ %setono_sylius_facebook.fbp_ttl%
+
+
+
+
+
+
diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php
index 2e3e127..cba639d 100644
--- a/tests/DependencyInjection/ConfigurationTest.php
+++ b/tests/DependencyInjection/ConfigurationTest.php
@@ -58,6 +58,8 @@ public function processed_value_contains_required_and_default_values(): void
'api_version' => 'v12.0',
'send_delay' => 300,
'cleanup_delay' => 2592000,
+ 'fbc_ttl' => 2419200,
+ 'fbp_ttl' => 31536000,
]);
}
}