Commits

Anonymous committed c9af77b

add 0.3 code

  • Participants
  • Parent commits 923f3ca
  • Tags 0.3

Comments (0)

Files changed (39)

 --------
 
 Ed Crewe, `ILRT
-<http://www.ilrt.bris.ac.uk/>`_ at University of Bristol, January 2009
+<http://www.ilrt.bris.ac.uk/>`_ at University of Bristol, April 5th 2009
 
 Formal workflow is designed for sites where there may be many editors
 for whom unmoderated access to change live published content on the 
-web site is not desired. A typical scenario may be an organistaions 
+web site is not desired. A typical scenario may be an organistaion's 
 public website which has to comply with certain legal restrictions or
 editorial style for example. To ensure this compliance only a limited
 subset of editors are trusted to review and publish content.
+Whilst content in the private state is available to all editors.
 
 This package applies a workflow definition based on simple publication 
 workflow ... but it ensures that editors cannot modify public content.
 
 .. _`plone.app.iterate`: http://pypi.python.org/pypi/plone.app.iterate
 
-Editors and owners are also restricted from deleting items or reverting 
-them to past versions ... essentially anything that could change published 
-content ... without review.
-
-Hence only new items can be published, instead working copies are checked in 
-by users with the reviewer or manager role.
-
-The package installs a skin layer in order to allow for modifying the iterate 
-interfaces security declaration. If used in conjunction with a theme egg just 
-copy the contents of browser/configuration.xml to the one in your theme.
+Editors and owners are also restricted from deleting published items or 
+reverting them to past versions, essentially anything that could change 
+published content, without review.
 
 Workflow
 --------
   - A week later some more information needs to be added to the document
   - The editor goes to it, but it there is no workflow menu just 
     State:Published so they cannot retract it. The edit and history tabs
-    are also gone. So instead they must click on 'Check out' from the actions menu.
+    are also gone. So instead they must click on 'Make changes' from the actions menu.
   - This locks the item and marks it as being edited in a working copy.
   - The editor does their edits then clicks on submit to bring their changes to the
     attention of the reviewer
   - The reviewer sees the page pop up in their review list
   - They click on it and look at the changes the editor has made. 
     They like the changes but decide they want some modifications made to them
-    by the editor. They dont want to 'Cancel check-out' since the editor has done a
+    by the editor. They dont want to 'Cancel changes' since the editor has done a
     lot of changes, so they just add a note of the further changes needed and make
     the working copy private again.
 
   Reviewer
   - The reviewer notices the item is back again in their review list, so realises 
     it has been re-edited.
-    They click on it ... see that it is ready and so do the 'Check in' to replace 
+    They click on it ... see that it is ready and so do the 'Accept changes' to replace 
     the current published version, at which point the working copy is removed.
 
-This package hardly deserves to be in pypi since it is really all just
-xml config data, however since it is a commonly required workflow which
-does require some time consuming configuration tweaks, It seemed worthwhile to
-submit it ... at least it has some python in the functional tests. 
+Notes
+-----
 
-NB: If this workflow is applied to an existing site ... then you may require the 
-ilrt.migrationtool to use its utility for mapping  existing content states from 
-the old workflow to formalworkflow
+This package is really all just xml config data and reworking skin copy, paste
+and delete security to be object specific rather than folder based.
+However though it contains little python aside from the functional tests, 
+it is a commonly required workflow which does require some time consuming 
+configuration tweaks.
 
+If this workflow is applied in conjunction with a theme egg then the formalworkflow
+skin should be added near the top of the editing layer's skins listing in the 
+portal_skins tool.
+
+If this workflow is applied to an existing site ... then you may require the 
+`ilrt.migrationtool 
+<http://pypi.python.org/pypi/ilrt.migrationtool>`_ to use its utility for mapping  
+existing content states from the old workflow to formalworkflow
+
+If you wish to use custom types with this workflow you will need to make them versionable 
+via the 
+`portal_repository tool
+<http://plone.org/documentation/how-to/enabling-versioning-on-your-custom-content-types>`_ or 
+plone.app.iterate will not be available for them. 

File docs/HISTORY.txt

 Changelog for ilrt.formalworkflow
 ---------------------------------
 
-(name of developer listed in brackets)
+    (name of developer listed in brackets)
 
-ilrt.formalworkflow - 0.2 Released
+ilrt.formalworkflow - 0.3 Released - 5th April 2009
 
-- Released to pypi with some documentation
-  [Ed Crewe, ILRT - University of Bristol]
+    - Changed action names and templates to be less versioning orientated
+      Now uses Make changes, Accept changes and Cancel changes
+    - Fixed folder permissions checks blocking delete, copy and paste of objects
+      in published folders for editors
+    - Cleaned up permissions
+    - Removed unnecessary formalworkflow layer
+
+    [Ed Crewe, ILRT - University of Bristol]
+
+ilrt.formalworkflow - 0.2 Released - 20th Jan 2009
+
+    - Released to pypi with some documentation
+
+    [Ed Crewe, ILRT - University of Bristol]
 
 ilrt.formalworkflow - 0.1 Unreleased
 
-- Initial package structure.
-  [zopeskel]
+    - Initial package structure.
 
+    [zopeskel]
+

File docs/TODO.txt

+To Do list
+----------
+
+Find a different way to allow permissions of objects to be used rather 
+than those of their containers for delete, copy and paste.
+Currently it uses a workaround that needs proxy manager then does a
+manual permission check, within the skins.
+
+
+

File ilrt.formalworkflow.egg-info/PKG-INFO

 Metadata-Version: 1.0
 Name: ilrt.formalworkflow
-Version: 0.2
-Summary: Formal workflow for Plone prevents editing, deletion or reversion of published content from skipping review
-Home-page: https://svn.ilrt.bris.ac.uk/repos/ecu/trunk/src/ilrt.formalworkflow
+Version: 0.3
+Summary: Formal workflow is designed to prevent editing, deletion or reversion of published content from skipping review
+Home-page: https://svn.ilrt.bris.ac.uk/repos/pypi/ilrt.formalworkflow
 Author: Internet Development, ILRT, University of Bristol
 Author-email: internet-development@bris.ac.uk
 License: GPL
         --------
         
         Ed Crewe, `ILRT
-        <http://www.ilrt.bris.ac.uk/>`_ at University of Bristol, January 2009
+        <http://www.ilrt.bris.ac.uk/>`_ at University of Bristol, April 5th 2009
         
         Formal workflow is designed for sites where there may be many editors
         for whom unmoderated access to change live published content on the
-        web site is not desired. A typical scenario may be an organistaions
+        web site is not desired. A typical scenario may be an organistaion's
         public website which has to comply with certain legal restrictions or
         editorial style for example. To ensure this compliance only a limited
         subset of editors are trusted to review and publish content.
+        Whilst content in the private state is available to all editors.
         
         This package applies a workflow definition based on simple publication
         workflow ... but it ensures that editors cannot modify public content.
         
         .. _`plone.app.iterate`: http://pypi.python.org/pypi/plone.app.iterate
         
-        Editors and owners are also restricted from deleting items or reverting
-        them to past versions ... essentially anything that could change published
-        content ... without review.
-        
-        Hence only new items can be published, instead working copies are checked in
-        by users with the reviewer or manager role.
-        
-        The package installs a skin layer in order to allow for modifying the iterate
-        interfaces security declaration. If used in conjunction with a theme egg just
-        copy the contents of browser/configuration.xml to the one in your theme.
+        Editors and owners are also restricted from deleting published items or
+        reverting them to past versions, essentially anything that could change
+        published content, without review.
         
         Workflow
         --------
         - A week later some more information needs to be added to the document
         - The editor goes to it, but it there is no workflow menu just
         State:Published so they cannot retract it. The edit and history tabs
