Skip to content

Commit

Permalink
Implement a very simple conditional templating system (#73)
Browse files Browse the repository at this point in the history
* Try implementing a very simple conditional templating system.

* Fix tests.

* Fix tests.

* Implement template conditionals in PHP.

* Update Google Analytics to use conditionals for consent and set default consentValues to null.

* Fix bug resolved in #74 on the PHP side too.

* Ignore PHP vendor folder for ESLint.

* Improve regex.

* Improve regex.

* Improve regex and add another test for malformed code.

* Try again.

* Revert "Try again."

This reverts commit 4717ea0.
  • Loading branch information
felixarntz authored Sep 11, 2024
1 parent 1efad1c commit 62c950c
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 26 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ node_modules/
.DS_Store
lib/
.husky
dist/
dist/
vendor/
10 changes: 2 additions & 8 deletions data/google-analytics.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,12 @@
"key": "gtag"
},
{
"code": "window[{{l}}]=window[{{l}}]||[];window['gtag-'+{{l}}]=function (){window[{{l}}].push(arguments);};window['gtag-'+{{l}}]('consent', {{consentType}}, {{consentValues}});window['gtag-'+{{l}}]('js',new Date());window['gtag-'+{{l}}]('config',{{id}})",
"code": "window[{{l}}]=window[{{l}}]||[];window['gtag-'+{{l}}]=function (){window[{{l}}].push(arguments);};{{#consentValues}}window['gtag-'+{{l}}]('consent', {{consentType}}, {{consentValues}});{{/consentValues}}window['gtag-'+{{l}}]('js',new Date());window['gtag-'+{{l}}]('config',{{id}})",
"params": ["id"],
"optionalParams": {
"l": "dataLayer",
"consentType": "default",
"consentValues": {
"ad_user_data": "denied",
"ad_personalization": "denied",
"ad_storage": "denied",
"analytics_storage": "denied",
"wait_for_update": 500
}
"consentValues": null
},
"strategy": "worker",
"location": "head",
Expand Down
43 changes: 30 additions & 13 deletions inc/Data/ThirdPartyDataFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,19 @@ static function ($acc, ThirdPartyScriptData $scriptData) {
}
if (isset($newData['scripts']) && $newData['scripts']) {
$newData['scripts'] = array_map(
static function ($scriptData) use ($allScriptParams, $scriptUrlParamInputs) {
static function ($scriptData) use ($args) {
if (isset($scriptData['url'])) {
$scriptData['url'] = self::formatUrl(
$scriptData['url'],
$allScriptParams,
$scriptUrlParamInputs,
$scriptData['params'] ?? [],
$args,
[],
$scriptData['optionalParams'] ?? []
);
} else {
$scriptData['code'] = self::formatCode(
$scriptData['code'],
$scriptUrlParamInputs,
$args,
$scriptData['optionalParams'] ?? []
);
}
Expand Down Expand Up @@ -195,19 +195,22 @@ public static function formatUrl(
}
}

$queryArgs = [];
if ($params && $args) {
$queryArgs = self::intersectArgs($args, $params);
if ($optionalParams) {
$optionalArgs = self::intersectArgs($optionalParams, $params);
foreach ($optionalArgs as $k => $v) {
if (!isset($queryArgs[$k])) {
$queryArgs[$k] = $v;
}
}
if ($optionalParams) {
foreach ($optionalParams as $k => $v) {
if (isset($args[$k])) {
$queryArgs[$k] = $args[$k];
} elseif ($v) {
$queryArgs[$k] = $v;
}
}
if ($queryArgs) {
$url = self::setUrlQueryArgs($url, $queryArgs);
}
}

if ($queryArgs) {
$url = self::setUrlQueryArgs($url, $queryArgs);
}

return $url;
Expand All @@ -229,6 +232,20 @@ public static function formatCode(
array $args,
array $optionalParams = []
): string {
// Conditionals.
$code = preg_replace_callback(
'/{{#([^{}]+?)}}(.*){{\/\1}}/',
static function ($matches) use ($args, $optionalParams) {
if ((isset($args[ $matches[1] ]) && $args[ $matches[1] ]) ||
(isset($optionalParams[ $matches[1] ]) && $optionalParams[ $matches[1] ])) {
return $matches[2];
}
return '';
},
$code
);

// Variables.
return preg_replace_callback(
'/{{([^}]+)}}/',
static function ($matches) use ($args, $optionalParams) {
Expand Down
40 changes: 40 additions & 0 deletions src/utils/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,46 @@ describe('Utils', () => {
},
output: `{"key":"value"}`,
},
// conditional with true
{
input: '{{#enabled}}window.func("enable", true);{{/enabled}}',
params: {
enabled: true,
},
output: `window.func("enable", true);`,
},
// conditional with false
{
input: '{{#enabled}}window.func("enable", true);{{/enabled}}',
params: {
enabled: false,
},
output: ``,
},
// conditional with true including variable
{
input: '{{#name}}window.func("setName", {{name}});{{/name}}',
params: {
name: 'James',
},
output: `window.func("setName", "James");`,
},
// conditional with false including variable
{
input: '{{#name}}window.func("setName", {{name}});{{/name}}',
params: {
name: null,
},
output: ``,
},
// conditional with too many braces (do not do that!)
{
input: '{{{#name}}}window.func("setName", {{name}});{{{/name}}}',
params: {
name: 'James',
},
output: `{}window.func("setName", "James");{}`,
},
];

it.each(inputs)(
Expand Down
14 changes: 13 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,19 @@ export function formatCode(
args?: Inputs,
optionalParams?: Inputs,
) {
return code.replace(/{{(.*?)}}/g, (match) => {
// Conditionals.
code = code.replace(
/{{#([^{}]+?)}}(.*){{\/\1}}/g,
(match, name, innerCode) => {
if (args?.[name] || optionalParams?.[name]) {
return innerCode;
}
return '';
},
);

// Variable replacements.
return code.replace(/{{[^#/](.*?)}}/g, (match) => {
const name = match.split(/{{|}}/).filter(Boolean)[0];

return JSON.stringify(args?.[name] ?? optionalParams?.[name] ?? undefined);
Expand Down
40 changes: 40 additions & 0 deletions tests/phpunit/tests/Data/ThirdPartyDataFormatterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -372,4 +372,44 @@ public function testFormatCodeWithArgsIncorrectOrderAndTooMany()
$code
);
}

/**
* @dataProvider dataFormatCodeWithConditionals
*/
public function testFormatCodeWithConditionals(string $input, array $params, string $expected)
{
$code = ThirdPartyDataFormatter::formatCode($input, $params);
$this->assertSame($expected, $code);
}

public function dataFormatCodeWithConditionals(): array
{
return [
'true' => [
'{{#enabled}}window.func("enable", true);{{/enabled}}',
[ 'enabled' => true ],
'window.func("enable", true);',
],
'false' => [
'{{#enabled}}window.func("enable", true);{{/enabled}}',
[ 'enabled' => false ],
'',
],
'true with variable' => [
'{{#name}}window.func("setName", {{name}});{{/name}}',
[ 'name' => 'James' ],
'window.func("setName", "James");',
],
'false with variable' => [
'{{#name}}window.func("setName", {{name}});{{/name}}',
[ 'name' => null ],
'',
],
'too many braces' => [
'{{{#name}}}window.func("setName", {{name}});{{{/name}}}',
[ 'name' => 'James' ],
'{}window.func("setName", "James");{}',
],
];
}
}
61 changes: 58 additions & 3 deletions tests/phpunit/tests/ThirdParties/GoogleAnalyticsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ static function ($script) {

public function dataOutput(): array
{
$consentDefault = '{"ad_user_data":"denied","ad_personalization":"denied","ad_storage":"denied","analytics_storage":"denied","wait_for_update":500}';
return [
'basic example' => [
[ 'id' => 'G-12345678' ],
Expand All @@ -58,7 +57,7 @@ public function dataOutput(): array
'strategy' => ThirdPartyScriptData::STRATEGY_WORKER,
'location' => ThirdPartyScriptData::LOCATION_HEAD,
'action' => ThirdPartyScriptData::ACTION_APPEND,
'code' => "window[\"dataLayer\"]=window[\"dataLayer\"]||[];window['gtag-'+\"dataLayer\"]=function (){window[\"dataLayer\"].push(arguments);};window['gtag-'+\"dataLayer\"]('consent', \"default\", {$consentDefault});window['gtag-'+\"dataLayer\"]('js',new Date());window['gtag-'+\"dataLayer\"]('config',\"G-12345678\")",
'code' => "window[\"dataLayer\"]=window[\"dataLayer\"]||[];window['gtag-'+\"dataLayer\"]=function (){window[\"dataLayer\"].push(arguments);};window['gtag-'+\"dataLayer\"]('js',new Date());window['gtag-'+\"dataLayer\"]('config',\"G-12345678\")",
'key' => 'setup',
],
],
Expand All @@ -80,7 +79,63 @@ public function dataOutput(): array
'strategy' => ThirdPartyScriptData::STRATEGY_WORKER,
'location' => ThirdPartyScriptData::LOCATION_HEAD,
'action' => ThirdPartyScriptData::ACTION_APPEND,
'code' => "window[\"myDataLayer1\"]=window[\"myDataLayer1\"]||[];window['gtag-'+\"myDataLayer1\"]=function (){window[\"myDataLayer1\"].push(arguments);};window['gtag-'+\"myDataLayer1\"]('consent', \"default\", {$consentDefault});window['gtag-'+\"myDataLayer1\"]('js',new Date());window['gtag-'+\"myDataLayer1\"]('config',\"G-13579\")",
'code' => "window[\"myDataLayer1\"]=window[\"myDataLayer1\"]||[];window['gtag-'+\"myDataLayer1\"]=function (){window[\"myDataLayer1\"].push(arguments);};window['gtag-'+\"myDataLayer1\"]('js',new Date());window['gtag-'+\"myDataLayer1\"]('config',\"G-13579\")",
'key' => 'setup',
],
],
],
'with default consent' => [
[
'id' => 'G-12345678',
'consentValues' => [
'ad_user_data' => 'denied',
'ad_personalization' => 'denied',
'ad_storage' => 'denied',
'analytics_storage' => 'denied',
'wait_for_update' => 500,
],
],
[
[
'strategy' => ThirdPartyScriptData::STRATEGY_WORKER,
'location' => ThirdPartyScriptData::LOCATION_HEAD,
'action' => ThirdPartyScriptData::ACTION_APPEND,
'url' => 'https://www.googletagmanager.com/gtag/js?id=G-12345678',
'key' => 'gtag',
],
[
'strategy' => ThirdPartyScriptData::STRATEGY_WORKER,
'location' => ThirdPartyScriptData::LOCATION_HEAD,
'action' => ThirdPartyScriptData::ACTION_APPEND,
'code' => "window[\"dataLayer\"]=window[\"dataLayer\"]||[];window['gtag-'+\"dataLayer\"]=function (){window[\"dataLayer\"].push(arguments);};window['gtag-'+\"dataLayer\"]('consent', \"default\", {\"ad_user_data\":\"denied\",\"ad_personalization\":\"denied\",\"ad_storage\":\"denied\",\"analytics_storage\":\"denied\",\"wait_for_update\":500});window['gtag-'+\"dataLayer\"]('js',new Date());window['gtag-'+\"dataLayer\"]('config',\"G-12345678\")",
'key' => 'setup',
],
],
],
'with consent update' => [
[
'id' => 'G-12345678',
'consentType' => 'update',
'consentValues' => [
'ad_user_data' => 'granted',
'ad_personalization' => 'granted',
'ad_storage' => 'granted',
'analytics_storage' => 'granted',
],
],
[
[
'strategy' => ThirdPartyScriptData::STRATEGY_WORKER,
'location' => ThirdPartyScriptData::LOCATION_HEAD,
'action' => ThirdPartyScriptData::ACTION_APPEND,
'url' => 'https://www.googletagmanager.com/gtag/js?id=G-12345678',
'key' => 'gtag',
],
[
'strategy' => ThirdPartyScriptData::STRATEGY_WORKER,
'location' => ThirdPartyScriptData::LOCATION_HEAD,
'action' => ThirdPartyScriptData::ACTION_APPEND,
'code' => "window[\"dataLayer\"]=window[\"dataLayer\"]||[];window['gtag-'+\"dataLayer\"]=function (){window[\"dataLayer\"].push(arguments);};window['gtag-'+\"dataLayer\"]('consent', \"update\", {\"ad_user_data\":\"granted\",\"ad_personalization\":\"granted\",\"ad_storage\":\"granted\",\"analytics_storage\":\"granted\"});window['gtag-'+\"dataLayer\"]('js',new Date());window['gtag-'+\"dataLayer\"]('config',\"G-12345678\")",
'key' => 'setup',
],
],
Expand Down

0 comments on commit 62c950c

Please sign in to comment.