Add /dynamic segment to thumbnail route to prevent conflicts
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,7 @@ use App\RSpade\Core\Session\Session;
|
|||||||
*
|
*
|
||||||
* GET /_download/:key - Download file as attachment
|
* GET /_download/:key - Download file as attachment
|
||||||
* GET /_inline/:key - View file inline (browser display)
|
* GET /_inline/:key - View file inline (browser display)
|
||||||
* GET /_thumbnail/:key/:type/:width/:height? - Generate static thumbnail (WebP)
|
* GET /_thumbnail/dynamic/:key/:type/:width/:height? - Generate dynamic thumbnail (WebP)
|
||||||
* GET /_icon_by_extension/:extension - Get file type icon as PNG
|
* GET /_icon_by_extension/:extension - Get file type icon as PNG
|
||||||
*
|
*
|
||||||
* FILE RESPONSE PATTERN:
|
* FILE RESPONSE PATTERN:
|
||||||
@@ -561,7 +561,7 @@ class File_Attachment_Controller extends Rsx_Controller_Abstract
|
|||||||
/**
|
/**
|
||||||
* Generate dynamic thumbnail for image attachments
|
* Generate dynamic thumbnail for image attachments
|
||||||
*
|
*
|
||||||
* Route: /_thumbnail/:key/:type/:width/:height?
|
* Route: /_thumbnail/dynamic/:key/:type/:width/:height?
|
||||||
*
|
*
|
||||||
* Security: Checks file.thumbnail.authorize only
|
* Security: Checks file.thumbnail.authorize only
|
||||||
*
|
*
|
||||||
@@ -570,7 +570,7 @@ class File_Attachment_Controller extends Rsx_Controller_Abstract
|
|||||||
* @param int $width Thumbnail width in pixels
|
* @param int $width Thumbnail width in pixels
|
||||||
* @param int $height Optional thumbnail height in pixels
|
* @param int $height Optional thumbnail height in pixels
|
||||||
*/
|
*/
|
||||||
#[Route('/_thumbnail/:key/:type/:width/:height?', methods: ['GET'])]
|
#[Route('/_thumbnail/dynamic/:key/:type/:width/:height?', methods: ['GET'])]
|
||||||
#[Auth('Permission::anybody()')]
|
#[Auth('Permission::anybody()')]
|
||||||
public static function thumbnail(Request $request, array $params = [])
|
public static function thumbnail(Request $request, array $params = [])
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -264,9 +264,9 @@ class File_Attachment_Model extends Rsx_Site_Model_Abstract
|
|||||||
public function get_thumbnail_url($type = 'fit', $width = 400, $height = null)
|
public function get_thumbnail_url($type = 'fit', $width = 400, $height = null)
|
||||||
{
|
{
|
||||||
if ($height === null) {
|
if ($height === null) {
|
||||||
return url("/_thumbnail/{$this->key}/{$type}/{$width}");
|
return url("/_thumbnail/dynamic/{$this->key}/{$type}/{$width}");
|
||||||
}
|
}
|
||||||
return url("/_thumbnail/{$this->key}/{$type}/{$width}/{$height}");
|
return url("/_thumbnail/dynamic/{$this->key}/{$type}/{$width}/{$height}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ Edge case: Identical file content uploaded with different extensions (e.g., `doc
|
|||||||
|
|
||||||
### Request Processing
|
### Request Processing
|
||||||
|
|
||||||
**Route**: `/_thumbnail/:key/:type/:width/:height` (dynamic) or `/_thumbnail/preset/:key/:preset_name` (preset)
|
**Route**: `/_thumbnail/dynamic/:key/:type/:width/:height` (dynamic) or `/_thumbnail/preset/:key/:preset_name` (preset)
|
||||||
|
|
||||||
**Common Flow** (via `generate_and_serve_thumbnail()`):
|
**Common Flow** (via `generate_and_serve_thumbnail()`):
|
||||||
1. Validate authorization (`file.thumbnail.authorize` event)
|
1. Validate authorization (`file.thumbnail.authorize` event)
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ HTTP ENDPOINTS
|
|||||||
POST /_upload - Upload file
|
POST /_upload - Upload file
|
||||||
GET /_download/:key - Download file
|
GET /_download/:key - Download file
|
||||||
GET /_inline/:key - View file inline
|
GET /_inline/:key - View file inline
|
||||||
GET /_thumbnail/:key/:type/:width/:height? - Generate thumbnail
|
GET /_thumbnail/dynamic/:key/:type/:width/:height? - Generate dynamic thumbnail
|
||||||
GET /_icon_by_extension/:extension - Get file type icon
|
GET /_icon_by_extension/:extension - Get file type icon
|
||||||
|
|
||||||
UPLOAD ENDPOINT
|
UPLOAD ENDPOINT
|
||||||
@@ -594,9 +594,9 @@ DOWNLOAD AND INLINE VIEWING
|
|||||||
|
|
||||||
THUMBNAIL GENERATION
|
THUMBNAIL GENERATION
|
||||||
|
|
||||||
GET /_thumbnail/:key/:type/:width/:height?
|
GET /_thumbnail/dynamic/:key/:type/:width/:height?
|
||||||
|
|
||||||
Generate thumbnails for uploaded files with automatic fallback to file type icons.
|
Generate dynamic thumbnails for uploaded files with automatic fallback to file type icons.
|
||||||
|
|
||||||
URL Parameters:
|
URL Parameters:
|
||||||
key - 64-character attachment key
|
key - 64-character attachment key
|
||||||
|
|||||||
69
app/RSpade/upstream_changes/thumbnail_url_routes_01_28.txt
Executable file
69
app/RSpade/upstream_changes/thumbnail_url_routes_01_28.txt
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
Thumbnail URL Route Changes
|
||||||
|
Date: 2026-01-28
|
||||||
|
|
||||||
|
SUMMARY
|
||||||
|
Both thumbnail routes now include a literal path segment to prevent route
|
||||||
|
matching conflicts. If you manually construct thumbnail URLs (rather than
|
||||||
|
using get_thumbnail_url() or get_thumbnail_url_preset()), update them.
|
||||||
|
|
||||||
|
- Preset: /_thumbnail/preset/{key}/{preset_name} (unchanged from earlier today)
|
||||||
|
- Dynamic: /_thumbnail/dynamic/{key}/{type}/{width}/{height} (NEW)
|
||||||
|
|
||||||
|
AFFECTED FILES
|
||||||
|
/rsx/app/dev/attachments/dev_attachments.js
|
||||||
|
/rsx/theme/components/inputs/photo/profile_photo_input.js
|
||||||
|
|
||||||
|
Any custom code manually constructing thumbnail URLs.
|
||||||
|
|
||||||
|
CHANGES REQUIRED
|
||||||
|
|
||||||
|
If you manually construct dynamic thumbnail URLs, add "dynamic" segment:
|
||||||
|
|
||||||
|
Before:
|
||||||
|
`/_thumbnail/${key}/cover/300/200`
|
||||||
|
`/_thumbnail/${key}/fit/400`
|
||||||
|
|
||||||
|
After:
|
||||||
|
`/_thumbnail/dynamic/${key}/cover/300/200`
|
||||||
|
`/_thumbnail/dynamic/${key}/fit/400`
|
||||||
|
|
||||||
|
File: /rsx/app/dev/attachments/dev_attachments.js
|
||||||
|
-------------------------------------------------------------------------
|
||||||
|
Find:
|
||||||
|
$('#thumb-profile').attr('src', `/_thumbnail/${key}/cover/96/96`);
|
||||||
|
$('#thumb-200').attr('src', `/_thumbnail/${key}/cover/200/200`);
|
||||||
|
$('#thumb-240x180').attr('src', `/_thumbnail/${key}/cover/240/180`);
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
$('#thumb-profile').attr('src', `/_thumbnail/dynamic/${key}/cover/96/96`);
|
||||||
|
$('#thumb-200').attr('src', `/_thumbnail/dynamic/${key}/cover/200/200`);
|
||||||
|
$('#thumb-240x180').attr('src', `/_thumbnail/dynamic/${key}/cover/240/180`);
|
||||||
|
|
||||||
|
File: /rsx/theme/components/inputs/photo/profile_photo_input.js
|
||||||
|
-------------------------------------------------------------------------
|
||||||
|
Find:
|
||||||
|
this.state.thumbnail_url = `/_thumbnail/${this.state.attachment_key}/cover/${width}/${height}`;
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
this.state.thumbnail_url = `/_thumbnail/dynamic/${this.state.attachment_key}/cover/${width}/${height}`;
|
||||||
|
|
||||||
|
RECOMMENDED APPROACH
|
||||||
|
|
||||||
|
Instead of manually constructing URLs, use the model methods which always
|
||||||
|
generate correct URLs:
|
||||||
|
|
||||||
|
PHP:
|
||||||
|
$attachment->get_thumbnail_url('cover', 300, 200);
|
||||||
|
$attachment->get_thumbnail_url_preset('profile');
|
||||||
|
|
||||||
|
JavaScript (via Ajax stub):
|
||||||
|
const url = await File_Attachment_Model.get_thumbnail_url(key, 'cover', 300, 200);
|
||||||
|
|
||||||
|
VERIFICATION
|
||||||
|
|
||||||
|
1. Search your codebase for manual thumbnail URL construction:
|
||||||
|
grep -r "/_thumbnail/" rsx/ --include="*.js" --include="*.php"
|
||||||
|
|
||||||
|
2. Update any matches that don't use get_thumbnail_url methods
|
||||||
|
|
||||||
|
3. Test thumbnail display in your application
|
||||||
Reference in New Issue
Block a user