Skip to content

Commit

Permalink
- added "session_key" config
Browse files Browse the repository at this point in the history
- added "allowed_utm_parameter"  config
- added tests
  • Loading branch information
toni-suarez committed Jun 21, 2024
1 parent 760d3df commit 3b9ea0d
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 15 deletions.
37 changes: 37 additions & 0 deletions config/statamic-utm-parameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,41 @@
* - Disabled (false): The initial UTM parameters will persist throughout the session.
*/
'override_utm_parameters' => false,

/*
* Session Key for UTM Parameters (default: 'utm')
*
* This key specifies the name used to access and store UTM parameters within the session data.
*
* If you're already using 'utm' for another purpose in your application,
* you can customize this key to avoid conflicts.
* Simply provide your preferred key name as a string value.
*/
'session_key' => 'utm',

/*
* Allowed UTM Parameters (default: ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_campaign_id'])
*
* This setting defines the UTM parameters that are allowed within your application.
*
* In this array, you can specify a list of allowed UTM parameter names. Each parameter should be listed as a string.
* Only parameters from this list will be stored and processed in the session.
* and any parameter without the 'utm_' prefix will be ignored regardless of its inclusion in this list.
*
* Example: To only allow the basic UTM parameters (source, medium, and campaign), you could update the array like this:
*
* 'allowed_utm_parameters' => [
* 'utm_source',
* 'utm_medium',
* 'utm_campaign',
* ],
*/
'allowed_utm_parameters' => [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_term',
'utm_content',
'utm_campaign_id'
],
];
35 changes: 25 additions & 10 deletions src/UtmParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@ class UtmParameter
*/
public $parameters;

/**
* Utm Parameter Session Key.
*
* @var string
*/
public string $sessionKey;

public function __construct(array $parameters = [])
{
$this->sessionKey = config('statamic-utm-parameter.session_key');
$this->parameters = $parameters;
}

