ploneintranet.themeswitcher makes it possible to access a Plone Intranet
site styled in the default Plone5 Barceloneta theme, instead of in the
Plone Intranet theme.
The reason for doing this is, that not all parts of the Plone user interface are supported in Plone Intranet. Our goal is to first support all the user interface used by a “typical” user. That excludes pages like the control panels which are only used by site administrators.
Up to Plone 4 theme switching was a normal practice, and supported by for example collective.themeswitcher.
In Plone 5, the theming implementation has changed in ways that make theme switching much more difficult, because it depends on registry settings stored in the ZODB and of the way themes are cached for performance.
ploneintranet.themeswitcher depends on changes in
plone.app.theming. These are now run as forks until the required changes to
support theme switching have been merged and released.
How theme switching works¶
Theme switching is implemented based on host name. Out of the box,
if you run Plone Intranet on localhost:8080/Plone you will be able to
access that same site styled in Barceloneta on cms.localhost:8080/Plone.
Of course that requires a
For production, you can configure theme switching to be triggered by a
different host name, by configuring it in the registry via GenericSetup,
hostname_switchlist record of
The default theme switching configuration in Plone Intranet is applied
ploneintranet.suite:default as follows:
<registry> <records interface="ploneintranet.themeswitcher.interfaces.IThemeSwitcherSettings"> <value key="enabled">True</value> <value key="fallback_theme">barceloneta</value> <value key="fallback_rules">/++theme++barceloneta/rules.xml</value> <value key="fallback_absoluteprefix">/++theme++barceloneta</value> <value key="hostname_default">localhost</value> <value key="hostname_switchlist"> <element>cms.localhost</element> </value> <value key="browserlayer_filterlist"> <element>ploneintranet.theme.interfaces.IThemeSpecific</element> </value> <value key="fallback_enabled_bundles"></value> <value key="fallback_disabled_bundles"> <element>ploneintranet</element> </value> </records> </registry>
All of these records are described in
To support theme switching, in
plone.app.theming we’ve introduced a
ThemingPolicy API that is called as an adapter on
ploneintranet.themeswitcher a custom policy adapter is registered:
<adapter factory=".policy.SwitchableThemingPolicy" for="ploneintranet.themeswitcher.interfaces.IThemeSwitcher" />
Because this adapter is bound to a custom browserlayer, it takes precedence over
the default adapter in
plone.app.theming which is bound to
zope.publisher.interfaces.IRequest - our browserlayer is a subclass of that hence more specific.
Note that this is a normal ZCA registration in
configure.zcml, which is sufficient to override the default policy without needing
Our custom policy adapter subclasses the default policy.
Normally, theme settings are stored in the ZODB in the form of registry records.
Because the registry is not layered, that means the original Barceloneta records
got overwritten when we installed
For normal rendering that is not a problem, we just access the registry and get
the values for
ploneintranet.theme which is the default theme.
If we want to fallback to Barceloneta though,
ploneintranet.themeswitcher uses a
takes most of the fallback settings from the actual registry records
for the default theme (ploneintranet), but returns
some “fake” records for the fallback theme (barceloneta) where needed.
The record overrides for the fallback theme can be configured in GenericSetup. The fallback settings provided as defaults here were obtained by comparing, in pdb, the theme settings for a default Barcelonta site, with the values returned for a Plone Intranet themed site. YMMV.
A special helper method decides whether to return the normal settings or the fallback settings, based on the hostname contained in the request.
You can configure hostnames that activate the fallback theme via GenericSetup.
Development mode switching¶
Additionally, when running in development mode you can trigger a theme fallback
?themeswitcher.fallback=1 on the URL. That does not propagate
across links though so you normally are better off as a developer to set up
a /etc/hosts alias for
cms.localhost and accessing your site on cms.localhost
if you want to work with the fallback theme.
Switching Site Action¶
A special helper view
@@switch_theme is provided as a site action to switch between
the main theme and the fallback theme.
By using a different hostname for the fallback themed site we avoid
such caching conflicts. For the main site it uses the
setting; for the fallback site it uses the first hostname listed in
@@switch_theme replaces the hostname in the current URL and redirects
to the other site, either from main to fallback or the other way around, depending where
In addition to the settings switching described above, we also need to manipulate the request object to get Barceloneta to work properly.
ploneintranet.themeswitcher registers an event subscriber that enables us to mangle the request in a way that is needed for theme switching to work:
<subscriber for="plone.app.layout.navigation.interfaces.INavigationRoot zope.app.publication.interfaces.IBeforeTraverseEvent" handler=".policy.filter_request" />
This handler delegates to a method on the theming policy, that:
- Removes any browser layers that conflict with the fallback theme. Typically that is your own theme layer which extends CMFDefault. You should not extend CMFDefault for non-theme browser layers.
- Disables our custom resource bundle(s) by setting a special variable on the request that gets picked up by CMFPlone.
Both the browser layers to be removed and the bundle disabling can be configured via GenericSetup.
ploneintranet.themeswitcher has been set up as a generically re-usable
package. It has no dependencies on the rest of the ploneintranet stack.
All ploneintranet-specific themeswitcher settings are made outside
of the themeswitcher package in
Even though it’s part of the single
ploneintranet egg, in the python sense
ploneintranet.themeswitcher is a separate package. It has it’s own GenericSetup profile and it’s own test suite.
To re-use it, you can add the
ploneintranet egg to your buildout,
and then in your own GenericSetup:
- Declare an installer dependency on
- Configure your own registry.xml settings.
In other words, you need to pull in the whole of ploneintranet but install only ploneintranet.themeswitcher.
This package should probably be factored out into
Like all of ploneintranet, the code is GPL and anybody is welcome to make
that happen. The main things to work on are:
- Reconciling the policy adapter approach, which we now have contributed
plone.app.theming, with the slightly different
switchermulti-adapters used in
- Add/port missing features, like the mobile agent switching.
- Make a decision on whether or not to drop support for CMF skin layer switching support.
None of the above is needed for the Plone Intranet project but we’re happy
to collaborate with anybody who needs those and wants to make an effort
to generalize the
ploneintranet.themeswitcher code into a collective