mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2649395f8 | ||
|
|
b7d9ba8258 | ||
|
|
6526cf8c44 | ||
|
|
a85afb2bee | ||
|
|
8b4067efbe | ||
|
|
c7c2272fab | ||
|
|
bc77750713 | ||
|
|
1ceb38f50b | ||
|
|
d273b56144 | ||
|
|
5cd7305666 | ||
|
|
3040a22c02 | ||
|
|
6991138812 | ||
|
|
5eb1808217 | ||
|
|
5eb14c5315 | ||
|
|
a18360a4d6 |
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-*
|
||||||
|
|||||||
37
CHANGELOG.md
37
CHANGELOG.md
@@ -4,6 +4,43 @@ 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
|
## [4.4.4] - 2025-02-19
|
||||||
### 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",
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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