-        are also gone. So instead they must click on 'Check out' from the actions menu.
+        are also gone. So instead they must click on 'Make changes' from the actions menu.
         - This locks the item and marks it as being edited in a working copy.
         - The editor does their edits then clicks on submit to bring their changes to the
         attention of the reviewer
         - The reviewer sees the page pop up in their review list
         - They click on it and look at the changes the editor has made.
         They like the changes but decide they want some modifications made to them
-        by the editor. They dont want to 'Cancel check-out' since the editor has done a
+        by the editor. They dont want to 'Cancel changes' since the editor has done a
         lot of changes, so they just add a note of the further changes needed and make
         the working copy private again.
         
         Reviewer
         - The reviewer notices the item is back again in their review list, so realises
         it has been re-edited.
-        They click on it ... see that it is ready and so do the 'Check in' to replace
+        They click on it ... see that it is ready and so do the 'Accept changes' to replace
         the current published version, at which point the working copy is removed.
         
-        This package hardly deserves to be in pypi since it is really all just
-        xml config data, however since it is a commonly required workflow which
-        does require some time consuming configuration tweaks, It seemed worthwhile to
-        submit it ... at least it has some python in the functional tests.
+        Notes
+        -----
         
-        NB: If this workflow is applied to an existing site ... then you may require the
-        ilrt.migrationtool to use its utility for mapping  existing content states from
-        the old workflow to formalworkflow
+        This package is really all just xml config data and reworking skin copy, paste
+        and delete security to be object specific rather than folder based.
+        However though it contains little python aside from the functional tests,
+        it is a commonly required workflow which does require some time consuming
+        configuration tweaks.
         
+        If this workflow is applied in conjunction with a theme egg then the formalworkflow
+        skin should be added near the top of the editing layer's skins listing in the
+        portal_skins tool.
+        
+        If this workflow is applied to an existing site ... then you may require the
+        `ilrt.migrationtool
+        <http://pypi.python.org/pypi/ilrt.migrationtool>`_ to use its utility for mapping
+        existing content states from the old workflow to formalworkflow
+        
+        If you wish to use custom types with this workflow you will need to make them versionable
+        via the
+        `portal_repository tool
+        <http://plone.org/documentation/how-to/enabling-versioning-on-your-custom-content-types>`_ or
+        plone.app.iterate will not be available for them.
         
         Changelog for ilrt.formalworkflow
         ---------------------------------
         
         (name of developer listed in brackets)
         
-        ilrt.formalworkflow - 0.2 Released
+        ilrt.formalworkflow - 0.3 Released - 5th April 2009
+        
+        - Changed action names and templates to be less versioning orientated
+        Now uses Make changes, Accept changes and Cancel changes
+        - Fixed folder permissions checks blocking delete, copy and paste of objects
+        in published folders for editors
+        - Cleaned up permissions
+        - Removed unnecessary formalworkflow layer
+        
+        [Ed Crewe, ILRT - University of Bristol]
+        
+        ilrt.formalworkflow - 0.2 Released - 20th Jan 2009
         
         - Released to pypi with some documentation
+        
         [Ed Crewe, ILRT - University of Bristol]
         
         ilrt.formalworkflow - 0.1 Unreleased
         
         - Initial package structure.
+        
         [zopeskel]
         
         
+        To Do list
+        ----------
+        
+        Find a different way to allow permissions of objects to be used rather
+        than those of their containers for delete, copy and paste.
+        Currently it uses a workaround that needs proxy manager then does a
+        manual permission check, within the skins.
+        
+        
+        
+        
 Keywords: web zope plone workflow
 Platform: UNKNOWN
 Classifier: Programming Language :: Python

File ilrt.formalworkflow.egg-info/SOURCES.txt

 MANIFEST.in
-PKG-INFO
 README.txt
 ilrt.formalworkflow-configure.zcml
 setup.cfg
 setup.py
-dist/ilrt.formalworkflow-0.2-py2.4.egg
-dist/ilrt.formalworkflow-0.2.tar.gz
 docs/HISTORY.txt
 docs/INSTALL.txt
 docs/LICENSE.GPL
 docs/LICENSE.txt
+docs/TODO.txt
 docs/formalworkflow.png
 ilrt/__init__.py
 ilrt.formalworkflow.egg-info/PKG-INFO
 ilrt/formalworkflow/configure.zcml
 ilrt/formalworkflow/profiles.zcml
 ilrt/formalworkflow/setuphandlers.py
+ilrt/formalworkflow/skins.zcml
 ilrt/formalworkflow/version.txt
-ilrt/formalworkflow/browser/README.txt
 ilrt/formalworkflow/browser/__init__.py
+ilrt/formalworkflow/browser/cancel.pt
+ilrt/formalworkflow/browser/cancel.py
+ilrt/formalworkflow/browser/checkin.pt
+ilrt/formalworkflow/browser/checkin.py
+ilrt/formalworkflow/browser/checkout.pt
+ilrt/formalworkflow/browser/checkout.py
 ilrt/formalworkflow/browser/configure.zcml
-ilrt/formalworkflow/browser/interfaces.py
 ilrt/formalworkflow/profiles/default/actions.xml
 ilrt/formalworkflow/profiles/default/ilrt.formalworkflow_various.txt
 ilrt/formalworkflow/profiles/default/import_steps.xml
 ilrt/formalworkflow/profiles/default/skins.xml
 ilrt/formalworkflow/profiles/default/workflows.xml
 ilrt/formalworkflow/profiles/default/workflows/formal_workflow/definition.xml
+ilrt/formalworkflow/skins/formalworkflow/check_obj_permission.py
+ilrt/formalworkflow/skins/formalworkflow/delete_confirmation.cpy
+ilrt/formalworkflow/skins/formalworkflow/delete_confirmation.cpy.metadata
+ilrt/formalworkflow/skins/formalworkflow/folder_copy.cpy
+ilrt/formalworkflow/skins/formalworkflow/folder_copy.cpy.metadata
+ilrt/formalworkflow/skins/formalworkflow/folder_paste.cpy
+ilrt/formalworkflow/skins/formalworkflow/folder_paste.cpy.metadata
+ilrt/formalworkflow/skins/formalworkflow/object_copy.cpy
+ilrt/formalworkflow/skins/formalworkflow/object_copy.cpy.metadata
+ilrt/formalworkflow/skins/formalworkflow/object_delete.cpy
+ilrt/formalworkflow/skins/formalworkflow/object_delete.cpy.metadata
+ilrt/formalworkflow/skins/formalworkflow/object_paste.cpy
+ilrt/formalworkflow/skins/formalworkflow/object_paste.cpy.metadata
 ilrt/formalworkflow/tests/__init__.py
 ilrt/formalworkflow/tests/base.py
-ilrt/formalworkflow/tests/functional.txt
-ilrt/formalworkflow/tests/test_functional.py
+ilrt/formalworkflow/tests/editorpastedelete.txt
+ilrt/formalworkflow/tests/test_functional.py
+ilrt/formalworkflow/tests/workflowprocess.txt

File ilrt/formalworkflow/__init__.py

+
 
 def initialize(context):
     """Initializer called when used as a Zope 2 product."""

File ilrt/formalworkflow/browser/cancel.pt

