Initial commit

This commit is contained in:
Björn Fromme
2018-04-18 17:24:00 +02:00
commit da4db35be3
875 changed files with 80368 additions and 0 deletions
@@ -0,0 +1,55 @@
<?php
namespace EP\EpTrack\Hook;
/***************************************************************
*
* Copyright notice
*
* (c) 2016 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use TYPO3\CMS\Core\DataHandling\DataHandler;
class Tcemain
{
/**
* @param array $fields
* @param string $table
* @param string $table
* @param int $id
* @param DataHandler $dataHandler
*/
public function processDatamap_preProcessFieldArray(array &$fields, $table, $id, DataHandler $dataHandler)
{
// Force trailing slash
if ($table === 'tx_eptrack_domain_model_redirect') {
$url = $fields['redirect_from'];
if (substr($url, 0, 1) !== '/') {
$url = '/' . $url;
}
if (substr($url, -1) !== '/') {
$url .= '/';
}
$fields['redirect_from'] = $url;
}
}
}
@@ -0,0 +1,177 @@
<?php
namespace EP\EpTrack\Service;
/***************************************************************
*
* Copyright notice
*
* (c) 2016 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\DatabaseConnection;
use TYPO3\CMS\Core\TimeTracker\NullTimeTracker;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\Page\PageGenerator;
use TYPO3\CMS\Frontend\Utility\EidUtility;
class RequestService
{
const COOKIE_NAME = 'ep_trk';
const COOKIE_TTL = 30;
const CODE_PATTERN = '([a-z]{4}|PA)\d{4}.*';
public function handleRequest()
{
$currentUri = GeneralUtility::getIndpEnv('REQUEST_URI');
$this->matchRedirect($currentUri);
$this->matchTrackingCodeInUri($currentUri);
}
/**
* @param string $currentUri
*/
public function matchRedirect($currentUri)
{
$uri = strtolower(parse_url($currentUri, \PHP_URL_PATH));
/** @var DatabaseConnection $db */
$db = $GLOBALS['TYPO3_DB'];
$redirect = $db->exec_SELECTgetSingleRow(
'*',
'tx_eptrack_domain_model_redirect',
'tx_eptrack_domain_model_redirect.redirect_from=LOWER(' . $db->fullQuoteStr($uri, 'tx_eptrack_domain_model_redirect') . ')'
. ' AND tx_eptrack_domain_model_redirect.deleted=0 AND tx_eptrack_domain_model_redirect.hidden=0'
);
if ($redirect === false) {
return;
}
$trackingCode = $redirect['tracking_code'];
if (!empty($trackingCode)) {
$this->setCookie($trackingCode);
}
$targetPageUid = $redirect['target_page_uid'];
$responseCode = $redirect['response_code'];
$this->initTSFE($targetPageUid);
/** @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj */
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$typolinkConfig = ['parameter' => $targetPageUid, 'forceAbsoluteUrl' => true ];
$query = $this->buildUtmParameters($redirect);
if ($query) {
$typolinkConfig['additionalParams'] = '&' . $query;
}
$uri = $cObj->typolink_URL($typolinkConfig);
$this->redirect($uri, $responseCode);
}
/**
* @param array $redirect
* @return string
*/
public function buildUtmParameters(array $redirect)
{
if ($redirect['utm_source'] && $redirect['utm_medium'] && $redirect['utm_campaign'] && $redirect['utm_content']) {
return http_build_query([
'utm_source' => $redirect['utm_source'],
'utm_medium' => $redirect['utm_medium'],
'utm_campaign' => $redirect['utm_campaign'],
'utm_content' => $redirect['utm_content'],
]);
}
return '';
}
/**
* @param string $currentUri
*/
public function matchTrackingCodeInUri($currentUri)
{
$query = parse_url($currentUri, \PHP_URL_QUERY);
$path = parse_url($currentUri, \PHP_URL_PATH);
if (preg_match('/(' . static::CODE_PATTERN . ')\/?$/i', $path)) {
$urlItems = GeneralUtility::trimExplode('/', $path, true);
$code = array_pop($urlItems);
$this->setCookie($code);
$uri = '/';
if (count($urlItems) > 0) {
$uri = '/' . implode('/', $urlItems) . '/';
}
if ($query !== null) {
$uri .= '?' . $query;
}
$this->redirect($uri);
}
}
/**
* @param string $code
*/
protected function setCookie($code)
{
$expiratonDate = new \DateTime('now');
$expiratonDate->add(new \DateInterval('P' . static::COOKIE_TTL. 'D'));
setcookie(static::COOKIE_NAME, $code, $expiratonDate->getTimestamp(), '/');
}
/**
* @param string $uri
* @param string $responseCode
*/
protected function redirect($uri, $responseCode = '301')
{
HttpUtility::redirect($uri, constant('\TYPO3\CMS\Core\Utility\HttpUtility::HTTP_STATUS_' . $responseCode));
exit;
}
/**
* @param int $id
* @param int $typeNum
*/
public function initTSFE($id = 1, $typeNum = 0)
{
EidUtility::initTCA();
if (!is_object($GLOBALS['TT'])) {
$GLOBALS['TT'] = new NullTimeTracker;
$GLOBALS['TT']->start();
}
$GLOBALS['TSFE'] = GeneralUtility::makeInstance(TypoScriptFrontendController::class, $GLOBALS['TYPO3_CONF_VARS'], $id, $typeNum);
$GLOBALS['TSFE']->connectToDB();
$GLOBALS['TSFE']->initFEuser();
$GLOBALS['TSFE']->determineId();
$GLOBALS['TSFE']->initTemplate();
$GLOBALS['TSFE']->rootLine = $GLOBALS['TSFE']->sys_page->getRootLine($id, '');
$GLOBALS['TSFE']->getConfigArray();
if (ExtensionManagementUtility::isLoaded('realurl')) {
$rootline = BackendUtility::BEgetRootLine($id);
$host = BackendUtility::firstDomainRecord($rootline);
$_SERVER['HTTP_HOST'] = $host;
}
PageGenerator::pagegenInit();
}
}
@@ -0,0 +1,228 @@
<?php
return [
'ctrl' => [
'title' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect',
'default_sortby' => 'ORDER BY label',
'label' => 'label',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'dividers2tabs' => TRUE,
'versioningWS' => 2,
'versioning_followPages' => TRUE,
'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l10n_parent',
'transOrigDiffSourceField' => 'l10n_diffsource',
'delete' => 'deleted',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'label, redirect_from, tracking_code',
'iconfile' => 'EXT:ep_track/Resources/Public/Icons/tx_eptrack_domain_model_redirect.gif'
],
'interface' => [
'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, label, redirect_from, target_page_uid, tracking_code, response_code, utm_source, utm_medium, utm_campaign, utm_content',
],
'types' => [
'1' => [
'showitem' => 'hidden, label, redirect_from, target_page_uid, --palette--;;codes, --palette--;UTM;utm',
],
],
'palettes' => [
'codes' => ['showitem' => 'tracking_code, response_code'],
'utm' => ['showitem' => 'utm_source, utm_medium, utm_campaign, utm_content'],
],
'columns' => [
'sys_language_uid' => [
'exclude' => 0,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.language',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'foreign_table' => 'sys_language',
'foreign_table_where' => 'ORDER BY sys_language.title',
'items' => [
['LLL:EXT:lang/locallang_general.xlf:LGL.allLanguages', -1],
['LLL:EXT:lang/locallang_general.xlf:LGL.default_value', 0]
],
],
],
'l10n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0',
'exclude' => 0,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.l18n_parent',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [
['', 0],
],
'foreign_table' => 'tx_eptrack_domain_model_redirect',
'foreign_table_where' => 'AND tx_eptrack_domain_model_redirect.pid=###CURRENT_PID### AND tx_eptrack_domain_model_redirect.sys_language_uid IN (-1,0)',
],
],
'l10n_diffsource' => [
'config' => [
'type' => 'passthrough',
],
],
't3ver_label' => [
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.versionLabel',
'config' => [
'type' => 'input',
'size' => 30,
'max' => 255,
]
],
'hidden' => [
'exclude' => 0,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.hidden',
'config' => [
'type' => 'check',
],
],
'starttime' => [
'exclude' => 0,
'l10n_mode' => 'mergeIfNotBlank',
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.starttime',
'config' => [
'type' => 'input',
'size' => 13,
'max' => 20,
'eval' => 'datetime',
'checkbox' => 0,
'default' => 0,
'range' => [
'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y'))
],
],
],
'endtime' => [
'exclude' => 0,
'l10n_mode' => 'mergeIfNotBlank',
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.endtime',
'config' => [
'type' => 'input',
'size' => 13,
'max' => 20,
'eval' => 'datetime',
'checkbox' => 0,
'default' => 0,
'range' => [
'lower' => mktime(0, 0, 0, date('m'), date('d'), date('Y'))
],
],
],
'sorting' => [
'config' => [
'type' => 'passthrough'
],
],
'label' => [
'exclude' => 0,
'label' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect.label',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim,required'
],
],
'redirect_from' => [
'exclude' => 0,
'label' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect.redirect_from',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim,required,unique,alphanum_x,lower'
],
],
'target_page_uid' => [
'exclude' => 0,
'label' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect.target_page_uid',
'config' => [
'type' => 'input',
'size' => 4,
'eval' => 'int,required',
'wizards' => [
'link' => [
'type' => 'popup',
'title' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:header_link_formlabel',
'icon' => 'EXT:backend/Resources/Public/Images/FormFieldWizard/wizard_link.gif',
'module' => [
'name' => 'wizard_link',
'urlParameters' => [
'mode' => 'wizard'
]
],
'JSopenParams' => 'height=300,width=500,status=0,menubar=0,scrollbars=1'
]
],
],
],
'tracking_code' => [
'exclude' => 0,
'label' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect.tracking_code',
'config' => [
'type' => 'input',
'size' => 16,
'eval' => 'trim',
],
],
'response_code' => [
'exclude' => 0,
'label' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect.response_code',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'default' => '301',
'items' => [
['301', '301'],
['302', '302'],
],
],
],
'utm_source' => [
'exclude' => 0,
'label' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect.utm_source',
'config' => [
'type' => 'input',
'size' => 16,
'eval' => 'trim',
],
],
'utm_medium' => [
'exclude' => 0,
'label' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect.utm_medium',
'config' => [
'type' => 'input',
'size' => 16,
'eval' => 'trim',
],
],
'utm_campaign' => [
'exclude' => 0,
'label' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect.utm_campaign',
'config' => [
'type' => 'input',
'size' => 16,
'eval' => 'trim',
],
],
'utm_content' => [
'exclude' => 0,
'label' => 'LLL:EXT:ep_track/Resources/Private/Language/locallang.xlf:tx_eptrack_domain_model_redirect.utm_content',
'config' => [
'type' => 'input',
'size' => 16,
'eval' => 'trim',
],
],
]
];
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<xliff version="1.0">
<file source-language="de" datatype="plaintext" original="messages" date="2016-05-04T20:49:43Z" product-name="ep_track">
<header/>
<body>
<trans-unit id="tx_eptrack_domain_model_redirect">
<source>Redirect</source>
</trans-unit>
<trans-unit id="tx_eptrack_domain_model_redirect.label">
<source>Bezeichnung</source>
</trans-unit>
<trans-unit id="tx_eptrack_domain_model_redirect.redirect_from">
<source>Redirect URL</source>
</trans-unit>
<trans-unit id="tx_eptrack_domain_model_redirect.target_page_uid">
<source>Zielseite</source>
</trans-unit>
<trans-unit id="tx_eptrack_domain_model_redirect.tracking_code">
<source>Trackingcode</source>
</trans-unit>
<trans-unit id="tx_eptrack_domain_model_redirect.response_code">
<source>Typ</source>
</trans-unit>
<trans-unit id="tx_eptrack_domain_model_redirect.utm_source">
<source>Source</source>
</trans-unit>
<trans-unit id="tx_eptrack_domain_model_redirect.utm_medium">
<source>Medium</source>
</trans-unit>
<trans-unit id="tx_eptrack_domain_model_redirect.utm_campaign">
<source>Campaign</source>
</trans-unit>
<trans-unit id="tx_eptrack_domain_model_redirect.utm_content">
<source>Content</source>
</trans-unit>
</body>
</file>
</xliff>
Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

