Skin switching in Plone 5/replacement for collective.ediskinswitcher

Is there a replacement for collective.editskinswitcher in Plone 5(.2)?

We have the requirement to show switch between standard Plone theme and a custom Diazo theme based on role(s) of the current user and/or public hostname (www.domain.com vs. edit.domain.com).

both can be achieved with diazo. There is a body tag for the user roles.
See https://docs.plone.org/adapt-and-extend/theming/barceloneta.html#using-the-barceloneta-theme-only-for-the-backend for an example that uses rather than a url or a role, switches the theme based on if the the page requires more than just view permission.

Doable but the logic which theme to use does not belong into a theme itself.

it does if you are using a single theme and not using different domain names but rather switching to a simple part of teh same theme when doing admin type actions like edit, site setup etc. Means you can use the same domain and switch dynamically. Works pretty well in reality. We use it for all out sites.

but you can do it another way. I've no idea of the status of other solutions.

Is it in some way possible to present/render different themes based on the url/domain?

mekell via Plone Community wrote at 2022-6-4 17:38 +0000:

...
Is it in some way possible to present/render different themes based on the url/domain?

Plone 5 uses three skin mechanisms: portal_skin, skin layer views
and the theme which controls the output transform.

portal_skin can be controlled via cookies or request parameters.
The applicable skin layers are determined by the interfaces provided by the
current request object. The theme is determined via
IThemingPolicy(request), thus can also be controlled via the
request object.

You could implement your custom logic in a traversal hook
(e.g. a SiteAccess AccessRule). The challenge will be that
your settings are not overridden by subsequence traversal hooks.

At your place, I would look how collective.ediskinswitcher
has solved this problem (it had to handle the first 2
of the 3 skin types; its approach will be extendable to the 3rd as well).

@dieter: thank you for the hint.

I've started yesterday to play with collective.editskinswitcher and traced some parts of the code. But I am overwhelmed.

I must accept that my skills are not enough at the moment for this kind of Grundlagenforschung.

Related to what Dylan said.
This is how I do it (by a setting in control panel)

    <rules if="$default">
  	 <xi:include href="++theme++dutchestheme/default.xml"/>
  	</rules>

    <rules if="$spot">
  	 <xi:include href="++theme++dutchestheme/spot.xml"/>
  	</rules>

    <rules if="$spot_2">
  	 <xi:include href="++theme++dutchestheme/spot_2.xml"/>
  	</rules>

You can use the same for domains and / or users, I assume:

Probably something like:

<rules css:if-content="body.userrole-authenticated">
    <xi:include href=++theme++dutchestheme/spot_2.xml/>|
</rules>|


 or <rules css:if-content="body.userrole-manager"> 

 or <rules css:if-not-content="body.userrole-authenticated">

@espenmn: Could you please give an example how to know the url/domain in a diazo rule?

Even if I think this logic should be not in the theme (@zopyx) this could be a workaround.

While it does not fullfill your use case, you may get some ideas from lineage.themeselection · PyPI

The body tag includes data-portal-url, I am not sure how it can be used in a diazo rule.

Do you need different theme for something different than

  • frontend
  • logged in
  • anon

?

mekel: If it should be of any interest, this add-on can be used to theme 'folders', which can also be subsites (and own domain)

