Skip to content

Commit

Permalink
Merge pull request #89 from Setono/client-metadata-storage
Browse files Browse the repository at this point in the history
Allow the user to save the tracking information server side instead of in a cookie
  • Loading branch information
loevgaard authored May 14, 2024
2 parents aa322f3 + 689c83e commit 5397a28
Show file tree
Hide file tree
Showing 20 changed files with 323 additions and 161 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ jobs:
- "highest"

symfony:
- "~5.4.0"
- "~6.4.0"

steps:
Expand Down Expand Up @@ -126,7 +125,6 @@ jobs:
- "highest"

symfony:
- "~5.4.0"
- "~6.4.0"

steps:
Expand Down Expand Up @@ -170,7 +168,6 @@ jobs:
- "highest"

symfony:
- "~5.4.0"
- "~6.4.0"

steps:
Expand Down Expand Up @@ -211,7 +208,6 @@ jobs:
- "highest"

symfony:
- "~5.4.0"
- "~6.4.0"

steps:
Expand Down
5 changes: 5 additions & 0 deletions composer-require-checker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"symbol-whitelist": [
"Setono\\ClientBundle\\Context\\ClientContextInterface"
]
}
37 changes: 19 additions & 18 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@
"sylius/order": "^1.0",
"sylius/resource-bundle": "^1.6",
"sylius/ui-bundle": "^1.0",
"symfony/config": "^5.4 || ^6.4",
"symfony/console": "^5.4 || ^6.4",
"symfony/dependency-injection": "^5.4 || ^6.4",
"symfony/event-dispatcher": "^5.4 || ^6.4",
"symfony/config": "^6.4 || ^7.0",
"symfony/console": "^6.4 || ^7.0",
"symfony/dependency-injection": "^6.4 || ^7.0",
"symfony/event-dispatcher": "^6.4 || ^7.0",
"symfony/event-dispatcher-contracts": "^2.5 || ^3.3",
"symfony/form": "^5.4 || ^6.4",
"symfony/http-foundation": "^5.4 || ^6.4",
"symfony/http-kernel": "^5.4 || ^6.4",
"symfony/messenger": "^5.4 || ^6.4",
"symfony/options-resolver": "^5.4 || ^6.4",
"symfony/routing": "^5.4 || ^6.4",
"symfony/string": "^5.4 || ^6.4",
"symfony/validator": "^5.4 || ^6.4",
"symfony/workflow": "^5.4 || ^6.4",
"symfony/form": "^6.4 || ^7.0",
"symfony/http-foundation": "^6.4 || ^7.0",
"symfony/http-kernel": "^6.4 || ^7.0",
"symfony/messenger": "^6.4 || ^7.0",
"symfony/options-resolver": "^6.4 || ^7.0",
"symfony/routing": "^6.4 || ^7.0",
"symfony/string": "^6.4 || ^7.0",
"symfony/validator": "^6.4 || ^7.0",
"symfony/workflow": "^6.4 || ^7.0",
"twig/twig": "^2.15 || ^3.4",
"webmozart/assert": "^1.11"
},
Expand All @@ -57,13 +57,14 @@
"matthiasnoback/symfony-config-test": "^4.3 || ^5.1",
"phpunit/phpunit": "^9.6",
"psalm/plugin-phpunit": "^0.18",
"setono/client-bundle": "^1.0@beta",
"setono/code-quality-pack": "^2.7",
"sylius/sylius": "~1.12.15",
"symfony/debug-bundle": "^5.4 || ^6.4",
"symfony/dotenv": "^5.4 || ^6.4",
"symfony/intl": "^5.4 || ^6.4",
"symfony/serializer": "^5.4 || ^6.0.1",
"symfony/web-profiler-bundle": "^5.4 || ^6.4",
"symfony/debug-bundle": "^6.4 || ^7.0",
"symfony/dotenv": "^6.4 || ^7.0",
"symfony/intl": "^6.4 || ^7.0",
"symfony/serializer": "^6.4 || ^7.0",
"symfony/web-profiler-bundle": "^6.4 || ^7.0",
"symfony/webpack-encore-bundle": "^1.17",
"weirdan/doctrine-psalm-plugin": "^2.9",
"willdurand/negotiation": "^3.1"
Expand Down
2 changes: 1 addition & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<pluginClass class="Weirdan\DoctrinePsalmPlugin\Plugin"/>
</plugins>
<issueHandlers>
<PluginIssue name="QueryBuilderSetParameter" errorLevel="info" />
<PluginIssue name="QueryBuilderSetParameter" errorLevel="info"/>
<UnnecessaryVarAnnotation errorLevel="suppress"/> <!-- We use unnecessary var annotations to aid the IDE -->
<TooManyTemplateParams>
<errorLevel type="suppress">
Expand Down
18 changes: 18 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ public function getConfigTreeBuilder(): TreeBuilder
/** @var ArrayNodeDefinition $rootNode */
$rootNode = $treeBuilder->getRootNode();