+<html metal:use-macro="here/@@standard_macros/page" i18n:domain="plone">
+
+<div metal:fill-slot="body">
+    
+    <form action="#"
+        method="post"
+        tal:attributes="action string:${context/absolute_url}/@@cancel-changes">
+
+      <h1 class="documentFirstHeading"
+          i18n:translate="title_cancel">
+        Cancel changed version of <span i18n:name="object_title" tal:content="context/Title" />.
+      </h1>
+
+      <p i18n:translate="description_cancel">
+        Canceling the changes will delete this working copy, and any modifications
+        made to it will be lost. The existing version of the content will become
+        unlocked. 
+      </p>
+    
+      <div class="formControls">
+    
+        <input 
+            class="destructive"
+            type="submit"
+            name="form.button.Cancel"
+            value="Cancel changes"
+            i18n:attributes="value"
+            />
+            
+        <input 
+            class="standalone"
+            type="submit"
+            name="form.button.Keep"
+            value="Keep changed version"
+            i18n:attributes="value"
+            />
+
+      </div>
+
+    </form>
+</div>
+
+</html>

File ilrt/formalworkflow/browser/cancel.py

+##################################################################
+#
+# (C) Copyright 2006 ObjectRealms, LLC
+# All Rights Reserved
+#
+# Ed Crewe - modified to make cancel checkout based on whether
+# the user can delete the object and it is a checkout rather
+# than tying to checkin rights.
+#
+##################################################################
+
+from zope.component import getMultiAdapter
+
+from Acquisition import aq_inner
+from Products.Five.browser import BrowserView
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+
+from Products.statusmessages.interfaces import IStatusMessage
+from Products.CMFPlone import PloneMessageFactory as _
+
+from plone.app.iterate.interfaces import ICheckinCheckoutPolicy
+from plone.app.iterate.interfaces import CheckoutException
+
+from plone.memoize.view import memoize
+from AccessControl import getSecurityManager
+from Products.CMFCore.permissions import DeleteObjects
+
+class Cancel(BrowserView):
+
+    template = ViewPageTemplateFile('cancel.pt')
+    
+    def __call__(self):
+        context = aq_inner(self.context)
+        
+        if self.request.form.has_key('form.button.Cancel'):
+            control = getMultiAdapter((context, self.request), name=u"iterate_control")
+            if not self.cancel_allowed():
+                raise CheckoutException(u"Not a checkout")
+
+            policy = ICheckinCheckoutPolicy(context)
+            baseline = policy.cancelCheckout()
+            baseline.reindexObject()
+            
+            IStatusMessage(self.request).addStatusMessage(_(u"Checkout cancelled"), type='info')
+            view_url = baseline.restrictedTraverse("@@plone_context_state").view_url()
+            self.request.response.redirect(view_url)
+        elif self.request.form.has_key('form.button.Keep'):
+            view_url = context.restrictedTraverse("@@plone_context_state").view_url()
+            self.request.response.redirect(view_url)
+        else:
+            return self.template()
+
+    @memoize
+    def cancel_allowed(self):
+        """ Use instead of the iterate.control.cancel_allowed to cater for
+            users without checkin rights to be able to cancel their checkouts
+            if they can delete the object.
+        """
+        object = aq_inner(self.context)
+        checkPermission = getSecurityManager().checkPermission
+        if not checkPermission(DeleteObjects, object):
+            return False
+
+        if hasattr(object,'getRefs') and object.getRefs("Working Copy Relation"):
+            return True
+
+        return False

File ilrt/formalworkflow/browser/checkin.pt

+<html metal:use-macro="context/@@standard_macros/page" i18n:domain="plone">
+
+<div metal:fill-slot="body">
+
+    <form action="#"
+        method="post"
+        tal:attributes="action string:${context/absolute_url}/@@accept-changes">
+
+    <h1 class="documentFirstHeading"
+        i18n:translate="title_checkin">
+        Accept changes <span i18n:name="object_title" tal:content="here/Title" />
+    </h1>
+
+    <p i18n:translate="description_checkin">
+        Accept changes for this working copy will replace the existing item
+        with the working copy.
+    </p>
+    
+    <fieldset>
+
+            <div class="field">
+              <label for="description"
+                    i18n:translate="label_checkin_message">Accept Changes Message
+               </label>
+
+              <div class="formHelp" i18n:translate="help_checkin_message">
+                  Enter a message to be saved alongside the changes. This
+                  should explain what was changed, for audit purposes.
+              </div>
+              
+              <textarea cols="60"
+                        rows="3"
+                        id="checkin_message"
+                        name="checkin_message"></textarea>
+            </div>
+
+            <div class="formControls">
+                <input
+                    class="context"
+                    type="submit"
+                    name="form.button.Checkin"
+                    value="Accept changes"
+                    i18n:attributes="value"
+                    />
+                
+                <input
+                    class="standalone"
+                    type="submit"
+                    name="form.button.Cancel"
+                    value="Cancel"
+                    i18n:attributes="value label_cancel"
+                    />
+            </div>
+
+    </fieldset>
+
+    </form>
+
+</div>
+
+</html>

File ilrt/formalworkflow/browser/checkin.py

+from plone.app.iterate.browser.checkin import Checkin as BaseCheckin
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+
+class Checkin(BaseCheckin):
+    """ This seems to be the only way to override the template 
+        because its set in the base class and so the zcml template directive
+        is unusable
+    """
+    template = ViewPageTemplateFile('checkin.pt')
+
+

File ilrt/formalworkflow/browser/checkout.pt

+<html metal:use-macro="context/@@standard_macros/page" i18n:domain="plone">
+
+<div metal:fill-slot="body">
+
+    <form action="#"
+        method="post"
+        tal:attributes="action string:${context/absolute_url}/@@make-changes">
+
+    <h1 i18n:translate="title_checkout">
+        Make changes <span i18n:name="object_title" tal:content="here/Title" />
+    </h1>
+
+    <p i18n:translate="description_checkout">
+        On clicking 'Make changes', a working copy of the content item will be created in the selected
+        container, and the original will be locked to prevent other users from
+        editing it.
+    </p>
+    
+    <fieldset>
+        
+        <legend i18n:translate="legend_details">Details</legend>
+
+            <div class="field">
+
+            <label for="checkout_folder" i18n:translate="label_checkout_folder">
+                Location for changed version
+            </label>
+            <div class="formHelp" i18n:translate="help_checkout_folder">
+                Select the folder in which to create the working copy.
+            </div>
+         
+            <div tal:repeat="item view/containers">
+                
+                <input 
+                    type="radio" 
+                    name="checkout_location" 
+                    tal:attributes="id string:checkout_location_${item/name};
+                                    value item/name"
+                    />
+                    
+                <label
+                    tal:attributes="for string:checkout_location_${item/name}"
+                    tal:content="item/locator/title"
+                    />
+           
+           </div>
+           
+        </fieldset>
+
+        <div class="formControls">
+
+            <input
+                class="context"
+                type="submit"
+                name="form.button.Checkout"
+                value="Make changes"
+                i18n:attributes="value"
+                />
+
+            <input
+                class="standalone"
+                type="submit"
+                name="form.button.Cancel"
+                value="Cancel"
+                i18n:attributes="value label_cancel"
+                />
+                
+        </div>
+
+    </form>
+
+</div>
+
+</html>

File ilrt/formalworkflow/browser/checkout.py

+from plone.app.iterate.browser.checkout import Checkout as BaseCheckout
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+
+class Checkout(BaseCheckout):
+    """ This seems to be the only way to override the template 
+        because its set in the base class and so the zcml template directive
+        is unusable
+    """
+    template = ViewPageTemplateFile('checkout.pt')
+
+

File ilrt/formalworkflow/browser/configure.zcml

     xmlns="http://namespaces.zope.org/zope"
     xmlns:browser="http://namespaces.zope.org/browser"
     i18n_domain="ilrt.formalworkflow">