Expand All @@ -40,16 +48,16 @@ public function boot(Request $request)
public function useRequestOrSession(Request $request)
{
$currentRequestParameter = self::getParameter($request);
$sessionParameter = session('utm');
$sessionParameter = session($this->sessionKey);

if (!empty($currentRequestParameter) && empty($sessionParameter)) {
session(['utm' => $currentRequestParameter]);
session([$this->sessionKey => $currentRequestParameter]);
return $currentRequestParameter;
}

if (!empty($currentRequestParameter) && !empty($sessionParameter) && config('statamic-utm-parameter.override_utm_parameters')) {
$mergedParameters = array_merge($sessionParameter, $currentRequestParameter);
session(['utm' => $mergedParameters]);
session([$this->sessionKey => $mergedParameters]);
return $mergedParameters;
}

Expand All @@ -61,7 +69,7 @@ public function useRequestOrSession(Request $request)
*
* @return array
*/
public static function all()
public static function all() : array
{
return app(UtmParameter::class)->parameters ?? [];
}
Expand All @@ -73,7 +81,7 @@ public static function all()
*
* @return string|null
*/
public static function get($key)
public static function get(string $key)
{
$parameters = self::all();
$key = self::ensureUtmPrefix($key);
Expand All @@ -93,7 +101,7 @@ public static function get($key)
* @param string $value
* @return bool
*/
public static function contains($key, $value)
public static function contains(string $key, string $value)
{
$parameters = self::all();
$key = self::ensureUtmPrefix($key);
Expand All @@ -113,7 +121,7 @@ public static function contains($key, $value)
*
* @return bool
*/
public static function has($key, $value = null)
public static function has(string $key, $value = null)
{
$parameters = self::all();
$key = self::ensureUtmPrefix($key);
Expand All @@ -136,7 +144,7 @@ public static function has($key, $value = null)
*/
public static function clear()
{
session()->forget('utm');
session()->forget(app(UtmParameter::class)->sessionKey);
app(UtmParameter::class)->parameters = null;
return true;
}
Expand All @@ -146,11 +154,18 @@ public static function clear()
*
* @return array
*/
protected static function getParameter(Request $request)
protected static function getParameter(Request $request) : array
{
$allowedKeys = config('statamic-utm-parameter.allowed_utm_parameters', [
'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'
]);

return collect($request->all())
->filter(fn ($value, $key) => substr($key, 0, 4) === 'utm_')
->map(fn ($value) => htmlspecialchars($value, ENT_QUOTES, 'UTF-8'))
->filter(fn ($value, $key) => in_array($key, $allowedKeys))
->mapWithKeys(fn ($value, $key) => [
htmlspecialchars($key, ENT_QUOTES, 'UTF-8') => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
])
->toArray();
}

Expand Down
121 changes: 116 additions & 5 deletions tests/UtmParametersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@

class UtmParametersTest extends TestCase
{
protected $sessionKey;

public function setUp(): void
{
parent::setUp();
Config::set('statamic-utm-parameter.override_utm_parameters', false);
Config::set('statamic-utm-parameter.session_key', 'custom_utm_key');
$this->sessionKey = Config::get('statamic-utm-parameter.session_key');

$parameters = [
'utm_source' => 'google',
Expand All @@ -26,7 +30,17 @@ public function setUp(): void

app()->singleton(UtmParameter::class, fn () => new UtmParameter());
app(UtmParameter::class)->boot($request);
session(['utm' => $parameters]);
session([$this->sessionKey => $parameters]);
}

public function tearDown() : void
{
session()->forget($this->sessionKey);

Config::set('statamic-utm-parameter.override_utm_parameters', null);
Config::set('statamic-utm-parameter.session_key', null);

parent::tearDown();
}

public function test_it_should_be_bound_in_the_app()
Expand All @@ -35,6 +49,29 @@ public function test_it_should_be_bound_in_the_app()
$this->assertInstanceOf(UtmParameter::class, $utm);
}

public function test_it_should_have_a_session_key()
{
$this->assertIsString($this->sessionKey);
}

public function test_it_should_have_a_session()
{
$sessionContent = session($this->sessionKey);
$this->assertIsArray($sessionContent);
$this->assertArrayHasKey('utm_source', $sessionContent);
$this->assertIsNotString(session($this->sessionKey));
}

public function test_it_should_also_clear_a_session()
{
$sessionContent = session($this->sessionKey);
$this->assertIsArray($sessionContent);

$sessionEmptyContent = session()->forget($this->sessionKey);
$this->assertIsNotArray($sessionEmptyContent);
$this->assertNull($sessionEmptyContent);
}

public function test_it_should_have_an_utm_attribute_bag()
{
$utm = UtmParameter::all();
Expand Down Expand Up @@ -154,19 +191,19 @@ public function test_it_should_determine_if_an_utm_contains_not_a_value()

public function test_it_should_determine_if_an_utm_contains_a_non_string_value()
{
$campaign = UtmParameter::contains('utm_campaign', null);
$campaign = UtmParameter::contains('utm_campaign', 'null');
$this->assertIsBool($campaign);
$this->assertFalse($campaign);

$term = UtmParameter::contains('utm_term', false);
$term = UtmParameter::contains('utm_term', 'false');
$this->assertIsBool($term);
$this->assertFalse($term);

$content = UtmParameter::contains('utm_content', []);
$content = UtmParameter::contains('utm_content', '[]');
$this->assertIsBool($content);
$this->assertFalse($content);

$medium = UtmParameter::contains('utm_medium', 1);
$medium = UtmParameter::contains('utm_medium', '1');
$this->assertIsBool($medium);
$this->assertFalse($medium);
}
Expand All @@ -175,9 +212,11 @@ public function test_it_should_clear_and_remove_the_utm_parameter_again()
{
$source = UtmParameter::get('source');
$this->assertEquals('google', $source);
$this->assertArrayHasKey('utm_source', session($this->sessionKey));

UtmParameter::clear();
$emptySource = UtmParameter::get('source');
$this->assertNull(session($this->sessionKey));
$this->assertNull($emptySource);
}

Expand Down Expand Up @@ -250,4 +289,76 @@ public function test_it_should_keep_existing_parameters_while_browsing()
$source = UtmParameter::get('source');
$this->assertEquals('google', $source);
}

public function test_it_should_only_use_utm_parameters_in_the_allowed_list()
{
session()->forget($this->sessionKey);
Config::set('statamic-utm-parameter.override_utm_parameters', true);
Config::set('statamic-utm-parameter.allowed_utm_parameters', ['utm_source', 'utm_medium']);

$parameters = [
'utm_source'=> 'newsletter',
'utm_medium' => 'email',
'utm_campaign' => 'not-allowed',
];

$request = Request::create('/test', 'GET', $parameters);
app(UtmParameter::class)->boot($request);

$source = UtmParameter::get('source');
$this->assertEquals('newsletter', $source);

$medium = UtmParameter::get('medium');
$this->assertEquals('email', $medium);

$campaign = UtmParameter::get('campaign');
$this->assertNull($campaign);
}

public function test_it_should_sanitize_utm_parameter()
{
Config::set('statamic-utm-parameter.override_utm_parameters', true);

$parameters = [
'utm_source'=> '<span onclick="alert(\'alert\')">google</span>',
'utm_medium' => 'cpc<script>alert(1)</script>',
'utm_campaign' => '<script href="x" onload="alert(1)">',
'utm_content' => '<img src="x" onerror="alert(1)">',
'utm_term' => '%3Cscript%3Ealert(1)%3C%2Fscript%3E',
'utm_sql_injection' => '" OR 1=1; --',
'utm_html_tag' => '<b>bold</b>',
'utm_<html onclick="alert(\'alert\')">_tag' => '<b>bold</b>',
'utm_" OR 1=1; --' => '<b>bold</b>',
];

$request = Request::create('/test', 'GET', $parameters);
app(UtmParameter::class)->boot($request);

$source = UtmParameter::get('source');
$this->assertEquals('&lt;span onclick=&quot;alert(&#039;alert&#039;)&quot;&gt;google&lt;/span&gt;', $source);

$medium = UtmParameter::get('medium');
$this->assertEquals('cpc&lt;script&gt;alert(1)&lt;/script&gt;', $medium);

$campaign = UtmParameter::get('campaign');
$this->assertEquals('&lt;script href=&quot;x&quot; onload=&quot;alert(1)&quot;&gt;', $campaign);

$content = UtmParameter::get('content');
$this->assertEquals('&lt;img src=&quot;x&quot; onerror=&quot;alert(1)&quot;&gt;', $content);

$term = UtmParameter::get('term');
$this->assertEquals('%3Cscript%3Ealert(1)%3C%2Fscript%3E', $term);

$sql = UtmParameter::get('sql_injection');
$this->assertNull($sql);

$html = UtmParameter::get('html_tag');
$this->assertNull($html);

$randomKey = UtmParameter::get('utm_&lt;html onclick=&quot;alert(&#039;alert&#039;)&quot;&gt;_tag');
$this->assertNull($randomKey);

$randomSqlKey = UtmParameter::get('utm_&quot; OR 1=1; --');
$this->assertNull($randomSqlKey);
}
}

0 comments on commit 3b9ea0d

Please sign in to comment.