Skip to content

Commit 1a19ab9

Browse files
committed
[bugfix] image defaults, progressive JPEG, session reopen, debugger on redirects
Four fixes plus a changelog consolidation. #4282 - `images.defaults` was gated on `method_exists()` since 2.0.22, but image manipulations are dispatched by `ImageMediaTrait::__call()` rather than being real methods, so `resize`, `crop`, `rotate` and every other processing default was silently dropped. Check the magic-action allowlist too. Separately, `link()` swept the image's own HTML attributes onto the anchor as `data-*` and `Link` then reset them off the image, so `loading: lazy` in the defaults became `data-loading` on the `<a>`. Attributes now follow the image they describe; only width/height are still mirrored onto the anchor for lightbox scripts. #4284 - `unset($this->image)` removed the declared property, so every later write fell through to `Data::__set()` and landed in `$items['image']`, overwriting the media type's own settings. That has disabled every `media.yaml` per-type default filter since 1.4.6, progressive JPEG among them. Assign null instead, and promote progressive JPEG to a real `system.images.progressive_jpeg` option applied to JPEG output only - the old blanket filter would also have interlaced every resized PNG. #4281 - `reopen()` re-read the session from storage without keeping what the request had already done, so a write made before the reopen was reverted. Capture and merge, bail out if the session was destroyed underneath us, and drop the `use_only_cookies` option that does nothing and is deprecated on 8.4. `close()` now reopens so an in-place change nothing announced still commits. #4280 - `render()` asked for `$grav['page']` before checking there was a bar to draw, so a response that returned early from `InitializeProcessor` resolved a page from scratch that late and fell over on Login's `user` service.
1 parent 5ff3950 commit 1a19ab9

12 files changed

Lines changed: 133 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33