-
-    <!-- 'formalworkflow' Zope 3 browser layer 
-	 This layer is purely for modifying the interface permissions on app.iterate
-         So that iterations can be made of content that the user has copy but
-         not modify permissions for - e.g. published content
-    -->
-
-    <interface
-        interface=".interfaces.IThemeSpecific"
-        type="zope.publisher.interfaces.browser.IBrowserSkinType"
-        name="formalworkflow"
-        />
-    
+        
     <browser:page
         for="plone.app.iterate.interfaces.IIterateAware"
-        name="content-checkout"
-        class="plone.app.iterate.browser.checkout.Checkout"
-        permission="zope2.CopyOrMove"
-        layer=".interfaces.IThemeSpecific"
+        name="accept-changes"
+	class=".checkin.Checkin"
+        permission="cmf.ModifyPortalContent"
         />
 
     <browser:page
         for="plone.app.iterate.interfaces.IIterateAware"
-        name="content-cancel-checkout"
-        class="plone.app.iterate.browser.cancel.Cancel"
+        name="make-changes"
+        class=".checkout.Checkout"
         permission="zope2.CopyOrMove"
-        layer=".interfaces.IThemeSpecific"
         />
 
+    <browser:page
+        for="plone.app.iterate.interfaces.IIterateAware"
+        name="cancel-changes"
+        class=".cancel.Cancel"
+        permission="zope2.DeleteObjects"
+        />
 
 </configure>

File ilrt/formalworkflow/configure.zcml

 
    <five:registerPackage package="." initialize=".initialize" />
    <include package=".browser"/>
+   <include file="skins.zcml" />
    <include file="profiles.zcml" />
 
 </configure>

File ilrt/formalworkflow/profiles/default/actions.xml

  <object name="object_buttons" meta_type="CMF Action Category">
      
   <object name="iterate_checkout" meta_type="CMF Action" i18n:domain="plone">
-   <property name="title" i18n:translate="">Check out</property>
+   <property name="title" i18n:translate="">Make changes</property>
    <property name="description" i18n:translate=""></property>
-   <property name="url_expr">string:${object_url}/@@content-checkout</property>
+   <property name="url_expr">string:${object_url}/@@make-changes</property>
    <property name="icon_expr"></property>
    <property name="available_expr">python:path('object/@@iterate_control').checkout_allowed()</property>
    <property name="permissions">
-    <element value="Copy or Move"/>
+    <element value="iterate : Check out content"/>
    </property>
    <property name="visible">True</property>
   </object>
   
   <object name="iterate_checkout_cancel" meta_type="CMF Action" i18n:domain="plone">
-   <property name="title" i18n:translate="">Cancel check-out</property>
+   <property name="title" i18n:translate="">Cancel changes</property>
    <property name="description" i18n:translate=""></property>
-   <property name="url_expr">string:${object_url}/@@content-cancel-checkout</property>
+   <property name="url_expr">string:${object_url}/@@cancel-changes</property>
    <property name="icon_expr"></property>
-   <property name="available_expr">python:path('object/@@iterate_control').cancel_allowed()</property>
+   <property name="available_expr">python:hasattr(object,'getRefs') and object.getRefs("Working Copy Relation")</property>
    <property name="permissions">
-    <element value="Copy or Move"/>
+    <element value="Delete objects" />
    </property>
    <property name="visible">True</property>
   </object>
   
+  <object name="iterate_checkin" meta_type="CMF Action" i18n:domain="plone">
+   <property name="title" i18n:translate="">Accept changes</property>
+   <property name="description" i18n:translate=""></property>
+   <property name="url_expr">string:${object_url}/@@accept-changes</property>
+   <property name="icon_expr"></property>
+   <property name="available_expr">python:path('object/@@iterate_control').checkin_allowed()</property>
+   <property name="permissions">
+    <element value="iterate : Check in content"/>
+   </property>
+   <property name="visible">True</property>
+  </object>
+
+  <object name="delete" meta_type="CMF Action" i18n:domain="plone">
+   <property name="title" i18n:translate="">Delete</property>
+   <property name="description" i18n:translate=""></property>
+   <property name="url_expr">string:${globals_view/getCurrentObjectUrl}/delete_confirmation</property>
+   <property name="icon_expr"></property>
+   <property name="available_expr">python:checkPermission("Delete objects", object) and not globals_view.isPortalOrPortalDefaultPage()</property>
+   <property name="permissions">
+    <element value="Delete objects"/>
+   </property>
+   <property name="visible">True</property>
+  </object>
+
+  <object name="cut" meta_type="CMF Action" i18n:domain="plone">
+   <property name="title" i18n:translate="">Cut</property>
+   <property name="description" i18n:translate=""></property>
+   <property name="url_expr">string:${globals_view/getCurrentObjectUrl}/object_cut</property>
+   <property name="icon_expr"></property>
+   <property name="available_expr">python:checkPermission("Delete objects", object) and checkPermission("Copy or Move", object) and not globals_view.isPortalOrPortalDefaultPage()</property>
+   <property name="permissions">
+    <element value="Delete objects"/>
+   </property>
+   <property name="visible">True</property>
+  </object>
+
+  
  </object>
 </object>

File ilrt/formalworkflow/profiles/default/skins.xml

 <?xml version="1.0"?>
-<!-- This is just so that the security settings of this layer are applied
- see browser/README.txt to allow for using your preferred skin  -->
 
- <object name="portal_skins" allow_any="False" cookie_persistence="False"
+ <!-- This skin is purely to allow for editors to delete objects over which 
+      they have delete permissions - from which they are otherwise blocked by the containing
+      folder permission being different if it is published.
+      -->
+
+<object name="portal_skins" allow_any="False" cookie_persistence="False"
    default_skin="formalworkflow">
 
+ <object name="formalworkflow"
+    meta_type="Filesystem Directory View"
+    directory="ilrt.formalworkflow:skins/formalworkflow"/>
+
  <skin-path name="formalworkflow" based-on="Plone Default">
+  <layer name="formalworkflow"
+     insert-after="custom"/>
  </skin-path>
 
-
 </object>

File ilrt/formalworkflow/profiles/default/workflows/formal_workflow/definition.xml

  <permission>List folder contents</permission>
  <permission>Modify portal content</permission>
  <permission>View</permission>
+ <permission>Copy or Move</permission>
  <permission>iterate : Check in content</permission>
  <permission>iterate : Check out content</permission>
+
+ <state state_id="private" title="Private">
+  <description>Can be edited by other editors and managers. Seen by reviewers.</description>
+  <exit-transition transition_id="publish"/>
+  <exit-transition transition_id="submit"/>
+  <permission-map name="Access contents information" acquired="False">
+   <permission-role>Reviewer</permission-role>
+   <permission-role>Editor</permission-role>
+   <permission-role>Manager</permission-role>
+   <permission-role>Owner</permission-role>
+  </permission-map>
+  <permission-map name="Change portal events" acquired="False">
+   <permission-role>Manager</permission-role>
+   <permission-role>Editor</permission-role>
+   <permission-role>Owner</permission-role>
+   <permission-role>Reviewer</permission-role>
+  </permission-map>
+  <permission-map name="Delete objects" acquired="False">
+   <permission-role>Manager</permission-role>
+   <permission-role>Editor</permission-role>
+   <permission-role>Owner</permission-role>
+  </permission-map>
+  <permission-map name="List folder contents" acquired="False">
+   <permission-role>Manager</permission-role>
+   <permission-role>Editor</permission-role>
+   <permission-role>Owner</permission-role>
+   <permission-role>Reviewer</permission-role>
+  </permission-map>
+  <permission-map name="Modify portal content" acquired="False">
+   <permission-role>Manager</permission-role>
+   <permission-role>Editor</permission-role>
+   <permission-role>Owner</permission-role>
+  </permission-map>
+  <permission-map name="View" acquired="False">
+   <permission-role>Manager</permission-role>
+   <permission-role>Editor</permission-role>
+   <permission-role>Owner</permission-role>
+   <permission-role>Reviewer</permission-role>
+  </permission-map>
+  <permission-map name="Copy or Move" acquired="False">
+   <permission-role>Manager</permission-role>
+   <permission-role>Editor</permission-role>
+   <permission-role>Owner</permission-role>
+  </permission-map>
+  <permission-map name="iterate : Check in content" acquired="False">
+   <permission-role>Manager</permission-role>
+   <permission-role>Reviewer</permission-role>
+  </permission-map>
+  <permission-map name="iterate : Check out content" acquired="False">
+  </permission-map>
+ </state>
+
  <state state_id="pending" title="Pending review">