So, for these two I have two different themes (they are quite similar, mainly font-sizes, colors and 'mathjax':

http://www.marfag.no/formelsamlinger/formelhefte-navigering-f1/formelhefte-2013-navigering#autotoc-item-autotoc-0

http://www.marfag.no/f11/kapittel-1-innledning-1

With their own domains also, like:

http://f11.marfag.no/

@dieter: following your tipps I've tried the following but I don't know whether I'm on the right track:

I have a configure.zcml with a subscriber for IBeforeTraverseEvent

  <subscriber
      for="*
           zope.traversing.interfaces.IBeforeTraverseEvent"
      handler=".traversal.switch_theme"
      />

which calls switch_theme from traversal.py:

from plone.app.theming import utils

def switch_theme(object, event):
    if "www.mydomain.org" in event.request["ACTUAL_URL"]:
        for theme in utils.getAvailableThemes():
            print(theme.__name__)
            if theme.__name__ == "barceloneta":
                try:
                    #the following raises "'NoneType' object has no attribute 'currentTheme'"
                    utils.applyTheme(theme)
                    print("applyTheme", theme)
                except Exception as e:
                    print("exception", e)

Is utils.applyTheme the right place to set the theme? Why is it None?

How do I decide when to switch_theme? A page request triggers subsequent requests like the following and the theme should not be changed on every (sub)request:

    /++theme++my-theme/rules.xml
    /++theme++my-theme/index.html
    /config.js

Something 'quick and dirty' would be just adding the portal url as a CSS class in Products.CMFPlone/Products/CMFPlone/browser/templates/main_template.pt at master · plone/Products.CMFPlone · GitHub

In line 50, you could change the line to

 class="${body_class} ${portal_url}"
 tal:attributes="dir python:isRTL and 'rtl' or 'ltr';

Which uses from line 16

  portal_url python:portal_state.portal_url();

Important
you should probably decode / parse the url so you get a 'proper CSS class"


PS: I dont know from what file the CSS body classes come from, probably better to do it there

mekell via Plone Community wrote at 2022-6-7 15:44 +0000:

@dieter: following your tipps I've tried the following but I don't know whether I'm on the right track:
...
Is utils.applyTheme the right place to set the theme? Why is it None?

I do not think so.

Your use case requires different behavior based on
individual request properties. As you are aware,
you do not want to change persistent state.
applyTheme does not depend on the current request:
it sets the "global" (request independent) theme
and will modify persistent state.

As I wrote earlier, there are 3 different kinds of skinning.
If you need only to adapt the theming kind (i.e. the output transform)
to depend on the request,
you could follow Espen's suggestion
(i.e. use a fixed transform but let it behave slightly differently
depending on request specific information put into the response).
I likely would use a viewlet to add the necessary information into
the response.

Alternatively, you could override the IThemingPolicy
adapter registration (if necessary with z3c.unconfigure),
look in your adapter at whatever request properties you
like and return the appropriate theme.

It is really better to use data-xxx attributes, they exists exactly for this kind of purpouse. Url must always be encoded when inserted in attributes but we know exacly the value here, so it should not be a problem (just encode & and " but this chars should not be in a portal url).

How do you use data-attributes with diazo ?

Being an attribute, you can use xsl for example:
https://docs.diazo.org/en/latest/recipes/adding-an-attribute/index.html
you can load it in a variable:'
https://spiritplonetheming.readthedocs.io/en/latest/diazo.html

<xsl:variable name="header" css:select="#PLONE_THEMING_HEADER_OPTION"></xsl:variable>

I don't really know Diazo, but I think it should be possible (and also the default so relative urls can be prepended with the portal_url).

The data-attribute is already there (< body data-portal-url="" ), so if someone knows 'the right syntax", please add it here

(Maybe it can be used to for example test/showcase different layouts / themes by prepending the theme name to the url )

You can do tricks like these:

        <!-- assign html template to default content -->
        
        <rules if-not-content=" //*[@data-template='product_filter_view'] | //*[@data-template='collection_album_view']" >
            <theme href="html/index-mega-responsive.html" />
        </rules>
        <!-- Replace title with Plone's page title 
            we add our company name first
            if breadcrumbs-1 is a link, we are on a category subpage or lower
            and also include the main category
            if there is a data-template product_item_view we are on a product page
            and insert text from  .item-title > h4 and double-width horizontal separator
        -->
        <replace css:theme="html head title" mode="raw">
            <title>
                <xsl:choose>
                    <xsl:when test="//*[@id='breadcrumbs-1']/a">
                        PNZ:
                        <xsl:value-of select="//*[@id='breadcrumbs-1']/a/text()" />,
                        <xsl:choose>
                            <xsl:when test="//*[@data-template='product_item_view']">
                                <xsl:value-of select="//*/header[@class='item-title']/h1/text()" />
                                —
                            </xsl:when>
                        </xsl:choose>
                    </xsl:when>
                    <xsl:otherwise>
                        PNZ Produkte GmbH:
                    </xsl:otherwise>
                </xsl:choose>
                <xsl:value-of select="//html/head/title" />
            </title>
        </replace>