@@ -0,0 +1,88 @@
<?php
namespace EP\EpTrack\Tests\Unit\Userfunction;
/***************************************************************
*
* Copyright notice
*
* (c) 2016 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project 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 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script 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.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use EP\EpTrack\Userfunction\RequestHandler;
use TYPO3\CMS\Core\Tests\UnitTestCase;
class RequestHandlerTest extends UnitTestCase
{
public function testIgnoresUrlsWithoutTrackingCode()
{
$handler = $this->getHandlerInstance();
$uriWithoutTrackingCode = '/foo/bar?baz=1234';
$redirect = $handler->parseUri($uriWithoutTrackingCode);
$this->assertNull($redirect);
}
public function testRedirectsWithValidTrackingCodeInUrl()
{
$handler = $this->getHandlerInstance();
$uriWithoutTrackingCode = '/foo/bar/abcd1234foo';
$redirect = $handler->parseUri($uriWithoutTrackingCode);
$this->assertEquals($redirect, '/foo/bar/');
$uriWithoutTrackingCode = '/abcd1234foo';
$redirect = $handler->parseUri($uriWithoutTrackingCode);
$this->assertEquals($redirect, '/');
}
public function testRedirectsWithValidTrackingCodeInQueryString()
{
$handler = $this->getHandlerInstance();
$uriWithoutTrackingCode = '/foo/bar/?trk=abcd1234foo';
$redirect = $handler->parseUri($uriWithoutTrackingCode);
$this->assertEquals($redirect, '/foo/bar/');
}
public function testRedirectsWithValidTrackingCodeInUrlAndAdditionalQueryString()
{
$handler = $this->getHandlerInstance();
$uriWithoutTrackingCode = '/foo/bar/abcd1234foo?bar=baz';
$redirect = $handler->parseUri($uriWithoutTrackingCode);
$this->assertEquals($redirect, '/foo/bar/?bar=baz');
}
/**
* @return RequestHandler
*/
protected function getHandlerInstance()
{
$handler = new RequestHandler();
$cookieUtility = $this->getMockObjectGenerator()->getMock('\EP\EpTrack\Utility\CookieUtility');
$cookieUtility->expects($this->any())->method('setCookie');
$this->inject($handler, 'cookieUtility', $cookieUtility);
return $handler;
}
}
@@ -0,0 +1,11 @@
<?php
// Register composer autoloader
if (!file_exists(__DIR__ . '/../vendor/autoload.php')) {
throw new \RuntimeException(
'Could not find vendor/autoload.php, make sure you ran composer.'
);
}
/** @var Composer\Autoload\ClassLoader $autoloader */
$autoloader = require __DIR__ . '/../vendor/autoload.php';
$autoloader->addPsr4('EP\\EpTrack\\Tests\\Unit\\', __DIR__ . '/Unit/');
+23
View File
@@ -0,0 +1,23 @@
<?php
$EM_CONF[$_EXTKEY] = [
'title' => 'E&P Tracking',
'description' => '',
'category' => 'plugin',
'author' => 'Björn Fromme',
'author_email' => '[email protected]',
'state' => 'beta',
'author_company' => 'dreipunktnull',
'version' => '1.1.0',
'constraints' => [
'depends' => [
'ods_redirects' => '*',
'realurl' => '*'
],
],
'autoload' => [
'psr-4' => [
'EP\\EpTrack\\' => 'Classes/',
]
]
];
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,9 @@
<?php
if (!is_array($TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'])) {
$TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'] = [];
}
array_unshift($TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['checkAlternativeIdMethods-PostProc'], 'EP\EpTrack\Service\RequestService->handleRequest');
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][$_EXTKEY] =
\EP\EpTrack\Hook\Tcemain::class;
+49
View File
@@ -0,0 +1,49 @@
# noinspection SqlNoDataSourceInspectionForFile
#
# Table structure for table 'tx_eptrack_domain_model_redirect'
#
CREATE TABLE tx_eptrack_domain_model_redirect (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
label varchar(255) DEFAULT '' NOT NULL,
redirect_from varchar(255) DEFAULT '' NOT NULL,
target_page_uid int(11) unsigned DEFAULT '0' NOT NULL,
tracking_code varchar(255) DEFAULT '' NOT NULL,
response_code char(3) DEFAULT '301' NOT NULL,
utm_source varchar(255) DEFAULT '' NOT NULL,
utm_medium varchar(255) DEFAULT '' NOT NULL,
utm_campaign varchar(255) DEFAULT '' NOT NULL,
utm_content varchar(255) DEFAULT '' NOT NULL,
tstamp int(11) unsigned DEFAULT '0' NOT NULL,
crdate int(11) unsigned DEFAULT '0' NOT NULL,
cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
deleted tinyint(4) unsigned DEFAULT '0' NOT NULL,
hidden tinyint(4) unsigned DEFAULT '0' NOT NULL,
starttime int(11) unsigned DEFAULT '0' NOT NULL,
endtime int(11) unsigned DEFAULT '0' NOT NULL,
t3ver_oid int(11) DEFAULT '0' NOT NULL,
t3ver_id int(11) DEFAULT '0' NOT NULL,
t3ver_wsid int(11) DEFAULT '0' NOT NULL,
t3ver_label varchar(255) DEFAULT '' NOT NULL,
t3ver_state tinyint(4) DEFAULT '0' NOT NULL,
t3ver_stage int(11) DEFAULT '0' NOT NULL,
t3ver_count int(11) DEFAULT '0' NOT NULL,
t3ver_tstamp int(11) DEFAULT '0' NOT NULL,
t3ver_move_id int(11) DEFAULT '0' NOT NULL,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
PRIMARY KEY (uid),
KEY parent (pid),
KEY redirectfrom (redirect_from),
KEY t3ver_oid (t3ver_oid,t3ver_wsid),
KEY language (l10n_parent,sys_language_uid)
);
@@ -0,0 +1,25 @@
<phpunit bootstrap="./Tests/bootstrap.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="false"
convertErrorsToExceptions="true"
convertWarningsToExceptions="true"
forceCoversAnnotation="false"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
verbose="false">
<testsuites>
<testsuite name="unit">
<directory>Tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>Classes</directory>
</whitelist>
</filter>
</phpunit>