-  <description>Waiting to be reviewed, not editable by the owner.
-</description>
+  <description>Waiting to be reviewed, editable by reviewers and managers but not editors.</description>
   <exit-transition transition_id="make_private"/>
   <exit-transition transition_id="publish"/>
   <exit-transition transition_id="reject"/>
-  <permission-map name="Access contents information"
-                  acquired="False">
-   <permission-role>Contributor</permission-role>
+  <permission-map name="Access contents information" acquired="False">
+   <permission-role>Manager</permission-role>
    <permission-role>Editor</permission-role>
-   <permission-role>Manager</permission-role>
    <permission-role>Owner</permission-role>
-   <permission-role>Reader</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
-  <permission-map name="Change portal events"
-                  acquired="False">
+  <permission-map name="Change portal events" acquired="False">
    <permission-role>Manager</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
    <permission-role>Manager</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
-  <permission-map name="List folder contents"
-                  acquired="False">
-   <permission-role>Contributor</permission-role>
+  <permission-map name="List folder contents" acquired="False">
+   <permission-role>Manager</permission-role>
    <permission-role>Editor</permission-role>
-   <permission-role>Manager</permission-role>
    <permission-role>Owner</permission-role>
-   <permission-role>Reader</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
-  <permission-map name="Modify portal content"
-                  acquired="False">
+  <permission-map name="Modify portal content" acquired="False">
    <permission-role>Manager</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
   <permission-map name="View" acquired="False">
-   <permission-role>Contributor</permission-role>
+   <permission-role>Manager</permission-role>
    <permission-role>Editor</permission-role>
-   <permission-role>Manager</permission-role>
    <permission-role>Owner</permission-role>
-   <permission-role>Reader</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
-  <permission-map name="iterate : Check in content"
-                  acquired="False">
+  <permission-map name="Copy or Move" acquired="False">
    <permission-role>Manager</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
-  <permission-map name="iterate : Check out content"
-                  acquired="False">
+  <permission-map name="iterate : Check in content" acquired="False">
+   <permission-role>Manager</permission-role>
+   <permission-role>Reviewer</permission-role>
+  </permission-map>
+  <permission-map name="iterate : Check out content" acquired="False">
   </permission-map>
  </state>
- <state state_id="private" title="Private">
-  <description>Can only be seen and edited by the owner.
-</description>
-  <exit-transition transition_id="publish"/>
-  <exit-transition transition_id="submit"/>
-  <permission-map name="Access contents information"
-                  acquired="False">
-   <permission-role>Contributor</permission-role>
-   <permission-role>Editor</permission-role>
-   <permission-role>Manager</permission-role>
-   <permission-role>Owner</permission-role>
-   <permission-role>Reader</permission-role>
-  </permission-map>
-  <permission-map name="Change portal events"
-                  acquired="False">
-   <permission-role>Editor</permission-role>
-   <permission-role>Manager</permission-role>
-   <permission-role>Owner</permission-role>
-  </permission-map>
-  <permission-map name="Delete objects" acquired="True">
-  </permission-map>
-  <permission-map name="List folder contents"
-                  acquired="False">
-   <permission-role>Contributor</permission-role>
-   <permission-role>Editor</permission-role>
-   <permission-role>Manager</permission-role>
-   <permission-role>Owner</permission-role>
-   <permission-role>Reader</permission-role>
-  </permission-map>
-  <permission-map name="Modify portal content"
-                  acquired="False">
-   <permission-role>Editor</permission-role>
-   <permission-role>Manager</permission-role>
-   <permission-role>Owner</permission-role>
-  </permission-map>
-  <permission-map name="View" acquired="False">
-   <permission-role>Contributor</permission-role>
-   <permission-role>Editor</permission-role>
-   <permission-role>Manager</permission-role>
-   <permission-role>Owner</permission-role>
-   <permission-role>Reader</permission-role>
-  </permission-map>
-  <permission-map name="iterate : Check in content"
-                  acquired="False">
-   <permission-role>Manager</permission-role>
-  </permission-map>
-  <permission-map name="iterate : Check out content"
-                  acquired="False">
-  </permission-map>
- </state>
+
  <state state_id="published" title="Published">
-  <description>Visible to everyone, editable only by manager or reviewer
-</description>
+  <description>Visible to everyone, editable only by manager or reviewer. Editors can checkout</description>
   <exit-transition transition_id="retract"/>
-  <permission-map name="Access contents information"
-                  acquired="False">
+  <permission-map name="Access contents information" acquired="False">
    <permission-role>Anonymous</permission-role>
   </permission-map>
-  <permission-map name="Change portal events"
-                  acquired="False">
+  <permission-map name="Change portal events" acquired="False">
    <permission-role>Editor</permission-role>
    <permission-role>Manager</permission-role>
    <permission-role>Reviewer</permission-role>
    <permission-role>Manager</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
-  <permission-map name="List folder contents"
-                  acquired="False">
+  <permission-map name="List folder contents" acquired="False">
    <permission-role>Anonymous</permission-role>
   </permission-map>
-  <permission-map name="Modify portal content"
-                  acquired="False">
+  <permission-map name="Modify portal content" acquired="False">
    <permission-role>Manager</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
   <permission-map name="View" acquired="False">
    <permission-role>Anonymous</permission-role>
   </permission-map>
-  <permission-map name="iterate : Check in content"
-                  acquired="False">
+  <permission-map name="iterate : Check in content" acquired="False">
    <permission-role>Manager</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
-  <permission-map name="iterate : Check out content"
-                  acquired="False">
+  <permission-map name="iterate : Check out content" acquired="False">
    <permission-role>Editor</permission-role>
    <permission-role>Manager</permission-role>
    <permission-role>Reviewer</permission-role>
   </permission-map>
  </state>
+
  <transition transition_id="publish"
              title="Reviewer publishes content"
              new_state="published" trigger="USER"
              before_script="" after_script="">
-  <description>Publishing the item makes it visible to users of the site.
-</description>
+  <description>Publishing the item makes it visible to users of the site.</description>
   <action url="%(content_url)s/content_status_modify?workflow_action=publish"
           category="workflow">Publish</action>
   <guard>
    <guard-expression>python:not path('object/@@iterate_control').cancel_allowed()</guard-expression>
   </guard>
  </transition>
+
  <transition transition_id="reject"
              title="Content taken, or sent back for re-drafting"
              new_state="private" trigger="USER"
              before_script="" after_script="">
-  <description>Sending the item back will return the item to the original author and make it private, instead of publishing it. Reviewers should preferably include a reason for why it was not published.
-</description>
+  <description>Sending the item back will return the item to the original author and make it private, instead of publishing it. Reviewers should preferably include a reason for why it was not published.</description>
   <action url="%(content_url)s/content_status_modify?workflow_action=reject"
           category="workflow">Make private</action>
   <guard>
    <guard-permission>Request review</guard-permission>
   </guard>
  </transition>
