Switching Skins
Note: Return to tutorial view.
Overview
This Tutorial describes how Plone chooses a skin when you open a web page, and then describes the multiple ways that you can intervene to switch (a) the skin proper, i.e. the skin layers that are used; (b) the CSS and javascript resources that are used.
More specifically, the Tutorial discusses:
- Using standard TTW forms to change underlying settings in a portal that determine what skins are displayed;
- Using one-time Python scripts that change these underlying settings;
- Using Access Rules that switch the skin every time a web page is loaded.
The Tutorial also discusses two common use cases, switching skins based on URL and the type of user (e.g., Anonymous or Authenticated), giving references for the first use case and sample code for the second.
How Does Plone Choose a Skin?
The current skin is determined every time
you open a Zope or Plone web page on an object -- whether a page
through the Zope Management Interface (ZMI) or in your Plone web site.
Each time you do so, there are several key variables in play:
- portal_skins, the skins tool.
- object, the object you are accessing.
- REQUEST, the HTTP request that is processed as you access the object and which expires when the page is rendered.
- REQUEST.portal_skin.
If this property exists on the REQUEST object, it says which skin to
use. It is typically a cookie which is created when a user logs in, or
when a user changes her/his preferences.
- member.portal_skin.
The standard way of creating a REQUEST.portal_skin cookie is by calling portal_skins.updateSkinCookie(). Provided that the current member has a 'portal_skin' property, this method creates a cookie portal_skin with a value equal to member.portal_skin.
- SKINDATA, a variable tucked in the
Python code of Products.CMFCore.Skinnable. During the life of a
REQUEST, it stores information about what skin to use. The standard way
of changing SKINDATA is a call of the form object.changeSkin('mySkin').
In the final stages of determining the skin, Plone takes three steps:
- Look if a skin name is encoded in SKINDATA. If so, use it.
- Otherwise, look if a skin name is stored in REQUEST.portal_skin. If so, use it.
- Otherwise, look up the default skin in the portal_skins tool.
Intervening to Switch a Skin
With all these variables and steps, you can intervene to switch the skin in many different ways. (In the below list, sibling items represent different alternatives.)
- (1) Encode a skin in the SKINDATA variable ...
- (1A) Call object.changeSkin('mySkin').
- (1B) Manually modify the SKINDATA variable.
- (2) Add a 'portal_skin' property to the REQUEST object ...
- (2A) Add a cookie 'portal_skin' ...
- (2B.i) On the user's personal preferences page, update the 'portal_skin' property (which autoamtically creates the cookie). In order to enable this, you must enable an option on the portal_skins tool using the Properties tab in the ZMI.
- (2B.ii) Update the user's
'portal_skin' property manually, then manually call
portal_skins.updateSkinCookie(). It is not necessary to enable the ZMI
option to change the 'portal_skin' property with Python code outside of
the personal preferences page.
- (2B.iii) Create a cookie manually.
- (2B) Add 'portal_skin' as a simple string attribute.
- (3) Set the default skin in the portal_skins tool, using the Properties tab in the ZMI.
Making a Skin Switch Last
Because Plone determines the current skin on every page load, we need to make sure that any changes we make last.
Two of the above interventions are standard ways of changing underlying settings, ensuring that the skin switch lasts:
- (2B.i) Use the ZMI to enable changing users' skin preferences. Then use the Plone personal preferences page to change a user's preferred skin, and thus automatically create a cookie with the skin name.
- (3) Use the ZMI to change the default skin.
Two of the interventions involve changing underlying settings, but not in a standard way.
- (2B.ii) Set a user's preferred skin outside of the preferences form, and update the cookie.
- (2B.iii) Create a portal_skins cookie manually.
To do this, you should create a one-time script.
Finally, three of the interventions only make short-term changes that only last for the short lifetime of a single REQUEST:
- (1A) Call object.changeSkin('mySkin').
- (1B) Manually modify the SKINDATA variable.
- (2B) Add 'portal_skin' to the REQUEST as a simple string attribute.
To make these changes sustainable, you must use a script that repeats on every page load. The typical way of doing this is using an Access Rule. See EnSimpleStaging Tutorial - Switching Skins By URL and Using Zope/Apache to Switch Skins By URL for examples of how to do this.
Use Case One: Skin By URL
Suppose that you want your site to use different skins depending on whether it is accessed via http://prefix1.---, http://prefix2.---, etc.
A skin must be being determined every time the site is accessed ("What does the URL look like? Well then, let's use this skin"). So, an Access Rule is the best way to switch the skin.
Once again, see EnSimpleStaging Tutorial - Switching Skins By URL and Using Zope/Apache to Switch Skins By URL for examples.
Use Case Two: Skin By Type of User
Suppose that you want your site to use different skins depending on
whether an anonymous user, member, manager, etc. is viewing the page.
The skin only needs to be determined at one point. So, a longer-lasting switch would work. And in fact, Access Rules are loaded early on while rendering a page, before the page is aware of who is viewing, so an Access Rule will also think that the current user is the Anonymous User. So, running a one-time script makes the most sense for this use case.
The following example sets up a user action that shows in
in the personal bar, which allows certain users with a custom
permission to perform that action and switch between the standard Plone
Default skin, and a custom Public View skin. Our example is for a skin
product MySkin, with a permission SWITCH_SKINS_PERMISSION = "MySkin:
Switch Skins" defined in Products/MySkin/config.py.
We define a method in MySkin/Extensions/utils.py
from AccessControl import getSecurityManager, Unauthorized
from Acquisition import aq_base
from Products.CMFCore.utils import getToolByName
from Products.MySkin.config import SWITCH_SKINS_PERMISSION
def switchSkin(self, context):
skins_tool = getToolByName(self, 'portal_skins')
mtool = getToolByName(self, 'portal_membership')
if getSecurityManager().checkPermission(SWITCH_SKINS_PERMISSION, self):
member = mtool.getAuthenticatedMember()
if not hasattr(aq_base(member), 'portal_skin'):
member.setProperties(portal_skin = 'My Public View')
portal_skin = member.portal_skin
if portal_skin == 'My Public View':
member.setProperties(portal_skin = 'Plone Default')
else:
member.setProperties(portal_skin = 'My Public View')
skins_tool.updateSkinCookie()
else:
raise Unauthorized
In this case, the method switchSkin uses code that through-the-web (TTW) Python scripts cannot call. So we add it as an external method ext_switchSkin to the Plone site root, and then add a proxy script switchSkin in a skins folder:
## Script (Python) "switchSkin"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=
##title=Switch skin between my public skin and default Plone skin
##
context.portal_url.getPortalObject().ext_switchSkin(context)
context.REQUEST['RESPONSE'].redirect(context.REQUEST['HTTP_REFERER'])
Finally, we use portal_actions to add an action that shows the public view
- Title: Public View
- Id: publicskinswitch
- URL (Expression): string:$portal_url/switchSkin
- Condition (Expression): python:portal.getCurrentSkinName() != 'My Public View'
- Permission: MySkin: Switch Skins
- Category: User
- Visible: True
and an action that returns to the Plone Default view
- Title: Plone View
- Id: ploneskinswitch
- URL (Expression): string:$portal_url/switchSkin
- Condition (Expression): python:portal.getCurrentSkinName() == 'My Public View'
- Permission: MySkin: Switch Skins
- Category: User
- Visible: True
Voila! A user with the permission "MySkin: Switch Skins" will now see an option in the personal bar to switch skins. (Notice that one should not manually enter a URL to the switchSkin script, because the script will redirect to itself, resulting in an infinite loop.)
It would also be possible to create a script that fired every time somebody logged in, which switched the skin immediately for them -- although we don't supply the code here.
The Trouble With Cookies!
If we use cookies, then after somebody logs out, the cookie may continue to exist. In this case, an anonymous user will see the same skin as the logged-in user.
To avoid this issue, make sure to:
- Avoid enabling the option for cookies to exist after the end of a session (by default this is disabled, but it can be changed in the Zope management screen for portal_skins).
- Design all skins with the knowledge that any user can switch skins with enough technical savvy. Skins are not security!
- Change the skin back to the default skin upon logging out -- for instance, by including the following code in a customized logout.cpy:
from Products.CMFCore.utils import getToolByName
skins_tool = getToolByName(context, 'portal_skins')
mtool = getToolByName(context, 'portal_membership')
member = mtool.getAuthenticatedMember()
try:
member.setProperties(portal_skin = 'My Public View')
skins_tool.updateSkinCookie()
context.acl_users.logout(context.REQUEST)
except:
pass
Synchronizing Skin Layers and CSS/Javascript Resources
Switching
skins changes what skin layers in portal_skins are used, but it does not switch what CSS and javscript resources to load. This could
be disastrous if you have CSS that applies only to the page templates
in one skin, but not to the page templates in another skin.
The solution is simple: in the portal_css or portal_javascripts tools, just add a condition on certain resources similar to
python:portal.getCurrentSkinName() == 'My Public View'
Additional Documentation on Switching Skins
Here are links to additional documentation on switching skins:
- EnSimpleStaging Tutorial - Switching Skins By URL and Using Zope/Apache to Switch Skins By URL. These describe how to switch a skin depending on URL, if the same site is accessed via different URL's (e.g., through a virtual hosting setup). They also demonstrate how to use Access Rules.