From f443f71ed4c4347130d712e6ca65537e86a6e552 Mon Sep 17 00:00:00 2001 From: IanM Date: Thu, 29 Oct 2026 09:11:06 +0100 Subject: [PATCH] docs: theme images chosen on each request, and the image settings while a theme sets them --- docs/extend/theme.md | 40 +++++++++++++++++++++++++++++++++------ docs/extend/update-2_x.md | 2 ++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/docs/extend/theme.md b/docs/extend/theme.md index 35aa2c7b4..597d747eb 100644 --- a/docs/extend/theme.md +++ b/docs/extend/theme.md @@ -76,18 +76,46 @@ return [ | Method | Sets | |---|---| -| `logo(string $file)` | The forum logo. A PNG, JPEG, WebP, GIF or SVG file. | -| `logoDarkMode(string $file)` | The logo shown in dark mode. The same formats as `logo()`. | -| `emailLogo(string $file)` | The logo at the top of emails. Not an SVG, which mail clients can't be relied on to show. | -| `favicon(string $file, ?string $svg = null)` | The favicon, from a PNG, JPEG, WebP or GIF, ideally at least 48px square, and optionally an SVG version for browsers that support SVG favicons. | -| `appIcon(string $file, ?string $background = null)` | The icon phones show when the forum is added to a home screen, from a square PNG, JPEG, WebP or GIF at least 512px. The background colour fills its transparent parts on iPhones and iPads and the space around it on Android, and defaults to the one set on the Appearance page. | +| `logo(string\|callable $file)` | The forum logo. A PNG, JPEG, WebP, GIF or SVG file. | +| `logoDarkMode(string\|callable $file)` | The logo shown in dark mode. The same formats as `logo()`. | +| `emailLogo(string\|callable $file)` | The logo at the top of emails. Not an SVG, which mail clients can't be relied on to show. | +| `favicon(string\|callable $file, string\|callable\|null $svg = null)` | The favicon, from a PNG, JPEG, WebP or GIF, ideally at least 48px square, and optionally an SVG version for browsers that support SVG favicons. | +| `appIcon(string\|callable $file, string\|callable\|null $background = null)` | The icon phones show when the forum is added to a home screen, from a square PNG, JPEG, WebP or GIF at least 512px. The background colour fills its transparent parts on iPhones and iPads and the space around it on Android, and defaults to the one set on the Appearance page. | Each image goes through the same steps as an uploaded one. A raster logo is stored at the [logo size and quality](#logo-size-and-quality), and an SVG logo is used as it is. The favicon becomes a `.ico` and a PNG, and the app icon becomes Apple's touch icon, the web app manifest's icons and a maskable icon. Without `emailLogo()`, emails use a copy of the logo, unless it is an SVG. -The images are drawn into the forum's assets once, and again only when a file or one of its options changes. An image that can't be read is logged and left out. On the Appearance page, admins see the images the theme sets, marked as set by your extension, but can't change them. +The images are drawn into the forum's assets once, and again only when a file's contents or one of its options change. The files an image no longer uses are kept for 30 days, for cached pages and emails that still show them. An image that can't be read is logged and left out. On the Appearance page, admins see the images the theme sets, marked as set by your extension, but can't change them. A path that doesn't exist, or an SVG where one isn't allowed, throws an `InvalidArgumentException` when you call the method. +### Choosing Images on Each Request + +Instead of a path, any of these arguments can be a callable that returns one. Flarum calls it on each request, with its parameters resolved from the container, so a theme can let admins choose between sets of images: + +```php +use Flarum\Extend; +use Flarum\Settings\SettingsRepositoryInterface; + +return [ + (new Extend\Theme()) + ->logo(fn (SettingsRepositoryInterface $settings) => __DIR__.'/resources/'.($settings->get('acme-theme.season') ?: 'standard').'/logo.svg') + ->appIcon( + fn (SettingsRepositoryInterface $settings) => __DIR__.'/resources/'.($settings->get('acme-theme.season') ?: 'standard').'/app-icon.png', + background: fn (SettingsRepositoryInterface $settings) => $settings->get('acme-theme.season') === 'winter' ? '#0f172a' : '#ffffff', + ), +]; +``` + +While a callable returns `null`, the image is the admin's to set, as if the theme didn't set it. When it returns a different file, the image is drawn again, and switching back within 30 days uses the files drawn before. + +A callable can be called more than once in a request, so it should be quick and give the same answer each time. It should also give every visitor the same answer: an image that changes from one visitor to the next is drawn again on every request. A path a callable returns that doesn't exist, or an SVG where one isn't allowed, is logged and left out rather than thrown. + +### Reading Logos and Icons + +`Flarum\Forum\Branding\Branding` gives the URL of each logo and icon, whether an admin uploaded it or a theme set it, as do the forum's `logoUrl`, `faviconUrl` and other attributes in the frontend. + +Code written before Flarum 2.1 may read the paths of uploaded images from the `logo_path`, `logo_dark_mode_path`, `favicon_path` and `email_logo_path` settings. While a theme sets one of these images, its setting holds the drawn file, so that code gets the theme's image. An SVG logo is the exception: an upload is never an SVG, so `logo_path` keeps the uploaded logo. When the theme stops setting an image, the setting returns to the upload. + ### Web App Manifest Flarum serves a web app manifest at `/manifest`, which phones read for the name and icons of a home screen shortcut. It opens the forum in the browser. An extension can change it with `manifest()`, for example to open the forum as an installed app: diff --git a/docs/extend/update-2_x.md b/docs/extend/update-2_x.md index 2a9a9b01f..e461acd0c 100644 --- a/docs/extend/update-2_x.md +++ b/docs/extend/update-2_x.md @@ -12,6 +12,8 @@ If you need help applying these changes or using new features, please start a di Forums can now set an app icon, which Flarum turns into Apple's touch icon and the icons of a new web app manifest at `/manifest`, and the favicon is also stored as a `.ico`. The `Theme` extender can set every logo and icon with `logo()`, `logoDarkMode()`, `emailLogo()`, `favicon()` and `appIcon()`, and change the manifest with `manifest()`. See [Logos and Icons](theme.md#logos-and-icons). +Each image can also be given a callable that chooses it on each request. While a theme sets an image, the `logo_path`, `logo_dark_mode_path`, `favicon_path` and `email_logo_path` settings hold the drawn file, so code that reads them keeps working. New code should ask `Flarum\Forum\Branding\Branding` for the URLs. See [Choosing Images on Each Request](theme.md#choosing-images-on-each-request) and [Reading Logos and Icons](theme.md#reading-logos-and-icons). + `UploadImageController::makeImage()` may now also return the image as a string. An extension that serves its own web app manifest, such as a progressive web app, should change Flarum's with `manifest()` instead, as browsers use the first manifest a page links. ### Logos