fix(theming): allow request theme override#61095
Conversation
JustificationThis pull request introduces a query string override to control the visual theme of embedded Nextcloud Forms. The main use case is embedding public forms inside external websites via iframe. In this scenario, the surrounding website may have a fixed light design, while the embedded Nextcloud form can automatically switch to dark mode depending on the visitor’s device or browser preference, for example when the user has system-wide dark mode enabled. This creates an inconsistent user experience: the external page remains white, but the embedded form is rendered with a dark background. In some cases, this also affects readability, branding consistency, and accessibility, especially when the form is intended to visually integrate with a light-themed public website. A global configuration such as forcing the whole Nextcloud instance to light mode is not a suitable solution, because administrators may still want the rest of Nextcloud to respect user preferences or system theme settings. Similarly, applying custom CSS globally can have unintended side effects on other forms or other parts of the instance. The proposed query string override solves this in a narrow and explicit way. It allows the embedding page to request a specific theme only for that embedded form view, without changing the global Nextcloud theme behavior and without affecting other users, other forms, or the rest of the instance. Example use case: <iframe
src="https://cloud.example.com/index.php/apps/forms/embed/FORM_ID?theme=light"
style="width:100%; border:0;">
</iframe>This makes embedding Nextcloud Forms in external websites more predictable and easier to integrate, while preserving the existing default behavior when no override is provided. The change is especially useful for organizations that use Nextcloud Forms as part of public-facing websites, intranets, landing pages, or customer portals where the visual appearance of the embedded form needs to match the host page. |
b816150 to
b7ca7d0
Compare
come-nc
left a comment
There was a problem hiding this comment.
Won’t that conflict for routes that have a "theme" named parameter?
You are correct - thanks for spotting this. |
Add a request-scoped light/dark theme override via the query stringSummaryAdds a request-scoped query string override for light/dark theming:
The override is not persisted to the user's settings — it only affects the Why this approach (addressing the review feedback)A previous iteration read the value with $this->items['parameters'] = array_merge(
$this->items['get'], // ?theme=... (query string)
$this->items['post'], // POST body
$this->items['urlParams'], // route placeholders such as {theme}
$this->items['params']
);The documented precedence is 1. URL/route parameters → 2. POST → 3. GET, so a To eliminate the conflict, the override now reads exclusively from the URL query Details
TestingStatic checks: Unit tests added/updated in
Manual testing on a live instance:
Notes for reviewers
|
Support ?theme=light and ?theme=dark as a request-scoped override that is not persisted to user settings and never bypasses an admin-configured enforce_theme. Invalid, empty, non-string or unknown values are ignored, and non-visual themes such as the OpenDyslexic font theme are preserved. The override is applied only in ThemesService::getEnabledThemes() so that getThemes() and theme registration remain unchanged. Assisted-by: Cline Signed-off-by: Jack Arru <giacomo@beta.srl> Signed-off-by: Jack Arru <jack@x2x.cloud>
The request-scoped ?theme=light / ?theme=dark override set the data-theme-* body attribute but ThemeInjectionService still injected the OS prefers-color-scheme stylesheets on :root and a combined color-scheme meta. On a dark-OS machine the root element and native controls stayed dark, so the override appeared not to work. When an override is active (and no enforce_theme is set), force the chosen theme unconditionally on :root, drop the prefers-color-scheme auto-switching stylesheets, and expose only the override color-scheme meta. Behavior without an override and admin enforce_theme precedence are unchanged. Assisted-by: Cline Signed-off-by: Jack Arru <giacomo@beta.srl> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Joas Schilling <coding@schilljs.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Andrey Dyakov <adduxa@gmail.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Josh <josh.t.richards@gmail.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
…Manager No longer part of the public interface. Just an internal utility function. Apps/etc register in other ways and still end up here appropriately. Signed-off-by: Josh <josh.t.richards@gmail.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Josh <josh.t.richards@gmail.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
…back When decrypting a v3 ciphertext with a mismatched secret, the first attempt throws an Exception (HMAC mismatch). The fallback then calls decryptWithoutSecret() with an empty string, which causes hash_hkdf() to throw a ValueError. Since ValueError extends \Error rather than \Exception, it bypassed the catch block and propagated as an unhandled error, crashing the whole request. Wrap the fallback in its own try/catch(\Throwable) and rethrow the original Exception so callers get a meaningful HMAC mismatch error. Signed-off-by: Anna Larch <anna@nextcloud.com> AI-Assisted-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
If an object is passed from one app to another app through a web component, then only reference changes (new objects) are marked as reactive changes in the receiving app. In this case e.g. `mtime` changes were not properly propagated, so to fix this we explicitly clone the objects before passing to the sidebar tab. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
saves ~50s while running tests Signed-off-by: Robin Appelman <robin@icewind.nl> Signed-off-by: Jack Arru <jack@x2x.cloud>
saves ~5s Signed-off-by: Robin Appelman <robin@icewind.nl> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Robin Appelman <robin@icewind.nl> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Robin Appelman <robin@icewind.nl> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Robin Appelman <robin@icewind.nl> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Robin Appelman <robin@icewind.nl> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de> Signed-off-by: Jack Arru <jack@x2x.cloud>
… client Signed-off-by: skjnldsv <skjnldsv@protonmail.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: John Molakvoæ <skjnldsv@users.noreply.github.com> Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
… groups Signed-off-by: krazyhell <hellwarrior@riseup.net> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Louis Chmn <louis@chmn.me> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Louis Chmn <louis@chmn.me> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Louis Chmn <louis@chmn.me> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Josh <josh.t.richards@gmail.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Josh <josh.t.richards@gmail.com> Signed-off-by: Jack Arru <jack@x2x.cloud>
Signed-off-by: Robin Appelman <robin@icewind.nl> Signed-off-by: Jack Arru <jack@x2x.cloud>
d4830a9 to
8fbf671
Compare
Summary
Adds a request-scoped query string override for light/dark theming:
?theme=light?theme=darkThe override is not persisted to the user's settings.
Details
lightanddark.enforce_themebehavior unchanged.getThemes()unchanged; the override is applied only ingetEnabledThemes().Testing
php -l apps/theming/lib/Service/ThemesService.phpphp -l apps/theming/tests/Service/ThemesServiceTest.phpcomposer test -- apps/theming/tests/Service/ThemesServiceTest.phpManual testing
/login?theme=darkand verifieddata-theme-dark./login?theme=lightand verifieddata-theme-light./login?theme=sepiaand verified no override is applied.