Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Below is a rundown of all configuration options
#### General:
1) Authentication key: This is used to communicate with tweakwise and determines your navigator instance, it should be the same as the key found in the navigator under `Connectivity > End points`.
2) Grouped producets: Enable this after the grouped export has been enabled and imported in tweakwise. This wil enable filtering based on variant data and enabled the product image to be controlled by tweakwise so that the correct color is shown based on selected filters. This requires the image url in tweakwise to be configured correctly. If this is not enabled and you've exported grouped products the catalog may be empty.
3) Internal traffic IP addresses: Comma-separated list of IP addresses that should be tagged as internal traffic. When a visitor's IP matches one of the configured addresses, all Tweakwise API requests triggered by that visit will include a `TWN-Source: Internal-Traffic` header. This allows you to filter out internal traffic (e.g. employees, testers) from Tweakwise Analytics reports. See [Tweakwise docs](https://docs.tweakwise.com/reference/identify-requests-as-internal-traffic) for details.

#### Layered Navigation (All settings depend on Enabled having value yes):
1) Enabled: Use tweakwise results in navigation, if disabled the standard magento navigation is used. Don't disable the anchor tag on main categories, this causes al products to be shown. The anchor tag can be disabled on sub-categories.
Expand Down
1 change: 1 addition & 0 deletions codeception.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace: Tweakwise\Test
support_namespace: Support
bootstrap: bootstrap.php
paths:
tests: tests
output: tests/_output
Expand Down
26 changes: 23 additions & 3 deletions src/Model/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use SimpleXMLElement;
use GuzzleHttp\Client as HttpClient;
use Magento\Framework\UrlInterface;
use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress;
use GuzzleHttp\Psr7\Uri;

class Client
Expand Down Expand Up @@ -79,7 +80,8 @@ public function __construct(
ResponseFactory $responseFactory,
EndpointManager $endpointManager,
Timer $timer,
private UrlInterface $urlBuilder
private UrlInterface $urlBuilder,
private RemoteAddress $remoteAddress
) {
$this->config = $config;
$this->log = $log;
Expand Down Expand Up @@ -108,6 +110,24 @@ protected function getClient(): HttpClient
return $this->client;
}

/**
* @return array<string, string>
*/
protected function buildInternalTrafficHeader(): array
{
$internalIps = $this->config->getInternalIpAddresses();
if (empty($internalIps)) {
return [];
}

$visitorIp = $this->remoteAddress->getRemoteAddress();
if (!in_array($visitorIp, $internalIps, true)) {
return [];
}

return ['TWN-Source' => 'Internal-Traffic'];
}

/**
* @param Request $tweakwiseRequest
* @return HttpRequest
Expand All @@ -128,7 +148,7 @@ protected function createHttpRequest(Request $tweakwiseRequest): HttpRequest
protected function createPostRequest(Request $tweakwiseRequest): HttpRequest
{
$path = $tweakwiseRequest->getPath();
$headers = [];
$headers = $this->buildInternalTrafficHeader();

$headers['Content-Type'] = 'application/json';
$headers['Instance-Key'] = $this->config->getGeneralAuthenticationKey();
Expand All @@ -149,7 +169,7 @@ protected function createGetRequest(Request $tweakwiseRequest): HttpRequest
$path = $tweakwiseRequest->getPath();
$pathSuffix = $tweakwiseRequest->getPathSuffix();

$headers = [];
$headers = $this->buildInternalTrafficHeader();

if ($path === 'recommendations/featured') {
if ($this->config->getRecommendationsFeaturedCategory()) {
Expand Down
13 changes: 13 additions & 0 deletions src/Model/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -670,4 +670,17 @@ public function getProductCardLifetime(): int
{
return (int) $this->config->getValue(self::PRODUCT_CARD_LIFETIME_XML_PATH, ScopeInterface::SCOPE_STORE);
}

/**
* @return string[]
*/
public function getInternalIpAddresses(): array
{
$value = $this->getStoreConfig('tweakwise/general/internal_ip_addresses');
if (empty($value)) {
return [];
}

return array_filter(array_map('trim', explode(',', $value)));
}
}
4 changes: 4 additions & 0 deletions src/etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
<label>Language</label>
<source_model>Tweakwise\Magento2Tweakwise\Model\Config\Source\Language</source_model>
</field>
<field id="internal_ip_addresses" translate="label,comment" type="textarea" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Internal traffic IP addresses</label>
<comment>Comma-separated list of IP addresses. Requests from these IPs will be tagged with TWN-Source: Internal-Traffic in Tweakwise API calls, allowing you to filter them from Tweakwise Analytics reports.</comment>
</field>
</group>
<group id="layered" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Layered navigation</label>
Expand Down
30 changes: 30 additions & 0 deletions tests/Unit/Model/ClientExposed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Tweakwise\Test\Unit\Model;