+
  <transition transition_id="retract"
              title="Reviewer retracts submission"
              new_state="private" trigger="USER"
    <guard-permission>Review portal content</guard-permission>
   </guard>
  </transition>
+
  <transition transition_id="submit"
              title="Member submits content for publication"
              new_state="pending" trigger="USER"
              before_script="" after_script="">
-  <description>Puts your item in a review queue, so it can be published on the site.
-</description>
+  <description>Puts your item in a review queue, so it can be published on the site.</description>
   <action url="%(content_url)s/content_status_modify?workflow_action=submit"
           category="workflow">Submit for publication</action>
   <guard>
    <guard-permission>Request review</guard-permission>
   </guard>
  </transition>
+
  <worklist worklist_id="reviewer_queue" title="">
   <description>Reviewer tasks</description>
   <action url="%(portal_url)s/search?review_state=pending"
            for_status="True" update_always="True">
   <description>Previous transition</description>
   <default>
-   
    <expression>transition/getId|nothing</expression>
   </default>
   <guard>
            for_status="True" update_always="True">
   <description>The ID of the user who performed the previous transition</description>
   <default>
-   
    <expression>user/getUserName</expression>
   </default>
   <guard>
            for_status="True" update_always="True">
   <description>Comment about the last transition</description>
   <default>
-   
    <expression>python:state_change.kwargs.get('comment', '')</expression>
   </default>
   <guard>
            for_status="False" update_always="False">
   <description>Provides access to workflow history</description>
   <default>
-   
    <expression>state_change/getHistory</expression>
   </default>
   <guard>
            for_status="True" update_always="True">
   <description>When the previous transition was performed</description>
   <default>
-   
    <expression>state_change/getDateTime</expression>
   </default>
   <guard>

File ilrt/formalworkflow/setuphandlers.py

         return
 
     from Products.CMFCore.utils import getToolByName
-    qi_tool = getToolByName(context.getSite(), 'portal_quickinstaller')
+    portal = context.getSite()
+    qi_tool = getToolByName(portal, 'portal_quickinstaller')
     if not qi_tool.isProductInstalled('plone.app.iterate'):
         qi_tool.installProduct('plone.app.iterate', swallowExceptions=1)
 
         from Products.Five import zcml
         import ilrt.formalworkflow
         zcml.load_config('configure.zcml', ilrt.formalworkflow)
-        
-
+        # Now need to rerun the generic setup steps overridden by iterate
+        gsetup = getToolByName(portal, 'portal_setup')
+        gsetup.manage_importSelectedSteps(context_id='profile-ilrt.formalworkflow:formalworkflow',
+                                              ids=['actions','workflow'],
+                                              run_dependencies=True)
 
     
 

File ilrt/formalworkflow/skins.zcml

+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:cmf="http://namespaces.zope.org/cmf"
+    i18n_domain="ilrt.formalworkflow">
+
+   <cmf:registerDirectory name="skins" directory="skins" recursive="True" />
+
+</configure>

File ilrt/formalworkflow/skins/formalworkflow/check_obj_permission.py

+## Python Script "check_obj_permission"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind subpath=traverse_subpath
+##parameters=permission
+##title=Delete objects from a folder permission check
+##
+from Products.CMFCore.utils import getToolByName
+
+mt = getToolByName(context, 'portal_membership')
+if mt.checkPermission(permission, context):
+    return True
+return False
+

File ilrt/formalworkflow/skins/formalworkflow/delete_confirmation.cpy

+## Controlled Python Script "delete_confirmation"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=
+##title=Redirects to the regular vs link integrity confirmation page
+##
+from Products.CMFPlone.utils import isLinked
+from Products.CMFPlone.utils import safe_unicode
+from Products.CMFPlone.utils import transaction_note
+from Products.CMFPlone import PloneMessageFactory as _
+
+# WARNING: don't use the `isLinked` function in your code!!
+#   it is a helper for the link integrity code and will potentially abort
+#   the ongoing transaction, giving you unexpected results...
+if isLinked(context):
+    # go ahead with the removal, triggering link integrity...
+    # we need to copy the code from 'object_delete' here, since traversing
+    # there would yield a (disallowed) GET request without the intermediate
+    # confirmation page (see `object_delete.cpy`)
+    parent = context.aq_inner.aq_parent
+    title = safe_unicode(context.title_or_id())
+
+    try:
+        lock_info = context.restrictedTraverse('@@plone_lock_info')
+    except AttributeError:
+        lock_info = None
+    
+    if lock_info is not None and lock_info.is_locked():
+        message = _(u'${title} is locked and cannot be deleted.',
+            mapping={u'title' : title})
+    else:
+	# formalworkflow needs to check if the permission to delete exists on 
+    	# the object not its container
+
+	if not context.check_obj_permission('Delete objects'):
+            message = _(u'Sorry you do not have permission to delete ${title}',
+    	        mapping={u'title' : title})
+	else:
+            parent.manage_delObjects(context.getId())
+            message = _(u'${title} has been deleted.',
+                    mapping={u'title' : title})
+            transaction_note('Deleted %s' % context.absolute_url())
+
+    context.plone_utils.addPortalMessage(message)
+    status = 'success'
+else:
+    # navigate to the regular confirmation page...
+    status = 'confirm'
+
+return state.set(status=status)

File ilrt/formalworkflow/skins/formalworkflow/delete_confirmation.cpy.metadata

+[default]
+proxy=Manager
+
+[actions]
+action.success=redirect_to:python:object.aq_inner.aq_parent.absolute_url()
+action.confirm=traverse_to:string:delete_confirmation_page

File ilrt/formalworkflow/skins/formalworkflow/folder_copy.cpy

+## Controller Python Script "folder_copy"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=
+##title=Copy object from a folder to the clipboard
+##
+
+from OFS.CopySupport import CopyError
+from Products.CMFPlone.utils import transaction_note
+from Products.CMFPlone import PloneMessageFactory as _
+
+if not context.check_obj_permission(permission='Copy or Move'):
+    msg = _(u'Permission denied to copy ${title}.',
+            mapping={u'title' : title})
+    raise Unauthorized, msg
+
+REQUEST=context.REQUEST
+if REQUEST.has_key('paths'):
+    ids = [p.split('/')[-1] or p.split('/')[-2] for p in REQUEST['paths']]
+    
+    try:
+        context.manage_copyObjects(ids, REQUEST, REQUEST.RESPONSE)
+    except CopyError:
+        message = _(u'One or more items not copyable.')
+        context.plone_utils.addPortalMessage(message, 'error')
+        return state.set(status = 'failure')
+    except AttributeError:
+        message = _(u'One or more selected items is no longer available.')
+        context.plone_utils.addPortalMessage(message, 'error')
+        return state.set(status = 'failure')
+
+    transaction_note('Copied %s from %s' % (str(ids), context.absolute_url()))
+
+    message = _(u'${count} item(s) copied.', mapping={u'count' : len(ids)})
+
+    context.plone_utils.addPortalMessage(message)
+    return state
+
+context.plone_utils.addPortalMessage(_(u'Please select one or more items to copy.'), 'error')
+return state.set(status='failure')

File ilrt/formalworkflow/skins/formalworkflow/folder_copy.cpy.metadata

+[default]
+proxy=Manager
+
+[actions]
+action.success=redirect_to:request/orig_template|string:folder_contents
+action.success_no_edit=redirect_to:request/orig_template|string:folder_contents
+action.failure=redirect_to:request/orig_template|string:folder_contents

File ilrt/formalworkflow/skins/formalworkflow/folder_paste.cpy

