Plone’s workflow functionality, which is the portal_workflow tool of the underlying Zope CMF, can do just about anything you want it to do, provided that you can figure out how to do it. Plone itself still doesn’t have a UI for creating and configuring workflow, so you have to do it in the ZMI, which AFAICT has changed little in the time I have used Zope and the CMF. Plone has fairly recently integrated placeful workflow, adding the ability to override sitewide workflows at the folder level, which is an important consideration in a large and complex site. Hopefully, they’ve fixed the bugs in the UI (both Plone and the ZMI) which I encountered when I first tried using it.
There are a couple things about CMF/Plone workflow that are not exactly intuitive, or at least are different from how other CMS systems implement workflow functionality:
- A content item is always under workflow, if its content type is configured to use workflow at all. In other words, a content item doesn’t go “through” workflow; rather, the item is bound to its workflow.
- Workflow has no security per se. In other words, access to objects in various workflow states is determined by the security settings on the objects in the ZODB. Workflow transitions implement changes in ZODB security settings.
An access control gotcha
Zope security settings are, so to speak, atomic. Inheritance of permissions can be toggled on/off at any level of the object hierarchy and explicitly overridden on any object. So far, so good. However, unlike file systems, access to objects is not strictly dependent on access to their containers. For example, a “private” folder which is not publicly accessible can contain “published” or “public draft” items which are. The security problem is that content authors may assume, not without reason, that any content they put in a private folder will also be private. This is a deep issue because CMF workflow explicitly sets object permissions and Zope security only considers the permissions on an object, not its container (factoring in inherited permissions, of course). A workaround I have implemented is a modification of the default workflow to force setting the initial state of an object to “private” if its parent object is in the private state. At least this provides a modicum of protection to unwitting content authors; they have to explicity “make visible” or “publish” these items to make them more widely accessible.
1. Wrote a script hideInPrivateParent:
## Script (Python) "hideInPrivateParent" ##bind container=container ##bind context=context ##bind namespace= ##bind script=script ##bind subpath=traverse_subpath ##parameters=state_change ##title=Transition state_change.object to private state if parent object is private ## wftool = context.portal_workflow PRIVATE_TRANSITION = 'hide' PRIVATE_STATE = 'private' obj = state_change.object parent = obj.aq_inner.getParentNode() if parent is not None: try: if wftool.getInfoFor(parent, 'review_state') == PRIVATE_STATE: wftool.doActionFor(obj, PRIVATE_TRANSITION) except: pass
2. Created a new workflow state called “initialized” and set it as the default workflow state.
3. Created a new workflow transition call “initialize”. Set the “trigger type” to “automatic”. Set the “destination state” to “visible”. Set “script (after)” to hideInPrivate Parent.
The result is that when a new content item is created with this workflow, its state will be private if its parent object (typically a folder) is private, otherwise it will be “visible”.
Continued in Part 4.