use GuzzleHttp\Psr7\Request as HttpRequest;
use Tweakwise\Magento2Tweakwise\Model\Client;
use Tweakwise\Magento2Tweakwise\Model\Client\Request;

class ClientExposed extends Client
{
/**
* @param Request $request
* @return HttpRequest
*/
public function createGetRequestPublic(Request $request): HttpRequest
{
return parent::createGetRequest($request);
}

/**
* @param Request $request
* @return HttpRequest
*/
public function createPostRequestPublic(Request $request): HttpRequest
{
return parent::createPostRequest($request);
}
}
142 changes: 142 additions & 0 deletions tests/Unit/Model/ClientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

declare(strict_types=1);

namespace Tweakwise\Test\Unit\Model;

use Emico\CodeCept\Test\Unit;
use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress;
use Magento\Framework\UrlInterface;
use Tweakwise\Magento2Tweakwise\Model\Client\EndpointManager;
use Tweakwise\Magento2Tweakwise\Model\Client\Request;
use Tweakwise\Magento2Tweakwise\Model\Client\ResponseFactory;
use Tweakwise\Magento2Tweakwise\Model\Client\Timer;
use Tweakwise\Magento2Tweakwise\Model\Config;
use Tweakwise\Magento2TweakwiseExport\Model\Logger;
use Tweakwise\Test\Support\UnitTester;
use Tweakwise\Magento2Tweakwise\Model\Client\Request\AnalyticsRequest;

class ClientTest extends Unit
{
/**
* @var UnitTester
*/
protected UnitTester $tester;

/**
* @return void
*/
public function testCreateGetRequestAddsInternalTrafficHeaderForMatchingIp(): void
{
$config = $this->createMock(Config::class);
$config->method('getInternalIpAddresses')->willReturn(['127.0.0.1']);
$config->method('getGeneralAuthenticationKey')->willReturn('123456');
$config->method('getRecommendationsFeaturedCategory')->willReturn(false);

$endpointManager = $this->createMock(EndpointManager::class);
$endpointManager->method('getServerUrl')->willReturn('https://gateway.tweakwisenavigator.net');

$remoteAddress = $this->createMock(RemoteAddress::class);
$remoteAddress->method('getRemoteAddress')->willReturn('127.0.0.1');

$client = $this->createClient($config, $endpointManager, $remoteAddress);

$request = $this->createMock(Request::class);
$request->method('getPath')->willReturn('navigation');
$request->method('getPathSuffix')->willReturn('');
$request->method('getParameters')->willReturn(['tn_ps' => '12']);

$httpRequest = $client->createGetRequestPublic($request);

$this->assertSame('Internal-Traffic', $httpRequest->getHeaderLine('TWN-Source'));
}

/**
* @return void
*/
public function testCreateGetRequestSkipsInternalTrafficHeaderForNonMatchingIp(): void
{
$config = $this->createMock(Config::class);
$config->method('getInternalIpAddresses')->willReturn(['10.0.0.99']);
$config->method('getGeneralAuthenticationKey')->willReturn('123456');
$config->method('getRecommendationsFeaturedCategory')->willReturn(false);

$endpointManager = $this->createMock(EndpointManager::class);
$endpointManager->method('getServerUrl')->willReturn('https://gateway.tweakwisenavigator.net');

$remoteAddress = $this->createMock(RemoteAddress::class);
$remoteAddress->method('getRemoteAddress')->willReturn('127.0.0.1');

$client = $this->createClient($config, $endpointManager, $remoteAddress);

$request = $this->createMock(Request::class);
$request->method('getPath')->willReturn('navigation');
$request->method('getPathSuffix')->willReturn('');
$request->method('getParameters')->willReturn(['tn_ps' => '12']);

$httpRequest = $client->createGetRequestPublic($request);

$this->assertSame('', $httpRequest->getHeaderLine('TWN-Source'));
}

/**
* @return void
*/
public function testCreatePostRequestAddsInternalTrafficHeaderForAnalyticsRequests(): void
{
$config = $this->createMock(Config::class);
$config->method('getInternalIpAddresses')->willReturn(['127.0.0.1']);
$config->method('getGeneralAuthenticationKey')->willReturn('123456');

$urlBuilder = $this->createMock(UrlInterface::class);
$urlBuilder->expects($this->once())
->method('getUrl')
->with('https://navigator-analytics.tweakwise.com/analytics')
->willReturn('https://navigator-analytics.tweakwise.com/analytics');

$remoteAddress = $this->createMock(RemoteAddress::class);
$remoteAddress->method('getRemoteAddress')->willReturn('127.0.0.1');

$client = new ClientExposed(
$config,
$this->createMock(Logger::class),
$this->createMock(ResponseFactory::class),
$this->createMock(EndpointManager::class),
$this->createMock(Timer::class),
$urlBuilder,
$remoteAddress
);

$request = $this->createMock(AnalyticsRequest::class);
$request->method('getPath')->willReturn('analytics');
$request->method('getApiUrl')->willReturn('https://navigator-analytics.tweakwise.com');
$request->method('getParameters')->willReturn(['event' => 'click']);

$httpRequest = $client->createPostRequestPublic($request);

$this->assertSame('Internal-Traffic', $httpRequest->getHeaderLine('TWN-Source'));
$this->assertSame('123456', $httpRequest->getHeaderLine('Instance-Key'));
}

/**
* @param Config $config
* @param EndpointManager $endpointManager
* @param RemoteAddress $remoteAddress
* @return ClientExposed
*/
private function createClient(
Config $config,
EndpointManager $endpointManager,
RemoteAddress $remoteAddress
): ClientExposed {
return new ClientExposed(
$config,
$this->createMock(Logger::class),
$this->createMock(ResponseFactory::class),
$endpointManager,
$this->createMock(Timer::class),
$this->createMock(UrlInterface::class),
$remoteAddress
);
}
}
75 changes: 75 additions & 0 deletions tests/Unit/Model/ConfigTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Tweakwise\Test\Unit\Model;