+## Controller Python Script "folder_paste"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=
+##title=Paste objects into a folder
+##
+
+from Products.CMFPlone import PloneMessageFactory as _
+from AccessControl import Unauthorized
+from ZODB.POSException import ConflictError
+
+msg=_(u'Copy or cut one or more items to paste.')
+
+if not context.check_obj_permission(permission='Copy or Move'):
+    msg = _(u'Permission denied to copy ${title}.',
+            mapping={u'title' : title})
+    raise Unauthorized, msg
+
+if context.cb_dataValid:
+    try:
+        context.manage_pasteObjects(context.REQUEST['__cp'])
+        from Products.CMFPlone.utils import transaction_note
+        transaction_note('Pasted content to %s' % (context.absolute_url()))
+        context.plone_utils.addPortalMessage(_(u'Item(s) pasted.'))
+        return state
+    except ConflictError:
+        raise
+    except ValueError: 
+        msg=_(u'Disallowed to paste item(s).')
+    except (Unauthorized, 'Unauthorized'):
+        msg=_(u'Unauthorized to paste item(s).')
+    except: # fallback
+        msg=_(u'Paste could not find clipboard content.')
+
+context.plone_utils.addPortalMessage(msg, 'error')
+return state.set(status='failure')
+

File ilrt/formalworkflow/skins/formalworkflow/folder_paste.cpy.metadata

+[default]
+proxy=Manager
+
+[actions]
+action.success=redirect_to:request/orig_template|string:folder_contents
+action.success_no_edit=redirect_to:request/orig_template|string:folder_contents
+action.failure=redirect_to:request/orig_template|string:folder_contents

File ilrt/formalworkflow/skins/formalworkflow/object_copy.cpy

+## Controller Python Script "object_copy"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=
+##title=Copy object from a folder to the clipboard
+##
+
+from Products.CMFPlone.utils import transaction_note
+from Products.CMFCore.utils import getToolByName
+from Products.CMFPlone.utils import safe_unicode
+from Products.CMFPlone import PloneMessageFactory as _
+from OFS.CopySupport import CopyError
+from AccessControl import Unauthorized
+
+REQUEST = context.REQUEST
+title = safe_unicode(context.title_or_id())
+
+if not context.check_obj_permission(permission='Copy or Move'):
+    msg = _(u'Permission denied to copy ${title}.',
+            mapping={u'title' : title})
+    raise Unauthorized, msg
+
+parent = context.aq_inner.aq_parent
+try:
+    parent.manage_copyObjects(context.getId(), REQUEST)
+except CopyError:
+    message = _(u'${title} is not copyable.',
+                mapping={u'title' : title})
+    context.plone_utils.addPortalMessage(message, 'error')
+    return state.set(status = 'failure')
+
+message = _(u'${title} copied.',
+            mapping={u'title' : title})
+transaction_note('Copied object %s' % context.absolute_url())
+
+context.plone_utils.addPortalMessage(message)
+return state.set(status = 'success')

File ilrt/formalworkflow/skins/formalworkflow/object_copy.cpy.metadata

+[default]
+proxy=Manager
+title=Copy an object
+
+[actions]
+action.success=redirect_to:python:request.get('HTTP_REFERER')
+action.failure=redirect_to:python:request.get('HTTP_REFERER')

File ilrt/formalworkflow/skins/formalworkflow/object_delete.cpy

+## Controller Python Script "object_delete"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=
+##title=Delete objects from a folder
+##
+
+from AccessControl import Unauthorized
+from Products.CMFPlone.utils import safe_unicode
+from Products.CMFPlone.utils import transaction_note
+from Products.CMFPlone import PloneMessageFactory as _
+
+REQUEST = context.REQUEST
+if REQUEST.get('REQUEST_METHOD', 'GET').upper() == 'GET':
+    raise Unauthorized, 'This method can not be accessed using a GET request'
+
+parent = context.aq_inner.aq_parent
+title = safe_unicode(context.title_or_id())
+
+try:
+    lock_info = context.restrictedTraverse('@@plone_lock_info')
+except AttributeError:
+    lock_info = None
+
+if lock_info is not None and lock_info.is_locked():
+    message = _(u'${title} is locked and cannot be deleted.',
+            mapping={u'title' : title})
+    context.plone_utils.addPortalMessage(message, type='error')
+    return state.set(status = 'failure')
+else:
+    # formalworkflow needs to check if the permission to delete exists on 
+    # the object not its container so this proxy manager script calls a check one
+
+    #authenticator = context.restrictedTraverse('@@authenticator', None)
+    #if not authenticator.verify():
+    #    raise 'Forbidden'
+
+    if not context.check_obj_permission(permission='Delete objects'):
+       message = _(u'Sorry you do not have permission to delete ${title}',
+    	        mapping={u'title' : title})
+    else:
+        parent.manage_delObjects(context.getId())
+        message = _(u'${title} has been deleted.',
+                mapping={u'title' : title})
+        transaction_note('Deleted %s' % context.absolute_url())
+
+    context.plone_utils.addPortalMessage(message)
+    return state.set(status = 'success')

File ilrt/formalworkflow/skins/formalworkflow/object_delete.cpy.metadata

+[default]
+proxy=Manager
+title=Delete an object
+
+[actions]
+action.success=redirect_to:python:object.aq_inner.aq_parent.absolute_url()
+action.failure=redirect_to:object/absolute_url

File ilrt/formalworkflow/skins/formalworkflow/object_paste.cpy

+## Controller Python Script "object_paste"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=
+##title=Paste objects into the parent/this folder
+##
+
+from Products.CMFPlone.utils import transaction_note
+from Products.CMFPlone import PloneMessageFactory as _
+from AccessControl import Unauthorized
+from ZODB.POSException import ConflictError
+
+msg=_(u'Copy or cut one or more items to paste.')
+
+if not context.check_obj_permission(permission='Copy or Move'):
+    msg = _(u'Permission denied to copy ${title}.',
+            mapping={u'title' : title})
+    raise Unauthorized, msg
+
+if context.cb_dataValid:
+    try:
+        context.manage_pasteObjects(context.REQUEST['__cp'])        
+        transaction_note('Pasted content to %s' % (context.absolute_url()))
+        context.plone_utils.addPortalMessage(_(u'Item(s) pasted.'))
+        return state
+    except ConflictError:
+        raise
+    except ValueError:
+        msg=_(u'Disallowed to paste item(s).')
+    except (Unauthorized, 'Unauthorized'):
+        msg=_(u'Unauthorized to paste item(s).')
+    except: # fallback
+        msg=_(u'Paste could not find clipboard content.')
+
+context.plone_utils.addPortalMessage(msg, 'error')
+return state.set(status='failure')

File ilrt/formalworkflow/skins/formalworkflow/object_paste.cpy.metadata

+[default]
+proxy=Manager
+title=Paste an object into this folder
+
+[actions]
+action.success=redirect_to:python:request.get('HTTP_REFERER')
+action.failure=redirect_to:python:request.get('HTTP_REFERER')

File ilrt/formalworkflow/tests/base.py

 
 @onsetup
 def setup_products():
+    """ install ilrt.formalworkflow and any dodgy packages
+        that may break Plone if present but not installed
+    """
     fiveconfigure.debug_mode = True
     zcml.load_config('configure.zcml',
                      ilrt.formalworkflow)
     fiveconfigure.debug_mode = False    
     ztc.installPackage('ilrt.formalworkflow')
 
-
 class BaseTestCase(ptc.PloneTestCase):
     """Base class for test cases.
     """

File ilrt/formalworkflow/tests/editorpastedelete.txt

