Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\PageBundle\Tests\Model;
|
||||
|
||||
use Mautic\CoreBundle\Entity\IpAddress;
|
||||
use Mautic\CoreBundle\Helper\ClickthroughHelper;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\PageBundle\Entity\Hit;
|
||||
use Mautic\PageBundle\Entity\Page;
|
||||
use Mautic\PageBundle\Entity\Redirect;
|
||||
use Mautic\PageBundle\Tests\PageTestAbstract;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class PageModelTest extends PageTestAbstract
|
||||
{
|
||||
public function testUtf8CharsInTitleWithTransletirationEnabled(): void
|
||||
{
|
||||
$providedTitle = '你好,世界';
|
||||
$expectedTitle = 'ni hao, shi jie';
|
||||
$hit = new Hit();
|
||||
$page = new Page();
|
||||
$request = new Request();
|
||||
$contact = new Lead();
|
||||
$pageModel = $this->getPageModel();
|
||||
|
||||
$hit->setIpAddress(new IpAddress());
|
||||
$hit->setQuery(['page_title' => $providedTitle]);
|
||||
|
||||
$pageModel->processPageHit($hit, $page, $request, $contact, false);
|
||||
|
||||
$this->assertSame($expectedTitle, $hit->getUrlTitle());
|
||||
$this->assertSame(['page_title' => $expectedTitle], $hit->getQuery());
|
||||
}
|
||||
|
||||
public function testUtf8CharsInTitleWithTransletirationDisabled(): void
|
||||
{
|
||||
$providedTitle = '你好,世界';
|
||||
$expectedTitle = '你好,世界';
|
||||
$hit = new Hit();
|
||||
$page = new Page();
|
||||
$request = new Request();
|
||||
$contact = new Lead();
|
||||
$pageModel = $this->getPageModel(false);
|
||||
|
||||
$hit->setIpAddress(new IpAddress());
|
||||
$hit->setQuery(['page_title' => $providedTitle]);
|
||||
|
||||
$pageModel->processPageHit($hit, $page, $request, $contact, false);
|
||||
|
||||
$this->assertSame($expectedTitle, $hit->getUrlTitle());
|
||||
$this->assertSame(['page_title' => $expectedTitle], $hit->getQuery());
|
||||
}
|
||||
|
||||
public function testGenerateUrlWhenCalledReturnsValidUrl(): void
|
||||
{
|
||||
$page = new Page();
|
||||
$page->setAlias('this-is-a-test');
|
||||
$pageModel = $this->getPageModel();
|
||||
|
||||
$this->router->expects($this->once())
|
||||
->method('generate')
|
||||
->willReturnCallback(
|
||||
function (string $route, array $routeParams, int $referenceType) {
|
||||
$this->assertSame('mautic_page_public', $route);
|
||||
$this->assertSame(['slug' => 'this-is-a-test'], $routeParams);
|
||||
$this->assertSame(0, $referenceType);
|
||||
|
||||
return '/'.$routeParams['slug'];
|
||||
}
|
||||
);
|
||||
|
||||
$url = $pageModel->generateUrl($page);
|
||||
$this->assertStringContainsString('/this-is-a-test', $url);
|
||||
}
|
||||
|
||||
public function testUrlTitleFallbacksToPageTitleWhenNotInQuery(): void
|
||||
{
|
||||
$providedTitle = '你好,世界';
|
||||
$expectedTitle = 'ni hao, shi jie';
|
||||
$hit = new Hit();
|
||||
$page = new Page();
|
||||
$request = new Request();
|
||||
$contact = new Lead();
|
||||
$ipAddress = new IpAddress();
|
||||
$pageModel = $this->getPageModel();
|
||||
|
||||
$page->setTitle($providedTitle);
|
||||
$hit->setIpAddress($ipAddress);
|
||||
$hit->setQuery([]);
|
||||
|
||||
$pageModel->processPageHit($hit, $page, $request, $contact, false);
|
||||
|
||||
$this->assertSame($expectedTitle, $hit->getUrlTitle());
|
||||
}
|
||||
|
||||
public function testCleanQueryWhenCalledReturnsSafeAndValidData(): void
|
||||
{
|
||||
$pageModel = $this->getPageModel();
|
||||
$pageModelReflection = new \ReflectionClass($pageModel::class);
|
||||
$cleanQueryMethod = $pageModelReflection->getMethod('cleanQuery');
|
||||
$cleanQueryMethod->setAccessible(true);
|
||||
$res = $cleanQueryMethod->invokeArgs($pageModel, [
|
||||
[
|
||||
'page_title' => 'Mautic & PHP',
|
||||
'page_url' => 'http://mautic.com/page/test?hello=world&lorem=ipsum&q=this%20has%20spaces',
|
||||
'page_language' => 'en',
|
||||
],
|
||||
]);
|
||||
$this->assertEquals($res, [
|
||||
'page_title' => 'Mautic & PHP',
|
||||
'page_url' => 'http://mautic.com/page/test?hello=world&lorem=ipsum&q=this%20has%20spaces',
|
||||
'page_language' => 'en',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getHitQuery when the hit is a Request
|
||||
* (e.g. POST Ajax or Landingpage hit).
|
||||
*/
|
||||
public function testGetHitQueryRequest(): void
|
||||
{
|
||||
$pageModel = $this->getPageModel();
|
||||
|
||||
foreach ($this->getQueryParams() as $params) {
|
||||
$request = new Request($params);
|
||||
|
||||
$query = $pageModel->getHitQuery($request);
|
||||
$this->assertUtmQuery($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getHitQuery when the hit is a Redirect.
|
||||
*/
|
||||
public function testGetHitQueryRedirect(): void
|
||||
{
|
||||
$pageModel = $this->getPageModel();
|
||||
$request = new Request();
|
||||
$redirect = new Redirect();
|
||||
|
||||
foreach ($this->getQueryParams() as $params) {
|
||||
$redirect->setUrl($params['page_url']);
|
||||
$query = $pageModel->getHitQuery($request, $redirect);
|
||||
$this->assertUtmQuery($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This test is somewhat synthetic to test the missing $query['ct'].
|
||||
*/
|
||||
public function testNoClickThroughInQuery(): void
|
||||
{
|
||||
$redirectUrl = '/somewhat';
|
||||
$pageModel = $this->getPageModel();
|
||||
|
||||
$ipAddress = $this->createMock(IpAddress::class);
|
||||
$ipAddress->method('isTrackable')->willReturn(true);
|
||||
|
||||
$this->security->method('isAnonymous')->willReturn(true);
|
||||
$this->ipLookupHelper->method('getIpAddress')->willReturn($ipAddress);
|
||||
$this->companyModel->method('fetchCompanyFields')->willReturn([]);
|
||||
|
||||
$redirect = $this->createMock(Redirect::class);
|
||||
$redirect->method('getUrl')->willReturn($redirectUrl);
|
||||
|
||||
$this->contactRequestHelper->expects($this->once())
|
||||
->method('getContactFromQuery')
|
||||
->with(['page_url' => $redirectUrl])
|
||||
->willReturn(null);
|
||||
|
||||
$result = $pageModel->hitPage($redirect, new Request());
|
||||
self::assertFalse($result);
|
||||
}
|
||||
|
||||
private function assertUtmQuery(array $query): void
|
||||
{
|
||||
$this->assertArrayHasKey('utm_source', $query, 'utm_source not found');
|
||||
$this->assertArrayHasKey('utm_medium', $query, 'utm_medium not found');
|
||||
$this->assertArrayHasKey('utm_campaign', $query, 'utm_campaign not found');
|
||||
$this->assertArrayHasKey('utm_content', $query, 'utm_content not found');
|
||||
// evaluate all utm tags that they contain the key name in the value
|
||||
foreach ($query as $key => $value) {
|
||||
if (str_contains($key, 'utm_')) {
|
||||
$this->assertNotFalse(strpos($value, (string) $key), sprintf('%s not found in %s', $key, $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getQueryParams(): array
|
||||
{
|
||||
$utm = [
|
||||
'utm_source' => 'test-utm_source',
|
||||
'utm_medium' => 'test-utm_medium',
|
||||
'utm_campaign'=> 'test-utm_campaign',
|
||||
'utm_content' => 'test-utm_content',
|
||||
];
|
||||
$querystring = '';
|
||||
foreach ($utm as $key => $value) {
|
||||
$querystring .= sprintf('&%s=%s', $key, $value);
|
||||
}
|
||||
|
||||
$ctParams = [
|
||||
'source' => ['email', '4'],
|
||||
'email' => 4,
|
||||
'stat' => '5f5dedc3b0dc0366144010',
|
||||
'lead' => 2,
|
||||
'channel' => [
|
||||
'email' => 4,
|
||||
],
|
||||
];
|
||||
$ct = ClickthroughHelper::encodeArrayForUrl($ctParams);
|
||||
|
||||
$params = [[
|
||||
'page_title' => 'Testpage',
|
||||
'page_language' => 'en-GB',
|
||||
'page_referrer' => '',
|
||||
'page_url' => sprintf('https://www.domain.com/testpage/?%s', $querystring),
|
||||
'counter' => 0,
|
||||
'mautic_device_id'=> 'nowvkqdf6113236eokcg7qs',
|
||||
'resolution' => '1792x1120',
|
||||
'timezone_offset' => -120,
|
||||
'platform' => 'MacIntel',
|
||||
'do_not_track' => 1,
|
||||
'adblock' => false,
|
||||
'fingerprint' => 'fec25ab2d659c4153c7f1d5724841132',
|
||||
], [
|
||||
'page_title' => 'Testpage Special Chars',
|
||||
'page_language' => 'en-GB',
|
||||
'page_referrer' => '',
|
||||
'page_url' => 'https://www.domain.com/testpage/?utm_source=t%C3%A9%C3%A0%C3%A8st-utm_source&utm_medium=t%C3%A4%C3%B6ust-utm_medium&utm_campaign=te+%20%C2%B0st-utm_campaign&utm_content=t%E4%BD%A0%E5%A5%BDt-utm_content',
|
||||
'counter' => 0,
|
||||
'mautic_device_id'=> 'nowvkqdf6113236eokcg7qs',
|
||||
'resolution' => '1792x1120',
|
||||
'timezone_offset' => -120,
|
||||
'platform' => 'MacIntel',
|
||||
'do_not_track' => 1,
|
||||
'adblock' => false,
|
||||
'fingerprint' => 'fec25ab2d659c4153c7f1d5724841132',
|
||||
], [
|
||||
'page_title' => 'Testpage With Encoded Params',
|
||||
'page_language' => 'en-GB',
|
||||
'page_referrer' => '',
|
||||
'page_url' => sprintf('https://www.domain.com/testpage/?ct=%s&%s', $ct, $querystring),
|
||||
'counter' => 0,
|
||||
'mautic_device_id'=> 'nowvkqdf6113236eokcg7qs',
|
||||
'resolution' => '1792x1120',
|
||||
'timezone_offset' => -120,
|
||||
'platform' => 'MacIntel',
|
||||
'do_not_track' => 1,
|
||||
'adblock' => false,
|
||||
'fingerprint' => 'fec25ab2d659c4153c7f1d5724841132',
|
||||
]];
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PageBundle\Tests\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Shortener\Shortener;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\PageBundle\Entity\Redirect;
|
||||
use Mautic\PageBundle\Event\RedirectGenerationEvent;
|
||||
use Mautic\PageBundle\Model\RedirectModel;
|
||||
use Mautic\PageBundle\PageEvents;
|
||||
use Mautic\PageBundle\Tests\PageTestAbstract;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Routing\Router;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
class RedirectModelTest extends PageTestAbstract
|
||||
{
|
||||
public function testCreateRedirectEntityWhenCalledReturnsRedirect(): void
|
||||
{
|
||||
$redirectModel = $this->getRedirectModel();
|
||||
$entity = $redirectModel->createRedirectEntity('http://some-url.com');
|
||||
|
||||
$this->assertInstanceOf(Redirect::class, $entity);
|
||||
}
|
||||
|
||||
public function testGenerateRedirectUrlWhenCalledReturnsValidUrl(): void
|
||||
{
|
||||
$redirect = new Redirect();
|
||||
$redirect->setUrl('http://some-url.com');
|
||||
$redirect->setRedirectId('redirect-id');
|
||||
|
||||
$redirectModel = $this->getRedirectModel();
|
||||
$url = $redirectModel->generateRedirectUrl($redirect);
|
||||
|
||||
$this->assertStringContainsString($url, 'http://some-url.com');
|
||||
}
|
||||
|
||||
public function testRedirectGenerationEvent(): void
|
||||
{
|
||||
$shortener = $this->createMock(Shortener::class);
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
|
||||
$url = 'https://mautic.org';
|
||||
$clickthrough = ['foo' => 'bar'];
|
||||
|
||||
$router = $this->createMock(Router::class);
|
||||
$router->expects($this->exactly(2))
|
||||
->method('generate')
|
||||
->willReturn($url);
|
||||
|
||||
$model = new RedirectModel(
|
||||
$this->createMock(EntityManagerInterface::class),
|
||||
$this->createMock(CorePermissions::class),
|
||||
$dispatcher,
|
||||
$router,
|
||||
$this->createMock(Translator::class),
|
||||
$this->createMock(UserHelper::class),
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$this->createMock(CoreParametersHelper::class),
|
||||
$shortener
|
||||
);
|
||||
|
||||
$redirect = new Redirect();
|
||||
$redirect->setUrl($url);
|
||||
|
||||
// URL should just have foo = bar in the CT
|
||||
$url = $model->generateRedirectUrl($redirect, $clickthrough);
|
||||
$this->assertEquals('https://mautic.org?ct=YToxOntzOjM6ImZvbyI7czozOiJiYXIiO30%3D', $url);
|
||||
|
||||
// Add the listener to append something else to the CT
|
||||
$dispatcher->addListener(
|
||||
PageEvents::ON_REDIRECT_GENERATE,
|
||||
function (RedirectGenerationEvent $event): void {
|
||||
$event->setInClickthrough('bar', 'foo');
|
||||
}
|
||||
);
|
||||
$url = $model->generateRedirectUrl($redirect, $clickthrough);
|
||||
$this->assertEquals('https://mautic.org?ct=YToyOntzOjM6ImZvbyI7czozOiJiYXIiO3M6MzoiYmFyIjtzOjM6ImZvbyI7fQ%3D%3D', $url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,688 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PageBundle\Tests\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\LeadFieldRepository;
|
||||
use Mautic\PageBundle\Entity\Redirect;
|
||||
use Mautic\PageBundle\Entity\Trackable;
|
||||
use Mautic\PageBundle\Model\RedirectModel;
|
||||
use Mautic\PageBundle\Model\TrackableModel;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(TrackableModel::class)]
|
||||
class TrackableModelTest extends TestCase
|
||||
{
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that content is detected as HTML')]
|
||||
public function testHtmlIsDetectedInContent(): void
|
||||
{
|
||||
$mockRedirectModel = $this->createMock(RedirectModel::class);
|
||||
$mockLeadFieldRepository = $this->createMock(LeadFieldRepository::class);
|
||||
|
||||
$mockModel = $this->getMockBuilder(TrackableModel::class)
|
||||
->setConstructorArgs([
|
||||
$mockRedirectModel,
|
||||
$mockLeadFieldRepository,
|
||||
$this->createMock(EntityManagerInterface::class),
|
||||
$this->createMock(CorePermissions::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(UrlGeneratorInterface::class),
|
||||
$this->createMock(Translator::class),
|
||||
$this->createMock(UserHelper::class),
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$this->createMock(CoreParametersHelper::class),
|
||||
])
|
||||
->onlyMethods(['getDoNotTrackList', 'getEntitiesFromUrls', 'createTrackingTokens', 'extractTrackablesFromHtml'])
|
||||
->getMock();
|
||||
|
||||
$mockModel->expects($this->once())
|
||||
->method('getEntitiesFromUrls')
|
||||
->willReturn([]);
|
||||
|
||||
$mockModel->expects($this->once())
|
||||
->method('getDoNotTrackList')
|
||||
->willReturn([]);
|
||||
|
||||
$mockModel->expects($this->once())
|
||||
->method('extractTrackablesFromHtml')
|
||||
->willReturn(
|
||||
[
|
||||
'',
|
||||
[],
|
||||
]
|
||||
);
|
||||
|
||||
$mockModel->expects($this->once())
|
||||
->method('createTrackingTokens')
|
||||
->willReturn([]);
|
||||
|
||||
[$content, $trackables] = $mockModel->parseContentForTrackables(
|
||||
$this->generateContent('https://foo-bar.com', 'html'),
|
||||
[],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that content is detected as plain text')]
|
||||
public function testPlainTextIsDetectedInContent(): void
|
||||
{
|
||||
$mockRedirectModel = $this->createMock(RedirectModel::class);
|
||||
$mockLeadFieldRepository = $this->createMock(LeadFieldRepository::class);
|
||||
|
||||
$mockModel = $this->getMockBuilder(TrackableModel::class)
|
||||
->setConstructorArgs([
|
||||
$mockRedirectModel,
|
||||
$mockLeadFieldRepository,
|
||||
$this->createMock(EntityManagerInterface::class),
|
||||
$this->createMock(CorePermissions::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(UrlGeneratorInterface::class),
|
||||
$this->createMock(Translator::class),
|
||||
$this->createMock(UserHelper::class),
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$this->createMock(CoreParametersHelper::class),
|
||||
])
|
||||
->onlyMethods(['getDoNotTrackList', 'getEntitiesFromUrls', 'createTrackingTokens', 'extractTrackablesFromText'])
|
||||
->getMock();
|
||||
|
||||
$mockModel->expects($this->once())
|
||||
->method('getDoNotTrackList')
|
||||
->willReturn([]);
|
||||
|
||||
$mockModel->expects($this->once())
|
||||
->method('getEntitiesFromUrls')
|
||||
->willReturn([]);
|
||||
|
||||
$mockModel->expects($this->once())
|
||||
->method('extractTrackablesFromText')
|
||||
->willReturn(
|
||||
[
|
||||
'',
|
||||
[],
|
||||
]
|
||||
);
|
||||
|
||||
$mockModel->expects($this->once())
|
||||
->method('createTrackingTokens')
|
||||
->willReturn([]);
|
||||
|
||||
[$content, $trackables] = $mockModel->parseContentForTrackables(
|
||||
$this->generateContent('https://foo-bar.com', 'text'),
|
||||
[],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('trackMapProvider')]
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that a standard link with a standard query is parsed correctly')]
|
||||
public function testStandardLinkWithStandardQuery(?bool $useMap): void
|
||||
{
|
||||
$url = 'https://foo-bar.com?foo=bar&one=two&three=four&five=six';
|
||||
$model = $this->getModel();
|
||||
|
||||
if (null !== $useMap) {
|
||||
$emailContent = $this->generateContent($url, 'html', false, $useMap);
|
||||
} else {
|
||||
$emailContent = $this->generateContent($url, 'html', false, true)
|
||||
.$this->generateContent($url, 'html', false, false);
|
||||
}
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$emailContent,
|
||||
[],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$tokenFound = preg_match('/\{trackable=(.*?)\}/', $content, $match);
|
||||
|
||||
// Assert that a trackable token exists
|
||||
Assert::assertTrue((bool) $tokenFound, $content);
|
||||
|
||||
// Assert the Trackable exists
|
||||
Assert::assertArrayHasKey($match[0], $trackables);
|
||||
|
||||
// Assert that exactly one trackable found
|
||||
Assert::assertCount(1, $trackables);
|
||||
|
||||
// Assert that the URL redirect equals $url
|
||||
$redirect = $trackables[$match[0]]->getRedirect();
|
||||
Assert::assertEquals(str_replace('&', '&', $url), $redirect->getUrl());
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('trackMapProvider')]
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that a standard link without a query parses correctly')]
|
||||
public function testStandardLinkWithoutQuery(?bool $useMap): void
|
||||
{
|
||||
$url = 'https://foo-bar.com';
|
||||
$model = $this->getModel();
|
||||
|
||||
if (null !== $useMap) {
|
||||
$emailContent = $this->generateContent($url, 'html', false, $useMap);
|
||||
} else {
|
||||
$emailContent = $this->generateContent($url, 'html', false, true)
|
||||
.$this->generateContent($url, 'html', false, false);
|
||||
}
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$emailContent,
|
||||
[],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$tokenFound = preg_match('/\{trackable=(.*?)\}/', $content, $match);
|
||||
|
||||
// Assert that a trackable token exists
|
||||
Assert::assertTrue((bool) $tokenFound, $content);
|
||||
|
||||
// Assert the Trackable exists
|
||||
Assert::assertArrayHasKey($match[0], $trackables);
|
||||
|
||||
// Assert that exactly one trackable found
|
||||
Assert::assertCount(1, $trackables);
|
||||
|
||||
// Assert that the URL redirect equals $url
|
||||
$redirect = $trackables[$match[0]]->getRedirect();
|
||||
Assert::assertEquals($url, $redirect->getUrl());
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('trackMapProvider')]
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that a standard link with a tokenized query parses correctly')]
|
||||
public function testStandardLinkWithTokenizedQuery(?bool $useMap): void
|
||||
{
|
||||
$url = 'https://foo-bar.com?foo={contactfield=bar}&bar=foo';
|
||||
$model = $this->getModel();
|
||||
|
||||
if (null !== $useMap) {
|
||||
$emailContent = $this->generateContent($url, 'html', false, $useMap);
|
||||
} else {
|
||||
$emailContent = $this->generateContent($url, 'html', false, true)
|
||||
.$this->generateContent($url, 'html', false, false);
|
||||
}
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$emailContent,
|
||||
[
|
||||
'{contactfield=bar}' => '',
|
||||
],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$tokenFound = preg_match('/\{trackable=(.*?)\}/', $content, $match);
|
||||
|
||||
// Assert that a trackable token exists
|
||||
Assert::assertTrue((bool) $tokenFound, $content);
|
||||
|
||||
// Assert that exactly one trackable found
|
||||
Assert::assertCount(1, $trackables);
|
||||
|
||||
// Assert the Trackable exists
|
||||
Assert::assertArrayHasKey('{trackable='.$match[1].'}', $trackables);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that a token used in place of a URL is parsed properly')]
|
||||
public function testTokenizedDomain(): void
|
||||
{
|
||||
$url = 'http://{contactfield=foo}.org';
|
||||
$model = $this->getModel();
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$this->generateContent($url, 'html'),
|
||||
[
|
||||
'{contactfield=foo}' => 'mautic',
|
||||
],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$tokenFound = preg_match('/\{trackable=(.*?)\}/', $content, $match);
|
||||
|
||||
// Assert that a trackable token exists
|
||||
$this->assertTrue((bool) $tokenFound, $content);
|
||||
|
||||
// Assert the Trackable exists
|
||||
$this->assertArrayHasKey('{trackable='.$match[1].'}', $trackables);
|
||||
}
|
||||
|
||||
public function testTokenizedHostWithScheme(): void
|
||||
{
|
||||
$url = '{contactfield=foo}';
|
||||
$model = $this->getModel();
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$this->generateContent($url, 'html'),
|
||||
[
|
||||
'{contactfield=foo}' => 'https://mautic.org',
|
||||
],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$tokenFound = preg_match('/\{trackable=(.*?)\}/', $content, $match);
|
||||
|
||||
// Assert that a trackable token exists
|
||||
$this->assertTrue((bool) $tokenFound, $content);
|
||||
|
||||
// Assert the Trackable exists
|
||||
$this->assertArrayHasKey('{trackable='.$match[1].'}', $trackables);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that a token used in place of a URL is parsed')]
|
||||
public function testTokenizedHostWithQuery(): void
|
||||
{
|
||||
$url = 'http://{contactfield=foo}.com?foo=bar';
|
||||
$model = $this->getModel();
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$this->generateContent($url, 'html'),
|
||||
[
|
||||
'{contactfield=foo}' => '',
|
||||
],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$tokenFound = preg_match('/\{trackable=(.*?)\}/', $content, $match);
|
||||
|
||||
// Assert that a trackable token exists
|
||||
$this->assertTrue((bool) $tokenFound, $content);
|
||||
|
||||
// Assert the Trackable exists
|
||||
$this->assertArrayHasKey('{trackable='.$match[1].'}', $trackables);
|
||||
}
|
||||
|
||||
public function testTokenizedHostWithTokenizedQuery(): void
|
||||
{
|
||||
$url = 'http://{contactfield=foo}.com?foo={contactfield=bar}';
|
||||
$model = $this->getModel();
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$this->generateContent($url, 'html'),
|
||||
[
|
||||
'{contactfield=foo}' => '',
|
||||
'{contactfield=bar}' => '',
|
||||
],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$tokenFound = preg_match('/\{trackable=(.*?)\}/', $content, $match);
|
||||
|
||||
// Assert that a trackable token exists
|
||||
$this->assertTrue((bool) $tokenFound, $content);
|
||||
|
||||
// Assert the Trackable exists
|
||||
$this->assertArrayHasKey('{trackable='.$match[1].'}', $trackables);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that tokens that are supposed to be ignored are')]
|
||||
public function testIgnoredTokensAreNotConverted(): void
|
||||
{
|
||||
$url = 'https://{unsubscribe_url}';
|
||||
$model = $this->getModel(['{unsubscribe_url}']);
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$this->generateContent($url, 'html'),
|
||||
[
|
||||
'{unsubscribe_url}' => 'https://domain.com/email/unsubscribe/xxxxxxx',
|
||||
],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$this->assertEmpty($trackables, $content);
|
||||
$this->assertFalse(strpos($content, $url), 'https:// should have been stripped from the token URL');
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that tokens that are supposed to be ignored are')]
|
||||
public function testUnsupportedTokensAreNotConverted(): void
|
||||
{
|
||||
$url = '{random_token}';
|
||||
$model = $this->getModel();
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$this->generateContent($url, 'text'),
|
||||
[
|
||||
'{unsubscribe_url}' => 'https://domain.com/email/unsubscribe/xxxxxxx',
|
||||
],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$this->assertEmpty($trackables, $content);
|
||||
}
|
||||
|
||||
public function testTokenWithDefaultValueInPlaintextWillCountAsOne(): void
|
||||
{
|
||||
$url = '{contactfield=website|https://mautic.org}';
|
||||
$model = $this->getModel();
|
||||
$inputContent = $this->generateContent($url, 'text');
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$inputContent,
|
||||
[
|
||||
'{contactfield=website}' => 'https://mautic.org/about-us',
|
||||
],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$tokenFound = preg_match('/\{trackable=(.*?)\}/', $content, $match);
|
||||
|
||||
// Assert that a trackable token exists
|
||||
$this->assertTrue((bool) $tokenFound, $content);
|
||||
|
||||
// Assert the Trackable exists
|
||||
$trackableKey = '{trackable='.$match[1].'}';
|
||||
$this->assertArrayHasKey('{trackable='.$match[1].'}', $trackables);
|
||||
|
||||
$this->assertEquals(1, count($trackables));
|
||||
$this->assertEquals('{contactfield=website|https://mautic.org}', $trackables[$trackableKey]->getRedirect()->getUrl());
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that a URL injected into the do not track list is not converted')]
|
||||
public function testIgnoredUrlDoesNotCrash(): void
|
||||
{
|
||||
$url = 'https://domain.com';
|
||||
$model = $this->getModel([$url]);
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$this->generateContent($url, 'html'),
|
||||
[],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$this->assertTrue(str_contains($content, $url), $content);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('trackMapProvider')]
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that a token used in place of a URL is not parsed')]
|
||||
public function testTokenAsHostIsConvertedToTrackableToken(?bool $useMap): void
|
||||
{
|
||||
$url = 'http://{pagelink=1}';
|
||||
$model = $this->getModel();
|
||||
|
||||
if (null !== $useMap) {
|
||||
$emailContent = $this->generateContent($url, 'html', false, $useMap);
|
||||
} else {
|
||||
$emailContent = $this->generateContent($url, 'html', false, true)
|
||||
.$this->generateContent($url, 'html', false, false);
|
||||
}
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$emailContent,
|
||||
[
|
||||
'{pagelink=1}' => 'http://foo-bar.com',
|
||||
],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
$token = array_key_first($trackables);
|
||||
Assert::assertNotEmpty($trackables, $content);
|
||||
Assert::assertStringContainsString($token, $content);
|
||||
|
||||
// Assert that exactly one trackable found
|
||||
Assert::assertCount(1, $trackables);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('trackMapProvider')]
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that a URLs with same base or correctly replaced')]
|
||||
public function testUrlsWithSameBaseAreReplacedCorrectly(?bool $useMap): void
|
||||
{
|
||||
$urls = [
|
||||
'https://foo-bar.com',
|
||||
'https://foo-bar.com?foo=bar',
|
||||
'https://FOO-bar.com/bar',
|
||||
];
|
||||
|
||||
$model = $this->getModel();
|
||||
|
||||
if (null !== $useMap) {
|
||||
$emailContent = $this->generateContent($urls, 'html', false, $useMap);
|
||||
} else {
|
||||
$emailContent = $this->generateContent($urls, 'html', false, true)
|
||||
.$this->generateContent($urls, 'html', false, false);
|
||||
}
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$emailContent,
|
||||
[],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
// Assert that both trackables found
|
||||
Assert::assertCount(3, $trackables);
|
||||
|
||||
foreach ($trackables as $redirectId => $trackable) {
|
||||
// If the shared base was correctly parsed, all generated tokens will be in the content
|
||||
Assert::assertNotFalse(strpos($content, (string) $redirectId), $content);
|
||||
}
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that css images are not converted if there are no links')]
|
||||
public function testCssUrlsAreNotConvertedIfThereAreNoLinks(): void
|
||||
{
|
||||
$model = $this->getModel();
|
||||
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
'<style> .mf-modal { background-image: url(\'https://www.mautic.org/wp-content/uploads/2014/08/iTunesArtwork.png\'); } </style>',
|
||||
[],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$this->assertEmpty($trackables);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Tests that URLs in the plaintext does not contaminate HTML')]
|
||||
public function testPlainTextDoesNotContaminateHtml(): void
|
||||
{
|
||||
$model = $this->getModel();
|
||||
|
||||
$html = <<<TEXT
|
||||
Hi {contactfield=firstname},
|
||||
<br />
|
||||
Come to our office in {contactfield=city}!
|
||||
<br />
|
||||
John Smith<br />
|
||||
VP of Sales<br />
|
||||
https://plaintexttest.io
|
||||
TEXT;
|
||||
|
||||
$plainText = strip_tags($html);
|
||||
|
||||
$combined = [$html, $plainText];
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$combined,
|
||||
[],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$this->assertCount(1, $trackables);
|
||||
|
||||
// No links so no trackables
|
||||
$this->assertEquals($html, $content[0]);
|
||||
$token = array_key_first($trackables);
|
||||
self::assertNotNull($token);
|
||||
|
||||
$this->assertEquals(str_replace('https://plaintexttest.io', $token, $plainText), $content[1]);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Tests that URL based contact fields are found in plain text')]
|
||||
public function testPlainTextFindsUrlContactFields(): void
|
||||
{
|
||||
$model = $this->getModel([], ['website']);
|
||||
|
||||
$html = <<<TEXT
|
||||
Hi {contactfield=firstname},
|
||||
<br />
|
||||
Come to our office in {contactfield=city}!
|
||||
<br />
|
||||
John Smith<br />
|
||||
VP of Sales<br />
|
||||
{contactfield=website}
|
||||
TEXT;
|
||||
|
||||
$plainText = strip_tags($html);
|
||||
|
||||
$combined = [$html, $plainText];
|
||||
[$content, $trackables] = $model->parseContentForTrackables(
|
||||
$combined,
|
||||
[],
|
||||
'email',
|
||||
1
|
||||
);
|
||||
|
||||
$this->assertCount(1, $trackables);
|
||||
|
||||
// No links so no trackables
|
||||
$this->assertEquals($html, $content[0]);
|
||||
$token = array_key_first($trackables);
|
||||
self::assertNotNull($token);
|
||||
|
||||
$this->assertEquals(str_replace('{contactfield=website}', $token, $plainText), $content[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $doNotTrack
|
||||
* @param array $urlFieldsForPlaintext
|
||||
*
|
||||
* @return TrackableModel|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected function getModel($doNotTrack = [], $urlFieldsForPlaintext = [])
|
||||
{
|
||||
// Add default DoNotTrack
|
||||
$doNotTrack = array_merge(
|
||||
$doNotTrack,
|
||||
[
|
||||
'{webview_url}',
|
||||
'{unsubscribe_url}',
|
||||
'{trackable=(.*?)}',
|
||||
]
|
||||
);
|
||||
|
||||
$mockRedirectModel = $this->createMock(RedirectModel::class);
|
||||
$mockLeadFieldRepository = $this->createMock(LeadFieldRepository::class);
|
||||
|
||||
$mockModel = $this->getMockBuilder(TrackableModel::class)
|
||||
->setConstructorArgs([
|
||||
$mockRedirectModel,
|
||||
$mockLeadFieldRepository,
|
||||
$this->createMock(EntityManagerInterface::class),
|
||||
$this->createMock(CorePermissions::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(UrlGeneratorInterface::class),
|
||||
$this->createMock(Translator::class),
|
||||
$this->createMock(UserHelper::class),
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$this->createMock(CoreParametersHelper::class),
|
||||
])
|
||||
->onlyMethods(['getDoNotTrackList', 'getEntitiesFromUrls', 'getContactFieldUrlTokens'])
|
||||
->getMock();
|
||||
|
||||
$mockModel->expects($this->once())
|
||||
->method('getDoNotTrackList')
|
||||
->willReturn($doNotTrack);
|
||||
|
||||
$mockModel->expects($this->any())
|
||||
->method('getEntitiesFromUrls')
|
||||
->willReturnCallback(
|
||||
function ($trackableUrls, $channel, $channelId) {
|
||||
$entities = [];
|
||||
foreach ($trackableUrls as $url) {
|
||||
$entities[$url] = $this->getTrackableEntity($url);
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
);
|
||||
|
||||
$mockModel->expects($this->any())
|
||||
->method('getContactFieldUrlTokens')
|
||||
->willReturn($urlFieldsForPlaintext);
|
||||
|
||||
return $mockModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Trackable
|
||||
*/
|
||||
protected function getTrackableEntity($url)
|
||||
{
|
||||
$redirect = new Redirect();
|
||||
$redirect->setUrl($url);
|
||||
$redirect->setRedirectId();
|
||||
|
||||
$trackable = new Trackable();
|
||||
$trackable->setChannel('email')
|
||||
->setChannelId(1)
|
||||
->setRedirect($redirect)
|
||||
->setHits(random_int(1, 10))
|
||||
->setUniqueHits(random_int(1, 10));
|
||||
|
||||
return $trackable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string>|string $urls
|
||||
*/
|
||||
protected function generateContent($urls, string $type, bool $doNotTrack = false, bool $useMap = false): string
|
||||
{
|
||||
$content = '';
|
||||
if (!is_array($urls)) {
|
||||
$urls = [$urls];
|
||||
}
|
||||
|
||||
foreach ($urls as $url) {
|
||||
if ('html' === $type) {
|
||||
$dnc = ($doNotTrack) ? ' mautic:disable-tracking' : '';
|
||||
|
||||
if ($useMap) {
|
||||
$content .= <<<CONTENT
|
||||
ABC123 321ABC
|
||||
ABC123 <map><area href="$url"$dnc alt="alt" /></map> 321ABC
|
||||
CONTENT;
|
||||
} else {
|
||||
$content .= <<<CONTENT
|
||||
ABC123 321ABC
|
||||
ABC123 <a href="$url"$dnc>$url</a> 321ABC
|
||||
CONTENT;
|
||||
}
|
||||
} else {
|
||||
$content .= <<<CONTENT
|
||||
ABC123 321ABC
|
||||
ABC123 $url 321ABC
|
||||
CONTENT;
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array<bool|null>> Use null to include both <a> and <map> tags
|
||||
*/
|
||||
public static function trackMapProvider(): array
|
||||
{
|
||||
return [
|
||||
[true],
|
||||
[false],
|
||||
[null],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\PageBundle\Tests\Model;
|
||||
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Tracker\ContactTracker;
|
||||
use Mautic\PageBundle\Model\PageModel;
|
||||
use Mautic\PageBundle\Model\Tracking404Model;
|
||||
|
||||
class Tracking404ModelTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var ContactTracker|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $mockContactTracker;
|
||||
|
||||
/**
|
||||
* @var CoreParametersHelper|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $mockCoreParametersHelper;
|
||||
|
||||
/**
|
||||
* @var PageModel|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $mockPageModel;
|
||||
|
||||
private Lead $lead;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->mockCoreParametersHelper = $this->createMock(CoreParametersHelper::class);
|
||||
|
||||
$this->mockContactTracker = $this->createMock(ContactTracker::class);
|
||||
|
||||
$this->mockPageModel = $this->createMock(PageModel::class);
|
||||
|
||||
$this->lead = new Lead();
|
||||
}
|
||||
|
||||
public function testIsTrackableIfTracking404OptionEnabled(): void
|
||||
{
|
||||
$this->mockCoreParametersHelper->expects($this->once())
|
||||
->method('get')
|
||||
->with('do_not_track_404_anonymous')
|
||||
->willReturn(true);
|
||||
|
||||
$tracking404Model = new Tracking404Model($this->mockCoreParametersHelper, $this->mockContactTracker, $this->mockPageModel);
|
||||
$this->assertFalse($tracking404Model->isTrackable());
|
||||
}
|
||||
|
||||
public function testIsTrackableIfTracking404OptionDisable(): void
|
||||
{
|
||||
$this->mockCoreParametersHelper->expects($this->once())
|
||||
->method('get')
|
||||
->with('do_not_track_404_anonymous')
|
||||
->willReturn(false);
|
||||
|
||||
$tracking404Model = new Tracking404Model($this->mockCoreParametersHelper, $this->mockContactTracker, $this->mockPageModel);
|
||||
$this->assertTrue($tracking404Model->isTrackable());
|
||||
}
|
||||
|
||||
public function testIsTrackableForIdentifiedContacts(): void
|
||||
{
|
||||
$this->mockCoreParametersHelper->expects($this->once())
|
||||
->method('get')
|
||||
->with('do_not_track_404_anonymous')
|
||||
->willReturn(true);
|
||||
|
||||
$this->lead->setFirstname('identified');
|
||||
$this->mockContactTracker->expects($this->any())
|
||||
->method('getContactByTrackedDevice')
|
||||
->willReturn($this->lead);
|
||||
|
||||
$tracking404Model = new Tracking404Model($this->mockCoreParametersHelper, $this->mockContactTracker, $this->mockPageModel);
|
||||
$this->assertTrue($tracking404Model->isTrackable());
|
||||
}
|
||||
|
||||
public function testIsTrackableForAnonymouse(): void
|
||||
{
|
||||
$this->mockCoreParametersHelper->expects($this->once())
|
||||
->method('get')
|
||||
->with('do_not_track_404_anonymous')
|
||||
->willReturn(true);
|
||||
|
||||
$this->mockContactTracker->expects($this->any())
|
||||
->method('getContactByTrackedDevice')
|
||||
->willReturn($this->lead);
|
||||
|
||||
$tracking404Model = new Tracking404Model($this->mockCoreParametersHelper, $this->mockContactTracker, $this->mockPageModel);
|
||||
$this->assertFalse($tracking404Model->isTrackable());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user