Skip to content

Commit

Permalink
FEAT: Allow redirectedurls to apply to assets (#86)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Peel <[email protected]>
Co-authored-by: Scott Hutchinson <[email protected]>
Co-authored-by: Chris Penny <[email protected]>
  • Loading branch information
4 people authored Feb 9, 2023
1 parent 022d623 commit 415b43d
Show file tree
Hide file tree
Showing 20 changed files with 556 additions and 281 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
resources/
vendor/
/resources/
/vendor/
142 changes: 107 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
Redirected URLs
===============
# Redirected URLs

[![Latest Stable Version](https://poser.pugx.org/silverstripe/redirectedurls/version)](https://packagist.org/packages/silverstripe/redirectedurls)
[![License](https://poser.pugx.org/silverstripe/redirectedurls/license)](https://packagist.org/packages/silverstripe/redirectedurls)
[![Monthly Downloads](https://poser.pugx.org/silverstripe/redirectedurls/d/monthly)](https://packagist.org/packages/silverstripe/redirectedurls)


**Author:** Sam Minnée

**Author:** Stig Lindqvist

**Author:** Russ Michell

**Authors:**
* Sam Minnée
* Stig Lindqvist
* Russ Michell

This module provides a system for users to configure arbitrary redirections in the CMS. These can be
used for legacy redirections, friendly URLs, and anything else that involves redirecting one URL to
Expand All @@ -23,43 +19,119 @@ admin included.
The redirection is implemented as a plug-in to the 404 handler, which means that you can't create a
redirection for a page that already exists on the site.

Installation
------------
## Installation

- Use composer to run the following in the command line:

```
composer require silverstripe/redirectedurls dev-master
```

- Then run **dev/build** (http://www.mysite.com/dev/build)

Usage
-----
1. Click 'Redirects' in the main menu of the CMS.
2. Click 'Add Redirected URL' to create a mapping of an old URL to a new URL on your Silverstripe website.
3. Enter a 'From Base' which is the URL from your old website (not including the domain name). For example, "/about-us.html".
4. Alternatively, depending on your old websites URL structure you can redirect based on a query string using the combination of 'From Base' and 'From Querystring' fields. For exmaple, "index.html" as the base and "page=about-us" as the query string.
5. As a further alternative, you can include a trailing '/\*' for a wildcard match to any file with the same stem. For example, "/about/\*".
6. Complete the 'To' field which is the URL you wish to redirect traffic to if any traffic from. For example, "/about-us".
7. Alternatively you can terminate the 'To' field with '/\*' to redirect to the specific file requested by the user. For example, "/new-about/\*". Note that if this specific file is not in the target directory tree, the 404 error will be handled by the target site.
8. Create a new Redirection for each URL mapping you need to redirect.
## Usage

1. Click 'Redirects' in the main menu of the CMS.
2. Click 'Add Redirected URL' to create a mapping of an old URL to a new URL on your Silverstripe website.
3. Enter a 'From Base' which is the URL from your old website (not including the domain name). For example, "/about-us.html".
4. Alternatively, depending on your old websites URL structure you can redirect based on a query string using the combination of 'From Base' and 'From Querystring' fields. For exmaple, "index.html" as the base and "page=about-us" as the query string.
5. As a further alternative, you can include a trailing '/\*' for a wildcard match to any file with the same stem. For example, "/about/\*".
6. Complete the 'To' field which is the URL you wish to redirect traffic to if any traffic from. For example, "/about-us".
7. Alternatively you can terminate the 'To' field with '/\*' to redirect to the specific file requested by the user. For example, "/new-about/\*". Note that if this specific file is not in the target directory tree, the 404 error will be handled by the target site.
8. Create a new Redirection for each URL mapping you need to redirect.

For example, to redirect "/about-us/index.html?item=1" to "/about-us/item/1", set:

From Base: /about-us/index.html
From Querystring: item=1
To: /about-us/item/1
```
From Base: /about-us/index.html
From Querystring: item=1
To: /about-us/item/1
```

## Importing

Importing
---------
1. Create a CSV file with the columns headings 'FromBase', 'FromQuerystring' and 'To' and enter your URL mappings.
2. Click 'Redirects' in the main menu of the CMS.
3. In the 'Import' section click 'Choose file', select your CSV file and then click 'Import from CSV'.
4. Optionally select the 'Replace data' option if you want to replace the RedirectedURL database table contents with the imported data.
1. Create a CSV file with the columns headings 'FromBase', 'FromQuerystring' and 'To' and enter your URL mappings.
2. Click 'Redirects' in the main menu of the CMS.
3. In the 'Import' section click 'Choose file', select your CSV file and then click 'Import from CSV'.
4. Optionally select the 'Replace data' option if you want to replace the RedirectedURL database table contents with the imported data.

CSV Importer, example file format:

FromBase, FromQuerystring, To
/about-us/index.html, item=1, /about/item/1
/example/no-querystring.html, ,/example/no-querystring/
/example/two-queryparams.html, foo=1&bar=2, /example/foo/1/bar/2
/about/*, ,/about-us
```
FromBase, FromQuerystring, To
/about-us/index.html, item=1, /about/item/1
/example/no-querystring.html, ,/example/no-querystring/
/example/two-queryparams.html, foo=1&bar=2, /example/foo/1/bar/2
/about/*, ,/about-us
```

## Allowing redirects from Asset URLs

This assumes that your project as `silverstripe/assets`.

**Please note:** By default, many web services will route assets (and other resources) directly through Nginx. If this
is the case for you, then please be aware that adding the following extension **will not be enough** to enable this
functionality.

```yaml
---
Name: app-redirectedurls
---
SilverStripe\Assets\Flysystem\FlysystemAssetStore:
extensions:
- SilverStripe\RedirectedURLs\Extension\AssetStoreURLHandler
```
### Routing assets through Apache
This might differ for your web service, but you can use the following for any service that respect the `.platform.yml`
configuration, and you can use this if you are using the latest Silverstripe `dev-boxes`.

#### Performance considerations

Be very aware that Apache is slower than Nginx for serving static resources. Making this change could mean a significant
impact to your application's performance.

#### Implementation

URL rules allow you to customise default behaviour:

```yaml
url_rules:
mysite:
- '<regex>': '<rule>'
```

The regex must be in a format accepted by nginx. This will be used as a case-insensitive location matcher and is
compared against the full URL.

* `^/assets/` - match all URLs pointing to the assets directory
* `\.(gif|jpg|jpeg)$` - match extensions at the end of the URL

For example, if you wanted to route all assets through Apache.

`platform.yml`:
```yaml
url_rules:
mysite:
- '^/assets/': 'apache'
```

#### Some thoughts on limiting what assets are served from Apache

Instead of serving all assets, is there are specific extension (or extensions) that you could call out? EG, is it only
PDFs that you want to support redirect for?

```yaml
url_rules:
mysite:
- '^/assets/.+\.(pdf)$': 'apache'
```

Or what about only serving a specific asset directory that you've specified with your content authors?

```yaml
url_rules:
mysite:
- '^/assets/Documents/': 'apache'
```
6 changes: 5 additions & 1 deletion _config/redirectedurls.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
---
name: redirectedurls
Name: redirectedurls
---
SilverStripe\Control\RequestHandler:
extensions:
- SilverStripe\RedirectedURLs\Extension\RedirectedURLHandler

SilverStripe\CMS\Controllers\ContentController:
extensions:
- SilverStripe\RedirectedURLs\Extension\RedirectedURLHandler

SilverStripe\CMS\Controllers\ModelAsController:
extensions:
- SilverStripe\RedirectedURLs\Extension\RedirectedURLHandler

SilverStripe\ORM\DatabaseAdmin:
classname_value_remapping:
RedirectedURL: 'SilverStripe\RedirectedURLs\Model\RedirectedURL'

SilverStripe\RedirectedURLs\Model\RedirectedURL:
default_redirect_code: 301
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
}
],
"require": {
"php": "^7.4 || ^8",
"silverstripe/framework": "^4",
"silverstripe/cms": "^4",
"unclecheese/display-logic": "~2"
Expand Down
33 changes: 8 additions & 25 deletions src/Admin/RedirectedURLAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,19 @@

/**
* Provides CMS Administration of {@link: RedirectedURL} objects
*
* @package redirectedurls
* @author [email protected]
* @author [email protected]
*/
class RedirectedURLAdmin extends ModelAdmin
{

/**
* @var string
* @config
*/
private static $url_segment = 'redirects';
private static string $url_segment = 'redirects';

/**
* @var string
* @config
*/
private static $menu_title = 'Redirects';
private static string $menu_title = 'Redirects';

/**
* @var string
* @config
*/
private static $menu_icon = 'silverstripe/redirectedurls:images/redirect.svg';
private static string $menu_icon = 'silverstripe/redirectedurls:images/redirect.svg';

/**
* @var array
* @config
*/
private static $managed_models = array(
private static array $managed_models = [
RedirectedURL::class,
);
];

/**
* Overridden to add duplicate checking to the bulkloader to prevent
Expand All @@ -57,6 +37,7 @@ public function getModelImporters()
$importer->duplicateChecks = [
'FromBase' => ['callback' => 'findByFrom'],
];

return [
RedirectedURL::class => $importer
];
Expand All @@ -73,9 +54,11 @@ public function getModelImporters()
public function getExportFields()
{
$fields = array();

foreach (DataObject::getSchema()->databaseFields($this->modelClass) as $field => $spec) {
$fields[$field] = $field;
}

return $fields;
}
}
50 changes: 50 additions & 0 deletions src/Extension/AssetStoreURLHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace SilverStripe\RedirectedURLs\Extension;

use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Extension;
use SilverStripe\RedirectedURLs\Model\RedirectedURL;
use SilverStripe\RedirectedURLs\Service\RedirectedURLService;

/**
* This extension applies to FlysystemAssetStore, and ensures that an appropriate redirect response is returned when an
* asset isn't found and the path matches a {@link RedirectedURL} object.
*/
class AssetStoreURLHandler extends Extension
{
/**
* @var array An array of HTTP status codes that should be acted upon if they are returned by the AssetStore.
* @config
*/
private static array $act_upon = [
404,
];

public function updateResponse(HTTPResponse &$response, string $asset, array $context = [])
{
// Only change the response if the response provided by FlysystemAssetStore matches one we should act on
if (!in_array($response->getStatusCode(), $this->owner->config()->act_upon)) {
return;
}

// We are unable to progress if there is no current Controller
if (!Controller::has_curr()) {
return;
}

// Get the current request, then attempt to find a RedirectedURL object that matches
$controller = Controller::curr();
$request = $controller->getRequest();

$service = RedirectedURLService::create();
$match = $service->findBestRedirectedURLMatch($request);

if ($match) {
// We have a matching RedirectedURL, so replace the base HTTPResponse provided by
// FlysystemAssetStore with our redirect response
$response = $service->getResponse($match);
}
}
}
Loading

0 comments on commit 415b43d

Please sign in to comment.