Changing an asset's focal point permanently 404s its images on statically cached pages (Glide cached mode)
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 52/100
Hướng nghiên cứu
Start by tracing Invalidate/DefaultInvalidator::getAssetUrls() and ClearAssetGlideCache::handleSaved(), then check ImageGenerator::applyDefaultManipulations() and GlideManager::getCachePathCallable(). Reproduce with the listed cached-mode and static-caching settings, changing an asset's focal point. Done means the affected cached page no longer references a deleted image and serves an image cropped to the new focal point.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Bug description
With statamic.assets.image_manipulation.cache = true (Glide "cached" mode, images served as plain
files out of public/img) and static caching enabled, changing an asset's focal point deletes every
generated image for that asset but leaves the statically cached HTML that references them untouched.
Cached mode registers no /img route, so the stale paths in that HTML have no PHP fallback. They are
plain 404s and nothing regenerates them. The page stays visibly broken until its HTML happens to be
invalidated by something else, or an editor clears the static cache by hand.
Reproduced on two Statamic 6 instances. Opening this as a new issue at @duncanmcclean's request —
this is the same report as #8856, which was closed as not reproducible.
Two config values decide whether this is visible, and the repro attempt in #8856 had both set to the
values that hide it:
1. image_manipulation.cache. With the default cache = false, a stale URL still reaches
GlideController, which regenerates on demand. The worst case is an out-of-date crop for one
request, which looks cosmetic and self-heals. With cache = true there is nothing to fall back to —
php artisan route:list --path=img comes back empty.
2. 'rules' => 'all'. That short-circuits in DefaultInvalidator::invalidate():
if ($this->rules === 'all') {
$this->refreshing
? $this->cacher->refreshUrls($this->cacher->getUrls()->all())
: $this->cacher->flush();
return;
}
It flushes before ever reaching getItemUrls(), so every asset save nukes the whole static cache and
the problem is invisible. Anything other than 'all' — including the shipped default, which is an
empty 'rules' => [] — lands in getAssetUrls() instead:
protected function getAssetUrls($asset)
{
$rules = collect(Arr::get($this->rules, "assets.{$asset->container()->handle()}.urls", []));
...
}
That reads rules.assets.{container}.urls and returns [] when the key is absent. Neither the
shipped default nor either of my sites has an assets key — one site defines only collections
rules, the other only globals rules. So Invalidate is wired up (AssetSaved => refreshAsset),
fires correctly, and invalidates nothing. A silent no-op.
Why the focal point in particular
ImageGenerator::applyDefaultManipulations() adds fit = crop-{focus} when auto_crop is on, and
GlideManager::getCachePathCallable() hashes the whole parameter set into the filename:
md5($sourcePath.'?'.http_build_query($params))
So a new focal point (including a change to just the zoom value) produces a different filename.
Meanwhile ClearAssetGlideCache::handleSaved() calls Glide::clearAsset(), which deletes the
asset's entire cache directory and forgets every manipulation key from the manifest.
PresetGenerator::generate() then regenerates only configured presets — nothing at all when
image_manipulation.presets is empty, which is the default.
Net result: the old file is gone, the new one lives at a different path, and the cached HTML still
points at the old one. Regenerating cannot fix it, because the URL in the HTML no longer corresponds
to anything that will ever be generated. Only invalidating that HTML can.
Queue ordering
Worth noting even for sites that do configure an assets rule: Invalidate and
ClearAssetGlideCache are both ShouldQueue and both land on the same queue. If Invalidate runs
first and traffic re-renders the page before ClearAssetGlideCache gets to it, the freshly cached
HTML captures the about-to-be-deleted path and breaks a second later. Both of my instances run
QUEUE_CONNECTION=redis, so these genuinely run concurrently.
How to reproduce
- Fresh Statamic 6 install.
config/statamic/assets.php:'image_manipulation' => [ 'cache' => true, 'cache_path' => public_path('img'), ], 'auto_crop' => true,STATAMIC_STATIC_CACHING_STRATEGY=half. Leaveinvalidation.rulesat the shipped default — no
assetskey. Do not use'all'.- Page template:
{{ glide:image width="800" height="500" }}. - Visit the page. The HTML is cached and the file lands at
public/img/containers/assets/<folder>/<file>.jpg/<hash>/<file>.jpg. - Control Panel → open the asset → change the focal point (or just its zoom) → save.
- Reload the page.
Expected: the page renders a new Glide URL cropped to the new focal point.
Actual: the cached HTML serves the old path, the file behind it is gone, and the image 404s. It
stays broken until that page's HTML is invalidated by something unrelated, or an editor clears the
static cache by hand.
Related
#8856 (same symptom, closed unreproduced), #6308 (auto-crop URL doesn't change in dynamic mode — same
root cause, benign variant), #8632 (presets deleted on focal point change, none regenerated), #15199
(regenerates when the cached file is missing, which covers the sibling case where files are wiped but
the cache entry survives — not this one, since here the manifest keys are correctly forgotten and the
path genuinely changes).
Logs
No response.
Environment
Statamic: v6.18.1 Pro and v6.31.0 Pro
Laravel: v13.9.0
PHP: 8.4.23
league/glide: 3.2.0
Static caching: half
Queue: redis
statamic.assets.image_manipulation.cache: true
statamic.assets.auto_crop: true
statamic.assets.image_manipulation.presets: []
- Ngôn ngữ chính
- PHP
- Star
- 4.9k
- Fork
- 647
- Merge trung bình
- 1 ngày 8 giờ
- Pull request đã merge (30 ngày)
- 100
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của statamic/cms
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
There's a default crop when cropping imagesCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mởassets
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Make native filesystem directory permissions configurable to support shared POSIX ACL accessĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
Maintainer thường phản hồi trong vòng 1 ngày
-
accessibility
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
Maintainer thường phản hồi trong vòng 1 ngày
-
antlers
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
Maintainer thường phản hồi trong vòng 1 ngày
Issue tương tự
-
needs-triage Platform(Default)
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[Store] RstLoader detects headings inside indented code blocksCó thể đã có người làm @lazerg đã nhận hôm nay. Đang mởBug Status: Needs Review Store
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug repo:ckeditor
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
-
No Code Attached Yet
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
joomla/joomla-cms#48611 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
Maintainer thường phản hồi trong vòng 1 ngày