mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 12:13:13 +08:00
Merge pull request #2556 from acelaya-forks/before-date-rule
Support for redirects with a before-date condition
This commit is contained in:
@@ -6,7 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
* *Nothing*
|
||||
* [#2431](https://github.com/shlinkio/shlink/issues/2431) Add new date-based condition for the dynamic rules redirections system.
|
||||
|
||||
* `before-date`: Allows to perform redirections based on an ISO-8601 date value, when the current date and time is earlier than the defined threshold.
|
||||
|
||||
### Changed
|
||||
* [#2522](https://github.com/shlinkio/shlink/issues/2522) Shlink no longer tries to detect trusted proxies automatically, when resolving the visitor's IP address, as this is a potential security issue.
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"valueless-query-param",
|
||||
"ip-address",
|
||||
"geolocation-country-code",
|
||||
"geolocation-city-name"
|
||||
"geolocation-city-name",
|
||||
"before-date"
|
||||
],
|
||||
"description": "The type of the condition, which will determine the logic used to match it"
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ use function max;
|
||||
use function min;
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\map;
|
||||
use function Shlinkio\Shlink\Core\enumValues;
|
||||
use function Shlinkio\Shlink\Core\normalizeDate;
|
||||
use function sprintf;
|
||||
use function str_pad;
|
||||
use function strlen;
|
||||
@@ -122,7 +123,10 @@ class RedirectRuleHandler implements RedirectRuleHandlerInterface
|
||||
),
|
||||
RedirectConditionType::GEOLOCATION_CITY_NAME => RedirectCondition::forGeolocationCityName(
|
||||
$this->askMandatory('City name to match?', $io),
|
||||
)
|
||||
),
|
||||
RedirectConditionType::BEFORE_DATE => RedirectCondition::forBeforeDate(
|
||||
normalizeDate($this->askMandatory('Date to match?', $io)),
|
||||
),
|
||||
};
|
||||
|
||||
$continue = $io->confirm('Do you want to add another condition?');
|
||||
|
||||
@@ -19,6 +19,7 @@ use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectConditionType;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Symfony\Component\Console\Style\StyleInterface;
|
||||
|
||||
use function Shlinkio\Shlink\Core\normalizeDate;
|
||||
use function sprintf;
|
||||
|
||||
#[AllowMockObjectsWithoutExpectations]
|
||||
@@ -122,6 +123,7 @@ class RedirectRuleHandlerTest extends TestCase
|
||||
'IP address, CIDR block or wildcard-pattern (1.2.*.*)' => '1.2.3.4',
|
||||
'Country code to match?' => 'FR',
|
||||
'City name to match?' => 'Los angeles',
|
||||
'Date to match?' => '2016-05-01T20:34:16+02:00',
|
||||
default => '',
|
||||
},
|
||||
);
|
||||
@@ -186,6 +188,10 @@ class RedirectRuleHandlerTest extends TestCase
|
||||
RedirectConditionType::GEOLOCATION_CITY_NAME,
|
||||
[RedirectCondition::forGeolocationCityName('Los angeles')],
|
||||
];
|
||||
yield 'Before date' => [
|
||||
RedirectConditionType::BEFORE_DATE,
|
||||
[RedirectCondition::forBeforeDate(normalizeDate('2016-05-01T20:34:16+02:00'))],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Shlinkio\Shlink\Core\RedirectRule\Entity;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use JsonSerializable;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
@@ -16,6 +17,7 @@ use function Shlinkio\Shlink\Core\acceptLanguageToLocales;
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\some;
|
||||
use function Shlinkio\Shlink\Core\geolocationFromRequest;
|
||||
use function Shlinkio\Shlink\Core\ipAddressFromRequest;
|
||||
use function Shlinkio\Shlink\Core\normalizeDate;
|
||||
use function Shlinkio\Shlink\Core\normalizeLocale;
|
||||
use function Shlinkio\Shlink\Core\splitLocale;
|
||||
use function sprintf;
|
||||
@@ -75,6 +77,11 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
return new self(RedirectConditionType::GEOLOCATION_CITY_NAME, $cityName);
|
||||
}
|
||||
|
||||
public static function forBeforeDate(Chronos $date): self
|
||||
{
|
||||
return new self(RedirectConditionType::BEFORE_DATE, $date->toAtomString());
|
||||
}
|
||||
|
||||
public static function fromRawData(array $rawData): self
|
||||
{
|
||||
$type = RedirectConditionType::from($rawData[RedirectRulesInputFilter::CONDITION_TYPE]);
|
||||
@@ -100,6 +107,7 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
RedirectConditionType::IP_ADDRESS => self::forIpAddress($cond->matchValue),
|
||||
RedirectConditionType::GEOLOCATION_COUNTRY_CODE => self::forGeolocationCountryCode($cond->matchValue),
|
||||
RedirectConditionType::GEOLOCATION_CITY_NAME => self::forGeolocationCityName($cond->matchValue),
|
||||
RedirectConditionType::BEFORE_DATE => self::forBeforeDate(normalizeDate($cond->matchValue)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -117,6 +125,7 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
RedirectConditionType::IP_ADDRESS => $this->matchesRemoteIpAddress($request),
|
||||
RedirectConditionType::GEOLOCATION_COUNTRY_CODE => $this->matchesGeolocationCountryCode($request),
|
||||
RedirectConditionType::GEOLOCATION_CITY_NAME => $this->matchesGeolocationCityName($request),
|
||||
RedirectConditionType::BEFORE_DATE => $this->matchesBeforeDate(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -200,6 +209,11 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
return strcasecmp($geolocation->city, $this->matchValue) === 0;
|
||||
}
|
||||
|
||||
private function matchesBeforeDate(): bool
|
||||
{
|
||||
return Chronos::now()->lessThan(Chronos::parse($this->matchValue));
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
@@ -230,6 +244,7 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||
RedirectConditionType::IP_ADDRESS => sprintf('IP address matches %s', $this->matchValue),
|
||||
RedirectConditionType::GEOLOCATION_COUNTRY_CODE => sprintf('country code is %s', $this->matchValue),
|
||||
RedirectConditionType::GEOLOCATION_CITY_NAME => sprintf('city name is %s', $this->matchValue),
|
||||
RedirectConditionType::BEFORE_DATE => sprintf('date before %s', $this->matchValue),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ enum RedirectConditionType: string
|
||||
case IP_ADDRESS = 'ip-address';
|
||||
case GEOLOCATION_COUNTRY_CODE = 'geolocation-country-code';
|
||||
case GEOLOCATION_CITY_NAME = 'geolocation-city-name';
|
||||
case BEFORE_DATE = 'before-date';
|
||||
|
||||
/**
|
||||
* Tells if a value is valid for the condition type
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\RedirectRule\Entity;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
@@ -195,4 +196,19 @@ class RedirectConditionTest extends TestCase
|
||||
);
|
||||
self::assertEquals($expectedType, $condition?->type);
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideVisitsWithBeforeDateCondition')]
|
||||
public function matchesBeforeDate(Chronos $date, bool $expectedResult): void
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals();
|
||||
$result = RedirectCondition::forBeforeDate($date)->matchesRequest($request);
|
||||
|
||||
self::assertEquals($expectedResult, $result);
|
||||
}
|
||||
|
||||
public static function provideVisitsWithBeforeDateCondition(): iterable
|
||||
{
|
||||
yield 'date later than current' => [Chronos::now()->addHours(1), true];
|
||||
yield 'date earlier than current' => [Chronos::now()->subHours(1), false];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user