44
1. [](#new)
55
* **A dependency can now name the generation of Grav it is for.** A plugin that supports both 1.7 and 2.0 often needs a different version of the same dependency on each, so a `dependencies` entry takes an optional `grav` key: `- { name: form, version: '>=9.1.0', grav: '2.0' }`. Entries without it apply everywhere, so existing blueprints are unchanged. See [Plugin Compatibility](https://learn.getgrav.org/20/plugins/plugin-compatibility#requiring-different-versions-per-grav-generation)
6+
* A new `system.images.progressive_jpeg` setting, on by default, controls whether resized and cached JPEGs are saved as progressive
67

78
1. [](#bugfix)
89
* Installing a package whose dependency is not in the GPM index now says so and carries on, instead of stopping the command with a PHP fatal error. A plugin that still asks for the Grav 1.7 admin plugin was enough to trigger it [getgrav/grav-premium-issues#618](https://github.com/getgrav/grav-premium-issues/issues/618)
910
* A plugin that asks for the `admin` plugin now has that read as Admin 2 on Grav 2, so a plugin written for both 1.7 and 2.0 installs instead of failing on a dependency that cannot exist there. The version it asks for is not carried over, because it describes the old admin's numbering [getgrav/grav-premium-issues#618](https://github.com/getgrav/grav-premium-issues/issues/618)
1011
* Any other dependency that cannot be installed on this generation of Grav is now left out of the install rather than attempted and failed
11-
12-
# v2.0.24
13-
## 09/03/2026
14-
15-
1. [](#bugfix)
1612
* A JSON request whose body is a bare scalar (`"text"`, `12345`, `true`) sent with `Content-Type: application/json` no longer answers a 500 from the request pipeline before any route runs. It is treated as an empty body, so a plugin's webhook or API route gets to answer it, log it and refuse it itself. With `errors.display` on, the old failure also printed a stack trace with server paths to whoever sent it
13+
* An image default such as `resize` set in `system.images.defaults` works again. Every image manipulation was being skipped since 2.0.22, leaving only the loading and decoding hints [#4282](https://github.com/getgrav/grav/issues/4282)
14+
* Settings such as `loading: lazy` now stay on the image when `link` is also on, instead of moving onto the surrounding link where the browser never sees them [#4282](https://github.com/getgrav/grav/issues/4282)
15+
* Resized and cached JPEGs are saved as progressive again, so a photo appears as a whole blurry image that sharpens instead of filling in one line at a time. It has been Grav's default since 2014 but silently stopped working in 1.4.6 [#4284](https://github.com/getgrav/grav/issues/4284)
16+
* A media file's own settings from `media.yaml` are no longer overwritten the moment the image is opened, so custom default filters for an image type work again
17+
* A URL typed with a trailing slash no longer errors out on a site that has the debugger switched on and the Login plugin protecting page media, because the debug bar is now skipped on redirects, where there is no page to put it on [#4280](https://github.com/getgrav/grav/issues/4280)
18+
* With the optional `system.session.read_and_close` setting turned on, a change made to the session early in a request is no longer thrown away by a later write in that same request, and a message added just before a redirect now reaches the page it was meant for [#4281](https://github.com/getgrav/grav/issues/4281)
1719

1820
# v2.0.23
1921
## 09/02/2026

system/blueprints/config/system.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,17 @@ form:
13931393
min: 1
13941394
max: 100
13951395

1396+
images.progressive_jpeg:
1397+
type: toggle
1398+
label: PLUGIN_ADMIN.IMAGES_PROGRESSIVE_JPEG
1399+
help: PLUGIN_ADMIN.IMAGES_PROGRESSIVE_JPEG_HELP
1400+
highlight: 1
1401+
options:
1402+
1: PLUGIN_ADMIN.YES
1403+
0: PLUGIN_ADMIN.NO
1404+
validate:
1405+
type: bool
1406+
13961407
images.cache_all:
13971408
type: toggle
13981409
label: PLUGIN_ADMIN.CACHE_ALL

system/config/media.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
# Progressive JPEG is no longer set here: it applied to every image type, and GD's
2+
# interlace flag also makes PNGs Adam7-interlaced (bigger, slower). It is now
3+
# `system.images.progressive_jpeg`, applied to JPEG output only. The
4+
# `image.filters.default` hook below is still honoured for per-type filters.
15
types:
26
defaults:
37
type: file
48
thumb: media/thumb.png
59
mime: application/octet-stream
6-
image:
7-
filters:
8-
default:
9-
- enableProgressive
1010

1111
jpg:
1212
type: image

system/config/system.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ debugger:
186186
images:
187187
adapter: gd # Image adapter to use: gd | imagick
188188
default_image_quality: 85 # Default image quality to use when resampling images (85%)
189+
progressive_jpeg: true # Save resized and cached JPEGs as progressive, so they appear whole and sharpen rather than filling in a line at a time
189190
cache_all: false # Cache all image by default
190191
cache_perms: '0755' # MUST BE IN QUOTES!! Default cache folder perms. Usually '0755' or '0775'
191192
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example

system/src/Grav/Common/Debugger.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,9 +715,21 @@ public function getCollector($name)
715715
public function render()
716716
{
717717
if ($this->enabled && $this->debugbar) {
718-
// Only add assets if Page is HTML
718+
// The bar is HTML injected into the response body, and the renderer that
719+
// produces it only exists once addAssets() has run -- so there is nothing
720+
// to inject on a response that never got that far. Test for that before
721+
// asking for `page`: a request that returns early from InitializeProcessor
722+
// (the trailing slash redirect, for one) never resolved a page, and
723+
// resolving one here would build the pages index and run the
724+
// onPageFallBackUrl hooks this late, against plugin services that
725+
// onPluginsInitialized never got to register -- Login's `user` among them.
726+
if (!$this->renderer || !$this->grav->initialized('page')) {
727+
return $this;
728+
}
729+
730+
// Only render the bar if the page is HTML.
719731
$page = $this->grav['page'];
720-
if (!$this->renderer || $page->templateFormat() !== 'html') {
732+
if ($page->templateFormat() !== 'html') {
721733
return $this;
722734
}
723735

system/src/Grav/Common/Media/Interfaces/MediaObjectInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,13 @@ public function parsedownElement($title = null, $alt = null, $class = null, $id
125125
*/
126126
public function reset();
127127

128+
/**
129+
* Get the HTML attributes set on this medium.
130+
*
131+
* @return array
132+
*/
133+
public function getAttributes();
134+
128135
/**
129136
* Add custom attribute to medium.
130137
*

system/src/Grav/Common/Media/Traits/ImageMediaTrait.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use function extension_loaded;
2020
use function func_num_args;
2121
use function function_exists;
22+
use function in_array;
2223

2324
/**
2425
* Trait ImageMediaTrait
@@ -53,6 +54,9 @@ trait ImageMediaTrait
5354
/** @var bool */
5455
protected $watermark;
5556

57+
/** @var bool */
58+
protected $progressive;
59+
5660
/** @var array */
5761
public static $magic_actions = [
5862
'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop',
@@ -353,8 +357,12 @@ protected function image()
353357
// Use existing cache folder or if it doesn't exist, create it.
354358
$cacheDir = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
355359

356-
// Make sure we free previous image.
357-
unset($this->image);
360+
// Make sure we free previous image. Assign null rather than unset(): unset()
361+
// removes the declared property, and every later write then falls through to
362+
// Data's __set() and lands in $items['image'], overwriting the media type's
363+
// own `image` settings with the ImageFile object. That is what silently
364+
// killed the default filters in 1.4.6. getgrav/grav#4284.
365+
$this->image = null;
358366

359367
/** @var MediaCollectionInterface $media */
360368
$media = $this->get('media');
@@ -382,6 +390,7 @@ protected function image()
382390
$this->retina_scale = $config->get('system.images.cls.retina_scale', 1);
383391

384392
$this->watermark = $config->get('system.images.watermark.watermark_all', false);
393+
$this->progressive = $config->get('system.images.progressive_jpeg', true);
385394

386395
return $this;
387396
}
@@ -423,6 +432,14 @@ protected function saveImage()
423432
$this->watermark();
424433
}
425434

435+
// Queued last on purpose: GD keeps the interlace flag on the image resource,
436+
// and any operation that builds a new resource (a resize) drops it. Checked
437+
// against the resolved output format so a JPEG converted to PNG or WebP is
438+
// not interlaced along with it. getgrav/grav#4284.
439+
if ($this->progressive && in_array($this->format, ['jpg', 'jpeg'], true)) {
440+
$this->image->enableProgressive();
441+
}
442+
426443
return $this->image->cacheFile($this->format, $this->quality, false, [$this->get('width'), $this->get('height'), $this->get('modified')]);
427444
}
428445
}

system/src/Grav/Common/Media/Traits/MediaObjectTrait.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,16 @@ public function parsedownElement($title = null, $alt = null, $class = null, $id
342342
return $element;
343343
}
344344

345+
/**
346+
* Get the HTML attributes set on this medium.
347+
*
348+
* @return array
349+
*/
350+
public function getAttributes()
351+
{
352+
return $this->attributes;
353+
}
354+
345355
/**
346356
* Reset medium.
347357
*
@@ -480,8 +490,13 @@ public function link($reset = true, array $attributes = [])
480490
$this->display('source');
481491
}
482492

483-
foreach ($this->attributes as $key => $value) {
484-
empty($attributes['data-' . $key]) && $attributes['data-' . $key] = $value;
493+
// The image's own HTML attributes stay on the image; Link re-applies them to
494+
// the medium it wraps. Only the dimensions are also mirrored onto the anchor,
495+
// which is what lightbox scripts read. getgrav/grav#4282.
496+
foreach (['width', 'height'] as $key) {
497+
if (isset($this->attributes[$key])) {
498+
empty($attributes['data-' . $key]) && $attributes['data-' . $key] = $this->attributes[$key];
499+
}
485500
}
486501

487502
empty($attributes['href']) && $attributes['href'] = $this->url();

system/src/Grav/Common/Page/Markdown/Excerpts.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,14 @@ static function ($carry, $item) {
304304
// would leak those the same way. getgrav/grav#4264.
305305
$defaults = $this->config['images']['defaults'] ?? [];
306306
if (count($defaults) && $medium instanceof ImageMediaInterface) {
307+
// An image manipulation such as `resize` is not a real method, it is
308+
// dispatched by ImageMediaTrait::__call() off its own allowlist, so ask
309+
// for that too. Without it every processing default was silently dropped.
310+
// getgrav/grav#4282.
311+
$magic = property_exists($medium, 'magic_actions') ? (array) $medium::$magic_actions : [];
312+
307313
foreach ($defaults as $method => $params) {
308-
if (!method_exists($medium, (string) $method)) {
314+
if (!method_exists($medium, (string) $method) && !in_array((string) $method, $magic, true)) {
309315
continue;
310316
}
311317

system/src/Grav/Common/Page/Medium/ImageMedium.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public function getMeta(): array
9696
#[\ReturnTypeWillChange]
9797
public function __destruct()
9898
{
99-
unset($this->image);
99+
$this->image = null;
100100
}
101101

102102
/**

0 commit comments

Comments
 (0)