mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Merge pull request #1434 from acelaya-forks/feature/drop-php-8.0
Feature/drop php 8.0
This commit is contained in:
commit
24b06c24dc
128 changed files with 596 additions and 950 deletions
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.0']
|
||||
php-version: ['8.1']
|
||||
command: ['cs', 'stan', 'swagger:validate']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
@ -32,7 +32,7 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.0', '8.1']
|
||||
php-version: ['8.1']
|
||||
test-group: ['unit', 'api']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
@ -51,7 +51,7 @@ jobs:
|
|||
- run: composer install --no-interaction --prefer-dist
|
||||
- run: composer test:${{ matrix.test-group }}:ci
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: ${{ matrix.php-version == '8.0' }}
|
||||
if: ${{ matrix.php-version == '8.1' }}
|
||||
with:
|
||||
name: coverage-${{ matrix.test-group }}
|
||||
path: |
|
||||
|
@ -62,7 +62,7 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.0', '8.1']
|
||||
php-version: ['8.1']
|
||||
platform: ['sqlite:ci', 'mysql', 'maria', 'postgres', 'ms']
|
||||
env:
|
||||
LC_ALL: C
|
||||
|
@ -91,7 +91,7 @@ jobs:
|
|||
run: composer test:db:${{ matrix.platform }}
|
||||
- name: Upload code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ matrix.php-version == '8.0' && matrix.platform == 'sqlite:ci' }}
|
||||
if: ${{ matrix.php-version == '8.1' && matrix.platform == 'sqlite:ci' }}
|
||||
with:
|
||||
name: coverage-db
|
||||
path: |
|
||||
|
@ -105,7 +105,7 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.0', '8.1']
|
||||
php-version: ['8.1']
|
||||
test-group: ['unit', 'db', 'api']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
@ -136,7 +136,7 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.0']
|
||||
php-version: ['8.1']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
@ -152,8 +152,8 @@ jobs:
|
|||
- run: mv build/coverage-unit/coverage-unit.cov build/coverage-unit.cov
|
||||
- run: mv build/coverage-db/coverage-db.cov build/coverage-db.cov
|
||||
- run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov
|
||||
- run: wget https://phar.phpunit.de/phpcov-8.2.0.phar
|
||||
- run: php phpcov-8.2.0.phar merge build --clover build/clover.xml
|
||||
- run: wget https://phar.phpunit.de/phpcov-8.2.1.phar
|
||||
- run: php phpcov-8.2.1.phar merge build --clover build/clover.xml
|
||||
- name: Publish coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
|
|
6
.github/workflows/publish-release.yml
vendored
6
.github/workflows/publish-release.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.0', '8.1']
|
||||
php-version: ['8.1']
|
||||
swoole: ['yes', 'no']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
@ -53,8 +53,8 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: [ '8.0', '8.1' ]
|
||||
swoole: [ 'yes', 'no' ]
|
||||
php-version: ['8.1']
|
||||
swoole: ['yes', 'no']
|
||||
steps:
|
||||
- uses: geekyeggo/delete-artifact@v1
|
||||
with:
|
||||
|
|
2
.github/workflows/publish-swagger-spec.yml
vendored
2
.github/workflows/publish-swagger-spec.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.0']
|
||||
php-version: ['8.1']
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
|
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -4,6 +4,23 @@ 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).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
* *Nothing*
|
||||
|
||||
### Changed
|
||||
* *Nothing*
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
||||
### Removed
|
||||
* [#1280](https://github.com/shlinkio/shlink/issues/1280) Dropped support for PHP 8.0
|
||||
|
||||
### Fixed
|
||||
* *Nothing*
|
||||
|
||||
|
||||
## [3.1.0] - 2022-04-23
|
||||
### Added
|
||||
* [#1294](https://github.com/shlinkio/shlink/issues/1294) Allowed to provide a specific domain when importing URLs from YOURLS.
|
||||
|
|
|
@ -35,7 +35,7 @@ The idea is that you can just generate a container using the image and provide t
|
|||
|
||||
First, make sure the host where you are going to run shlink fulfills these requirements:
|
||||
|
||||
* PHP 8.0 or 8.1
|
||||
* PHP 8.1
|
||||
* The next PHP extensions: json, curl, pdo, intl, gd and gmp/bcmath.
|
||||
* apcu extension is recommended if you don't plan to use openswoole.
|
||||
* xml extension is required if you want to generate QR codes in svg format.
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"php": "^8.1",
|
||||
"ext-json": "*",
|
||||
"ext-pdo": "*",
|
||||
"akrabat/ip-address-middleware": "^2.1",
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shlinkio\Shlink;
|
|||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
return (static function (): array {
|
||||
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD()->loadFromEnv();
|
||||
$threshold = EnvVars::DELETE_SHORT_URL_THRESHOLD->loadFromEnv();
|
||||
|
||||
return [
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
|||
use function Functional\contains;
|
||||
|
||||
return (static function (): array {
|
||||
$driver = EnvVars::DB_DRIVER()->loadFromEnv();
|
||||
$driver = EnvVars::DB_DRIVER->loadFromEnv();
|
||||
$isMysqlCompatible = contains(['maria', 'mysql'], $driver);
|
||||
|
||||
$resolveDriver = static fn () => match ($driver) {
|
||||
|
@ -35,12 +35,12 @@ return (static function (): array {
|
|||
],
|
||||
default => [
|
||||
'driver' => $resolveDriver(),
|
||||
'dbname' => EnvVars::DB_NAME()->loadFromEnv('shlink'),
|
||||
'user' => EnvVars::DB_USER()->loadFromEnv(),
|
||||
'password' => EnvVars::DB_PASSWORD()->loadFromEnv(),
|
||||
'host' => EnvVars::DB_HOST()->loadFromEnv(EnvVars::DB_UNIX_SOCKET()->loadFromEnv()),
|
||||
'port' => EnvVars::DB_PORT()->loadFromEnv($resolveDefaultPort()),
|
||||
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET()->loadFromEnv() : null,
|
||||
'dbname' => EnvVars::DB_NAME->loadFromEnv('shlink'),
|
||||
'user' => EnvVars::DB_USER->loadFromEnv(),
|
||||
'password' => EnvVars::DB_PASSWORD->loadFromEnv(),
|
||||
'host' => EnvVars::DB_HOST->loadFromEnv(EnvVars::DB_UNIX_SOCKET->loadFromEnv()),
|
||||
'port' => EnvVars::DB_PORT->loadFromEnv($resolveDefaultPort()),
|
||||
'unix_socket' => $isMysqlCompatible ? EnvVars::DB_UNIX_SOCKET->loadFromEnv() : null,
|
||||
'charset' => $resolveCharset(),
|
||||
],
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ return [
|
|||
'geolite2' => [
|
||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||
'temp_dir' => __DIR__ . '/../../data',
|
||||
'license_key' => EnvVars::GEOLITE_LICENSE_KEY()->loadFromEnv(),
|
||||
'license_key' => EnvVars::GEOLITE_LICENSE_KEY->loadFromEnv(),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -24,7 +24,7 @@ return [
|
|||
LOCAL_LOCK_FACTORY => ConfigAbstractFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'lock_store' => EnvVars::REDIS_SERVERS()->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
|
||||
'lock_store' => EnvVars::REDIS_SERVERS->existsInEnv() ? 'redis_lock_store' : 'local_lock_store',
|
||||
|
||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
||||
'local_lock_store' => Lock\Store\FlockStore::class,
|
||||
|
|
|
@ -9,14 +9,14 @@ use Symfony\Component\Mercure\Hub;
|
|||
use Symfony\Component\Mercure\HubInterface;
|
||||
|
||||
return (static function (): array {
|
||||
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL()->loadFromEnv();
|
||||
$publicUrl = EnvVars::MERCURE_PUBLIC_HUB_URL->loadFromEnv();
|
||||
|
||||
return [
|
||||
|
||||
'mercure' => [
|
||||
'public_hub_url' => $publicUrl,
|
||||
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL()->loadFromEnv($publicUrl),
|
||||
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET()->loadFromEnv(),
|
||||
'internal_hub_url' => EnvVars::MERCURE_INTERNAL_HUB_URL->loadFromEnv($publicUrl),
|
||||
'jwt_secret' => EnvVars::MERCURE_JWT_SECRET->loadFromEnv(),
|
||||
'jwt_issuer' => 'Shlink',
|
||||
],
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@ use const Shlinkio\Shlink\DEFAULT_QR_CODE_SIZE;
|
|||
return [
|
||||
|
||||
'qr_codes' => [
|
||||
'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE()->loadFromEnv(DEFAULT_QR_CODE_SIZE),
|
||||
'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN()->loadFromEnv(DEFAULT_QR_CODE_MARGIN),
|
||||
'format' => EnvVars::DEFAULT_QR_CODE_FORMAT()->loadFromEnv(DEFAULT_QR_CODE_FORMAT),
|
||||
'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION()->loadFromEnv(
|
||||
'size' => (int) EnvVars::DEFAULT_QR_CODE_SIZE->loadFromEnv(DEFAULT_QR_CODE_SIZE),
|
||||
'margin' => (int) EnvVars::DEFAULT_QR_CODE_MARGIN->loadFromEnv(DEFAULT_QR_CODE_MARGIN),
|
||||
'format' => EnvVars::DEFAULT_QR_CODE_FORMAT->loadFromEnv(DEFAULT_QR_CODE_FORMAT),
|
||||
'error_correction' => EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION->loadFromEnv(
|
||||
DEFAULT_QR_CODE_ERROR_CORRECTION,
|
||||
),
|
||||
'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE()->loadFromEnv(
|
||||
'round_block_size' => (bool) EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE->loadFromEnv(
|
||||
DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -10,12 +10,12 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
|||
return [
|
||||
|
||||
'rabbitmq' => [
|
||||
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED()->loadFromEnv(false),
|
||||
'host' => EnvVars::RABBITMQ_HOST()->loadFromEnv(),
|
||||
'port' => (int) EnvVars::RABBITMQ_PORT()->loadFromEnv('5672'),
|
||||
'user' => EnvVars::RABBITMQ_USER()->loadFromEnv(),
|
||||
'password' => EnvVars::RABBITMQ_PASSWORD()->loadFromEnv(),
|
||||
'vhost' => EnvVars::RABBITMQ_VHOST()->loadFromEnv('/'),
|
||||
'enabled' => (bool) EnvVars::RABBITMQ_ENABLED->loadFromEnv(false),
|
||||
'host' => EnvVars::RABBITMQ_HOST->loadFromEnv(),
|
||||
'port' => (int) EnvVars::RABBITMQ_PORT->loadFromEnv('5672'),
|
||||
'user' => EnvVars::RABBITMQ_USER->loadFromEnv(),
|
||||
'password' => EnvVars::RABBITMQ_PASSWORD->loadFromEnv(),
|
||||
'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv('/'),
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
|
|
|
@ -10,14 +10,14 @@ use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE;
|
|||
return [
|
||||
|
||||
'not_found_redirects' => [
|
||||
'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT()->loadFromEnv(),
|
||||
'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT()->loadFromEnv(),
|
||||
'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT()->loadFromEnv(),
|
||||
'invalid_short_url' => EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT->loadFromEnv(),
|
||||
'regular_404' => EnvVars::DEFAULT_REGULAR_404_REDIRECT->loadFromEnv(),
|
||||
'base_url' => EnvVars::DEFAULT_BASE_URL_REDIRECT->loadFromEnv(),
|
||||
],
|
||||
|
||||
'redirects' => [
|
||||
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE()->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE),
|
||||
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME()->loadFromEnv(
|
||||
'redirect_status_code' => (int) EnvVars::REDIRECT_STATUS_CODE->loadFromEnv(DEFAULT_REDIRECT_STATUS_CODE),
|
||||
'redirect_cache_lifetime' => (int) EnvVars::REDIRECT_CACHE_LIFETIME->loadFromEnv(
|
||||
DEFAULT_REDIRECT_CACHE_LIFETIME,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
|
||||
return (static function (): array {
|
||||
$redisServers = EnvVars::REDIS_SERVERS()->loadFromEnv();
|
||||
$redisServers = EnvVars::REDIS_SERVERS->loadFromEnv();
|
||||
|
||||
return match ($redisServers) {
|
||||
null => [],
|
||||
|
@ -13,7 +13,7 @@ return (static function (): array {
|
|||
'cache' => [
|
||||
'redis' => [
|
||||
'servers' => $redisServers,
|
||||
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE()->loadFromEnv(),
|
||||
'sentinel_service' => EnvVars::REDIS_SENTINEL_SERVICE->loadFromEnv(),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
|
@ -8,7 +8,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
|||
return [
|
||||
|
||||
'router' => [
|
||||
'base_path' => EnvVars::BASE_PATH()->loadFromEnv(''),
|
||||
'base_path' => EnvVars::BASE_PATH->loadFromEnv(''),
|
||||
|
||||
'fastroute' => [
|
||||
FastRouteRouter::CONFIG_CACHE_ENABLED => true,
|
||||
|
|
|
@ -7,7 +7,7 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
|||
use const Shlinkio\Shlink\MIN_TASK_WORKERS;
|
||||
|
||||
return (static function (): array {
|
||||
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM()->loadFromEnv(16);
|
||||
$taskWorkers = (int) EnvVars::TASK_WORKER_NUM->loadFromEnv(16);
|
||||
|
||||
return [
|
||||
|
||||
|
@ -17,11 +17,11 @@ return (static function (): array {
|
|||
|
||||
'swoole-http-server' => [
|
||||
'host' => '0.0.0.0',
|
||||
'port' => (int) EnvVars::PORT()->loadFromEnv(8080),
|
||||
'port' => (int) EnvVars::PORT->loadFromEnv(8080),
|
||||
'process-name' => 'shlink',
|
||||
|
||||
'options' => [
|
||||
'worker_num' => (int) EnvVars::WEB_WORKER_NUM()->loadFromEnv(16),
|
||||
'worker_num' => (int) EnvVars::WEB_WORKER_NUM->loadFromEnv(16),
|
||||
'task_worker_num' => max($taskWorkers, MIN_TASK_WORKERS),
|
||||
],
|
||||
],
|
||||
|
|
|
@ -9,28 +9,28 @@ return [
|
|||
'tracking' => [
|
||||
// Tells if IP addresses should be anonymized before persisting, to fulfil data protection regulations
|
||||
// This applies only if IP address tracking is enabled
|
||||
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR()->loadFromEnv(true),
|
||||
'anonymize_remote_addr' => (bool) EnvVars::ANONYMIZE_REMOTE_ADDR->loadFromEnv(true),
|
||||
|
||||
// Tells if visits to not-found URLs should be tracked. The disable_tracking option takes precedence
|
||||
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS()->loadFromEnv(true),
|
||||
'track_orphan_visits' => (bool) EnvVars::TRACK_ORPHAN_VISITS->loadFromEnv(true),
|
||||
|
||||
// A query param that, if provided, will disable tracking of one particular visit. Always takes precedence
|
||||
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM()->loadFromEnv(),
|
||||
'disable_track_param' => EnvVars::DISABLE_TRACK_PARAM->loadFromEnv(),
|
||||
|
||||
// If true, visits will not be tracked at all
|
||||
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING()->loadFromEnv(false),
|
||||
'disable_tracking' => (bool) EnvVars::DISABLE_TRACKING->loadFromEnv(false),
|
||||
|
||||
// If true, visits will be tracked, but neither the IP address, nor the location will be resolved
|
||||
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING()->loadFromEnv(false),
|
||||
'disable_ip_tracking' => (bool) EnvVars::DISABLE_IP_TRACKING->loadFromEnv(false),
|
||||
|
||||
// If true, the referrer will not be tracked
|
||||
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING()->loadFromEnv(false),
|
||||
'disable_referrer_tracking' => (bool) EnvVars::DISABLE_REFERRER_TRACKING->loadFromEnv(false),
|
||||
|
||||
// If true, the user agent will not be tracked
|
||||
'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING()->loadFromEnv(false),
|
||||
'disable_ua_tracking' => (bool) EnvVars::DISABLE_UA_TRACKING->loadFromEnv(false),
|
||||
|
||||
// A list of IP addresses, patterns or CIDR blocks from which tracking is disabled by default
|
||||
'disable_tracking_from' => EnvVars::DISABLE_TRACKING_FROM()->loadFromEnv(),
|
||||
'disable_tracking_from' => EnvVars::DISABLE_TRACKING_FROM->loadFromEnv(),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -9,7 +9,7 @@ use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH;
|
|||
|
||||
return (static function (): array {
|
||||
$shortCodesLength = max(
|
||||
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH()->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
|
||||
(int) EnvVars::DEFAULT_SHORT_CODES_LENGTH->loadFromEnv(DEFAULT_SHORT_CODES_LENGTH),
|
||||
MIN_SHORT_CODES_LENGTH,
|
||||
);
|
||||
|
||||
|
@ -17,12 +17,12 @@ return (static function (): array {
|
|||
|
||||
'url_shortener' => [
|
||||
'domain' => [ // TODO Refactor this structure to url_shortener.schema and url_shortener.default_domain
|
||||
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED()->loadFromEnv(true)) ? 'https' : 'http',
|
||||
'hostname' => EnvVars::DEFAULT_DOMAIN()->loadFromEnv(''),
|
||||
'schema' => ((bool) EnvVars::IS_HTTPS_ENABLED->loadFromEnv(true)) ? 'https' : 'http',
|
||||
'hostname' => EnvVars::DEFAULT_DOMAIN->loadFromEnv(''),
|
||||
],
|
||||
'default_short_codes_length' => $shortCodesLength,
|
||||
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES()->loadFromEnv(false),
|
||||
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH()->loadFromEnv(false),
|
||||
'auto_resolve_titles' => (bool) EnvVars::AUTO_RESOLVE_TITLES->loadFromEnv(false),
|
||||
'append_extra_path' => (bool) EnvVars::REDIRECT_APPEND_EXTRA_PATH->loadFromEnv(false),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -6,14 +6,14 @@ use Shlinkio\Shlink\Core\Config\EnvVars;
|
|||
|
||||
// Deprecated. Webhooks are no longer supported. To be removed in Shlink 4.0.0
|
||||
return (static function (): array {
|
||||
$webhooks = EnvVars::VISITS_WEBHOOKS()->loadFromEnv();
|
||||
$webhooks = EnvVars::VISITS_WEBHOOKS->loadFromEnv();
|
||||
|
||||
return [
|
||||
|
||||
'visits_webhooks' => [
|
||||
'webhooks' => $webhooks === null ? [] : explode(',', $webhooks),
|
||||
'notify_orphan_visits_to_webhooks' =>
|
||||
(bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS()->loadFromEnv(false),
|
||||
(bool) EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS->loadFromEnv(false),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -13,7 +13,7 @@ chdir(dirname(__DIR__));
|
|||
require 'vendor/autoload.php';
|
||||
|
||||
// This is one of the first files loaded. Configure the timezone here
|
||||
date_default_timezone_set(EnvVars::TIMEZONE()->loadFromEnv(date_default_timezone_get()));
|
||||
date_default_timezone_set(EnvVars::TIMEZONE->loadFromEnv(date_default_timezone_get()));
|
||||
|
||||
// This class alias tricks the ConfigAbstractFactory to return Lock\Factory instances even with a different service name
|
||||
// It needs to be placed here as individual config files will not be loaded once config is cached
|
||||
|
|
|
@ -11,7 +11,7 @@ server {
|
|||
|
||||
location ~ \.php$ {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
|
||||
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ use Doctrine\DBAL\Platforms\MySQLPlatform;
|
|||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
|
||||
final class Version20210207100807 extends AbstractMigration
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ final class Version20210207100807 extends AbstractMigration
|
|||
]);
|
||||
$visits->addColumn('type', Types::STRING, [
|
||||
'length' => 255,
|
||||
'default' => Visit::TYPE_VALID_SHORT_URL,
|
||||
'default' => VisitType::VALID_SHORT_URL->value,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -73,13 +73,16 @@ class GenerateKeyCommand extends Command
|
|||
$authorOnly,
|
||||
'a',
|
||||
InputOption::VALUE_NONE,
|
||||
sprintf('Adds the "%s" role to the new API key.', Role::AUTHORED_SHORT_URLS),
|
||||
sprintf('Adds the "%s" role to the new API key.', Role::AUTHORED_SHORT_URLS->value),
|
||||
)
|
||||
->addOption(
|
||||
$domainOnly,
|
||||
'd',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
sprintf('Adds the "%s" role to the new API key, with the domain provided.', Role::DOMAIN_SPECIFIC),
|
||||
sprintf(
|
||||
'Adds the "%s" role to the new API key, with the domain provided.',
|
||||
Role::DOMAIN_SPECIFIC->value,
|
||||
),
|
||||
)
|
||||
->setHelp($help);
|
||||
}
|
||||
|
@ -99,7 +102,7 @@ class GenerateKeyCommand extends Command
|
|||
if (! $apiKey->isAdmin()) {
|
||||
ShlinkTable::default($io)->render(
|
||||
['Role name', 'Role metadata'],
|
||||
$apiKey->mapRoles(fn (string $name, array $meta) => [$name, arrayToString($meta, 0)]),
|
||||
$apiKey->mapRoles(fn (Role $role, array $meta) => [$role->value, arrayToString($meta, 0)]),
|
||||
null,
|
||||
'Roles',
|
||||
);
|
||||
|
|
|
@ -60,10 +60,10 @@ class ListKeysCommand extends Command
|
|||
}
|
||||
$rowData[] = $expiration?->toAtomString() ?? '-';
|
||||
$rowData[] = $apiKey->isAdmin() ? 'Admin' : implode("\n", $apiKey->mapRoles(
|
||||
fn (string $roleName, array $meta) =>
|
||||
fn (Role $role, array $meta) =>
|
||||
empty($meta)
|
||||
? Role::toFriendlyName($roleName)
|
||||
: sprintf('%s: %s', Role::toFriendlyName($roleName), Role::domainAuthorityFromMeta($meta)),
|
||||
? Role::toFriendlyName($role)
|
||||
: sprintf('%s: %s', Role::toFriendlyName($role), Role::domainAuthorityFromMeta($meta)),
|
||||
));
|
||||
|
||||
return $rowData;
|
||||
|
|
|
@ -53,7 +53,7 @@ class DomainRedirectsCommand extends Command
|
|||
|
||||
/** @var string[] $availableDomains */
|
||||
$availableDomains = invoke(
|
||||
filter($this->domainService->listDomains(), static fn (DomainItem $item) => ! $item->isDefault()),
|
||||
filter($this->domainService->listDomains(), static fn (DomainItem $item) => ! $item->isDefault),
|
||||
'toString',
|
||||
);
|
||||
if (empty($availableDomains)) {
|
||||
|
|
|
@ -48,12 +48,12 @@ class ListDomainsCommand extends Command
|
|||
$table->render(
|
||||
$showRedirects ? [...$commonFields, '"Not found" redirects'] : $commonFields,
|
||||
map($domains, function (DomainItem $domain) use ($showRedirects) {
|
||||
$commonValues = [$domain->toString(), $domain->isDefault() ? 'Yes' : 'No'];
|
||||
$commonValues = [$domain->toString(), $domain->isDefault ? 'Yes' : 'No'];
|
||||
|
||||
return $showRedirects
|
||||
? [
|
||||
...$commonValues,
|
||||
$this->notFoundRedirectsToString($domain->notFoundRedirectConfig()),
|
||||
$this->notFoundRedirectsToString($domain->notFoundRedirectConfig),
|
||||
]
|
||||
: $commonValues;
|
||||
}),
|
||||
|
|
|
@ -81,6 +81,6 @@ class DeleteShortUrlCommand extends Command
|
|||
private function runDelete(SymfonyStyle $io, ShortUrlIdentifier $identifier, bool $ignoreThreshold): void
|
||||
{
|
||||
$this->deleteShortUrlService->deleteByShortCode($identifier, $ignoreThreshold);
|
||||
$io->success(sprintf('Short URL with short code "%s" successfully deleted.', $identifier->shortCode()));
|
||||
$io->success(sprintf('Short URL with short code "%s" successfully deleted.', $identifier->shortCode));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
|
|||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||
use Shlinkio\Shlink\Core\Validation\ShortUrlsParamsInputFilter;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -120,9 +121,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
|
|||
$page = (int) $input->getOption('page');
|
||||
$searchTerm = $input->getOption('search-term');
|
||||
$tags = $input->getOption('tags');
|
||||
$tagsMode = $input->getOption('including-all-tags') === true
|
||||
? ShortUrlsParams::TAGS_MODE_ALL
|
||||
: ShortUrlsParams::TAGS_MODE_ANY;
|
||||
$tagsMode = $input->getOption('including-all-tags') === true ? TagsMode::ALL->value : TagsMode::ANY->value;
|
||||
$tags = ! empty($tags) ? explode(',', $tags) : [];
|
||||
$all = $input->getOption('all');
|
||||
$startDate = $this->getStartDateOption($input, $output);
|
||||
|
|
|
@ -46,7 +46,7 @@ class ListTagsCommand extends Command
|
|||
|
||||
return map(
|
||||
$tags,
|
||||
static fn (TagInfo $tagInfo) => [$tagInfo->tag(), $tagInfo->shortUrlsCount(), $tagInfo->visitsCount()],
|
||||
static fn (TagInfo $tagInfo) => [$tagInfo->tag, $tagInfo->shortUrlsCount, $tagInfo->visitsCount],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,11 @@ abstract class AbstractLockedCommand extends Command
|
|||
final protected function execute(InputInterface $input, OutputInterface $output): ?int
|
||||
{
|
||||
$lockConfig = $this->getLockConfig();
|
||||
$lock = $this->locker->createLock($lockConfig->lockName(), $lockConfig->ttl(), $lockConfig->isBlocking());
|
||||
$lock = $this->locker->createLock($lockConfig->lockName, $lockConfig->ttl, $lockConfig->isBlocking);
|
||||
|
||||
if (! $lock->acquire($lockConfig->isBlocking())) {
|
||||
if (! $lock->acquire($lockConfig->isBlocking)) {
|
||||
$output->writeln(
|
||||
sprintf('<comment>Command "%s" is already in progress. Skipping.</comment>', $lockConfig->lockName()),
|
||||
sprintf('<comment>Command "%s" is already in progress. Skipping.</comment>', $lockConfig->lockName),
|
||||
);
|
||||
return ExitCodes::EXIT_WARNING;
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ final class LockedCommandConfig
|
|||
public const DEFAULT_TTL = 600.0; // 10 minutes
|
||||
|
||||
private function __construct(
|
||||
private string $lockName,
|
||||
private bool $isBlocking,
|
||||
private float $ttl = self::DEFAULT_TTL,
|
||||
public readonly string $lockName,
|
||||
public readonly bool $isBlocking,
|
||||
public readonly float $ttl = self::DEFAULT_TTL,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -24,19 +24,4 @@ final class LockedCommandConfig
|
|||
{
|
||||
return new self($lockName, false);
|
||||
}
|
||||
|
||||
public function lockName(): string
|
||||
{
|
||||
return $this->lockName;
|
||||
}
|
||||
|
||||
public function isBlocking(): bool
|
||||
{
|
||||
return $this->isBlocking;
|
||||
}
|
||||
|
||||
public function ttl(): float
|
||||
{
|
||||
return $this->ttl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ class InvalidRoleConfigException extends InvalidArgumentException implements Exc
|
|||
return new self(sprintf(
|
||||
'You cannot create an API key with the "%s" role attached to the default domain. '
|
||||
. 'The role is currently limited to non-default domains.',
|
||||
Role::DOMAIN_SPECIFIC,
|
||||
Role::DOMAIN_SPECIFIC->value,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ final class ShlinkTable
|
|||
private const DEFAULT_STYLE_NAME = 'default';
|
||||
private const TABLE_TITLE_STYLE = '<options=bold> %s </>';
|
||||
|
||||
private function __construct(private Table $baseTable, private bool $withRowSeparators)
|
||||
private function __construct(private readonly Table $baseTable, private readonly bool $withRowSeparators)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,11 @@ class DeleteShortUrlCommandTest extends TestCase
|
|||
public function successMessageIsPrintedIfUrlIsProperlyDeleted(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$deleteByShortCode = $this->service->deleteByShortCode(new ShortUrlIdentifier($shortCode), false)->will(
|
||||
function (): void {
|
||||
},
|
||||
);
|
||||
$deleteByShortCode = $this->service->deleteByShortCode(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
false,
|
||||
)->will(function (): void {
|
||||
});
|
||||
|
||||
$this->commandTester->execute(['shortCode' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
@ -55,7 +56,7 @@ class DeleteShortUrlCommandTest extends TestCase
|
|||
public function invalidShortCodePrintsMessage(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$identifier = new ShortUrlIdentifier($shortCode);
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode);
|
||||
$deleteByShortCode = $this->service->deleteByShortCode($identifier, false)->willThrow(
|
||||
Exception\ShortUrlNotFoundException::fromNotFound($identifier),
|
||||
);
|
||||
|
@ -77,7 +78,7 @@ class DeleteShortUrlCommandTest extends TestCase
|
|||
string $expectedMessage,
|
||||
): void {
|
||||
$shortCode = 'abc123';
|
||||
$identifier = new ShortUrlIdentifier($shortCode);
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode);
|
||||
$deleteByShortCode = $this->service->deleteByShortCode($identifier, Argument::type('bool'))->will(
|
||||
function (array $args) use ($shortCode): void {
|
||||
$ignoreThreshold = array_pop($args);
|
||||
|
@ -114,12 +115,13 @@ class DeleteShortUrlCommandTest extends TestCase
|
|||
public function deleteIsNotRetriedWhenThresholdIsReachedAndQuestionIsDeclined(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$deleteByShortCode = $this->service->deleteByShortCode(new ShortUrlIdentifier($shortCode), false)->willThrow(
|
||||
Exception\DeleteShortUrlException::fromVisitsThreshold(
|
||||
10,
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
),
|
||||
);
|
||||
$deleteByShortCode = $this->service->deleteByShortCode(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
false,
|
||||
)->willThrow(Exception\DeleteShortUrlException::fromVisitsThreshold(
|
||||
10,
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
));
|
||||
$this->commandTester->setInputs(['no']);
|
||||
|
||||
$this->commandTester->execute(['shortCode' => $shortCode]);
|
||||
|
|
|
@ -44,7 +44,7 @@ class GetVisitsCommandTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(DateRange::emptyInstance()),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([])))
|
||||
|
@ -60,7 +60,7 @@ class GetVisitsCommandTest extends TestCase
|
|||
$startDate = '2016-01-01';
|
||||
$endDate = '2016-02-01';
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(DateRange::withStartAndEndDate(Chronos::parse($startDate), Chronos::parse($endDate))),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([])))
|
||||
|
@ -79,7 +79,7 @@ class GetVisitsCommandTest extends TestCase
|
|||
$shortCode = 'abc123';
|
||||
$startDate = 'foo';
|
||||
$info = $this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(DateRange::emptyInstance()),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
|
@ -100,7 +100,10 @@ class GetVisitsCommandTest extends TestCase
|
|||
public function outputIsProperlyGenerated(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsHelper->visitsForShortUrl(new ShortUrlIdentifier($shortCode), Argument::any())->willReturn(
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
Argument::any(),
|
||||
)->willReturn(
|
||||
new Paginator(new ArrayAdapter([
|
||||
Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('bar', 'foo', '', ''))->locate(
|
||||
VisitLocation::fromGeolocation(new Location('', 'Spain', '', '', 0, 0, '')),
|
||||
|
|
|
@ -16,6 +16,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
|||
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
|
||||
use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
@ -205,23 +206,23 @@ class ListShortUrlsCommandTest extends TestCase
|
|||
|
||||
public function provideArgs(): iterable
|
||||
{
|
||||
yield [[], 1, null, [], ShortUrlsParams::TAGS_MODE_ANY];
|
||||
yield [['--page' => $page = 3], $page, null, [], ShortUrlsParams::TAGS_MODE_ANY];
|
||||
yield [['--including-all-tags' => true], 1, null, [], ShortUrlsParams::TAGS_MODE_ALL];
|
||||
yield [['--search-term' => $searchTerm = 'search this'], 1, $searchTerm, [], ShortUrlsParams::TAGS_MODE_ANY];
|
||||
yield [[], 1, null, [], TagsMode::ANY->value];
|
||||
yield [['--page' => $page = 3], $page, null, [], TagsMode::ANY->value];
|
||||
yield [['--including-all-tags' => true], 1, null, [], TagsMode::ALL->value];
|
||||
yield [['--search-term' => $searchTerm = 'search this'], 1, $searchTerm, [], TagsMode::ANY->value];
|
||||
yield [
|
||||
['--page' => $page = 3, '--search-term' => $searchTerm = 'search this', '--tags' => $tags = 'foo,bar'],
|
||||
$page,
|
||||
$searchTerm,
|
||||
explode(',', $tags),
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
TagsMode::ANY->value,
|
||||
];
|
||||
yield [
|
||||
['--start-date' => $startDate = '2019-01-01'],
|
||||
1,
|
||||
null,
|
||||
[],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
TagsMode::ANY->value,
|
||||
$startDate,
|
||||
];
|
||||
yield [
|
||||
|
@ -229,7 +230,7 @@ class ListShortUrlsCommandTest extends TestCase
|
|||
1,
|
||||
null,
|
||||
[],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
TagsMode::ANY->value,
|
||||
null,
|
||||
$endDate,
|
||||
];
|
||||
|
@ -238,7 +239,7 @@ class ListShortUrlsCommandTest extends TestCase
|
|||
1,
|
||||
null,
|
||||
[],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
TagsMode::ANY->value,
|
||||
$startDate,
|
||||
$endDate,
|
||||
];
|
||||
|
@ -276,7 +277,7 @@ class ListShortUrlsCommandTest extends TestCase
|
|||
'page' => 1,
|
||||
'searchTerm' => null,
|
||||
'tags' => [],
|
||||
'tagsMode' => ShortUrlsParams::TAGS_MODE_ANY,
|
||||
'tagsMode' => TagsMode::ANY->value,
|
||||
'startDate' => null,
|
||||
'endDate' => null,
|
||||
'orderBy' => null,
|
||||
|
|
|
@ -37,8 +37,9 @@ class ResolveUrlCommandTest extends TestCase
|
|||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
$shortUrl = ShortUrl::withLongUrl($expectedUrl);
|
||||
$this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode))->willReturn($shortUrl)
|
||||
->shouldBeCalledOnce();
|
||||
$this->urlResolver->resolveShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode))->willReturn(
|
||||
$shortUrl,
|
||||
)->shouldBeCalledOnce();
|
||||
|
||||
$this->commandTester->execute(['shortCode' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
@ -48,8 +49,8 @@ class ResolveUrlCommandTest extends TestCase
|
|||
/** @test */
|
||||
public function incorrectShortCodeOutputsErrorMessage(): void
|
||||
{
|
||||
$identifier = new ShortUrlIdentifier('abc123');
|
||||
$shortCode = $identifier->shortCode();
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain('abc123');
|
||||
$shortCode = $identifier->shortCode;
|
||||
|
||||
$this->urlResolver->resolveShortUrl($identifier)
|
||||
->willThrow(ShortUrlNotFoundException::fromNotFound($identifier))
|
||||
|
|
|
@ -20,7 +20,7 @@ class InvalidRoleConfigExceptionTest extends TestCase
|
|||
self::assertEquals(sprintf(
|
||||
'You cannot create an API key with the "%s" role attached to the default domain. '
|
||||
. 'The role is currently limited to non-default domains.',
|
||||
Role::DOMAIN_SPECIFIC,
|
||||
Role::DOMAIN_SPECIFIC->value,
|
||||
), $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,11 @@ namespace Shlinkio\Shlink\Core;
|
|||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
|
||||
use Doctrine\ORM\Mapping\Builder\FieldBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
|
||||
return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
@ -61,10 +63,13 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('type', Types::STRING)
|
||||
->columnName('type')
|
||||
->length(255)
|
||||
->build();
|
||||
(new FieldBuilder($builder, [
|
||||
'fieldName' => 'type',
|
||||
'type' => Types::STRING,
|
||||
'enumType' => VisitType::class,
|
||||
]))->columnName('type')
|
||||
->length(255)
|
||||
->build();
|
||||
|
||||
$builder->createField('potentialBot', Types::BOOLEAN)
|
||||
->columnName('potential_bot')
|
||||
|
|
|
@ -29,11 +29,11 @@ final class QrCodeParams
|
|||
private const SUPPORTED_FORMATS = ['png', 'svg'];
|
||||
|
||||
private function __construct(
|
||||
private int $size,
|
||||
private int $margin,
|
||||
private WriterInterface $writer,
|
||||
private ErrorCorrectionLevelInterface $errorCorrectionLevel,
|
||||
private RoundBlockSizeModeInterface $roundBlockSizeMode,
|
||||
public readonly int $size,
|
||||
public readonly int $margin,
|
||||
public readonly WriterInterface $writer,
|
||||
public readonly ErrorCorrectionLevelInterface $errorCorrectionLevel,
|
||||
public readonly RoundBlockSizeModeInterface $roundBlockSizeMode,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -105,29 +105,4 @@ final class QrCodeParams
|
|||
{
|
||||
return strtolower(trim($param));
|
||||
}
|
||||
|
||||
public function size(): int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function margin(): int
|
||||
{
|
||||
return $this->margin;
|
||||
}
|
||||
|
||||
public function writer(): WriterInterface
|
||||
{
|
||||
return $this->writer;
|
||||
}
|
||||
|
||||
public function errorCorrectionLevel(): ErrorCorrectionLevelInterface
|
||||
{
|
||||
return $this->errorCorrectionLevel;
|
||||
}
|
||||
|
||||
public function roundBlockSizeMode(): RoundBlockSizeModeInterface
|
||||
{
|
||||
return $this->roundBlockSizeMode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,11 +42,11 @@ class QrCodeAction implements MiddlewareInterface
|
|||
$params = QrCodeParams::fromRequest($request, $this->defaultOptions);
|
||||
$qrCodeBuilder = Builder::create()
|
||||
->data($this->stringifier->stringify($shortUrl))
|
||||
->size($params->size())
|
||||
->margin($params->margin())
|
||||
->writer($params->writer())
|
||||
->errorCorrectionLevel($params->errorCorrectionLevel())
|
||||
->roundBlockSizeMode($params->roundBlockSizeMode());
|
||||
->size($params->size)
|
||||
->margin($params->margin)
|
||||
->writer($params->writer)
|
||||
->errorCorrectionLevel($params->errorCorrectionLevel)
|
||||
->roundBlockSizeMode($params->roundBlockSizeMode);
|
||||
|
||||
return new QrCodeResponse($qrCodeBuilder->build());
|
||||
}
|
||||
|
|
|
@ -2,155 +2,70 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
// phpcs:disable
|
||||
// TODO Enable coding style checks again once code sniffer 3.7 is released https://github.com/squizlabs/PHP_CodeSniffer/issues/3474
|
||||
namespace Shlinkio\Shlink\Core\Config;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionClassConstant;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
|
||||
use function array_values;
|
||||
use function Functional\contains;
|
||||
use function Shlinkio\Shlink\Config\env;
|
||||
|
||||
// TODO Convert to enum after dropping PHP 8.0 support
|
||||
|
||||
/**
|
||||
* @method static EnvVars DELETE_SHORT_URL_THRESHOLD()
|
||||
* @method static EnvVars DB_DRIVER()
|
||||
* @method static EnvVars DB_NAME()
|
||||
* @method static EnvVars DB_USER()
|
||||
* @method static EnvVars DB_PASSWORD()
|
||||
* @method static EnvVars DB_HOST()
|
||||
* @method static EnvVars DB_UNIX_SOCKET()
|
||||
* @method static EnvVars DB_PORT()
|
||||
* @method static EnvVars GEOLITE_LICENSE_KEY()
|
||||
* @method static EnvVars REDIS_SERVERS()
|
||||
* @method static EnvVars REDIS_SENTINEL_SERVICE()
|
||||
* @method static EnvVars MERCURE_PUBLIC_HUB_URL()
|
||||
* @method static EnvVars MERCURE_INTERNAL_HUB_URL()
|
||||
* @method static EnvVars MERCURE_JWT_SECRET()
|
||||
* @method static EnvVars DEFAULT_QR_CODE_SIZE()
|
||||
* @method static EnvVars DEFAULT_QR_CODE_MARGIN()
|
||||
* @method static EnvVars DEFAULT_QR_CODE_FORMAT()
|
||||
* @method static EnvVars DEFAULT_QR_CODE_ERROR_CORRECTION()
|
||||
* @method static EnvVars DEFAULT_QR_CODE_ROUND_BLOCK_SIZE()
|
||||
* @method static EnvVars RABBITMQ_ENABLED()
|
||||
* @method static EnvVars RABBITMQ_HOST()
|
||||
* @method static EnvVars RABBITMQ_PORT()
|
||||
* @method static EnvVars RABBITMQ_USER()
|
||||
* @method static EnvVars RABBITMQ_PASSWORD()
|
||||
* @method static EnvVars RABBITMQ_VHOST()
|
||||
* @method static EnvVars DEFAULT_INVALID_SHORT_URL_REDIRECT()
|
||||
* @method static EnvVars DEFAULT_REGULAR_404_REDIRECT()
|
||||
* @method static EnvVars DEFAULT_BASE_URL_REDIRECT()
|
||||
* @method static EnvVars REDIRECT_STATUS_CODE()
|
||||
* @method static EnvVars REDIRECT_CACHE_LIFETIME()
|
||||
* @method static EnvVars BASE_PATH()
|
||||
* @method static EnvVars PORT()
|
||||
* @method static EnvVars TASK_WORKER_NUM()
|
||||
* @method static EnvVars WEB_WORKER_NUM()
|
||||
* @method static EnvVars ANONYMIZE_REMOTE_ADDR()
|
||||
* @method static EnvVars TRACK_ORPHAN_VISITS()
|
||||
* @method static EnvVars DISABLE_TRACK_PARAM()
|
||||
* @method static EnvVars DISABLE_TRACKING()
|
||||
* @method static EnvVars DISABLE_IP_TRACKING()
|
||||
* @method static EnvVars DISABLE_REFERRER_TRACKING()
|
||||
* @method static EnvVars DISABLE_UA_TRACKING()
|
||||
* @method static EnvVars DISABLE_TRACKING_FROM()
|
||||
* @method static EnvVars DEFAULT_SHORT_CODES_LENGTH()
|
||||
* @method static EnvVars IS_HTTPS_ENABLED()
|
||||
* @method static EnvVars DEFAULT_DOMAIN()
|
||||
* @method static EnvVars AUTO_RESOLVE_TITLES()
|
||||
* @method static EnvVars REDIRECT_APPEND_EXTRA_PATH()
|
||||
* @method static EnvVars TIMEZONE()
|
||||
* @method static EnvVars VISITS_WEBHOOKS()
|
||||
* @method static EnvVars NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS()
|
||||
*/
|
||||
final class EnvVars
|
||||
enum EnvVars: string
|
||||
{
|
||||
public const DELETE_SHORT_URL_THRESHOLD = 'DELETE_SHORT_URL_THRESHOLD';
|
||||
public const DB_DRIVER = 'DB_DRIVER';
|
||||
public const DB_NAME = 'DB_NAME';
|
||||
public const DB_USER = 'DB_USER';
|
||||
public const DB_PASSWORD = 'DB_PASSWORD';
|
||||
public const DB_HOST = 'DB_HOST';
|
||||
public const DB_UNIX_SOCKET = 'DB_UNIX_SOCKET';
|
||||
public const DB_PORT = 'DB_PORT';
|
||||
public const GEOLITE_LICENSE_KEY = 'GEOLITE_LICENSE_KEY';
|
||||
public const REDIS_SERVERS = 'REDIS_SERVERS';
|
||||
public const REDIS_SENTINEL_SERVICE = 'REDIS_SENTINEL_SERVICE';
|
||||
public const MERCURE_PUBLIC_HUB_URL = 'MERCURE_PUBLIC_HUB_URL';
|
||||
public const MERCURE_INTERNAL_HUB_URL = 'MERCURE_INTERNAL_HUB_URL';
|
||||
public const MERCURE_JWT_SECRET = 'MERCURE_JWT_SECRET';
|
||||
public const DEFAULT_QR_CODE_SIZE = 'DEFAULT_QR_CODE_SIZE';
|
||||
public const DEFAULT_QR_CODE_MARGIN = 'DEFAULT_QR_CODE_MARGIN';
|
||||
public const DEFAULT_QR_CODE_FORMAT = 'DEFAULT_QR_CODE_FORMAT';
|
||||
public const DEFAULT_QR_CODE_ERROR_CORRECTION = 'DEFAULT_QR_CODE_ERROR_CORRECTION';
|
||||
public const DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = 'DEFAULT_QR_CODE_ROUND_BLOCK_SIZE';
|
||||
public const RABBITMQ_ENABLED = 'RABBITMQ_ENABLED';
|
||||
public const RABBITMQ_HOST = 'RABBITMQ_HOST';
|
||||
public const RABBITMQ_PORT = 'RABBITMQ_PORT';
|
||||
public const RABBITMQ_USER = 'RABBITMQ_USER';
|
||||
public const RABBITMQ_PASSWORD = 'RABBITMQ_PASSWORD';
|
||||
public const RABBITMQ_VHOST = 'RABBITMQ_VHOST';
|
||||
public const DEFAULT_INVALID_SHORT_URL_REDIRECT = 'DEFAULT_INVALID_SHORT_URL_REDIRECT';
|
||||
public const DEFAULT_REGULAR_404_REDIRECT = 'DEFAULT_REGULAR_404_REDIRECT';
|
||||
public const DEFAULT_BASE_URL_REDIRECT = 'DEFAULT_BASE_URL_REDIRECT';
|
||||
public const REDIRECT_STATUS_CODE = 'REDIRECT_STATUS_CODE';
|
||||
public const REDIRECT_CACHE_LIFETIME = 'REDIRECT_CACHE_LIFETIME';
|
||||
public const BASE_PATH = 'BASE_PATH';
|
||||
public const PORT = 'PORT';
|
||||
public const TASK_WORKER_NUM = 'TASK_WORKER_NUM';
|
||||
public const WEB_WORKER_NUM = 'WEB_WORKER_NUM';
|
||||
public const ANONYMIZE_REMOTE_ADDR = 'ANONYMIZE_REMOTE_ADDR';
|
||||
public const TRACK_ORPHAN_VISITS = 'TRACK_ORPHAN_VISITS';
|
||||
public const DISABLE_TRACK_PARAM = 'DISABLE_TRACK_PARAM';
|
||||
public const DISABLE_TRACKING = 'DISABLE_TRACKING';
|
||||
public const DISABLE_IP_TRACKING = 'DISABLE_IP_TRACKING';
|
||||
public const DISABLE_REFERRER_TRACKING = 'DISABLE_REFERRER_TRACKING';
|
||||
public const DISABLE_UA_TRACKING = 'DISABLE_UA_TRACKING';
|
||||
public const DISABLE_TRACKING_FROM = 'DISABLE_TRACKING_FROM';
|
||||
public const DEFAULT_SHORT_CODES_LENGTH = 'DEFAULT_SHORT_CODES_LENGTH';
|
||||
public const IS_HTTPS_ENABLED = 'IS_HTTPS_ENABLED';
|
||||
public const DEFAULT_DOMAIN = 'DEFAULT_DOMAIN';
|
||||
public const AUTO_RESOLVE_TITLES = 'AUTO_RESOLVE_TITLES';
|
||||
public const REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
|
||||
public const TIMEZONE = 'TIMEZONE';
|
||||
case DELETE_SHORT_URL_THRESHOLD = 'DELETE_SHORT_URL_THRESHOLD';
|
||||
case DB_DRIVER = 'DB_DRIVER';
|
||||
case DB_NAME = 'DB_NAME';
|
||||
case DB_USER = 'DB_USER';
|
||||
case DB_PASSWORD = 'DB_PASSWORD';
|
||||
case DB_HOST = 'DB_HOST';
|
||||
case DB_UNIX_SOCKET = 'DB_UNIX_SOCKET';
|
||||
case DB_PORT = 'DB_PORT';
|
||||
case GEOLITE_LICENSE_KEY = 'GEOLITE_LICENSE_KEY';
|
||||
case REDIS_SERVERS = 'REDIS_SERVERS';
|
||||
case REDIS_SENTINEL_SERVICE = 'REDIS_SENTINEL_SERVICE';
|
||||
case MERCURE_PUBLIC_HUB_URL = 'MERCURE_PUBLIC_HUB_URL';
|
||||
case MERCURE_INTERNAL_HUB_URL = 'MERCURE_INTERNAL_HUB_URL';
|
||||
case MERCURE_JWT_SECRET = 'MERCURE_JWT_SECRET';
|
||||
case DEFAULT_QR_CODE_SIZE = 'DEFAULT_QR_CODE_SIZE';
|
||||
case DEFAULT_QR_CODE_MARGIN = 'DEFAULT_QR_CODE_MARGIN';
|
||||
case DEFAULT_QR_CODE_FORMAT = 'DEFAULT_QR_CODE_FORMAT';
|
||||
case DEFAULT_QR_CODE_ERROR_CORRECTION = 'DEFAULT_QR_CODE_ERROR_CORRECTION';
|
||||
case DEFAULT_QR_CODE_ROUND_BLOCK_SIZE = 'DEFAULT_QR_CODE_ROUND_BLOCK_SIZE';
|
||||
case RABBITMQ_ENABLED = 'RABBITMQ_ENABLED';
|
||||
case RABBITMQ_HOST = 'RABBITMQ_HOST';
|
||||
case RABBITMQ_PORT = 'RABBITMQ_PORT';
|
||||
case RABBITMQ_USER = 'RABBITMQ_USER';
|
||||
case RABBITMQ_PASSWORD = 'RABBITMQ_PASSWORD';
|
||||
case RABBITMQ_VHOST = 'RABBITMQ_VHOST';
|
||||
case DEFAULT_INVALID_SHORT_URL_REDIRECT = 'DEFAULT_INVALID_SHORT_URL_REDIRECT';
|
||||
case DEFAULT_REGULAR_404_REDIRECT = 'DEFAULT_REGULAR_404_REDIRECT';
|
||||
case DEFAULT_BASE_URL_REDIRECT = 'DEFAULT_BASE_URL_REDIRECT';
|
||||
case REDIRECT_STATUS_CODE = 'REDIRECT_STATUS_CODE';
|
||||
case REDIRECT_CACHE_LIFETIME = 'REDIRECT_CACHE_LIFETIME';
|
||||
case BASE_PATH = 'BASE_PATH';
|
||||
case PORT = 'PORT';
|
||||
case TASK_WORKER_NUM = 'TASK_WORKER_NUM';
|
||||
case WEB_WORKER_NUM = 'WEB_WORKER_NUM';
|
||||
case ANONYMIZE_REMOTE_ADDR = 'ANONYMIZE_REMOTE_ADDR';
|
||||
case TRACK_ORPHAN_VISITS = 'TRACK_ORPHAN_VISITS';
|
||||
case DISABLE_TRACK_PARAM = 'DISABLE_TRACK_PARAM';
|
||||
case DISABLE_TRACKING = 'DISABLE_TRACKING';
|
||||
case DISABLE_IP_TRACKING = 'DISABLE_IP_TRACKING';
|
||||
case DISABLE_REFERRER_TRACKING = 'DISABLE_REFERRER_TRACKING';
|
||||
case DISABLE_UA_TRACKING = 'DISABLE_UA_TRACKING';
|
||||
case DISABLE_TRACKING_FROM = 'DISABLE_TRACKING_FROM';
|
||||
case DEFAULT_SHORT_CODES_LENGTH = 'DEFAULT_SHORT_CODES_LENGTH';
|
||||
case IS_HTTPS_ENABLED = 'IS_HTTPS_ENABLED';
|
||||
case DEFAULT_DOMAIN = 'DEFAULT_DOMAIN';
|
||||
case AUTO_RESOLVE_TITLES = 'AUTO_RESOLVE_TITLES';
|
||||
case REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
|
||||
case TIMEZONE = 'TIMEZONE';
|
||||
/** @deprecated */
|
||||
public const VISITS_WEBHOOKS = 'VISITS_WEBHOOKS';
|
||||
case VISITS_WEBHOOKS = 'VISITS_WEBHOOKS';
|
||||
/** @deprecated */
|
||||
public const NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS = 'NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS';
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function cases(): array
|
||||
{
|
||||
static $constants;
|
||||
if ($constants !== null) {
|
||||
return $constants;
|
||||
}
|
||||
|
||||
$ref = new ReflectionClass(self::class);
|
||||
return $constants = array_values($ref->getConstants(ReflectionClassConstant::IS_PUBLIC));
|
||||
}
|
||||
|
||||
private function __construct(private string $envVar)
|
||||
{
|
||||
}
|
||||
|
||||
public static function __callStatic(string $name, array $arguments): self
|
||||
{
|
||||
if (! contains(self::cases(), $name)) {
|
||||
throw new InvalidArgumentException('Invalid env var: "' . $name . '"');
|
||||
}
|
||||
|
||||
return new self($name);
|
||||
}
|
||||
case NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS = 'NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS';
|
||||
|
||||
public function loadFromEnv(mixed $default = null): mixed
|
||||
{
|
||||
return env($this->envVar, $default);
|
||||
return env($this->value, $default);
|
||||
}
|
||||
|
||||
public function existsInEnv(): bool
|
||||
|
|
|
@ -13,7 +13,9 @@ use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
|
|||
use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface;
|
||||
|
||||
use function Functional\compose;
|
||||
use function Functional\id;
|
||||
use function str_replace;
|
||||
use function urlencode;
|
||||
|
||||
class NotFoundRedirectResolver implements NotFoundRedirectResolverInterface
|
||||
{
|
||||
|
@ -71,10 +73,10 @@ class NotFoundRedirectResolver implements NotFoundRedirectResolverInterface
|
|||
$replacePlaceholderForPattern(self::ORIGINAL_PATH_PLACEHOLDER, $path, $modifier),
|
||||
);
|
||||
$replacePlaceholdersInPath = compose(
|
||||
$replacePlaceholders('\Functional\id'),
|
||||
static fn (?string $path) => $path === null ? null : str_replace('//', '/', $path), // Fix duplicated bars
|
||||
$replacePlaceholders(id(...)),
|
||||
static fn (?string $path) => $path === null ? null : str_replace('//', '/', $path),
|
||||
);
|
||||
$replacePlaceholdersInQuery = $replacePlaceholders('\urlencode');
|
||||
$replacePlaceholdersInQuery = $replacePlaceholders(urlencode(...));
|
||||
|
||||
return $redirectUri
|
||||
->withPath($replacePlaceholdersInPath($redirectUri->getPath()))
|
||||
|
|
|
@ -9,9 +9,9 @@ use JsonSerializable;
|
|||
final class NotFoundRedirects implements JsonSerializable
|
||||
{
|
||||
private function __construct(
|
||||
private ?string $baseUrlRedirect,
|
||||
private ?string $regular404Redirect,
|
||||
private ?string $invalidShortUrlRedirect,
|
||||
public readonly ?string $baseUrlRedirect,
|
||||
public readonly ?string $regular404Redirect,
|
||||
public readonly ?string $invalidShortUrlRedirect,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -33,21 +33,6 @@ final class NotFoundRedirects implements JsonSerializable
|
|||
return new self($config->baseUrlRedirect(), $config->regular404Redirect(), $config->invalidShortUrlRedirect());
|
||||
}
|
||||
|
||||
public function baseUrlRedirect(): ?string
|
||||
{
|
||||
return $this->baseUrlRedirect;
|
||||
}
|
||||
|
||||
public function regular404Redirect(): ?string
|
||||
{
|
||||
return $this->regular404Redirect;
|
||||
}
|
||||
|
||||
public function invalidShortUrlRedirect(): ?string
|
||||
{
|
||||
return $this->invalidShortUrlRedirect;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -12,9 +12,9 @@ use Shlinkio\Shlink\Core\Entity\Domain;
|
|||
final class DomainItem implements JsonSerializable
|
||||
{
|
||||
private function __construct(
|
||||
private string $authority,
|
||||
private NotFoundRedirectConfigInterface $notFoundRedirectConfig,
|
||||
private bool $isDefault,
|
||||
private readonly string $authority,
|
||||
public readonly NotFoundRedirectConfigInterface $notFoundRedirectConfig,
|
||||
public readonly bool $isDefault,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,9 @@ final class DomainItem implements JsonSerializable
|
|||
return new self($domain->getAuthority(), $domain, false);
|
||||
}
|
||||
|
||||
public static function forDefaultDomain(string $authority, NotFoundRedirectConfigInterface $config): self
|
||||
public static function forDefaultDomain(string $defaultDomain, NotFoundRedirectConfigInterface $config): self
|
||||
{
|
||||
return new self($authority, $config, true);
|
||||
return new self($defaultDomain, $config, true);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
|
@ -41,14 +41,4 @@ final class DomainItem implements JsonSerializable
|
|||
{
|
||||
return $this->authority;
|
||||
}
|
||||
|
||||
public function isDefault(): bool
|
||||
{
|
||||
return $this->isDefault;
|
||||
}
|
||||
|
||||
public function notFoundRedirectConfig(): NotFoundRedirectConfigInterface
|
||||
{
|
||||
return $this->notFoundRedirectConfig;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Core\Domain\Repository;
|
|||
use Doctrine\ORM\Query\Expr\Join;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||
use Happyr\DoctrineSpecification\Spec;
|
||||
use Shlinkio\Shlink\Core\Domain\Spec\IsDomain;
|
||||
use Shlinkio\Shlink\Core\Entity\Domain;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
|
@ -77,10 +76,9 @@ class DomainRepository extends EntitySpecificationRepository implements DomainRe
|
|||
// FIXME The $apiKey->spec() method cannot be used here, as it returns a single spec which assumes the
|
||||
// ShortUrl is the root entity. Here, the Domain is the root entity.
|
||||
// Think on a way to centralize the conditional behavior and make $apiKey->spec() more flexible.
|
||||
yield from $apiKey?->mapRoles(fn (string $roleName, array $meta) => match ($roleName) {
|
||||
yield from $apiKey?->mapRoles(fn (Role $role, array $meta) => match ($role) {
|
||||
Role::DOMAIN_SPECIFIC => ['d', new IsDomain(Role::domainIdFromMeta($meta))],
|
||||
Role::AUTHORED_SHORT_URLS => ['s', new BelongsToApiKey($apiKey)],
|
||||
default => [null, Spec::andX()],
|
||||
}) ?? [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,8 +66,8 @@ class Domain extends AbstractEntity implements JsonSerializable, NotFoundRedirec
|
|||
|
||||
public function configureNotFoundRedirects(NotFoundRedirects $redirects): void
|
||||
{
|
||||
$this->baseUrlRedirect = $redirects->baseUrlRedirect();
|
||||
$this->regular404Redirect = $redirects->regular404Redirect();
|
||||
$this->invalidShortUrlRedirect = $redirects->invalidShortUrlRedirect();
|
||||
$this->baseUrlRedirect = $redirects->baseUrlRedirect;
|
||||
$this->regular404Redirect = $redirects->regular404Redirect;
|
||||
$this->invalidShortUrlRedirect = $redirects->invalidShortUrlRedirect;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
|||
use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver;
|
||||
use Shlinkio\Shlink\Core\Validation\ShortUrlInputFilter;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
|
@ -174,7 +175,7 @@ class ShortUrl extends AbstractEntity
|
|||
{
|
||||
/** @var Selectable $visits */
|
||||
$visits = $this->visits;
|
||||
$criteria = Criteria::create()->where(Criteria::expr()->eq('type', Visit::TYPE_IMPORTED))
|
||||
$criteria = Criteria::create()->where(Criteria::expr()->eq('type', VisitType::IMPORTED))
|
||||
->orderBy(['id' => 'DESC'])
|
||||
->setMaxResults(1);
|
||||
|
||||
|
|
|
@ -11,29 +11,24 @@ use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
|||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitLocationInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit;
|
||||
|
||||
use function Shlinkio\Shlink\Core\isCrawler;
|
||||
|
||||
class Visit extends AbstractEntity implements JsonSerializable
|
||||
{
|
||||
public const TYPE_VALID_SHORT_URL = 'valid_short_url';
|
||||
public const TYPE_IMPORTED = 'imported';
|
||||
public const TYPE_INVALID_SHORT_URL = 'invalid_short_url';
|
||||
public const TYPE_BASE_URL = 'base_url';
|
||||
public const TYPE_REGULAR_404 = 'regular_404';
|
||||
|
||||
private string $referer;
|
||||
private Chronos $date;
|
||||
private ?string $remoteAddr = null;
|
||||
private ?string $visitedUrl = null;
|
||||
private string $userAgent;
|
||||
private string $type;
|
||||
private VisitType $type;
|
||||
private ?ShortUrl $shortUrl;
|
||||
private ?VisitLocation $visitLocation = null;
|
||||
private bool $potentialBot;
|
||||
|
||||
private function __construct(?ShortUrl $shortUrl, string $type)
|
||||
private function __construct(?ShortUrl $shortUrl, VisitType $type)
|
||||
{
|
||||
$this->shortUrl = $shortUrl;
|
||||
$this->date = Chronos::now();
|
||||
|
@ -42,7 +37,7 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
|
||||
public static function forValidShortUrl(ShortUrl $shortUrl, Visitor $visitor, bool $anonymize = true): self
|
||||
{
|
||||
$instance = new self($shortUrl, self::TYPE_VALID_SHORT_URL);
|
||||
$instance = new self($shortUrl, VisitType::VALID_SHORT_URL);
|
||||
$instance->hydrateFromVisitor($visitor, $anonymize);
|
||||
|
||||
return $instance;
|
||||
|
@ -50,7 +45,7 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
|
||||
public static function fromImport(ShortUrl $shortUrl, ImportedShlinkVisit $importedVisit): self
|
||||
{
|
||||
$instance = new self($shortUrl, self::TYPE_IMPORTED);
|
||||
$instance = new self($shortUrl, VisitType::IMPORTED);
|
||||
$instance->userAgent = $importedVisit->userAgent();
|
||||
$instance->potentialBot = isCrawler($instance->userAgent);
|
||||
$instance->referer = $importedVisit->referer();
|
||||
|
@ -64,7 +59,7 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
|
||||
public static function forBasePath(Visitor $visitor, bool $anonymize = true): self
|
||||
{
|
||||
$instance = new self(null, self::TYPE_BASE_URL);
|
||||
$instance = new self(null, VisitType::BASE_URL);
|
||||
$instance->hydrateFromVisitor($visitor, $anonymize);
|
||||
|
||||
return $instance;
|
||||
|
@ -72,7 +67,7 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
|
||||
public static function forInvalidShortUrl(Visitor $visitor, bool $anonymize = true): self
|
||||
{
|
||||
$instance = new self(null, self::TYPE_INVALID_SHORT_URL);
|
||||
$instance = new self(null, VisitType::INVALID_SHORT_URL);
|
||||
$instance->hydrateFromVisitor($visitor, $anonymize);
|
||||
|
||||
return $instance;
|
||||
|
@ -80,7 +75,7 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
|
||||
public static function forRegularNotFound(Visitor $visitor, bool $anonymize = true): self
|
||||
{
|
||||
$instance = new self(null, self::TYPE_REGULAR_404);
|
||||
$instance = new self(null, VisitType::REGULAR_404);
|
||||
$instance->hydrateFromVisitor($visitor, $anonymize);
|
||||
|
||||
return $instance;
|
||||
|
@ -88,10 +83,10 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
|
||||
private function hydrateFromVisitor(Visitor $visitor, bool $anonymize = true): void
|
||||
{
|
||||
$this->userAgent = $visitor->getUserAgent();
|
||||
$this->referer = $visitor->getReferer();
|
||||
$this->remoteAddr = $this->processAddress($anonymize, $visitor->getRemoteAddress());
|
||||
$this->visitedUrl = $visitor->getVisitedUrl();
|
||||
$this->userAgent = $visitor->userAgent;
|
||||
$this->referer = $visitor->referer;
|
||||
$this->remoteAddr = $this->processAddress($anonymize, $visitor->remoteAddress);
|
||||
$this->visitedUrl = $visitor->visitedUrl;
|
||||
$this->potentialBot = $visitor->isPotentialBot();
|
||||
}
|
||||
|
||||
|
@ -150,7 +145,7 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
return $this->visitedUrl;
|
||||
}
|
||||
|
||||
public function type(): string
|
||||
public function type(): VisitType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
@ -159,11 +154,19 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
* Needed only for ArrayCollections to be able to apply criteria filtering
|
||||
* @internal
|
||||
*/
|
||||
public function getType(): string
|
||||
public function getType(): VisitType
|
||||
{
|
||||
return $this->type();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getDate(): Chronos
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
|
@ -174,12 +177,4 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
'potentialBot' => $this->potentialBot,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getDate(): Chronos
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ namespace Shlinkio\Shlink\Core\ErrorHandler\Model;
|
|||
use Mezzio\Router\RouteResult;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Core\Action\RedirectAction;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
|
||||
use function rtrim;
|
||||
|
||||
class NotFoundType
|
||||
{
|
||||
private function __construct(private string $type)
|
||||
private function __construct(private readonly VisitType $type)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -24,10 +24,10 @@ class NotFoundType
|
|||
$isBaseUrl = rtrim($request->getUri()->getPath(), '/') === $basePath;
|
||||
|
||||
$type = match (true) {
|
||||
$isBaseUrl => Visit::TYPE_BASE_URL,
|
||||
$routeResult->isFailure() => Visit::TYPE_REGULAR_404,
|
||||
$routeResult->getMatchedRouteName() === RedirectAction::class => Visit::TYPE_INVALID_SHORT_URL,
|
||||
default => self::class,
|
||||
$isBaseUrl => VisitType::BASE_URL,
|
||||
$routeResult->isFailure() => VisitType::REGULAR_404,
|
||||
$routeResult->getMatchedRouteName() === RedirectAction::class => VisitType::INVALID_SHORT_URL,
|
||||
default => VisitType::VALID_SHORT_URL,
|
||||
};
|
||||
|
||||
return new self($type);
|
||||
|
@ -35,16 +35,16 @@ class NotFoundType
|
|||
|
||||
public function isBaseUrl(): bool
|
||||
{
|
||||
return $this->type === Visit::TYPE_BASE_URL;
|
||||
return $this->type === VisitType::BASE_URL;
|
||||
}
|
||||
|
||||
public function isRegularNotFound(): bool
|
||||
{
|
||||
return $this->type === Visit::TYPE_REGULAR_404;
|
||||
return $this->type === VisitType::REGULAR_404;
|
||||
}
|
||||
|
||||
public function isInvalidShortUrl(): bool
|
||||
{
|
||||
return $this->type === Visit::TYPE_INVALID_SHORT_URL;
|
||||
return $this->type === VisitType::INVALID_SHORT_URL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,15 +8,10 @@ use JsonSerializable;
|
|||
|
||||
abstract class AbstractVisitEvent implements JsonSerializable
|
||||
{
|
||||
public function __construct(protected string $visitId)
|
||||
public function __construct(public readonly string $visitId)
|
||||
{
|
||||
}
|
||||
|
||||
public function visitId(): string
|
||||
{
|
||||
return $this->visitId;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['visitId' => $this->visitId];
|
||||
|
|
|
@ -6,13 +6,8 @@ namespace Shlinkio\Shlink\Core\EventDispatcher\Event;
|
|||
|
||||
final class UrlVisited extends AbstractVisitEvent
|
||||
{
|
||||
public function __construct(string $visitId, private ?string $originalIpAddress = null)
|
||||
public function __construct(string $visitId, public readonly ?string $originalIpAddress = null)
|
||||
{
|
||||
parent::__construct($visitId);
|
||||
}
|
||||
|
||||
public function originalIpAddress(): ?string
|
||||
{
|
||||
return $this->originalIpAddress;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class LocateVisit
|
|||
|
||||
public function __invoke(UrlVisited $shortUrlVisited): void
|
||||
{
|
||||
$visitId = $shortUrlVisited->visitId();
|
||||
$visitId = $shortUrlVisited->visitId;
|
||||
|
||||
/** @var Visit|null $visit */
|
||||
$visit = $this->em->find(Visit::class, $visitId);
|
||||
|
@ -41,7 +41,7 @@ class LocateVisit
|
|||
return;
|
||||
}
|
||||
|
||||
$this->locateVisit($visitId, $shortUrlVisited->originalIpAddress(), $visit);
|
||||
$this->locateVisit($visitId, $shortUrlVisited->originalIpAddress, $visit);
|
||||
$this->eventDispatcher->dispatch(new VisitLocated($visitId));
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class NotifyVisitToMercure
|
|||
|
||||
public function __invoke(VisitLocated $shortUrlLocated): void
|
||||
{
|
||||
$visitId = $shortUrlLocated->visitId();
|
||||
$visitId = $shortUrlLocated->visitId;
|
||||
|
||||
/** @var Visit|null $visit */
|
||||
$visit = $this->em->find(Visit::class, $visitId);
|
||||
|
|
|
@ -37,7 +37,7 @@ class NotifyVisitToRabbitMq
|
|||
return;
|
||||
}
|
||||
|
||||
$visitId = $shortUrlLocated->visitId();
|
||||
$visitId = $shortUrlLocated->visitId;
|
||||
$visit = $this->em->find(Visit::class, $visitId);
|
||||
|
||||
if ($visit === null) {
|
||||
|
|
|
@ -40,7 +40,7 @@ class NotifyVisitToWebHooks
|
|||
return;
|
||||
}
|
||||
|
||||
$visitId = $shortUrlLocated->visitId();
|
||||
$visitId = $shortUrlLocated->visitId;
|
||||
|
||||
/** @var Visit|null $visit */
|
||||
$visit = $this->em->find(Visit::class, $visitId);
|
||||
|
|
|
@ -20,8 +20,8 @@ class DeleteShortUrlException extends DomainException implements ProblemDetailsE
|
|||
|
||||
public static function fromVisitsThreshold(int $threshold, ShortUrlIdentifier $identifier): self
|
||||
{
|
||||
$shortCode = $identifier->shortCode();
|
||||
$domain = $identifier->domain();
|
||||
$shortCode = $identifier->shortCode;
|
||||
$domain = $identifier->domain;
|
||||
$suffix = $domain === null ? '' : sprintf(' for domain "%s"', $domain);
|
||||
$e = new self(sprintf(
|
||||
'Impossible to delete short URL with short code "%s"%s, since it has more than "%s" visits.',
|
||||
|
|
|
@ -20,8 +20,8 @@ class ShortUrlNotFoundException extends DomainException implements ProblemDetail
|
|||
|
||||
public static function fromNotFound(ShortUrlIdentifier $identifier): self
|
||||
{
|
||||
$shortCode = $identifier->shortCode();
|
||||
$domain = $identifier->domain();
|
||||
$shortCode = $identifier->shortCode;
|
||||
$domain = $identifier->domain;
|
||||
$suffix = $domain === null ? '' : sprintf(' for domain "%s"', $domain);
|
||||
$e = new self(sprintf('No URL found with short code "%s"%s', $shortCode, $suffix));
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use function sprintf;
|
|||
|
||||
final class ShortUrlImporting
|
||||
{
|
||||
private function __construct(private ShortUrl $shortUrl, private bool $isNew)
|
||||
private function __construct(private readonly ShortUrl $shortUrl, private readonly bool $isNew)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ abstract class AbstractInfinitePaginableListParams
|
|||
{
|
||||
private const FIRST_PAGE = 1;
|
||||
|
||||
private int $page;
|
||||
private int $itemsPerPage;
|
||||
public readonly int $page;
|
||||
public readonly int $itemsPerPage;
|
||||
|
||||
protected function __construct(?int $page, ?int $itemsPerPage)
|
||||
{
|
||||
|
@ -28,14 +28,4 @@ abstract class AbstractInfinitePaginableListParams
|
|||
{
|
||||
return $itemsPerPage === null || $itemsPerPage < 0 ? Paginator::ALL_ITEMS : $itemsPerPage;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function getItemsPerPage(): int
|
||||
{
|
||||
return $this->itemsPerPage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ final class Ordering
|
|||
{
|
||||
private const DEFAULT_DIR = 'ASC';
|
||||
|
||||
private function __construct(private ?string $field, private string $dir)
|
||||
private function __construct(public readonly ?string $field, public readonly string $direction)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -26,16 +26,6 @@ final class Ordering
|
|||
return self::fromTuple([null, null]);
|
||||
}
|
||||
|
||||
public function orderField(): ?string
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
public function orderDirection(): string
|
||||
{
|
||||
return $this->dir;
|
||||
}
|
||||
|
||||
public function hasOrderField(): bool
|
||||
{
|
||||
return $this->field !== null;
|
||||
|
|
|
@ -10,7 +10,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
|
||||
final class ShortUrlIdentifier
|
||||
{
|
||||
public function __construct(private string $shortCode, private ?string $domain = null)
|
||||
private function __construct(public readonly string $shortCode, public readonly ?string $domain = null)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -54,14 +54,4 @@ final class ShortUrlIdentifier
|
|||
{
|
||||
return new self($shortCode, $domain);
|
||||
}
|
||||
|
||||
public function shortCode(): string
|
||||
{
|
||||
return $this->shortCode;
|
||||
}
|
||||
|
||||
public function domain(): ?string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\Model;
|
|||
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||
use Shlinkio\Shlink\Core\Validation\ShortUrlsParamsInputFilter;
|
||||
|
||||
use function Shlinkio\Shlink\Common\buildDateRange;
|
||||
|
@ -15,15 +16,12 @@ final class ShortUrlsParams
|
|||
{
|
||||
public const ORDERABLE_FIELDS = ['longUrl', 'shortCode', 'dateCreated', 'title', 'visits'];
|
||||
public const DEFAULT_ITEMS_PER_PAGE = 10;
|
||||
public const TAGS_MODE_ANY = 'any';
|
||||
public const TAGS_MODE_ALL = 'all';
|
||||
|
||||
private int $page;
|
||||
private int $itemsPerPage;
|
||||
private ?string $searchTerm;
|
||||
private array $tags;
|
||||
/** @var self::TAGS_MODE_ANY|self::TAGS_MODE_ALL */
|
||||
private string $tagsMode = self::TAGS_MODE_ANY;
|
||||
private TagsMode $tagsMode = TagsMode::ANY;
|
||||
private Ordering $orderBy;
|
||||
private ?DateRange $dateRange;
|
||||
|
||||
|
@ -68,7 +66,16 @@ final class ShortUrlsParams
|
|||
$this->itemsPerPage = (int) (
|
||||
$inputFilter->getValue(ShortUrlsParamsInputFilter::ITEMS_PER_PAGE) ?? self::DEFAULT_ITEMS_PER_PAGE
|
||||
);
|
||||
$this->tagsMode = $inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS_MODE) ?? self::TAGS_MODE_ANY;
|
||||
$this->tagsMode = $this->resolveTagsMode($inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS_MODE));
|
||||
}
|
||||
|
||||
private function resolveTagsMode(?string $rawTagsMode): TagsMode
|
||||
{
|
||||
if ($rawTagsMode === null) {
|
||||
return TagsMode::ANY;
|
||||
}
|
||||
|
||||
return TagsMode::tryFrom($rawTagsMode) ?? TagsMode::ANY;
|
||||
}
|
||||
|
||||
public function page(): int
|
||||
|
@ -101,10 +108,7 @@ final class ShortUrlsParams
|
|||
return $this->dateRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self::TAGS_MODE_ANY|self::TAGS_MODE_ALL
|
||||
*/
|
||||
public function tagsMode(): string
|
||||
public function tagsMode(): TagsMode
|
||||
{
|
||||
return $this->tagsMode;
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ final class Visitor
|
|||
public const REMOTE_ADDRESS_MAX_LENGTH = 256;
|
||||
public const VISITED_URL_MAX_LENGTH = 2048;
|
||||
|
||||
private string $userAgent;
|
||||
private string $referer;
|
||||
private string $visitedUrl;
|
||||
private ?string $remoteAddress;
|
||||
public readonly string $userAgent;
|
||||
public readonly string $referer;
|
||||
public readonly string $visitedUrl;
|
||||
public readonly ?string $remoteAddress;
|
||||
private bool $potentialBot;
|
||||
|
||||
public function __construct(string $userAgent, string $referer, ?string $remoteAddress, string $visitedUrl)
|
||||
|
@ -61,26 +61,6 @@ final class Visitor
|
|||
return new self('cf-facebook', '', null, '');
|
||||
}
|
||||
|
||||
public function getUserAgent(): string
|
||||
{
|
||||
return $this->userAgent;
|
||||
}
|
||||
|
||||
public function getReferer(): string
|
||||
{
|
||||
return $this->referer;
|
||||
}
|
||||
|
||||
public function getRemoteAddress(): ?string
|
||||
{
|
||||
return $this->remoteAddress;
|
||||
}
|
||||
|
||||
public function getVisitedUrl(): string
|
||||
{
|
||||
return $this->visitedUrl;
|
||||
}
|
||||
|
||||
public function isPotentialBot(): bool
|
||||
{
|
||||
return $this->potentialBot;
|
||||
|
|
|
@ -10,13 +10,13 @@ use function Shlinkio\Shlink\Core\parseDateRangeFromQuery;
|
|||
|
||||
final class VisitsParams extends AbstractInfinitePaginableListParams
|
||||
{
|
||||
private DateRange $dateRange;
|
||||
public readonly DateRange $dateRange;
|
||||
|
||||
public function __construct(
|
||||
?DateRange $dateRange = null,
|
||||
?int $page = null,
|
||||
?int $itemsPerPage = null,
|
||||
private bool $excludeBots = false,
|
||||
public readonly bool $excludeBots = false,
|
||||
) {
|
||||
parent::__construct($page, $itemsPerPage);
|
||||
$this->dateRange = $dateRange ?? DateRange::emptyInstance();
|
||||
|
@ -31,14 +31,4 @@ final class VisitsParams extends AbstractInfinitePaginableListParams
|
|||
isset($query['excludeBots']),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDateRange(): DateRange
|
||||
{
|
||||
return $this->dateRange;
|
||||
}
|
||||
|
||||
public function excludeBots(): bool
|
||||
{
|
||||
return $this->excludeBots;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
|||
use Shlinkio\Shlink\Core\Model\Ordering;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
|
||||
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
|
||||
|
@ -47,8 +47,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
|||
|
||||
private function processOrderByForList(QueryBuilder $qb, Ordering $orderBy): array
|
||||
{
|
||||
$fieldName = $orderBy->orderField();
|
||||
$order = $orderBy->orderDirection();
|
||||
$fieldName = $orderBy->field;
|
||||
$order = $orderBy->direction;
|
||||
|
||||
if ($fieldName === 'visits') {
|
||||
// FIXME This query is inefficient.
|
||||
|
@ -116,8 +116,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
|||
|
||||
// Filter by tags if provided
|
||||
if (! empty($tags)) {
|
||||
$tagsMode = $filtering->tagsMode() ?? ShortUrlsParams::TAGS_MODE_ANY;
|
||||
$tagsMode === ShortUrlsParams::TAGS_MODE_ANY
|
||||
$tagsMode = $filtering->tagsMode() ?? TagsMode::ANY;
|
||||
$tagsMode === TagsMode::ANY
|
||||
? $qb->join('s.tags', 't')->andWhere($qb->expr()->in('t.name', $tags))
|
||||
: $this->joinAllTags($qb, $tags);
|
||||
}
|
||||
|
@ -146,8 +146,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
|||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$query->setMaxResults(1)
|
||||
->setParameters([
|
||||
'shortCode' => $identifier->shortCode(),
|
||||
'domain' => $identifier->domain(),
|
||||
'shortCode' => $identifier->shortCode,
|
||||
'domain' => $identifier->domain,
|
||||
]);
|
||||
|
||||
// Since we ordered by domain, we will have first the URL matching provided domain, followed by the one
|
||||
|
@ -198,10 +198,10 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
|||
$qb->from(ShortUrl::class, 's')
|
||||
->where($qb->expr()->isNotNull('s.shortCode'))
|
||||
->andWhere($qb->expr()->eq('s.shortCode', ':slug'))
|
||||
->setParameter('slug', $identifier->shortCode())
|
||||
->setParameter('slug', $identifier->shortCode)
|
||||
->setMaxResults(1);
|
||||
|
||||
$this->whereDomainIs($qb, $identifier->domain());
|
||||
$this->whereDomainIs($qb, $identifier->domain);
|
||||
|
||||
$this->applySpecification($qb, $spec, 's');
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
|||
*/
|
||||
public function findTagsWithInfo(?TagsListFiltering $filtering = null): array
|
||||
{
|
||||
$orderField = $filtering?->orderBy()?->orderField();
|
||||
$orderDir = $filtering?->orderBy()?->orderDirection();
|
||||
$orderField = $filtering?->orderBy?->field;
|
||||
$orderDir = $filtering?->orderBy?->direction;
|
||||
$orderMainQuery = contains(['shortUrlsCount', 'visitsCount'], $orderField);
|
||||
|
||||
$conn = $this->getEntityManager()->getConnection();
|
||||
|
@ -51,16 +51,16 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
|||
|
||||
if (! $orderMainQuery) {
|
||||
$subQb->orderBy('t.name', $orderDir ?? 'ASC')
|
||||
->setMaxResults($filtering?->limit() ?? PHP_INT_MAX)
|
||||
->setFirstResult($filtering?->offset() ?? 0);
|
||||
->setMaxResults($filtering?->limit ?? PHP_INT_MAX)
|
||||
->setFirstResult($filtering?->offset ?? 0);
|
||||
}
|
||||
|
||||
$searchTerm = $filtering?->searchTerm();
|
||||
$searchTerm = $filtering?->searchTerm;
|
||||
if ($searchTerm !== null) {
|
||||
$subQb->andWhere($subQb->expr()->like('t.name', $conn->quote('%' . $searchTerm . '%')));
|
||||
}
|
||||
|
||||
$apiKey = $filtering?->apiKey();
|
||||
$apiKey = $filtering?->apiKey;
|
||||
$this->applySpecification($subQb, new WithInlinedApiKeySpecsEnsuringJoin($apiKey), 't');
|
||||
|
||||
// A native query builder needs to be used here, because DQL and ORM query builders do not support
|
||||
|
@ -81,14 +81,13 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
|||
->groupBy('t.id_0', 't.name_1');
|
||||
|
||||
// Apply API key role conditions to the native query too, as they will affect the amounts on the aggregates
|
||||
$apiKey?->mapRoles(static fn (string $roleName, array $meta) => match ($roleName) {
|
||||
$apiKey?->mapRoles(static fn (Role $role, array $meta) => match ($role) {
|
||||
Role::DOMAIN_SPECIFIC => $nativeQb->andWhere(
|
||||
$nativeQb->expr()->eq('s.domain_id', $conn->quote(Role::domainIdFromMeta($meta))),
|
||||
),
|
||||
Role::AUTHORED_SHORT_URLS => $nativeQb->andWhere(
|
||||
$nativeQb->expr()->eq('s.author_api_key_id', $conn->quote($apiKey->getId())),
|
||||
),
|
||||
default => $nativeQb,
|
||||
});
|
||||
|
||||
if ($orderMainQuery) {
|
||||
|
@ -97,8 +96,8 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
|||
$orderField === 'shortUrlsCount' ? 'short_urls_count' : 'visits_count',
|
||||
$orderDir ?? 'ASC',
|
||||
)
|
||||
->setMaxResults($filtering?->limit() ?? PHP_INT_MAX)
|
||||
->setFirstResult($filtering?->offset() ?? 0);
|
||||
->setMaxResults($filtering?->limit ?? PHP_INT_MAX)
|
||||
->setFirstResult($filtering?->offset ?? 0);
|
||||
}
|
||||
|
||||
// Add ordering by tag name, as a fallback in case of same amount, or as default ordering
|
||||
|
|
|
@ -86,7 +86,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
public function findVisitsByShortCode(ShortUrlIdentifier $identifier, VisitsListFiltering $filtering): array
|
||||
{
|
||||
$qb = $this->createVisitsByShortCodeQueryBuilder($identifier, $filtering);
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countVisitsByShortCode(ShortUrlIdentifier $identifier, VisitsCountFiltering $filtering): int
|
||||
|
@ -103,7 +103,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
): QueryBuilder {
|
||||
/** @var ShortUrlRepositoryInterface $shortUrlRepo */
|
||||
$shortUrlRepo = $this->getEntityManager()->getRepository(ShortUrl::class);
|
||||
$shortUrlId = $shortUrlRepo->findOne($identifier, $filtering->apiKey()?->spec())?->getId() ?? '-1';
|
||||
$shortUrlId = $shortUrlRepo->findOne($identifier, $filtering->apiKey?->spec())?->getId() ?? '-1';
|
||||
|
||||
// Parameters in this query need to be part of the query itself, as we need to use it as sub-query later
|
||||
// Since they are not provided by the caller, it's reasonably safe
|
||||
|
@ -111,12 +111,12 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
$qb->from(Visit::class, 'v')
|
||||
->where($qb->expr()->eq('v.shortUrl', $shortUrlId));
|
||||
|
||||
if ($filtering->excludeBots()) {
|
||||
if ($filtering->excludeBots) {
|
||||
$qb->andWhere($qb->expr()->eq('v.potentialBot', 'false'));
|
||||
}
|
||||
|
||||
// Apply date range filtering
|
||||
$this->applyDatesInline($qb, $filtering->dateRange());
|
||||
$this->applyDatesInline($qb, $filtering->dateRange);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
public function findVisitsByTag(string $tag, VisitsListFiltering $filtering): array
|
||||
{
|
||||
$qb = $this->createVisitsByTagQueryBuilder($tag, $filtering);
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countVisitsByTag(string $tag, VisitsCountFiltering $filtering): int
|
||||
|
@ -144,12 +144,12 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
->join('s.tags', 't')
|
||||
->where($qb->expr()->eq('t.name', $this->getEntityManager()->getConnection()->quote($tag)));
|
||||
|
||||
if ($filtering->excludeBots()) {
|
||||
if ($filtering->excludeBots) {
|
||||
$qb->andWhere($qb->expr()->eq('v.potentialBot', 'false'));
|
||||
}
|
||||
|
||||
$this->applyDatesInline($qb, $filtering->dateRange());
|
||||
$this->applySpecification($qb, $filtering->apiKey()?->inlinedSpec(), 'v');
|
||||
$this->applyDatesInline($qb, $filtering->dateRange);
|
||||
$this->applySpecification($qb, $filtering->apiKey?->inlinedSpec(), 'v');
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
public function findVisitsByDomain(string $domain, VisitsListFiltering $filtering): array
|
||||
{
|
||||
$qb = $this->createVisitsByDomainQueryBuilder($domain, $filtering);
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countVisitsByDomain(string $domain, VisitsCountFiltering $filtering): int
|
||||
|
@ -185,12 +185,12 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
->where($qb->expr()->eq('d.authority', $this->getEntityManager()->getConnection()->quote($domain)));
|
||||
}
|
||||
|
||||
if ($filtering->excludeBots()) {
|
||||
if ($filtering->excludeBots) {
|
||||
$qb->andWhere($qb->expr()->eq('v.potentialBot', 'false'));
|
||||
}
|
||||
|
||||
$this->applyDatesInline($qb, $filtering->dateRange());
|
||||
$this->applySpecification($qb, $filtering->apiKey()?->inlinedSpec(), 'v');
|
||||
$this->applyDatesInline($qb, $filtering->dateRange);
|
||||
$this->applySpecification($qb, $filtering->apiKey?->inlinedSpec(), 'v');
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
{
|
||||
$qb = $this->createAllVisitsQueryBuilder($filtering);
|
||||
$qb->andWhere($qb->expr()->isNull('v.shortUrl'));
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countOrphanVisits(VisitsCountFiltering $filtering): int
|
||||
|
@ -215,9 +215,9 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
$qb = $this->createAllVisitsQueryBuilder($filtering);
|
||||
$qb->andWhere($qb->expr()->isNotNull('v.shortUrl'));
|
||||
|
||||
$this->applySpecification($qb, $filtering->apiKey()?->inlinedSpec());
|
||||
$this->applySpecification($qb, $filtering->apiKey?->inlinedSpec());
|
||||
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countNonOrphanVisits(VisitsCountFiltering $filtering): int
|
||||
|
@ -232,11 +232,11 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->from(Visit::class, 'v');
|
||||
|
||||
if ($filtering->excludeBots()) {
|
||||
if ($filtering->excludeBots) {
|
||||
$qb->andWhere($qb->expr()->eq('v.potentialBot', 'false'));
|
||||
}
|
||||
|
||||
$this->applyDatesInline($qb, $filtering->dateRange());
|
||||
$this->applyDatesInline($qb, $filtering->dateRange);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
|
13
module/Core/src/ShortUrl/Model/TagsMode.php
Normal file
13
module/Core/src/ShortUrl/Model/TagsMode.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// phpcs:disable
|
||||
// TODO Enable coding style checks again once code sniffer 3.7 is released https://github.com/squizlabs/PHP_CodeSniffer/issues/3474
|
||||
namespace Shlinkio\Shlink\Core\ShortUrl\Model;
|
||||
|
||||
enum TagsMode: string
|
||||
{
|
||||
case ANY = 'any';
|
||||
case ALL = 'all';
|
||||
}
|
|
@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core\ShortUrl\Persistence;
|
|||
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
class ShortUrlsCountFiltering
|
||||
|
@ -13,7 +14,7 @@ class ShortUrlsCountFiltering
|
|||
public function __construct(
|
||||
private ?string $searchTerm = null,
|
||||
private array $tags = [],
|
||||
private ?string $tagsMode = null,
|
||||
private ?TagsMode $tagsMode = null,
|
||||
private ?DateRange $dateRange = null,
|
||||
private ?ApiKey $apiKey = null,
|
||||
) {
|
||||
|
@ -34,7 +35,7 @@ class ShortUrlsCountFiltering
|
|||
return $this->tags;
|
||||
}
|
||||
|
||||
public function tagsMode(): ?string
|
||||
public function tagsMode(): ?TagsMode
|
||||
{
|
||||
return $this->tagsMode;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Core\ShortUrl\Persistence;
|
|||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Model\Ordering;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
class ShortUrlsListFiltering extends ShortUrlsCountFiltering
|
||||
|
@ -17,7 +18,7 @@ class ShortUrlsListFiltering extends ShortUrlsCountFiltering
|
|||
private Ordering $orderBy,
|
||||
?string $searchTerm = null,
|
||||
array $tags = [],
|
||||
?string $tagsMode = null,
|
||||
?TagsMode $tagsMode = null,
|
||||
?DateRange $dateRange = null,
|
||||
?ApiKey $apiKey = null,
|
||||
) {
|
||||
|
|
|
@ -8,23 +8,11 @@ use JsonSerializable;
|
|||
|
||||
final class TagInfo implements JsonSerializable
|
||||
{
|
||||
public function __construct(private string $tag, private int $shortUrlsCount, private int $visitsCount)
|
||||
{
|
||||
}
|
||||
|
||||
public function tag(): string
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
public function shortUrlsCount(): int
|
||||
{
|
||||
return $this->shortUrlsCount;
|
||||
}
|
||||
|
||||
public function visitsCount(): int
|
||||
{
|
||||
return $this->visitsCount;
|
||||
public function __construct(
|
||||
public readonly string $tag,
|
||||
public readonly int $shortUrlsCount,
|
||||
public readonly int $visitsCount,
|
||||
) {
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
|
|
|
@ -10,7 +10,7 @@ use function sprintf;
|
|||
|
||||
final class TagRenaming
|
||||
{
|
||||
private function __construct(private string $oldName, private string $newName)
|
||||
private function __construct(public readonly string $oldName, public readonly string $newName)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -31,16 +31,6 @@ final class TagRenaming
|
|||
return self::fromNames($payload['oldName'], $payload['newName']);
|
||||
}
|
||||
|
||||
public function oldName(): string
|
||||
{
|
||||
return $this->oldName;
|
||||
}
|
||||
|
||||
public function newName(): string
|
||||
{
|
||||
return $this->newName;
|
||||
}
|
||||
|
||||
public function nameChanged(): bool
|
||||
{
|
||||
return $this->oldName !== $this->newName;
|
||||
|
|
|
@ -10,41 +10,16 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
|||
final class TagsListFiltering
|
||||
{
|
||||
public function __construct(
|
||||
private ?int $limit = null,
|
||||
private ?int $offset = null,
|
||||
private ?string $searchTerm = null,
|
||||
private ?Ordering $orderBy = null,
|
||||
private ?ApiKey $apiKey = null,
|
||||
public readonly ?int $limit = null,
|
||||
public readonly ?int $offset = null,
|
||||
public readonly ?string $searchTerm = null,
|
||||
public readonly ?Ordering $orderBy = null,
|
||||
public readonly ?ApiKey $apiKey = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromRangeAndParams(int $limit, int $offset, TagsParams $params, ?ApiKey $apiKey): self
|
||||
{
|
||||
return new self($limit, $offset, $params->searchTerm(), $params->orderBy(), $apiKey);
|
||||
}
|
||||
|
||||
public function limit(): ?int
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function offset(): ?int
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
public function searchTerm(): ?string
|
||||
{
|
||||
return $this->searchTerm;
|
||||
}
|
||||
|
||||
public function orderBy(): ?Ordering
|
||||
{
|
||||
return $this->orderBy;
|
||||
}
|
||||
|
||||
public function apiKey(): ?ApiKey
|
||||
{
|
||||
return $this->apiKey;
|
||||
return new self($limit, $offset, $params->searchTerm, $params->orderBy, $apiKey);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ use function Shlinkio\Shlink\Common\parseOrderBy;
|
|||
final class TagsParams extends AbstractInfinitePaginableListParams
|
||||
{
|
||||
private function __construct(
|
||||
private ?string $searchTerm,
|
||||
private Ordering $orderBy,
|
||||
private bool $withStats,
|
||||
public readonly ?string $searchTerm,
|
||||
public readonly Ordering $orderBy,
|
||||
public readonly bool $withStats,
|
||||
?int $page,
|
||||
?int $itemsPerPage,
|
||||
) {
|
||||
|
@ -31,19 +31,4 @@ final class TagsParams extends AbstractInfinitePaginableListParams
|
|||
isset($query['itemsPerPage']) ? (int) $query['itemsPerPage'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
public function searchTerm(): ?string
|
||||
{
|
||||
return $this->searchTerm;
|
||||
}
|
||||
|
||||
public function orderBy(): Ordering
|
||||
{
|
||||
return $this->orderBy;
|
||||
}
|
||||
|
||||
public function withStats(): bool
|
||||
{
|
||||
return $this->withStats;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ abstract class AbstractTagsPaginatorAdapter implements AdapterInterface
|
|||
new WithApiKeySpecsEnsuringJoin($this->apiKey),
|
||||
];
|
||||
|
||||
$searchTerm = $this->params->searchTerm();
|
||||
$searchTerm = $this->params->searchTerm;
|
||||
if ($searchTerm !== null) {
|
||||
$conditions[] = Spec::like('name', $searchTerm);
|
||||
}
|
||||
|
|
|
@ -15,13 +15,13 @@ class TagsPaginatorAdapter extends AbstractTagsPaginatorAdapter
|
|||
new WithApiKeySpecsEnsuringJoin($this->apiKey),
|
||||
Spec::orderBy(
|
||||
'name', // Ordering by other fields makes no sense here
|
||||
$this->params->orderBy()->orderDirection(),
|
||||
$this->params->orderBy->direction,
|
||||
),
|
||||
Spec::limit($length),
|
||||
Spec::offset($offset),
|
||||
];
|
||||
|
||||
$searchTerm = $this->params->searchTerm();
|
||||
$searchTerm = $this->params->searchTerm;
|
||||
if ($searchTerm !== null) {
|
||||
$conditions[] = Spec::like('name', $searchTerm);
|
||||
}
|
||||
|
|
|
@ -49,8 +49,8 @@ class TagService implements TagServiceInterface
|
|||
private function createPaginator(AdapterInterface $adapter, TagsParams $params): Paginator
|
||||
{
|
||||
return (new Paginator($adapter))
|
||||
->setMaxPerPage($params->getItemsPerPage())
|
||||
->setCurrentPage($params->getPage());
|
||||
->setMaxPerPage($params->itemsPerPage)
|
||||
->setCurrentPage($params->page);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,17 +83,17 @@ class TagService implements TagServiceInterface
|
|||
$repo = $this->em->getRepository(Tag::class);
|
||||
|
||||
/** @var Tag|null $tag */
|
||||
$tag = $repo->findOneBy(['name' => $renaming->oldName()]);
|
||||
$tag = $repo->findOneBy(['name' => $renaming->oldName]);
|
||||
if ($tag === null) {
|
||||
throw TagNotFoundException::fromTag($renaming->oldName());
|
||||
throw TagNotFoundException::fromTag($renaming->oldName);
|
||||
}
|
||||
|
||||
$newNameExists = $renaming->nameChanged() && $repo->count(['name' => $renaming->newName()]) > 0;
|
||||
$newNameExists = $renaming->nameChanged() && $repo->count(['name' => $renaming->newName]) > 0;
|
||||
if ($newNameExists) {
|
||||
throw TagConflictException::forExistingTag($renaming);
|
||||
}
|
||||
|
||||
$tag->rename($renaming->newName());
|
||||
$tag->rename($renaming->newName);
|
||||
$this->em->flush();
|
||||
|
||||
return $tag;
|
||||
|
|
|
@ -9,6 +9,7 @@ use Laminas\Validator\InArray;
|
|||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Common\Validation;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||
|
||||
class ShortUrlsParamsInputFilter extends InputFilter
|
||||
{
|
||||
|
@ -43,7 +44,7 @@ class ShortUrlsParamsInputFilter extends InputFilter
|
|||
|
||||
$tagsMode = $this->createInput(self::TAGS_MODE, false);
|
||||
$tagsMode->getValidatorChain()->attach(new InArray([
|
||||
'haystack' => [ShortUrlsParams::TAGS_MODE_ALL, ShortUrlsParams::TAGS_MODE_ANY],
|
||||
'haystack' => [TagsMode::ALL->value, TagsMode::ANY->value],
|
||||
'strict' => InArray::COMPARE_STRICT,
|
||||
]));
|
||||
$this->add($tagsMode);
|
||||
|
|
16
module/Core/src/Visit/Model/VisitType.php
Normal file
16
module/Core/src/Visit/Model/VisitType.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// phpcs:disable
|
||||
// TODO Enable coding style checks again once code sniffer 3.7 is released https://github.com/squizlabs/PHP_CodeSniffer/issues/3474
|
||||
namespace Shlinkio\Shlink\Core\Visit\Model;
|
||||
|
||||
enum VisitType: string
|
||||
{
|
||||
case VALID_SHORT_URL = 'valid_short_url';
|
||||
case IMPORTED = 'imported';
|
||||
case INVALID_SHORT_URL = 'invalid_short_url';
|
||||
case BASE_URL = 'base_url';
|
||||
case REGULAR_404 = 'regular_404';
|
||||
}
|
|
@ -26,8 +26,8 @@ class DomainVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
|||
return $this->visitRepository->countVisitsByDomain(
|
||||
$this->domain,
|
||||
new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
),
|
||||
);
|
||||
|
@ -38,8 +38,8 @@ class DomainVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
|||
return $this->visitRepository->findVisitsByDomain(
|
||||
$this->domain,
|
||||
new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
$length,
|
||||
$offset,
|
||||
|
|
|
@ -23,8 +23,8 @@ class NonOrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAda
|
|||
protected function doCount(): int
|
||||
{
|
||||
return $this->repo->countNonOrphanVisits(new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
));
|
||||
}
|
||||
|
@ -32,8 +32,8 @@ class NonOrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAda
|
|||
public function getSlice(int $offset, int $length): iterable
|
||||
{
|
||||
return $this->repo->findNonOrphanVisits(new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
$length,
|
||||
$offset,
|
||||
|
|
|
@ -19,16 +19,16 @@ class OrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
|||
protected function doCount(): int
|
||||
{
|
||||
return $this->repo->countOrphanVisits(new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
));
|
||||
}
|
||||
|
||||
public function getSlice(int $offset, int $length): iterable
|
||||
{
|
||||
return $this->repo->findOrphanVisits(new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
null,
|
||||
$length,
|
||||
$offset,
|
||||
|
|
|
@ -27,8 +27,8 @@ class ShortUrlVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdap
|
|||
return $this->visitRepository->findVisitsByShortCode(
|
||||
$this->identifier,
|
||||
new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
$length,
|
||||
$offset,
|
||||
|
@ -41,8 +41,8 @@ class ShortUrlVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdap
|
|||
return $this->visitRepository->countVisitsByShortCode(
|
||||
$this->identifier,
|
||||
new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -26,8 +26,8 @@ class TagVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapter
|
|||
return $this->visitRepository->findVisitsByTag(
|
||||
$this->tag,
|
||||
new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
$length,
|
||||
$offset,
|
||||
|
@ -40,8 +40,8 @@ class TagVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapter
|
|||
return $this->visitRepository->countVisitsByTag(
|
||||
$this->tag,
|
||||
new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -10,9 +10,9 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
|||
class VisitsCountFiltering
|
||||
{
|
||||
public function __construct(
|
||||
private ?DateRange $dateRange = null,
|
||||
private bool $excludeBots = false,
|
||||
private ?ApiKey $apiKey = null,
|
||||
public readonly ?DateRange $dateRange = null,
|
||||
public readonly bool $excludeBots = false,
|
||||
public readonly ?ApiKey $apiKey = null,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -20,19 +20,4 @@ class VisitsCountFiltering
|
|||
{
|
||||
return new self(null, false, $apiKey);
|
||||
}
|
||||
|
||||
public function dateRange(): ?DateRange
|
||||
{
|
||||
return $this->dateRange;
|
||||
}
|
||||
|
||||
public function excludeBots(): bool
|
||||
{
|
||||
return $this->excludeBots;
|
||||
}
|
||||
|
||||
public function apiKey(): ?ApiKey
|
||||
{
|
||||
return $this->apiKey;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,19 +13,9 @@ final class VisitsListFiltering extends VisitsCountFiltering
|
|||
?DateRange $dateRange = null,
|
||||
bool $excludeBots = false,
|
||||
?ApiKey $apiKey = null,
|
||||
private ?int $limit = null,
|
||||
private ?int $offset = null,
|
||||
public readonly ?int $limit = null,
|
||||
public readonly ?int $offset = null,
|
||||
) {
|
||||
parent::__construct($dateRange, $excludeBots, $apiKey);
|
||||
}
|
||||
|
||||
public function limit(): ?int
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function offset(): ?int
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,14 +22,14 @@ class CountOfNonOrphanVisits extends BaseSpecification
|
|||
{
|
||||
$conditions = [
|
||||
Spec::isNotNull('shortUrl'),
|
||||
new InDateRange($this->filtering->dateRange()),
|
||||
new InDateRange($this->filtering->dateRange),
|
||||
];
|
||||
|
||||
if ($this->filtering->excludeBots()) {
|
||||
if ($this->filtering->excludeBots) {
|
||||
$conditions[] = Spec::eq('potentialBot', false);
|
||||
}
|
||||
|
||||
$apiKey = $this->filtering->apiKey();
|
||||
$apiKey = $this->filtering->apiKey;
|
||||
if ($apiKey !== null) {
|
||||
$conditions[] = new WithApiKeySpecsEnsuringJoin($apiKey, 'shortUrl');
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ class CountOfOrphanVisits extends BaseSpecification
|
|||
{
|
||||
$conditions = [
|
||||
Spec::isNull('shortUrl'),
|
||||
new InDateRange($this->filtering->dateRange()),
|
||||
new InDateRange($this->filtering->dateRange),
|
||||
];
|
||||
|
||||
if ($this->filtering->excludeBots()) {
|
||||
if ($this->filtering->excludeBots) {
|
||||
$conditions[] = Spec::eq('potentialBot', false);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class OrphanVisitDataTransformer implements DataTransformerInterface
|
|||
{
|
||||
$serializedVisit = $visit->jsonSerialize();
|
||||
$serializedVisit['visitedUrl'] = $visit->visitedUrl();
|
||||
$serializedVisit['type'] = $visit->type();
|
||||
$serializedVisit['type'] = $visit->type()->value;
|
||||
|
||||
return $serializedVisit;
|
||||
}
|
||||
|
|
|
@ -129,8 +129,8 @@ class VisitsStatsHelper implements VisitsStatsHelperInterface
|
|||
private function createPaginator(AdapterInterface $adapter, VisitsParams $params): Paginator
|
||||
{
|
||||
$paginator = new Paginator($adapter);
|
||||
$paginator->setMaxPerPage($params->getItemsPerPage())
|
||||
->setCurrentPage($params->getPage());
|
||||
$paginator->setMaxPerPage($params->itemsPerPage)
|
||||
->setCurrentPage($params->page);
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
|
|
@ -72,6 +72,6 @@ class VisitsTracker implements VisitsTrackerInterface
|
|||
$this->em->persist($visit);
|
||||
$this->em->flush();
|
||||
|
||||
$this->eventDispatcher->dispatch(new UrlVisited($visit->getId(), $visitor->getRemoteAddress()));
|
||||
$this->eventDispatcher->dispatch(new UrlVisited($visit->getId(), $visitor->remoteAddress));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ use Shlinkio\Shlink\Core\Entity\Visit;
|
|||
use Shlinkio\Shlink\Core\Model\Ordering;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver;
|
||||
|
@ -227,7 +227,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
Ordering::emptyInstance(),
|
||||
null,
|
||||
['foo', 'bar'],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
TagsMode::ANY,
|
||||
)));
|
||||
self::assertCount(1, $this->repo->findList(new ShortUrlsListFiltering(
|
||||
null,
|
||||
|
@ -235,15 +235,11 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
Ordering::emptyInstance(),
|
||||
null,
|
||||
['foo', 'bar'],
|
||||
ShortUrlsParams::TAGS_MODE_ALL,
|
||||
TagsMode::ALL,
|
||||
)));
|
||||
self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar'])));
|
||||
self::assertEquals(5, $this->repo->countList(
|
||||
new ShortUrlsCountFiltering(null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ANY),
|
||||
));
|
||||
self::assertEquals(1, $this->repo->countList(
|
||||
new ShortUrlsCountFiltering(null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ALL),
|
||||
));
|
||||
self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar'], TagsMode::ANY)));
|
||||
self::assertEquals(1, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar'], TagsMode::ALL)));
|
||||
|
||||
self::assertCount(4, $this->repo->findList(
|
||||
new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['bar', 'baz']),
|
||||
|
@ -254,7 +250,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
Ordering::emptyInstance(),
|
||||
null,
|
||||
['bar', 'baz'],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
TagsMode::ANY,
|
||||
)));
|
||||
self::assertCount(2, $this->repo->findList(new ShortUrlsListFiltering(
|
||||
null,
|
||||
|
@ -262,14 +258,14 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
Ordering::emptyInstance(),
|
||||
null,
|
||||
['bar', 'baz'],
|
||||
ShortUrlsParams::TAGS_MODE_ALL,
|
||||
TagsMode::ALL,
|
||||
)));
|
||||
self::assertEquals(4, $this->repo->countList(new ShortUrlsCountFiltering(null, ['bar', 'baz'])));
|
||||
self::assertEquals(4, $this->repo->countList(
|
||||
new ShortUrlsCountFiltering(null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY),
|
||||
new ShortUrlsCountFiltering(null, ['bar', 'baz'], TagsMode::ANY),
|
||||
));
|
||||
self::assertEquals(2, $this->repo->countList(
|
||||
new ShortUrlsCountFiltering(null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL),
|
||||
new ShortUrlsCountFiltering(null, ['bar', 'baz'], TagsMode::ALL),
|
||||
));
|
||||
|
||||
self::assertCount(5, $this->repo->findList(
|
||||
|
@ -281,7 +277,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
Ordering::emptyInstance(),
|
||||
null,
|
||||
['foo', 'bar', 'baz'],
|
||||
ShortUrlsParams::TAGS_MODE_ANY,
|
||||
TagsMode::ANY,
|
||||
)));
|
||||
self::assertCount(0, $this->repo->findList(new ShortUrlsListFiltering(
|
||||
null,
|
||||
|
@ -289,14 +285,14 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
Ordering::emptyInstance(),
|
||||
null,
|
||||
['foo', 'bar', 'baz'],
|
||||
ShortUrlsParams::TAGS_MODE_ALL,
|
||||
TagsMode::ALL,
|
||||
)));
|
||||
self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'])));
|
||||
self::assertEquals(5, $this->repo->countList(
|
||||
new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY),
|
||||
new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], TagsMode::ANY),
|
||||
));
|
||||
self::assertEquals(0, $this->repo->countList(
|
||||
new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL),
|
||||
new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], TagsMode::ALL),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ class TagRepositoryTest extends DatabaseTestCase
|
|||
$this->getEntityManager()->persist(new Tag($name));
|
||||
}
|
||||
|
||||
$apiKey = $filtering?->apiKey();
|
||||
$apiKey = $filtering?->apiKey;
|
||||
if ($apiKey !== null) {
|
||||
$this->getEntityManager()->persist($apiKey);
|
||||
}
|
||||
|
@ -101,9 +101,9 @@ class TagRepositoryTest extends DatabaseTestCase
|
|||
|
||||
self::assertCount(count($expectedList), $result);
|
||||
foreach ($expectedList as $index => [$tag, $shortUrlsCount, $visitsCount]) {
|
||||
self::assertEquals($shortUrlsCount, $result[$index]->shortUrlsCount());
|
||||
self::assertEquals($visitsCount, $result[$index]->visitsCount());
|
||||
self::assertEquals($tag, $result[$index]->tag());
|
||||
self::assertEquals($shortUrlsCount, $result[$index]->shortUrlsCount);
|
||||
self::assertEquals($visitsCount, $result[$index]->visitsCount);
|
||||
self::assertEquals($tag, $result[$index]->tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,9 +37,10 @@ class PixelActionTest extends TestCase
|
|||
public function imageIsReturned(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))->willReturn(
|
||||
ShortUrl::withLongUrl('http://domain.com/foo/bar'),
|
||||
)->shouldBeCalledOnce();
|
||||
$this->urlResolver->resolveEnabledShortUrl(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''),
|
||||
)->willReturn(ShortUrl::withLongUrl('http://domain.com/foo/bar'))
|
||||
->shouldBeCalledOnce();
|
||||
$this->requestTracker->trackIfApplicable(Argument::cetera())->shouldBeCalledOnce();
|
||||
|
||||
$request = (new ServerRequest())->withAttribute('shortCode', $shortCode);
|
||||
|
|
|
@ -59,7 +59,7 @@ class QrCodeActionTest extends TestCase
|
|||
public function aNotFoundShortCodeWillDelegateIntoNextMiddleware(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''))
|
||||
->willThrow(ShortUrlNotFoundException::class)
|
||||
->shouldBeCalledOnce();
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
@ -74,7 +74,7 @@ class QrCodeActionTest extends TestCase
|
|||
public function aCorrectRequestReturnsTheQrCodeResponse(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''))
|
||||
->willReturn(ShortUrl::createEmpty())
|
||||
->shouldBeCalledOnce();
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
@ -100,7 +100,7 @@ class QrCodeActionTest extends TestCase
|
|||
): void {
|
||||
$this->options->setFromArray(['format' => $defaultFormat]);
|
||||
$code = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($code, ''))->willReturn(
|
||||
ShortUrl::createEmpty(),
|
||||
);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
@ -134,7 +134,7 @@ class QrCodeActionTest extends TestCase
|
|||
): void {
|
||||
$this->options->setFromArray($defaults);
|
||||
$code = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($code, ''))->willReturn(
|
||||
ShortUrl::createEmpty(),
|
||||
);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
@ -214,7 +214,7 @@ class QrCodeActionTest extends TestCase
|
|||
->withQueryParams(['size' => 250, 'roundBlockSize' => $roundBlockSize])
|
||||
->withAttribute('shortCode', $code);
|
||||
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($code, ''))->willReturn(
|
||||
ShortUrl::withLongUrl('https://shlink.io'),
|
||||
);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
|
|
@ -54,7 +54,7 @@ class RedirectActionTest extends TestCase
|
|||
$shortCode = 'abc123';
|
||||
$shortUrl = ShortUrl::withLongUrl(self::LONG_URL);
|
||||
$shortCodeToUrl = $this->urlResolver->resolveEnabledShortUrl(
|
||||
new ShortUrlIdentifier($shortCode, ''),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''),
|
||||
)->willReturn($shortUrl);
|
||||
$track = $this->requestTracker->trackIfApplicable(Argument::cetera())->will(function (): void {
|
||||
});
|
||||
|
@ -74,7 +74,7 @@ class RedirectActionTest extends TestCase
|
|||
public function nextMiddlewareIsInvokedIfLongUrlIsNotFound(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''))
|
||||
->willThrow(ShortUrlNotFoundException::class)
|
||||
->shouldBeCalledOnce();
|
||||
$this->requestTracker->trackIfApplicable(Argument::cetera())->shouldNotBeCalled();
|
||||
|
|
|
@ -6,7 +6,6 @@ namespace ShlinkioTest\Shlink\Core\Config;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
|
||||
use function putenv;
|
||||
|
||||
|
@ -14,92 +13,14 @@ class EnvVarsTest extends TestCase
|
|||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
putenv(EnvVars::BASE_PATH . '=the_base_path');
|
||||
putenv(EnvVars::DB_NAME . '=shlink');
|
||||
putenv(EnvVars::BASE_PATH->value . '=the_base_path');
|
||||
putenv(EnvVars::DB_NAME->value . '=shlink');
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
putenv(EnvVars::BASE_PATH . '=');
|
||||
putenv(EnvVars::DB_NAME . '=');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function casesReturnsTheSameListEveryTime(): void
|
||||
{
|
||||
$list = EnvVars::cases();
|
||||
self::assertSame($list, EnvVars::cases());
|
||||
self::assertSame([
|
||||
EnvVars::DELETE_SHORT_URL_THRESHOLD,
|
||||
EnvVars::DB_DRIVER,
|
||||
EnvVars::DB_NAME,
|
||||
EnvVars::DB_USER,
|
||||
EnvVars::DB_PASSWORD,
|
||||
EnvVars::DB_HOST,
|
||||
EnvVars::DB_UNIX_SOCKET,
|
||||
EnvVars::DB_PORT,
|
||||
EnvVars::GEOLITE_LICENSE_KEY,
|
||||
EnvVars::REDIS_SERVERS,
|
||||
EnvVars::REDIS_SENTINEL_SERVICE,
|
||||
EnvVars::MERCURE_PUBLIC_HUB_URL,
|
||||
EnvVars::MERCURE_INTERNAL_HUB_URL,
|
||||
EnvVars::MERCURE_JWT_SECRET,
|
||||
EnvVars::DEFAULT_QR_CODE_SIZE,
|
||||
EnvVars::DEFAULT_QR_CODE_MARGIN,
|
||||
EnvVars::DEFAULT_QR_CODE_FORMAT,
|
||||
EnvVars::DEFAULT_QR_CODE_ERROR_CORRECTION,
|
||||
EnvVars::DEFAULT_QR_CODE_ROUND_BLOCK_SIZE,
|
||||
EnvVars::RABBITMQ_ENABLED,
|
||||
EnvVars::RABBITMQ_HOST,
|
||||
EnvVars::RABBITMQ_PORT,
|
||||
EnvVars::RABBITMQ_USER,
|
||||
EnvVars::RABBITMQ_PASSWORD,
|
||||
EnvVars::RABBITMQ_VHOST,
|
||||
EnvVars::DEFAULT_INVALID_SHORT_URL_REDIRECT,
|
||||
EnvVars::DEFAULT_REGULAR_404_REDIRECT,
|
||||
EnvVars::DEFAULT_BASE_URL_REDIRECT,
|
||||
EnvVars::REDIRECT_STATUS_CODE,
|
||||
EnvVars::REDIRECT_CACHE_LIFETIME,
|
||||
EnvVars::BASE_PATH,
|
||||
EnvVars::PORT,
|
||||
EnvVars::TASK_WORKER_NUM,
|
||||
EnvVars::WEB_WORKER_NUM,
|
||||
EnvVars::ANONYMIZE_REMOTE_ADDR,
|
||||
EnvVars::TRACK_ORPHAN_VISITS,
|
||||
EnvVars::DISABLE_TRACK_PARAM,
|
||||
EnvVars::DISABLE_TRACKING,
|
||||
EnvVars::DISABLE_IP_TRACKING,
|
||||
EnvVars::DISABLE_REFERRER_TRACKING,
|
||||
EnvVars::DISABLE_UA_TRACKING,
|
||||
EnvVars::DISABLE_TRACKING_FROM,
|
||||
EnvVars::DEFAULT_SHORT_CODES_LENGTH,
|
||||
EnvVars::IS_HTTPS_ENABLED,
|
||||
EnvVars::DEFAULT_DOMAIN,
|
||||
EnvVars::AUTO_RESOLVE_TITLES,
|
||||
EnvVars::REDIRECT_APPEND_EXTRA_PATH,
|
||||
EnvVars::TIMEZONE,
|
||||
EnvVars::VISITS_WEBHOOKS,
|
||||
EnvVars::NOTIFY_ORPHAN_VISITS_TO_WEBHOOKS,
|
||||
], $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideInvalidEnvVars
|
||||
*/
|
||||
public function exceptionIsThrownWhenTryingToLoadInvalidEnvVar(string $envVar): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Invalid env var: "' . $envVar . '"');
|
||||
|
||||
EnvVars::{$envVar}();
|
||||
}
|
||||
|
||||
public function provideInvalidEnvVars(): iterable
|
||||
{
|
||||
yield 'foo' => ['foo'];
|
||||
yield 'bar' => ['bar'];
|
||||
yield 'invalid' => ['invalid'];
|
||||
putenv(EnvVars::BASE_PATH->value . '=');
|
||||
putenv(EnvVars::DB_NAME->value . '=');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,10 +34,10 @@ class EnvVarsTest extends TestCase
|
|||
|
||||
public function provideExistingEnvVars(): iterable
|
||||
{
|
||||
yield 'DB_NAME' => [EnvVars::DB_NAME(), true];
|
||||
yield 'BASE_PATH' => [EnvVars::BASE_PATH(), true];
|
||||
yield 'DB_DRIVER' => [EnvVars::DB_DRIVER(), false];
|
||||
yield 'DEFAULT_REGULAR_404_REDIRECT' => [EnvVars::DEFAULT_REGULAR_404_REDIRECT(), false];
|
||||
yield 'DB_NAME' => [EnvVars::DB_NAME, true];
|
||||
yield 'BASE_PATH' => [EnvVars::BASE_PATH, true];
|
||||
yield 'DB_DRIVER' => [EnvVars::DB_DRIVER, false];
|
||||
yield 'DEFAULT_REGULAR_404_REDIRECT' => [EnvVars::DEFAULT_REGULAR_404_REDIRECT, false];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,11 +51,11 @@ class EnvVarsTest extends TestCase
|
|||
|
||||
public function provideEnvVarsValues(): iterable
|
||||
{
|
||||
yield 'DB_NAME without default' => [EnvVars::DB_NAME(), 'shlink', null];
|
||||
yield 'DB_NAME with default' => [EnvVars::DB_NAME(), 'shlink', 'foobar'];
|
||||
yield 'BASE_PATH without default' => [EnvVars::BASE_PATH(), 'the_base_path', null];
|
||||
yield 'BASE_PATH with default' => [EnvVars::BASE_PATH(), 'the_base_path', 'foobar'];
|
||||
yield 'DB_DRIVER without default' => [EnvVars::DB_DRIVER(), null, null];
|
||||
yield 'DB_DRIVER with default' => [EnvVars::DB_DRIVER(), 'foobar', 'foobar'];
|
||||
yield 'DB_NAME without default' => [EnvVars::DB_NAME, 'shlink', null];
|
||||
yield 'DB_NAME with default' => [EnvVars::DB_NAME, 'shlink', 'foobar'];
|
||||
yield 'BASE_PATH without default' => [EnvVars::BASE_PATH, 'the_base_path', null];
|
||||
yield 'BASE_PATH with default' => [EnvVars::BASE_PATH, 'the_base_path', 'foobar'];
|
||||
yield 'DB_DRIVER without default' => [EnvVars::DB_DRIVER, null, null];
|
||||
yield 'DB_DRIVER with default' => [EnvVars::DB_DRIVER, 'foobar', 'foobar'];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated;
|
|||
use Shlinkio\Shlink\Core\EventDispatcher\NotifyVisitToMercure;
|
||||
use Shlinkio\Shlink\Core\Mercure\MercureUpdatesGeneratorInterface;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
|
||||
|
@ -160,8 +161,8 @@ class NotifyVisitToMercureTest extends TestCase
|
|||
{
|
||||
$visitor = Visitor::emptyInstance();
|
||||
|
||||
yield Visit::TYPE_REGULAR_404 => [Visit::forRegularNotFound($visitor)];
|
||||
yield Visit::TYPE_INVALID_SHORT_URL => [Visit::forInvalidShortUrl($visitor)];
|
||||
yield Visit::TYPE_BASE_URL => [Visit::forBasePath($visitor)];
|
||||
yield VisitType::REGULAR_404->value => [Visit::forRegularNotFound($visitor)];
|
||||
yield VisitType::INVALID_SHORT_URL->value => [Visit::forInvalidShortUrl($visitor)];
|
||||
yield VisitType::BASE_URL->value => [Visit::forBasePath($visitor)];
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue