getgrav / getgrav/grav-plugin-api
Feature: optional server-side resize of oversized raster uploads
- Dominant language
- PHP
- Stars
- 5
- Forks
- 8
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 2
Description
Split out of #41, which was closed once the immediate gap (admin-next ignoring the classic admin `pagemedia` resizer) was fixed.
Grav resizes oversized uploads in the browser, in two places: classic admin does it in Dropzone, and admin-next now does the same in Uppy, both reading `plugins.admin.pagemedia.resize_width` / `resize_height` / `resize_quality`. The form plugin has its own version for frontend forms via FilePond.
All of those are client-side, which makes them a convenience rather than a policy. Nothing resizes on the server, so a full-size original still lands in `user/pages` whenever the upload does not come from one of those widgets: a direct API call, grav-mcp, a sync or import script, a mobile client, or anything else talking to `/api/v1`. On a Git Sync site that original is then in the repo and every clone, forever.
### Proposal
An optional maximum edge applied in `HandlesMediaUploads::processUploadedFile()`, after the SVG sanitise and before it returns. That single point sits ahead of `onAdminAfterAddMedia` on all three upload paths (page media, site media, flex-object media) at once. **Off by default**, with 2560 pre-filled so enabling it is one toggle.
Off by default is deliberate rather than a hedge. Unlike WordPress, Grav never serves the file that was uploaded, since the theme resizes on demand, so an on-by-default maximum edge is a lossy, irreversible rewrite of someone's original that buys nothing back on the serving side. Sites where storage or a Git Sync repo is the constraint turn it on and get exactly the benefit.
### Constraints worth writing down up front
- It cannot run before `moveTo()`. PSR-7 gives no path to the temp file, so there is nothing on disk to open yet.
- Core's `ImageFile` provides the image library and `fixOrientation()`. Orientation must be applied before the resize, so the cap lands on the long edge of the upright image rather than of a phone photo on its side.
- GIF is excluded and will not be configurable: both the GD and Imagick adapters decode and write a single frame, so resizing an animated GIF would silently discard the animation. Animated WebP is excluded for the same reason.
- A 6000px decode is well over 100MB in GD, so dimensions need checking against the remaining `memory_limit` before anything is opened; if it will not fit, store as-is.
- If the resize fails for any reason (CMYK JPEG, missing GD, anything at all) the original is kept and the failure is logged. It must never turn into a failed upload: the file passed every security check and is already on disk, and an image library having a bad day is not a reason to throw it away.
- If the re-encode comes out larger than the original, keep the original.
Once this exists the browser-side resizes become a pure bandwidth optimisation rather than the only line of defence, which is the right division of labour.
Originally reported by @onetrev in #41.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at HandlesMediaUploads::processUploadedFile() and trace moveTo(), SVG sanitisation, Core's ImageFile, and the onAdminAfterAddMedia flow across page, site, and flex-object uploads. Done means an opt-in maximum edge resizes eligible raster images safely, preserves originals on memory or re-encode failure, excludes GIF and animated WebP, and logs failures without rejecting uploads.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100