/** @psalm-suppress MixedMethodCall,PossiblyNullReference,UndefinedInterfaceMethod */
$rootNode
->addDefaultsIfNotSet()
->children()
->scalarNode('cookie_name')
->defaultValue('ssga_tinfo')
->cannotBeEmpty()
->info('The name of the cookie to store the tracking information in if the storage is set to cookie')
->end()
->scalarNode('storage')
->defaultValue('cookie')
->cannotBeEmpty()
->info('The storage to use for tracking information. Available options are: cookie and client_metadata')
->validate()
->ifNotInArray(['cookie', 'client_metadata'])
->thenInvalid('Invalid storage %s')
;

$this->addResourcesSection($rootNode);

return $treeBuilder;
Expand Down
19 changes: 18 additions & 1 deletion src/DependencyInjection/SetonoSyliusGoogleAdsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Webmozart\Assert\Assert;

final class SetonoSyliusGoogleAdsExtension extends AbstractResourceExtension implements PrependExtensionInterface
{
Expand All @@ -22,7 +23,7 @@ public function load(array $configs, ContainerBuilder $container): void
/**
* @psalm-suppress PossiblyNullArgument
*
* @var array{resources: array<string, mixed>} $config
* @var array{cookie_name: string, storage: string, resources: array<string, mixed>} $config
*/
$config = $this->processConfiguration($this->getConfiguration([], $container), $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
Expand All @@ -35,6 +36,22 @@ public function load(array $configs, ContainerBuilder $container): void
->addTag('setono_sylius_google_ads.qualification_voter')
;

$container->setParameter('setono_sylius_google_ads.cookie_name', $config['cookie_name']);
$container->setParameter('setono_sylius_google_ads.storage', $config['storage']);

if ('cookie' === $config['storage']) {
$loader->load('services/conditional/storage_cookie.xml');
} else {
$bundles = $container->getParameter('kernel.bundles');
Assert::isArray($bundles);

if (!array_key_exists('SetonoClientBundle', $bundles)) {
throw new \RuntimeException('You need to install the SetonoClientBundle in order to use the client_metadata storage. Run "composer require setono/client-bundle" to install it. See https://github.com/Setono/client-bundle');
}

$loader->load('services/conditional/storage_client_metadata.xml');
}

$loader->load('services.xml');

$this->registerResources(
Expand Down
65 changes: 65 additions & 0 deletions src/EventSubscriber/MigrateStorageSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusGoogleAdsPlugin\EventSubscriber;

use Setono\SyliusGoogleAdsPlugin\TrackingInformation\TrackingInformation;
use Setono\SyliusGoogleAdsPlugin\TrackingInformation\TrackingInformationStorageInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
* This subscriber is responsible for migrating the storage of the tracking information from the cookie to the client metadata.
* This subscriber should only be used when the storage is set to client_metadata
*/
final class MigrateStorageSubscriber implements EventSubscriberInterface
{
private ?TrackingInformation $trackingInformation = null;

public function __construct(
private readonly TrackingInformationStorageInterface $trackingInformationStorage,
private readonly string $cookieName,
) {
}

public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['migrate', 5], // we want to run this before the StoreTrackingInformationSubscriber
KernelEvents::RESPONSE => 'remove',
];
}

public function migrate(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}

try {
$this->trackingInformation = TrackingInformation::fromCookie($event->getRequest(), $this->cookieName);
} catch (\Throwable) {
return;
}

$this->trackingInformationStorage->store($this->trackingInformation);
}

public function remove(ResponseEvent $event): void
{
if (null === $this->trackingInformation || !$event->isMainRequest()) {
return;
}

try {
$cookie = $this->trackingInformation->toCookie($this->cookieName, 1);
} catch (\Throwable) {
return;
}

$event->getResponse()->headers->setCookie($cookie);
}
}
1 change: 0 additions & 1 deletion src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@
<import resource="services/message.xml"/>
<import resource="services/provider.xml"/>
<import resource="services/resolver.xml"/>
<import resource="services/tracking_information.xml"/>
</imports>
</container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>

