mirror of
https://github.com/werkraum-media/abtest.git
synced 2024-12-06 13:46:09 +01:00
Integrate Matomo tracking for A/B Testing (#2)
* Adds new fields to pages. * Adds new event handler and integration to add necessary JS to track experiment with variant.
This commit is contained in:
parent
7ace7e2625
commit
9f0e6cd6c5
11 changed files with 311 additions and 6 deletions
|
@ -24,16 +24,21 @@ declare(strict_types=1);
|
|||
namespace WerkraumMedia\ABTest\Hook;
|
||||
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController as Typo3TypoScriptFrontendController;
|
||||
use WerkraumMedia\ABTest\MatomoTracker;
|
||||
use WerkraumMedia\ABTest\Switcher;
|
||||
|
||||
class TypoScriptFrontendController
|
||||
{
|
||||
private Switcher $switcher;
|
||||
|
||||
private MatomoTracker $matomoTracker;
|
||||
|
||||
public function __construct(
|
||||
Switcher $switcher
|
||||
Switcher $switcher,
|
||||
MatomoTracker $matomoTracker
|
||||
) {
|
||||
$this->switcher = $switcher;
|
||||
$this->matomoTracker = $matomoTracker;
|
||||
}
|
||||
|
||||
public function determineIdPostProc(
|
||||
|
@ -43,8 +48,16 @@ class TypoScriptFrontendController
|
|||
$this->switcher->switch($frontendController);
|
||||
}
|
||||
|
||||
public function contentPostProcAll(
|
||||
array $params,
|
||||
Typo3TypoScriptFrontendController $frontendController
|
||||
): void {
|
||||
$frontendController->content = $this->matomoTracker->addScriptToHtmlMarkup($frontendController->content);
|
||||
}
|
||||
|
||||
public static function register(): void
|
||||
{
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['determineId-PostProc'][self::class] = self::class . '->determineIdPostProc';
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'][self::class] = self::class . '->contentPostProcAll';
|
||||
}
|
||||
}
|
||||
|
|
62
Classes/MatomoTracker.php
Normal file
62
Classes/MatomoTracker.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace WerkraumMedia\ABTest;
|
||||
|
||||
use WerkraumMedia\ABTest\Events\SwitchedToVariant;
|
||||
|
||||
class MatomoTracker
|
||||
{
|
||||
private string $experiment = '';
|
||||
|
||||
private string $variation = '';
|
||||
|
||||
public function handleVariant(SwitchedToVariant $event): void
|
||||
{
|
||||
$this->experiment = $event->getOriginalPage()['tx_abtest_matomo_experiment_id'] ?? '';
|
||||
$this->variation = $event->getVariantPage()['tx_abtest_matomo_variant_id'] ?? '';
|
||||
}
|
||||
|
||||
public function addScriptToHtmlMarkup(string $markup): string
|
||||
{
|
||||
if ($this->experiment === '' || $this->variation === '') {
|
||||
return $markup;
|
||||
}
|
||||
|
||||
$script = $this->generateScript();
|
||||
return str_replace('</body>', $script . '</body>', $markup);
|
||||
}
|
||||
|
||||
private function generateScript(): string
|
||||
{
|
||||
$experiment = htmlspecialchars($this->experiment);
|
||||
$variation = htmlspecialchars($this->variation);
|
||||
|
||||
return '<script>'
|
||||
. 'var _paq = window._paq = window._paq || [];'
|
||||
. "_paq.push(['AbTesting::enter', {experiment: '$experiment', variation: '$variation'}]);"
|
||||
. '</script>'
|
||||
. PHP_EOL
|
||||
;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WerkraumMedia\ABTest\TCA;
|
||||
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
|
||||
|
||||
class VariantFilter
|
||||
|
@ -36,7 +35,7 @@ class VariantFilter
|
|||
$this->pageRepository = $pageRepository;
|
||||
}
|
||||
|
||||
public function doFilter(array $parameters, DataHandler $dataHandler): array
|
||||
public function doFilter(array $parameters): array
|
||||
{
|
||||
return array_filter($parameters['values'], [$this, 'filterPage']);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace DanielSiepmann\Configuration;
|
|||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
use WerkraumMedia\ABTest\Events\SwitchedToVariant;
|
||||
use WerkraumMedia\ABTest\Hook\TypoScriptFrontendController;
|
||||
use WerkraumMedia\ABTest\MatomoTracker;
|
||||
use WerkraumMedia\ABTest\Middleware\SetCookie;
|
||||
use WerkraumMedia\ABTest\TCA\VariantFilter;
|
||||
|
||||
|
@ -26,4 +27,8 @@ return static function (ContainerConfigurator $containerConfigurator) {
|
|||
'method' => 'handleVariant',
|
||||
'event' => SwitchedToVariant::class,
|
||||
]);
|
||||
$services->set(MatomoTracker::class)->tag('event.listener', [
|
||||
'method' => 'handleVariant',
|
||||
'event' => SwitchedToVariant::class,
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
'tx_abtest_variant' => [
|
||||
'exclude' => 1,
|
||||
'label' => $languagePath . 'tx_abtest_variant',
|
||||
'description' => $languagePath . 'tx_abtest_variant.description',
|
||||
'config' => [
|
||||
'type' => 'group',
|
||||
'allowed' => 'pages',
|
||||
|
@ -31,6 +32,7 @@
|
|||
'tx_abtest_cookie_time' => [
|
||||
'exclude' => 1,
|
||||
'label' => $languagePath . 'tx_abtest_cookie_time',
|
||||
'description' => $languagePath . 'tx_abtest_cookie_time.description',
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'eval' => 'int',
|
||||
|
@ -50,17 +52,52 @@
|
|||
'tx_abtest_counter' => [
|
||||
'exclude' => 1,
|
||||
'label' => $languagePath . 'tx_abtest_counter',
|
||||
'description' => $languagePath . 'tx_abtest_counter.description',
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'eval' => 'int',
|
||||
'size' => 10,
|
||||
],
|
||||
],
|
||||
|
||||
'tx_abtest_matomo_experiment_id' => [
|
||||
'exclude' => 1,
|
||||
'label' => $languagePath . 'tx_abtest_matomo_experiment_id',
|
||||
'description' => $languagePath . 'tx_abtest_matomo_experiment_id.description',
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'eval' => 'nospace',
|
||||
],
|
||||
],
|
||||
'tx_abtest_matomo_variant_id' => [
|
||||
'exclude' => 1,
|
||||
'label' => $languagePath . 'tx_abtest_matomo_variant_id',
|
||||
'description' => $languagePath . 'tx_abtest_matomo_variant_id.description',
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'eval' => 'nospace',
|
||||
'valuePicker' => [
|
||||
'items' => [
|
||||
[$languagePath . 'tx_abtest_matomo_variant_id.original', 'original'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$GLOBALS['TCA'][$tableName]['palettes']['tx_abtest_matomo'] = [
|
||||
'showitem' => 'tx_abtest_matomo_experiment_id, tx_abtest_matomo_variant_id',
|
||||
];
|
||||
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes(
|
||||
$tableName,
|
||||
'--div--;' . $languagePath . 'div_title,tx_abtest_variant,tx_abtest_cookie_time,tx_abtest_counter',
|
||||
implode(',', [
|
||||
'--div--;' . $languagePath . 'div_title',
|
||||
'tx_abtest_variant',
|
||||
'tx_abtest_cookie_time',
|
||||
'tx_abtest_counter',
|
||||
'--palette--;' . $languagePath . 'palette_tx_abtest_matomo;tx_abtest_matomo',
|
||||
]),
|
||||
'',
|
||||
'after:content_from_pid'
|
||||
);
|
||||
|
|
11
README.md
11
README.md
|
@ -21,8 +21,17 @@ Additional header information may be specified both for the original version as
|
|||
|
||||
![Demo](https://raw.githubusercontent.com/werkraum-media/abtest/master/Documentation/Images/demo.gif)
|
||||
|
||||
### Matomo A/B integration
|
||||
|
||||
Provides an integration for "A/B Testing - Experiments" https://matomo.org/a-b-testing/.
|
||||
This is currently enabled out of the box and integrated into this extension.
|
||||
That is because we need this for one of our customers.
|
||||
We didn't think it is worth it to split it up into this own extension right now.
|
||||
|
||||
You can disable the corresponding event listener and hide the corresponding fields.
|
||||
|
||||
### Known issues
|
||||
|
||||
This extension currently does not support typeNum.
|
||||
|
||||
It always checks requested page for a variant.
|
||||
It always checks requested page for a variant, and it always adds the tracking code.
|
||||
|
|
|
@ -11,10 +11,18 @@
|
|||
<source>Variant</source>
|
||||
<target>Variante</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_variant.description" approved="yes">
|
||||
<source>The selected page will be used as Variation of this page.</source>
|
||||
<target>Die ausgewählte Seite wird als Variante dieser Seite genutzt.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_cookie_time" approved="yes">
|
||||
<source>Cookie Lifetime</source>
|
||||
<target>Cookie Lebenszeit</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_cookie_time.description" approved="yes">
|
||||
<source>Defines how long the selected variation should be saved on the visitors end. The visitor will see the same variation until end of this period.</source>
|
||||
<target>Definiert wie lange ein Besucher die ausgewählte Variante sehen soll. Nach Ablauf der Zeit wird die anzuzeigende Variante neu bestimmt.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_cookie_time.cookie_1_month" approved="yes">
|
||||
<source>1 month</source>
|
||||
<target>1 Monat</target>
|
||||
|
@ -43,6 +51,35 @@
|
|||
<source>Counter</source>
|
||||
<target>Zähler</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_counter.description" approved="yes">
|
||||
<source>Only technical. defines how often the page was shown to visitors. Used to determine whether to show this or a variant to next visitor.</source>
|
||||
<target>Rein technisch. Definiert wie viele Besucher diese Seite gesehen haben. Wird genutzt um zu bestimmen ob diese Seite oder eine Variante angezeigt werden soll.</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="pages.palette_tx_abtest_matomo" approved="yes">
|
||||
<source>Matomo</source>
|
||||
<target>Matomo</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_experiment_id" approved="yes">
|
||||
<source>Matomo Experiment ID</source>
|
||||
<target>Matomo Experiment ID</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_experiment_id.description" approved="yes">
|
||||
<source>Either the name of the experiment, or the technical ID to not expose the name in source code.</source>
|
||||
<target>Entweder der Name ohne Leerzeichen, so wie er in der Matomo UI angezeigt wird. Oder die technische ID, um den Namen nicht im Quellcode auszugeben.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_variant_id" approved="yes">
|
||||
<source>Matomo Variant ID</source>
|
||||
<target>Matomo Varianten ID</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_variant_id.description" approved="yes">
|
||||
<source>Either the name of the variant, or the technical ID to not expose the name in source code.</source>
|
||||
<target>Entweder der Name ohne Leerzeichen, so wie er in der Matomo UI angezeigt wird. Oder die technische ID, um den Namen nicht im Quellcode auszugeben.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_variant_id.original" approved="yes">
|
||||
<source>Original</source>
|
||||
<target>Original</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
|
|
@ -9,9 +9,15 @@
|
|||
<trans-unit id="pages.tx_abtest_variant" approved="yes">
|
||||
<source>Variant</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_variant.description" approved="yes">
|
||||
<source>The selected page will be used as Variation of this page.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_cookie_time" approved="yes">
|
||||
<source>Cookie Lifetime</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_cookie_time.description" approved="yes">
|
||||
<source>Defines how long the selected variation should be saved on the visitors end. The visitor will see the same variation until end of this period.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_cookie_time.cookie_1_month" approved="yes">
|
||||
<source>1 month</source>
|
||||
</trans-unit>
|
||||
|
@ -33,6 +39,28 @@
|
|||
<trans-unit id="pages.tx_abtest_counter" approved="yes">
|
||||
<source>Counter</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_counter.description" approved="yes">
|
||||
<source>Only technical. Defines how often the page was shown to visitors. Used to determine whether to show this or a variant to next visitor.</source>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="pages.palette_tx_abtest_matomo" approved="yes">
|
||||
<source>Matomo</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_experiment_id" approved="yes">
|
||||
<source>Matomo Experiment ID</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_experiment_id.description" approved="yes">
|
||||
<source>Either the name of the experiment, or the technical ID to not expose the name in source code.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_variant_id" approved="yes">
|
||||
<source>Matomo Variant ID</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_variant_id.description" approved="yes">
|
||||
<source>Either the name of the variant, or the technical ID to not expose the name in source code.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pages.tx_abtest_matomo_variant_id.original" approved="yes">
|
||||
<source>Original</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
|
8
Tests/Fixtures/BasicMatomoDatabase.csv
Normal file
8
Tests/Fixtures/BasicMatomoDatabase.csv
Normal file
|
@ -0,0 +1,8 @@
|
|||
"pages"
|
||||
,"uid","pid","slug","title",tx_abtest_variant,hidden,tx_abtest_cookie_time,tx_abtest_matomo_experiment_id,tx_abtest_matomo_variant_id
|
||||
,1,0,"/","Page 1 Title (No Variant)",0,0,604800,,
|
||||
,2,1,"/page-2","Page 2 Title (Variant A)",3,0,604800,TestForDevelopment,VariationA
|
||||
,3,1,"/page-3","Page 3 Title (Variant B)",0,0,604800,TestForDevelopment,VariationB
|
||||
"sys_template"
|
||||
,"uid","pid","root","clear","constants","config"
|
||||
,1,1,1,3,"databasePlatform = mysql","<INCLUDE_TYPOSCRIPT: source=""FILE:EXT:abtest/Tests/Fixtures/FrontendRendering.typoscript"">"
|
Can't render this file because it contains an unexpected character in line 2 and column 2.
|
104
Tests/Functional/MatomoTrackingTest.php
Normal file
104
Tests/Functional/MatomoTrackingTest.php
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
namespace WerkraumMedia\ABTest\Tests\Functional;
|
||||
|
||||
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
|
||||
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalResponse;
|
||||
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
|
||||
|
||||
class MatomoTrackingTest extends FunctionalTestCase
|
||||
{
|
||||
protected $testExtensionsToLoad = [
|
||||
'typo3conf/ext/abtest',
|
||||
];
|
||||
|
||||
protected $pathsToLinkInTestInstance = [
|
||||
'typo3conf/ext/abtest/Tests/Fixtures/Sites' => 'typo3conf/sites',
|
||||
];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpBackendUserFromFixture(1);
|
||||
|
||||
$this->importCSVDataSet(__DIR__ . '/../Fixtures/BasicMatomoDatabase.csv');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function rendersPageWithoutVariantWithoutMatomo(): void
|
||||
{
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$response = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertStringContainsString('Page 1 Title (No Variant)', $response->getBody()->__toString());
|
||||
$this->assertNoMatomoTrackingCode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function rendersVariantAWithMatomo(): void
|
||||
{
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(2);
|
||||
$response = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertStringContainsString('Page 2 Title (Variant A)', $response->getBody()->__toString());
|
||||
$this->assertMatomoTrackingCode($response, 'TestForDevelopment', 'VariationA');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function rendersVariantBWithMatomo(): void
|
||||
{
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(2);
|
||||
$request = $request->withAddedHeader('Cookie', 'ab-2=3');
|
||||
$response = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertStringContainsString('Page 3 Title (Variant B)', $response->getBody()->__toString());
|
||||
$this->assertMatomoTrackingCode($response, 'TestForDevelopment', 'VariationB');
|
||||
}
|
||||
|
||||
private function assertNoMatomoTrackingCode(InternalResponse $response): void
|
||||
{
|
||||
self::assertStringNotContainsString('_paq.push', $response->getBody()->__toString());
|
||||
}
|
||||
|
||||
private function assertMatomoTrackingCode(
|
||||
InternalResponse $response,
|
||||
string $experiment,
|
||||
string $variation
|
||||
): void {
|
||||
self::assertStringContainsString("_paq.push(['AbTesting::enter', {experiment: '$experiment', variation: '$variation'}]);", $response->getBody()->__toString());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
CREATE TABLE pages (
|
||||
tx_abtest_variant int(11) DEFAULT '0' NOT NULL,
|
||||
tx_abtest_cookie_time int(11) DEFAULT 604800 NOT NULL,
|
||||
tx_abtest_counter int(11) DEFAULT '0' NOT NULL
|
||||
tx_abtest_counter int(11) DEFAULT '0' NOT NULL,
|
||||
|
||||
tx_abtest_matomo_experiment_id varchar(255) DEFAULT '' NOT NULL,
|
||||
tx_abtest_matomo_variant_id varchar(255) DEFAULT '' NOT NULL,
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue