diff --git a/.github/workflows/ci-phpstan.yml b/.github/workflows/ci-phpstan.yml index 88393a0..d507b35 100644 --- a/.github/workflows/ci-phpstan.yml +++ b/.github/workflows/ci-phpstan.yml @@ -9,15 +9,11 @@ jobs: strategy: fail-fast: false matrix: - php: [ 7.3, 7.4, 8.0 ] - laravel: [ 8.*, 7.*, 6.* ] + php: [8.0] + laravel: [8.*] include: - laravel: 8.* testbench: 6.* - - laravel: 7.* - testbench: 5.* - - laravel: 6.* - testbench: 4.* name: PHP${{ matrix.php }} - Laravel ${{ matrix.laravel }} diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 56713cb..40284cf 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -9,15 +9,11 @@ jobs: strategy: fail-fast: false matrix: - php: [7.3, 7.4, 8.0] - laravel: [8.*, 7.*, 6.*] + php: [8.0] + laravel: [8.*] include: - laravel: 8.* testbench: 6.* - - laravel: 7.* - testbench: 5.* - - laravel: 6.* - testbench: 4.* name: PHP${{ matrix.php }} - Laravel ${{ matrix.laravel }} diff --git a/CHANGELOG.md b/CHANGELOG.md index a788f03..19e171c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +**v6.0.0 (released 2021-10-21):** +- Added the ability to forward query parameters to the destination URL. [#94](https://github.com/ash-jc-allen/short-url/pull/94) +- Dropped support for Laravel 6, 7. [#96](https://github.com/ash-jc-allen/short-url/pull/96), [#98](https://github.com/ash-jc-allen/short-url/pull/98) +- Dropped support for PHP 7.3, 7.4. [#85](https://github.com/ash-jc-allen/short-url/pull/85) + **v5.2.0 (released 2021-09-21):** - Updated the migration for the `short_urls` table so that `url_key` is now unique and `destination_url` is now a TEXT field rather than varchar. [#80](https://github.com/ash-jc-allen/short-url/pull/80) - Added the ability to configure the alphabet used for generating keys with `hashids`. [#77](https://github.com/ash-jc-allen/short-url/pull/77) diff --git a/README.md b/README.md index f0e3e08..b019c70 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ - [Tracking Referer URL](#tracking-referer-url) - [Single Use](#single-use) - [Enforce HTTPS](#enforce-https) + - [Forwarding Query Parameters](#forwarding-query-parameters) - [Redirect Status Code](#redirect-status-code) - [Activation and Deactivation Times](#activation-and-deactivation-times) - [Facade](#facade) @@ -70,8 +71,8 @@ A Laravel package that can be used for adding shortened URLs to your existing we ### Requirements The package has been developed and tested to work with the following minimum requirements: -- PHP 7.2 -- Laravel 6.0 +- PHP 8.0 +- Laravel 8.0 Short URL requires either the [BC Math](https://secure.php.net/manual/en/book.bc.php) or [GMP](https://secure.php.net/manual/en/book.gmp.php) PHP extensions in order to work. @@ -261,6 +262,19 @@ $shortURLObject = $builder->destinationUrl('http://destination.com')->secure()-> // Destination URL: https://destination.com ``` +#### Forwarding Query Parameters +When building a short URL, you might want to forward the query parameters sent in the request to destination URL. By default, this functionality is disabled, but can be enabled by setting the `forward_query_params` config option to `true`. + +Alternatively, you can also use the `->forwardQueryParams()` method when building your shortened URL, as shown in the example below: + + ```php +$builder = new \AshAllenDesign\ShortURL\Classes\Builder(); + +$shortURLObject = $builder->destinationUrl('http://destination.com?param1=test')->forwardQueryParams()->make(); + ``` + +Based on the example above, assuming that the original short URL's `destination_url` was `https://destination.com`, making a request to `https://webapp.com/short/xxx?param1=abc¶m2=def` would redirect to `https://destination.com?param1=test¶m2=def` + #### Redirect Status Code By default, all short URLs are redirected with a ``` 301 ``` HTTP status code. But, this can be overridden when building @@ -533,6 +547,7 @@ Note: A contribution guide will be added soon. - [Nathan Giesbrecht](https://github.com/NathanGiesbrecht) - [Carlos A. Escobar](https://github.com/carlosjs23) - [Victor-Emil Rossil Andersen](https://github.com/Victor-emil) +- [Julien Arcin](https://github.com/julienarcin) - [All Contributors](https://github.com/ash-jc-allen/short-url/graphs/contributors) ## Changelog diff --git a/UPGRADE.md b/UPGRADE.md index 839dd03..46f1481 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,11 +1,51 @@ # Upgrade Guide ## Contents +- [Upgrading from 5.* to 6.0.0](#upgrading-from-5-to-600) - [Upgrading from 4.* to 5.0.0](#upgrading-from-4-to-500) - [Upgrading from 3.* to 4.0.0](#upgrading-from-3-to-400) - [Upgrading from 2.* to 3.0.0](#upgrading-from-2-to-300) - [Upgrading from 1.* to 2.0.0](#upgrading-from-1-to-200) +## Upgrading from 5.* to 6.0.0 + +### Laravel - Minimum Required Version + +As of Short URL v6.0.0, Laravel 6.0 and 7.0 are no longer supported. Therefore, you must be using a minimum of Laravel 8.0 to use this library. + +### PHP - Minimum Required Version + +As of Short URL v6.0.0, PHP 7.3 and 7.4 are no longer supported. Therefore, you must be using a minimum of PHP 8.0 to use this library. + +### New Config Variable and Migration + +As of Short URL v6.0.0, you can now forward query parameters from your request onto the destination URL. This feature requires that you run a new migration to add the `forward_query_params` field to your `short_urls` table. + +To publish the migration to your own `database/migrations` folder, run the following command in your project root: + +```bash +php artisan vendor:publish --tag="short-url-migrations" +``` + +There is also a new `forward_query_params` config option (that defaults to `false`) for controlling the default behaviour of this feature. If you wish to override this option, you can add the following to your own config: + +```php + /* + |-------------------------------------------------------------------------- + | Forwards query parameters + |-------------------------------------------------------------------------- + | + | Here you can specify if the newly created short URLs will forward + | the query parameters to the destination by default. This option + | can be overridden when creating the short URL with the + | ->forwardQueryParams() method. + | + | eg: https://yoursite.com/short/xxx?a=b => https://destination.com/page?a=b + | + */ + 'forward_query_params' => false, +``` + ## Upgrading from 4.* to 5.0.0 ### Publish Migrations @@ -106,4 +146,4 @@ You can add these options to your config file like shown below: 'device_type' => true, ], ], -``` \ No newline at end of file +``` diff --git a/composer.json b/composer.json index cf989f3..1a0c36d 100755 --- a/composer.json +++ b/composer.json @@ -18,16 +18,16 @@ "laravel-package" ], "require": { - "php": "^7.2|^8.0", + "php": "^8.0", "nesbot/carbon": "~2.0", - "illuminate/container": "^6.0|^7.0|^8.0", - "illuminate/database": "^6.0|^7.0|^8.0", + "illuminate/container": "^8.0", + "illuminate/database": "^8.0", "jenssegers/agent": "^2.6", "hashids/hashids": "^4.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.0|^5.0|^6.0", + "orchestra/testbench": "^6.0", "phpunit/phpunit": "^8.2", "nunomaduro/larastan": "^0.7.12" }, diff --git a/config/short-url.php b/config/short-url.php index 4fb2d54..f5e41ef 100644 --- a/config/short-url.php +++ b/config/short-url.php @@ -17,6 +17,21 @@ */ 'disable_default_route' => false, + /* + |-------------------------------------------------------------------------- + | Forwards query parameters + |-------------------------------------------------------------------------- + | + | Here you can specify if the newly created short URLs will forward + | the query parameters to the destination by default. This option + | can be overridden when creating the short URL with the + | ->forwardQueryParams() method. + | + | eg: https://yoursite.com/short/xxx?a=b => https://destination.com/page?a=b + | + */ + 'forward_query_params' => false, + /* |-------------------------------------------------------------------------- | Enforce HTTPS in the destination URL diff --git a/database/migrations/2020_04_20_009283_update_short_url_table_add_option_to_forward_query_params.php b/database/migrations/2020_04_20_009283_update_short_url_table_add_option_to_forward_query_params.php new file mode 100644 index 0000000..586a248 --- /dev/null +++ b/database/migrations/2020_04_20_009283_update_short_url_table_add_option_to_forward_query_params.php @@ -0,0 +1,32 @@ +boolean('forward_query_params')->after('single_use')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('short_urls', function (Blueprint $table) { + $table->dropColumn(['forward_query_params']); + }); + } +} diff --git a/src/Classes/Builder.php b/src/Classes/Builder.php index 4b7e16f..afae7e7 100755 --- a/src/Classes/Builder.php +++ b/src/Classes/Builder.php @@ -43,6 +43,15 @@ class Builder */ protected $secure; + /** + * Whether or not the short url whould + * forward query params to the + * destination url. + * + * @var bool|null + */ + protected $forwardQueryParams; + /** * Whether or not if the short URL should track * statistics about the visitors. @@ -209,6 +218,20 @@ public function secure(bool $isSecure = true): self return $this; } + /** + * Set whether if the short URL should forward + * query params to the destination URL. + * + * @param bool $shouldForwardQueryParams + * @return Builder + */ + public function forwardQueryParams(bool $shouldForwardQueryParams = true): self + { + $this->forwardQueryParams = $shouldForwardQueryParams; + + return $this; + } + /** * Set whether if the short URL should track some * statistics of the visitors. @@ -434,6 +457,7 @@ protected function insertShortURLIntoDatabase(): ShortURL 'default_short_url' => config('app.url').'/short/'.$this->urlKey, 'url_key' => $this->urlKey, 'single_use' => $this->singleUse, + 'forward_query_params' => $this->forwardQueryParams, 'track_visits' => $this->trackVisits, 'redirect_status_code' => $this->redirectStatusCode, 'track_ip_address' => $this->trackIPAddress, @@ -476,6 +500,10 @@ private function setOptions(): void $this->destinationUrl = str_replace('http://', 'https://', $this->destinationUrl); } + if ($this->forwardQueryParams === null) { + $this->forwardQueryParams = config('short-url.forward_query_params') ?? false; + } + if (! $this->urlKey) { $this->urlKey = $this->keyGenerator->generateRandom(); } @@ -540,6 +568,7 @@ public function resetOptions(): self $this->urlKey = null; $this->singleUse = false; $this->secure = null; + $this->forwardQueryParams = null; $this->redirectStatusCode = 301; $this->trackVisits = null; diff --git a/src/Classes/Validation.php b/src/Classes/Validation.php index 6cb8f25..b6d4f96 100644 --- a/src/Classes/Validation.php +++ b/src/Classes/Validation.php @@ -20,7 +20,8 @@ public function validateConfig(): bool && $this->validateTrackingOptions() && $this->validateDefaultRouteOption() && $this->validateKeySalt() - && $this->validateEnforceHttpsOption(); + && $this->validateEnforceHttpsOption() + && $this->validateForwardQueryParamsOption(); } /** @@ -126,4 +127,20 @@ protected function validateEnforceHttpsOption(): bool return true; } + + /** + * Validate that the forward query params option is a boolean. + * + * @return bool + * + * @throws ValidationException + */ + protected function validateForwardQueryParamsOption(): bool + { + if (! is_bool(config('short-url.forward_query_params'))) { + throw new ValidationException('The forward_query_params config variable must be a boolean.'); + } + + return true; + } } diff --git a/src/Controllers/ShortURLController.php b/src/Controllers/ShortURLController.php index eff3f76..913ee01 100644 --- a/src/Controllers/ShortURLController.php +++ b/src/Controllers/ShortURLController.php @@ -32,6 +32,31 @@ public function __invoke(Request $request, Resolver $resolver, string $shortURLK $resolver->handleVisit(request(), $shortURL); + if ($shortURL->forward_query_params) { + return redirect($this->forwardQueryParams($request, $shortURL), $shortURL->redirect_status_code); + } + return redirect($shortURL->destination_url, $shortURL->redirect_status_code); } + + /** + * Add the query parameters from the request to the end of the + * destination URL that the user is to be forwarded to. + * + * @param Request $request + * @param ShortURL $shortURL + * @return string + */ + private function forwardQueryParams(Request $request, ShortURL $shortURL): string + { + $queryString = parse_url($shortURL->destination_url, PHP_URL_QUERY); + + if (empty($request->query())) { + return $shortURL->destination_url; + } + + $separator = $queryString ? '&' : '?'; + + return $shortURL->destination_url.$separator.http_build_query($request->query()); + } } diff --git a/src/Models/ShortURL.php b/src/Models/ShortURL.php index 91d98d0..7b0ae28 100755 --- a/src/Models/ShortURL.php +++ b/src/Models/ShortURL.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Facades\URL; /** * Class ShortURL. @@ -15,6 +16,7 @@ * @property string $default_short_url * @property string $url_key * @property bool $single_use + * @property bool $forward_query_params * @property bool $track_visits * @property int $redirect_status_code * @property bool $track_ip_address @@ -48,6 +50,7 @@ class ShortURL extends Model 'default_short_url', 'url_key', 'single_use', + 'forward_query_params', 'track_visits', 'redirect_status_code', 'track_ip_address', @@ -80,6 +83,7 @@ class ShortURL extends Model */ protected $casts = [ 'single_use' => 'boolean', + 'forward_query_parameters' => 'boolean', 'track_visits' => 'boolean', 'track_ip_address' => 'boolean', 'track_operating_system' => 'boolean', diff --git a/tests/Unit/Classes/BuilderTest.php b/tests/Unit/Classes/BuilderTest.php index edc1528..e670f41 100644 --- a/tests/Unit/Classes/BuilderTest.php +++ b/tests/Unit/Classes/BuilderTest.php @@ -89,6 +89,36 @@ public function destination_url_is_changed_to_https_if_enforce_https_flag_is_set $this->assertEquals('https://domain.com', $shortUrl->destination_url); } + /** @test */ + public function forward_query_params_is_set_from_the_config_if_it_is_not_explicitly_set() + { + Config::set('short-url.forward_query_params', true); + + $builder = new Builder(); + $shortUrl = $builder->destinationUrl('http://domain.com')->make(); + $this->assertTrue($shortUrl->forward_query_params); + + Config::set('short-url.forward_query_params', false); + + $shortUrl = $builder->destinationUrl('http://domain.com')->make(); + $this->assertFalse($shortUrl->forward_query_params); + } + + /** @test */ + public function forward_query_params_is_not_set_from_the_config_if_it_is_explicitly_set() + { + Config::set('short-url.forward_query_params', true); + + $builder = new Builder(); + $shortUrl = $builder->destinationUrl('http://domain.com')->forwardQueryParams(false)->make(); + $this->assertFalse($shortUrl->forward_query_params); + + Config::set('short-url.forward_query_params', false); + + $shortUrl = $builder->destinationUrl('http://domain.com')->forwardQueryParams()->make(); + $this->assertTrue($shortUrl->forward_query_params); + } + /** @test */ public function track_visits_flag_is_set_from_the_config_if_it_is_not_explicitly_set() { diff --git a/tests/Unit/Classes/ValidationTest.php b/tests/Unit/Classes/ValidationTest.php index af319c7..e204313 100644 --- a/tests/Unit/Classes/ValidationTest.php +++ b/tests/Unit/Classes/ValidationTest.php @@ -104,4 +104,16 @@ public function exception_is_thrown_if_the_enforce_https_variable_is_not_a_boole $validation = new Validation(); $validation->validateConfig(); } + + /** @test */ + public function exception_is_thrown_if_the_forward_query_params_variable_is_not_a_boolean() + { + $this->expectException(ValidationException::class); + $this->expectExceptionMessage('The forward_query_params config variable must be a boolean.'); + + Config::set('short-url.forward_query_params', 'INVALID'); + + $validation = new Validation(); + $validation->validateConfig(); + } } diff --git a/tests/Unit/Controllers/ShortURLControllerTest.php b/tests/Unit/Controllers/ShortURLControllerTest.php index eab12af..382d57e 100644 --- a/tests/Unit/Controllers/ShortURLControllerTest.php +++ b/tests/Unit/Controllers/ShortURLControllerTest.php @@ -64,6 +64,7 @@ public function event_is_dispatched_when_the_short_url_is_visited() 'default_short_url' => config('app.url').'/short/12345', 'url_key' => '12345', 'single_use' => true, + 'forward_query_params' => false, 'track_visits' => true, 'redirect_status_code' => 301, 'track_ip_address' => true, @@ -161,4 +162,73 @@ public function visitor_is_redirected_to_the_destination_url_if_the_deactivation $this->get('/short/12345')->assertStatus(302)->assertRedirect('https://google.com'); } + + /** @test */ + public function visitor_is_redirected_to_the_destination_without_source_query_parameters_if_option_set_to_false() + { + ShortURL::create([ + 'destination_url' => 'https://google.com?param1=abc', + 'default_short_url' => config('app.url').'/short/12345', + 'url_key' => '12345', + 'forward_query_params' => false, + 'redirect_status_code' => 301, + 'single_use' => true, + 'track_visits' => true, + ]); + + $this->get('/short/12345?param1=test¶m2=test2')->assertStatus(301)->assertRedirect('https://google.com?param1=abc'); + } + + /** + * @test + * @dataProvider forwardQueryParamsProvider + */ + public function visitor_is_redirected_to_the_destination_with_source_query_parameters_if_option_set_to_true( + string $shortUrl, + string $requestUrl, + string $destinationUrl, + string $expectedDestinationUrl + ): void { + ShortURL::query()->create([ + 'destination_url' => $destinationUrl, + 'default_short_url' => $shortUrl, + 'url_key' => '12345', + 'forward_query_params' => true, + 'redirect_status_code' => 301, + 'single_use' => true, + 'track_visits' => true, + ]); + + $this->get($requestUrl)->assertStatus(301)->assertRedirect($expectedDestinationUrl); + } + + public function forwardQueryParamsProvider(): array + { + return [ + [ + '/short/12345', + '/short/12345?param1=test¶m2=test2', + 'https://google.com?param1=abc', + 'https://google.com?param1=abc¶m1=test¶m2=test2', + ], + [ + '/short/12345', + '/short/12345?param1=abc', + 'https://google.com', + 'https://google.com?param1=abc', + ], + [ + '/short/12345', + '/short/12345?param1=abc', + 'https://google.com?param1=hello', + 'https://google.com?param1=hello¶m1=abc', + ], + [ + '/short/12345', + '/short/12345?param3=abc', + 'https://google.com?param1=hello¶m2=123', + 'https://google.com?param1=hello¶m2=123¶m3=abc', + ], + ]; + } } diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index ddbd83c..dd6ffff 100755 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -62,11 +62,13 @@ private function migrateDatabase(): void include_once __DIR__.'/../../database/migrations/2020_02_11_224848_update_short_url_table_for_version_two_zero_zero.php'; include_once __DIR__.'/../../database/migrations/2020_02_12_008432_update_short_url_visits_table_for_version_two_zero_zero.php'; include_once __DIR__.'/../../database/migrations/2020_04_10_224546_update_short_url_table_for_version_three_zero_zero.php'; + include_once __DIR__.'/../../database/migrations/2020_04_20_009283_update_short_url_table_add_option_to_forward_query_params.php'; (new \CreateShortUrlsTable)->up(); (new \CreateShortUrlVisitsTable)->up(); (new \UpdateShortURLTableForVersionTwoZeroZero)->up(); (new \UpdateShortURLVisitsTableForVersionTwoZeroZero)->up(); (new \UpdateShortURLTableForVersionThreeZeroZero)->up(); + (new \UpdateShortUrlTableAddOptionToForwardQueryParams)->up(); } }