<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="setono_sylius_google_ads.tracking_information.storage.default"
alias="setono_sylius_google_ads.tracking_information.storage.client_metadata_based"/>

<service id="setono_sylius_google_ads.tracking_information.storage.client_metadata_based"
class="Setono\SyliusGoogleAdsPlugin\TrackingInformation\ClientMetadataBasedTrackingInformationStorage">
<argument type="service" id="setono_client.client_context.default"/>

<call method="setLogger">
<argument type="service" id="logger"/>
</call>

<tag name="kernel.event_subscriber"/>
</service>

<service id="setono_sylius_google_ads.event_subscriber.migrate_storage"
class="Setono\SyliusGoogleAdsPlugin\EventSubscriber\MigrateStorageSubscriber">
<argument type="service" id="setono_sylius_google_ads.tracking_information.storage.default"/>
<argument>%setono_sylius_google_ads.cookie_name%</argument>

<tag name="kernel.event_subscriber"/>
</service>
</services>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,14 @@

<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<!--
The cookie name namespace 'ssga' is an acronym for 'setono sylius google ads'
DO NOT change this on a live site since you will lose conversion data from Google Ads if you do
-->
<parameter key="setono_sylius_google_ads.tracking_information_cookie_name">ssga_tinfo</parameter>
</parameters>
<services>
<service id="setono_sylius_google_ads.tracking_information.storage.default"
alias="setono_sylius_google_ads.tracking_information.storage.cookie_based"/>

<service id="setono_sylius_google_ads.tracking_information.storage.cookie_based"
class="Setono\SyliusGoogleAdsPlugin\TrackingInformation\CookieBasedTrackingInformationStorage">
<argument type="service" id="request_stack"/>
<argument>%setono_sylius_google_ads.tracking_information_cookie_name%</argument>
<argument>%setono_sylius_google_ads.cookie_name%</argument>

<call method="setLogger">
<argument type="service" id="logger"/>
Expand Down
52 changes: 52 additions & 0 deletions src/TrackingInformation/AbstractTrackingInformationStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusGoogleAdsPlugin\TrackingInformation;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

abstract class AbstractTrackingInformationStorage implements TrackingInformationStorageInterface, EventSubscriberInterface, LoggerAwareInterface
{
protected LoggerInterface $logger;

protected ?TrackingInformation $trackingInformation = null;

public function __construct()
{
$this->logger = new NullLogger();
}

public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => 'persist',
];
}

public function store(Request|TrackingInformation $value): void
{
if ($value instanceof Request) {
try {
$value = TrackingInformation::fromQuery($value);
} catch (\InvalidArgumentException) {
return;
}
}

$this->trackingInformation = $value;
}

abstract public function persist(ResponseEvent $event): void;

public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusGoogleAdsPlugin\TrackingInformation;

use Setono\ClientBundle\Context\ClientContextInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Webmozart\Assert\Assert;

final class ClientMetadataBasedTrackingInformationStorage extends AbstractTrackingInformationStorage
{
public function __construct(
private readonly ClientContextInterface $clientContext,
private readonly string $metadataKey = 'ssga_tracking_information',
) {
parent::__construct();
}

public function get(): ?TrackingInformation
{
$clientMetadata = $this->clientContext->getClient()->metadata;
if (!$clientMetadata->has($this->metadataKey)) {
return null;
}

try {
$data = $clientMetadata->get($this->metadataKey);
Assert::isArray($data);

return TrackingInformation::fromArray($data);
} catch (\InvalidArgumentException) {
// the data is corrupted, remove it
$clientMetadata->remove($this->metadataKey);

return null;
}
}

public function persist(ResponseEvent $event): void
{
if (null === $this->trackingInformation) {
return;
}

$this->clientContext->getClient()->metadata->set($this->metadataKey, $this->trackingInformation);
}
}
Loading

0 comments on commit 5397a28

Please sign in to comment.