diff --git a/CHANGELOG.md b/CHANGELOG.md index ff64a0b3..a8b5dadd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Retour Changelog +## 5.0.0-beta.5 - 2024.03.25 +### Fixed +* Fixed an issue with impropertly text-encoded characters in URLs potentially causing a db exception ([#291](https://github.com/nystudio107/craft-retour/issues/291)) +* Fixed an issue where an empty redirect in the `excludePatterns` list could cause redirects to stop functioning, add logging when a redirect is excluded ([#297](https://github.com/nystudio107/craft-retour/issues/297)) + ## 5.0.0-beta.4 - 2024.02.09 ### Fixed * Fixed an issue with the Sites menu styling diff --git a/composer.json b/composer.json index 9d3758bd..62569a9a 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nystudio107/craft-retour", "description": "Retour allows you to intelligently redirect legacy URLs, so that you don't lose SEO value when rebuilding & restructuring a website", "type": "craft-plugin", - "version": "5.0.0-beta.4", + "version": "5.0.0-beta.5", "keywords": [ "craftcms", "craft-plugin", @@ -26,13 +26,19 @@ "require-dev": { }, "require": { - "php": "^8.0.2", + "php": "^8.2", "craftcms/cms": "^5.0.0-beta.1", "nystudio107/craft-plugin-vite": "^5.0.0-beta.1", "league/csv": "^8.2 || ^9.0", "jean85/pretty-package-versions": "^1.5 || ^2.0" }, "require-dev": { + "codeception/codeception": "^5.0.11", + "codeception/module-asserts": "^3.0.0", + "codeception/module-datafactory": "^3.0.0", + "codeception/module-phpbrowser": "^3.0.0", + "codeception/module-rest": "^3.3.2", + "codeception/module-yii2": "^1.1.9", "craftcms/ecs": "dev-main", "craftcms/phpstan": "dev-main", "craftcms/rector": "dev-main" diff --git a/docs/docs/.vitepress/config.ts b/docs/docs/.vitepress/config.ts index b24da2a6..1787e831 100644 --- a/docs/docs/.vitepress/config.ts +++ b/docs/docs/.vitepress/config.ts @@ -23,8 +23,11 @@ export default defineConfig({ }, algolia: { appId: 'PBLZ7FT9Z3', - apiKey: 'ab56b755c575dc94a58f7d1cae6e4e0e', - indexName: 'retour' + apiKey: '953923b236f39535b8553f4143cab98d', + indexName: 'retour', + searchParameters: { + facetFilters: ["version:v5"], + }, }, lastUpdatedText: 'Last Updated', sidebar: [ diff --git a/src/services/Redirects.php b/src/services/Redirects.php index c9309ee8..1f777e08 100644 --- a/src/services/Redirects.php +++ b/src/services/Redirects.php @@ -16,6 +16,7 @@ use craft\base\ElementInterface; use craft\base\Plugin; use craft\db\Query; +use craft\errors\ElementNotFoundException; use craft\errors\SiteNotFoundException; use craft\helpers\Db; use craft\helpers\StringHelper; @@ -24,9 +25,11 @@ use nystudio107\retour\events\RedirectResolvedEvent; use nystudio107\retour\events\ResolveRedirectEvent; use nystudio107\retour\fields\ShortLink; +use nystudio107\retour\helpers\Text as TextHelper; use nystudio107\retour\helpers\UrlHelper; use nystudio107\retour\models\StaticRedirects as StaticRedirectsModel; use nystudio107\retour\Retour; +use Throwable; use yii\base\ExitException; use yii\base\InvalidConfigException; use yii\base\InvalidRouteException; @@ -272,9 +275,21 @@ public function excludeUri($uri): bool $uri = '/' . ltrim($uri, '/'); if (!empty(Retour::$settings->excludePatterns)) { foreach (Retour::$settings->excludePatterns as $excludePattern) { + if (empty($excludePattern['pattern'])) { + continue; + } $pattern = '`' . $excludePattern['pattern'] . '`i'; try { if (preg_match($pattern, $uri) === 1) { + Craft::info( + Craft::t( + 'retour', + 'Excluded URI: {uri} due to match of pattern: {pattern}', + ['uri' => $uri, 'pathOnly' => $pattern] + ), + __METHOD__ + ); + return true; } } catch (\Exception $e) { @@ -467,11 +482,11 @@ public function getStaticRedirect(string $fullUrl, string $pathOnly, $siteId, bo 'or', ['and', ['redirectSrcMatch' => 'pathonly'], - ['redirectSrcUrlParsed' => $pathOnly], + ['redirectSrcUrlParsed' => TextHelper::cleanupText($pathOnly)], ], ['and', ['redirectSrcMatch' => 'fullurl'], - ['redirectSrcUrlParsed' => $fullUrl], + ['redirectSrcUrlParsed' => TextHelper::cleanupText($fullUrl)], ], ]; @@ -1041,8 +1056,8 @@ public function getRedirectsByElementId(int $elementId, int $siteId = null) * Delete a short link by its ID. * * @param int $redirectId - * @throws \Throwable - * @throws \craft\errors\ElementNotFoundException + * @throws Throwable + * @throws ElementNotFoundException * @throws \yii\base\Exception */ public function deleteShortlinkById(int $redirectId): bool @@ -1227,7 +1242,7 @@ public function getRedirectByRedirectSrcUrl(string $redirectSrcUrl, int $siteId // Query the db table $query = (new Query()) ->from(['{{%retour_static_redirects}}']) - ->where(['redirectSrcUrl' => $redirectSrcUrl]); + ->where(['redirectSrcUrl' => TextHelper::cleanupText($redirectSrcUrl)]); if ($siteId) { $query ->andWhere(['or', [ diff --git a/src/services/Statistics.php b/src/services/Statistics.php index ab1be1db..a09ac9c1 100644 --- a/src/services/Statistics.php +++ b/src/services/Statistics.php @@ -17,6 +17,7 @@ use craft\helpers\Db; use craft\helpers\UrlHelper; use DateTime; +use nystudio107\retour\helpers\Text as TextHelper; use nystudio107\retour\models\Stats as StatsModel; use nystudio107\retour\Retour; use yii\db\Exception; @@ -194,7 +195,7 @@ public function incrementStatistics(string $url, bool $handled = false, $siteId // Find any existing retour_stats record $statsConfig = (new Query()) ->from(['{{%retour_stats}}']) - ->where(['redirectSrcUrl' => $stats->redirectSrcUrl]) + ->where(['redirectSrcUrl' => TextHelper::cleanupText($stats->redirectSrcUrl)]) ->one(); // If no record is found, initialize some values if ($statsConfig === null) { @@ -275,28 +276,6 @@ public function saveStatistics(array $statsConfig): void } } - /** - * Don't trim more than a given interval, so that performance is not affected - * - * @return bool - */ - protected function rateLimited(): bool - { - $limited = false; - $now = round(microtime(true) * 1000); - $cache = Craft::$app->getCache(); - $then = $cache->get(self::LAST_STATISTICS_TRIM_CACHE_KEY); - if (($then !== false) && ($now - (int)$then < Retour::$settings->statisticsRateLimitMs)) { - $limited = true; - } - $cache->set(self::LAST_STATISTICS_TRIM_CACHE_KEY, $now, 0); - - return $limited; - } - - // Protected Methods - // ========================================================================= - /** * Trim the retour_stats db table based on the statsStoredLimit config.php * setting @@ -364,4 +343,26 @@ public function trimStatistics(int $limit = null): int return $affectedRows; } + + // Protected Methods + // ========================================================================= + + /** + * Don't trim more than a given interval, so that performance is not affected + * + * @return bool + */ + protected function rateLimited(): bool + { + $limited = false; + $now = round(microtime(true) * 1000); + $cache = Craft::$app->getCache(); + $then = $cache->get(self::LAST_STATISTICS_TRIM_CACHE_KEY); + if (($then !== false) && ($now - (int)$then < Retour::$settings->statisticsRateLimitMs)) { + $limited = true; + } + $cache->set(self::LAST_STATISTICS_TRIM_CACHE_KEY, $now, 0); + + return $limited; + } } diff --git a/src/translations/en/retour.php b/src/translations/en/retour.php index d623eb5d..36885d26 100644 --- a/src/translations/en/retour.php +++ b/src/translations/en/retour.php @@ -171,4 +171,5 @@ 'Some errors occured .' => 'Some errors occured .', 'Some errors occured importing the CSV file.' => 'Some errors occured importing the CSV file.', 'Select the priority that will determine the order of mathching the redirect.' => 'Select the priority that will determine the order of mathching the redirect.', + 'Excluded URI: {uri} due to match of pattern: {pattern}' => 'Excluded URI: {uri} due to match of pattern: {pattern}' ];