mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2649395f8 | ||
|
|
b7d9ba8258 | ||
|
|
6526cf8c44 | ||
|
|
a85afb2bee | ||
|
|
8b4067efbe | ||
|
|
c7c2272fab | ||
|
|
bc77750713 | ||
|
|
1ceb38f50b | ||
|
|
d273b56144 | ||
|
|
5cd7305666 | ||
|
|
3040a22c02 | ||
|
|
6991138812 | ||
|
|
5eb1808217 | ||
|
|
5eb14c5315 | ||
|
|
a18360a4d6 | ||
|
|
104b1e7d04 | ||
|
|
af2d67695b | ||
|
|
449a588796 | ||
|
|
7bbc938743 | ||
|
|
766758ff9b |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -96,7 +96,7 @@ jobs:
|
|||||||
- upload-coverage
|
- upload-coverage
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: geekyeggo/delete-artifact@v2
|
- uses: geekyeggo/delete-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: |
|
name: |
|
||||||
coverage-*
|
coverage-*
|
||||||
|
|||||||
2
.github/workflows/publish-release.yml
vendored
2
.github/workflows/publish-release.yml
vendored
@@ -45,6 +45,6 @@ jobs:
|
|||||||
needs: ['publish']
|
needs: ['publish']
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: geekyeggo/delete-artifact@v2
|
- uses: geekyeggo/delete-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: dist-files-*
|
name: dist-files-*
|
||||||
|
|||||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -4,6 +4,61 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
|
The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
|
## [4.4.6] - 2025-03-20
|
||||||
|
### Added
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* [#2391](https://github.com/shlinkio/shlink/issues/2391) When sending visits to Matomo, send the country code, not the country name.
|
||||||
|
* Fix error with new option introduced by `endroid/qr-code` 6.0.4.
|
||||||
|
|
||||||
|
|
||||||
|
## [4.4.5] - 2025-03-01
|
||||||
|
### Added
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* [#2373](https://github.com/shlinkio/shlink/issues/2373) Ensure deprecation warnings do not end up escalated to `ErrorException`s by `ProblemDetailsMiddleware`.
|
||||||
|
|
||||||
|
In order to do this, Shlink will entirely ignore deprecation warnings when running in production, as those do not mean something is not working, but only that something will break in future versions.
|
||||||
|
|
||||||
|
|
||||||
|
## [4.4.4] - 2025-02-19
|
||||||
|
### Added
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* [#2366](https://github.com/shlinkio/shlink/issues/2366) Fix error "Cannot use 'SCRIPT' with redis-cluster" thrown when creating a lock while using a redis cluster.
|
||||||
|
* [#2368](https://github.com/shlinkio/shlink/issues/2368) Fix error when listing non-orphan visits using API key with `AUTHORED_SHORT_URLS` role.
|
||||||
|
|
||||||
|
|
||||||
## [4.4.3] - 2025-02-15
|
## [4.4.3] - 2025-02-15
|
||||||
### Added
|
### Added
|
||||||
* *Nothing*
|
* *Nothing*
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"doctrine/migrations": "^3.8",
|
"doctrine/migrations": "^3.8",
|
||||||
"doctrine/orm": "^3.3",
|
"doctrine/orm": "^3.3",
|
||||||
"donatj/phpuseragentparser": "^1.10",
|
"donatj/phpuseragentparser": "^1.10",
|
||||||
"endroid/qr-code": "^6.0",
|
"endroid/qr-code": "^6.0.5",
|
||||||
"friendsofphp/proxy-manager-lts": "^1.0",
|
"friendsofphp/proxy-manager-lts": "^1.0",
|
||||||
"geoip2/geoip2": "^3.1",
|
"geoip2/geoip2": "^3.1",
|
||||||
"guzzlehttp/guzzle": "^7.9",
|
"guzzlehttp/guzzle": "^7.9",
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
"spiral/roadrunner-jobs": "^4.6",
|
"spiral/roadrunner-jobs": "^4.6",
|
||||||
"symfony/console": "^7.2",
|
"symfony/console": "^7.2",
|
||||||
"symfony/filesystem": "^7.2",
|
"symfony/filesystem": "^7.2",
|
||||||
"symfony/lock": "7.2.0",
|
"symfony/lock": "7.1.6",
|
||||||
"symfony/process": "^7.2",
|
"symfony/process": "^7.2",
|
||||||
"symfony/string": "^7.2"
|
"symfony/string": "^7.2"
|
||||||
},
|
},
|
||||||
@@ -77,7 +77,8 @@
|
|||||||
"veewee/composer-run-parallel": "^1.4"
|
"veewee/composer-run-parallel": "^1.4"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/var-exporter": ">=6.3.9,<=6.4.0"
|
"symfony/var-exporter": ">=6.3.9,<=6.4.0",
|
||||||
|
"phpunit/phpunit": "12.0.9"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use function Shlinkio\Shlink\Core\enumValues;
|
|||||||
|
|
||||||
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
use const Shlinkio\Shlink\LOCAL_LOCK_FACTORY;
|
||||||
|
|
||||||
|
// Set current directory to the project's root directory
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
require 'vendor/autoload.php';
|
||||||
@@ -21,7 +22,11 @@ loadEnvVarsFromConfig(
|
|||||||
enumValues(EnvVars::class),
|
enumValues(EnvVars::class),
|
||||||
);
|
);
|
||||||
|
|
||||||
// This is one of the first files loaded. Configure the timezone and memory limit here
|
// This is one of the first files loaded. Set global configuration here
|
||||||
|
error_reporting(
|
||||||
|
// Set a less strict error reporting for prod, where deprecation warnings should be ignored
|
||||||
|
EnvVars::isProdEnv() ? E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED : E_ALL,
|
||||||
|
);
|
||||||
ini_set('memory_limit', EnvVars::MEMORY_LIMIT->loadFromEnv());
|
ini_set('memory_limit', EnvVars::MEMORY_LIMIT->loadFromEnv());
|
||||||
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv());
|
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv());
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
display_errors=On
|
display_errors=On
|
||||||
error_reporting=-1
|
|
||||||
log_errors_max_len=0
|
log_errors_max_len=0
|
||||||
zend.assertions=1
|
zend.assertions=1
|
||||||
assert.exception=1
|
assert.exception=1
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ final readonly class QrCodeParams
|
|||||||
public int $size,
|
public int $size,
|
||||||
public int $margin,
|
public int $margin,
|
||||||
public WriterInterface $writer,
|
public WriterInterface $writer,
|
||||||
|
public array $writerOptions,
|
||||||
public ErrorCorrectionLevel $errorCorrectionLevel,
|
public ErrorCorrectionLevel $errorCorrectionLevel,
|
||||||
public RoundBlockSizeMode $roundBlockSizeMode,
|
public RoundBlockSizeMode $roundBlockSizeMode,
|
||||||
public ColorInterface $color,
|
public ColorInterface $color,
|
||||||
@@ -49,11 +50,13 @@ final readonly class QrCodeParams
|
|||||||
public static function fromRequest(ServerRequestInterface $request, QrCodeOptions $defaults): self
|
public static function fromRequest(ServerRequestInterface $request, QrCodeOptions $defaults): self
|
||||||
{
|
{
|
||||||
$query = $request->getQueryParams();
|
$query = $request->getQueryParams();
|
||||||
|
[$writer, $writerOptions] = self::resolveWriterAndWriterOptions($query, $defaults);
|
||||||
|
|
||||||
return new self(
|
return new self(
|
||||||
size: self::resolveSize($query, $defaults),
|
size: self::resolveSize($query, $defaults),
|
||||||
margin: self::resolveMargin($query, $defaults),
|
margin: self::resolveMargin($query, $defaults),
|
||||||
writer: self::resolveWriter($query, $defaults),
|
writer: $writer,
|
||||||
|
writerOptions: $writerOptions,
|
||||||
errorCorrectionLevel: self::resolveErrorCorrection($query, $defaults),
|
errorCorrectionLevel: self::resolveErrorCorrection($query, $defaults),
|
||||||
roundBlockSizeMode: self::resolveRoundBlockSize($query, $defaults),
|
roundBlockSizeMode: self::resolveRoundBlockSize($query, $defaults),
|
||||||
color: self::resolveColor($query, $defaults),
|
color: self::resolveColor($query, $defaults),
|
||||||
@@ -83,14 +86,17 @@ final readonly class QrCodeParams
|
|||||||
return max($intMargin, 0);
|
return max($intMargin, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function resolveWriter(array $query, QrCodeOptions $defaults): WriterInterface
|
/**
|
||||||
|
* @return array{WriterInterface, array}
|
||||||
|
*/
|
||||||
|
private static function resolveWriterAndWriterOptions(array $query, QrCodeOptions $defaults): array
|
||||||
{
|
{
|
||||||
$qFormat = self::normalizeParam($query['format'] ?? '');
|
$qFormat = self::normalizeParam($query['format'] ?? '');
|
||||||
$format = contains($qFormat, self::SUPPORTED_FORMATS) ? $qFormat : self::normalizeParam($defaults->format);
|
$format = contains($qFormat, self::SUPPORTED_FORMATS) ? $qFormat : self::normalizeParam($defaults->format);
|
||||||
|
|
||||||
return match ($format) {
|
return match ($format) {
|
||||||
'svg' => new SvgWriter(),
|
'svg' => [new SvgWriter(), []],
|
||||||
default => new PngWriter(),
|
default => [new PngWriter(), [PngWriter::WRITER_OPTION_NUMBER_OF_COLORS => null]],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ readonly class QrCodeAction implements MiddlewareInterface
|
|||||||
$params = QrCodeParams::fromRequest($request, $this->options);
|
$params = QrCodeParams::fromRequest($request, $this->options);
|
||||||
$qrCodeBuilder = new Builder(
|
$qrCodeBuilder = new Builder(
|
||||||
writer: $params->writer,
|
writer: $params->writer,
|
||||||
|
writerOptions: $params->writerOptions,
|
||||||
data: $this->stringifier->stringify($shortUrl),
|
data: $this->stringifier->stringify($shortUrl),
|
||||||
errorCorrectionLevel: $params->errorCorrectionLevel,
|
errorCorrectionLevel: $params->errorCorrectionLevel,
|
||||||
size: $params->size,
|
size: $params->size,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
|||||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitIterationRepositoryInterface;
|
use Shlinkio\Shlink\Core\Visit\Repository\VisitIterationRepositoryInterface;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
use function strtolower;
|
||||||
|
|
||||||
readonly class MatomoVisitSender implements MatomoVisitSenderInterface
|
readonly class MatomoVisitSender implements MatomoVisitSenderInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -60,7 +62,7 @@ readonly class MatomoVisitSender implements MatomoVisitSenderInterface
|
|||||||
if ($location !== null) {
|
if ($location !== null) {
|
||||||
$tracker
|
$tracker
|
||||||
->setCity($location->cityName)
|
->setCity($location->cityName)
|
||||||
->setCountry($location->countryName)
|
->setCountry(strtolower($location->countryCode))
|
||||||
->setLatitude($location->latitude)
|
->setLatitude($location->latitude)
|
||||||
->setLongitude($location->longitude);
|
->setLongitude($location->longitude);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
|
|||||||
use Shlinkio\Shlink\Core\Visit\Spec\CountOfNonOrphanVisits;
|
use Shlinkio\Shlink\Core\Visit\Spec\CountOfNonOrphanVisits;
|
||||||
use Shlinkio\Shlink\Core\Visit\Spec\CountOfOrphanVisits;
|
use Shlinkio\Shlink\Core\Visit\Spec\CountOfOrphanVisits;
|
||||||
use Shlinkio\Shlink\Rest\ApiKey\Role;
|
use Shlinkio\Shlink\Rest\ApiKey\Role;
|
||||||
|
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||||
|
|
||||||
use const PHP_INT_MAX;
|
use const PHP_INT_MAX;
|
||||||
|
|
||||||
@@ -177,7 +178,12 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||||||
$qb = $this->createAllVisitsQueryBuilder($filtering);
|
$qb = $this->createAllVisitsQueryBuilder($filtering);
|
||||||
$qb->andWhere($qb->expr()->isNotNull('v.shortUrl'));
|
$qb->andWhere($qb->expr()->isNotNull('v.shortUrl'));
|
||||||
|
|
||||||
$this->applySpecification($qb, $filtering->apiKey?->inlinedSpec());
|
$apiKey = $filtering->apiKey;
|
||||||
|
if (ApiKey::isShortUrlRestricted($apiKey)) {
|
||||||
|
$qb->join('v.shortUrl', 's');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->applySpecification($qb, $apiKey?->inlinedSpec(), 'v');
|
||||||
|
|
||||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -470,22 +470,18 @@ class VisitRepositoryTest extends DatabaseTestCase
|
|||||||
#[Test]
|
#[Test]
|
||||||
public function findNonOrphanVisitsReturnsExpectedResult(): void
|
public function findNonOrphanVisitsReturnsExpectedResult(): void
|
||||||
{
|
{
|
||||||
$shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => 'https://1']));
|
$authoredApiKey = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forAuthoredShortUrls()));
|
||||||
$this->getEntityManager()->persist($shortUrl);
|
$this->getEntityManager()->persist($authoredApiKey);
|
||||||
$this->createVisitsForShortUrl($shortUrl, 7);
|
|
||||||
|
|
||||||
$shortUrl2 = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => 'https://2']));
|
$this->createShortUrlsAndVisits(withDomain: false, visitsAmount: 7);
|
||||||
$this->getEntityManager()->persist($shortUrl2);
|
$this->createShortUrlsAndVisits(withDomain: false, apiKey: $authoredApiKey, visitsAmount: 4);
|
||||||
$this->createVisitsForShortUrl($shortUrl2, 4);
|
$this->createShortUrlsAndVisits(withDomain: false, visitsAmount: 10);
|
||||||
|
|
||||||
$shortUrl3 = ShortUrl::create(ShortUrlCreation::fromRawData(['longUrl' => 'https://3']));
|
|
||||||
$this->getEntityManager()->persist($shortUrl3);
|
|
||||||
$this->createVisitsForShortUrl($shortUrl3, 10);
|
|
||||||
|
|
||||||
$this->getEntityManager()->flush();
|
$this->getEntityManager()->flush();
|
||||||
|
|
||||||
self::assertCount(21, $this->repo->findNonOrphanVisits(new VisitsListFiltering()));
|
self::assertCount(21, $this->repo->findNonOrphanVisits(new VisitsListFiltering()));
|
||||||
self::assertCount(21, $this->repo->findNonOrphanVisits(new VisitsListFiltering(DateRange::allTime())));
|
self::assertCount(21, $this->repo->findNonOrphanVisits(new VisitsListFiltering(DateRange::allTime())));
|
||||||
|
self::assertCount(4, $this->repo->findNonOrphanVisits(new VisitsListFiltering(apiKey: $authoredApiKey)));
|
||||||
self::assertCount(7, $this->repo->findNonOrphanVisits(new VisitsListFiltering(DateRange::since(
|
self::assertCount(7, $this->repo->findNonOrphanVisits(new VisitsListFiltering(DateRange::since(
|
||||||
Chronos::parse('2016-01-05')->endOfDay(),
|
Chronos::parse('2016-01-05')->endOfDay(),
|
||||||
))));
|
))));
|
||||||
@@ -503,11 +499,11 @@ class VisitRepositoryTest extends DatabaseTestCase
|
|||||||
self::assertCount(3, $this->repo->findNonOrphanVisits(new VisitsListFiltering(DateRange::between(
|
self::assertCount(3, $this->repo->findNonOrphanVisits(new VisitsListFiltering(DateRange::between(
|
||||||
Chronos::parse('2016-01-03')->startOfDay(),
|
Chronos::parse('2016-01-03')->startOfDay(),
|
||||||
Chronos::parse('2016-01-08')->endOfDay(),
|
Chronos::parse('2016-01-08')->endOfDay(),
|
||||||
), false, null, 10, 10)));
|
), limit: 10, offset: 10)));
|
||||||
self::assertCount(15, $this->repo->findNonOrphanVisits(new VisitsListFiltering(null, true)));
|
self::assertCount(15, $this->repo->findNonOrphanVisits(new VisitsListFiltering(excludeBots: true)));
|
||||||
self::assertCount(10, $this->repo->findNonOrphanVisits(new VisitsListFiltering(null, false, null, 10)));
|
self::assertCount(10, $this->repo->findNonOrphanVisits(new VisitsListFiltering(limit: 10)));
|
||||||
self::assertCount(1, $this->repo->findNonOrphanVisits(new VisitsListFiltering(null, false, null, 10, 20)));
|
self::assertCount(1, $this->repo->findNonOrphanVisits(new VisitsListFiltering(limit: 10, offset: 20)));
|
||||||
self::assertCount(5, $this->repo->findNonOrphanVisits(new VisitsListFiltering(null, false, null, 5, 5)));
|
self::assertCount(5, $this->repo->findNonOrphanVisits(new VisitsListFiltering(limit: 5, offset: 5)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
@@ -535,6 +531,7 @@ class VisitRepositoryTest extends DatabaseTestCase
|
|||||||
bool|string $withDomain = true,
|
bool|string $withDomain = true,
|
||||||
array $tags = [],
|
array $tags = [],
|
||||||
ApiKey|null $apiKey = null,
|
ApiKey|null $apiKey = null,
|
||||||
|
int $visitsAmount = 6,
|
||||||
): array {
|
): array {
|
||||||
$shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([
|
$shortUrl = ShortUrl::create(ShortUrlCreation::fromRawData([
|
||||||
ShortUrlInputFilter::LONG_URL => 'https://longUrl',
|
ShortUrlInputFilter::LONG_URL => 'https://longUrl',
|
||||||
@@ -545,7 +542,7 @@ class VisitRepositoryTest extends DatabaseTestCase
|
|||||||
$shortCode = $shortUrl->getShortCode();
|
$shortCode = $shortUrl->getShortCode();
|
||||||
$this->getEntityManager()->persist($shortUrl);
|
$this->getEntityManager()->persist($shortUrl);
|
||||||
|
|
||||||
$this->createVisitsForShortUrl($shortUrl);
|
$this->createVisitsForShortUrl($shortUrl, $visitsAmount);
|
||||||
|
|
||||||
if ($withDomain !== false) {
|
if ($withDomain !== false) {
|
||||||
$shortUrlWithDomain = ShortUrl::create(ShortUrlCreation::fromRawData([
|
$shortUrlWithDomain = ShortUrl::create(ShortUrlCreation::fromRawData([
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ class MatomoVisitSenderTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Test, DataProvider('provideTrackerMethods')]
|
#[Test, DataProvider('provideTrackerMethods')]
|
||||||
|
/**
|
||||||
|
* @param array<string, string[]> $invokedMethods
|
||||||
|
*/
|
||||||
public function visitIsSentToMatomo(Visit $visit, string|null $originalIpAddress, array $invokedMethods): void
|
public function visitIsSentToMatomo(Visit $visit, string|null $originalIpAddress, array $invokedMethods): void
|
||||||
{
|
{
|
||||||
$tracker = $this->createMock(MatomoTracker::class);
|
$tracker = $this->createMock(MatomoTracker::class);
|
||||||
@@ -66,8 +69,8 @@ class MatomoVisitSenderTest extends TestCase
|
|||||||
)->willReturn($tracker);
|
)->willReturn($tracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($invokedMethods as $invokedMethod) {
|
foreach ($invokedMethods as $invokedMethod => $args) {
|
||||||
$tracker->expects($this->once())->method($invokedMethod)->willReturn($tracker);
|
$tracker->expects($this->once())->method($invokedMethod)->with(...$args)->willReturn($tracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->trackerBuilder->expects($this->once())->method('buildMatomoTracker')->willReturn($tracker);
|
$this->trackerBuilder->expects($this->once())->method('buildMatomoTracker')->willReturn($tracker);
|
||||||
@@ -81,18 +84,28 @@ class MatomoVisitSenderTest extends TestCase
|
|||||||
yield 'located regular visit' => [
|
yield 'located regular visit' => [
|
||||||
Visit::forValidShortUrl(ShortUrl::withLongUrl('https://shlink.io'), Visitor::empty())
|
Visit::forValidShortUrl(ShortUrl::withLongUrl('https://shlink.io'), Visitor::empty())
|
||||||
->locate(VisitLocation::fromGeolocation(new Location(
|
->locate(VisitLocation::fromGeolocation(new Location(
|
||||||
countryCode: 'countryCode',
|
countryCode: 'US',
|
||||||
countryName: 'countryName',
|
countryName: 'countryName',
|
||||||
regionName: 'regionName',
|
regionName: 'regionName',
|
||||||
city: 'city',
|
city: 'city',
|
||||||
latitude: 123,
|
latitude: 123,
|
||||||
longitude: 123,
|
longitude: 456,
|
||||||
timeZone: 'timeZone',
|
timeZone: 'timeZone',
|
||||||
))),
|
))),
|
||||||
'1.2.3.4',
|
'1.2.3.4',
|
||||||
['setCity', 'setCountry', 'setLatitude', 'setLongitude', 'setIp'],
|
[
|
||||||
|
'setCity' => ['city'],
|
||||||
|
'setCountry' => ['us'],
|
||||||
|
'setLatitude' => [123],
|
||||||
|
'setLongitude' => [456],
|
||||||
|
'setIp' => ['1.2.3.4'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
yield 'fallback IP' => [
|
||||||
|
Visit::forBasePath(Visitor::fromParams(remoteAddress: '5.6.7.8')),
|
||||||
|
null,
|
||||||
|
['setIp' => ['5.6.7.0']],
|
||||||
];
|
];
|
||||||
yield 'fallback IP' => [Visit::forBasePath(Visitor::fromParams(remoteAddress: '1.2.3.4')), null, ['setIp']];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test, DataProvider('provideUrlsToTrack')]
|
#[Test, DataProvider('provideUrlsToTrack')]
|
||||||
|
|||||||
Reference in New Issue
Block a user