use Emico\CodeCept\Test\Unit;
use Magento\Framework\App\Area;
use Magento\Framework\App\Cache\TypeListInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Config\Storage\WriterInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\State;
use Magento\Framework\Serialize\Serializer\Json;
use Tweakwise\Magento2Tweakwise\Model\Config;
use Tweakwise\Test\Support\UnitTester;

class ConfigTest extends Unit
{
/**
* @var UnitTester
*/
protected UnitTester $tester;

/**
* @return void
*/
public function testGetInternalIpAddressesReturnsEmptyArrayWhenConfigIsEmpty(): void
{
$scopeConfig = $this->createMock(ScopeConfigInterface::class);
$scopeConfig->expects($this->once())
->method('getValue')
->with('tweakwise/general/internal_ip_addresses', 'store', null)
->willReturn('');

$config = $this->createConfig($scopeConfig);

$this->assertSame([], $config->getInternalIpAddresses());
}

/**
* @return void
*/
public function testGetInternalIpAddressesParsesCommaSeparatedValuesWithWhitespace(): void
{
$scopeConfig = $this->createMock(ScopeConfigInterface::class);
$scopeConfig->expects($this->once())
->method('getValue')
->with('tweakwise/general/internal_ip_addresses', 'store', null)
->willReturn('127.0.0.1 , 10.0.0.1 , 192.168.1.1');

$config = $this->createConfig($scopeConfig);

$this->assertSame(['127.0.0.1', '10.0.0.1', '192.168.1.1'], $config->getInternalIpAddresses());
}

/**
* @param ScopeConfigInterface $scopeConfig
* @return Config
*/
private function createConfig(ScopeConfigInterface $scopeConfig): Config
{
$state = $this->createMock(State::class);
$state->method('getAreaCode')->willReturn(Area::AREA_FRONTEND);

return new Config(
$scopeConfig,
$this->createMock(Json::class),
$this->createMock(RequestInterface::class),
$state,
$this->createMock(WriterInterface::class),
$this->createMock(TypeListInterface::class)
);
}
}
20 changes: 20 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

spl_autoload_register(static function (string $class): void {
$prefix = 'Tweakwise\\Test\\';
$baseDir = __DIR__ . '/';

if (strncmp($prefix, $class, strlen($prefix)) !== 0) {
return;
}

$file = $baseDir . str_replace('\\', '/', substr($class, strlen($prefix))) . '.php';

if (!file_exists($file)) {
return;
}

require $file;
});