Skin layer customisation
Traditionally, customisation in Zope has been done through acquisition. For example, a developer might have placed a page template in the root of the site and acquire it to render an object in a subfolder /foo/bar. If the developer wanted a different rendering for objects in /foo/bar/baz only, he may place a template with the same name (id) in that folder, which would take precedence.
Since Plone (and the CMF) is all about letting site users create their own content hierarchies, placing templates and code inside the site structure would become messy. Therefore, the CMF developers invented the portal_skins tool.If you view this tool in the ZMI, you will find one or more skins on its Properties tab, notably Plone Default. A skin (which we tend to call themes to avoid confusion), is just an ordered list of skin layers. The skin layers listed on the Properties tab corresponds to folders inside the portal_skins tool. When Zope is looking for a resource (logo.jpg, say) it will search the current folder, and then parent folders up until the site root. If the resource can't be found, CMF will look for it in the skin layers for the current theme, starting at the top of the list and going down until it finds one.
Notice how custom is the first entry in the list. This is how through-the-web customisation works. Copy an item to this folder, and the new copy takes precedence over existing entries. Also notice how all the other folders read-only. That is because they are not folders in the ZODB at all - they are Filesystem Directory Views - folders that reflect the files inside a particular folder in the filesystem.
Thus, to create a product that overrides some resource in a skin layer that ships with Plone, we must:
- Register a new filesystem directory view
- Insert this in the list of skin layers for the current skin, normally just below the custom folder
- Copy the relevant resource into the new skin layer directory, keeping the same name
- Customise this copy
Note: Some resources will have an associated .metadata file. If so, you must copy the .metadata file along with the resource when customising the resource.
Traditionally, all Plone resources - images, style sheets, Javascript files, page templates and Python scripts such as form handlers, were kept in skin layers. However, as you may imagine this became a bit cumbersome as Plone grew, since the skin layer mechanism presupposes that every resource has a globally unique name (id). Still, most of the core views, images and style sheets that are part of Plone are kept in skin layers that have names prefixed with plone_, such as plone_images or plone_templates. On the filesystem, you will find these inside CMFPlone/skins. You can search the subfolders of this folder to find various resources.
In general, if a resource is mentioned in the Plone UI (e.g. from a url, an action in portal_actions or an alias in portal_types) and it is not prefixed with either @@ (as in @@view) or ++resource++ (as ++resource++stylesheet.css), then it is likely to be found in a skin layer.
Registering and installing a new filesystem-based skin layer
In the example.customization product, we have a skins/ subdirectory, with a single skin layer directory, example_customiation. For us to be able to register this a filesystem directory view, we must tell CMF about the top-level skins directory. We do so in the package's __init__.py file:
from Products.CMFCore.DirectoryView import registerDirectory
GLOBALS = globals()
registerDirectory('skins', GLOBALS)
def initialize(context):
"""Intializer called when used as a Zope 2 product."""
For this to work, we also need to ensure that this package is a Zope 2 product. If it is in the magical Products.* namespace (e.g. a traditional product placed in the Products directory if a Zope instance), this happens automatically. If we are using an egg-based product in a different namespace, we add this to the package's configure.zcml:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
xmlns:five="http://namespaces.zope.org/five"
i18n_domain="example.customization">
<five:registerPackage package="." initialize=".initialize" />
...
</configure>
We then need to create the directory view and install it for the current skin when the product is installed in a Plone site. We do this using GenericSetup. First, we must register a new extension profile so that the product is installable. This is done in configure.zcml, as follows:
<genericsetup:registerProfile
name="default"
title="Example customizations"
directory="profiles/default"
description='Install various customizations from the example.customization package'
provides="Products.GenericSetup.interfaces.EXTENSION"
/>
Then we use the skins.xml import handler to configure the portal_skins tool:
<?xml version="1.0"?>
<!-- This file holds the setup configuration for the portal_skins tool -->
<object name="portal_skins">
<object name="example_customization"
meta_type="Filesystem Directory View"
directory="example.customization:skins/example_customization"/>
<skin-path name="*">
<layer name="example_customization"
insert-after="custom"/>
</skin-path>
</object>
If we wanted to, we could register as many directory views as we wanted here.
To test it all, we have placed an exciting new logo in skins/example_customizations/logo.jpg, overriding logo.jpg from CMFPlone/skins/plone_images. When the product is installed, you will see this logo instead of the default Plone one. You may need to do a hard-refresh in your browser.
How to remove a skin layer?
After example.customization deinstallation an example_customization layer in Plone Default theme is still there. How can I remove this?
Thanks.