+======================================================
+Functional test that editor has item level permissions
+======================================================
+
+The default plone delete only checks permissions of the containing folder, 
+this is no good for this workflow, since editors need to be able to delete 
+private items in published folders. Whilst the published state will block
+them from tampering with the containing folder.
+
+Hence formalworkflow adds a skin level proxy manager workaround.
+
+by Ed Crewe, ILRT (University of Bristol) 
+March 2009
+
+    >>> from Products.Five.testbrowser import Browser
+    >>> from Products.PloneTestCase.setup import portal_owner, default_password
+    >>> from Products.CMFPlone.utils import getToolByName
+    >>> from mechanize._mechanize import LinkNotFoundError
+
+    >>> self.setRoles(['Manager'])
+
+Register a new demo editor user
+
+    >>> editor = "demoeditor"
+    >>> roles = ['Member','Editor','Contributor']
+    >>> uf = portal.acl_users
+    >>> if uf.getUserById(editor) is None:
+    ...     uf.userFolderAddUser(editor, default_password,roles,[]) 
+    >>> user = uf.getUserById(editor)
+    >>> user.getId()
+    'demoeditor'
+
+Start functional test for editor
+================================
+
+    >>> portal_url = portal.absolute_url()
+    >>> browser = Browser()
+
+Set this to false to see all errors
+
+    >>> browser.handleErrors = True
+
+
+Log in as the manager and create and a document
+===============================================
+
+Login as the manager
+
+    >>> browser.open(portal_url)
+    >>> browser.getControl(name='__ac_name').value = portal_owner
+    >>> browser.getControl(name='__ac_password').value = default_password
+    >>> browser.getControl(name='submit').click()
+    >>> "You are now logged in" in browser.contents
+    True
+
+Open the create document link
+
+    >>> browser.open(portal_url)
+    >>> browser.getLink(url=portal_url + '/createObject?type_name=Document').click()
+    >>> "/portal_factory/Document/document." in browser.contents
+    True
+
+Fill in the form with dummy content for the test page
+
+    >>> title  = 'boss'
+    >>> browser.getControl(name="title").value = title
+    >>> browser.getControl(name='text').value = "<h1>Test Bosses page</h1>\n\n"
+    >>> browser.getControl(name='form_submit').click()
+    >>> "Changes saved." in browser.contents
+    True
+    >>> title in browser.contents
+    True
+
+Publish the document
+
+    >>> doc_url = portal_url + '/' + title
+    >>> submit_url =  doc_url + '/content_status_modify?workflow_action=publish'
+    >>> browser.getLink(url=submit_url).click()
+    >>> browser.getLink(url=doc_url + '/content_status_history').click()
+    >>> browser.getControl(name='workflow_action').value
+    ['published']
+    >>> browser.getLink('Log out').click()
+
+Login as the demo editor user
+=============================
+
+We have the login portlet, so let's use that:
+
+    >>> browser.open(portal_url)
+    >>> browser.getControl(name='__ac_name').value = editor
+    >>> browser.getControl(name='__ac_password').value = default_password
+    >>> browser.getControl(name='submit').click()
+
+We check that we get the logged-in message:
+
+    >>> "You are now logged in" in browser.contents
+    True
+
+Now lets see if we can copy and paste the published document
+===========================================================
+
+    >>> browser.open(portal_url + '/' + title)
+    >>> browser.getLink('[IMG] Copy').click()
+    >>> browser.open(portal_url + '/@@folder_contents')
+    >>> browser.getLink('[IMG] Paste').click()
+    >>> 'Item(s) pasted.' in browser.contents
+    True
+    >>> copyid = 'copy_of_' + title
+    >>> copyid in portal.objectIds()
+    True
+    >>> browser.open(portal_url + '/' + copyid)
+    >>> 'class="state-private' in browser.contents
+    True
+
+Check delete for private content is OK (in published folders)
+=============================================================
+
+Check we can delete stuff in the private state so lets create a new item
+and click on the 'Add New ...' > 'Document' link via url to be language safe 
+
+    >>> browser.open(portal_url)
+    >>> browser.getLink(url=portal_url + '/createObject?type_name=Document').click()
+    >>> "/portal_factory/Document/document." in browser.contents
+    True
+
+Fill in the form with dummy content for the test page
+
+    >>> another_title  = 'quack'
+    >>> browser.getControl(name="title").value = another_title
+    >>> browser.getControl(name='text').value = "<h1>Test Delete page</h1>\n\n"
+    >>> browser.getControl(name='form_submit').click()
+    >>> "Changes saved." in browser.contents
+    True
+    >>> another_title in browser.contents
+    True
+
+Check we should have the rights to delete this page
+
+    >>> page = getattr(portal,another_title)
+    >>> from Products.Five.security import checkPermission
+    >>> {'selected': 'SELECTED', 'name': 'Editor'} in page.rolesOfPermission('Delete objects')
+    True
+    >>> 'Editor' in user.getRoles()
+    True
+
+OK then lets delete it
+
+   >>> another_title in portal.objectIds()
+   True
+   >>> browser.getLink(url=portal_url + '/' + another_title + '/delete_confirmation').click()
+   >>> form = browser.getForm(index=1)
+   >>> form.submit()
+   >>> another_title in portal.objectIds()
+   False
+
+

File ilrt/formalworkflow/tests/test_functional.py

     return unittest.TestSuite([
 
         ztc.ZopeDocFileSuite(
-            'tests/functional.txt', package='ilrt.formalworkflow',
+            'tests/workflowprocess.txt', package='ilrt.formalworkflow',
+            test_class=base.BaseFunctionalTestCase),
+
+        ztc.ZopeDocFileSuite(
+            'tests/editorpastedelete.txt', package='ilrt.formalworkflow',
             test_class=base.BaseFunctionalTestCase),
 
         ])

File ilrt/formalworkflow/tests/workflowprocess.txt

+===================================================================
+Functional test of the whole workflow ensuring that editors cannot
+modify published content but must use iterations instead
+===================================================================
+
+Functional tests for the workflow to confirm that it prevents
+users or owners who do not have reviewer rights from 
+re-editing public content that they have created.
+
+Instead they must use iterate and resubmit.
+
+by Ed Crewe, ILRT (University of Bristol) 
+January 2009
+
+    >>> from Products.Five.testbrowser import Browser
+    >>> from Products.PloneTestCase.setup import portal_owner, default_password
+    >>> from Products.CMFPlone.utils import getToolByName
+    >>> from mechanize._mechanize import LinkNotFoundError
+
+
+Site layer security setup 
+=========================
+
+Confirm we are in the skin that allows access to the iterate
+checkout for the Copy or Move permission ... rather than 
+modify portal content - so we can take an iteration of content 
+when we are not allowed to modify the (published) original 
+
+    >>> wftool = getToolByName(portal, "portal_workflow")
+    >>> wftool._default_chain
+    ('formal_workflow',)    
+    >>> wftool.getChainForPortalType('Document')
+    ('formal_workflow',)    
+
+Toggle off membership area creation to save going via the iterate location page
+
+    >>> membership = getToolByName(portal,'portal_membership')
+    >>> if membership.getMemberareaCreationFlag():
+    ...    membership.setMemberareaCreationFlag()
+
+Set up the editor and a folder
+==============================
+
+Make the implicit test user (portal_owner) a manager
+
+    >>> self.setRoles(['Manager'])
+    >>> portal_url = portal.absolute_url()
+
+Register a new demo editor user
+
+    >>> editor = "demoeditor"
+    >>> roles = ['Member','Editor','Contributor']
+    >>> uf = portal.acl_users
+    >>> if uf.getUserById(editor) is None:
+    ...     uf.userFolderAddUser(editor, default_password,roles,[]) 
+
+Check we have created the user
+
+    >>> user = uf.getUserById(editor)
+    >>> user.getId()
+    'demoeditor'
+
+Add a folder for the editor to work in ...
+